These release notes describe a release candidate, not the final release. They may be incomplete and are subject to change.
Lean 4.33.0-rc2 (2026-08-03)
For this release, 208 changes landed. In addition to the 53 feature additions, and 50 fixes listed below, there were 12 refactoring changes, 11 documentation improvements, 21 performance improvements, 6 improvements to the test suite, and 55 other changes.
Highlights
Lean 4.33.0 concentrates on responsiveness and consolidation: the editor keeps more of your work while you type, try? can propose proofs on its own, lia and grind tactics are improved, and Float stops being an opaque type. Continuing the transparency work of v4.31.0, it also enables backward.isDefEq.respectTransparency.types by default — the change most likely to need attention when porting.
This highlights section was contributed by Juanjo Madrigal.
A More Responsive Editor
Several independent changes make interactive editing noticeably smoother:
-
#11958 stops the elaborator from rerunning a tactic when only the whitespace following it changes. Pressing return after a tactic to prepare the next line no longer discards the progress made by everything after it.
-
#13712 makes
exact?,apply?,rw?, andgrind +localsstop waiting for earlier theorems in the same file to finish kernel-checking. In an editor session this used to show up astry?andexact?appearing to hang near the top of long files. -
#14234 makes completion, hovers, and the interactive term goal see the open namespaces and options of a term-level
open … inorset_option … inscope, rather than those of the enclosing command. -
#14296 restores go-to-definition and find-references on a
let mutvariable that is referenced after aforloop.
Diagnostics became more actionable as well. The unusedVariables linter now offers the underscore rename as an applicable hint (#14259):
def constantly (n : Nat) : Nat := 0
And a new linter warns about an open that does not in fact open every namespace ending in the given name (#14325): inside namespace A, an open B stops reaching _root_.B as soon as an upstream A.B appears, which explains the otherwise puzzling unknown identifier errors that follow. Finally, #14196 clarifies the warnings around reducibility attributes.
Automatic try? Suggestions
#13830 lets try? run on its own where a proof is missing, gated by three options that default to off:
-
autoTry.onEmptyProof— an emptyby, an empty·, an emptycase h =>, and so on. -
autoTry.onUnsolvedGoal— like the above, but also fires on proofs that already contain tactics and left a goal open; there the suggestion is appended to what is already written. -
autoTry.onSorry— asorry, which the suggestion replaces.
set_option autoTry.onEmptyProof true in
example (a b : Nat) : a + b = b + a := by
Tactic Improvements
lia ran with E-matching disabled, so it could not see definitional lemmas. #14098 gives it its own @[lia] set — far smaller than @[grind], which stays disabled — and #14107 tags the min/max definitions, closing the most common case where omega could not simply be replaced by lia:
example (a b : Nat) : min a b ≤ max a b := a:Natb:Nat⊢ min a b ≤ max a b All goals completed! 🐙
example (a b : Int) : max a b = max b a := a:Intb:Int⊢ max a b = max b a All goals completed! 🐙
grind gains propagators that evaluate BitVec operations on literals, including through equalities recorded in the e-graph (#14393):
example {x : BitVec 64} (h : x = 0#64 + 42#64) :
BitVec.extractLsb' 63 32 x = 0#32 := x:BitVec 64h:x = 0#64 + 42#64⊢ BitVec.extractLsb' 63 32 x = 0#32 All goals completed! 🐙
It also collects a batch of correctness fixes:
-
bitvector literals that were not normalized to the form
grindexpects could be treated as two distinct values, in one case yielding a proof the kernel rejects (#14371 / #14370 / #14379); -
constraints of the form
0 ∣ pcould send the search into a loop (#14373); -
the ring solver could lose information in rings without
NoNatZeroDivisors(#14390); -
SymMterm invariants that user simprocs could silently break are now detected and repaired (#14299).
A new liaSteps option bounds the search on hard linear integer arithmetic (#14392). Finally, the e-matching annotations on container operations were retuned to connect two theories only when both are already in the e-graph, instead of one dragging in the other. More information in #14177 / #14194 / #14192 / #14182 / #14178.
Float Is No Longer Opaque
Float and Float32 were opaque types with no logical content. #14079 adds Float.Model and Float32.Model, validated against the native implementation on cases from Berkeley TestFloat, and #14091 redefines the types to wrap them, with arithmetic, comparison, and conversion delegating to the model. Compiled code is unaffected. This is deliberately not a full floating-point library — the point is to let a downstream one connect to Float so its theorems transport.
Two consequences are visible right away. #14180 adds a DecidableEq instance that compares bit patterns, which is not the IEEE 754 relation == implements. And #14110 rewrites Float.ofScientific so that it rounds correctly — it passes over five million tests of the parse-number-fxx-test-data suite, at the cost of a much slower fallback path — and now reduces in the kernel:
def nan : Float := 0.0 / 0.0
/-- info: false -/
#guard_msgs in
#eval nan == nan
example : nan = nan := ⊢ nan = nan All goals completed! 🐙
example : (0.0 : Float) ≠ -0.0 := ⊢ 0.0 ≠ -0.0 All goals completed! 🐙
example : 0.1 + 0.2 != 0.3 := rfl
That instance also backs float literals as match patterns (#14181), so 0.0 and -0.0 select different branches:
def describe (x : Float) : String :=
match x with
| 0.0 => "zero" | -0.0 => "negative zero" | _ => "other"
/-- info: "negative zero" -/
#guard_msgs in
#eval describe (-0.0)
Lake
#14235 makes module archives (.ltar) content-stable: byte-identical module outputs now produce a byte-identical archive whatever the inputs, checkout path, or build machine, so an input-only change uploads no new bytes and identical outputs deduplicate across revisions on cache services. #13646 adds a requiresModuleSystem package option, warning when a file without a module header imports the package; allowNonModules opts out.
Two fixes retire a class of compiled configuration is invalid; run with '-R' to reconfigure failures: #14284 makes an interrupted configuration leave a valid trace behind, and #14285 reconfigures when the trace cannot be read at all. There are also new module facets for dependency and link information (#14300 / #14254), and lake new/lake init with the exe template no longer emits library files (#14366).
Kernel soundness fixes and further improvements
This release fixes bugs in the Lean kernel and improves its robustness. Some of the soundness bugs can only be exploited from a malicious meta program running in the same process (which is already known to be unsafe). Others survive the export format and affect proof checking via comparator too, unless external checkers like nanoda are used as well.
-
PR #14498 guards against free variables in opaque values. Soundness bug, but not affecting users of
comparator. -
PR #14577 typechecks arguments to phantom parameters of nesting inductives. Soundness bug, affecting users of
comparator. -
PR #14607 adds more checking against free variables. Possible soundness issue, not affecting
comparator. -
PR #14608 checks level parameters uniformity in recursive definitions. Not a known soundness issue, as it only affects declarations marked as
partialorunsafe. -
PR #14609 fixes a soundness issue in the module system. Does not affect users of
comparator. -
PR #14613 recognize level expressions that may normalize to
Prop. Soundness bug, affecting users ofcomparator. -
PR #14615 adds more level normalization to the handling of inductives. Not a soundness bug.
-
PR #14616 rejects names using
_nestedin the names, to prevent clashes with the kernel’s internal construction for nested inductives. Soundness bug, but not affecting users ofcomparator. -
PR #14621 adds more hardening to the handling of nested inductives.
-
PR #14631 checks the name field of a projection expression when comparing them. This hardens the kernel.
-
PR #14632 hardens the kernel by checking more invariants explicitly.
-
PR #14633 hardens the kernel by checking types of local context declarations sooner.
Also see the Postmortem for Kernel Soundness Bug #14576.
Breaking Changes
Transparency
#13895 enables backward.isDefEq.respectTransparency.types by default: a metavariable assigned at reducible, instances, or implicit transparency now has its type compared with its value's type at implicit rather than default transparency, and many existing declarations are marked implicit-reducible to compensate. The payoff is more control over what unfolds, and better scaling on large projects.
The symptom of a breakage is a lemma that simp, grind, or another tactic stops applying, because an argument's type is not definitionally equal to the expected one at implicit transparency.
Migration:
-
set_option backward.isDefEq.respectTransparency.types falserestores the old behavior. Scope it as narrowly as possible. -
The durable fix is to work out why the lemma statement or the goal is not well-typed at implicit transparency and address that, or to mark the definitions involved
@[implicit_reducible]. -
To diagnose, reach first for
set_option linter.tacticCheckInstances true, thentrace.Meta.isDefEq,trace.Meta.isDefEq.printTransparency, andtrace.Meta.Tactic.simp. Runningsimp?on the old toolchain shows which lemmas should fire. -
For auto-generated lemmas whose statements are
simp-normalized (in Mathlib, those from@[simps]and@[reassoc]), the fix usually belongs where the lemma is generated, not where the error appears.
Relatedly, #13637 splits the old instances transparency in two, giving none < reducible < instances < implicit < default < all. @[implicit_reducible] no longer carries the side effects of @[instance_reducible], such as letting type class search see through the declaration; use @[instance_reducible] for that. A with_implicit tactic joins with_reducible_and_instances.
Other Breaking Changes
-
#13956 bounds kernel type checking by
maxRecDepthrather than the physical stack, making(kernel) deep recursion detecteddeterministic across platforms and builds. Deeply recursive code may need aset_option maxRecDepthbump. -
#14372 moves
Lean.initializing,enableInitializersExecution, andisInitializerExecutionEnabledfromIOtoBaseIO.lean_enable_initializer_executionnow returns a scalar, so C FFI callers must stop handling its result with thelean_io_result_*functions orlean_dec_ref; failing to adapt is likely to segfault. -
#13679 stops code generation from inspecting the private constructor of a public type. In rare cases this changes a structure's FFI representation; the manual no longer recommends accessing such fields directly from C.
-
#14241 makes
bv_decideuseext_ifflemmas for structure equality and otherwise not reason about it, so structures may need@[ext]or hand-written extensionality lemmas. -
#14091 changes
Float.ltandFloat.lefromFloat → Float → ProptoFloat → Float → Bool; theLEandLTinstances are unaffected. -
#14290 splits
int_toBitVecinto aSymMand aMetaMsimp set;simpcalls should now useint_toBitVec_meta. -
#14206 moves Lake's deferred docstring checks onto the linter framework, under the
linter.doc.deferredoption; custom Verso docstring elements become a two-constructor type. -
A round of namespace and module hygiene relocated declarations that were in the wrong place —
Int.LineartoInt.Internal.Linear(#14255),IO.AsyncListtoLean.AsyncList(#14263), and more in #14265 / #14260 / #14258 / #14256 / #14303 / #14302 / #14293.Nat.ne_of_gtis nowprotected(#14216). -
Lake's
setupfacet is no longer buildable from the CLI, since it produces JSON rather than artifacts (#14300).
Language
-
#14498 fixes a kernel unsoundness: just as for definitions and theorems, an
opaquedeclaration's value must not contain fvars. -
#14352 provides the experimental
postprocess_traces tracePostprocessor in cmdcommand, which is useful for working with large trees of trace nodes. It runs the commandcmdand then transforms the traces using a functiontracePostprocessor. The transformation can affect which nodes are expanded or collaped by default, it can change messages of the trace nodes, and it can add or delete nodes. Example:module meta import Lean.PostprocessTraces -- Expand all ancestors of `synthInstance` trace nodes -- for better discoverability in large trace trees postprocess_traces exposeSubtrees (ofClass `Meta.synthInstance) in set_option trace.Meta.isDefEq true in set_option trace.Meta.synthInstance true in def x ...
-
#14375 adds proper borrow annotations to
Syntax.structEq. They are needed because it is so early in bootstrapping that it routes throughSubstring's bootstrapping wrappers without borrow annotations. They are relevant becauseSyntax.structEqends up getting called transitively fromalphaEq. -
#14196 improves on the warnings and errors regarding reducibility attributes. Partially addresses #13351.
-
#14361 optimizes
applyAbstractResult?by attempting to skip thecheckinvocation used to propagate universe constraints. The optimization is very simple: it checks whether the result contains any metavariables that may be assigned. -
#14333 makes the
@[deprecated]attribute error when deprecating a declaration in favor of itself. -
#14325 adds a linter which warns on
openstatements which do not in fact open all namespaces which end in the given name. -
#14335 makes
partial_fixpointreport a helpful monotonicity error instead of a confusingUnknown constanterror when a non-monadic definition uses a nested recursive call (e.g.f (f x)), which requires the function to be tail recursive. -
#14330 makes
tryResolveassign the goal metavariable directly after successfully unifying the goal type with the candidate instance type, instead of re-checking the types withisDefEq. The recheck is redundant for metavariable-free goals and can be expensive. -
#14259 adds a hint to a
unusedVariableslinter, suggesting to rename the unreferenced name with an underscore. -
#14153 adds
Insertinstances forNameMapandNameSet. -
#13956 makes the kernel's
(kernel) deep recursion detectederror deterministic by bounding kernel type checking with the existingmaxRecDepthoption instead of the physical stack size. The limit previously depended on the native stack, so it varied across platforms, builds, and optimization levels and could not be reproduced reliably; it is now a function ofmaxRecDepthalone and is raised the usual way withset_option maxRecDepth <num>. -
#14297 makes a bare
returninside amatch (dependent := true)branch of adoblock target the dependently-refined branch type, so a branch like| 0 => return 0type-checks against the refineddo-block result type without wrapping it in a nested(do …). -
#13895 enables the
backward.isDefEq.respectTransparency.typesoption by default. When assigning a metavariable at reducible, instances or implicit transparency, this means that the metavariable's and its assigned value's types are compared at implicit, previously default, transparency. It also makes many existing declarations implicit-reducible. This change increases users' control over what is being unfolded, improving scalability for large projects. -
#14249 extends
dupNamespacelinter to allow users to opt-in vialinter.extra.dupNamespace.consecutiveOnlyoption to check non-consecutive repeated usages of namespace components. By default, only consecutive ones are checked. Opting in for non-consecutive checks matches the behaviour introduced in mathlib4#39793. -
#14247 fixes an error ("Couldn't interpret binder") when a docstring is attached to a
coinductivepredicate anddoc.versois enabled. -
#14234 fixes autocompletion (and other InfoTree-driven consumers such as the interactive term goal and hover popups) to see the augmented
openDeclsandoptionswhen the cursor is under a term-levelopen ... in <term>orset_option ... in <term>scope. Previously both elaborators only updated the runtimeCore.ContextviawithTheReader/withOptions, but did not push a correspondingPartialContextInfo.commandCtxnode into the InfoTree; consumers therefore saw the outer command'sopenDecls/optionsand, for example, offered names fully-qualified even under a matchingopen, or rendered pretty-printed goals ignoring a localset_option pp.fullNames true. -
#14214 reverts leanprover/lean4#14193 . The benchmark issue that it was a reaction to was almost certainly not caused by it, but was instead noise; the actual gains were modest compared to the downside of a more complex mental model for users.
-
#14200 causes docstrings in macros to follow the value of the
doc.versooption at the the macro's definition site, rather than its use site. Before, the use-site option was used, making it impossible to use macros in contexts where the option's value disagreed because the parsed format was incorrect. Now, the parsed format in the syntax is used regardless of the option's local setting. -
#14198 fixes a bug where references to parameters by names failed for unbracketed binders, in the presence of
_parameters, and in macro-generated declarations. -
#14191 fixes an issue where escaped content at the start of a line in a valid block-opening positon in Verso content was skipped as if it were whitespace.
-
#14193 restricts the options propagated to parsers to those that start with
doc.verso, to improve performance over #14189. -
#14189 propagates the values of options from set_option ... in ... forms used in commands, terms, and tactics into the parsing of the body. This means that Verso syntax can be more conveniently enabled or disabled, and brings
set_option ... in ...semantically in line withopen ... in .... -
#14115 adds support for extensible Markdown rendering of Verso docstrings.
-
#14181 lets
matchuseFloatandFloat32literals as patterns, just likeString,UInt64, and other literal types. The compiler compares the scrutinee against each literal using the type'sDecidableEqinstance (bit-pattern equality), so e.g.0.0and-0.0are distinct patterns. Negative literals such as-1.5are supported. -
#14114 changes the Hoare-triple notation so
;introduces the exceptional postcondition as a singleEPredterm rather than a list of exception cases wrapped inepost⟨…⟩. This lets the notation express anepostvariable or anyEPred, and makesepost⟨…⟩an ordinary explicit constructor written in that slot. -
#13637 splits
TransparencyMode.instancesandReducibilityStatus.implicitReducibleinto two transparency levels so that@[implicit_reducible]annotations no longer have the side effects@[instance_reducible]has, such as allowing type class search to see through the marked declarations. -
#14120 fixes language server-oriented API such as
findDocString?that sources its information from.olean.serverunder the module system to also work on the cmdline if the module in question is imported withall. -
#14112 adds the
wait_for_expected_type%term elaborator, which elaborates its argument against the expected type but postpones while that type is an unassigned metavariable. This lets a notation defer an assertion until anoutParamis resolved by instance synthesis, so a bare lambda checks against the folded carrier instead of unfolding it into a pointwise function lattice.
Library
-
#14303 removes helper definitions used by the builtin simprocs for basic types from the respective namespaces.
-
#14302 moves three public lemmas about
ExceptTout ofStd.Internal.Do.WP.Lemmas(an internal module) toInit.Control.Lawful.Instances, where the rest of theExceptTlemmas live. -
#14293 moves the public declaration
Function.Injective.leftInverseout ofInit.Grindand intoInit.Data.Function. -
#14255 renames
Int.LineartoInt.Internal.Linearto make it more clear that these are internal implementation details ofomega/grind/simp +arithand should not be relied on directly by users. -
#14265 moves various declarations that were polluting public namespaces into internal namespaces (like
Lean). -
#14269 makes Windows transition times use ceil when converting seconds, which avoids dropping fractional seconds and causing off-by-one behavior.
-
#14231 marks
Array.back,Array.back!, andArray.back?as@[expose], so that in a downstream module their bodies are available for definitional reduction. Previouslydecidecould not evaluate goals such as#[1, 2, 3].back? = some 3from another module, even though thegetElem?/sizeaccessors these functions are defined in terms of are already exposed. -
#14267 makes
Fin.foldlreduce in the kernel by marking its innerloopsemireducible(well-founded definitions areirreducibleby default) and exposingfoldl, so the kernel's special support for well-founded recursion onNatapplies. PreviouslyFin.foldlgot stuck underdecide/#reduce/Decidable, unlike the already-reducingFin.foldr:example : Fin.foldl 8 (fun a i => a + i.val) 0 = 28 := by decide -- now succeeds
-
#13804 adds the parsing of Posix TZ String (generating a
RecurringRuletype) in TzIf V2 and V3 footer so lean can generate timezone transitions in case the timestamp is not covered by the transitions array inZoneRules. -
#14263 renames
IO.AsyncListtoLean.AsyncListto avoid polluting the publicIOnamespace. -
#14260 moves some declarations in
Lean.Data.Lsp.Communicationthat were polluting the globalIO.FS.Streamnamespace into an internal namespace. -
#14258 moves the declarations in
Lean.Data.Lsp.Utf16fromChartoChar.Internaland fromStringtoString.Internal, so that theStringnamespace is not polluted in this implementation module. -
#14256 renames the
LLVMnamespace toLean.LLVMin order to reduce pollution of the global namespace. -
#14252 speeds up
Selectable.one,Selectable.combine, andSelectable.tryOneby using a cheap randomness source as opposed to calling intogetRandomBytesevery time. -
#14244 replaces "can seen" with "can be seen" in the documentation of
Quotand rewraps the paragraph to fit 100 columns without splitting short snippets. -
#14212 adds
List.Nodup.length_le_of_subset: a duplicate-free list that is a subset of another list is no longer than that list. This is currently only available in Batteries (via theSubpermAPI); here it is proved directly by induction. -
#14211 adds
List.perm_ext_iff_of_nodup: two duplicate-free lists are permutations of each other if and only if they have the same elements. This is currently only available in Batteries (where it is proved viaSubperm); here it is proved directly fromperm_iff_count. -
#14210 adds the round-trip lemmas between
List.idxOfand indexing:List.getElem_idxOf(xs[xs.idxOf x] = xwhenxoccurs inxs) andList.Nodup.idxOf_getElem(idxOf xs[i] xs = ifor a duplicate-freexs). These are currently only available in Batteries. -
#14216 marks
Nat.ne_of_gtasprotectedso that it must be referred to by its fully qualified name, consistent with the surroundingNatorder lemmas. In-namespace references are updated to the qualified name accordingly. -
#14209 adds
List.pairwise_lt_finRange,List.pairwise_le_finRange, andList.nodup_finRange, stating thatList.finRange nis strictly increasing, increasing, and free of duplicates. These are basic facts aboutfinRangecurrently only available in Batteries. -
#14177 reduces the aggressiveness of e-matching for
List.countandArray.count. Previously, any invocation of count would directly trigger theory about filter. However, given thatcounthas its own set ofgrindannotations, we believe thatcountshould only start connecting withfilter, when an invocation offilteris already available in the e-graph. This way we do not unnecessarily triggerfiltertheory fromcount. -
#14194 reduces the aggressiveness of e-matching for
eraseIdxby not automatically translating intodrop/takeanymore at every opportunity. -
#14192 reduces the aggressiveness of e-matching annotations for bounding the result of
countoperations above by the size of their container. They now only get triggered when the size and count operations are both already in the e-graph. Similarly to how find's annotations work. -
#14190 adds two small, independent pieces of
Lean.Order.CompleteLatticeinfrastructure used by theStd.Internal.Doverification framework. -
#14182 stops automatically connected
findIdxtofindIdx?through e-matching wheneverfindIdxis available and instead only does so whenfindIdxandfindIdx?are available. -
#13799 fixes the
alignedtypes and order of the names soWeek.Ordinal.OfMonthnow isWeek.OfMonth.Ordinaland we haveWeek.OfMonth.Aligned.Ordinalthat is a really big type but it express that we can have from 1 to 5 aligned weeks. -
#14180 implements a
DecidableEq Floatinstance, which checks for equality of the underlying bit patterns. -
#14178 teaches grind about the fact that it might be interesting to work with
count a xs = 0 ↔ a ∉ xsoncecount a xsanda ∈ xshave appeared in the e-graph. -
#14116 fixes the deadlock using
Selectable.combineand also fixes a simple problem with recursive mutexes inSelectable.one. This PR fixes #14090 -
#14174 updates the docstrings for
Std.Time.GenericFormat.parseandStd.Time.GenericFormat.parse!to say that they parse intoDateTime, matching their return types. -
#14110 completely rewrites the implementation of
Float.ofScientific. -
#14091 changes the definition of the
FloatandFloat32types to wrap theFloat.Modeltype introduced in #14079. -
#14079 adds types
Float.ModelandFloat32.Modelwhich will serve as logical models for theFloatandFloat32types. -
#14034 adds the Hoare triple lemma
Triple.observetoStd.Do. It proves a triple forprogfrom a specification of a stateless programobs: observing the postconditionQofobs(viah) and usingQto establishwp⟦prog⟧ Post(viahgoal) yields⦃Pre⦄ prog ⦃Post⦄. The premisehprequiresobsto be stateless: its successful runs leave the state unchanged, which holds for every program of a stateless monad such asExcept. -
#14067 adds the weakest-precondition spec lemma
Spec.monadLift_Idso thatmvcgen/mvcgen'can discharge a do-bind that lifts anIdvalue into aPure/WPMonadtransformer stack (for examplelet x ← (pure 5 : Id Nat)insideStateT Nat Id). -
#14078 fixes a bug in the
IntX.ofIntClampfamily of functions.
Tactics
-
#14618 fixes a
grindregression on goals that use a bit-vector literal written with#syntax under a quantifier, such asexample (f g : Nat → BitVec 2) (h : ∀ n, f n = g n ||| 1#2) : f 0 = g 0 ||| 1#2 := by grind. The tactic failed with a kernel error instead of closing the goal. -
#14393 implements
grindpropagators that evaluateBitVecoperations on literals -
#14392 adds a new
liaStepsconfiguration option togrind. The motivation is to quickly interrupt the search for hard linear integer arithmetic problems. -
#14390 fixes an issue in the
grindring solver. When a ringRdoes not satisfy[NoNatZeroDivisors R], polynomial simplification could lose information, affecting completeness. -
#14195 extends
vcgenwith automatic frame inference: the@[frameproc]attribute lets a program type register how it frames a resource, andvcgenthen carries that resource across a call without the user specifying an explicitframesclause. Framing is no longer tied to the lattice meet but works for any join-preserving frame operator, so cost budgets, separation-logic footprints, and trace invariants frame through the same mechanism. -
#14379 fixes a bootstrapping issue in the
grindnormalizer. TheBitVec.ofNatLTnormalization theorem must be added to thegrindnormalization set before we processInit/Data/BitVec/Lemmas.lean. Otherwise, patterns are not properly normalized. -
#14373 fixes nontermination in
grindtriggered by constraints of the form0 ∣ p. -
#14371 fixes two bugs in
grindcaused by non-normalized bitvector literals.BitVec.ofNatLTliterals and out-of-rangeOfNat.ofNatliterals (e.g.,(17 : BitVec 4)) were not reduced to theOfNat.ofNatnormal form used bygrind, so two representations of the same value were treated as distinct values, andgrindproduced invalid proofs rejected by the kernel:example (x : BitVec 4) (_h1 : x = BitVec.ofNatLT 1 (by decide)) (_h2 : x = 1#4) : True := by grind -- kernel error before this PR
-
#14370 fixes a bug in the
BitVecsimproc whenbitVecOfNat := false. This bug affectsgrindsince it usesbitVecOfNat := false. Here is an example reported by Henrik that exposed the issue. -
#14358 implements a fast path for
grind's internal envelope typeRing.OfSemiring.Q type. -
#14346 optimizes the construction of the
grind-relevant instances for the auxiliarygrindtypeIntModule.OfNatModule.Q. It was a major bottleneck in Mathlib. -
#14314 ensures the
shareCommoninternal cache is reused atrepairAndShare. -
#14299 makes
shareCommonmaintain theSymMrepresentation invariants used bygrindandSym.simp: reducible constants are eagerly unfolded, and kernel projections are folded into projection function applications. These invariants were previously established only by thegrindpreprocessor and were easy to violate from user simprocs and internal code paths (e.g.,Sym.inferTypereturns types from environment signatures that were never preprocessed), producing silent E-matching and indexing failures. Violations are now detected when terms enter the table of maximally shared terms and repaired automatically. -
#14295 lets
vcgenhandle a program wrapped in anmdatanode, such as thesave_infoannotation left behind by spec elaboration, instead of failing with an internal error. -
#14290 makes
int_toBitVecSymM compatible by splitting it into a SymM and a MetaM simp set. Existing users ofint_toBitVecshould now useint_toBitVec_metain theirsimpinvocation instead. -
#14289 ensures that
finish?addsintrosandby_contra, when needed, to the resulting tactic script as preprocessing steps. -
#14287 implements the
rwtactic forsym =>mode. It also breaksLean/Elab/Tatic/Grind/Sym.leaninto smaller files. -
#14137 adds a pointer-equality fast path to the
Sympattern matcher: when a pattern subterm is pointer-equal to the target, it is a closed term equal to the target with no variables to bind, so matching succeeds immediately without traversing the subterm. -
#14281 ensures the RHS of equational theorems is not zeta reduced during preprocessing. This issue was affecting vcgen (see new test by @sgraf812 ).
-
#14280 fixes a bug where
sym => apply <rule>could close a goal with a proof term containing a loose instance metavariable. -
#14279 adds a new
hygienicparameter tointro-like functions inSymM. -
#14278 implements the
case => ..tactic insym =>mode. It is relevant forvcgen(see new test). The new feature tries to simulate thecase => ..tactic in regular tactic mode. -
#14277 fixes spurious
applyfailures inSymM. -
#14241 changes the way that structures are handled by
bv_decide. Previously support for equality of structures inbv_decidewas limited. Now it will use theext_ifflemmas if available and otherwise not reason about equality of structures. This change should increase the reasoning power ofbv_decideon structures. However, this is a breaking change and might require users to annotated previously existing structures with@[ext]or otherwise define and tag extensionality lemmas for them. -
#14227 refactors the way that
bv_decidehandlesUSizeandISize. This is necessary forSymMsupport inbv_decidebecause callingrevertinSymMis illegal. -
#13830 adds automatic
try?suggestions at common proof sites, gated by three options that default to off:-
autoTry.onEmptyProof— suggests on empty proofs and empty subproofs: emptyby, empty·, emptycase h =>, and so on. -
autoTry.onUnsolvedGoal— likeautoTry.onEmptyProof, but also fires on proofs and subproofs that already contain some tactics and left a goal unsolved. The suggestion is appended to the existing sequence (e.g.by skip→by skip; <found>). -
autoTry.onSorry— suggests onsorrytactics; the suggestion replaces thesorry.
-
-
#14205 stops the
impossibletactic combinator to runcleanupon the goal before negation, as that would defeat the point. -
#13712 makes
exact?,apply?,rw?, andgrind +localsno longer wait for prior async theorem bodies in the same file to finish kernel-checking when iterating the current module's declarations. Before, these tactics walkedenv.constants.map₂, which forcesenv.checkedand thus blocks on every pending async branch; in an editor session this manifested astry?andexact?appearing to hang near the top of long files. -
#14167 adds a
framesclause to thevcgentactic that attaches a state assertion (a frame) to a matched program, so facts about state the program leaves untouched survive a call whose registered spec drops them. -
#14146 renames the experimental Sym-based
mvcgen'tactic tovcgen, including its grind-mode step, thewithdischarging clause, and thesimplifying_assumptions/until/invariantssyntax. The originalmvcgentactic is unchanged. -
#14142 speeds up
mvcgen'spec lookup by internalizing each matched spec pattern into theSymMshare table on first lookup, so its instance arguments become pointer-equal to the program's and need not be re-internalized on every later lookup. -
#14138 fixes the error message for
mvcegn' ... with <tac>. The main motivation for doing so is when we writemvcgen' with grindthe error message user sees isunexpected identifier; expected grind, which cannot be more confusing. That happens because we except agrindsequence, syntax category for which is calledgrind. I am defining a separate syntax category for discharger tactic calledmvcgenWith, and throw a more meaningful exception if it is a tactic. -
#14134 speeds up
mvcgen'matching by internalizing each backward rule's pattern into theSymMshare table once, when the rule is cached, instead of re-internalizing its instance arguments on every match. -
#14080 extracts a
WPtype class fromWPMonadso that weakest-precondition reasoning andmvcgen'apply to any program type, not only monads. This enables verifying deeply embedded languages: a program type with aWPinstance but noWPMonadinstance, for example an inductive command syntax with its own operational semantics, can now be specified withTripleand decomposed bymvcgen'. -
#14119 fixes
mvcgen/mvcgen'failing to split amatchwhose discriminant telescope is dependent, i.e. when a later discriminant's type mentions an earlier discriminant (such asmatch n, h withwhereh : 0 < n). Abstracting such a matcher previously produced an ill-typed pre-splitter motive. -
#14107 tags
Nat.min_def,Nat.max_def,Int.min_def, andInt.max_defwith the@[lia]attribute, so theliatactic instantiates them via E-matching and can prove goals involvingmin/maxout of the box. This addresses the most common case whereomegacould previously be replaced byliabutliacould not see themin/maxdefinitions, requiring a fall back to the fullgrindtactic. -
#14098 adds a builtin
@[lia]attribute that supplies a small E-matching lemma set to theliatactic. Previouslylia(thecutsat-onlygrindconfiguration) ran with E-matching disabled, so it could not see definitional lemmas such asNat.max_def. Nowliainstantiates only the lemmas tagged@[lia], leaving the much larger@[grind]set disabled. -
#14102 changes the name
WhileInvariantfromStd/Internal/SpecLemmastoRepeatInvariant, since in most of the cases it will be called when verifyingforIn-repeat loops. Also, we add a new abbreviation to constructRepeatInvarinats. This abbreviation specifies a conditioninvwhich should hold at the end of each loop itreation (even the breaking one), and a conditiononDonewhich should hold in the end of the loop in addition toinv. In the case of a normalwhileloop the latter one could always be taken as negation of the loop condition. -
#14099 adds
⊤normalisation inmvcgen'. During the run ofmvcgen, in particular when introducing extra state arguments,⊤might turn into⊤ s₁ s₂ ⋯ sₙ. I am adding a procedure which constructs the proof of⊤ s₁ s₂ ⋯ sₙ = ⊤on the fly and replaces it. -
#14095 makes
mvcgen'report a clear error when an entailment's assertion lattice is a dependent function type such as(a : α) → β a → Prop, instead of looping until it runs out of heartbeats. The order is the ordinary pointwise function order; the limitation is thatmvcgen''s peel ruleLean.Order.le_of_forall_lecannot currently be applied to a dependent function lattice. -
#14081 unifies how
mvcgen'turns@[spec]annotations into backward rules, makes the annotated priority take effect for equational specs, and rejects@[spec]annotations that are neither a Hoare triple, an equation, nor a definition to unfold. -
#14089 lets
mvcgenandmvcgen'apply@[spec]theorems whose statement is a reducible abbreviation wrapping a Hoare triple, such asabbrev foo.spec := ⦃P⦄ foo ⦃Q⦄. Previously the program stayed stuck because the spec, although registered, was discarded at lookup time. -
#14015 ports the experimental
mvcgen'tactic to the newStd.Internal.Dometa theory, where verification-condition generation works on lattice entailmentspre ⊑ wp x post epost. Two changes are visible at the proof surface:mvcgen'now eagerly introduces all state components as local hypotheses, so more facts reachgrind; and loop invariants no longer have to restate the exceptional postcondition.
Compiler
-
#14372 moves
Lean.initializing,enableInitializersExecution, andisInitializerExecutionEnabledtoBaseIOfromIO. -
#14365 ensures that
unsafeterms get properly inlined. Previously having some unsafe termtoccurring inline asunsafe twould create a separate auxiliary declaration that might not end up getting inlined. -
#14343 fixes the new 1GB stack size not being used for the main
leanthread itself, e.g. for serialization or--run. -
#14139 disables symbol stripping of our libleanshared.so in release mode again. As it turns out
--strip-unneededdoesn't only strip symbols that we do not care about. -
#14272 changes
dbgTraceIfSharedto take its message borrowed (s : @& String), with the matchingb_obj_arg/b_lean_obj_argadjustments in the runtime and header. The C implementation only reads the string and never consumed it, so the owned argument leaked on every call; the leak goes unnoticed in typical use because string literals are compiled to persistent constants, which are exempt from reference counting. A dynamically constructed message leaks once per call. Borrowing matches what the implementation actually does and spares callers a reference-count operation. Found while auditing the runtime for the pattern fixed in #14271. -
#13679 fixes an issue where code generation broke when using structures with private fields and types inaccessible in the current scope.
-
#14127 fixes a theoretical but not practical race condition on
lean_task_imp.m_canceledby making it atomic. -
#14108 changes the
m_impfield oflean_taskto an atomic. This is necessary because inget_task_state_corewe access them_impto see if the task is finished before taking the mutex. Thus the memory access as it is done currently is UB.
FFI
-
#14184 exports a
lean_set_initializingsymbol for users of Lean that need to emulate multiplewithImportingcalls from a C FFI.
Documentation
-
#14222 converts a number of comments that seem to have been clearly intended as docstrings into docstrings, avoiding those already converted in #13006.
Server
-
#11958 adjusts the elaborator and snapshot tree system so as not to rerun tactics when whitespace directly following them is changed, preventing loss of progress when preparing to type the next tactic.
-
#14296 makes go-to-definition and find-references work on a
let mutvariable that is referenced after aforloop (or any construct that threads mutable state through a tuple).
Lake
-
#14366 fixes
lake newandlake initto not emit library files on theexetemplate. It also fixes a related bug where the commands could sometimes overwrite library files for existing packages. -
#14300 adds the
presetup,depTrace, anddepHashfacets, which provide different views of a module's full set of dependencies. Also, thesetupfacet is no longer buildable on the CLI (as it produces JSON and not artifacts) and now includes full set of transitive import artifacts, fixing their absence fromlake lean'ssetup.json. -
#14364 adds
getLakeSharedDynlibto the Lake API. It is a simple monadic convenience function that retrievesLakeInstall.sharedDynlibfor the detected Lake installation. -
#14285 makes Lake reconfigure when it cannot read the configuration trace, instead of aborting with
error: compiled configuration is invalid; run with '-R' to reconfigure.importConfigFilealready reconfigures automatically for a stale, wrong-toolchain, or partially-malformed trace; an unparsable trace is no more informative than a missing one, so it is now handled the same way — routed into the sameelabConfig (← acquireTrace h) …path — recovering on its own rather than requiring a manual-R. When the trace has no usableoptionsfield it falls back tocfg.lakeOpts, the same options value the fresh-configure branch uses when no trace exists. -
#14284 makes an interrupted Lake configuration recoverable.
importConfigFilewrites the compiled-configuration trace to a buffered handle and then callsIO.FS.Handle.truncate— which sets the file size but does not flush buffered writes, as its own docstring notes — before the potentially slow configuration elaboration. Alakeprocess killed in that window (an interrupted or cancelled build) leaves the trace on disk as a NUL-byte size placeholder with no.olean, so later invocations fail witherror: compiled configuration is invalid; run with '-R' to reconfigure. Flushing the complete trace before truncating and elaborating means an interruption instead leaves a valid trace and no.olean, which Lake's existing up-to-date check already treats as a trigger to reconfigure automatically. -
#14254 adds two new module facets:
linkInfoExportandlinkInfoNoExport. They provide information on how to link a module. It also providesSyncvariants forbuildSharedLib,buildLeanSharedLib, andbuildLeanExethat work from within aJobrather than across them. -
#14235 makes Lake's module archives (
.ltar) content-stable: byte-identical module outputs now produce a byte-identical archive regardless of the inputs, checkout path, or machine that built them, so input-only changes (e.g. a comment edit in an imported module) upload no new archive bytes and identical outputs deduplicate across revisions on cache services. -
#14206 adapts the deferred docstring check mechanism to use the linter mechanism, presenting an interface akin to that of environment linters. This replaces custom CI setup with an already-used interface. Deferred checks are governed by the option
linter.doc.deferred. -
#14240 ensures executables are executable when restored from the Lake cache even if they were not originally executable in the cache (e.g., because they were downloaded through
lake cache get). -
#14219 adds API for retrieving the complete set of core dynamic libraries. In current terms, these are
libleanshared,libleanshared_1, andlibleanshared_2. andlibInit_shared. These libraries have different interdependencies on Windows and Unix, so they are modelled withDynlibin order to track this information. -
#14220 adds
Dynlib.runtimeOnlyDeps. It specifies transitive dependencies that should not be linked, but need to be preloaded forleanelaboration when precompiling (e.g., libraries dynamically loaded at runtime viadlopen). -
#14156 allows modules which do not depend on any dynamic libraries to toggle
platformIndependentbetweentrueand unset without a rebuild. -
#14130 fixes
Package.remoteUrl?so an emptyremoteUrlreturnsnoneand a non-emptyremoteUrlreturnssome remoteUrl. -
#13646 adds a new Lake package option
requiresModuleSystem. When a package sets it totrue, Lake emits a warning whenever a non-module-system file (one without amoduleheader) imports a module of the package, both from downstream consumers and from non-module files within the package itself. This signals that the package's API expects the visibility and elaboration semantics of the module system. A companion optionallowNonModuleslets an importing package opt out of these warnings, declaring that it knowingly mixes non-module-system files with module-system dependencies.
Other
-
#14633 makes
infer_lambdaandinfer_letcheck a binder's type, and forletalso its value, before adding the corresponding declaration to the local context, which is whatinfer_pialready did. No valid declaration changes behavior. -
#14632 is a hardening pass over the kernel. None of these commits fixes a bug reachable from ordinary Lean code: each one takes an invariant the kernel already depends on and checks it locally instead of assuming it holds elsewhere. The intent is that a future mistake in a neighbouring part of the kernel surfaces as a clean error rather than being amplified.
-
#14631 makes the kernel compare the structure name when deciding whether two projection expressions are definitionally equal. Both
type_checker::is_def_eq_coreandequiv_manager::is_equiv_corecompared only the projection index and the projected expression, ignoringproj_sname. -
#14621 makes the kernel recheck the declarations it adds to the environment after eliminating a nested inductive type.
-
#14616 fixes a kernel bug: an inductive declaration could reference one of the auxiliary types the kernel generates when eliminating nested inductives, and end up with a stored constructor type that is ill typed. Such a declaration can only be produced with metaprogramming.
-
#14615 makes the inductive checker test a resulting universe for zero up to normalization, so that
Sort (imax 1 0)andSort 0describe the same inductive type. The two spellings previously disagreed on whether a constructor field may carry data, on whether the recursor eliminates only intoProp, and on whether the type is a K-like reduction target. Only declarations produced with metaprogramming are affected, since the elaborator normalizes levels before the kernel sees them. -
#14613 fixes a kernel bug: a type whose sort is
Proponly after universe normalization, such asSort (imax 1 0), was not recognized as a proposition, so the kernel allowed a non-proof field to be projected out of a proof. Such a declaration cannot be written in surface syntax and can only be produced with metaprogramming, andnanodarejects it. -
#14609 fixes a soundness bug in the module system. A
partialdefinition lost itspartialmarking when it crossed a module boundary, so downstream modules could use it from safe declarations. This issue can only be exploited using meta-programming. -
#14608 checks that declarations in a mutual block use the same universe parameters. The elaborator already enforces this invariant, but meta-programming can bypass it.
-
#14607 adds a missing
check_no_metavar_no_fvarchecks to the kernel inductive type module. Without it, users could use metaprogramming to sneak in nested inductive declarations containing free variables or metavariables. Note that Comparator would catch this exploit, since lean4export refuses to export declarations containing free variables or metavariables. -
#14577 fixes a kernel bug where a nested inductive datatype whose parametric arguments are ill typed could be accepted.
-
#14354 implements a minor optimization at
withExporting/withoutExporting. When they callmodifyEnvto toggleEnvironment.isExporting, andMonadEnvMetaM'smodifyEnvwipes allCoreandMetacaches. -
#14131 fixes
finishCommentBlockso it does not skip a-when it is not followed by/.