feat: name the functional argument to brecOn in structural recursion#12987
feat: name the functional argument to brecOn in structural recursion#12987
Conversation
This PR extracts the functional (lambda) passed to `brecOn` in structural recursion into a named `_f` helper definition (e.g. `foo._f`), similar to how well-founded recursion uses `._unary`. This way the functional shows up with a helpful name in kernel diagnostics rather than as an anonymous lambda. The `_f` definition is added as a reducible abbreviation. For inductive predicates and nested inductives with unused type formers, we fall back to the previous inline lambda behavior. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
These tests only care about the signature, not the definition body. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
!radar |
|
Benchmark results for 2602bf2 against 61a3443 are in. Significant changes detected! @nomeata
Large changes (2✅)
Medium changes (3🟥)
Small changes (8✅, 76🟥) Too many entries to display here. View the full report on radar instead. |
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Each function now gets its own `_f` definition from its individual functional argument. For single-function position groups, the `_f` constant is referenced in the packed brecOn argument (showing in kernel diagnostics). For multi-function groups, the packed functional stays inline to preserve FunInd behavior, but individual `_f` defs still exist. This also removes the `positions.all (\!·.isEmpty)` restriction, so `_f` is created even for nested inductives with unused type formers. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
Reference manual CI status:
|
- Remove try/catch around _f axiom creation (no expected failures) - Use ElimRecResult struct instead of tuple for clarity - Pass function type to mkBRecOnMotive instead of using inferType - Simplify _f axiom creation (use addDecl directly instead of addAsAxiom) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This PR separates the `withoutModifyingEnv` for `preprocess` from the one covering `getFixedParamPerms` through `elimMutualRecursion`, making the axiom requirements of each phase explicit. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This PR splits `tryAllArgs` into two phases: `findRecArgCandidates` precomputes all candidate argument combinations (the part needing function axioms), and `tryCandidates` runs the retry loop using `Meta.saveState`/`restore` for proper environment backtracking instead of `withoutModifyingEnv`. This removes the inner `withoutModifyingEnv` around `elimMutualRecursion`. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…hase This PR removes the `tryAllArgs` wrapper, calling `findRecArgCandidates` and `tryCandidates` directly. Each phase that needs function axioms has its own `withoutModifyingEnv` + `addAsAxiom` pair: `preprocess`, `getFixedParamPerms`, `findRecArgCandidates`, and the `tryCandidates` callback. The `withoutModifyingEnv` in `tryCandidates` is moved into the callback alongside the axiom additions. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…s callback Move the body of `structuralRecursion` that follows `inferRecArgPos` into a callback that runs inside the `tryCandidates` backtracking loop but outside `withoutModifyingEnv`. This allows `_f` helper definitions and non-recursive definitions to be added directly within the backtracking context. Introduce `TM = StateRefT State TermElabM` alongside the existing `M = StateRefT State MetaM` so the callback can call `TermElabM` functions like `addNonRec` and `addAndCompilePartialRec`. `M` functions lift to `TM` via an explicit `MonadLift` instance. `tryCandidates` moves from `FindRecArg.lean` to `Main.lean` and now also saves/restores `Structural.State` on backtracking. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Extract the repeated `withoutModifyingEnv` + `addAsAxiom` pattern into a `withRecFunsAsAxioms` helper with a docstring explaining its purpose and limitations. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This reverts the callback-style `inferRecArgPos` and `TM` monad (commit d1138cc), restoring the simpler tuple-returning approach where post-processing happens after `run`. The `tryCandidates` function moves back to `FindRecArg.lean`. The `withRecFunsAsAxioms` helper is kept. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This stores the functional types in ElimRecResult (avoiding redundant inferType calls) and uses addDecl directly instead of the heavier addNonRec for _f helper definitions. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…etaM This removes the global M = StateRefT State MetaM monad from structural recursion elaboration. The State was only needed to collect matcher side-effects from mkBelowMatcher in the inductive predicate case. Instead, mkIndPredBRecOnF now returns matchers explicitly as part of its result, and replaceIndPredRecApps uses a local IndPredM monad. All other functions work in plain MetaM. The _f definitions are now added directly in elimMutualRecursion using addDecl, and withRecFunsAsAxioms is scoped narrowly around the functions that need it. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This removes the now-single-field ElimRecResult structure, replacing it with a plain Array PreDefinition return type from elimMutualRecursion. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This uses mkForallFVars on the already-computed FTypes to get the closed _f types, avoiding a redundant inferType call on each fValue. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This uses a single recArgInfos.mapIdxM with the isIndPred branch inside the callback, wrapping withRecFunsAsAxioms around each individual mkBRecOnF/mkIndPredBRecOnF call. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This removes the poss.size == 1 check and references _f constants in the packed functionals for all position groups, not just singletons. This ensures _f definitions show up in kernel diagnostics for multi-function position groups as well. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Structural recursion now creates `_f` auxiliary definitions to make the functional arguments visible in kernel diagnostics. FunInd needs to see through these definitions to split induction principles into per-constructor cases. Add a case in `buildInductionBody` that unfolds constant applications taking `oldIH` as an argument, mirroring the existing pattern in `foldAndCollect`. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Update `letToHaveCleanup.lean` for `let` vs `have` in `_f` type (inferred type uses `let`, not the user-facing `have`). Update `issue6281.lean` for `_f` proof terms now visible as `sorry` after FunInd unfolds `_f` definitions. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Apply `Meta.letToHave` to the `_f` value and type before adding the declaration, so that `let` bindings in user code are properly displayed as `have` in the `_f` definition. Update `issue6281.lean` test expectations for `_f` proof terms now visible as `sorry` after FunInd unfolds `_f` definitions. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
Mathlib CI status (docs):
|
This reverts commit 3115752.
This reverts commit 13a67dc.
Use `.regular` reduction hints instead of `.abbrev` for `_f` definitions to prevent the kernel from eagerly unfolding them, which could cause timeouts (e.g. in bv_decide tests where the kernel enters an infinite Nat.brecOn unfolding chain). The `@[reducible]` attribute is kept so the elaborator can still unfold `_f` as needed. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
!radar |
|
Benchmark results for 2c6049f against 90b5e31 are in. Significant changes detected! @nomeata
Large changes (4✅, 2🟥)
Medium changes (10🟥)
Small changes (26✅, 64🟥) Too many entries to display here. View the full report on radar instead. |
|
Mathlib CI status (docs):
|
Nat.add._f only exists in stage2. Use a local myAdd definition so the test works in stage1 too. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
Mathlib CI status (docs):
|
# Conflicts: # tests/elab/1024.lean.out.expected # tests/elab/1237.lean.out.expected # tests/elab/3965_2.lean.out.expected # tests/elab/issue6281.lean.out.expected
Use `.abbrev` reducibility hints for `_f` definitions instead of `.regular`. This ensures the kernel always unfolds `_f` before other definitions in `lazy_delta_reduction_step`, avoiding deep recursion when `_f` (with low height) faces definitions like `Nat.sub` (with higher height) during definitional equality checks on large nat literals. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Analysis of stage2 test failuresWith Root causeNaming the functional as a constant (
|
This PR extracts the functional (lambda) passed to
brecOnin structuralrecursion into a named
_fhelper definition (e.g.foo._f), similar tohow well-founded recursion uses
._unary. This way the functional shows upwith a helpful name in kernel diagnostics rather than as an anonymous lambda.
The
_fdefinition is added with.regularkernel reducibility hints andthe
@[reducible]elaborator attribute. For inductive predicates, theprevious inline lambda behavior is kept.
Using
.regularrather than.abbrevkernel hints is important forperformance: with
.abbrev, the kernel'slazy_delta_reduction_stepalways unfolds
_fbefore other definitions (since.abbrevhas thelowest priority in
compare()), and the "compare arguments beforeunfolding" optimization (which only fires for
.regularheads with thesame definition on both sides of a defeq check) cannot apply. This causes
the kernel to eagerly unfold the
_f→brecOn→casesOnchain,leading to deep recursion errors in tests like
bv_decide. With.regularhints, height-based comparison delays_funfoldingappropriately, and the argument-comparison optimization avoids unnecessary
unfolding entirely.
This change improves code size (a bit). It may regress kernel reduction times,
especially if a function defined by structural recursion is used in kernel reduction
proofs on the hot path. Functions defined by structural recursion are not particularly
fast to reduce anyways (due to the
.brecOnconstruction), so already now it may beworth writing a kernel-reduction-friendly function manually (using the recursor directly,
avoiding overloaded operations). This change will guide you in knowing which function to
optimize.
🤖 Generated with Claude Code