Zulip Chat Archive
Stream: general
Topic: panic
Kevin Buzzard (Apr 26 2025 at 07:00):
In VS Code on current mathlib master (so lean4:v4.19.0-rc3
) if I click on "cases" on line 17 of the following code(which is a half-written line of code)
import Mathlib
open Cardinal
example : Module.finrank ℤ ℚ = 1 := by
rw [← Module.rank_eq_one_iff_finrank_eq_one]
suffices Module.rank ℤ ℚ ≤ 1 ∧ Module.rank ℤ ℚ ≠ 0 by
obtain ⟨h1, h0⟩ := this
generalize Module.rank ℤ ℚ = c at *
sorry -- cardinals
constructor
· apply rank_le (R := ℤ) (M := ℚ) (n := 1)
intro s hs
by_contra! h
rw [Finset.one_lt_card] at h
obtain ⟨a, ha, b, hb, h⟩ := h
cases
-- clear denominators
let D := a.den * b.den
sorry
· sorry
then I get a panic:
Lean server printed an error: PANIC at Lean.MetavarContext.getDecl Lean.MetavarContext:430:17: unknown metavariable backtrace:
Here's the full text as a github gist. No idea if this is important, made no attempt to minimise. Can others reproduce? I tried it on https://live.lean-lang.org/ and just got the expected error (basically "cases
by itself doesn't make sense")
Vasilii Nesterov (Apr 26 2025 at 08:48):
I minimized it to
import Mathlib
example : True := by
cases
let x := 5
sorry
The panic occurs when I hover over cases
. It doesn't panic without import Mathlib
.
Kevin Buzzard (Apr 26 2025 at 08:49):
Oh thanks! I'll minimise more while I'm waiting for FLT CI
Kevin Buzzard (Apr 26 2025 at 09:03):
Well mathlib's off the hook:
import Batteries.CodeAction
example : True := by
cases -- click here for panic
let x := 5
sorry
Kevin Buzzard (Apr 26 2025 at 09:03):
I don't really know my way around batteries so I'll leave it here
Kevin Buzzard (Apr 26 2025 at 09:05):
(what I mean by that is that something annoying happens at this point: I seem to need both of
import Batteries.CodeAction.Basic
import Batteries.CodeAction.Misc
example : True := by
cases -- click here for panic
let x := 5
sorry
to cause the panic -- deleting either import makes the panic go away, so I don't actually know what to do next)
Edward van de Meent (Apr 26 2025 at 10:49):
since it might be relevant: what version of the extension are you on?
Kevin Buzzard (Apr 26 2025 at 11:45):
0.0.204
Kevin Buzzard (Apr 26 2025 at 11:46):
and VS Code 1.96.0
. Can you not reproduce?
Julian Berman (Apr 26 2025 at 14:20):
I can in VSCode (though oddly it does not reproduce in lean.nvim
, so it does have something to do with the interaction between the editors and server and not just the server.)
Kevin Buzzard (Apr 27 2025 at 08:48):
Here's a bit more minimisation: it's the actual code which is probably causing the panic, i.e. if you delete one of these declarations (the 1st or the 4th) then the panic goes away.
import Lean.Elab.Tactic.Induction
import Batteries.Lean.Position
import Batteries.CodeAction.Attr
import Lean.Server.CodeActions.Provider
namespace Batteries.CodeAction
open Lean Meta Elab Server RequestM CodeAction
/-
## Overview
This file contains four declarations, the first copied from `Batteries.CodeAction.Basic`
and the second, third and fourth copied from `Batteries.CodeAction.Misc`. The second is needed to
compile the third, the third is needed to compile the 4th. The 1st and the 4th together cause
the panic; deleting either of them makes the panic not happen.
-/
-- stuff copied from `Batteries.CodeAction.Basic`
-- declaration 1: delete this and the panic goes away
/-- A code action which calls `@[tactic_code_action]` code actions. -/
@[code_action_provider] def tacticCodeActionProvider : CodeActionProvider := fun params snap => do
let doc ← readDoc
let startPos := doc.meta.text.lspPosToUtf8Pos params.range.start
let endPos := doc.meta.text.lspPosToUtf8Pos params.range.end
let pointerCol :=
if params.range.start.line == params.range.end.line then
max params.range.start.character params.range.end.character
else 0
let some result := findTactic?
(fun pos => (doc.meta.text.utf8PosToLspPos pos).character ≤ pointerCol)
⟨startPos, endPos⟩ snap.stx | return #[]
let tgtTac := match result with
| .tactic (tac :: _)
| .tacticSeq _ _ (_ :: tac :: _) => tac.1
| _ => unreachable!
let tgtRange := tgtTac.getRange?.get!
have info := findInfoTree? tgtTac.getKind tgtRange none snap.infoTree (canonicalOnly := true)
fun _ info => info matches .ofTacticInfo _
let some (ctx, node@(.node (.ofTacticInfo info) _)) := info | return #[]
let mut out := #[]
match result with
| .tactic stk@((tac, _) :: _) => do
let ctx := { ctx with mctx := info.mctxBefore }
let actions := (tacticCodeActionExt.getState snap.env).2
if let some arr := actions.onTactic.find? tac.getKind then
for act in arr do
try out := out ++ (← act params snap ctx stk node) catch _ => pure ()
for act in actions.onAnyTactic do
try out := out ++ (← act params snap ctx stk node) catch _ => pure ()
| .tacticSeq _ i stk@((seq, _) :: _) =>
let (ctx, goals) ← if 2*i < seq.getNumArgs then
let stx := seq[2*i]
let some stxRange := stx.getRange? | return #[]
let some (ctx, .node (.ofTacticInfo info') _) :=
findInfoTree? stx.getKind stxRange ctx node fun _ info => (info matches .ofTacticInfo _)
| return #[]
pure ({ ctx with mctx := info'.mctxBefore }, info'.goalsBefore)
else
pure ({ ctx with mctx := info.mctxAfter }, info.goalsAfter)
for act in (tacticSeqCodeActionExt.getState snap.env).2 do
try out := out ++ (← act params snap ctx i stk goals) catch _ => pure ()
| _ => unreachable!
pure out
-- end of stuff copied from `Batteries.CodeAction.Basic`
-- copied from `Batteries.CodeAction.Misc`
-- declaration 2: needed for declaration 3
/--
Similar to `getElimExprInfo`, but returns the names of binders instead of just the numbers;
intended for code actions which need to name the binders.
-/
def getElimExprNames (elimType : Expr) : MetaM (Array (Name × Array Name)) := do
-- let inductVal ← getConstInfoInduct inductName
-- let decl ← getConstInfo declName
forallTelescopeReducing elimType fun xs type => do
let motive := type.getAppFn
let targets := type.getAppArgs
let motiveType ← inferType motive
let mut altsInfo := #[]
for _h : i in [:xs.size] do
let x := xs[i]
if x != motive && !targets.contains x then
let xDecl ← x.fvarId!.getDecl
if xDecl.binderInfo.isExplicit then
let args ← forallTelescopeReducing xDecl.type fun args _ => do
let lctx ← getLCtx
pure <| args.filterMap fun y =>
let yDecl := (lctx.find? y.fvarId!).get!
if yDecl.binderInfo.isExplicit then some yDecl.userName else none
altsInfo := altsInfo.push (xDecl.userName, args)
pure altsInfo
-- declaration 3: needed for declaration 4
/-- Finds the `TermInfo` for an elaborated term `stx`. -/
def findTermInfo? (node : InfoTree) (stx : Term) : Option TermInfo :=
match node.findInfo? fun
| .ofTermInfo i => i.stx.getKind == stx.raw.getKind && i.stx.getRange? == stx.raw.getRange?
| _ => false
with
| some (.ofTermInfo info) => pure info
| _ => none
-- declaration 4: delete this and the panic goes away
/--
Invoking tactic code action "Generate an explicit pattern match for 'induction'" in the
following:
````lean
example (x : Nat) : x = x := by
induction x
````
produces:
````lean
example (x : Nat) : x = x := by
induction x with
| zero => sorry
| succ n ih => sorry
````
It also works for `cases`.
-/
@[tactic_code_action Parser.Tactic.cases Parser.Tactic.induction]
def casesExpand : TacticCodeAction := fun _ snap ctx _ node => do
let .node (.ofTacticInfo info) _ := node | return #[]
let (targets, induction, using_, alts) ← match info.stx with
| `(tactic| cases $[$[$_ :]? $targets],* $[using $u]? $(alts)?) =>
pure (targets, false, u, alts)
| `(tactic| induction $[$[$_ :]? $targets],* $[using $u]? $[generalizing $_*]? $(alts)?) =>
pure (targets, true, u, alts)
| _ => return #[]
let some discrInfos := targets.mapM (findTermInfo? node) | return #[]
let some discr₀ := discrInfos[0]? | return #[]
let mut some ctors ← discr₀.runMetaM ctx do
let targets := discrInfos.map (·.expr)
match using_ with
| none =>
if Tactic.tactic.customEliminators.get (← getOptions) then
if let some elimName ← getCustomEliminator? targets induction then
return some (← getElimExprNames (← getConstInfo elimName).type)
matchConstInduct (← whnf (← inferType discr₀.expr)).getAppFn
(fun _ => failure) fun val _ => do
let elimName := if induction then mkRecName val.name else mkCasesOnName val.name
return some (← getElimExprNames (← getConstInfo elimName).type)
| some u =>
let some info := findTermInfo? node u | return none
return some (← getElimExprNames (← inferType info.expr))
| return #[]
let mut fallback := none
if let some alts := alts then
if let `(Parser.Tactic.inductionAlts| with $(_)? $alts*) := alts then
for alt in alts do
match alt with
| `(Parser.Tactic.inductionAlt| | _ $_* => $fb) => fallback := fb.raw.getRange?
| `(Parser.Tactic.inductionAlt| | $id:ident $_* => $_) =>
ctors := ctors.filter (fun x => x.1 != id.getId)
| _ => pure ()
if ctors.isEmpty then return #[]
let tacName := info.stx.getKind.updatePrefix .anonymous
let eager := {
title := s!"Generate an explicit pattern match for '{tacName}'."
kind? := "quickfix"
}
let doc ← readDoc
pure #[{
eager
lazy? := some do
let tacPos := info.stx.getPos?.get!
let endPos := doc.meta.text.utf8PosToLspPos info.stx.getTailPos?.get!
let indent := "\n".pushn ' ' (findIndentAndIsStart doc.meta.text.source tacPos).1
let (startPos, str') := if alts.isSome then
let stx' := if fallback.isSome then
info.stx.modifyArg (if induction then 4 else 3)
(·.modifyArg 0 (·.modifyArg 2 (·.modifyArgs (·.filter fun s =>
!(s matches `(Parser.Tactic.inductionAlt| | _ $_* => $_))))))
else info.stx
(doc.meta.text.utf8PosToLspPos stx'.getTailPos?.get!, "")
else (endPos, " with")
let fallback := if let some ⟨startPos, endPos⟩ := fallback then
doc.meta.text.source.extract startPos endPos
else
"sorry"
let newText := Id.run do
let mut str := str'
for (name, args) in ctors do
let mut ctor := toString name
if let some _ := (Parser.getTokenTable snap.env).find? ctor then
ctor := s!"{idBeginEscape}{ctor}{idEndEscape}"
str := str ++ indent ++ s!"| {ctor}"
-- replace n_ih with just ih if there is only one
let args := if induction &&
args.foldl (fun c n =>
if n.eraseMacroScopes.getString!.endsWith "_ih" then c+1 else c) 0 == 1
then
args.map (fun n => if !n.hasMacroScopes && n.getString!.endsWith "_ih" then `ih else n)
else args
for arg in args do
str := str ++ if arg.hasNum || arg.isInternal then " _" else s!" {arg}"
str := str ++ s!" => " ++ fallback
str
pure { eager with
edit? := some <|.ofTextEdit doc.versionedIdentifier {
range := ⟨startPos, endPos⟩
newText
}
}
}]
-- end of stuff copied from `Batteries.CodeAction.Misc`
end Batteries.CodeAction
example : True := by
cases -- click here for panic
let x := 5
sorry
Marc Huisinga (Apr 28 2025 at 11:29):
The getCustomEliminator?
call in the casesExpand
code action appears to be causing the panic (and in there, inferType
), but I haven't investigated it further than that
Last updated: May 02 2025 at 03:31 UTC