These release notes describe a release candidate, not the final release. They may be incomplete and are subject to change.
Lean 4.34.0-rc1 (2026-08-10)
For this release, 144 changes landed. In addition to the 52 feature additions and 53 fixes listed below, there were 5 refactoring changes, 5 documentation improvements, 6 performance improvements, 1 improvement to the test suite, and 22 other changes.
Language
-
#14701 lets the
ensuresclause of adefcontract be written like afun, so a postcondition may be stated per shape of the result:ensures | none => False | some v => 2 * v β€ n. A contract clause now also starts on its own line when pretty printed, as it is written in source. -
#14686 makes the
requires,ensuresandinvariantclauses accept a type ascription on their binders, asfundoes:requires s : Nat => s = 0now elaborates as a binder form instead of being read as a term. An ascription covering all binders of aninvariantclause is reported as an error, since its first two binders are the loop's consumed prefix and remaining suffix. -
#14682 lets a
forloop that destructures its binder carry aninvariantclause, so a loop over a map may bind(k, v)and still state its invariant. A container that the clause cannot verify is reported where the clause appears, naming thePureForIninstance it lacks, instead of surfacing later as avcgengadget with no applicable specification. -
#14596 makes
vcgen's loop invariants available for every container whose iteration produces its elements without effects. Hash maps, tree maps, their sets, the polymorphic ranges, slices and iterators now supportfor β¦ invariant, including containers whose element type is universe-polymorphic, which previously had no loop specification at all. A new container is supported by declaring that its loop is effect-free, rather than by adding a loop specification for it. -
#14604 adds
cbv atfeature to runcbvon local hypotheses, but now it is safe with respect toSymMinvariants, namely, eachcbvcall (to a local hypothesis) is contained within a singleSymMcontext, which remains incremental -
#14602 adds an
assertelement todonotation for intrinsic verification.assert Pstates thatPholds at that point in the program;assert s => P sbinds the arguments of the assertion itself, such as the state of a state monad, using the same bindersfunaccepts.vcgenreads the assertion from the program and proves it as a verification condition; at runtime the element does nothing. -
#14603 lets the
requiresandinvariantclauses bind the arguments of the assertion itself, so a monad whose assertions are functions no longer needs an explicitfun. For a state monad, the state can be named directly:def sumIntoState (xs : List Nat) : StateM Nat Unit requires s => s = 0 ensures _ s => s = xs.sum := do for x in xs invariant pref _ s => s = pref.sum do modify (Β· + x) -
#14601 makes the loop invariant of
Std.Internal.Doa plain function of the elements consumed so far and the elements remaining, rather than a cursor indexed by the list being iterated. Thefor β¦ invariantclause binds two lists,invariant pref suff => β¦, and verification conditions mention them directly instead of{ prefix := β¦, suffix := β¦, property := β― }.prefix. -
#14589 spells the precondition clause of a
defcontractrequires, pairing withensures. -
#14586 warns about
public/privatevisibility modifiers on unnamedinitializeblocks - they do not do anything which can be confusing. -
#14581 generalizes
withSetOptionInover the result type of the wrapped function. The previous signature only accepted aCommandElab, which returnsUnit. The phases of a stateful linter (#14357) return values, so they could not use the helper (see for example leanprover-community/mathlib4#42186). All existing call sites instantiate the result type withUnitand do not change. -
#14579 lets a
defcontract discharge the verification conditionsvcgencannot prove on its own, in aspecsection ofwhere β¦ finally. The section is an ordinary tactic block, run on whatevervcgenleaves open, so the conditions are addressed by their case names and their binders name the variables the condition speaks about:def sumEvens (xs : List Nat) : Id Nat ensures r => β k, r = 2 * k := do let mut acc := 0 for x in xs invariant _cur => acc % 2 = 0 do acc := acc + 2 * x return acc where finally | spec => case vc1 acc h => exact β¨acc / 2, by omegaβ© -
#14567 allows
cbvto handle stacks of dependent projections, whose composite is non-dependent. -
#14533 changes the way deprecates syntax warnings are displayed. Inside of definitions, which are themselves deprecated, deprecated syntax warnings are silenced.
-
#14564 changes the handling of deprecated module warnings. Previously, deprecation warnings were displayed at the syntax ref corresponding to the first command of the file. Now, headers are re-parsed and used to extract correct position for displaying the deprecation warning.
-
#14389 adds intrinsic verification syntax for
Std.Internal.Dodo-notation: loop invariants and function contracts thatvcgendischarges automatically. -
#14402 adds the support for code actions to linters. When
Elab.asyncis enabled, before a linter task is dispatched, we create a promise for the info tree node. Then, we accumulate newly added info trees through the linter execution and we resolve the promise inside of the linter task. Finally, on the main task, we modify the info tree (wrapped in command context) and add a new leaf, with an mvar id, that will eventually be filled with a promise value. -
#14520 fixes an exponential blowup (time and memory, typically surfacing as an out-of-memory failure) in
instantiateMVarson proof terms that repeatedly reference hypotheses introduced viaMVarId.assert/introβ as done byMVarId.note,replaceLocalDecl,simp at h, and, per step, by LNSym'ssym_ntactic. Fixes #14329. -
#14478 changes the way we deprecate user-registered options (added via
register_option). To ensure we get warnings both when interacting with option usingset_optionand in meta code, we require the deprecation to happen via@[deprecated]attribute, and we populate the internaldeprecation?field using the information from that attribute. -
#7577 generalizes the
convandsimptactics to applypi_congrinstead offorall_congr. The test case for #7507 has examples that work now, but only worked at universev=0before. -
#14391 refactors
Lean.Environment.replaytoLean.Kernel.Environment.replay, so that environment replays work onKernel.Environmentinstead ofEnvironment, avoiding using the unstableEnvironment.ofKernelEnv. See #13783 for more context. -
#14357 introduces stateful linters, which allow linters to persist and share state across command elaboration.
-
#14418 changes the behaviour of
checkUnivslinter to take all declarations and constructors (if dealing with an inductive type) when calculating universes that do not appear on their own. -
#14437 fixes
inferInstanceAsmarking its wrapper auxiliary definitions@[expose]even when their bodies are well-typed only in the private scope, which made instances defined viainferInstanceAsfor types without an exposed body publicly ill-typed. -
#14386 is a follow-up to #14352 (introducing
postprocess_traces). It provides a new commandstore_traces_as myTraces in cmdthat runs the commandcmdand stores its traces in-memory under the namename. The stored traces can be transformed and viewed using#postprocess_traces tracePostprocessor myTraces. -
#14397 makes the
set_option ... intactic support incremental elaboration, so edits inside its tactic block reuse the results of unchanged leading tactics instead of re-running the whole block. -
#14387 changes the level at which
logLintExtdata is persisted toserver. Previously, it was all persisted atpubliclevel, thus causing negative performance regression.
Library
-
#14728 makes
Expr.getUsedConstantscollect thetypeNamefield ofExpr.projso we get a full list of constants that are directly used. -
#14699 changes the statement of the theorem
Nat.div_lt_div_right, whose conclusion isb / a < c / a β b < c, to not requirea β£ bas an assumption. -
#14726 weakens the hypothesis of
List.dropLast_takefromi < l.lengthtoi β€ l.length. -
#14507 generalizes the termination measures of
vcgen'swhileloop specifications. A measure may map into any type with aWellFoundedRelationinstance and may read monadic state:case inv2 => exact .ofMeasure fun i => i -- Nat measure case inv2 => exact .ofMeasure fun (i, j) => (i, j) -- lexicographic case inv2 => exact .ofMeasure fun _ s => n - s -- reads the monadic state
-
#14707 adds missing
cbv_evalannotations toofList/ofArray,get!,getD,insertoperations onHashMap/HashSet. -
#14687 fixes a use after free in
String.Pos.Raw.extractwhen calling it with gigantic slice limits. -
#14623 generalizes the
MonadTail (StateT Ο m)instance to work without needingNonempty Ο. This means that proving specifications aboutwhilewith aStateTmonad now works even if there is noNonemptyinstance for the state type. -
#14268 adds a HTTP Server benchmark
-
#14541 fixes a possible time-sensitive overwrite of the known size by the
Builder.streamfunctions. -
#14571 deflakes an HTTP unknown-size stream test. In some specific scenarios, it can fail because
tryRecv?runs in the interval between sending the response header and when"aaa"is sent. -
#14588 turns
cond_eq_iteinto asimplemma. -
#14538 moves
eq_false_of_ne_trueinto theBoolnamespace to be consistent with all otherBoolfunctions, and movesBool.and'(agrindhelper function) toInternal.Bool.and'. -
#14501 establishes
diteanditeas the recommended spelling for thedifandifsyntax. -
#14168 lets a Hoare
Tripleuse an assertion typePredat a universe independent of the program's value type, so specifications can quantify over assertions likeΟ β Propwhile values stay atType 0, andvcgenreasons over such specifications directly. -
#14523 deprecates the
letFunfunction, which went out of use in #9086 a year ago. -
#14062 makes the HTTP/1.1 client correctly finish reading responses that carry no body (head responses)
-
#14059 adds
closeWithErrorthat enables the body stream to fail. -
#13901 adds a
RedirectPlantype that uses the RFC9110 logic to validate redirect responses and automatically redirect. -
#14253 makes
Selectable.oneand other related functions handle errors and simplify them by using aSelectorononeandcombine. -
#14502 scopes
Lean.Order.instCCPO_stdintoStd.Internal.Doso Hoare triple notation (which defaults the exception postcondition toβ₯) elaborates afteropen Std.Internal.Dowithout also requiringopen Lean.Order. -
#12166 removes the dependency of
pairwise_iff_getElemonInit.Data.List.Nat.TakeDropand implementsnodup_iff_getElem_inj. -
#14495 redefines
IntN.toFloatandFloat.ofIntN(and the correspondingFloat32andISizefunctions) in terms ofFloat.ModelandFloat32.Model. The model already existed but was not used because of an oversight. -
#13900 adds a
Replayabletype class that is useful for checking if someBodycan be replayed in a redirect request. -
#14481 improves the API surrounding
Float/Float.Model/Float32/Float32.Model/UnpackedFloatin the following ways:-
The declarations
Float.nan/Float.inf/Float32.nan/Float32.infand their corresponding modelsFloat.Model.nan/Float.Model.inf/Float32.Model.nan/Float32.Model.infare added (upstreamed from batteries, if you will). -
The abbreviations
Int.toFloatandInt.toFloat32are added, analogous to the existingNat.toFloatandNat.toFloat32. -
Float.Model.Formatnow requires2 β€ exponentBitsinstead of just0 < exponentBits; which is a necessary condition forpackandunpackto behave correctly -
The definitions
Float.ofNat/Float.ofInt/Float32.ofNat/Float32.ofIntare now exposed. -
The type
Float.Model.UnpackedFloat.Signnow hasderiving DecidableEqinstead of justderiving BEq. -
The definitions for
unpackMantissa/unpackExponent/unpackSignnow useBitVec.extractLsb'instead ofBitVec.extractLsb
-
-
#14462 renames
Nat.div_eqtoNat.div_eq_iteandNat.mod_eqtoNat.mod_eq_ite. -
#14458 adds somme lemmas about
Nat.nextPowerOfTwo. -
#14412 deprecates and removes the unused parameter
s : Ξ΅fromExceptCpsT.runK. -
#14294 makes
String.toListsemireducible because unfolding it throws the definitional equality checker deep into the weeds of its internal implementation.
Tactics
-
#14713 adds support for
bv_decideto make use of thegrindstate when used insym/grindinteractive mode.bv_decidenow picks up on the (relevant) equivalence classes, encodes them into the SAT problem and then handles the problem as normally. -
#14709 ensures beta-reduction is applied when canonicalizing types in
grind. -
#14694 ensures assigned metavariables are properly handled in the
SymMdiscrimination tree module. -
#14691 ensures that the
SymMmatcher/unifier does not get confused byExpr.mdata. -
#14683 makes
bv_decide's embedded constraints pass understand botha = trueand(!a) = truecorrectly. This allows us to solve slightly more problems in pre-processing. -
#14681 adds support for restricting the set of complex types that
bv_decideis going to analyze as a user. By defaultbv_decideguesses that enums and structures in its context might be relevant and tries to incorporate them into the solving process. Now users can supply a restricted set of types viabv_decide types [MyEnum, MyStruct].bv_decideis only going to work these types and disable automated discovery once this option is passed. -
#14672 makes
bv_decideavailable from withinsym =>mode. -
#14669 makes
vcgentry the@[spec]theorems matching a program in priority order and apply the first one that fits the goal, so a spec whose instance argument the call site cannot synthesize no longer shadows a more specific one. -
#14215 ports
bv_decide's pre-processor toSymM. For large, rewriting heavy problems we observe a performance win of up to 6x. Furthermore, it fixes the asymptotics of embedded constraint substitution to be linear in the size of all hypotheses. There are also some breaking changes included:-
bv_normalize's proving power got slightly changed (both positively and negatively) -
@[bv_normalize]is now aSym.simpset which comes with some differences in terms of pattern matching power and required shape of the theorem.
-
-
#14664 fixes a bug in
mkTheoremFromDeclinSymM. It did not correctly handled polymorphic theorems that require adapters. -
#14529 reworks how a
@[frameproc]procedure discharges its split verification condition so that frame inference scales to operators whose residual the built-in lattice split cannot decompose. A procedure for separating conjunctionβused to leave behind aβthat no split rule could discharge, haltingvcgen; a procedure may now discharge its split VC however it wants, so separation-logic framing closes withvcgen β¦ with finish. -
#14535 fixes
vcgen [f, h, β¦]reportingNo spec foundfor a sibling call inside a self-recursivefwhen the list both bracketsfto unfold and supplies a spechforf, whetherhis named or pulled by*. A bracketed definition's unfoldings now rank below both a named spec and a*hypothesis for the same program, so at a recursive callvcgenapplies that spec and stops rather than unfoldingfagain into a branch whose sibling call has no matching spec. The regression came from #14528, which had raised these unfoldings to the named-spec priority. -
#14530 fixes a panic in
vcgenwhen an equation or unfold spec supplied viavcgen [someDef]is used for a program in a deep embedding, i.e. a program type with a bareStd.Internal.Do.WPinstance rather than a monadic one. -
#14528 makes every
vcgen [f]argument enter the spec database at the call-site priority band, so a definition to unfold or a spec supplied as a term outranks an ambient@[spec]on the same program. -
#14524 fixes
vcgen β¦ with finishon a provably unreachablematchbranch: it no longer reports success while leaving an unassigned metavariable that the kernel rejects (declaration has metavariables), nor fails withfinish failedon a verification condition whose proof needs a lifted precondition. -
#14492 makes
vcgenprefer a spec named in avcgen [...]argument over one collected from an ambient local hypothesis, and preferfooover a hypothesis pulled in by*invcgen [foo, *], so the spec you supply at a call site wins when several match. -
#14497 teaches
vcgento decompose a rawβ/βon the RHS of aPropentailment and aniInfon anyPiassertion lattice. -
#14490 makes
vcgenreport a clean missing-spec error when the spec it selects for a program turns out not to unify with it, instead of dumping the internal backward rule and its type. -
#14487 lets
vcgen [...]accept arbitrary term arguments, not just bare identifiers, mirroringsimp [...]. A term that proves a Hoare-triple orβ wpspecification is registered as a spec, and any other term proof is handled as a simp lemma, so forms likevcgen [show l = r from h],vcgen [foo x], andvcgen [@foo]now work. -
#14429 makes
vcgen [f]handle a definitionfwhose body is amatchon its arguments likesimp [f]does. A call with an opaque discriminant now rewrites through the unfold theoremf.eq_defand splits the exposedmatch, instead of reporting a missing spec. -
#14475 fixes a spurious "Too many variable names provided" error from
fun_induction(andinduction/cases) when an alternative had alet-bound field, so that all hypotheses of such an alternative can now be named. -
#14469 makes
vcgenwork after a preceding tactichave,let, orsuffices, which previously failed with "vcgen: could not determine the program type of the goal". -
#14468 migrates the standard library to the
[grind hom]and[grind hom_pred]attribute modifiers and removes the deprecated[grind homo]and[grind homo_pred]spellings. -
#14460 adds additional
BitVecoperations to the set of operations supported bySimp.Simp.evalGroundandSym.DSimp.evalGround. -
#14459 adds an option for
Sym.dsimpto rewrite in instances. This is usually not desirable as it can lead to non-standard instances. However, we might for example want to rewrite ground terms in instances to make more terms syntactically equal. -
#14464 renames the
[grind homo]and[grind homo_pred]attribute modifiers to[grind hom]and[grind hom_pred]. The previous spellings remain as deprecated aliases with identical behavior, and will be removed once the standard library migrates to the new spellings in a follow-up PR. -
#14457 records the homomorphism source types of a
[grind homo]theorem set: when an=-injection rule (a rule translatingEq Ο) is registered, the head constant ofΟis added to a new environment extension, and rules whose source type is not headed by a constant are rejected. The source types identify the terms thegrindhomomorphism engine must track in the E-graph. Thereset_grind_attrs%command clears the new extension. -
#14454 annotates theorems for
BitVec,Fin, and fixed (signed and unsigned) integers using then new[grind homo]and[grind homo_pred]attributes. This PR is based on the prototype implemented by Andres Erbsen at https://github.com/AeneasVerif/kraken/pull/122 -
#14452 rejects
[grind homo]theorems that are conditional rewriting rules. Conditional theorems are rejected with an error pointing to the E-matching attributes. Thereset_grind_attrs%command now also clears the[grind homo]and[grind homo_pred]extensions. -
#14451 adds the attribute
[grind homo_pred]. This attribute is used for a separate mechanism which complements[grind homo]. It is not a rewrite set but an eager fact injector keyed by head symbol. Where[grind homo]`` rules translate terms,[grind homo_pred]` theorems generate new facts about terms the moment they enter the E-graph. -
#14446 adds the attribute
[grind homo]. This is just the first step. We are going to use it to implement the approach described at https://hackmd.io/Qd0nkWdzQImVe7TDGSAGbA -
#14444 ensures
grinddoesn't timeout checking for definitionally equality while trying to propagatematch-expressions conditions. -
#14439 fixes a
grindbug where the canonicalizer could resynthesize a propositional instance (e.g.Nonempty Ξ±) occurring in a binder body skipped by preprocessing, producing a closed nested proof lacking theGrind.nestedProofwrapper. Congruence closure then treated the term as distinct from correctly wrapped occurrences of the same application, andgrindmissed valid contradictions. Closes #13655. -
#14431 fixes
vcgenfailing withFailed to apply rulewhen the same equality spec matches two different programs within one run, e.g. the equations of a recursive function registered viavcgen [f]: the cached backward rule was specialized to the first matched program and could not be applied to the next one. -
#14428 fixes the
grindfilter syntax. It preventedgrind =>from being used nested inmatchexpressions. -
#14426 fixes
grinddropping E-matching theorems from customgrindattributes when a partially activated theorem was reinserted under the same symbol. -
#14425 implements support for using
grindto discharge hypotheses in conditionalSym.simptheorems. -
#14424 fixes a maximal-sharing violation in
Sym.simp: when a conditional rewrite discharged a hypothesis that occurs in the theorem's right-hand side., the discharger-provided proof was spliced into the resulting term without restoring maximal sharing, violating theSymMsharing invariant (detected bysym.debug). Dischargers are not required to return maximally shared proofs. This issue was reported by @hargoniX -
#14416 fixes
vcgenandmvcgenfailing to splitmatch h : e with ...expressions, whose alternatives bind an equalityh : e = pattern. Fixes #12275. -
#14405 improves the support for offsets in
SymMmatcher/unifier. See new test for example that could not be handled. -
#13587 fixes a kernel type mismatch raised by
lia/grindwhen internalizing an integer expression whose syntactic structure differs from the structure of its polynomial representation. The mismatch occurred because theeq_defproof term bridgedx.denote ctx = e.denote ctxtoPoly.denote' ctx p = 0via a plainEq.refl e, butPoly.denote'collapses sub-structure such as a trailing+ 0(the(.num 0)monomial is dropped) whileekeeps it. The kernel then rejected the application because the equality betweenx.denoteandPoly.denote' pdid not hold definitionally. -
#14404 fixes
Sym.simpfailing to rewrite terms containing unassigned metavariables, and prevents the matcher from unsoundly unifying such metavariables when matching nonlinear patterns. -
#14401 fixes
preprocessTypeinSymM. It must not performzetaDeltaby default.
Compiler
-
#14717 the model/runtime mismatch in
String.Pos.Raw.extractand adds a faster variant (lean_string_utf8_extract_fast) forString.extractthat assumes that the positions are valid positions. -
#14505 fixes a compiler issue where private imports of the
Leanlibrary could lead to segfaults by ensuring the necessary call tolean_initializehappens in each module's initializer when necessary. As a follow-up clean up, the call tolean_initialize_runtime_moduleis made implicit as well, meaning users of Lean as an FFI library do not need to call these functions themselves anymore. -
#14332 adds
DT_SONAMEentries to the shared librarieslibInit_shared, libleanshared*, libLake_sharedon Linux. This is analogous toLC_ID_DYLIBon Mac which we already set via-install_name. Fixes #9420. -
#14479 prevents possible corruption if two threads simultaneously call
lean_decode_io_error. It also changes the semantics ofosCodeinIO.Error, such that it emulates posixerrnorather than forwarding uv error codes cast to unsigned integers. -
#14471 fixes a sanitizer warning where
initializefunctions were passed uninitialized memory as theirworldargument, by failing to callio_mk_world. -
#14463 reverts #14423 until we can get the situation on Windows figured out.
-
#14423 prevents possible corruption if two threads simultaneously call
lean_decode_io_error. It also changes the semantics ofosCodeinIO.Error, such that it emulates posixerrnorather than forwarding uv error codes cast to unsigned integers. -
#14204 prevents silent olean truncation when disk space is exhausted.
Pretty Printing
-
#14512 makes a
fordo-element pretty-print with a space beforedo. The do-elementforparser emitted"do "with no leading space, so reformatting afor β¦ doblock glued the range to the keyword (for x in xs doprinted asfor x in xsdo). Every sibling do-keyword (while,unless, term-levelfor) already emitsdo; this alignsfor. -
#14367 fixes an issue where the
@[simp β]attribute would pretty-print as@[simpβ ], along with analogous issues with@[grind norm β],@[wf_preprocess β],@[bv_normalize β], etc. See also discussion on Zulip.
Documentation
-
#14436 removes references to the unfolding lemma from the
repeatMdocstrings and moves that lemma into therepeatM.Internalnamespace.
Lake
-
#14723 makes the
MACOSX_DEPLOYMENT_TARGETconfigurable via the Lake API -- both across a build and for custom builds of shared libraries or executables. It also includes the target in traces, ensuring a rebuilding if the value changes (e.g., if the environment variableMACOSX_DEPLOYMENT_TARGETis set). -
#14724 adds the
--packageoption forlake cache get, which fetches outputs for a specific package in the workspace (not just the root). This is particularly useful for downloading dependency outputs from a custom service. In addition, the undocumented--revsupport has been removed fromputand documented forput-staged. -
#14720 demotes all cache-related failures during a build to
trace-level messages. This ensures that builds run with--wfailor--iofaildo not fail solely due to the cache. -
#14622 adds a
--code-qualityoption tolake lintthat emits builtin linter results as machine-readable JSON entries instead of human-readable diagnostics. Text-linter warnings are aggregated per module and linter into one entry holding the warning count, and environment-linter findings are reported per flagged declaration; both are keyed by the linter's option name. The option implies--builtin-lintand--builtin-only. -
#14617 refactors
lake lint --builtin-lintinternals so that it has aModeflag (reporting vs recording exceptions) in the anticipation of the third mode of running upcoming code quality checks. -
#14629 suppresses Lake's wrapper line
error: Lean exited with code 1whenleanhas already emitted error-level diagnostics and exited with code 1, which is the usual type-error path and was pure noise next to the real errors. -
#14625 makes Lake report the underlying file error when a
lean_libroot module has no source file, instead of only reporting that some modules have bad imports. -
#14651 fixes a number of ways a failed artifact transfer in
lake cache get/lake cache putcould fail to be recorded or could lead to an early abort of the entire transfer batch. Sometimes this would leave a corrupted artifact in the local Lake cache, which could break downstream builds. -
#14630 makes
lake update <pkg>...fail with a clear error when a specified package name is not known to the current dependency manifest. Previously, unknown or misspelled names (including case mismatches) were silently ignored, which was confusing.
Other
-
#14161 adds support for compiling with thread sanitizer. This both increases memory consumption and slows lean down massively so we only run a very small subset of tests to remain in a reasonable time. Developers need to add additional tests to the set themselves.