Zulip Chat Archive
Stream: mathlib4
Topic: Error when executing files with Mathlib import
Amos Nicodemus (Nov 23 2025 at 12:15):
I am sure whether this is the correct place to ask, so please correct me if I am wrong.
When I try to execute the following code in my lean project, I get the error "INTERNAL PANIC: unreachable code has been reached":
import Mathlib.Data.Finset.Defs
def main : IO Unit :=
IO.println "Hello world"
Everything works if I remove the line "import Mathlib.Data.Finset.Defs" or if I replace it by "import Mathlib.Init". I am working with lean version "v4.26.0-rc2". As far as I can tell this is a bug in Lean or Mathlib, is this correct?
Aaron Liu (Nov 23 2025 at 12:25):
doesn't seem to panic when I do this in the web editor
Amos Nicodemus (Nov 23 2025 at 13:03):
Is there a way to execute a file in the web editor? Because the problem is not building the file/project, or working with lean in the editor (this works fine), but executing it (using lake exe).
Kevin Buzzard (Nov 23 2025 at 13:11):
If you want to investigate further yourself then one way of course is to keep replacing a file with its imports, deleting imports when there is more than one etc etc
Kevin Buzzard (Nov 23 2025 at 13:12):
When you've found the offending file, cut and paste it in and then start deleting code from the file starting at the bottom and eventually you'll find your culprit
Amos Nicodemus (Nov 23 2025 at 13:31):
The problem seems to be with "Mathlib.Data.List.Defs", when I import it and try to execute the file, then it does not work, but when I import all its imports then it does work. When I copy paste the file (and remove the module and public declarations, as this otherwise gives error in the editor), then everything runs. So I'm not sure what is exactly wrong here...
Amos Nicodemus (Nov 23 2025 at 18:44):
Can someone maybe check whether it works for them or whether this is a general issue?
And could this be related to this issue?
Julian Berman (Nov 23 2025 at 20:01):
Did you try the second half of Kevin's suggestion? Once you have a file which causes this, which it sounds like you do, you can start to minimize what within it causes this and use that to create a minimal reproducer of the issue.
Amos Nicodemus (Nov 23 2025 at 20:06):
I pasted the whole file, and then I worked (when removing the public and module keywords). But when I imported the same file, it did not work.
Amos Nicodemus (Nov 23 2025 at 20:06):
So as far as I can tell it is not a specific definition/lemma alone which breaks it
Julian Berman (Nov 23 2025 at 20:27):
Here is a bit more minimizing, I created a project with just your main function and a single file which replicates the import you found, then deleted stuff from the end while preserving the error (so hopefully I haven't overminimized). I ended up with a file with:
module
public import Mathlib.Util.CompileInductive
namespace List
variable (p : α → Prop) [DecidablePred p] (l : List α)
/-- Given a decidable predicate `p` and a proof of existence of `a ∈ l` such that `p a`,
choose the first element with this property. This version returns both `a` and proofs
of `a ∈ l` and `p a`. -/
def chooseX : ∀ l : List α, ∀ _ : ∃ a, a ∈ l ∧ p a, { a // a ∈ l ∧ p a }
| [], hp => False.elim (Exists.elim hp fun _ h => not_mem_nil h.left)
| l :: ls, hp =>
if pl : p l then ⟨l, ⟨mem_cons.mpr <| Or.inl rfl, pl⟩⟩
else
-- pattern matching on `hx` too makes this not reducible!
let ⟨a, ha⟩ :=
chooseX ls
(hp.imp fun _ ⟨o, h₂⟩ => ⟨(mem_cons.mp o).resolve_left fun e => pl <| e ▸ h₂, h₂⟩)
⟨a, mem_cons.mpr <| Or.inr ha.1, ha.2⟩
Now you can keep going probably by inlining that one remaining import file and continuing to delete.
Julian Berman (Nov 23 2025 at 20:39):
I couldn't resist a bit more, so here, now there's two files (the above, and now the below) and this is free of Mathlib:
module
public meta import Lean.Elab.Command
public meta import Lean.Compiler.CSimpAttr
public meta section
open Lean Meta
private def replaceConst (repl : AssocList Name Name) (e : Expr) : Expr :=
e.replace fun | .const n us => repl.find? n |>.map (.const · us) | _ => none
/-- Returns the names of the recursors for a nested or mutual inductive,
using the `all` and `numMotives` arguments from `RecursorVal`. -/
def mkRecNames (all : List Name) (numMotives : Nat) : List Name :=
if numMotives ≤ all.length then
all.map mkRecName
else
let main := all[0]!
all.map mkRecName ++
(List.range (numMotives - all.length)).map (fun i => main.str s!"rec_{i+1}")
private def addAndCompile' (decl : Declaration) : CoreM Unit := do
try addAndCompile decl
catch e =>
match decl with
| .defnDecl val => throwError "while compiling {val.name}: {e.toMessageData}"
| .mutualDefnDecl val => throwError "while compiling {val.map (·.name)}: {e.toMessageData}"
| _ => unreachable!
/--
Compile the definition `dv` by adding a second definition `dv✝` with the same body,
and registering a `csimp`-lemma `dv = dv✝`.
-/
def compileDefn (dv : DefinitionVal) : MetaM Unit := do
if ((← getEnv).getModuleIdxFor? dv.name).isNone then
-- If it's in the same module then we can safely just call `compileDecl`
-- on the original definition
return ← compileDecl <| .defnDecl dv
let name ← mkFreshUserName dv.name
addAndCompile' <| .defnDecl { dv with name }
let levels := dv.levelParams.map .param
let old := .const dv.name levels
let new := .const name levels
let name ← mkFreshUserName <| dv.name.str "eq"
addDecl <| .thmDecl {
name
levelParams := dv.levelParams
type := ← mkEq old new
value := ← mkEqRefl old
}
Compiler.CSimp.add name .global
open Elab
/-- Returns true if the given declaration has a `@[csimp]` lemma. -/
def hasCSimpLemma (env : Environment) (n : Name) : Bool :=
(Compiler.CSimp.ext.getState env).map.contains n
/--
Generate compiled code for the recursor for `iv`, excluding the `sizeOf` function.
-/
def compileInductiveOnly (iv : InductiveVal) (rv : RecursorVal) (warn := true) : MetaM Unit := do
if ← isProp rv.type then
if warn then logWarning m!"not compiling {rv.name}"
return
let levels := rv.levelParams.map .param
let rvs ←
if rv.numMotives == 1 then pure [rv]
else mkRecNames iv.all rv.numMotives |>.mapM getConstInfoRec
let rvs ← rvs.mapM fun rv => return (rv, ← mkFreshUserName rv.name)
let repl := rvs.foldl (fun l (rv, name) => .cons rv.name name l) .nil
addAndCompile' <| .mutualDefnDecl <|← rvs.mapM fun (rv, name) => do
pure { rv with
name
value := ← forallTelescope rv.type fun xs body => do
let major := xs[rv.getMajorIdx]!
(← whnfD <| ← inferType major).withApp fun head args => do
let .const iv levels' := head | throwError "not an inductive"
let iv ← getConstInfoInduct iv
let rv' ← getConstInfoRec <| mkRecName iv.name
if !iv.isRec && rv'.numMotives == 1 && iv.numCtors == 1 && iv.numIndices == 0 then
let rule := rv.rules[0]!
let val := .beta (replaceConst repl rule.rhs) xs[:rv.getFirstIndexIdx]
let val := .beta val ⟨.map (major.proj iv.name) <| .range rule.nfields⟩
mkLambdaFVars xs val
else
let val := .const (mkCasesOnName iv.name) (.param rv.levelParams.head! :: levels')
let val := mkAppN val args[:rv'.numParams]
let val := .app val <| ← mkLambdaFVars xs[rv.getFirstIndexIdx:] body
let val := mkAppN val xs[rv.getFirstIndexIdx:]
let val := mkAppN val <| rv.rules.toArray.map fun rule =>
.beta (replaceConst repl rule.rhs) xs[:rv.getFirstIndexIdx]
mkLambdaFVars xs val
hints := .opaque
safety := .partial
}
for (rv, name) in rvs do
let old := .const rv.name levels
let new := .const name levels
let name ← mkFreshUserName <| rv.name.str "eq"
addDecl <| .mutualDefnDecl [{
name
levelParams := rv.levelParams
type := ← mkEq old new
value := .const name levels
hints := .opaque
safety := .partial
}]
Compiler.CSimp.add name .global
for name in iv.all do
for aux in [mkRecOnName name, (mkBRecOnName name).str "go", mkBRecOnName name] do
if let some (.defnInfo dv) := (← getEnv).find? aux then
compileDefn dv
mutual
/--
Generate compiled code for the recursor for `iv`.
-/
partial def compileInductive (iv : InductiveVal) (warn := true) : MetaM Unit := do
let rv ← getConstInfoRec <| mkRecName iv.name
if hasCSimpLemma (← getEnv) rv.name then
if warn then logWarning m!"already compiled {rv.name}"
return
compileInductiveOnly iv rv warn
compileSizeOf iv rv
/--
Compiles the `sizeOf` auxiliary functions. It also recursively compiles any inductives required to
compile the `sizeOf` definition (because `sizeOf` definitions depend on `T.rec`).
-/
partial def compileSizeOf (iv : InductiveVal) (rv : RecursorVal) : MetaM Unit := do
let go aux := do
if let some (.defnInfo dv) := (← getEnv).find? aux then
if !hasCSimpLemma (← getEnv) aux then
let deps : NameSet := dv.value.foldConsts ∅ fun c arr =>
if let .str name "_sizeOf_inst" := c then arr.insert name else arr
for i in deps do
-- We only want to recompile inductives defined in external modules, because attempting
-- to recompile `sizeOf` functions defined in the current module multiple times will lead
-- to errors. An entire mutual block of inductives is compiled when compiling any
-- inductive within it, so every inductive within the same module can be explicitly
-- compiled using `compile_inductive%` if necessary.
if ((← getEnv).getModuleIdxFor? i).isSome then
if let some (.inductInfo iv) := (← getEnv).find? i then
compileInductive iv (warn := false)
compileDefn dv
for name in iv.all do
for i in [:rv.numMotives] do
go <| name.str s!"_sizeOf_{i+1}"
go <| name.str "_sizeOf_inst"
end
/--
`compile_inductive% Foo` creates compiled code for the recursor `Foo.rec`,
so that `Foo.rec` can be used in a definition
without having to mark the definition as `noncomputable`.
-/
elab tk:"compile_inductive% " i:ident : command => Command.liftTermElabM do
let n ← realizeGlobalConstNoOverloadWithInfo i
let iv ← withRef i <| getConstInfoInduct n
withRef tk <| compileInductive iv
compile_inductive% False
Julian Berman (Nov 23 2025 at 20:40):
Possibly that still can be minimized a bit but if it's free of Mathlib you can essentially file a bug.
Julian Berman (Nov 23 2025 at 20:41):
(n.b. there's one visible unreachable! in that file -- but if I change it to throwError it doesn't seem that that's the one being hit.)
Malvin Gattinger (Nov 27 2025 at 11:12):
Fyi, the original problem seems to not happen with Lean and mathlib version v4.25.2.
Henrik Böving (Nov 28 2025 at 08:32):
This issue occurs due to two factors. The first is the module system that changed the behavior of the inliner for, so far, unknown reasons. However, the behavior of compile_inductive% on False has always been wrong to begin with and so far merely been fixed by the inliner by chance. First things first, it is unnecessary to even compile_inductive% False because False.rec is the single recursor that the compiler has native support for. The reason that calling compile_inductive% on it causes an issue, is that it introduces a new constant False.rec' = unreach. This constant used to be inlined but with the new module system does not get inlined for some reason. Because of this, the compiler decides to closed term lift it and we end up executing an unreachable during initialization. This behavior is a known limitation/bug of the current closed term extraction feature.
The correct fix would be for mathlib to either stop calling compile_inductive% on False as it is actively harming compiler optimizations and unnecessary or to at least place a @[no_extract] attribute on the False.rec' that compile_inductive% generates. This attribute informs the closed term extractor that it is not supposed to pull the constant out and is also placed on other always crashing symbols like panic! or unreachable!.
Ruben Van de Velde (Nov 28 2025 at 20:29):
Last updated: Dec 20 2025 at 21:32 UTC