Zulip Chat Archive
Stream: lean4
Topic: Panic while typing eval'd `fun`
Thomas Murrills (Dec 16 2025 at 22:24):
I have a temporary convenience command local_linter which allows me to declare a linter locally via e.g. local_linter foo := fun stx => do ... which will run on the file. However, in the middle of typing, at local_linter foo := fun, Lean panics. Here's why:
Although we have a parse error at this location, lean still attempts to elaborate fun, and this yields a sorry in the resulting nomatch from this MVarId.admit during match elaboration (despite withoutErrToSorry being present, since the elaboration happens in MetaM). Then, my subsequent evalExpr panics on the sorry when trying evaluate it.
I think there are three things to examine here:
- Should elaboration proceed even though we have a parse error here? In general it makes sense to elaborate as much as possible, but I'm not sure about elaborating a lone
funin these circumstances specifically. - Should match elaboration insert a
sorryhere? I would have maybe expected an error, and a sorry up inTermElabMwhenerrToSorryistrue; though perhaps that would mean throwing an error for the entire match (which we don't want)? - Should
evalExpr(or something it calls) be robustified againstsorry? It's easy enough tocollectAxiomsmyself and check forsorrylike#evaldoes, but I wonder ifevalExprshould do this too.
All in all this is not so much a "how do I work around this" thread, but a "should we change these behaviors that it brought to light" thread. :)
And here's an #mwe:
module
public import Lean
open Lean Meta Elab Term Command
public section
elab "#huh " t:term : command => liftTermElabM do
let e ← withoutErrToSorry <| withSynthesize <| elabTerm t (Expr.const ``Bool [])
let axioms ← (e.getUsedConstants).mapM collectAxioms
let hasSorry := axioms.any (·.contains ``sorryAx)
-- note: logInfo's message is obliterated in this circumstance, so trace instead
trace[debug] "{e} has sorry := {hasSorry}"
set_option trace.debug true
/-- trace: [debug] nomatch has sorry := true -/
#guard_msgs in
#huh fun -- unexpected end of input; expected '[', '{', '|' or '⦃'
Kyle Miller (Dec 17 2025 at 00:35):
- If you don't want to do anything when there is partial syntax, you can abort elaboration if you see that
thas amissingnode. You can assumemissingmeans a parse error was emitted. - The
#evalcommand usescollectAxiomsfor the stronger check that there is nosorryat all. To detect elaborator errors, it suffices to check for a synthetic sorry in the term. I'm not sure whywithoutErrToSorrydoesn't suffice here. Possibly it's not hitting an elaboration error, despite thesorryinside the term. Or possibly there's some interaction withmissingI'm not sure about. Or there's something aboutmatchthat's not respecting it. - This
collectAxiomscheck is making sure you're not getting away with usingsorryfor empty terms (e.g. false propositions). There's nothing inherently wrong with havingsorryin a term. If you try#eval! (sorry : Nat)you can see there's an error despite turning off the the sorry check, not a panic.
Thomas Murrills (Dec 17 2025 at 01:15):
Thanks! :)
- Good to know; though I was wondering more about whether the current behavior makes sense, than about what I could do! I.e. I'm skeptical it makes sense for elaboration of a lone
funitself to proceed when those.missingnodes are present. - a. re: synthetic sorry checking: makes sense; though, do you happen to know if there's API for that? Note that this sorry is hidden in the value of an autogenerated
match_ndecl, sohasSyntheticSorryreturns false. (collectAxiomstherefore seems more convenient than e.g.ImportGraph'stransitivelyUsedConstants.) - b. Does something during term elaboration check for synthetic sorries, and error if it finds them? I thought that because no error is logged (by
mkLabeledSorry,MVarId.admit, ormatchwhen admitting),withoutErrToSorryis not relevant in the first place—the sorry is directly constructed. (Perhaps one of these should log an error;mkLabeledSorrysays it's the caller's responsibility.) - Ah, right, thanks for pointing that out. The "unreachable code" panic (which now that I look at it closely, might not be a
panic!per se?) doesn't come fromevalExpr; it seems like it comes from actually trying to run the resultingCommandElabon syntax (whether in theelabimmediately or through thelintersRefas intended).
Thomas Murrills (Dec 17 2025 at 02:11):
(Here’s an updated panicking mwe for completeness!)
module
public import Lean
public import Qq
open Lean Meta Elab Term Command Qq
public section
abbrev BoolToBool := Bool → Bool
elab "#huh " t:term : command => unsafe liftTermElabM do
let e ← withoutErrToSorry <| withSynthesize <| elabTerm t q(Bool → Bool)
let axioms ← (e.getUsedConstants).mapM collectAxioms
let hasSorry := axioms.any (·.contains ``sorryAx)
-- note: logInfo's message is obliterated in this circumstance, so trace instead
trace[debug] "{e} has sorry := {hasSorry}"
let e ← evalExpr (Bool → Bool) q(Bool → Bool) e
logInfo m!"{e true}"
set_option trace.debug true
/-- trace: [debug] nomatch has sorry := true -/
#guard_msgs in
#huh fun -- panic; unexpected end of input; expected '[', '{', '|' or '⦃'
Kyle Miller (Dec 17 2025 at 04:51):
- I don't mean this as a workaround, but rather it's intended that elaboration proceed even if there are missing nodes. This is how term info (hovers) for partial terms works.
- a. Yes,
Expr.hasSyntheticSorry. However, if there's already been a parse error, it's unclear to me whether you can rely on the presence of a synthetic sorry. b. There's already a parse error logged, so further errors aren't shown. Log messages also have some interface for reporting whether they contain an expression containing synthetic sorries, which is used somehow for further error suppression. - It might be worth trying to figure out where the panic is coming from more precisely. Maybe try
set_option showPartialSyntaxErrors true(to disable the.missingelaboration error suppression) or otherwise create the partialSyntaxyourself.
Thomas Murrills (Dec 17 2025 at 21:42):
- Thanks for clarifying; that makes sense, but perhaps we are trying to make different points :) For my part, I believe that the core elaboration for
funormatchspecifically could be polished a little when.missingnodes are present; the hover forfunbecomesnomatchin this case, which is unhelpful. Perhaps a more deliberatesorryof function type might be ideal—but this is polishing. :) - a. As I mentioned, though,
hasSyntheticSorryis insufficient here; though there is indeed a sorry (and it is synthetic), it's locked behind an autogeneratedmatchdecl, andhasSyntheticSorrydoes not unfold constants likecollectAxiomsdoes (and thus misses that sorry). The hypothetical API I'm asking about would therefore unfold constants; I suspect it doesn't exist. - b. Hmm, interesting!
- I don't believe the panic has to do with syntax directly (though is of course downstream of the sorry produced by the missing syntax); rather, a declaration shaped like the autogenerated
match(withsorry : Falsein the given position) is sufficient.eval!errors with "'unreachable' code was reached" but does not panic;let _ := r truein theelabdoes not panic; butIO.println <| r truepanics. So perhaps this is a clue as to what is actually reaching the unreachable code—the language server? But I'm not sufficiently familiar with the structure there to diagnose it precisely.
syntax-free mwe
Last updated: Dec 20 2025 at 21:32 UTC