Compare commits

..

43 Commits

Author SHA1 Message Date
Henrik Böving
86175bea00 perf: teach borrow inference about arrays (#13064)
This PR informs the borrow inference that if an `Array` is borrowed and
we index into it, the value we obtain is effectively a borrowed value as
well. This helps improve the ABI of operations that recurse on linked
structures containing arrays such as tries or persistent hash maps.
2026-03-23 18:10:50 +00:00
Mac Malone
9eb249e38c fix: lake: error on executables with duplicate root module names (#13028)
This PR adds a check that rejects Lake configurations where multiple
executables share the same root module name. Previously, Lake would
silently compile the root module once and link it into all executables,
producing identical binaries regardless of differing `srcDir` settings.

Lake (and Lean) rely on module names being unique within a package.
Rather than attempting to support duplicate module names, Lake now
produces a clear error at configuration load time, for both TOML and
Lean configuration files.

Closes #13013

🤖 Prepared with Claude Code

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-23 18:10:10 +00:00
Joachim Breitner
b5036e4d81 doc: rewrite ReducibilityHints docstring (#13065)
This PR rewrites the docstring on `Lean.ReducibilityHints` to accurately
describe the
kernel's lazy delta reduction strategy: which side gets unfolded when
comparing two
definitions, how definitional height is computed, and how hints relate
to the
`@[reducible]`/`@[irreducible]` elaborator attributes.

The old docstring referenced a `selfOpt` flag that no longer exists and
contained a few
inaccuracies (e.g. `irrelevance` instead of `irreducible`).

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-23 17:02:27 +00:00
Lean stage0 autoupdater
fb1eb9aaa7 chore: update stage0 2026-03-23 15:31:56 +00:00
Henrik Böving
33e63bb6c3 perf: mark ReaderT context argument as borrow (#12942)
This PR marks the context argument of `ReaderT` as borrowed, causing a
wide spread of useful borrow annotations throughout the entire meta
stack which reduces RC pressure. This introduces a crucial new behavior:
When modifying `ReaderT` context, e.g. through `withReader` this will
almost always cause an allocation. Given that the `ReaderT` context is
frequently used in a non-linear fashion anyways we think this is an
acceptable behavior.
2026-03-23 14:45:52 +00:00
Garmelon
482d7a11f2 chore: handle empty dirs more gracefully (#13062)
This PR demotes the cmake error to a warning because it tends to get
triggered by a combination of add_dir_of_test_dirs and git checkout not
removing untracked files.
2026-03-23 14:23:47 +00:00
Lean stage0 autoupdater
aef0cea683 chore: update stage0 2026-03-23 14:42:07 +00:00
Joachim Breitner
720cbd6434 feat: theorems are opaque (#12973)
This PR makes theorems opaque in almost all ways, including in the
kernel.

Already now, because of proof irrelevance, theorems are almost never
unfolded. Furthermore, the import handling allows conflicting theorem
declaration with same type and different values. This is sound, but
would be confusing if the value, and thus the import order, matters for
completeness.

So with this change, a `theorem` becomes more like an `opaque`: It has a
value (for soundness), but it is never unfolded during reduction or type
checking. There are still some places in meta code that have to peek
into theorems (e.g. `FunInd`, wfrec processing), but these are code
transformations, not reduction.

One place where reducing proofs is necessary is reducing `Acc.rec`
eliminating into Type. With this change, all proofs that need to be
reducable that way have to be `def`, not `theorem`. This is already the
case due to the module system. This does not affect uses of `Acc` via
well-founded recursion, because that has already been made opaque in
#5182. This moves the reduction behavior of `Acc.rec` further into the
“supported by the theory but not relied upon by regular Lean“ corner.

Fixes #12804

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-23 13:57:07 +00:00
Joachim Breitner
26ad4d6972 feat: name the functional argument to brecOn in structural recursion (#12987)
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 with `.abbrev` kernel reducibility hints
and
the `@[reducible]` elaborator attribute, so the kernel unfolds it
eagerly
after `brecOn` iota-reduces. For inductive predicates, the previous
inline
lambda behavior is kept.

To ensure that parent definitions still get the correct reducibility
height
(since `getMaxHeight` ignores `.abbrev` definitions), each `_f`'s body
height is registered via a new `defHeightOverrideExt` environment
extension.
`getMaxHeight` checks this extension for all definitions, making the
height
computation transparent to the extraction.

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 `.brecOn` construction), so already
now it may be
worth 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](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-23 13:40:18 +00:00
Markus Himmel
4a17b2f471 chore: lemmas about BEq on List String.Slice (#13061)
This PR adds lemmas about `BEq` on `List String.Slice`.

We show `(l == l') = false ↔ l.map copy ≠ l'.map copy` and deduce a
`BEq` version of the theorem about "intercalate-then-split":
```lean
theorem toList_split_intercalate_beq {c : Char} {l : List String} (hl : ∀ s ∈ l, c ∉ s.toList) :
    ((String.intercalate (String.singleton c) l).split c).toList ==
      if l = [] then ["".toSlice] else l.map String.toSlice
```
2026-03-23 13:34:46 +00:00
Markus Himmel
fcdd9d1ae8 feat: EquivBEq and LawfulHashable for String.Slice (#13058)
This PR adds `EquivBEq` and `LawfulHashable` instances to
`String.Slice`.

To this end, we redefine `String.Slice.hash`, which used to be
completely opaque, to be defined as `String.hash s.copy` (and then
`String.hash` remains opaque). We add tests that the `lean_slice_hash`
and `lean_string_hash` functions do indeed satisfy this relationship.

Of course, it would be even better to have a streaming MurmurHash64A
implementation in core that could be used to implement both of these so
that we can avoid the `opaque`, but that is a project for another day.
2026-03-23 11:10:00 +00:00
Lean stage0 autoupdater
47427f8c77 chore: update stage0 2026-03-23 10:33:50 +00:00
Henrik Böving
08595c5f8f fix: interaction of extern annotations and calls to functions with borrowed parameters (#13052)
This PR fixes a bug in the borrow inference in connection with `export`
annotations.

Previously parameters of `export` functions were presumed as owned from
the beginning of the
analysis. However, they were not added into the set of owned parameters
and thus sometimes failed to
force necessary changes to borrowedness of other values that the
parameters flowed into.
2026-03-23 10:03:26 +00:00
Markus Himmel
019b104a7d chore: variants of String.toNat? lemmas (#13057)
This PR adds some variants of existing lemmas about `String.toNat?` and
friends.
2026-03-23 09:32:32 +00:00
Markus Himmel
2e421c9970 feat: Std.Iter.intercalateString (#13056)
This PR adds the functions `Std.Iter.joinString` and
`Std.Iter.intercalateString`.

`it.intercalateString s` is a more efficient version of
`s.copy.intercalate (it.toList.map toString)`, and we have a lemmas
proving exactly that.
2026-03-23 09:28:42 +00:00
Markus Himmel
e381960614 feat: simproc for turning "c" into String.singleton 'c' (#13054)
This PR adds the simproc String.reduceToSingleton`, which is disabled by
default and turns `"c"` into `String.singleton 'c'`.

Recall that the simproc `reduceSingleton`, which does the reverse, is
part of the default `simp` set.
2026-03-23 09:06:49 +00:00
Sebastian Ullrich
346c9cb16a chore: CI: bump git cache to 5GB (#13053) 2026-03-23 08:58:35 +00:00
Kim Morrison
189cea9f80 chore: check for empty PRs in CI (#12956)
This PR adds a CI check that fails when a PR introduces no changes
compared to its base branch. This catches cases where a duplicate PR is
queued for merge after an identical PR has already landed (as happened
with https://github.com/leanprover/lean4/pull/12876 and
https://github.com/leanprover/lean4/pull/12877).

The check is added as a second job in the existing `check-stage0.yml`
workflow, which already has the same trigger conditions and git setup
pattern. On `pull_request` events it diffs against the merge base; on
`merge_group` events it diffs `HEAD^1..HEAD` (the PR's contribution to
the synthetic merge commit). Note that batched merge groups are treated
as a unit — if the entire group is non-empty the check passes, which is
the right behaviour for lean4's typical single-PR queuing.

🤖 Prepared with Claude Code

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-23 03:09:52 +00:00
Kim Morrison
b9028fa6e9 fix: handle lean4-nightly toolchain prefix in release checklist (#12865)
This PR fixes a crash in release_checklist.py when a repository uses the
`leanprover/lean4-nightly:` toolchain prefix (e.g. leansqlite). The
`is_version_gte` function only checked for `leanprover/lean4:nightly-`
but
not `leanprover/lean4-nightly:`, causing a `ValueError: invalid literal
for
int() with base 10: 'nightly'` when trying to parse the version.

🤖 Prepared with Claude Code

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-23 03:01:58 +00:00
Leonardo de Moura
0c0edcc96c feat: add control and arrow_telescope simproc DSL primitives (#13048)
This PR adds two new `sym_simproc` DSL primitives and helper grind-mode
tactics.

Simproc primitives:
- `control` — simplifies control-flow expressions (`if-then-else`,
  `match`, `cond`, `dite`), visiting only conditions and discriminants.
  Intended as a `pre` simproc.
- `arrow_telescope` — simplifies arrow telescopes
  (`p₁ → p₂ → ... → q`) without entering binders. Intended as a `pre`
  simproc.

Grind-mode tactics:
- `show_goals` — displays pending goals (non-terminal `trace_state` for
  grind mode)
- `exact e` — macro delegating to `tactic => exact e`

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-23 02:19:13 +00:00
Leonardo de Moura
9f4db470c4 feat: add permutation theorem support to Sym.simp (#13046)
This PR prevents `Sym.simp` from looping on permutation theorems like
`∀ x y, x + y = y + x`.

- Add `perm : Bool` field to `Theorem`
- Add `isPerm` that checks if LHS and RHS have the same structure with
  pattern variables (de Bruijn indices) rearranged via a consistent
  bijection. Uses `ReaderT` (offset for binder entry), `StateT`
  (forward/backward maps), `ExceptT` (failure).
- Compute `perm` in `mkTheoremFromDecl` / `mkTheoremFromExpr`
- In `Theorem.rewrite`, when `perm` is true, only apply the rewrite if
  the result is strictly less than the input (using `acLt`)
- Tests include the classic AC normalization stress test with
  `add_comm`, `add_assoc`, `add_left_comm`

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-23 00:22:36 +00:00
Kim Morrison
8ae39633d1 fix: mark auxiliary definitions from normalizeInstance as meta (#13043)
This PR fixes a bug where `inferInstanceAs` and the default `deriving`
handler, when used inside a `meta section`, would create auxiliary
definitions (via `normalizeInstance`) that were not marked as `meta`.
This caused the compiler to reject the parent `meta` definition with:

```
Invalid `meta` definition `instEmptyCollectionNamePrefixRel`, `instEmptyCollectionNamePrefixRel._aux_1` not marked `meta`
```

The fix adds an `isMeta` parameter to `normalizeInstance` that is
propagated from the elaboration context (`isMarkedMeta` for
`inferInstanceAs`, `Scope.isMeta` for the deriving handler), and marks
each auxiliary definition created by `mkAuxDefinition` as `meta` when
appropriate.

Found while adapting Mathlib to
https://github.com/leanprover/lean4/pull/12897.

🤖 Prepared with Claude Code

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 22:41:57 +00:00
Leonardo de Moura
cffacf1b10 feat: support local hypotheses in simp [h] for sym => mode (#13042)
This PR extends the `simp` tactic in `sym =>` mode to support local
hypotheses in the extra theorem list.

`simp myVariant [h]` now resolves `h` against the local context first,
falling back to global constants. Local hypotheses are converted to
rewrite rules via `mkTheoremFromExpr`, which applies the `eq_true`/
`eq_false`/`propext` adapter from #13041.

- Add `ExtraTheorem` inductive (`.const` / `.fvar`) for cache keying
- Add `resolveExtraTheorems` that checks the local context before
globals
- Update `addExtraTheorems`, `mkDefaultMethods`, `elabVariant`
signatures

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 21:50:31 +00:00
Leonardo de Moura
b858d0fbf2 feat: adapt non-equality theorems in mkTheoremFromDecl (#13041)
This PR extends `mkTheoremFromDecl` and `mkTheoremFromExpr` to handle
theorems whose conclusion is not an equality, enabling `Sym.simp` to use
a broader class of lemmas as rewrite rules.

Adaptations:
- `¬ p` → `p = False` via `eq_false`
- `p ↔ q` → `p = q` via `propext`
- `p` (proposition) → `p = True` via `eq_true`

Conjunctions (`p ∧ q`) are not handled here since the `SymM` E-graph
aggressively splits them via case analysis.

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 19:34:37 +00:00
Leonardo de Moura
9a3678935d feat: add sanity checks to register_sym_simp (#13040)
This PR adds validation to the `register_sym_simp` command:

- Reject duplicate variant names
- Validate `pre`/`post` syntax by elaborating them via `elabSymSimproc`
  in a minimal `GrindTacticM` context, catching unknown theorem names
  and unknown theorem set references at registration time

Adds `withGrindTacticM` helper for running `GrindTacticM` from
`CommandElabM`,
and `validateOptionSimprocSyntax` for optional syntax validation.

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 17:41:30 +00:00
Chris Henson
c9708e7bd7 doc: update docstring of Lean.Parser.Tactic.Grind.first (#12998)
This PR updates the docstring for `Lean.Parser.Tactic.Grind.first`,
which changed syntax in #10828.
2026-03-22 16:54:33 +00:00
Leonardo de Moura
8f6411ad57 feat: add interactive simp tactic for sym => mode (#13039)
This PR adds the `simp` tactic to the `sym =>` interactive mode,
completing
the `Sym.simp` interactive infrastructure.

The `simp` tactic supports:
- `simp` — default variant with `@[sym_simp]` theorem set, `simpControl
>> simpArrowTelescope` pre, `evalGround >> thms.rewrite` post
- `simp myVariant` — named variant registered via `register_sym_simp`
- `simp [thm₁, thm₂]` — default variant with extra rewrite theorems
appended to `post`
- `simp myVariant [thm₁, thm₂]` — named variant with extra theorems

Per-variant persistent caching: each unique (variant name, extra theorem
list)
combination gets its own `Sym.Simp.State` cache, shared across
invocations
within a `sym =>` block. Test 10 verifies cache hits using
`trace.sym.simp.debug.cache`.

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 15:55:05 +00:00
Kyle Miller
ae0d4e3ac4 chore: remove change ... with tactic syntax (#13029)
This PR removes the unused `change ... with` tactic syntax.

It's been asked about a number of times recently, but it was never
implemented. In PR #6018 we decided it was a Lean-3-ism.
2026-03-22 15:25:21 +00:00
Sebastian Ullrich
4bf7fa7447 chore: fix build after rebootstrap 2026-03-22 13:25:46 +01:00
Sebastian Ullrich
40558129cf chore: update stage0 2026-03-22 13:25:46 +01:00
Sebastian Ullrich
88b746dd48 feat: unfold and rewrap instances in inferInstanceAs and deriving
This PR adjusts the results of `inferInstanceAs` and the `def` `deriving` handler to conform to recently strengthened restrictions on reducibility. This change ensures that when deriving or inferring an instance for a semireducible type definition, the definition's RHS is not leaked when the instance is reduced at lower than semireducible transparency.

More specifically, given the "source type" and "target type" (the given and expected type for `inferInstanceAs`, the right-hand side and applied left-hand side of the `def` for `deriving`), we synthesize an instance for the source type and then unfold and rewrap its components (fields, nested instances) as necessary to make them compatible with the target type. The individual steps are represented by the following options, which all default to enabled and can be disabled to help with porting:
- `backward.inferInstanceAs.wrap`: master switch for instance adjustment in both `inferInstanceAs` and the default `deriving` handler
- `backward.inferInstanceAs.wrap.reuseSubInstances`: reuse existing instances for the target type for sub-instance fields to avoid non-defeq instance diamonds
- `backward.inferInstanceAs.wrap.instances`: wrap non-reducible instances in auxiliary definitions
- `backward.inferInstanceAs.wrap.data`: wrap data fields in auxiliary definitions (proof fields are always wrapped)

This PR is an extension and rewrite of prior work in Mathlib: https://github.com/leanprover-community/mathlib4/pull/36420

Last(?) part of fix for #9077

🤖 Prepared with Claude Code

# Breaking changes

Proofs that relied on the prior "defeq abuse" of these instance or that depended on their specific structure may need adjustments. As `inferInstanceAs A` now needs to know the source and target types exactly before it can continue, it cannot be used anymore as a synonym for `(inferInstance : A)`, use the latter instead when source and target type are identical.
2026-03-22 13:25:46 +01:00
Kim Morrison
2a25e4f3ae fix: nightly dispatch creates today's tag when retrying a failed nightly (#13035)
This PR fixes the `workflow_dispatch` path for nightly releases.
Previously,
when a scheduled nightly failed (so no tag was created) and someone
manually
re-triggered the workflow, it would find the most recent existing
nightly tag
(from a previous day) and create a `-revK` revision of that old tag. Now
it
checks if today's nightly tag exists: if not, it creates it directly; if
it
already exists, it creates a `-revK` revision as before.

🤖 Prepared with Claude Code

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 11:30:36 +00:00
Leonardo de Moura
13f8ce8492 feat: add register_sym_simp command and SymSimpVariant infrastructure (#13034)
This PR adds the `register_sym_simp` command for declaring named
`Sym.simp`
variants with `pre`/`post` simproc chains and optional config overrides.

```
register_sym_simp myVariant where
  pre  := telescope
  post := ground >> rewrite [thm1, thm2] with self
  maxSteps := 50000
```

- `SymSimpVariant` structure storing `pre?`/`post?` syntax (elaborated
at use
  time in `GrindTacticM`) and `Config` overrides
- `SimpleScopedEnvExtension` for persistent cross-module variant storage
- `register_sym_simp` command syntax with `sym_simp_field` category
- Command elaborator with syntax quotation matching and duplicate field
detection
- `getSymSimpVariant?` lookup function
- `deriving Inhabited` on `Simp.Config` for extension support

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 04:30:58 +00:00
Leonardo de Moura
dbfd0d35f2 fix: simp +arith and grind loop on normalized Int equalities (#13033)
This PR adds `r == e` guards to the `norm_eq_var` and
`norm_eq_var_const` branches of `Int.Linear.simpEq?`. Without these
guards, `simpEq?` returns a non-trivial proof for already-normalized
equations like `x = -1`, causing `exists_prop_congr` to fire repeatedly
and build an infinitely growing term.

The existing `Nat.simpCnstrPos?` already had the equivalent guard (`if r
!= lhs then ... else return none`).

Closes #12812

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-22 04:04:56 +00:00
Leonardo de Moura
b4c4f265aa fix: grind ring solver OOM on high-degree polynomials (#13032)
This PR fixes #12842 where `grind` exhausts memory on goals involving
high-degree polynomials such as `(x + y)^2 = x^128 + y^2` over `Fin 2`.

The root cause is that `incSteps` in the ring module's Groebner basis
engine increments the step counter by 1 per simplification, regardless
of polynomial size. For high-degree polynomials (e.g., degree 128),
intermediate results can have hundreds of terms, making each operation
extremely expensive — but the flat counter cannot catch this before
memory is exhausted.

The fix weights each step by `Poly.numTerms` of the result polynomial
and increases the default `ringSteps` from 10 000 to 100 000 to
accommodate the new cost model.

Note: the example from #12842 will not be *proved* by `grind` even after
this fix, because Frobenius / Fermat's little theorem (`x^p = x` in `Fin
p`) is not available in core Lean. The long-term plan is to introduce a
type class in core stating this property, with an instance provided by
Mathlib, so that `grind` can exploit it when Mathlib is imported.

Closes  #12842

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-22 02:52:44 +00:00
Leonardo de Moura
e848039ba9 feat: add built-in sym_simproc and sym_discharger elaborators (#13031)
This PR adds the built-in elaborators for the `sym_simproc` and
`sym_discharger` DSL syntax categories introduced in #13026.

Simproc elaborators (`@[builtin_sym_simproc]`):
- `ground` → `evalGround`
- `telescope` → `simpTelescope`
- `self` → `simp` (recursive simplification)
- `none` → identity (no simplification)
- `rewrite setName [with discharger]` → named theorem set rewriting
- `rewrite [thm₁, ...] [with discharger]` → inline theorem rewriting
- `>>` → `andThen` combinator
- `<|>` → `orElse` combinator

Discharger elaborators (`@[builtin_sym_discharger]`):
- `self` → `dischargeSimpSelf`
- `none` → `dischargeNone`

Note: `orElse` requires a fully-qualified attribute name
(`Lean.Parser.Sym.Simp.orElse`) to avoid a name resolution conflict
where the bare `orElse` resolves to a different syntax node kind.

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 02:39:12 +00:00
Leonardo de Moura
9fa1a252f2 fix: nondeterministic crash in grind congruence closure (#13027)
This PR fixes a nondeterministic crash in `grind` caused by a
`BEq`/`Hashable` invariant
violation in the congruence table. `congrHash` uses each expression's
own `funCC` flag to
compute its hash (one-level decomposition for `funCC = true`, full
recursive decomposition
for `funCC = false`), but `isCongruent` only checked the stored
expression's flag. When two
expressions with mismatched `funCC` flags accidentally hash-collided
(via pointer-based
`ptrAddrUnsafe` hashing), `isCongruent` could declare them congruent
despite different
argument counts, leading to an assertion failure in `mkCongrProof`.

The fix requires matching `funCC` flags in `isCongruent`. The PR also
fixes the debug
invariant checker (`checkParents`) to skip `funCC` parents and adds a
regression test for
funCC congruence.

Observed as a nondeterministic crash in Mathlib at
`Analysis/ODE/PicardLindelof.lean`.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-21 22:54:58 +00:00
Lean stage0 autoupdater
1cf030c5d4 chore: update stage0 2026-03-21 23:14:34 +00:00
Leonardo de Moura
545f27956b feat: add sym_simproc and sym_discharger DSL syntax categories (#13026)
This PR adds the infrastructure for simproc and discharger DSLs used to
specify `pre`/`post` simproc chains and conditional rewrite dischargers
in `Sym.simp` variants.

**Syntax categories** (`src/Init/Sym/Simp/SimprocDSL.lean`):
- `sym_simproc` with primitives (`ground`, `telescope`, `rewrite`,
`self`, `none`) and combinators (`>>`, `<|>`)
- `sym_discharger` with primitives (`self`, `none`) for the `with`
clause of `rewrite`

**Elaboration attributes**
(`src/Lean/Elab/Tactic/Grind/SimprocDSL.lean`):
- `builtin_sym_simproc` / `sym_simproc` mapping syntax to `Syntax →
GrindTacticM Simproc`
- `builtin_sym_discharger` / `sym_discharger` mapping syntax to `Syntax
→ GrindTacticM Discharger`
- `elabSymSimproc`, `elabSymDischarger`, and `elabWithClause`
dispatchers

Built-in elaborators for each primitive/combinator will follow in a
subsequent PR after the stage0 update.

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-21 22:29:25 +00:00
Leonardo de Moura
7897dc91e6 fix: grind fails to prove conjunction of independently provable goals (#13024)
This PR fixes an issue where `grind` could prove each conjunct
individually but failed on the conjunction. The root cause:
`solverAction`'s `.propagated` path calls `processNewFacts` which drains
the `newFacts` queue, but the resulting propagation cascade (congruence
closure, or-propagation, `propagateForallPropDown`) can call
`addNewRawFact`, enqueuing to the separate `newRawFacts` queue. These
raw facts were never drained.

The fix moves `Solvers.mkAction` from `Types.lean` to `Intro.lean` where
it can compose the core solver action with `assertAll`, unconditionally
draining `newRawFacts` after every solver step.

Closes #12581

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-21 17:04:41 +00:00
Sebastian Ullrich
98f5266407 chore: remove temp bootstrap code (#13021) 2026-03-21 16:41:14 +00:00
Sebastian Ullrich
90b5e3185b fix: export ABI mismatch (#13022) 2026-03-21 14:52:23 +00:00
Leonardo de Moura
973062e4e1 feat: add Sym.simp theorem set attributes (#13018)
This PR adds named theorem sets for `Sym.simp` with associated
attributes, following the same pattern as `Meta.simp`'s
`register_simp_attr`.

- `register_sym_simp_attr my_set` creates a named set with its own
`PersistentEnvExtension` and attribute
- `@[my_set] theorem ...` adds a rewrite theorem
- `@[my_set] def ...` adds equation theorems from the definition
- `builtin_initialize symSimpExtension` registers a default
`@[sym_simp]` set
- `getSymSimpTheorems` / `getSymSimpExtension?` retrieve theorem sets at
tactic time

New files:
- `Sym/Simp/Attr.lean`: attribute logic (`mkSymSimpAttr`,
`registerSymSimpAttr`)
- `Sym/Simp/RegisterCommand.lean`: `register_sym_simp_attr` macro

Tests:
- `tests/pkg/sym_simp_attr/`: package test with user-defined set
(`my_sym_simp`)
- `tests/elab/sym_simp_set.lean`: tests for the builtin `@[sym_simp]`
set
2026-03-21 03:53:39 +00:00
1279 changed files with 3076 additions and 1006 deletions

View File

@@ -33,7 +33,7 @@ jobs:
include: ${{fromJson(inputs.config)}}
# complete all jobs
fail-fast: false
runs-on: ${{ endsWith(matrix.os, '-with-cache') && fromJSON(format('["{0}", "nscloud-git-mirror-1gb"]', matrix.os)) || matrix.os }}
runs-on: ${{ endsWith(matrix.os, '-with-cache') && fromJSON(format('["{0}", "nscloud-git-mirror-5gb"]', matrix.os)) || matrix.os }}
defaults:
run:
shell: ${{ matrix.shell || 'nix develop -c bash -euxo pipefail {0}' }}

29
.github/workflows/check-empty-pr.yml vendored Normal file
View File

@@ -0,0 +1,29 @@
name: Check for empty PR
on:
merge_group:
pull_request:
jobs:
check-empty-pr:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
with:
ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.sha }}
fetch-depth: 0
filter: tree:0
- name: Check for empty diff
run: |
if [[ "${{ github.event_name }}" == "pull_request" ]]; then
base=$(git merge-base "origin/${{ github.base_ref }}" HEAD)
else
base=$(git rev-parse HEAD^1)
fi
if git diff --quiet "$base" HEAD --; then
echo "This PR introduces no changes compared to its base branch." | tee "$GITHUB_STEP_SUMMARY"
echo "It may be a duplicate of an already-merged PR." | tee -a "$GITHUB_STEP_SUMMARY"
exit 1
fi
shell: bash

View File

@@ -61,15 +61,19 @@ jobs:
git remote add nightly https://foo:'${{ secrets.PUSH_NIGHTLY_TOKEN }}'@github.com/${{ github.repository_owner }}/lean4-nightly.git
git fetch nightly --tags
if [[ '${{ github.event_name }}' == 'workflow_dispatch' ]]; then
# Manual re-release: create a revision of the most recent nightly
BASE_NIGHTLY=$(git tag -l 'nightly-*' | sort -rV | head -1)
# Strip any existing -revK suffix to get the base date tag
BASE_NIGHTLY="${BASE_NIGHTLY%%-rev*}"
REV=1
while git rev-parse "refs/tags/${BASE_NIGHTLY}-rev${REV}" >/dev/null 2>&1; do
REV=$((REV + 1))
done
LEAN_VERSION_STRING="${BASE_NIGHTLY}-rev${REV}"
# Manual re-release: retry today's nightly, or create a revision if it already exists
TODAY_NIGHTLY="nightly-$(date -u +%F)"
if git rev-parse "refs/tags/${TODAY_NIGHTLY}" >/dev/null 2>&1; then
# Today's nightly already exists, create a revision
REV=1
while git rev-parse "refs/tags/${TODAY_NIGHTLY}-rev${REV}" >/dev/null 2>&1; do
REV=$((REV + 1))
done
LEAN_VERSION_STRING="${TODAY_NIGHTLY}-rev${REV}"
else
# Today's nightly doesn't exist yet (e.g. scheduled run failed), create it
LEAN_VERSION_STRING="${TODAY_NIGHTLY}"
fi
echo "nightly=$LEAN_VERSION_STRING" >> "$GITHUB_OUTPUT"
else
# Scheduled: do nothing if commit already has a different tag

View File

@@ -1,3 +1,4 @@
30
interp.lean:146:4: warning: declaration uses `sorry`
interp.lean:146:0: warning: declaration uses `sorry`
3628800

View File

@@ -236,7 +236,7 @@ def parse_version(version_str):
def is_version_gte(version1, version2):
"""Check if version1 >= version2, including proper handling of release candidates."""
# Check if version1 is a nightly toolchain
if version1.startswith("leanprover/lean4:nightly-"):
if version1.startswith("leanprover/lean4:nightly-") or version1.startswith("leanprover/lean4-nightly:"):
return False
return parse_version(version1) >= parse_version(version2)

View File

@@ -72,11 +72,11 @@ public instance [Monad m] [LawfulMonad m] [MonadAttach m] [LawfulMonadAttach m]
public instance [Monad m] [MonadAttach m] [LawfulMonad m] [WeaklyLawfulMonadAttach m] :
WeaklyLawfulMonadAttach (StateRefT' ω σ m) :=
inferInstanceAs (WeaklyLawfulMonadAttach (ReaderT _ _))
inferInstanceAs (WeaklyLawfulMonadAttach (ReaderT (ST.Ref ω σ) m))
public instance [Monad m] [MonadAttach m] [LawfulMonad m] [LawfulMonadAttach m] :
LawfulMonadAttach (StateRefT' ω σ m) :=
inferInstanceAs (LawfulMonadAttach (ReaderT _ _))
inferInstanceAs (LawfulMonadAttach (ReaderT (ST.Ref ω σ) m))
section

View File

@@ -103,11 +103,11 @@ namespace StateRefT'
instance {ω σ : Type} {m : Type Type} [Monad m] : LawfulMonadLift m (StateRefT' ω σ m) where
monadLift_pure _ := by
simp only [MonadLift.monadLift, pure]
unfold StateRefT'.lift ReaderT.pure
unfold StateRefT'.lift instMonad._aux_5 ReaderT.pure
simp only
monadLift_bind _ _ := by
simp only [MonadLift.monadLift, bind]
unfold StateRefT'.lift ReaderT.bind
unfold StateRefT'.lift instMonad._aux_13 ReaderT.bind
simp only
end StateRefT'

View File

@@ -66,3 +66,8 @@ theorem BEq.neq_of_beq_of_neq [BEq α] [PartialEquivBEq α] {a b c : α} :
instance (priority := low) [BEq α] [LawfulBEq α] : EquivBEq α where
symm h := beq_iff_eq.2 <| Eq.symm <| beq_iff_eq.1 h
trans hab hbc := beq_iff_eq.2 <| (beq_iff_eq.1 hab).trans <| beq_iff_eq.1 hbc
theorem equivBEq_of_iff_apply_eq [BEq α] (f : α β) (hf : a b, a == b f a = f b) : EquivBEq α where
rfl := by simp [hf]
symm := by simp [hf, eq_comm]
trans hab hbc := (hf _ _).2 (Eq.trans ((hf _ _).1 hab) ((hf _ _).1 hbc))

View File

@@ -168,6 +168,13 @@ instance Map.instIterator {α β γ : Type w} {m : Type w → Type w'} {n : Type
Iterator (Map α m n lift f) n γ :=
inferInstanceAs <| Iterator (FilterMap α m n lift _) n γ
theorem Map.instIterator_eq_filterMapInstIterator {α β γ : Type w} {m : Type w Type w'}
{n : Type w Type w''} [Monad n]
[Iterator α m β] {lift : α : Type w m α n α} {f : β PostconditionT n γ} :
Map.instIterator (α := α) (β := β) (γ := γ) (m := m) (n := n) (lift := lift) (f := f) =
FilterMap.instIterator :=
rfl
private def FilterMap.instFinitenessRelation {α β γ : Type w} {m : Type w Type w'}
{n : Type w Type w''} [Monad n] [Iterator α m β] {lift : α : Type w m α n α}
{f : β PostconditionT n (Option γ)} [Finite α m] :

View File

@@ -699,18 +699,16 @@ theorem IterM.toList_map {α β β' : Type w} {m : Type w → Type w'} [Monad m]
(it : IterM (α := α) m β) :
(it.map f).toList = (fun x => x.map f) <$> it.toList := by
rw [ List.filterMap_eq_map, toList_filterMap]
let t := type_of% (it.map f)
let t' := type_of% (it.filterMap (some f))
simp only [map, mapWithPostcondition, InternalCombinators.map, filterMap,
filterMapWithPostcondition, InternalCombinators.filterMap]
unfold Map
congr
· simp [Map]
· simp [Map.instIterator, inferInstanceAs]
· simp
· rw [Map.instIterator_eq_filterMapInstIterator]
congr
simp
· simp only [map, mapWithPostcondition, InternalCombinators.map, Function.comp_apply, filterMap,
filterMapWithPostcondition, InternalCombinators.filterMap]
congr
· simp [Map]
· simp
· simp
· simp
@[simp]
theorem IterM.toList_filter {α : Type w} {m : Type w Type w'} [Monad m] [LawfulMonad m]
@@ -1310,7 +1308,8 @@ theorem IterM.forIn_mapWithPostcondition
haveI : MonadLift n o := monadLift
forIn (it.mapWithPostcondition f) init g =
forIn it init (fun out acc => do g ( (f out).run) acc) := by
unfold mapWithPostcondition InternalCombinators.map Map.instIterator Map.instIteratorLoop Map
unfold mapWithPostcondition InternalCombinators.map Map.instIteratorLoop Map
rw [Map.instIterator_eq_filterMapInstIterator]
rw [ InternalCombinators.filterMap, filterMapWithPostcondition, forIn_filterMapWithPostcondition]
simp

View File

@@ -481,13 +481,13 @@ protected theorem maxIdxOn_nil_eq_iff_false [LE β] [DecidableLE β] {f : α
@[simp]
protected theorem maxIdxOn_singleton [LE β] [DecidableLE β] {x : α} {f : α β} :
[x].maxIdxOn f (of_decide_eq_false rfl) = 0 :=
letI : LE β := (inferInstanceAs (LE β)).opposite
letI : LE β := (inferInstance : LE β).opposite
List.minIdxOn_singleton
@[simp]
protected theorem maxIdxOn_lt_length [LE β] [DecidableLE β] {f : α β} {xs : List α}
(h : xs []) : xs.maxIdxOn f h < xs.length :=
letI : LE β := (inferInstanceAs (LE β)).opposite
letI : LE β := (inferInstance : LE β).opposite
List.minIdxOn_lt_length h
protected theorem maxIdxOn_le_of_apply_getElem_le_apply_maxOn [LE β] [DecidableLE β] [IsLinearPreorder β]
@@ -495,7 +495,7 @@ protected theorem maxIdxOn_le_of_apply_getElem_le_apply_maxOn [LE β] [Decidable
{k : Nat} (hi : k < xs.length) (hle : f (xs.maxOn f h) f xs[k]) :
xs.maxIdxOn f h k := by
simp only [List.maxIdxOn_eq_minIdxOn, List.maxOn_eq_minOn] at hle
letI : LE β := (inferInstanceAs (LE β)).opposite
letI : LE β := (inferInstance : LE β).opposite
exact List.minIdxOn_le_of_apply_getElem_le_apply_minOn h hi (by simpa [LE.le_opposite_iff] using hle)
protected theorem apply_maxOn_lt_apply_getElem_of_lt_maxIdxOn [LE β] [DecidableLE β] [LT β] [IsLinearPreorder β]
@@ -513,7 +513,7 @@ protected theorem getElem_maxIdxOn [LE β] [DecidableLE β] [IsLinearPreorder β
{f : α β} {xs : List α} (h : xs []) :
xs[xs.maxIdxOn f h] = xs.maxOn f h := by
simp only [List.maxIdxOn_eq_minIdxOn, List.maxOn_eq_minOn]
letI : LE β := (inferInstanceAs (LE β)).opposite
letI : LE β := (inferInstance : LE β).opposite
exact List.getElem_minIdxOn h
protected theorem le_maxIdxOn_of_apply_getElem_lt_apply_getElem [LE β] [DecidableLE β] [LT β]
@@ -562,14 +562,14 @@ protected theorem maxIdxOn_cons
else if f (xs.maxOn f h) f x then 0
else (xs.maxIdxOn f h) + 1 := by
simp only [List.maxIdxOn_eq_minIdxOn, List.maxOn_eq_minOn]
letI : LE β := (inferInstanceAs (LE β)).opposite
letI : LE β := (inferInstance : LE β).opposite
simpa [LE.le_opposite_iff] using List.minIdxOn_cons (f := f)
protected theorem maxIdxOn_eq_zero_iff [LE β] [DecidableLE β] [IsLinearPreorder β]
{xs : List α} {f : α β} (h : xs []) :
xs.maxIdxOn f h = 0 x xs, f x f (xs.head h) := by
simp only [List.maxIdxOn_eq_minIdxOn]
letI : LE β := (inferInstanceAs (LE β)).opposite
letI : LE β := (inferInstance : LE β).opposite
simpa [LE.le_opposite_iff] using List.minIdxOn_eq_zero_iff h (f := f)
protected theorem maxIdxOn_append [LE β] [DecidableLE β] [IsLinearPreorder β]
@@ -580,26 +580,26 @@ protected theorem maxIdxOn_append [LE β] [DecidableLE β] [IsLinearPreorder β]
else
xs.length + ys.maxIdxOn f hys := by
simp only [List.maxIdxOn_eq_minIdxOn, List.maxOn_eq_minOn]
letI : LE β := (inferInstanceAs (LE β)).opposite
letI : LE β := (inferInstance : LE β).opposite
simpa [LE.le_opposite_iff] using List.minIdxOn_append hxs hys (f := f)
protected theorem left_le_maxIdxOn_append [LE β] [DecidableLE β] [IsLinearPreorder β]
{xs ys : List α} {f : α β} (h : xs []) :
xs.maxIdxOn f h (xs ++ ys).maxIdxOn f (by simp [h]) :=
letI : LE β := (inferInstanceAs (LE β)).opposite
letI : LE β := (inferInstance : LE β).opposite
List.left_le_minIdxOn_append h
protected theorem maxIdxOn_take_le [LE β] [DecidableLE β] [IsLinearPreorder β]
{xs : List α} {f : α β} {i : Nat} (h : xs.take i []) :
(xs.take i).maxIdxOn f h xs.maxIdxOn f (List.ne_nil_of_take_ne_nil h) :=
letI : LE β := (inferInstanceAs (LE β)).opposite
letI : LE β := (inferInstance : LE β).opposite
List.minIdxOn_take_le h
@[simp]
protected theorem maxIdxOn_replicate [LE β] [DecidableLE β] [Refl (α := β) (· ·)]
{n : Nat} {a : α} {f : α β} (h : replicate n a []) :
(replicate n a).maxIdxOn f h = 0 :=
letI : LE β := (inferInstanceAs (LE β)).opposite
letI : LE β := (inferInstance : LE β).opposite
List.minIdxOn_replicate h
@[simp]

View File

@@ -297,13 +297,13 @@ protected theorem maxOn_cons
(x :: xs).maxOn f (by exact of_decide_eq_false rfl) =
if h : xs = [] then x else maxOn f x (xs.maxOn f h) := by
simp only [maxOn_eq_minOn]
letI : LE β := (inferInstanceAs (LE β)).opposite
letI : LE β := (inferInstance : LE β).opposite
exact List.minOn_cons (f := f)
protected theorem maxOn_cons_cons [LE β] [DecidableLE β] {a b : α} {l : List α} {f : α β} :
(a :: b :: l).maxOn f (by simp) = (maxOn f a b :: l).maxOn f (by simp) := by
simp only [List.maxOn_eq_minOn, maxOn_eq_minOn]
letI : LE β := (inferInstanceAs (LE β)).opposite
letI : LE β := (inferInstance : LE β).opposite
exact List.minOn_cons_cons
@[simp]
@@ -334,51 +334,51 @@ protected theorem maxOn_id [Max α] [LE α] [DecidableLE α] [LawfulOrderLeftLea
{xs : List α} (h : xs []) :
xs.maxOn id h = xs.max h := by
simp only [List.maxOn_eq_minOn]
letI : LE α := (inferInstanceAs (LE α)).opposite
letI : Min α := (inferInstanceAs (Max α)).oppositeMin
letI : LE α := (inferInstance : LE α).opposite
letI : Min α := (inferInstance : Max α).oppositeMin
simpa only [List.max_eq_min] using List.minOn_id h
@[simp]
protected theorem maxOn_mem [LE β] [DecidableLE β] {xs : List α}
{f : α β} {h : xs []} : xs.maxOn f h xs :=
letI : LE β := (inferInstanceAs (LE β)).opposite
letI : LE β := (inferInstance : LE β).opposite
List.minOn_mem (f := f)
protected theorem le_apply_maxOn_of_mem [LE β] [DecidableLE β] [IsLinearPreorder β]
{xs : List α} {f : α β} {y : α} (hx : y xs) :
f y f (xs.maxOn f (List.ne_nil_of_mem hx)) := by
rw [List.maxOn_eq_minOn]
letI : LE β := (inferInstanceAs (LE β)).opposite
letI : LE β := (inferInstance : LE β).opposite
simpa [LE.le_opposite_iff] using List.apply_minOn_le_of_mem (f := f) hx
protected theorem apply_maxOn_le_iff [LE β] [DecidableLE β] [IsLinearPreorder β] {xs : List α}
{f : α β} (h : xs []) {b : β} :
f (xs.maxOn f h) b x xs, f x b := by
rw [List.maxOn_eq_minOn]
letI : LE β := (inferInstanceAs (LE β)).opposite
letI : LE β := (inferInstance : LE β).opposite
simpa [LE.le_opposite_iff] using List.le_apply_minOn_iff (f := f) h
protected theorem le_apply_maxOn_iff [LE β] [DecidableLE β] [IsLinearPreorder β] {xs : List α}
{f : α β} (h : xs []) {b : β} :
b f (xs.maxOn f h) x xs, b f x := by
rw [List.maxOn_eq_minOn]
letI : LE β := (inferInstanceAs (LE β)).opposite
letI : LE β := (inferInstance : LE β).opposite
simpa [LE.le_opposite_iff] using List.apply_minOn_le_iff (f := f) h
protected theorem apply_maxOn_lt_iff
[LE β] [DecidableLE β] [LT β] [IsLinearPreorder β] [LawfulOrderLT β]
{xs : List α} {f : α β} (h : xs []) {b : β} :
f (xs.maxOn f h) < b x xs, f x < b := by
letI : LE β := (inferInstanceAs (LE β)).opposite
letI : LT β := (inferInstanceAs (LT β)).opposite
letI : LE β := (inferInstance : LE β).opposite
letI : LT β := (inferInstance : LT β).opposite
simpa [LT.lt_opposite_iff] using List.lt_apply_minOn_iff (f := f) h
protected theorem lt_apply_maxOn_iff
[LE β] [DecidableLE β] [LT β] [IsLinearPreorder β] [LawfulOrderLT β]
{xs : List α} {f : α β} (h : xs []) {b : β} :
b < f (xs.maxOn f h) x xs, b < f x := by
letI : LE β := (inferInstanceAs (LE β)).opposite
letI : LT β := (inferInstanceAs (LT β)).opposite
letI : LE β := (inferInstance : LE β).opposite
letI : LT β := (inferInstance : LT β).opposite
simpa [LT.lt_opposite_iff] using List.apply_minOn_lt_iff (f := f) h
protected theorem apply_maxOn_le_apply_maxOn_of_subset [LE β] [DecidableLE β]
@@ -386,14 +386,14 @@ protected theorem apply_maxOn_le_apply_maxOn_of_subset [LE β] [DecidableLE β]
haveI : xs [] := by intro h; rw [h] at hxs; simp_all [subset_nil]
f (ys.maxOn f hys) f (xs.maxOn f this) := by
rw [List.maxOn_eq_minOn]
letI : LE β := (inferInstanceAs (LE β)).opposite
letI : LE β := (inferInstance : LE β).opposite
simpa [LE.le_opposite_iff] using List.apply_minOn_le_apply_minOn_of_subset (f := f) hxs hys
protected theorem apply_maxOn_take_le [LE β] [DecidableLE β] [IsLinearPreorder β]
{xs : List α} {f : α β} {i : Nat} (h : xs.take i []) :
f ((xs.take i).maxOn f h) f (xs.maxOn f (List.ne_nil_of_take_ne_nil h)) := by
rw [List.maxOn_eq_minOn]
letI : LE β := (inferInstanceAs (LE β)).opposite
letI : LE β := (inferInstance : LE β).opposite
simpa [LE.le_opposite_iff] using List.le_apply_minOn_take (f := f) h
protected theorem le_apply_maxOn_append_left [LE β] [DecidableLE β] [IsLinearPreorder β]
@@ -401,7 +401,7 @@ protected theorem le_apply_maxOn_append_left [LE β] [DecidableLE β] [IsLinearP
f (xs.maxOn f h)
f ((xs ++ ys).maxOn f (append_ne_nil_of_left_ne_nil h ys)) := by
rw [List.maxOn_eq_minOn]
letI : LE β := (inferInstanceAs (LE β)).opposite
letI : LE β := (inferInstance : LE β).opposite
simpa [LE.le_opposite_iff] using List.apply_minOn_append_le_left (f := f) h
protected theorem le_apply_maxOn_append_right [LE β] [DecidableLE β] [IsLinearPreorder β]
@@ -409,7 +409,7 @@ protected theorem le_apply_maxOn_append_right [LE β] [DecidableLE β] [IsLinear
f (ys.maxOn f h)
f ((xs ++ ys).maxOn f (append_ne_nil_of_right_ne_nil xs h)) := by
rw [List.maxOn_eq_minOn]
letI : LE β := (inferInstanceAs (LE β)).opposite
letI : LE β := (inferInstance : LE β).opposite
simpa [LE.le_opposite_iff] using List.apply_minOn_append_le_right (f := f) h
@[simp]
@@ -417,21 +417,21 @@ protected theorem maxOn_append [LE β] [DecidableLE β] [IsLinearPreorder β] {x
{f : α β} (hxs : xs []) (hys : ys []) :
(xs ++ ys).maxOn f (by simp [hxs]) = maxOn f (xs.maxOn f hxs) (ys.maxOn f hys) := by
simp only [List.maxOn_eq_minOn, maxOn_eq_minOn]
letI : LE β := (inferInstanceAs (LE β)).opposite
letI : LE β := (inferInstance : LE β).opposite
simpa [LE.le_opposite_iff] using List.minOn_append (f := f) hxs hys
protected theorem maxOn_eq_head [LE β] [DecidableLE β] [IsLinearPreorder β] {xs : List α}
{f : α β} (h : xs []) (h' : x xs, f x f (xs.head h)) :
xs.maxOn f h = xs.head h := by
rw [List.maxOn_eq_minOn]
letI : LE β := (inferInstanceAs (LE β)).opposite
letI : LE β := (inferInstance : LE β).opposite
simpa [LE.le_opposite_iff] using List.minOn_eq_head (f := f) h (by simpa [LE.le_opposite_iff] using h')
protected theorem max_map
[LE β] [DecidableLE β] [Max β] [IsLinearPreorder β] [LawfulOrderLeftLeaningMax β] {xs : List α}
{f : α β} (h : xs []) : (xs.map f).max (by simpa) = f (xs.maxOn f h) := by
letI : LE β := (inferInstanceAs (LE β)).opposite
letI : Min β := (inferInstanceAs (Max β)).oppositeMin
letI : LE β := (inferInstance : LE β).opposite
letI : Min β := (inferInstance : Max β).oppositeMin
simpa [List.max_eq_min] using List.min_map (f := f) h
protected theorem maxOn_eq_max [Max α] [LE α] [DecidableLE α] [LawfulOrderLeftLeaningMax α]
@@ -458,7 +458,7 @@ protected theorem max_map_eq_max [Max α] [LE α] [DecidableLE α] [LawfulOrderL
protected theorem maxOn_replicate [LE β] [DecidableLE β] [IsLinearPreorder β]
{n : Nat} {a : α} {f : α β} (h : replicate n a []) :
(replicate n a).maxOn f h = a :=
letI : LE β := (inferInstanceAs (LE β)).opposite
letI : LE β := (inferInstance : LE β).opposite
List.minOn_replicate (f := f) h
/-! # minOn? -/
@@ -579,7 +579,7 @@ protected theorem maxOn?_nil [LE β] [DecidableLE β] {f : α → β} :
protected theorem maxOn?_cons_eq_some_maxOn
[LE β] [DecidableLE β] {f : α β} {x : α} {xs : List α} :
(x :: xs).maxOn? f = some ((x :: xs).maxOn f (fun h => nomatch h)) :=
letI : LE β := (inferInstanceAs (LE β)).opposite
letI : LE β := (inferInstance : LE β).opposite
List.minOn?_cons_eq_some_minOn
protected theorem maxOn?_cons
@@ -588,7 +588,7 @@ protected theorem maxOn?_cons
have : maxOn f x = (letI : LE β := LE.opposite inferInstance; minOn f x) := by
ext; simp only [maxOn_eq_minOn]
simp only [List.maxOn?_eq_minOn?, this]
letI : LE β := (inferInstanceAs (LE β)).opposite
letI : LE β := (inferInstance : LE β).opposite
exact List.minOn?_cons
@[simp]
@@ -599,8 +599,8 @@ protected theorem maxOn?_singleton [LE β] [DecidableLE β] {x : α} {f : α
@[simp]
protected theorem maxOn?_id [Max α] [LE α] [DecidableLE α] [LawfulOrderLeftLeaningMax α]
{xs : List α} : xs.maxOn? id = xs.max? := by
letI : LE α := (inferInstanceAs (LE α)).opposite
letI : Min α := (inferInstanceAs (Max α)).oppositeMin
letI : LE α := (inferInstance : LE α).opposite
letI : Min α := (inferInstance : Max α).oppositeMin
simpa only [List.maxOn?_eq_minOn?, List.max?_eq_min?] using List.minOn?_id (α := α)
protected theorem maxOn?_eq_if
@@ -610,7 +610,7 @@ protected theorem maxOn?_eq_if
some (xs.maxOn f h)
else
none :=
letI : LE β := (inferInstanceAs (LE β)).opposite
letI : LE β := (inferInstance : LE β).opposite
List.minOn?_eq_if
@[simp]
@@ -620,55 +620,55 @@ protected theorem isSome_maxOn?_iff [LE β] [DecidableLE β] {f : α → β} {xs
protected theorem maxOn_eq_get_maxOn? [LE β] [DecidableLE β] {f : α β} {xs : List α}
(h : xs []) : xs.maxOn f h = (xs.maxOn? f).get (List.isSome_maxOn?_iff.mpr h) :=
letI : LE β := (inferInstanceAs (LE β)).opposite
letI : LE β := (inferInstance : LE β).opposite
List.minOn_eq_get_minOn? (f := f) h
protected theorem maxOn?_eq_some_maxOn [LE β] [DecidableLE β] {f : α β} {xs : List α}
(h : xs []) : xs.maxOn? f = some (xs.maxOn f h) :=
letI : LE β := (inferInstanceAs (LE β)).opposite
letI : LE β := (inferInstance : LE β).opposite
List.minOn?_eq_some_minOn (f := f) h
@[simp]
protected theorem get_maxOn? [LE β] [DecidableLE β] {f : α β} {xs : List α}
(h : xs []) : (xs.maxOn? f).get (List.isSome_maxOn?_iff.mpr h) = xs.maxOn f h :=
letI : LE β := (inferInstanceAs (LE β)).opposite
letI : LE β := (inferInstance : LE β).opposite
List.get_minOn? (f := f) h
protected theorem maxOn_eq_of_maxOn?_eq_some
[LE β] [DecidableLE β] {f : α β} {xs : List α} {x : α} (h : xs.maxOn? f = some x) :
xs.maxOn f (List.isSome_maxOn?_iff.mp (Option.isSome_of_eq_some h)) = x :=
letI : LE β := (inferInstanceAs (LE β)).opposite
letI : LE β := (inferInstance : LE β).opposite
List.minOn_eq_of_minOn?_eq_some (f := f) h
protected theorem isSome_maxOn?_of_mem
[LE β] [DecidableLE β] {f : α β} {xs : List α} {x : α} (h : x xs) :
(xs.maxOn? f).isSome :=
letI : LE β := (inferInstanceAs (LE β)).opposite
letI : LE β := (inferInstance : LE β).opposite
List.isSome_minOn?_of_mem (f := f) h
protected theorem le_apply_get_maxOn?_of_mem
[LE β] [DecidableLE β] [IsLinearPreorder β] {f : α β} {xs : List α} {x : α} (h : x xs) :
f x f ((xs.maxOn? f).get (List.isSome_maxOn?_of_mem h)) := by
simp only [List.maxOn?_eq_minOn?]
letI : LE β := (inferInstanceAs (LE β)).opposite
letI : LE β := (inferInstance : LE β).opposite
simpa [LE.le_opposite_iff] using List.apply_get_minOn?_le_of_mem (f := f) h
protected theorem maxOn?_mem [LE β] [DecidableLE β] {xs : List α}
{f : α β} (h : xs.maxOn? f = some a) : a xs :=
letI : LE β := (inferInstanceAs (LE β)).opposite
letI : LE β := (inferInstance : LE β).opposite
List.minOn?_mem (f := f) h
protected theorem maxOn?_replicate [LE β] [DecidableLE β] [IsLinearPreorder β]
{n : Nat} {a : α} {f : α β} :
(replicate n a).maxOn? f = if n = 0 then none else some a :=
letI : LE β := (inferInstanceAs (LE β)).opposite
letI : LE β := (inferInstance : LE β).opposite
List.minOn?_replicate
@[simp]
protected theorem maxOn?_replicate_of_pos [LE β] [DecidableLE β] [IsLinearPreorder β]
{n : Nat} {a : α} {f : α β} (h : 0 < n) :
(replicate n a).maxOn? f = some a :=
letI : LE β := (inferInstanceAs (LE β)).opposite
letI : LE β := (inferInstance : LE β).opposite
List.minOn?_replicate_of_pos (f := f) h
@[simp]
@@ -678,7 +678,7 @@ protected theorem maxOn?_append [LE β] [DecidableLE β] [IsLinearPreorder β]
have : maxOn f = (letI : LE β := LE.opposite inferInstance; minOn f) := by
ext; simp only [maxOn_eq_minOn]
simp only [List.maxOn?_eq_minOn?, this]
letI : LE β := (inferInstanceAs (LE β)).opposite
letI : LE β := (inferInstance : LE β).opposite
exact List.minOn?_append xs ys f
end List

View File

@@ -39,8 +39,8 @@ public theorem minOn_id [Min α] [LE α] [DecidableLE α] [LawfulOrderLeftLeanin
public theorem maxOn_id [Max α] [LE α] [DecidableLE α] [LawfulOrderLeftLeaningMax α] {x y : α} :
maxOn id x y = max x y := by
letI : LE α := (inferInstanceAs (LE α)).opposite
letI : Min α := (inferInstanceAs (Max α)).oppositeMin
letI : LE α := (inferInstance : LE α).opposite
letI : Min α := (inferInstance : Max α).oppositeMin
simp [maxOn, minOn_id, Max.min_oppositeMin, this]
public theorem minOn_eq_or [LE β] [DecidableLE β] {f : α β} {x y : α} :
@@ -168,32 +168,32 @@ public theorem maxOn_eq_right_of_lt
[LE β] [DecidableLE β] [LT β] [Total (α := β) (· ·)] [LawfulOrderLT β]
{f : α β} {x y : α} (h : f x < f y) :
maxOn f x y = y :=
letI : LE β := (inferInstanceAs (LE β)).opposite
letI : LT β := (inferInstanceAs (LT β)).opposite
letI : LE β := (inferInstance : LE β).opposite
letI : LT β := (inferInstance : LT β).opposite
minOn_eq_right_of_lt (h := by simpa [LT.lt_opposite_iff] using h) ..
public theorem left_le_apply_maxOn [le : LE β] [DecidableLE β] [IsLinearPreorder β] {f : α β}
{x y : α} : f x f (maxOn f x y) := by
rw [maxOn_eq_minOn]
letI : LE β := (inferInstanceAs (LE β)).opposite
letI : LE β := (inferInstance : LE β).opposite
simpa only [LE.le_opposite_iff] using apply_minOn_le_left (f := f) ..
public theorem right_le_apply_maxOn [LE β] [DecidableLE β] [IsLinearPreorder β] {f : α β}
{x y : α} : f y f (maxOn f x y) := by
rw [maxOn_eq_minOn]
letI : LE β := (inferInstanceAs (LE β)).opposite
letI : LE β := (inferInstance : LE β).opposite
simpa only [LE.le_opposite_iff] using apply_minOn_le_right (f := f)
public theorem apply_maxOn_le_iff [LE β] [DecidableLE β] [IsLinearPreorder β] {f : α β}
{x y : α} {b : β} :
f (maxOn f x y) b f x b f y b := by
rw [maxOn_eq_minOn]
letI : LE β := (inferInstanceAs (LE β)).opposite
letI : LE β := (inferInstance : LE β).opposite
simpa only [LE.le_opposite_iff] using le_apply_minOn_iff (f := f)
public theorem maxOn_assoc [LE β] [DecidableLE β] [IsLinearPreorder β] {f : α β}
{x y z : α} : maxOn f (maxOn f x y) z = maxOn f x (maxOn f y z) :=
letI : LE β := (inferInstanceAs (LE β)).opposite
letI : LE β := (inferInstance : LE β).opposite
minOn_assoc (f := f)
public instance [LE β] [DecidableLE β] [IsLinearPreorder β] {f : α β} :
@@ -203,8 +203,8 @@ public instance [LE β] [DecidableLE β] [IsLinearPreorder β] {f : α → β} :
public theorem max_apply [LE β] [DecidableLE β] [Max β] [LawfulOrderLeftLeaningMax β]
{f : α β} {x y : α} : max (f x) (f y) = f (maxOn f x y) := by
letI : LE β := (inferInstanceAs (LE β)).opposite
letI : Min β := (inferInstanceAs (Max β)).oppositeMin
letI : LE β := (inferInstance : LE β).opposite
letI : Min β := (inferInstance : Max β).oppositeMin
simpa [Max.min_oppositeMin] using min_apply (f := f)
public theorem apply_maxOn [LE β] [DecidableLE β] [Max β] [LawfulOrderLeftLeaningMax β]

View File

@@ -44,7 +44,7 @@ def min' [LE α] [DecidableLE α] (a b : α) : α :=
open scoped Std.OppositeOrderInstances in
def max' [LE α] [DecidableLE α] (a b : α) : α :=
letI : LE α := (inferInstanceAs (LE α)).opposite
letI : LE α := (inferInstance : LE α).opposite
-- `DecidableLE` for the opposite order is derived automatically via `OppositeOrderInstances`
min' a b
```

View File

@@ -852,6 +852,10 @@ theorem Slice.rawEndPos_copy {s : Slice} : s.copy.rawEndPos = s.rawEndPos := by
theorem copy_toSlice {s : String} : s.toSlice.copy = s := by
simp [ toByteArray_inj, Slice.toByteArray_copy, size_toByteArray]
@[simp]
theorem copy_comp_toSlice : String.Slice.copy String.toSlice = id := by
ext; simp
theorem Slice.getUTF8Byte_eq_getUTF8Byte_copy {s : Slice} {p : Pos.Raw} {h : p < s.rawEndPos} :
s.getUTF8Byte p h = s.copy.getUTF8Byte p (by simpa) := by
simp [getUTF8Byte, String.getUTF8Byte, toByteArray_copy, ByteArray.getElem_extract]

View File

@@ -6,29 +6,5 @@ Authors: Markus Himmel
module
prelude
public import Init.Data.Iterators.Combinators.FilterMap
public import Init.Data.Iterators.Consumers.Collect
set_option doc.verso true
namespace Std
/--
Convenience function for turning an iterator into a list of strings, provided the output of the
iterator implements {name}`ToString`.
-/
@[inline]
public abbrev Iter.toStringList {α β : Type} [Iterator α Id β] [ToString β]
(it : Iter (α := α) β) : List String :=
it.map toString |>.toList
/--
Convenience function for turning an iterator into an array of strings, provided the output of the
iterator implements {name}`ToString`.
-/
@[inline]
public abbrev Iter.toStringArray {α β : Type} [Iterator α Id β] [ToString β]
(it : Iter (α := α) β) : Array String :=
it.map toString |>.toArray
end Std
public import Init.Data.String.Iter.Basic
public import Init.Data.String.Iter.Intercalate

View File

@@ -0,0 +1,34 @@
/-
Copyright (c) 2026 Lean FRO, LLC. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Markus Himmel
-/
module
prelude
public import Init.Data.Iterators.Combinators.FilterMap
public import Init.Data.Iterators.Consumers.Collect
set_option doc.verso true
namespace Std
/--
Convenience function for turning an iterator into a list of strings, provided the output of the
iterator implements {name}`ToString`.
-/
@[inline]
public abbrev Iter.toStringList {α β : Type} [Iterator α Id β] [ToString β]
(it : Iter (α := α) β) : List String :=
it.map toString |>.toList
/--
Convenience function for turning an iterator into an array of strings, provided the output of the
iterator implements {name}`ToString`.
-/
@[inline]
public abbrev Iter.toStringArray {α β : Type} [Iterator α Id β] [ToString β]
(it : Iter (α := α) β) : Array String :=
it.map toString |>.toArray
end Std

View File

@@ -0,0 +1,36 @@
/-
Copyright (c) 2026 Lean FRO, LLC. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Julia Markus Himmel
-/
module
prelude
public import Init.Data.Iterators.Combinators.Monadic.FilterMap
public import Init.Data.String.Basic
import Init.Data.String.Slice
set_option doc.verso true
namespace Std
/--
Appends all the elements in the iterator, in order.
-/
public def Iter.joinString {α β : Type} [Iterator α Id β] [IteratorLoop α Id Id] [ToString β]
(it : Std.Iter (α := α) β) : String :=
(it.map toString).fold (init := "") (· ++ ·)
/--
Appends the elements of the iterator into a string, placing the separator {name}`s` between them.
-/
@[inline]
public def Iter.intercalateString {α β : Type} [Iterator α Id β] [IteratorLoop α Id Id] [ToString β]
(s : String.Slice) (it : Std.Iter (α := α) β) : String :=
it.map toString
|>.fold (init := none) (fun
| none, sl => some sl
| some str, sl => some (str ++ s ++ sl))
|>.getD ""
end Std

View File

@@ -17,6 +17,8 @@ public import Init.Data.String.Lemmas.Pattern
public import Init.Data.String.Lemmas.Slice
public import Init.Data.String.Lemmas.Iterate
public import Init.Data.String.Lemmas.Intercalate
public import Init.Data.String.Lemmas.Iter
public import Init.Data.String.Lemmas.Hashable
import Init.Data.Order.Lemmas
public import Init.Data.String.Basic
import Init.Data.Char.Lemmas

View File

@@ -0,0 +1,25 @@
/-
Copyright (c) 2026 Lean FRO, LLC. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Julia Markus Himmel
-/
module
prelude
public import Init.Data.String.Slice
public import Init.Data.LawfulHashable
import all Init.Data.String.Slice
import Init.Data.String.Lemmas.Slice
namespace String
public theorem hash_eq {s : String} : hash s = String.hash s := rfl
namespace Slice
public theorem hash_eq {s : String.Slice} : hash s = String.hash s.copy := (rfl)
public instance : LawfulHashable String.Slice where
hash_eq a b hab := by simp [hash_eq, beq_eq_true_iff.1 hab]
end String.Slice

View File

@@ -10,6 +10,7 @@ public import Init.Data.String.Defs
import all Init.Data.String.Defs
public import Init.Data.String.Slice
import all Init.Data.String.Slice
import Init.ByCases
public section
@@ -42,6 +43,16 @@ theorem intercalate_cons_of_ne_nil {s t : String} {l : List String} (h : l ≠ [
match l, h with
| u::l, _ => by simp
theorem intercalate_append_of_ne_nil {l m : List String} {s : String} (hl : l []) (hm : m []) :
s.intercalate (l ++ m) = s.intercalate l ++ s ++ s.intercalate m := by
induction l with
| nil => simp_all
| cons hd tl ih =>
rw [List.cons_append, intercalate_cons_of_ne_nil (by simp_all)]
by_cases ht : tl = []
· simp_all
· simp [ih ht, intercalate_cons_of_ne_nil ht, String.append_assoc]
@[simp]
theorem toList_intercalate {s : String} {l : List String} :
(s.intercalate l).toList = s.toList.intercalate (l.map String.toList) := by

View File

@@ -0,0 +1,51 @@
/-
Copyright (c) 2026 Lean FRO, LLC. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Julia Markus Himmel
-/
module
prelude
public import Init.Data.String.Iter.Intercalate
public import Init.Data.String.Slice
import all Init.Data.String.Iter.Intercalate
import all Init.Data.String.Defs
import Init.Data.String.Lemmas.Intercalate
import Init.Data.Iterators.Lemmas.Consumers.Loop
import Init.Data.Iterators.Lemmas.Combinators.FilterMap
namespace Std.Iter
@[simp]
public theorem joinString_eq {α β : Type} [Std.Iterator α Id β] [Std.Iterators.Finite α Id]
[Std.IteratorLoop α Id Id] [Std.LawfulIteratorLoop α Id Id] [ToString β]
{it : Std.Iter (α := α) β} : it.joinString = String.join (it.toList.map toString) := by
rw [joinString, String.join, foldl_toList, toList_map]
@[simp]
public theorem intercalateString_eq {α β : Type} [Std.Iterator α Id β] [Std.Iterators.Finite α Id]
[Std.IteratorLoop α Id Id] [Std.LawfulIteratorLoop α Id Id] [ToString β] {s : String.Slice}
{it : Std.Iter (α := α) β} :
it.intercalateString s = s.copy.intercalate (it.toList.map toString) := by
simp only [intercalateString, String.appendSlice_eq, foldl_toList, toList_map]
generalize s.copy = s
suffices (l m : List String),
(l.foldl (init := if m = [] then none else some (s.intercalate m))
(fun | none, sl => some sl | some str, sl => some (str ++ s ++ sl))).getD ""
= s.intercalate (m ++ l) by
simpa [-foldl_toList] using this (it.toList.map toString) []
intro l m
induction l generalizing m with
| nil => cases m <;> simp
| cons hd tl ih =>
rw [List.append_cons, ih, List.foldl_cons]
congr
simp only [List.append_eq_nil_iff, List.cons_ne_self, and_false, reduceIte]
match m with
| [] => simp
| x::xs =>
simp only [reduceCtorEq, reduceIte, List.cons_append, Option.some.injEq]
rw [ List.cons_append, String.intercalate_append_of_ne_nil (by simp) (by simp),
String.intercalate_singleton]
end Std.Iter

View File

@@ -23,6 +23,7 @@ import Init.Data.String.OrderInstances
import Init.Data.String.Lemmas.Order
import Init.Data.String.Lemmas.Intercalate
import Init.Data.List.SplitOn.Lemmas
import Init.Data.String.Lemmas.Slice
public section
@@ -70,6 +71,11 @@ theorem Slice.toList_split_intercalate {c : Char} {l : List Slice} (hl : ∀ s
· simp_all
· rw [List.splitOn_intercalate] <;> simp_all
theorem Slice.toList_split_intercalate_beq {c : Char} {l : List Slice} (hl : s l, c s.copy.toList) :
((Slice.intercalate (String.singleton c) l).split c).toList ==
if l = [] then ["".toSlice] else l := by
split <;> simp_all [toList_split_intercalate hl, beq_list_iff]
theorem toList_split_intercalate {c : Char} {l : List String} (hl : s l, c s.toList) :
((String.intercalate (String.singleton c) l).split c).toList.map (·.copy) =
if l = [] then [""] else l := by
@@ -78,4 +84,9 @@ theorem toList_split_intercalate {c : Char} {l : List String} (hl : ∀ s ∈ l,
· simp_all
· rw [List.splitOn_intercalate] <;> simp_all
theorem toList_split_intercalate_beq {c : Char} {l : List String} (hl : s l, c s.toList) :
((String.intercalate (String.singleton c) l).split c).toList ==
if l = [] then ["".toSlice] else l.map String.toSlice := by
split <;> simp_all [toList_split_intercalate hl, Slice.beq_list_iff]
end String

View File

@@ -33,8 +33,22 @@ theorem beq_eq_true_iff {s t : Slice} : s == t ↔ s.copy = t.copy := by
theorem beq_eq_false_iff {s t : Slice} : (s == t) = false s.copy t.copy := by
simp [ Bool.not_eq_true]
theorem beq_eq_decide {s t : Slice} : (s == t) = decide (s.copy = t.copy) := by
cases h : s == t <;> simp_all
theorem beq_eq_decide {s t : Slice} : (s == t) = decide (s.copy = t.copy) :=
Bool.eq_iff_iff.2 (by simp)
instance : EquivBEq String.Slice :=
equivBEq_of_iff_apply_eq copy (by simp)
theorem beq_list_iff {l l' : List String.Slice} : l == l' l.map copy = l'.map copy := by
induction l generalizing l' <;> cases l' <;> simp_all
theorem beq_list_eq_false_iff {l l' : List String.Slice} :
(l == l') = false l.map copy l'.map copy := by
simp [ Bool.not_eq_true, beq_list_iff]
theorem beq_list_eq_decide {l l' : List String.Slice} :
(l == l') = decide (l.map copy = l'.map copy) :=
Bool.eq_iff_iff.2 (by simp [beq_list_iff])
end BEq

View File

@@ -11,7 +11,7 @@ public import Init.Data.Ord.Basic
public import Init.Data.Iterators.Combinators.FilterMap
public import Init.Data.String.ToSlice
public import Init.Data.String.Subslice
public import Init.Data.String.Iter
public import Init.Data.String.Iter.Basic
public import Init.Data.String.Iterate
import Init.Data.Iterators.Consumers.Collect
import Init.Data.Iterators.Consumers.Loop
@@ -84,10 +84,11 @@ instance : ToString String.Slice where
theorem toStringToString_eq : ToString.toString = String.Slice.copy := (rfl)
@[extern "lean_slice_hash"]
opaque hash (s : @& Slice) : UInt64
protected def hash (s : @& Slice) : UInt64 :=
String.hash s.copy
instance : Hashable Slice where
hash := hash
hash := Slice.hash
instance : LT Slice where
lt x y := x.copy < y.copy

View File

@@ -111,11 +111,10 @@ structure Config where
ring := true
/--
Maximum number of steps performed by the `ring` solver.
A step is counted whenever one polynomial is used to simplify another.
For example, given `x^2 + 1` and `x^2 * y^3 + x * y`, the first can be
used to simplify the second to `-1 * y^3 + x * y`.
A step is counted whenever one polynomial is used to simplify another,
weighted by the number of terms in the resulting polynomial.
-/
ringSteps := 10000
ringSteps := 100000
/--
When `true` (default: `true`), uses procedure for handling linear arithmetic for `IntModule`, and
`CommRing`.

View File

@@ -107,6 +107,9 @@ syntax (name := showLocalThms) "show_local_thms" : grind
-/
syntax (name := showTerm) "show_term " grindSeq : grind
/-- Shows the pending goals. -/
syntax (name := showGoals) "show_goals" : grind
declare_syntax_cat grind_ref (behavior := both)
syntax:max anchor : grind_ref
@@ -205,7 +208,7 @@ macro:1 x:grind tk:" <;> " y:grind:2 : grind => `(grind|
with_annotate_state $tk skip
all_goals $y:grind)
/-- `first | tac | ...` runs each `tac` until one succeeds, or else fails. -/
/-- `first (tac) ...` runs each `tac` until one succeeds, or else fails. -/
syntax (name := first) "first " withPosition((ppDedent(ppLine) colGe "(" grindSeq ")")+) : grind
/-- `try tac` runs `tac` and succeeds even if `tac` failed. -/
@@ -304,5 +307,19 @@ syntax (name := symInternalizeAll) "internalize_all" : grind
Only available in `sym =>` mode. -/
syntax (name := symByContra) "by_contra" : grind
/--
`simp` applies the structural simplifier to the goal target.
Only available in `sym =>` mode.
- `simp` — uses the default (identity) variant
- `simp myVariant` — uses a named variant registered via `register_sym_simp`
- `simp [thm₁, thm₂, ...]` — default variant with extra rewrite theorems appended to `post`
- `simp myVariant [thm₁, thm₂, ...]` — named variant with extra theorems
-/
syntax (name := symSimp) "simp" (ppSpace colGt ident)? (" [" ident,* "]")? : grind
/-- `exact e` closes the main goal if its target type matches that of `e`. -/
macro "exact " e:term : grind => `(grind| tactic => exact $e:term)
end Grind
end Lean.Parser.Tactic

View File

@@ -954,7 +954,7 @@ theorem monotone_readerTRun [PartialOrder γ]
instance [inst : PartialOrder (m α)] : PartialOrder (StateRefT' ω σ m α) := instOrderPi
instance [inst : CCPO (m α)] : CCPO (StateRefT' ω σ m α) := instCCPOPi
instance [Monad m] [ α, PartialOrder (m α)] [MonoBind m] : MonoBind (StateRefT' ω σ m) :=
inferInstanceAs (MonoBind (ReaderT _ _))
inferInstanceAs (MonoBind (ReaderT (ST.Ref ω σ) m))
@[partial_fixpoint_monotone]
theorem monotone_stateRefT'Run [PartialOrder γ]

View File

@@ -185,18 +185,17 @@ example : foo.default = (default, default) :=
abbrev inferInstance {α : Sort u} [i : α] : α := i
set_option checkBinderAnnotations false in
/-- `inferInstanceAs α` synthesizes a value of any target type by typeclass
inference. This is just like `inferInstance` except that `α` is given
explicitly instead of being inferred from the target type. It is especially
useful when the target type is some `α'` which is definitionally equal to `α`,
but the instance we are looking for is only registered for `α` (because
typeclass search does not unfold most definitions, but definitional equality
does.) Example:
/-- `inferInstanceAs α` synthesizes an instance of type `α` and normalizes it to
"instance normal form": the result is a constructor application whose sub-instance fields
are canonical instances and whose types match `α` exactly. This is useful when `α` is
definitionally equal to some `α'` for which instances are registered, as it prevents
leaking the definition's RHS at lower transparencies. See `Lean.Meta.InstanceNormalForm`
for details. Example:
```
#check inferInstanceAs (Inhabited Nat) -- Inhabited Nat
```
-/
abbrev inferInstanceAs (α : Sort u) [i : α] : α := i
abbrev «inferInstanceAs» (α : Sort u) [i : α] : α := i
@@ -4083,7 +4082,7 @@ Actions in the resulting monad are functions that take the local value as a para
ordinary actions in `m`.
-/
def ReaderT (ρ : Type u) (m : Type u Type v) (α : Type u) : Type (max u v) :=
ρ m α
(a : @&ρ) m α
/--
Interpret `ρ → m α` as an element of `ReaderT ρ m α`.

View File

@@ -6,3 +6,4 @@ Authors: Leonardo de Moura
module
prelude
public import Init.Sym.Lemmas
public import Init.Sym.Simp.SimprocDSL

View File

@@ -0,0 +1,137 @@
/-
Copyright (c) 2026 Lean FRO, LLC. All Rights Reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Leonardo de Moura
-/
module
prelude
public import Init.Tactics
public section
namespace Lean.Parser.Sym.Simp
/-!
# Simproc and Discharger DSLs for `Sym.simp`
## Simproc DSL (`sym_simproc`)
A syntax category for specifying `pre` and `post` simproc chains in `Sym.simp` variants.
### Primitives
- `ground` — evaluates ground (fully concrete) terms
- `telescope` — simplifies telescope binders (have-values, arrow hypotheses) but not the final body
- `rewrite setName [with discharger]` — rewrites using a named theorem set
- `rewrite [thm₁, thm₂, ...] [with discharger]` — rewrites using inline theorems
- `self` — recursive simplification (calls the full simplifier)
- `none` — identity (no simplification)
### Combinators
- `a >> b` — apply `a`, then apply `b` to the result (andThen)
- `a <|> b` — try `a`, if no progress try `b` (orElse)
## Discharger DSL (`sym_discharger`)
A syntax category for specifying dischargers used in the `with` clause of `rewrite`.
Dischargers attempt to prove side conditions of conditional rewrite rules.
### Primitives
- `self` — recursive simplifier discharge (`dischargeSimpSelf`)
- `none` — no discharge, only unconditional rewrites apply (`dischargeNone`)
-/
declare_syntax_cat sym_simproc (behavior := both)
declare_syntax_cat sym_discharger (behavior := both)
-- Simproc primitives
/-- Evaluate ground (fully concrete) terms. -/
syntax (name := ground) "ground" : sym_simproc
/-- Simplify telescope binders but not the final body. -/
syntax (name := telescope) "telescope" : sym_simproc
/-- Simplify control-flow expressions (`if-then-else`, `match`, `cond`, `dite`).
Visits only conditions and discriminants. Intended as a `pre` simproc. -/
syntax (name := control) "control" : sym_simproc
/-- Simplify arrow telescopes (`p₁ → p₂ → ... → q`) without entering binders.
Simplifies each `pᵢ` and `q` individually. Intended as a `pre` simproc. -/
syntax (name := arrowTelescope) "arrow_telescope" : sym_simproc
/-- Rewrite using a named theorem set. Optionally specify a discharger for conditional rewrites. -/
syntax (name := rewriteSet) "rewrite" ident (" with " sym_discharger)? : sym_simproc
/-- Rewrite using inline theorems. Optionally specify a discharger for conditional rewrites. -/
syntax (name := rewriteInline) "rewrite" " [" ident,* "]" (" with " sym_discharger)? : sym_simproc
/-- Recursive simplification (calls the full simplifier). -/
syntax (name := self) "self" : sym_simproc
/-- Identity simproc (no simplification). -/
syntax (name := none) "none" : sym_simproc
-- Simproc combinators
/-- Apply `a`, then apply `b` to the result. -/
syntax:60 (name := andThen) sym_simproc:61 " >> " sym_simproc:60 : sym_simproc
/-- Try `a`, if no progress try `b`. -/
syntax:20 (name := orElse) sym_simproc:21 " <|> " sym_simproc:20 : sym_simproc
/-- Parenthesized simproc expression. -/
syntax (name := simprocParen) "(" sym_simproc ")" : sym_simproc
-- Discharger primitives
/-- Recursive simplifier discharge. Calls the full simplifier to prove side conditions. -/
syntax (name := dischSelf) "self" : sym_discharger
/-- No discharge. Only unconditional rewrites will apply. -/
syntax (name := dischNone) "none" : sym_discharger
/-- Parenthesized discharger expression. -/
syntax (name := dischParen) "(" sym_discharger ")" : sym_discharger
end Lean.Parser.Sym.Simp
/-!
## `register_sym_simp` command
Declares a named `Sym.simp` variant with `pre`/`post` simproc chains and optional config overrides.
```
register_sym_simp myVariant where
pre := telescope
post := ground >> rewrite mySet with self
```
-/
namespace Lean.Parser.Command
declare_syntax_cat sym_simp_field (behavior := both)
/-- Pre-processing simproc chain. -/
syntax (name := symSimpFieldPre) "pre" " := " sym_simproc : sym_simp_field
/-- Post-processing simproc chain. -/
syntax (name := symSimpFieldPost) "post" " := " sym_simproc : sym_simp_field
/-- Maximum number of simplification steps. -/
syntax (name := symSimpFieldMaxSteps) "maxSteps" " := " num : sym_simp_field
/-- Maximum depth of recursive discharge attempts. -/
syntax (name := symSimpFieldMaxDischargeDepth) "maxDischargeDepth" " := " num : sym_simp_field
/--
Register a named `Sym.simp` variant.
```
register_sym_simp myVariant where
pre := telescope
post := ground >> rewrite [thm1, thm2] with self
maxSteps := 50000
```
-/
syntax (name := registerSymSimp) "register_sym_simp" ident "where"
(colGt sym_simp_field)* : command
end Lean.Parser.Command

View File

@@ -524,9 +524,6 @@ syntax location := withPosition(ppGroup(" at" (locationWildcard <|> locationHyp)
-/
syntax (name := change) "change " term (location)? : tactic
@[tactic_alt change]
syntax (name := changeWith) "change " term " with " term (location)? : tactic
/--
`show t` finds the first goal whose target unifies with `t`. It makes that the main goal,
performs the unification, and replaces the target with the unified version of `t`.

View File

@@ -21,6 +21,7 @@ public section
namespace Lean.IR
set_option compiler.ignoreBorrowAnnotation true in
@[export lean_add_extern]
def addExtern (declName : Name) (externAttrData : ExternAttrData) : CoreM Unit := do
if !isPrivateName declName then

View File

@@ -136,12 +136,11 @@ def findEnvDecl (env : Environment) (declName : Name) : Option Decl :=
Compiler.LCNF.findExtEntry? env declMapExt declName findAtSorted? (·.2.find?)
@[export lean_ir_find_env_decl]
private def findInterpDecl (env : Environment) (declName : Name) (includeServer := false) : Option Decl :=
private def findInterpDecl (env : Environment) (declName : Name) : Option Decl :=
-- This function is never used in `leanir`, so no need for `findExtEntry?`
match env.getModuleIdxFor? declName with
| some modIdx =>
-- `meta import/import all` and, optionally, additional server-mode IR
guard (includeServer || env.header.modules[modIdx]?.any (·.irPhases != .runtime)) *>
-- `meta import/import all` and additional server-mode IR
findAtSorted? (declMapExt.getModuleIREntries env modIdx) declName <|>
-- (closure of) `meta def`; will report `.extern`s for other `def`s so needs to come second
findAtSorted? (declMapExt.getModuleEntries env modIdx) declName

View File

@@ -171,7 +171,7 @@ private unsafe def runInitAttrs (env : Environment) (opts : Options) : IO Unit :
-- libraries with their corresponding module in the Environment) must
-- first be initialized
let pkg? := env.getModulePackageByIdx? modIdx
if env.header.isModule && /- TODO: remove after reboostrap -/ false then
if env.header.isModule then
let initializedRuntime pure initRuntime <&&> runModInit (phases := .runtime) mod.module pkg?
let initializedComptime runModInit (phases := .comptime) mod.module pkg?
if initializedRuntime || initializedComptime then

View File

@@ -49,7 +49,7 @@ structure CompilerM.Context where
abbrev CompilerM := ReaderT CompilerM.Context $ StateRefT CompilerM.State CoreM
@[always_inline]
instance : Monad CompilerM := let i := inferInstanceAs (Monad CompilerM); { pure := i.pure, bind := i.bind }
instance : Monad CompilerM := let i : Monad CompilerM := inferInstance; { pure := i.pure, bind := i.bind }
@[inline] def withPhase (phase : Phase) (x : CompilerM α) : CompilerM α :=
withReader (fun ctx => { ctx with phase }) x

View File

@@ -97,6 +97,7 @@ partial def collectCode (code : Code .impure) : M Unit := do
match decl.value with
| .oproj _ parent =>
addDerivedValue parent decl.fvarId
-- Keep in sync with PropagateBorrow, InferBorrow
| .fap ``Array.getInternal args =>
if let .fvar parent := args[1]! then
addDerivedValue parent decl.fvarId

View File

@@ -213,6 +213,8 @@ inductive OwnReason where
| jpArgPropagation (jpFVar : FVarId)
/-- Tail call preservation at a join point jump. -/
| jpTailCallPreservation (jpFVar : FVarId)
/-- Annotated as an owned parameter (currently only triggerable through `@[export]`)-/
| ownedAnnotation
def OwnReason.toString (reason : OwnReason) : CompilerM String := do
PP.run do
@@ -229,6 +231,7 @@ def OwnReason.toString (reason : OwnReason) : CompilerM String := do
| .tailCallPreservation funcName => return s!"tail call preservation of {funcName}"
| .jpArgPropagation jpFVar => return s!"backward propagation from JP {← PP.ppFVar jpFVar}"
| .jpTailCallPreservation jpFVar => return s!"JP tail call preservation {← PP.ppFVar jpFVar}"
| .ownedAnnotation => return s!"Annotated as owned"
/--
Determine whether an `OwnReason` is necessary for correctness (forced) or just an optimization
@@ -245,7 +248,7 @@ def OwnReason.isForced (reason : OwnReason) : Bool :=
| .constructorResult .. | .functionCallResult ..
-- We cannot pass borrowed values to reset or have borrow annotations destroy tail calls for
-- correctness reasons.
| .resetReuse .. | .tailCallPreservation .. | .jpTailCallPreservation ..
| .resetReuse .. | .tailCallPreservation .. | .jpTailCallPreservation .. | .ownedAnnotation
| .forwardProjectionProp .. | .backwardProjectionProp .. => true
/--
@@ -256,10 +259,19 @@ partial def infer (decls : Array (Decl .impure)) : CompilerM ParamMap := do
return map.paramMap
where
go : InferM Unit := do
for (_, params) in ( get).paramMap.map do
for param in params do
if !param.borrow && param.type.isPossibleRef then
-- if the param already disqualifies as borrow now this is because of an annotation
ownFVar param.fvarId .ownedAnnotation
modify fun s => { s with modified := false }
loop
loop : InferM Unit := do
step
if ( get).modified then
modify fun s => { s with modified := false }
go
loop
else
return ()
@@ -361,6 +373,16 @@ where
| .oproj _ x _ =>
if isOwned x then ownFVar z (.forwardProjectionProp z)
if isOwned z then ownFVar x (.backwardProjectionProp z)
-- Keep in sync with ExplicitRC, PropagateBorrow
| .fap ``Array.getInternal args =>
if let .fvar parent := args[1]! then
if isOwned parent then ownFVar z (.forwardProjectionProp z)
| .fap ``Array.get!Internal args =>
if let .fvar parent := args[2]! then
if isOwned parent then ownFVar z (.forwardProjectionProp z)
| .fap ``Array.uget args =>
if let .fvar parent := args[1]! then
if isOwned parent then ownFVar z (.forwardProjectionProp z)
| .fap f args =>
let ps getParamInfo (.decl f)
ownFVar z (.functionCallResult z)

View File

@@ -105,9 +105,22 @@ where
collectLetValue (z : FVarId) (v : LetValue .impure) : InferM Unit := do
match v with
| .oproj _ x _ =>
let xVal getOwnedness x
join z xVal
| .oproj _ parent _ =>
let parentVal getOwnedness parent
join z parentVal
-- Keep in sync with ExplicitRC, InferBorrow
| .fap ``Array.getInternal args =>
if let .fvar parent := args[1]! then
let parentVal getOwnedness parent
join z parentVal
| .fap ``Array.get!Internal args =>
if let .fvar parent := args[2]! then
let parentVal getOwnedness parent
join z parentVal
| .fap ``Array.uget args =>
if let .fvar parent := args[1]! then
let parentVal getOwnedness parent
join z parentVal
| .ctor .. | .fap .. | .fvar .. | .pap .. | .sproj .. | .uproj .. | .erased .. | .lit .. =>
join z .own
| _ => unreachable!

View File

@@ -78,7 +78,7 @@ structure State where
abbrev SimpM := ReaderT Context $ StateRefT State DiscrM
@[always_inline]
instance : Monad SimpM := let i := inferInstanceAs (Monad SimpM); { pure := i.pure, bind := i.bind }
instance : Monad SimpM := let i : Monad SimpM := inferInstance; { pure := i.pure, bind := i.bind }
instance : MonadFVarSubst SimpM .pure false where
getSubst := return ( get).subst

View File

@@ -256,7 +256,7 @@ abbrev CoreM := ReaderT Context <| StateRefT State (EIO Exception)
-- Make the compiler generate specialized `pure`/`bind` so we do not have to optimize through the
-- whole monad stack at every use site. May eventually be covered by `deriving`.
@[always_inline]
instance : Monad CoreM := let i := inferInstanceAs (Monad CoreM); { pure := i.pure, bind := i.bind }
instance : Monad CoreM := let i : Monad CoreM := inferInstance; { pure := i.pure, bind := i.bind }
instance : Inhabited (CoreM α) where
default := fun _ _ => throw default
@@ -343,13 +343,13 @@ def instantiateTypeLevelParams (c : ConstantVal) (us : List Level) : CoreM Expr
modifyInstLevelTypeCache fun s => s.insert c.name (us, r)
return r
def instantiateValueLevelParams (c : ConstantInfo) (us : List Level) : CoreM Expr := do
def instantiateValueLevelParams (c : ConstantInfo) (us : List Level) (allowOpaque := false) : CoreM Expr := do
if let some (us', r) := ( get).cache.instLevelValue.find? c.name then
if us == us' then
return r
unless c.hasValue do
unless c.hasValue (allowOpaque := allowOpaque) do
throwError "Not a definition or theorem: {.ofConstName c.name}"
let r := c.instantiateValueLevelParams! us
let r := c.instantiateValueLevelParams! us (allowOpaque := allowOpaque)
modifyInstLevelValueCache fun s => s.insert c.name (us, r)
return r

View File

@@ -14,29 +14,35 @@ public section
namespace Lean
/--
Reducibility hints are used in the convertibility checker.
When trying to solve a constraint such a
Reducibility hints guide the kernel's *lazy delta reduction* strategy. When the kernel encounters a
definitional equality constraint
(f ...) =?= (g ...)
where f and g are definitions, the checker has to decide which one will be unfolded.
If f (g) is opaque, then g (f) is unfolded if it is also not marked as opaque,
Else if f (g) is abbrev, then f (g) is unfolded if g (f) is also not marked as abbrev,
Else if f and g are regular, then we unfold the one with the biggest definitional height.
Otherwise both are unfolded.
where `f` and `g` are definitions, it must decide which side to unfold. The rules (implemented in
`lazy_delta_reduction_step` in `src/kernel/type_checker.cpp`) are:
The arguments of the `regular` Constructor are: the definitional height and the flag `selfOpt`.
* If `f` and `g` have the **same hint kind**:
- Both `.opaque` or both `.abbrev`: unfold both.
- Both `.regular`: unfold the one with the **greater** height first. If their heights are equal
(in particular, if `f` and `g` are the same definition), first try to compare their arguments
for definitional equality (short-circuiting the unfolding if they match), then unfold both.
* If `f` and `g` have **different hint kinds**: unfold the one that is *not* `.opaque`, preferring to
unfold `.abbrev` over `.regular`.
The definitional height is by default computed by the kernel. It only takes into account
other regular definitions used in a definition. When creating declarations using meta-programming,
we can specify the definitional depth manually.
The `.regular` constructor carries a `UInt32` *definitional height*, which is computed by the
elaborator as one plus the maximum height of all `.regular` constants appearing in the definition's
body (see `getMaxHeight`). This means `.abbrev` and `.opaque` constants do not contribute to the
height. When creating declarations via meta-programming, the height can be specified manually.
Remark: the hint only affects performance. None of the hints prevent the kernel from unfolding a
declaration during Type checking.
The hints only affect performance — they control the order in which definitions are unfolded, but
never prevent the kernel from unfolding a definition during type checking.
Remark: the ReducibilityHints are not related to the attributes: reducible/irrelevance/semireducible.
These attributes are used by the Elaborator. The ReducibilityHints are used by the kernel (and Elaborator).
Moreover, the ReducibilityHints cannot be changed after a declaration is added to the kernel. -/
The `ReducibilityHints` are not related to the `@[reducible]`/`@[irreducible]`/`@[semireducible]`
attributes. Those attributes are used by the elaborator to control which definitions tactics like
`simp`, `rfl`, and `dsimp` will unfold; they do not affect the kernel. Conversely,
`ReducibilityHints` are set when a declaration is added to the kernel and cannot be changed
afterwards. -/
inductive ReducibilityHints where
| opaque : ReducibilityHints
| abbrev : ReducibilityHints
@@ -469,24 +475,37 @@ def numLevelParams (d : ConstantInfo) : Nat :=
def type (d : ConstantInfo) : Expr :=
d.toConstantVal.type
/--
Returns the value of a definition. With `allowOpaque := true`, values
of theorems and opaque declarations are also returned.
-/
def value? (info : ConstantInfo) (allowOpaque := false) : Option Expr :=
match info with
| .defnInfo {value, ..} => some value
| .thmInfo {value, ..} => some value
| .thmInfo {value, ..} => if allowOpaque then some value else none
| .opaqueInfo {value, ..} => if allowOpaque then some value else none
| _ => none
/--
Returns `true` if this declaration as a value for the purpose of reduction
and type-checking, i.e. is a definition.
With `allowOpaque := true`, theorems and opaque declarations are also considered to have values.
-/
def hasValue (info : ConstantInfo) (allowOpaque := false) : Bool :=
match info with
| .defnInfo _ => true
| .thmInfo _ => true
| .thmInfo _ => allowOpaque
| .opaqueInfo _ => allowOpaque
| _ => false
/--
Returns the value of a definition. With `allowOpaque := true`, values
of theorems and opaque declarations are also returned.
-/
def value! (info : ConstantInfo) (allowOpaque := false) : Expr :=
match info with
| .defnInfo {value, ..} => value
| .thmInfo {value, ..} => value
| .thmInfo {value, ..} => if allowOpaque then value else panic! "declaration with value expected"
| .opaqueInfo {value, ..} => if allowOpaque then value else panic! "declaration with value expected"
| _ => panic! s!"declaration with value expected, but {info.name} has none"
@@ -510,6 +529,10 @@ def isDefinition : ConstantInfo → Bool
| .defnInfo _ => true
| _ => false
def isTheorem : ConstantInfo Bool
| .thmInfo _ => true
| _ => false
def inductiveVal! : ConstantInfo InductiveVal
| .inductInfo val => val
| _ => panic! "Expected a `ConstantInfo.inductInfo`."

View File

@@ -101,7 +101,7 @@ def inferDefEqAttr (declName : Name) : MetaM Unit := do
withoutExporting do
let info getConstInfo declName
let isRfl
if let some value := info.value? then
if let some value := info.value? (allowOpaque := true) then
isRflProofCore info.type value
else
pure false

View File

@@ -7,6 +7,7 @@ module
prelude
public import Lean.Meta.Diagnostics
public import Lean.Meta.InstanceNormalForm
public import Lean.Elab.Open
public import Lean.Elab.SetOption
public import Lean.Elab.Eval
@@ -313,6 +314,27 @@ private def mkSilentAnnotationIfHole (e : Expr) : TermElabM Expr := do
return val
| _ => panic! "resolveId? returned an unexpected expression"
@[builtin_term_elab Lean.Parser.Term.inferInstanceAs] def elabInferInstanceAs : TermElab := fun stx expectedType? => do
let expectedType tryPostponeIfHasMVars expectedType? "`inferInstanceAs` failed"
-- The type argument is the last child (works for both `inferInstanceAs T` and `inferInstanceAs <| T`)
let typeStx := stx[stx.getNumArgs - 1]!
let type withSynthesize (postpone := .yes) <| elabType typeStx
-- Unify with expected type to resolve metavariables (e.g., `_` placeholders)
discard <| isDefEq type expectedType
let type instantiateMVars type
-- Rebuild type with fresh synthetic mvars for instance-implicit args, so that
-- synthesis is not influenced by the expected type's instance choices.
let type abstractInstImplicitArgs type
let inst synthInstance type
let inst if backward.inferInstanceAs.wrap.get ( getOptions) then
-- Normalize to instance normal form.
let logCompileErrors := !( read).isNoncomputableSection && !( read).declName?.any (Lean.isNoncomputable ( getEnv))
let isMeta := ( read).isMetaSection
withNewMCtxDepth <| normalizeInstance inst expectedType (logCompileErrors := logCompileErrors) (isMeta := isMeta)
else
pure inst
ensureHasType expectedType? inst
@[builtin_term_elab clear] def elabClear : TermElab := fun stx expectedType? => do
let some (.fvar fvarId) isLocalIdent? stx[1]
| throwErrorAt stx[1] "not in scope"
@@ -383,14 +405,13 @@ private opaque evalFilePath (stx : Syntax) : TermElabM System.FilePath
let name mkAuxDeclName `_private
withoutExporting do
let e elabTermAndSynthesize e expectedType?
let compile := !( read).isNoncomputableSection && !( read).declName?.any (Lean.isNoncomputable ( getEnv))
let e mkAuxDefinitionFor (compile := false) name e
if compile then
-- Inline as changing visibility should not affect run time.
setInlineAttribute name
if ( read).declName?.any (isMarkedMeta ( getEnv)) then
modifyEnv (markMeta · name)
compileDecls #[name]
-- Inline as changing visibility should not affect run time.
setInlineAttribute name
if ( read).declName?.any (isMarkedMeta ( getEnv)) then
modifyEnv (markMeta · name)
let logCompileErrors := !( read).isNoncomputableSection && !( read).declName?.any (Lean.isNoncomputable ( getEnv))
compileDecls (logErrors := logCompileErrors) #[name]
return e
else
elabTerm e expectedType?

View File

@@ -71,7 +71,7 @@ whole monad stack at every use site. May eventually be covered by `deriving`.
Remark: see comment at TermElabM
-/
@[always_inline]
instance : Monad CommandElabM := let i := inferInstanceAs (Monad CommandElabM); { pure := i.pure, bind := i.bind }
instance : Monad CommandElabM := let i : Monad CommandElabM := inferInstance; { pure := i.pure, bind := i.bind }
/--
Like `Core.tryCatchRuntimeEx`; runtime errors are generally used to abort term elaboration, so we do
@@ -666,7 +666,8 @@ private def mkTermContext (ctx : Context) (s : State) : CommandElabM Term.Contex
return {
macroStack := ctx.macroStack
sectionVars := sectionVars
isNoncomputableSection := scope.isNoncomputable }
isNoncomputableSection := scope.isNoncomputable
isMetaSection := scope.isMeta }
/--
Lift the `TermElabM` monadic action `x` into a `CommandElabM` monadic action.

View File

@@ -9,6 +9,7 @@ prelude
public import Lean.Elab.App
public import Lean.Elab.DeclNameGen
import Lean.Compiler.NoncomputableAttr
import Lean.Meta.InstanceNormalForm
public section
@@ -187,7 +188,7 @@ def processDefDeriving (view : DerivingClassView) (decl : Expr) (isNoncomputable
let ConstantInfo.defnInfo info getConstInfo declName
| throwError "Failed to delta derive instance, `{.ofConstName declName}` is not a definition."
let value := info.value.beta decl.getAppArgs
let result : Closure.MkValueTypeClosureResult
let (result, preNormValue, instName) : Closure.MkValueTypeClosureResult × Expr × Name
-- Assumption: users intend delta deriving to apply to the body of a definition, even if in the source code
-- the function is written as a lambda expression.
-- Furthermore, we don't use `forallTelescope` because users want to derive instances for monads.
@@ -210,22 +211,34 @@ def processDefDeriving (view : DerivingClassView) (decl : Expr) (isNoncomputable
-- We don't reduce because of abbreviations such as `DecidableEq`
forallTelescope classExpr fun _ classExpr => do
let result mkInst classExpr declName decl value
Closure.mkValueTypeClosure result.instType result.instVal (zetaDelta := true)
-- Save the pre-normalization value for the noncomputable check below,
-- since `normalizeInstance` may inline noncomputable constants.
let preNormClosure Closure.mkValueTypeClosure result.instType result.instVal (zetaDelta := true)
-- Compute instance name early so `normalizeInstance` can use it for aux def naming.
let env getEnv
let mut instName := ( getCurrNamespace) ++ ( NameGen.mkBaseNameWithSuffix "inst" preNormClosure.type)
instName liftMacroM <| mkUnusedBaseName instName
if isPrivateName declName then
instName := mkPrivateName env instName
let isMeta := ( read).isMetaSection
let inst if backward.inferInstanceAs.wrap.get ( getOptions) then
withDeclNameForAuxNaming instName <| withNewMCtxDepth <|
normalizeInstance result.instVal result.instType
(logCompileErrors := false) -- covered by noncomputable check below
(isMeta := isMeta)
else
pure result.instVal
let closure Closure.mkValueTypeClosure result.instType inst (zetaDelta := true)
return (closure, preNormClosure.value, instName)
finally
Core.setMessageLog (msgLog ++ ( Core.getMessageLog))
let env getEnv
let mut instName := ( getCurrNamespace) ++ ( NameGen.mkBaseNameWithSuffix "inst" result.type)
-- We don't have a facility to let users override derived names, so make an unused name if needed.
instName liftMacroM <| mkUnusedBaseName instName
-- Make the instance private if the declaration is private.
if isPrivateName declName then
instName := mkPrivateName env instName
let hints := ReducibilityHints.regular (getMaxHeight env result.value + 1)
let decl mkDefinitionValInferringUnsafe instName result.levelParams.toList result.type result.value hints
-- Pre-check: if the instance value depends on noncomputable definitions and the user didn't write
-- `noncomputable`, give an actionable error with a `Try this:` suggestion.
unless isNoncomputable || ( read).isNoncomputableSection || ( isProp result.type) do
let noncompRef? := result.value.foldConsts none fun n acc =>
let noncompRef? := preNormValue.foldConsts none fun n acc =>
acc <|> if Lean.isNoncomputable (asyncMode := .local) env n then some n else none
if let some noncompRef := noncompRef? then
if let some cmdRef := cmdRef? then

View File

@@ -10,6 +10,7 @@ public import Lean.Compiler.NoncomputableAttr
public import Lean.Util.NumApps
public import Lean.Meta.Eqns
public import Lean.Elab.RecAppSyntax
public import Lean.Meta.InstanceNormalForm
public import Lean.Elab.DefView
public section

View File

@@ -63,10 +63,11 @@ def addPreDefAttributes (preDefs : Array PreDefinition) : TermElabM Unit := do
a wrong setting and creates bad `defEq` equations.
-/
for preDef in preDefs do
unless preDef.modifiers.attrs.any fun a =>
a.name = `reducible || a.name = `semireducible ||
a.name = `instance_reducible || a.name = `implicit_reducible do
setIrreducibleAttribute preDef.declName
unless preDef.kind.isTheorem do
unless preDef.modifiers.attrs.any fun a =>
a.name = `reducible || a.name = `semireducible ||
a.name = `instance_reducible || a.name = `implicit_reducible do
setIrreducibleAttribute preDef.declName
/-
`enableRealizationsForConst` must happen before `generateEagerEqns`

View File

@@ -184,6 +184,7 @@ def getUnfoldFor? (declName : Name) : MetaM (Option Name) := do
else
return none
set_option compiler.ignoreBorrowAnnotation true in
@[export lean_get_structural_rec_arg_pos]
def getStructuralRecArgPosImp? (declName : Name) : CoreM (Option Nat) := do
let some info := eqnInfoExt.find? ( getEnv) declName | return none

View File

@@ -80,6 +80,32 @@ private def elimMutualRecursion (preDefs : Array PreDefinition) (fixedParamPerms
withRecFunsAsAxioms preDefs do
mkBRecOnF recArgInfos positions r values[idx]! FTypes[idx]!
trace[Elab.definition.structural] "FArgs: {FArgs}"
-- Extract the functionals into named `_f` helper definitions (e.g. `foo._f`) so they show up
-- with a helpful name in kernel diagnostics. The `_f` definitions are `.abbrev` so the kernel
-- unfolds them eagerly; their body heights are registered via `setDefHeightOverride` so that
-- `getMaxHeight` computes the correct height for parent definitions.
-- For inductive predicates, the previous inline behavior is kept.
let FArgs
if isIndPred then
pure FArgs
else
let us := preDefs[0]!.levelParams.map mkLevelParam
FArgs.mapIdxM fun idx fArg => do
let fName := preDefs[idx]!.declName ++ `_f
let fValue eraseRecAppSyntaxExpr ( mkLambdaFVars xs fArg)
let fType Meta.letToHave ( inferType fValue)
let fHeight := getMaxHeight ( getEnv) fValue
addDecl (.defnDecl {
name := fName, levelParams := preDefs[idx]!.levelParams,
type := fType, value := fValue,
hints := .abbrev,
safety := if preDefs[idx]!.modifiers.isUnsafe then .unsafe else .safe,
all := [fName] })
modifyEnv (setDefHeightOverride · fName fHeight)
setReducibleAttribute fName
return mkAppN (mkConst fName us) xs
let brecOn := brecOnConst 0
-- the indices and the major premise are not mentioned in the minor premises
-- so using `default` is fine here

View File

@@ -63,7 +63,7 @@ See comment at `Monad TermElabM`
-/
@[always_inline]
instance : Monad TacticM :=
let i := inferInstanceAs (Monad TacticM);
let i : Monad TacticM := inferInstance;
{ pure := i.pure, bind := i.bind }
instance : Inhabited (TacticM α) where

View File

@@ -155,7 +155,7 @@ where
.hint' m!"Reduction got stuck on `▸` ({.ofConstName ``Eq.rec}), \
which suggests that one of the `{.ofConstName ``Decidable}` instances is defined using tactics such as `rw` or `simp`. \
To avoid tactics, make use of functions such as \
`{.ofConstName ``inferInstanceAs}` or `{.ofConstName ``decidable_of_decidable_of_iff}` \
`{.ofConstName `inferInstanceAs}` or `{.ofConstName ``decidable_of_decidable_of_iff}` \
to alter a proposition."
else if reason.isAppOf ``Classical.choice then
.hint' m!"Reduction got stuck on `{.ofConstName ``Classical.choice}`, \

View File

@@ -16,3 +16,6 @@ public import Lean.Elab.Tactic.Grind.Lint
public import Lean.Elab.Tactic.Grind.LintExceptions
public import Lean.Elab.Tactic.Grind.Annotated
public import Lean.Elab.Tactic.Grind.Sym
public import Lean.Elab.Tactic.Grind.SimprocDSL
public import Lean.Elab.Tactic.Grind.SimprocDSLBuiltin
public import Lean.Elab.Tactic.Grind.RegisterSymSimp

View File

@@ -10,6 +10,7 @@ public import Lean.Meta.Tactic.Grind.Main
import Lean.Meta.Tactic.Grind.Intro
public import Lean.Meta.Sym.Apply
public import Lean.Meta.Sym.Util
public import Lean.Meta.Sym.Simp.SimpM
import Init.Omega
public section
namespace Lean.Elab.Tactic.Grind
@@ -24,11 +25,25 @@ structure Context extends Tactic.Context where
open Meta.Grind (Goal)
/-- An extra theorem passed to `simp` in `sym =>` mode. -/
inductive ExtraTheorem where
| const (declName : Name)
| fvar (fvarId : FVarId)
deriving BEq, Hashable
/-- Cache key for `Sym.simp` variant invocations. -/
structure SimpCacheKey where
variant : Name
extras : Array ExtraTheorem
deriving BEq, Hashable
structure Cache where
/-- Cache for `BackwardRule`s created from declaration names (sym mode only). -/
backwardRuleName : PHashMap Name Sym.BackwardRule := {}
/-- Cache for `BackwardRule`s created from elaborated terms, keyed by syntax byte position range (sym mode only). -/
backwardRuleSyntax : PHashMap (Nat × Nat) Sym.BackwardRule := {}
/-- Per-variant persistent `Sym.simp` cache. Keyed by variant name + extra theorem names. -/
simpState : Std.HashMap SimpCacheKey Sym.Simp.State := {}
structure State where
symState : Meta.Sym.State
@@ -74,7 +89,7 @@ def SavedState.restore (b : SavedState) (restoreInfo := false) : GrindTacticM Un
@[always_inline]
instance : Monad GrindTacticM :=
let i := inferInstanceAs (Monad GrindTacticM)
let i : Monad GrindTacticM := inferInstance
{ pure := i.pure, bind := i.bind }
instance : Inhabited (GrindTacticM α) where

View File

@@ -76,6 +76,10 @@ def evalGrindSeq : GrindTactic := fun stx =>
@[builtin_grind_tactic skip] def evalSkip : GrindTactic := fun _ =>
return ()
@[builtin_grind_tactic showGoals] def evalShowGoals : GrindTactic := fun _ => do
let goals getUnsolvedGoalMVarIds
addRawTrace (goalsToMessageData goals)
@[builtin_grind_tactic paren] def evalParen : GrindTactic := fun stx =>
evalGrindTactic stx[1]

View File

@@ -0,0 +1,73 @@
/-
Copyright (c) 2026 Lean FRO, LLC. All Rights Reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Leonardo de Moura
-/
module
prelude
import Init.Sym.Simp.SimprocDSL
import Lean.Meta.Sym.Simp.Variant
import Lean.Elab.Tactic.Grind.SimprocDSL
import Lean.Elab.Command
namespace Lean.Elab.Command
open Meta Sym.Simp
/--
Runs a `GrindTacticM` computation in a minimal context for validation.
-/
def withGrindTacticM (k : Tactic.Grind.GrindTacticM α) : CommandElabM α := do
liftTermElabM do
let params Grind.mkDefaultParams {}
let (ctx, state) Grind.GrindM.run (params := params) do
let methods Grind.getMethods
let grindCtx readThe Meta.Grind.Context
let symCtx readThe Sym.Context
let grindState get
let symState getThe Sym.State
let ctx := {
elaborator := `registerSymSimp,
ctx := grindCtx, sctx := symCtx, methods, params
}
return (ctx, { grindState, symState, goals := [] })
let (a, _) Tactic.Grind.GrindTacticM.run k ctx state
return a
def validateOptionSimprocSyntax (proc? : Option Syntax) : CommandElabM Unit := do
let some proc := proc? | return ()
discard <| withGrindTacticM <| Tactic.Grind.elabSymSimproc proc
@[builtin_command_elab Lean.Parser.Command.registerSymSimp]
def elabRegisterSymSimp : CommandElab := fun stx => do
let id := stx[1]
let name := id.getId
-- Check for duplicate variant
if (getSymSimpVariant? ( getEnv) name).isSome then
throwErrorAt id "Sym.simp variant `{name}` is already registered"
let fields := stx[3].getArgs
let mut pre? : Option Syntax := none
let mut post? : Option Syntax := none
let mut maxSteps? : Option Nat := none
let mut maxDischargeDepth? : Option Nat := none
for field in fields do
match field with
| `(sym_simp_field| maxSteps := $val:num) =>
unless maxSteps?.isNone do throwErrorAt field "duplicate `maxSteps` field"
maxSteps? := some val.getNat
| `(sym_simp_field| maxDischargeDepth := $val:num) =>
unless maxDischargeDepth?.isNone do throwErrorAt field "duplicate `maxDischargeDepth` field"
maxDischargeDepth? := some val.getNat
| `(sym_simp_field| pre := $proc) =>
unless pre?.isNone do throwErrorAt field "duplicate `pre` field"
pre? := some proc
| `(sym_simp_field| post := $proc) =>
unless post?.isNone do throwErrorAt field "duplicate `post` field"
post? := some proc
| _ => throwErrorAt field "unexpected field"
-- Validate pre/post by elaborating them
validateOptionSimprocSyntax pre?
validateOptionSimprocSyntax post?
let config := { maxSteps := maxSteps?.getD 100_000, maxDischargeDepth := maxDischargeDepth?.getD 2 }
let variant : SymSimpVariant := { pre?, post?, config }
modifyEnv fun env => symSimpVariantExtension.addEntry env { name, variant }
end Lean.Elab.Command

View File

@@ -0,0 +1,57 @@
/-
Copyright (c) 2026 Lean FRO, LLC. All Rights Reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Leonardo de Moura
-/
module
prelude
public import Lean.Elab.Tactic.Grind.Basic
public import Lean.Meta.Sym.Simp.Discharger
import Init.Sym.Simp.SimprocDSL
public section
namespace Lean.Elab.Tactic.Grind
open Meta Sym.Simp
/-- Elaboration function for `sym_simproc` syntax. -/
abbrev SymSimprocElab := Syntax GrindTacticM Simproc
/-- Elaboration function for `sym_discharger` syntax. -/
abbrev SymDischargerElab := Syntax GrindTacticM Discharger
unsafe builtin_initialize symSimprocElabAttribute : KeyedDeclsAttribute SymSimprocElab
mkElabAttribute SymSimprocElab `builtin_sym_simproc `sym_simproc
`Lean.Parser.Sym.Simp `Lean.Elab.Tactic.Grind.SymSimprocElab "sym_simproc"
unsafe builtin_initialize symDischargerElabAttribute : KeyedDeclsAttribute SymDischargerElab
mkElabAttribute SymDischargerElab `builtin_sym_discharger `sym_discharger
`Lean.Parser.Sym.Simp `Lean.Elab.Tactic.Grind.SymDischargerElab "sym_discharger"
/-- Elaborate a `sym_simproc` syntax node into a `Simproc`. -/
partial def elabSymSimproc (stx : Syntax) : GrindTacticM Simproc := do
let elabFns := symSimprocElabAttribute.getEntries ( getEnv) stx.getKind
for elabFn in elabFns do
try
return ( elabFn.value stx)
catch ex =>
match ex with
| .internal id _ =>
if id == unsupportedSyntaxExceptionId then continue
else throw ex
| _ => throw ex
throwErrorAt stx "unsupported sym_simproc syntax `{stx.getKind}`"
/-- Elaborate a `sym_discharger` syntax node into a `Discharger`. -/
def elabSymDischarger (stx : Syntax) : GrindTacticM Discharger := do
let elabFns := symDischargerElabAttribute.getEntries ( getEnv) stx.getKind
for elabFn in elabFns do
try
return ( elabFn.value stx)
catch ex =>
match ex with
| .internal id _ =>
if id == unsupportedSyntaxExceptionId then continue
else throw ex
| _ => throw ex
throwErrorAt stx "unsupported sym_discharger syntax `{stx.getKind}`"
end Lean.Elab.Tactic.Grind

View File

@@ -0,0 +1,99 @@
/-
Copyright (c) 2026 Lean FRO, LLC. All Rights Reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Leonardo de Moura
-/
module
prelude
import Lean.Elab.Tactic.Grind.SimprocDSL
import Init.Sym.Simp.SimprocDSL
import Lean.Meta.Sym.Simp.EvalGround
import Lean.Meta.Sym.Simp.Telescope
import Lean.Meta.Sym.Simp.ControlFlow
import Lean.Meta.Sym.Simp.Forall
import Lean.Meta.Sym.Simp.Rewrite
namespace Lean.Elab.Tactic.Grind
open Meta Sym.Simp
-- Simproc elaborators
@[builtin_sym_simproc ground]
def elabSimprocGround : SymSimprocElab := fun _ =>
return evalGround
@[builtin_sym_simproc telescope]
def elabSimprocTelescope : SymSimprocElab := fun _ =>
return simpTelescope
@[builtin_sym_simproc Lean.Parser.Sym.Simp.control]
def elabSimprocControl : SymSimprocElab := fun _ =>
return simpControl
@[builtin_sym_simproc Lean.Parser.Sym.Simp.arrowTelescope]
def elabSimprocArrowTelescope : SymSimprocElab := fun _ =>
return simpArrowTelescope
@[builtin_sym_simproc self]
def elabSimprocSelf : SymSimprocElab := fun _ =>
return simp
@[builtin_sym_simproc none]
def elabSimprocNone : SymSimprocElab := fun _ =>
return fun _ => return .rfl
def elabOptDischarger (discharger? : Option (TSyntax `sym_discharger)) : GrindTacticM Discharger := do
let some discharger := discharger? | return dischargeNone
elabSymDischarger discharger
@[builtin_sym_simproc rewriteSet]
def elabSimprocRewriteSet : SymSimprocElab := fun stx => do
let `(sym_simproc| rewrite $setName:ident $[with $d?]?) := stx | throwUnsupportedSyntax
let some ext getSymSimpExtension? setName.getId
| throwErrorAt setName "unknown Sym.simp theorem set `{setName}`"
let thms ext.getTheorems
return thms.rewrite ( elabOptDischarger d?)
@[builtin_sym_simproc rewriteInline]
def elabSimprocRewriteInline : SymSimprocElab := fun stx => do
let `(sym_simproc| rewrite [ $[$names:ident],* ] $[with $d?]?) := stx | throwUnsupportedSyntax
let mut thms : Theorems := {}
for name in names do
let declName realizeGlobalConstNoOverload name
thms := thms.insert ( mkTheoremFromDecl declName)
return thms.rewrite ( elabOptDischarger d?)
@[builtin_sym_simproc andThen]
def elabSimprocAndThen : SymSimprocElab := fun stx => do
let `(sym_simproc| $left >> $right) := stx | throwUnsupportedSyntax
let left elabSymSimproc left
let right elabSymSimproc right
return left >> right
@[builtin_sym_simproc Lean.Parser.Sym.Simp.orElse]
def elabSimprocOrElse : SymSimprocElab := fun stx => do
let `(sym_simproc| $left <|> $right) := stx | throwUnsupportedSyntax
let left elabSymSimproc left
let right elabSymSimproc right
return left <|> right
@[builtin_sym_simproc simprocParen]
def elabSimprocParen : SymSimprocElab := fun stx => do
let `(sym_simproc| ( $proc) ) := stx | throwUnsupportedSyntax
elabSymSimproc proc
-- Discharger elaborators
@[builtin_sym_discharger dischSelf]
def elabDischSelf : SymDischargerElab := fun _ =>
return dischargeSimpSelf
@[builtin_sym_discharger dischNone]
def elabDischNone : SymDischargerElab := fun _ =>
return dischargeNone
@[builtin_sym_discharger dischParen]
def elabDischParen : SymDischargerElab := fun stx => do
let `(sym_discharger| ( $d ) ) := stx | throwUnsupportedSyntax
elabSymDischarger d
end Lean.Elab.Tactic.Grind

View File

@@ -6,7 +6,15 @@ Authors: Leonardo de Moura
module
prelude
import Lean.Elab.Tactic.Grind.Basic
import Lean.Elab.Tactic.Grind.SimprocDSL
import Lean.Meta.Sym.Grind
import Lean.Meta.Sym.Simp.Variant
import Lean.Meta.Sym.Simp.Rewrite
import Lean.Meta.Sym.Simp.EvalGround
import Lean.Meta.Sym.Simp.Goal
import Lean.Meta.Sym.Simp.Attr
import Lean.Meta.Sym.Simp.ControlFlow
import Lean.Meta.Sym.Simp.Forall
import Lean.Meta.Tactic.Apply
import Lean.Elab.SyntheticMVars
namespace Lean.Elab.Tactic.Grind
@@ -135,4 +143,75 @@ private def getOrCreateBackwardRuleFromTerm (term : Syntax) : GrindTacticM Sym.B
let goal liftGrindM <| Grind.Goal.internalizeAll goal
replaceMainGoal [goal]
section
open Sym.Simp
def trivialSimproc : Simproc := fun _ =>
return .rfl
def elabOptSimproc (stx? : Option Syntax) : GrindTacticM Simproc := do
let some stx := stx? | return trivialSimproc
elabSymSimproc stx
def resolveExtraTheorems (ids? : Option (Array (TSyntax `ident))) : GrindTacticM (Array ExtraTheorem × Array Theorem) := do
let some ids := ids? | return (#[], #[])
let mut extras := #[]
let mut thms := #[]
let lctx getLCtx
for id in ids do
if let some decl := lctx.findFromUserName? id.getId then
extras := extras.push <| .fvar decl.fvarId
thms := thms.push ( mkTheoremFromExpr decl.toExpr)
else
let declName realizeGlobalConstNoOverload id
extras := extras.push <| .const declName
thms := thms.push ( mkTheoremFromDecl declName)
return (extras, thms)
def addExtraTheorems (post : Simproc) (extraThms : Array Theorem) : GrindTacticM Simproc := do
if extraThms.isEmpty then return post
let mut thms : Theorems := {}
for thm in extraThms do
thms := thms.insert thm
return post >> thms.rewrite
def mkDefaultMethods (extraThms : Array Theorem) : GrindTacticM Sym.Simp.Methods := do
let thms getSymSimpTheorems
let pre := simpControl >> simpArrowTelescope
let post addExtraTheorems (evalGround >> thms.rewrite) extraThms
return { pre, post }
def elabVariant (variantName : Name) (extraThms : Array Theorem) : GrindTacticM (Sym.Simp.Methods × Sym.Simp.Config) := do
if variantName.isAnonymous then
return ( mkDefaultMethods extraThms, {})
let some v := getSymSimpVariant? ( getEnv) variantName
| throwError "unknown Sym.simp variant `{variantName}`"
let pre elabOptSimproc v.pre?
let post addExtraTheorems ( elabOptSimproc v.post?) extraThms
return ({ pre, post}, v.config)
@[builtin_grind_tactic Parser.Tactic.Grind.symSimp] def evalSymSimp : GrindTactic := fun stx => withMainContext do
ensureSym
let `(grind| simp $[$variantId?]? $[[ $[$extraIds],* ]]?) := stx | throwUnsupportedSyntax
-- Resolve variant
let variantName := variantId?.map (·.getId) |>.getD .anonymous
-- Resolve extra theorems (local hypotheses first, then global constants)
let (extras, thms) resolveExtraTheorems extraIds
-- Cache lookup/creation
let cacheKey : SimpCacheKey := { variant := variantName, extras }
let simpState := ( get).cache.simpState[cacheKey]?.getD {}
let (methods, config) elabVariant variantName thms
let goal getMainGoal
let (simpResult, simpState) liftGrindM <| goal.withContext do
Sym.Simp.SimpM.run (Sym.Simp.simp ( goal.mvarId.getType)) methods config simpState
-- Save updated cache
modify fun s => { s with cache.simpState := s.cache.simpState.insert cacheKey simpState }
-- Apply result to goal
match ( liftGrindM <| Sym.Simp.Result.toSimpGoalResult simpResult goal.mvarId) with
| .closed => replaceMainGoal []
| .goal mvarId => replaceMainGoal [{ goal with mvarId }]
| .noProgress => throwError "`Sym.simp` made no progress"
end
end Lean.Elab.Tactic.Grind

View File

@@ -787,6 +787,7 @@ where
throw ex
-- `evalSuggest` implementation
set_option compiler.ignoreBorrowAnnotation true in
@[export lean_eval_suggest_tactic]
private partial def evalSuggestImpl : TryTactic := fun tac => do
trace[try.debug] "{tac}"

View File

@@ -309,6 +309,8 @@ structure Context where
heedElabAsElim : Bool := true
/-- Noncomputable sections automatically add the `noncomputable` modifier to any declaration we cannot generate code for. -/
isNoncomputableSection : Bool := false
/-- `true` when inside a `meta section`. -/
isMetaSection : Bool := false
/-- When `true` we skip TC failures. We use this option when processing patterns. -/
ignoreTCFailures : Bool := false
/-- `true` when elaborating patterns. It affects how we elaborate named holes. -/
@@ -371,7 +373,7 @@ whole monad stack at every use site. May eventually be covered by `deriving`.
-/
@[always_inline]
instance : Monad TermElabM :=
let i := inferInstanceAs (Monad TermElabM)
let i : Monad TermElabM := inferInstance
{ pure := i.pure, bind := i.bind }
open Meta

View File

@@ -1193,8 +1193,8 @@ namespace ConstantInfo
def instantiateTypeLevelParams (c : ConstantInfo) (ls : List Level) : Expr :=
c.toConstantVal.instantiateTypeLevelParams ls
def instantiateValueLevelParams! (c : ConstantInfo) (ls : List Level) : Expr :=
c.value!.instantiateLevelParams c.levelParams ls
def instantiateValueLevelParams! (c : ConstantInfo) (ls : List Level) (allowOpaque := false) : Expr :=
(c.value! (allowOpaque := allowOpaque)).instantiateLevelParams c.levelParams ls
end ConstantInfo
@@ -2755,13 +2755,28 @@ def mkThmOrUnsafeDef [Monad m] [MonadEnv m] (thm : TheoremVal) : m Declaration :
else
return .thmDecl thm
/-- Environment extension for overriding the height that `getMaxHeight` assigns to a definition.
This is consulted for all definitions regardless of their reducibility hints. Currently used by
structural recursion to ensure that parent definitions get the correct height even though the
`_f` helper definitions are marked as `.abbrev` (which `getMaxHeight` would otherwise ignore). -/
builtin_initialize defHeightOverrideExt : EnvExtension (NameMap UInt32)
registerEnvExtension (pure {}) (asyncMode := .local)
/-- Register a height override for a definition so that `getMaxHeight` uses it. -/
def setDefHeightOverride (env : Environment) (declName : Name) (height : UInt32) : Environment :=
defHeightOverrideExt.modifyState env fun m => m.insert declName height
def getMaxHeight (env : Environment) (e : Expr) : UInt32 :=
let overrides := defHeightOverrideExt.getState env
e.foldConsts 0 fun constName max =>
match env.findAsync? constName with
| some { kind := .defn, constInfo := info, .. } =>
match info.get.hints with
| ReducibilityHints.regular h => if h > max then h else max
| _ => max
| _ => max
match overrides.find? constName with
| some h => if h > max then h else max
| none =>
match env.findAsync? constName with
| some { kind := .defn, constInfo := info, .. } =>
match info.get.hints with
| ReducibilityHints.regular h => if h > max then h else max
| _ => max
| _ => max
end Lean

View File

@@ -27,6 +27,7 @@ public import Lean.Meta.Match
public import Lean.Meta.ReduceEval
public import Lean.Meta.Closure
public import Lean.Meta.AbstractNestedProofs
public import Lean.Meta.InstanceNormalForm
public import Lean.Meta.LetToHave
public import Lean.Meta.ForEachExpr
public import Lean.Meta.Transform

View File

@@ -87,8 +87,13 @@ partial def visit (e : Expr) : M Expr := do
lctx := lctx.modifyLocalDecl xFVarId fun _ => localDecl
withLCtx lctx localInstances k
checkCache { val := e : ExprStructEq } fun _ => do
if ( isNonTrivialProof e) then
/- Ensure proofs nested in type are also abstracted -/
if ( isNonTrivialProof e) && !e.hasSorry then
/- Ensure proofs nested in type are also abstracted.
We skip abstraction for proofs containing `sorry` to avoid generating extra
"declaration uses sorry" warnings for auxiliary theorems: one per abstracted proof
instead of a single warning for the main declaration. Additionally, the `zetaDelta`
expansion in `mkAuxTheorem` can inline let-bound sorry values, causing warnings
even for proofs that only transitively reference sorry-containing definitions. -/
abstractProof e ( read).cache visit
else match e with
| .lam ..

View File

@@ -565,7 +565,7 @@ abbrev MetaM := ReaderT Context $ StateRefT State CoreM
-- Make the compiler generate specialized `pure`/`bind` so we do not have to optimize through the
-- whole monad stack at every use site. May eventually be covered by `deriving`.
@[always_inline]
instance : Monad MetaM := let i := inferInstanceAs (Monad MetaM); { pure := i.pure, bind := i.bind }
instance : Monad MetaM := let i : Monad MetaM := inferInstance; { pure := i.pure, bind := i.bind }
instance : Inhabited (MetaM α) where
default := fun _ _ => default
@@ -1321,7 +1321,7 @@ private def getDefInfoTemp (info : ConstantInfo) : MetaM (Option ConstantInfo) :
`constName` is an instance. This difference should be irrelevant for `isClassQuickConst?`. -/
private def getConstTemp? (constName : Name) : MetaM (Option ConstantInfo) := do
match ( getEnv).find? constName with
| some (info@(ConstantInfo.thmInfo _)) => getTheoremInfo info
| some (ConstantInfo.thmInfo _) => return none
| some (info@(ConstantInfo.defnInfo _)) => getDefInfoTemp info
| some info => pure (some info)
| none => throwUnknownConstantAt ( getRef) constName

View File

@@ -431,7 +431,8 @@ end Closure
A "closure" is computed, and a term of the form `name.{u_1 ... u_n} t_1 ... t_m` is
returned where `u_i`s are universe parameters and metavariables `type` and `value` depend on,
and `t_j`s are free and meta variables `type` and `value` depend on. -/
def mkAuxDefinition (name : Name) (type : Expr) (value : Expr) (zetaDelta : Bool := false) (compile : Bool := true) : MetaM Expr := do
def mkAuxDefinition (name : Name) (type : Expr) (value : Expr) (zetaDelta : Bool := false)
(compile : Bool := true) (logCompileErrors : Bool := true) : MetaM Expr := do
let result Closure.mkValueTypeClosure type value zetaDelta
let env getEnv
let hints := ReducibilityHints.regular (getMaxHeight env result.value + 1)
@@ -439,14 +440,16 @@ def mkAuxDefinition (name : Name) (type : Expr) (value : Expr) (zetaDelta : Bool
result.type result.value hints)
addDecl decl
if compile then
compileDecl decl
compileDecl decl (logErrors := logCompileErrors)
return mkAppN (mkConst name result.levelArgs.toList) result.exprArgs
/-- Similar to `mkAuxDefinition`, but infers the type of `value`. -/
def mkAuxDefinitionFor (name : Name) (value : Expr) (zetaDelta : Bool := false) (compile := true) : MetaM Expr := do
def mkAuxDefinitionFor (name : Name) (value : Expr) (zetaDelta : Bool := false)
(compile := true) (logCompileErrors : Bool := true) : MetaM Expr := do
let type inferType value
let type := type.headBeta
mkAuxDefinition name type value (zetaDelta := zetaDelta) (compile := compile)
(logCompileErrors := logCompileErrors)
/--
Create an auxiliary theorem with the given name, type and value. It is similar to `mkAuxDefinition`.

View File

@@ -1126,6 +1126,7 @@ def checkAssignment (mvarId : MVarId) (fvars : Array Expr) (v : Expr) : MetaM (O
return none
return some v
set_option compiler.ignoreBorrowAnnotation true in
-- Implementation for `_root_.Lean.MVarId.checkedAssign`
@[export lean_checked_assign]
def checkedAssignImpl (mvarId : MVarId) (val : Expr) : MetaM Bool := do
@@ -2233,6 +2234,7 @@ private def whnfCoreAtDefEq (e : Expr) : MetaM Expr := do
else
whnfCore e
set_option compiler.ignoreBorrowAnnotation true in
@[export lean_is_expr_def_eq]
partial def isExprDefEqAuxImpl (t : Expr) (s : Expr) : MetaM Bool := withIncRecDepth do
withTraceNodeBefore `Meta.isDefEq (fun _ => return m!"{t} =?= {s}") do

View File

@@ -46,11 +46,7 @@ External users wanting to look up names should be using `Lean.getConstInfo`.
def getUnfoldableConst? (constName : Name) : MetaM (Option ConstantInfo) := do
let some ainfo := ( getEnv).findAsync? constName | throwUnknownConstantAt ( getRef) constName
match ainfo.kind with
| .thm =>
if ( shouldReduceAll) then
return some ainfo.toConstantInfo
else
return none
| .thm => return none
| .defn => if ( canUnfold ainfo.toConstantInfo) then return ainfo.toConstantInfo else return none
| _ => return none
@@ -59,7 +55,7 @@ As with `getUnfoldableConst?` but return `none` instead of failing if the consta
-/
def getUnfoldableConstNoEx? (constName : Name) : MetaM (Option ConstantInfo) := do
match ( getEnv).find? constName with
| some (info@(.thmInfo _)) => getTheoremInfo info
| some (.thmInfo _) => return none
| some (info@(.defnInfo _)) => if ( canUnfold info) then return info else return none
| some (.axiomInfo _) => recordUnfoldAxiom constName; return none
| _ => return none

View File

@@ -206,6 +206,7 @@ because it overrides unrelated configurations.
else
withConfig (fun cfg => { cfg with beta := true, iota := true, zeta := true, zetaHave := true, zetaDelta := true, proj := .yesWithDelta, etaStruct := .all }) x
set_option compiler.ignoreBorrowAnnotation true in
@[export lean_infer_type]
def inferTypeImp (e : Expr) : MetaM Expr :=
let rec infer (e : Expr) : MetaM Expr := do

View File

@@ -0,0 +1,193 @@
/-
Copyright (c) 2026 Lean FRO, LLC. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Authors: Eric Wieser, Kyle Miller, Jovan Gerbscheid, Kim Morrison, Sebastian Ullrich
-/
module
prelude
public import Lean.Meta.Closure
public import Lean.Meta.SynthInstance
public import Lean.Meta.CtorRecognizer
public section
/-!
# Instance Normal Form
Both `inferInstanceAs` and the default `deriving` handler normalize instance bodies to
"instance normal form". This ensures that when deriving or inferring an instance for a
semireducible type definition, the definition's RHS is not leaked when reduced at lower
than semireducible transparency.
## Algorithm
Given an instance `i : I` and expected type `I'` (where `I'` must be mvar-free),
`normalizeInstance` constructs a result instance as follows, executing all steps at
`instances` transparency:
1. If `I'` is not a class, return `i` unchanged.
2. If `I'` is a proposition, wrap `i` in an auxiliary theorem of type `I'` and return it
(controlled by `backward.inferInstanceAs.wrap.instances`).
3. Reduce `i` to whnf.
4. If `i` is not a constructor application: if the type of `i` is already defeq to `I'`,
return `i`; otherwise wrap it in an auxiliary definition of type `I'` and return it
(controlled by `backward.inferInstanceAs.wrap.instances`).
5. Otherwise, for `i = ctor a₁ ... aₙ` with `ctor : C ?p₁ ... ?pₙ`:
- Unify `C ?p₁ ... ?pₙ` with `I'`.
- Return a new application `ctor a₁' ... aₙ' : I'` where each `aᵢ'` is constructed as:
- If the field type is a proposition: assign directly if types are defeq, otherwise
wrap in an auxiliary theorem.
- If the field type is a class: first try to reuse an existing synthesized instance
for the target type (controlled by `backward.inferInstanceAs.wrap.reuseSubInstances`);
if that fails, recurse with source instance `aᵢ` and expected type `?pᵢ`.
- Otherwise (data field): assign directly if types are defeq, otherwise wrap in an
auxiliary definition to fix the type (controlled by `backward.inferInstanceAs.wrap.data`).
## Options
- `backward.inferInstanceAs.wrap`: master switch for normalization in both `inferInstanceAs`
and the default `deriving` handler
- `backward.inferInstanceAs.wrap.reuseSubInstances`: reuse existing instances for sub-instance
fields to avoid non-defeq instance diamonds
- `backward.inferInstanceAs.wrap.instances`: wrap non-reducible instances in auxiliary
definitions
- `backward.inferInstanceAs.wrap.data`: wrap data fields in auxiliary definitions
-/
namespace Lean.Meta
register_builtin_option backward.inferInstanceAs.wrap : Bool := {
defValue := true
descr := "normalize instance bodies to constructor-based normal form in `inferInstanceAs` and the default `deriving` handler"
}
register_builtin_option backward.inferInstanceAs.wrap.reuseSubInstances : Bool := {
defValue := true
descr := "when recursing into sub-instances, reuse existing instances for the target type instead of re-wrapping them, which can be important to avoid non-defeq instance diamonds"
}
register_builtin_option backward.inferInstanceAs.wrap.instances : Bool := {
defValue := true
descr := "wrap non-reducible instances in auxiliary definitions to fix their types"
}
register_builtin_option backward.inferInstanceAs.wrap.data : Bool := {
defValue := true
descr := "wrap data fields in auxiliary definitions to fix their types"
}
builtin_initialize registerTraceClass `Meta.instanceNormalForm
/--
Rebuild a type application with fresh synthetic metavariables for instance-implicit arguments.
Non-instance-implicit arguments are assigned from the original application's arguments.
If the function is over-applied, extra arguments are preserved.
-/
def abstractInstImplicitArgs (type : Expr) : MetaM Expr := do
let fn := type.getAppFn
let args := type.getAppArgs
let (mvars, bis, _) forallMetaTelescope ( inferType fn)
for i in [:mvars.size] do
unless bis[i]!.isInstImplicit do
mvars[i]!.mvarId!.assign args[i]!
let args := mvars ++ args.drop mvars.size
instantiateMVars (mkAppN fn args)
/--
Normalize an instance value to "instance normal form".
See the module docstring for the full algorithm specification.
-/
partial def normalizeInstance (inst expectedType : Expr) (compile : Bool := true)
(logCompileErrors : Bool := true) (isMeta : Bool := false) : MetaM Expr := withTransparency .instances do
withTraceNode `Meta.instanceNormalForm
(fun _ => return m!"type: {expectedType}") do
let some className isClass? expectedType
| return inst
trace[Meta.instanceNormalForm] "class is {className}"
if isProp expectedType then
if backward.inferInstanceAs.wrap.instances.get ( getOptions) then
return ( mkAuxTheorem expectedType inst (zetaDelta := true))
else
return inst
-- Try to reduce it to a constructor.
let inst whnf inst
inst.withApp fun f args => do
let some (.ctorInfo ci) f.constName?.mapM getConstInfo
| do
trace[Meta.instanceNormalForm] "did not reduce to constructor application, returning/wrapping as is: {inst}"
if backward.inferInstanceAs.wrap.instances.get ( getOptions) then
let instType inferType inst
if isDefEq expectedType instType then
return inst
else
let name mkAuxDeclName
let wrapped mkAuxDefinition name expectedType inst (compile := false)
setReducibilityStatus name .implicitReducible
if isMeta then modifyEnv (markMeta · name)
if compile then
compileDecls (logErrors := logCompileErrors) #[name]
enableRealizationsForConst name
return wrapped
else
return inst
let (mvars, _, cls) forallMetaTelescope ( inferType f)
if h₁ : args.size mvars.size then
throwError "instance normal form: incorrect number of arguments for \
constructor application `{f}`: {args}"
else
unless isDefEq expectedType cls do
throwError "instance normal form: `{expectedType}` does not unify with the conclusion of \
`{.ofConstName ci.name}`"
for h₂ : i in ci.numParams...args.size do
have : i < mvars.size := by
simp only [ne_eq, Decidable.not_not] at h₁
rw [ h₁]
get_elem_tactic
let mvarId := mvars[i].mvarId!
let mvarDecl mvarId.getDecl
let argExpectedType instantiateMVars mvarDecl.type
let arg := args[i]
if isProp argExpectedType then
let argType inferType arg
if isDefEq argExpectedType argType then
mvarId.assign arg
else
trace[Meta.instanceNormalForm] "proof field {i} does not have expected type {argExpectedType} but {argType}, wrapping in auxiliary theorem: {arg}"
mvarId.assign ( mkAuxTheorem argExpectedType arg (zetaDelta := true))
-- Recurse into instance arguments of the constructor
else if ( isClass? argExpectedType).isSome then
if backward.inferInstanceAs.wrap.reuseSubInstances.get ( getOptions) then
-- Reuse existing instance for the target type if any. This is especially important when recursing
-- as it guarantees subinstances of overlapping instances are defeq under more than just
-- semireducible transparency.
try
if let .some new trySynthInstance argExpectedType then
trace[Meta.instanceNormalForm] "using existing instance {new}"
mvarId.assign new
continue
catch _ => pure ()
mvarId.assign ( normalizeInstance arg argExpectedType (compile := compile)
(logCompileErrors := logCompileErrors) (isMeta := isMeta))
else
-- For data fields, assign directly or wrap in aux def to fix types.
if backward.inferInstanceAs.wrap.data.get ( getOptions) then
let argType inferType arg
if isDefEq argExpectedType argType then
mvarId.assign arg
else
let name mkAuxDeclName
mvarId.assign ( mkAuxDefinition name argExpectedType arg (compile := false))
setInlineAttribute name
if isMeta then modifyEnv (markMeta · name)
if compile then
compileDecls (logErrors := logCompileErrors) #[name]
enableRealizationsForConst name
else
mvarId.assign arg
return mkAppN f ( mvars.mapM instantiateMVars)
end Lean.Meta

View File

@@ -85,6 +85,7 @@ private def isMVarWithGreaterDepth (v : Level) (mvarId : LMVarId) : MetaM Bool :
| Level.mvar mvarId' => return ( mvarId'.getLevel) > ( mvarId.getLevel)
| _ => return false
set_option compiler.ignoreBorrowAnnotation true in
mutual
private partial def solve (u v : Level) : MetaM LBool := do

View File

@@ -138,6 +138,7 @@ Creates conditional equations and splitter for the given match auxiliary declara
See also `getEquationsFor`.
-/
set_option compiler.ignoreBorrowAnnotation true in
@[export lean_get_match_equations_for]
def getEquationsForImpl (matchDeclName : Name) : MetaM MatchEqns := do
/-
@@ -246,6 +247,7 @@ where go baseName splitterName := withConfig (fun c => { c with etaStruct := .no
let result := { eqnNames, splitterName, splitterMatchInfo }
registerMatchEqns matchDeclName result
set_option compiler.ignoreBorrowAnnotation true in
/--
Generate the congruence equations for the given match auxiliary declaration.
The congruence equations have a completely unrestricted left-hand side (arbitrary discriminants),

View File

@@ -785,6 +785,7 @@ def isDefEqApp (tFn : Expr) (t : Expr) (s : Expr) (_ : tFn = t.getAppFn) : DefEq
let numArgs := t.getAppNumArgs
isDefEqAppWithInfo t s (numArgs - 1) info
set_option compiler.ignoreBorrowAnnotation true in
/--
`isDefEqMain` implementation.
-/

View File

@@ -24,4 +24,5 @@ public import Lean.Meta.Sym.Simp.ControlFlow
public import Lean.Meta.Sym.Simp.Goal
public import Lean.Meta.Sym.Simp.Telescope
public import Lean.Meta.Sym.Simp.Attr
public import Lean.Meta.Sym.Simp.Variant
public import Lean.Meta.Sym.Simp.RegisterCommand

View File

@@ -40,6 +40,7 @@ abbrev cacheResult (e : Expr) (r : Result) : SimpM Result := do
modify fun s => { s with persistentCache := s.persistentCache.insert { expr := e } r }
return r
set_option compiler.ignoreBorrowAnnotation true in
@[export lean_sym_simp]
def simpImpl (e₁ : Expr) : SimpM Result := withIncRecDepth do
let numSteps := ( get).numSteps

View File

@@ -6,6 +6,7 @@ Authors: Leonardo de Moura
module
prelude
public import Lean.Meta.Sym.Simp.Attr
public import Lean.Meta.Sym.Simp.Variant
public meta import Init.Data.ToString.Name
public meta import Init.Data.String.Extra
public section

View File

@@ -9,6 +9,7 @@ public import Lean.Meta.Sym.Simp.Simproc
public import Lean.Meta.Sym.Simp.Theorems
public import Lean.Meta.Sym.Simp.App
public import Lean.Meta.Sym.Simp.Discharger
import Lean.Meta.ACLt
import Lean.Meta.Sym.InstantiateS
import Lean.Meta.Sym.InstantiateMVarsS
import Init.Data.Range.Polymorphic.Iterators
@@ -71,10 +72,16 @@ public def Theorem.rewrite (thm : Theorem) (e : Expr) (d : Discharger := dischar
let expr instantiateRevBetaS rhs args.toArray
if isSameExpr e expr then
return mkRflResultCD isCD
else if !( checkPerm thm.perm e expr) then
return mkRflResultCD isCD
else
return .step expr proof (contextDependent := isCD)
else
return .rfl
where
checkPerm (perm : Bool) (e result : Expr) : MetaM Bool := do
if !perm then return true
acLt result e
public def Theorems.rewrite (thms : Theorems) (d : Discharger := dischargeNone) : Simproc := fun e => do
-- Track `cd` across all attempted theorems. If theorem A fails with cd=true

View File

@@ -106,6 +106,7 @@ structure Config where
Prevents infinite loops when conditional rewrite rules trigger recursive discharge attempts.
-/
maxDischargeDepth : Nat := 2
deriving Inhabited
/--
The result of simplifying an expression `e`.

View File

@@ -8,7 +8,9 @@ prelude
public import Lean.Meta.Sym.Pattern
public import Lean.Meta.DiscrTree
import Lean.Meta.Sym.Simp.DiscrTree
import Lean.Meta.AppBuilder
import Lean.ExtraModUses
import Init.Omega
public section
namespace Lean.Meta.Sym.Simp
@@ -25,6 +27,10 @@ structure Theorem where
pattern : Pattern
/-- Right-hand side of the equation. -/
rhs : Expr
/-- If `true`, the theorem is a permutation rule (e.g., `x + y = y + x`).
Rewriting is only applied when the result is strictly less than the input
(using `acLt`), preventing infinite loops. -/
perm : Bool := false
deriving Inhabited
instance : BEq Theorem where
@@ -44,9 +50,112 @@ def Theorems.getMatch (thms : Theorems) (e : Expr) : Array Theorem :=
def Theorems.getMatchWithExtra (thms : Theorems) (e : Expr) : Array (Theorem × Nat) :=
Sym.getMatchWithExtra thms.thms e
/--
Check whether `lhs` and `rhs` (with `numVars` pattern variables represented as `.bvar` indices
`≥ 0` before any binder entry) are permutations of each other — same structure with only
pattern variable indices rearranged via a consistent bijection.
Bvars with index `< offset` are "local" (introduced by binders inside the pattern) and must
match exactly. Bvars with index `≥ offset` are pattern variables and may be permuted,
but the mapping must be a bijection.
Simplified compared to `Meta.simp`'s `isPerm`:
- Uses de Bruijn indices instead of metavariables
- No `.proj` (folded into applications) or `.letE` (zeta-expanded) cases
-/
private abbrev IsPermM := ReaderT Nat $ StateT (Array (Option Nat)) $ Except Unit
private partial def isPermAux (a b : Expr) : IsPermM Unit := do
match a, b with
| .bvar i, .bvar j =>
let offset read
if i < offset && j < offset then
unless i == j do throw ()
else if i >= offset && j >= offset then
let pi := i - offset
let pj := j - offset
let fwd get
if h : pi >= fwd.size then throw () else
match fwd[pi] with
| none =>
-- Check injectivity: pj must not already be a target of another mapping
if fwd.contains (some pj) then throw ()
set (fwd.set pi (some pj))
| some pj' => unless pj == pj' do throw ()
else throw ()
| .app f₁ a₁, .app f₂ a₂ => isPermAux f₁ f₂; isPermAux a₁ a₂
| .mdata _ s, t => isPermAux s t
| s, .mdata _ t => isPermAux s t
| .forallE _ d₁ b₁ _, .forallE _ d₂ b₂ _ => isPermAux d₁ d₂; withReader (· + 1) (isPermAux b₁ b₂)
| .lam _ d₁ b₁ _, .lam _ d₂ b₂ _ => isPermAux d₁ d₂; withReader (· + 1) (isPermAux b₁ b₂)
| s, t => unless s == t do throw ()
def isPerm (numVars : Nat) (lhs rhs : Expr) : Bool :=
((isPermAux lhs rhs).run 0 |>.run (Array.replicate numVars none)) matches .ok _
/-- Describes how a theorem's conclusion was adapted to an equality for use in `Sym.simp`. -/
private inductive EqAdaptation where
/-- Already an equality `lhs = rhs`. Proof is used as-is. -/
| eq
/-- Was `¬ p`. Proof `h` adapted to `eq_false h : p = False`. -/
| eqFalse
/-- Was `p ↔ q`. Proof `h` adapted to `propext h : p = q`. -/
| iff
/-- Was a proposition `p`. Proof `h` adapted to `eq_true h : p = True`. -/
| eqTrue
/--
Analyze the conclusion of a theorem type and extract `(lhs, rhs)` for use as a
rewrite rule in `Sym.simp`. Handles:
- `lhs = rhs` — used as-is
- `¬ p` — adapted to `p = False`
- `p ↔ q` — adapted to `p = q`
- `p` (proposition) — adapted to `p = True`
-/
private def selectEqKey (type : Expr) : MetaM (Expr × Expr × EqAdaptation) := do
match_expr type with
| Eq _ lhs rhs => return (lhs, rhs, .eq)
| Not p => return (p, mkConst ``False, .eqFalse)
| Iff lhs rhs => return (lhs, rhs, .iff)
| _ =>
unless ( isProp type) do
throwError "cannot use as a simp theorem, conclusion is not a proposition{indentExpr type}"
return (type, mkConst ``True, .eqTrue)
/--
Wrap a proof expression according to the adaptation applied to its type.
Given a proof `h : <original type>`, returns a proof of the adapted equality.
This wrapping must be applied AFTER the proof has been applied to its quantified arguments.
-/
private def wrapProof (numVars : Nat) (expr : Expr) (adaptation : EqAdaptation) : MetaM Expr :=
match adaptation with
| .eq => return expr
| .eqFalse =>
wrapInner numVars expr fun h => mkAppM ``eq_false #[h]
| .iff =>
wrapInner numVars expr fun h => mkAppM ``propext #[h]
| .eqTrue =>
wrapInner numVars expr fun h => mkAppM ``eq_true #[h]
where
/-- Wraps the innermost application of `expr` (after `numVars` arguments) with `wrap`. -/
wrapInner (numVars : Nat) (expr : Expr) (wrap : Expr MetaM Expr) : MetaM Expr := do
let type inferType expr
forallBoundedTelescope type numVars fun xs _ => do
let h := mkAppN expr xs
mkLambdaFVars xs ( wrap h)
def mkTheoremFromDecl (declName : Name) : MetaM Theorem := do
let (pattern, rhs) mkEqPatternFromDecl declName
return { expr := mkConst declName, pattern, rhs }
let (pattern, (rhs, adaptation)) mkPatternFromDeclWithKey declName selectEqKey
let expr wrapProof pattern.varTypes.size (mkConst declName) adaptation
let perm := isPerm pattern.varTypes.size pattern.pattern rhs
return { expr, pattern, rhs, perm }
/-- Create a `Theorem` from a proof expression. Handles equalities, `¬`, `↔`, and propositions. -/
def mkTheoremFromExpr (e : Expr) : MetaM Theorem := do
let (pattern, (rhs, adaptation)) mkPatternFromExprWithKey e [] selectEqKey
let expr wrapProof pattern.varTypes.size e adaptation
let perm := isPerm pattern.varTypes.size pattern.pattern rhs
return { expr, pattern, rhs, perm }
/--
Environment extension storing a set of `Sym.Simp` theorems.

View File

@@ -0,0 +1,45 @@
/-
Copyright (c) 2026 Lean FRO, LLC. All Rights Reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Leonardo de Moura
-/
module
prelude
public import Lean.Meta.Sym.Simp.SimpM
import Lean.ScopedEnvExtension
public section
namespace Lean.Meta.Sym.Simp
/--
A named `Sym.simp` variant. Stores `pre`/`post` simproc chains as syntax
(elaborated at use time in `GrindTacticM`) and configuration overrides.
-/
structure SymSimpVariant where
/-- Pre-processing simproc chain (elaborated at use time). -/
pre? : Option Syntax := none
/-- Post-processing simproc chain (elaborated at use time). -/
post? : Option Syntax := none
/-- Configuration overrides. -/
config : Config := {}
deriving Inhabited
/-- Entry for the scoped environment extension. -/
structure SymSimpVariantEntry where
name : Name
variant : SymSimpVariant
deriving Inhabited
/-- Persistent environment extension mapping variant names to their definitions. -/
builtin_initialize symSimpVariantExtension :
SimpleScopedEnvExtension SymSimpVariantEntry (Std.HashMap Name SymSimpVariant)
registerSimpleScopedEnvExtension {
name := `symSimpVariantExtension
initial := {}
addEntry := fun map entry => map.insert entry.name entry.variant
}
/-- Look up a named `Sym.simp` variant. -/
def getSymSimpVariant? (env : Environment) (name : Name) : Option SymSimpVariant :=
(symSimpVariantExtension.getState env)[name]?
end Lean.Meta.Sym.Simp

View File

@@ -944,6 +944,7 @@ def synthInstance (type : Expr) (maxResultSize? : Option Nat := none) : MetaM Ex
| none => throwFailedToSynthesize type)
(fun _ => throwFailedToSynthesize type)
set_option compiler.ignoreBorrowAnnotation true in
@[export lean_synth_pending]
private def synthPendingImp (mvarId : MVarId) : MetaM Bool := withIncRecDepth <| mvarId.withContext do
let mvarDecl mvarId.getDecl

View File

@@ -10,18 +10,18 @@ import Lean.Meta.Transform
public section
namespace Lean.Meta
def delta? (e : Expr) (p : Name Bool := fun _ => true) : CoreM (Option Expr) :=
def delta? (e : Expr) (p : Name Bool := fun _ => true) (allowOpaque := false) : CoreM (Option Expr) :=
matchConst e.getAppFn (fun _ => return none) fun fInfo fLvls => do
if p fInfo.name && fInfo.hasValue && fInfo.levelParams.length == fLvls.length then
let f instantiateValueLevelParams fInfo fLvls
if p fInfo.name && fInfo.hasValue (allowOpaque := allowOpaque) && fInfo.levelParams.length == fLvls.length then
let f instantiateValueLevelParams fInfo fLvls (allowOpaque := allowOpaque)
return some (f.betaRev e.getAppRevArgs (useZeta := true))
else
return none
/-- Low-level delta expansion. It is used to implement equation lemmas and elimination principles for recursive definitions. -/
def deltaExpand (e : Expr) (p : Name Bool) : CoreM Expr :=
def deltaExpand (e : Expr) (p : Name Bool) (allowOpaque := false) : CoreM Expr :=
Core.transform e fun e => do
match ( delta? e p) with
match ( delta? e p (allowOpaque := allowOpaque)) with
| some e' => return .visit e'
| none => return .continue

View File

@@ -347,11 +347,13 @@ partial def foldAndCollect (oldIH newIH : FVarId) (isRecCall : Expr → Option E
if e.getAppArgs.any (·.isFVarOf oldIH) then
-- Sometimes Fix.lean abstracts over oldIH in a proof definition.
-- So beta-reduce that definition. We need to look through theorems here!
if let some e' withTransparency .all do unfoldDefinition? e then
return foldAndCollect oldIH newIH isRecCall e'
else
throwError "Internal error in `foldAndCollect`: Cannot reduce application of `{e.getAppFn}` in:{indentExpr e}"
-- So delta-beta-reduce that definition. We need to look through theorems here!
if let .const declName lvls := e.getAppFn then
if let some cinfo := ( getEnv).find? declName then
if let some val := cinfo.value? (allowOpaque := true) then
let e' := (val.instantiateLevelParams cinfo.levelParams lvls).betaRev e.getAppRevArgs
return foldAndCollect oldIH newIH isRecCall e'
throwError "Internal error in `foldAndCollect`: Cannot reduce application of `{e.getAppFn}` in:{indentExpr e}"
match e with
| .app e1 e2 =>
@@ -742,6 +744,13 @@ partial def buildInductionBody (toErase toClear : Array FVarId) (goal : Expr)
let b' buildInductionBody toErase toClear goal' oldIH newIH isRecCall (b.instantiate1 x)
mkLambdaFVars #[x] b'
-- Unfold constant applications that take `oldIH` as an argument (e.g. `_f` auxiliary
-- definitions from structural recursion), so that we can see their body structure.
-- Similar to the case in `foldAndCollect`.
if e.getAppFn.isConst && e.getAppArgs.any (·.isFVarOf oldIH) then
if let some e' withTransparency .all (unfoldDefinition? e) then
return buildInductionBody toErase toClear goal oldIH newIH isRecCall e'
liftM <| buildInductionCase oldIH newIH isRecCall toErase toClear goal e
/--

View File

@@ -109,7 +109,7 @@ def _root_.Lean.Grind.CommRing.Poly.findSimp? (p : Poly) : RingM (Option EqCnstr
/-- Simplifies `d.p` using `c`, and returns an extended polynomial derivation. -/
def PolyDerivation.simplifyWith (d : PolyDerivation) (c : EqCnstr) : RingM PolyDerivation := do
let some r := d.p.simp? c.p ( nonzeroChar?) | return d
incSteps
incSteps r.p.numTerms
trace_goal[grind.ring.simp] "{← r.p.denoteExpr}"
return .step r.p r.k₁ d r.k₂ r.m₂ c
@@ -137,7 +137,7 @@ def EqCnstr.simplifyWithCore (c₁ c₂ : EqCnstr) : RingM (Option EqCnstr) := d
p := r.p
h := .simp r.k₁ c₁ r.k₂ r.m₂ c₂
}
incSteps
incSteps r.p.numTerms
trace_goal[grind.ring.simp] "{← c.p.denoteExpr}"
return some c

View File

@@ -184,6 +184,15 @@ def Poly.degree : Poly → Nat
| .num _ => 0
| .add _ m _ => m.degree
/-- Returns the number of monomials in the polynomial. -/
def Poly.numTerms (p : Poly) : Nat :=
go p 0
where
go (p : Poly) (acc : Nat) : Nat :=
match p with
| .num .. => acc
| .add _ _ p => go p (acc + 1)
/-- Returns `true` if the leading monomial of `p` divides `m`. -/
def Poly.divides (p : Poly) (m : Mon) : Bool :=
match p with

View File

@@ -13,8 +13,8 @@ namespace Lean.Meta.Grind.Arith.CommRing
def checkMaxSteps : GoalM Bool := do
return ( get').steps >= ( getConfig).ringSteps
def incSteps : GoalM Unit := do
modify' fun s => { s with steps := s.steps + 1 }
def incSteps (n : Nat := 1) : GoalM Unit := do
modify' fun s => { s with steps := s.steps + n }
structure RingM.Context where
ringId : Nat

View File

@@ -276,6 +276,7 @@ private def propagateNonlinearPow (x : Var) : GoalM Bool := do
c'.assert
return true
set_option compiler.ignoreBorrowAnnotation true in
@[export lean_cutsat_propagate_nonlinear]
def propagateNonlinearTermImpl (y : Var) (x : Var) : GoalM Bool := do
unless ( isVarEqConst? y).isSome do return false
@@ -338,6 +339,7 @@ partial def _root_.Int.Linear.Poly.updateOccsForElimEq (p : Poly) (x : Var) : Go
go p
go p
set_option compiler.ignoreBorrowAnnotation true in
@[export lean_grind_cutsat_assert_eq]
def EqCnstr.assertImpl (c : EqCnstr) : GoalM Unit := do
if ( inconsistent) then return ()

View File

@@ -99,6 +99,7 @@ where
return some { p := c.p.addConst 1, h := .ofLeDiseq c c' }
return none
set_option compiler.ignoreBorrowAnnotation true in
@[export lean_grind_cutsat_assert_le]
def LeCnstr.assertImpl (c : LeCnstr) : GoalM Unit := do
if ( inconsistent) then return ()

View File

@@ -325,7 +325,9 @@ private def mkPowEqProof (ka : Int) (ca? : Option EqCnstr) (kb : Nat) (cb? : Opt
let h := mkApp8 (mkConst ``Int.Linear.pow_eq) a b (toExpr ka) (toExpr kbInt) (toExpr k) h₁ h₂ eagerReflBoolTrue
return mkApp6 (mkConst ``Int.Linear.of_var_eq) ( getContext) ( mkVarDecl x) (toExpr k) ( mkPolyDecl c'.p) eagerReflBoolTrue h
set_option compiler.ignoreBorrowAnnotation true in
mutual
@[export lean_cutsat_eq_cnstr_to_proof]
private partial def EqCnstr.toExprProofImpl (c' : EqCnstr) : ProofM Expr := caching c' do
trace[grind.debug.lia.proof] "{← c'.pp}"

View File

@@ -64,6 +64,7 @@ where
registerNonlinearOcc e x
| _ => registerNonlinearOcc e x
set_option compiler.ignoreBorrowAnnotation true in
@[export lean_grind_cutsat_mk_var]
def mkVarImpl (expr : Expr) : GoalM Var := do
if let some var := ( get').varMap.find? { expr } then

View File

@@ -239,6 +239,7 @@ private def normOfNatArgs? (args : Array Expr) : MetaM (Option (Array Expr)) :=
return some args.toArray
return none
set_option compiler.ignoreBorrowAnnotation true in
@[export lean_grind_canon]
partial def canonImpl (e : Expr) : GoalM Expr := do profileitM Exception "grind canon" ( getOptions) do
trace_goal[grind.debug.canon] "{e}"

View File

@@ -348,6 +348,7 @@ where
internalize rhs generation p
addEqCore lhs rhs proof isHEq
set_option compiler.ignoreBorrowAnnotation true in
@[export lean_grind_process_new_facts]
private def processNewFactsImpl : GoalM Unit := do
repeat

View File

@@ -535,6 +535,7 @@ private def internalizeOfNatFinBitVecLiteral (e : Expr) (generation : Nat) (pare
updateIndicesFound (.const ``OfNat.ofNat)
activateTheorems ``OfNat.ofNat generation
set_option compiler.ignoreBorrowAnnotation true in
@[export lean_grind_internalize]
private partial def internalizeImpl (e : Expr) (generation : Nat) (parent? : Option Expr := none) : GoalM Unit := withIncRecDepth do
if ( alreadyInternalized e) then

View File

@@ -300,4 +300,16 @@ def assertAll : Action :=
end Action
/-
Creates an action that tries all solver extensions using `Action.andAlso`,
then drains the `newRawFacts` queue via `assertAll`.
The `assertAll` step is necessary because `processNewFacts` (called by `solverAction` on the
`.propagated` path) drains the `newFacts` queue (equations and propositions for the e-graph),
but the resulting propagation cascade (e.g., congruence closure, or-propagation,
`propagateForallPropDown`) can call `addNewRawFact`, which enqueues to the separate
`newRawFacts` queue. Without this step, these raw facts are never asserted. See issue #12581.
-/
def Solvers.mkAction : IO Action := do
return ( Solvers.mkActionCore) >> Action.assertAll
end Lean.Meta.Grind

View File

@@ -63,32 +63,34 @@ def checkMatchCondParent (e : Expr) (parent : Expr) : GoalM Bool := do
return false
def checkParents (e : Expr) : GoalM Unit := do
-- **Note**: We currently do not check the `funCC` case
unless ( useFunCC e) do
if ( isRoot e) then
for parent in ( getParents e).elems do
if isMatchCond parent then
unless ( checkMatchCondParent e parent) do
-- **Note**: We skip `funCC` parents because the parent-child relationship uses
-- one-level decomposition (`appFn!`/`appArg!`) rather than full `getAppArgs`/`getAppFn`.
if ( isRoot e) then
for parent in ( getParents e).elems do
if parent.isApp && ( useFunCC parent) then
pure ()
else if isMatchCond parent then
unless ( checkMatchCondParent e parent) do
throwError "e: {e}, parent: {parent}"
assert! ( checkMatchCondParent e parent)
else
let mut found := false
-- There is an argument `arg` s.t. root of `arg` is `e`.
for arg in parent.getAppArgs do
if ( checkChild e arg) then
found := true
break
-- Recall that we have support for `Expr.forallE` propagation. See `ForallProp.lean`.
if let .forallE _ d b _ := parent then
if ( checkChild e d) then
found := true
unless b.hasLooseBVars do
if ( checkChild e b) then
found := true
unless found do
unless ( checkChild e parent.getAppFn) do
throwError "e: {e}, parent: {parent}"
assert! ( checkMatchCondParent e parent)
else
let mut found := false
-- There is an argument `arg` s.t. root of `arg` is `e`.
for arg in parent.getAppArgs do
if ( checkChild e arg) then
found := true
break
-- Recall that we have support for `Expr.forallE` propagation. See `ForallProp.lean`.
if let .forallE _ d b _ := parent then
if ( checkChild e d) then
found := true
unless b.hasLooseBVars do
if ( checkChild e b) then
found := true
unless found do
unless ( checkChild e parent.getAppFn) do
throwError "e: {e}, parent: {parent}"
assert! ( checkChild e parent.getAppFn)
assert! ( checkChild e parent.getAppFn)
else
-- All the parents are stored in the root of the equivalence class.
assert! ( getParents e).isEmpty

View File

@@ -328,6 +328,7 @@ mutual
end
set_option compiler.ignoreBorrowAnnotation true in
/--
Returns a proof that `a = b`.
It assumes `a` and `b` are in the same equivalence class.
@@ -338,6 +339,7 @@ def mkEqProofImpl (a b : Expr) : GoalM Expr := do
throwError "internal `grind` error, `mkEqProof` invoked with terms of different types{indentExpr a}\nhas type{indentExpr (← inferType a)}\nbut{indentExpr b}\nhas type{indentExpr (← inferType b)}"
mkEqProofCore a b (heq := false)
set_option compiler.ignoreBorrowAnnotation true in
@[export lean_grind_mk_heq_proof]
def mkHEqProofImpl (a b : Expr) : GoalM Expr :=
mkEqProofCore a b (heq := true)

View File

@@ -42,6 +42,7 @@ def dsimpCore (e : Expr) : GrindM Expr := do profileitM Exception "grind dsimp"
modify fun s => { s with simp }
return r
set_option compiler.ignoreBorrowAnnotation true in
/--
Preprocesses `e` using `grind` normalization theorems and simprocs,
and then applies several other preprocessing steps.

View File

@@ -202,6 +202,7 @@ protected def getSimpContext (config : Grind.Config) : MetaM Simp.Context := do
(simpTheorems := #[thms])
(congrTheorems := ( getSimpCongrTheorems))
set_option compiler.ignoreBorrowAnnotation true in
@[export lean_grind_normalize]
def normalizeImp (e : Expr) (config : Grind.Config) : MetaM Expr := do
let (r, _) Meta.simp e ( Grind.getSimpContext config) ( Grind.getSimprocs)

View File

@@ -575,6 +575,31 @@ where
| .app f a => go f (mixHash r (hashRoot enodes a))
| _ => mixHash r (hashRoot enodes e)
/-!
**Note**: `congrHash` and `isCongruent` must satisfy the `BEq`/`Hashable` invariant for
`PHashSet`: if `isCongruent e₁ e₂` returns `true`, then `congrHash e₁ == congrHash e₂`.
When `funCC = true`, `congrHash` hashes only the immediate function and argument:
`mixHash (hashRoot f) (hashRoot a)`. When `funCC = false`, it recursively decomposes all
application layers. These produce fundamentally different hash values, so `isCongruent` must
require matching `funCC` flags. Here is the scenario that leads to a nondeterministic crash
if mismatched flags are allowed:
1. `e₁` (with `funCC = true`) is inserted into the congruence table.
2. `e₂` (with `funCC = false`) is inserted. Its hash is computed using the non-`funCC` path.
3. Because `hashRoot` uses pointer addresses (`ptrAddrUnsafe`), the two different hash
computations can accidentally collide, placing `e₂` in the same bucket as `e₁`.
4. `isCongruent e₁ e₂` is called. If it used only `e₁`'s `funCC` flag (the old behavior),
the `funCC` comparison path could declare them congruent even when they have different
numbers of top-level arguments.
5. `addCongrTable` calls `pushEqHEq e₂ e₁ congrPlaceholderProof`, where `e₂` is the new
node with `funCC = false`.
6. During proof reconstruction, `mkCongrProof e₂ e₁` checks `useFunCC e₂ = false`, enters
the standard (non-`funCC`) proof path, and hits `assert! rhs.getAppNumArgs == numArgs`
because `e₁` and `e₂` have different argument counts.
This was observed as a nondeterministic crash in Mathlib (e.g., at `Analysis/ODE/PicardLindelof.lean`).
-/
/-- Returns `true` if `e₁` and `e₂` are congruent modulo the equivalence classes in `enodes`. -/
private partial def isCongruent (enodes : ENodeMap) (e₁ e₂ : Expr) : Bool :=
if let .forallE _ d₁ b₁ _ := e₁ then
@@ -600,7 +625,14 @@ private partial def isCongruent (enodes : ENodeMap) (e₁ e₂ : Expr) : Bool :=
**Note**: We are not in `MetaM` here. Thus, we cannot check whether `f` and `g` have the same type.
So, we approximate and try to handle this issue when generating the proof term.
-/
hasSameRoot enodes a b && hasSameRoot enodes f g
useFunCC' enodes e₂ && hasSameRoot enodes a b && hasSameRoot enodes f g
else if useFunCC' enodes e₂ then
/-
Mismatched `funCC` flags: `e₁` uses first-order congruence, `e₂` uses higher-order.
They hash differently (via `congrHash`), so declaring them congruent here would violate
the `BEq`/`Hashable` consistency invariant required by `PHashSet`.
-/
false
else
hasSameRoot enodes a b && go f g
where
@@ -1912,11 +1944,12 @@ Sequential conjunction: executes both `x` and `y`.
def Action.andAlso (x y : Action) : Action := fun goal kna kp => do
x goal (fun goal => y goal kna kp) (fun goal => y goal kp kp)
/-
Creates an action that tries all solver extensions. It uses the `Action.andAlso`
to combine them.
/--
Combines all solver extensions into a single action using `Action.andAlso`.
Does not drain `newRawFacts`; use `Solvers.mkAction` (defined in `Intro.lean`) which
wraps this with `assertAll`.
-/
def Solvers.mkAction : IO Action := do
def Solvers.mkActionCore : IO Action := do
let exts solverExtensionsRef.get
let rec go (i : Nat) (acc : Action) : Action :=
if h : i < exts.size then

View File

@@ -48,10 +48,12 @@ def simpEq? (e : Expr) : MetaM (Option (Expr × Expr)) := do
else match p with
| .add 1 x (.add (-1) y (.num 0)) =>
let r := mkIntEq atoms[x]! atoms[y]!
if r == e then return none
let h := mkApp6 (mkConst ``Int.Linear.norm_eq_var) ( toContextExpr atoms) (toExpr a) (toExpr b) (toExpr x) (toExpr y) eagerReflBoolTrue
return some (r, mkExpectedPropHint h (mkPropEq e r))
| .add 1 x (.num k) =>
let r := mkIntEq atoms[x]! (toExpr (-k))
if r == e then return none
let h := mkApp6 (mkConst ``Int.Linear.norm_eq_var_const) ( toContextExpr atoms) (toExpr a) (toExpr b) (toExpr x) (toExpr (-k)) eagerReflBoolTrue
return some (r, mkExpectedPropHint h (mkPropEq e r))
| _ =>

Some files were not shown because too many files have changed in this diff Show More