Skip to content

chore: bump Lean toolchain to v4.32.2 - #30

Merged
digama0 merged 2 commits into
digama0:masterfrom
kim-em:agent/bump-v4.32.2
Aug 4, 2026
Merged

chore: bump Lean toolchain to v4.32.2#30
digama0 merged 2 commits into
digama0:masterfrom
kim-em:agent/bump-v4.32.2

Conversation

@kim-em

@kim-em kim-em commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

This PR bumps the Lean toolchain to v4.32.2, mirrors the one kernel change in the interval that lean4lean did not already have, and pins the legacy do elaborator in the four files whose proofs depend on the shape do notation elaborates to.

git log v4.31.0..v4.32.2 -- src/kernel lists exactly two commits, and the diff over that range touches only environment.cpp (1 insertion) and inductive.cpp (15 insertions, 3 deletions). Both are fixes, and both are covered:

  • fix: kernel to check opaque values for fvars, in v4.32.1 and v4.32.2. It adds check_no_metavar_no_fvar(*this, v.get_name(), v.get_value()) to environment::add_opaque, between check_constant_val and checker.check. Lean4Lean.addOpaque now calls checkNoMVarNoFVar env v.name v.value in exactly that position.

  • fix: missing check at kernel inductive declaration, in v4.32.2 only. Already mirrored on master: Lean4Lean/Inductive/Add.lean type checks the nested applications I Ds against the post-declaration environment, using Result.lctx for C++'s m_params_lctx, and Lean4Lean.Tests.NestedInductive pins that an ill-typed dropped parameter is rejected. No change needed here.

lean4lean had the #14498 bug verbatim rather than by loose transcription, so this is a real fix and not a formality. addOpaque runs checkConstantVal and the value's checkType inside a single M.run, so both share one TypeChecker.State exactly as the C++ kernel shares one type_checker. A type that beta-reduces to False but whose inference pushes a free variable through the local context leaves that variable's type in inferTypeI. The value can then name the variable: inference answers from the cache instead of the already popped local context, and the declaration is accepted with type False. The leaked variable is _kernel_fresh.2, the same name the lean4 test for this issue uses, because TypeChecker.State.ngen has the same prefix and starts at the same index.

Lean4Lean.Tests.OpaqueFVar pins both directions and discriminates the fix: with the checkNoMVarNoFVar line removed it fails with "opaque value containing a free variable was accepted", and with it present the build is clean.

Worth flagging separately, because it is live on master right now: the same hole exists in addTheorem and in the safe branch of addDefinition, where it is not a lean4 bug at all. add_theorem and add_definition have called check_no_metavar_no_fvar on the value for a long time, and lean4lean does not. The identical cache-priming declaration is accepted as a theorem and as a safe definition. Verify front-end declaration checking already adds both calls, so this PR deliberately leaves them alone rather than widening scope, but that PR is still a draft.

The remainder of the diff is not a kernel change. feat: make new do elaborator the default flipped backward.do.legacy to false in v4.32.0, which reshapes the terms do notation produces: join points become have __do_jp instead of the lets that extract_lets destructures, and if chains are inlined. Lean4Lean/TypeChecker.lean, Lean4Lean/EquivManager.lean and Lean4Lean/Verify/TypeChecker/Reduce.lean are affected because the Verify proofs pin the elaborated shape of the implementation, and Lean4Lean/Experimental/ShapeLogRel.lean because it proves things about Option-monad do/return definitions the same way. Each of those four files now sets backward.do.legacy true with a comment saying why. This keeps the elaborated implementation identical to v4.31.0, which is the conservative choice for a kernel, and it is the whole of the fix: the single pin in ShapeLogRel.lean cleared all 38 errors in that file. Migrating the proofs to the new elaborator is follow-up work and a much larger change, tracked in Migrate the Verify proofs off the legacy do elaborator, which each pin points at.

Also refresh the toolchain-pinned Lean.Level divergence link, whose claim is about the current toolchain, and make stringProof a theorem for the new linter.defProp. fix: align Lean/Level.lean and kernel/level.cpp normalization rules is still open, so that divergence note stands.

Validated with lake build, lake build Lean4Lean.Experimental, and both CI replay commands: lean4lean Init.Core checked 1036 declarations and lean4lean --fresh Init.System.IO checked 43721. No statement changes and no new sorrys.

🤖 Prepared with Claude Code

Exactly two commits touch `src/kernel` in v4.31.0..v4.32.2, and both are fixes:

* leanprover/lean4#14498, "fix: kernel to check opaque values for fvars", adds a
  `check_no_metavar_no_fvar` call to `environment::add_opaque`. `addOpaque` now
  makes the same call in the same position, between `checkConstantVal` and the
  `checkType` of the value.
* leanprover/lean4#14577, "fix: missing check at kernel inductive declaration",
  is already mirrored: `Lean4Lean/Inductive/Add.lean` type checks the nested
  applications against the post-declaration environment, covered by
  `Lean4Lean.Tests.NestedInductive`.

lean4lean had the #14498 bug verbatim, not merely by transcription. `addOpaque`
runs `checkConstantVal` and the value's `checkType` inside one `M.run`, so they
share one `TypeChecker.State` exactly as C++ shares one `type_checker`. A type
that beta-reduces to `False` but whose inference pushes a free variable through
the local context leaves that variable in `inferTypeI`; the value can then name
it, inference answers from the cache rather than the popped local context, and
the declaration is accepted. The leaked variable is `_kernel_fresh.2`, the same
name as in the lean4 test for this issue, since `TypeChecker.State.ngen` uses the
same prefix and starts at the same index.

`Lean4Lean.Tests.OpaqueFVar` pins both directions and is a real regression test:
with the `checkNoMVarNoFVar` line removed it fails with "opaque value containing
a free variable was accepted".

The rest of the diff is not a kernel change. leanprover/lean4#13305 made the new
`do` elaborator the default in v4.32.0, which reshapes the terms that `do`
notation produces. The `Verify` proofs are written against the legacy shape, and
`Lean4Lean/Experimental/ShapeLogRel.lean` proves things about `Option`-monad
`do`/`return` definitions the same way, so those four files pin
`backward.do.legacy true` with a comment saying why. That keeps the elaborated
implementation identical to v4.31.0, which is the conservative choice for a
kernel. Migrating the proofs to the new elaborator is follow-up work.

Also refresh the toolchain-pinned `Lean.Level` divergence link, and make
`stringProof` a `theorem` for the new `linter.defProp`.

Validated with `lake build`, `lake build Lean4Lean.Experimental`, and both CI
replay commands: `lean4lean Init.Core` checked 1036 declarations and
`lean4lean --fresh Init.System.IO` checked 43721. No statement changes and no new
`sorry`s.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RFCAQgoGN4ndJA1rxNx9RE
@kim-em
kim-em force-pushed the agent/bump-v4.32.2 branch from 78ddec2 to 7afe199 Compare August 4, 2026 00:07
`check_no_metavar_no_fvar` is called on the value in three places in the C++ kernel:
`add_definition` (safe branch, environment.cpp:184), `add_theorem` (:203), and, since
leanprover/lean4#14498, `add_opaque` (:217). This branch added the third; the first two
were removed deliberately, and `divergences.md` recorded them as redundant.

That argument is wrong, and all three are soundness bugs. A free variable in the value is
harmless only while inference always consults the local context, and inference also answers
from its cache: the type and then the value are checked by the same `TypeChecker.State`,
exactly as C++ shares one `type_checker`, so a declaration whose *type* is inferred by
pushing a free variable through the local context leaves that variable's type in
`inferTypeI`. The value can then name the variable, inference answers from the cache
instead of the already popped local context, and the declaration is accepted. With a type
that beta-reduces to `False`, the result is a proof of `False` -- confirmed against the
v4.31.0 tree, where `theorem Bad : (fun _ => False) id := _kernel_fresh.2` was accepted and
`theorem FalseFromBad : False := Bad` then went through on top of it.

`addTheorem` is the more serious of the two, being both the common path and, unlike the
opaque case, not an upstream bug: lean4 has always had this call.

`Lean4Lean.Tests.DeclFVar` replaces `Tests.OpaqueFVar` and pins all three sites, in both
directions, and asserts that the rejection comes from the free variable check rather than
from some other path, so that it cannot quietly stop testing the cache route.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@digama0
digama0 merged commit 779c51f into digama0:master Aug 4, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants