Compare commits

..

92 Commits

Author SHA1 Message Date
Paul Reichert
42f7959726 grind annotations, min(?)_singleton, get_min? 2026-01-28 10:36:38 +01:00
Paul Reichert
30cc699f4f trivial improvements addressing remarks except the grind rempark 2026-01-28 09:40:54 +01:00
Paul Reichert
8405a20f41 retrigger mathlib ci 2026-01-28 09:40:54 +01:00
Paul Reichert
e7f08fee8d protect min/max 2026-01-28 09:40:54 +01:00
Paul Reichert
0bf5875041 remove some expensive grind annotations 2026-01-28 09:40:54 +01:00
Paul Reichert
2b21653725 update grind_lint_1 test 2026-01-28 09:40:54 +01:00
Paul Reichert
45755e629b add List authors to author list 2026-01-28 09:40:54 +01:00
Paul Reichert
cba10f46c2 rearrange 2026-01-28 09:40:54 +01:00
Paul Reichert
6625c0770c clean up simps 2026-01-28 09:40:54 +01:00
Paul Reichert
af8f7b2f2c cleanups 2026-01-28 09:40:54 +01:00
Paul Reichert
d4596af90f add min/max lemmas for array 2026-01-28 09:40:54 +01:00
Paul Reichert
ffce1d6e3b array min-max 2026-01-28 09:40:54 +01:00
Kim Morrison
42a0e92453 doc: clarify release notes timing with reference-manual tags (#12171)
This PR documents an issue encountered during the v4.28.0-rc1 release:
if release notes are merged to the reference-manual repository AFTER the
version tag is created, the deployed documentation won't include them.

The fix is to either:
1. Include release notes in the same PR as the toolchain bump (or merge
before tagging)
2. Regenerate the tag after merging release notes

🤖 Prepared with Claude Code

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-27 06:12:59 +00:00
Markus Himmel
d4c74b3566 fix: missing order instances for Int (#12181)
This PR adds two missing order instances for `Int`.

As reported on
[Zulip](https://leanprover.zulipchat.com/#narrow/channel/270676-lean4/topic/No.20Std.2EMaxOrEq.20Int.20instance.2C.20but.20yes.20Std.2EMinOrEq.20Int/near/570198709).
2026-01-27 05:42:30 +00:00
Kim Morrison
2e8afdf74d fix: use gh release create instead of action-gh-release (#12180)
This PR switches the PR release workflow from
`softprops/action-gh-release` to `gh release create`.

The `softprops/action-gh-release` action enumerates all releases to
check for existing ones, which fails when the repository has more than
10000 releases due to GitHub API pagination limits. The
`lean4-pr-releases` repository has accumulated over 10000 releases,
causing the PR release workflow to fail with:

```
Only the first 10000 results are available.
```

This is currently blocking all PR toolchain releases, including
https://github.com/leanprover/lean4/pull/12175.

🤖 Prepared with Claude Code

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-01-27 05:04:43 +00:00
Kim Morrison
c7f941076e fix: scope FamilyOut.fam_eq simp lemma to Lake namespace (#12178)
This PR scopes the `simp` attribute on `FamilyOut.fam_eq` to the `Lake`
namespace. The lemma has a very permissive discrimination tree key
(`_`), so when `Lake.Util.Family` is transitively imported into
downstream projects, it causes `simp` to attempt this lemma on every
goal, leading to timeouts.

See
https://leanprover.zulipchat.com/#narrow/channel/270676-lean4/topic/Lake.20.60FamilyOut.2Efam_eq.60.20leads.20to.20timeouts.3F

🤖 Prepared with Claude Code

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Mac Malone <tydeu@hatpress.net>
2026-01-27 04:24:08 +00:00
Kim Morrison
9185fd2a34 fix: correct comment about instance implicit arguments (#12173)
This PR fixes a comment that said "implicit arguments" when the code
actually checks `isInstImplicit`, which is specifically for instance
implicit arguments (`[...]` binders), not all implicit arguments.

🤖 Prepared with Claude Code

Co-authored-by: Claude <noreply@anthropic.com>
2026-01-27 01:33:55 +00:00
Kim Morrison
642863e8c5 chore: begin development cycle for v4.29.0 (#12169)
This PR bumps the version to 4.29.0 to begin the next development cycle
after v4.28.0-rc1.

🤖 Prepared with Claude Code

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-26 23:31:37 +00:00
Leonardo de Moura
62d2688579 feat: eta-reduction support in SymM (#12168)
This PR adds support for eta-reduction in `SymM`.
2026-01-26 21:30:29 +00:00
Sebastian Graf
e8870da205 chore: improve performance of mpure_intro and mvcgen by avoiding whnfD (#12165)
New measurements:

```
goal_10: 181.910200 ms, kernel: 37.241050 ms
goal_20: 386.540215 ms, kernel: 83.497428 ms
goal_30: 648.282057 ms, kernel: 117.038447 ms
goal_40: 946.733191 ms, kernel: 168.369124 ms
goal_50: 1325.846873 ms, kernel: 223.838786 ms
goal_60: 1734.175705 ms, kernel: 285.594486 ms
goal_70: 2199.522317 ms, kernel: 351.659865 ms
goal_80: 2700.077802 ms, kernel: 428.303337 ms
goal_90: 3260.446641 ms, kernel: 515.976499 ms
goal_100: 3865.503733 ms, kernel: 600.229962 ms
```

Previously, goal_100 took 7.8s.
2026-01-26 17:58:33 +00:00
Lean stage0 autoupdater
a011c9c5dd chore: update stage0 2026-01-26 18:21:01 +00:00
Joachim Breitner
a6a3df8af0 perf: use .inj in proof of .injEq (#12164)
This PR uses the `.inj` theorem in the proof of one direction of the
`.injEq` theorem.
2026-01-26 14:50:32 +00:00
Sebastian Graf
b44c7e161c test: add two benchmarks for mvcgen in the style of SymM (#12163)
This PR adds two benchmarks for mvcgen in the style of Leo's SymM
benchmarks.

While performance on add_sub_cancel_StateM.lean is in the same order of
magnitude as the corresponding MetaM benchmark, add_if_sub_StateM.lean
is far slower.

Measurements for add_sub_cancel:
```
goal_10:   245.576221 ms, kernel: 134.134182 ms
goal_20:   613.945320 ms, kernel: 115.453811 ms
goal_30:  1074.053596 ms, kernel: 179.076070 ms
goal_40:  1680.678302 ms, kernel: 252.066677 ms
goal_50:  2457.209584 ms, kernel: 293.974096 ms
goal_60:  3271.773330 ms, kernel: 368.394386 ms
goal_70:  3981.247921 ms, kernel: 434.297822 ms
goal_80:  5077.300540 ms, kernel: 507.047772 ms
goal_90:  6486.990060 ms, kernel: 556.952095 ms
goal_100: 7791.399986 ms, kernel: 623.605163 ms
```

Measurements for add_if_sub:

```
goal_2: 89.762349 ms, kernel: 43.320205 ms
goal_3: 190.655546 ms, kernel: 38.888499 ms
goal_4: 434.461936 ms, kernel: 75.234581 ms
goal_5: 1110.295284 ms, kernel: 161.698707 ms
goal_6: 3241.383031 ms, kernel: 326.137173 ms
goal_7: 11675.609970 ms, kernel: 684.907188 ms
```

Much room for improvement.
2026-01-26 13:17:47 +00:00
Sebastian Graf
0bcac0d46c feat: add Option.of_wp_eq and Except.of_wp_eq (#12161)
This PR adds `Option.of_wp_eq` and `Except.of_wp_eq`, similar to the
existing `Except.of_wp`. `Except.of_wp` is deprecated because applying
it requires prior generalization, at which point it is more convenient
to use `Except.of_wp_eq`.
2026-01-26 12:50:23 +00:00
Lean stage0 autoupdater
1bf16f710e chore: update stage0 2026-01-26 12:17:07 +00:00
Henrik Böving
c3d753640a feat: use static initializers where possible (#12082)
This PR makes the compiler produce C code that statically initializes
close terms when possible. This change reduces startup time as the terms
are directly stored in the binary instead of getting computed at
startup.

The set of terms currently supported by this mechanism are:
- string literals
- ctors called with other statically initializeable arguments
- `Name.mkStrX` and other `Name` ctors as they require special support
due to their computed field and occur frequently due to name literals.

In core there are currently 152,524 closed terms and of these 103,929
(68%) get initialized statically with this PR. The remaining 48585 ones
are not extracted because they use (potentially transitively) various
non trivial pieces of code like `stringToMessageData` etc. We might
decide to add special support for these in the future but for the moment
this feels like it's overfitting too much for core.
2026-01-26 11:22:12 +00:00
Joachim Breitner
e94ed002b5 perf: in FunInd, boldly do not check terms (#12160)
This PR removes calls to `check` that we expect to pass under normal
circumstances. This may be re-added later guarded by a `debug` option.
2026-01-26 11:22:00 +00:00
Sebastian Graf
7564329f06 fix: make Std.Do's post macro universe polymorphic (#12159)
This PR makes Std.Do's `post` macro universe polymorphic by expanding to
`PUnit.unit` instead of `()`.
2026-01-26 11:20:16 +00:00
Eric Wieser
0336a8385b chore: inline trace nodes (#11954)
This extracts a `postCallback` helper so that only the actual callback
is inlined.

Part of the motivation here is to exclude these tracing frames from
flame graph profiles.

---------

Co-authored-by: Sebastian Ullrich <sebasti@nullri.ch>
2026-01-26 08:51:25 +00:00
David Thrane Christiansen
c6e530a4f1 doc: add link to reference manual in stack overflow message (#12157)
This PR updates #12137 with a link to the Lean reference manual.

---------

Co-authored-by: Sebastian Ullrich <sebasti@nullri.ch>
2026-01-26 07:56:48 +00:00
Kim Morrison
97d427b32b doc: document release notes process and add guard check (#12158)
This PR documents the release notes writing process in detail and adds a
guard check to `release_checklist.py` to ensure release notes are
created for `-rc1` releases before proceeding with downstream repository
updates.

- **doc/dev/release_checklist.md**: Expanded "Writing the release notes"
section with detailed steps for generating, reviewing, and formatting
release notes in Verso format
- **script/release_checklist.py**: Added
`check_release_notes_file_exists()` to verify the release notes file
exists in reference-manual repository
- **.claude/commands/release.md**: Added "Release Notes" section
explaining the process for creating release notes during `-rc1` releases

🤖 Prepared with Claude Code

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-26 07:16:01 +00:00
Kim Morrison
bc1a22cc22 chore: add plausible as verso dependency in release_repos.yml (#12155)
verso depends on plausible, but this wasn't recorded in
release_repos.yml. This caused the release checklist to not properly
track the dependency ordering.
2026-01-26 06:55:45 +00:00
Leonardo de Moura
0e28043ec6 feat: add simpTelescope simproc for simplifying binders before intro (#12154)
This PR adds `simpTelescope`, a simproc that simplifies telescope
binders (`have`-expression values and arrow hypotheses) but not the
final body. This is useful for simplifying targets before introducing
hypotheses.
2026-01-25 23:16:30 +00:00
Leonardo de Moura
45862d5486 feat: improves simpArrowTelescope simproc (#12153)
This PR improves the `simpArrowTelescope` simproc that simplifies
non-dependent arrow telescopes: `p₁ → p₂ → ... → q`.

The simproc now also applies telescope-specific simplifications:
- `False → q` to `True` (when `q : Prop`)
- `True → q` to `q` (when `q : Prop`)
- `p → True` to `True`
2026-01-25 22:29:38 +00:00
Leonardo de Moura
ba8c2ed4ee feat: add simpArrowTelescope for compact proofs of arrow simplification (#12152)
This PR adds `simpArrowTelescope`, a simproc that simplifies telescopes
of non-dependent arrows (p₁ → p₂ → ... → q) while avoiding quadratic
proof growth.

When using `Expr.forallE` to represent nested implications, each nesting
level bumps de Bruijn indices in subterms, destroying sharing even with
hash-consing. For example, a free variable `x` gets different de Bruijn
representations at each depth, causing proof terms to grow.

`simpArrowTelescope` works by:

- Converting arrows to `Arrow p q` (a definitional wrapper)
- Simplifying each component
- Converting back to `→` form

Since `Arrow` arguments are not under binders, subterms remain identical
across nesting levels and can be shared.

The `simp_4` benchmark demonstrates the improvement:

With `forallE`: ~160ms, proof_size ≈ 173k
With `Arrow`: ~43ms, proof_size ≈ 16k
Tradeoff: `simpArrowTelescope` misses simplifications that depend on the
arrow structure (e.g., `p → p` to `True`), since post-methods aren't
applied to intermediate arrows. Thus, it is not used by default. to use
it, one has to set `simpArrowTelescope` as a `pre`-method.
2026-01-25 20:43:59 +00:00
Kim Morrison
9e241a4087 fix: revert "split ngen on async elab" (#12148)
This PR reverts #12000, which introduced a regression where `simp`
incorrectly rejects valid rewrites for perm lemmas.

The issue is that `NameGenerator.mkChild` creates names that don't
maintain the ordering assumption used by `acLt` for perm lemma
decisions. For example, after the change:
- Child generator creates names like `_uniq.102.2`
- Parent continues with `_uniq.7`
- But `Name.lt (.num (.num `_uniq 102) 2) (.num `_uniq 7)` is true

This causes fvars created later (in async tasks) to compare as smaller
than fvars created earlier, breaking the assumption that later fvars
compare greater according to `Name.lt`.

Fixes #12136.

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

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-25 03:18:24 +00:00
Leonardo de Moura
e90f6f77db test: local rewrite with Sym.simp (#12147)
This PR adds a new API for helping users write focused rewrites.
2026-01-25 01:32:50 +00:00
Leonardo de Moura
9deb9ab59d refactor: move commonly shared expressions to SymM (#12145)
This PR moves the pre-shared commonly used expressions from `GrindM` to
`SymM`.
2026-01-25 00:17:53 +00:00
Leonardo de Moura
6de7100f69 feat: add Goal API for SymM + grind (#12143)
This PR adds an API for building symbolic simulation engines and
verification
condition generators that leverage `grind`. The API wraps `Sym`
operations to
work with `grind`'s `Goal` type, enabling lightweight symbolic execution
while
carrying `grind` state for discharge steps.

New operations on `Goal`:
- `mkGoal`: create a `Goal` from an `MVarId`
- `introN`, `intros`: introduce binders
- `apply`: apply backward rules
- `simp`, `simpIgnoringNoProgress`: simplify using `Sym.Simp`
- `internalize`, `internalizeAll`: add hypotheses to the E-graph
- `grind`: attempt to close the goal using `grind`
- `assumption`: close by matching a hypothesis

A new test demonstrates the API on a stateful program with conditionals,
using `grind` to discharge arithmetic side conditions.
2026-01-24 20:30:08 +00:00
Mac Malone
6f409e0eea fix: lake: --no-build exit code w/ release fetch (#12142)
This PR fixes a bug introduced in #12086 where a `lake build :release
--no-build` would exit with code 1 rather than the `--no-build ` code 3.
Now both the bug from #12086 and this bug are fixed.
2026-01-24 17:03:07 +00:00
Sebastian Ullrich
3de1cc54c5 test: compiler test with big meta closure (#12141) 2026-01-24 15:18:33 +00:00
Kim Morrison
a3755fe0a5 fix: add syntax to recommended_spelling for inv (#12139)
This PR adds `«term_⁻¹»` to the `recommended_spelling` for `inv`,
matching
the pattern used by all other operators which include both the function
and the syntax in their spelling lists.

Reported at
https://leanprover.zulipchat.com/#narrow/channel/270676-lean4/topic/.60recommended_spelling.60.20for.20.60.C2.ABterm_.E2.81.BB.C2.B9.C2.BB.60

🤖 Prepared with Claude Code

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-24 11:04:36 +00:00
Leonardo de Moura
4c1e4a77b4 test: MetaM vs SymM on do notation (#12134)
This PR adds a new benchmark `shallow_add_sub_cancel.lean` that
demonstrates symbolic simulation using a shallow embedding into monadic
`do` notation, as opposed to the deep embedding approach in
`add_sub_cancel.lean`.

The shallow embedding approach:
- Uses Lean's `StateM` monad directly instead of a custom command
language

- Defines `Exec s k post` as a simple predicate: `post (k s).1 (k s).2`

- Proves helper theorems for reasoning about monadic operations (`pure`,
`bind`, `get`, `set`, `modify`, `ite`)

- Programs are written in actual `do`-notation rather than a custom AST

The benchmark solves goals using both the `MetaM` and `SymM` frameworks,
showing that the shallow embedding integrates well with the symbolic
simulation infrastructure. `SymM` is again way faster than `MetaM`

### Symbolic simulation benchmark — tactic time only

Problem size `n` corresponds to a program with `4·n` monadic actions.

| n   | MetaM tactic (ms) | SymM tactic (ms) | Speedup |
|-----|-------------------|------------------|---------|
| 10  | 82.10  | 11.37 | ~7.2×  |
| 20  | 176.21 | 17.71 | ~9.9×  |
| 30  | 306.47 | 25.39 | ~12.1× |
| 40  | 509.52 | 34.53 | ~14.7× |
| 50  | 689.19 | 43.51 | ~15.8× |
| 60  | 905.86 | 52.47 | ~17.3× |
| 70  | 1172.31 | 62.50 | ~18.8× |
| 80  | 1448.48 | 70.65 | ~20.5× |
| 90  | 1787.15 | 80.89 | ~22.1× |
| 100 | 2128.12 | 90.77 | ~23.5× |

<img width="580" height="455" alt="image"
src="https://github.com/user-attachments/assets/3511aaab-4d53-4520-8302-65d2d100df4a"
/>
2026-01-24 03:38:02 +00:00
Kim Morrison
896da85304 fix: CI CMakeLists.txt version check extracts wrong value (#12131)
This PR fixes a bug in the CI version validation where `grep -oE
'[0-9]+'` matches
multiple numbers from the comment on the same line:

```
set(LEAN_VERSION_IS_RELEASE 1)  # This number is 1 in the release revision, and 0 otherwise.
```

The grep extracts `1`, `1`, and `0`, causing the comparison to fail.

🤖 Prepared with Claude Code
2026-01-24 00:34:31 +00:00
Kim Morrison
11cd55b4f1 chore: check reference-manual release notes title in release checklist (#12130)
This PR adds a check to the release checklist script that verifies the
reference-manual release notes title matches the release type:

- For RC releases (e.g., v4.27.0-rc1): verifies the title contains the
exact
  RC suffix (e.g., "Lean 4.27.0-rc1")
- For final releases (e.g., v4.27.0): verifies the title does NOT
contain
  any "-rc" suffix (e.g., "Lean 4.27.0")

The check looks at the PR branch (bump_to_vX.Y.Z) for the release notes
file
at `Manual/Releases/v4_X_Y.lean` and parses the `#doc (Manual) "Lean
..."` line.

🤖 Prepared with Claude Code

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-24 00:33:53 +00:00
Kim Morrison
88823b27a6 doc: document nightly infrastructure in release command (#12129)
This PR adds documentation about the nightly build infrastructure to the
`/release` command to help future release managers understand the
relationship between branches and tags:

- `nightly` and `nightly-with-mathlib` are **branches** in
`leanprover/lean4`
- Dated tags like `nightly-YYYY-MM-DD` are **tags** in
`leanprover/lean4-nightly`
- When a nightly succeeds with mathlib, all three should point to the
same commit

🤖 Prepared with Claude Code

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-24 00:06:44 +00:00
Henrik Böving
c9facc8102 fix: move allocation of execvp args before fork (#12123)
This PR fixes an issue that may sporadically trigger ASAN to got into a
deadlock when running a subprocess through the `IO.Process.spawn`
framework.

The general issue here is that we run `fork()` and then perform an
allocation in the child before going to `execvp` (for allocating the
arguments to `execvp`). As it turns out, doing this can cause a race
condition in ASAN that ultimately causes a deadlock in the child. This
was fixed upstream but then rolled back (see
https://github.com/google/sanitizers/issues/774). Thus, we must avoid
allocating any memory in between `fork` and `execvp`.
2026-01-23 23:12:23 +00:00
Lean stage0 autoupdater
63d1b530ba chore: update stage0 2026-01-23 18:35:59 +00:00
Sebastian Ullrich
3f09741fb9 chore: revert "fix: do not compile with -fwrapv" (#12125)
We are seeing (non-deterministically?) some ubsan reports from implicit
casting to `int`:
https://github.com/leanprover/lean4/actions/runs/21290374536/job/61282105493?pr=12082

Reverts leanprover/lean4#12098
2026-01-23 17:39:52 +00:00
Sebastian Ullrich
9f9531fa13 fix: getParentDeclName? inside where inside public def (#12119)
This PR fixes the call hierarchy for `where` declarations under the
module system

---------

Co-authored-by: mhuisi <mhuisi@protonmail.com>
2026-01-23 17:32:05 +00:00
David Thrane Christiansen
dae0d6fa05 fix: context for info trees and warning hints in Verso docstrings (#12121)
This PR wraps info trees produced by the `lean` Verso docstring
codeblock in a context info node.

Closes #12065.
2026-01-23 16:22:09 +00:00
David Thrane Christiansen
4a3401f69a fix: enable Verso docstrings in where-blocks (#12122)
This PR adds support for Verso docstrings in `where` clauses.

Closes #12066.
2026-01-23 14:02:11 +00:00
Paul Reichert
4526cdda5f fix: fix verso's +warning hint (#12116)
This PR fixes the verso hint that appears when using `sorry` in an
example block. It previously said: `` The `+error` flag indicates that
warnings are expected: +warning `` This PR replaces `error` with
`warning`. Fixes #12064
2026-01-23 13:31:02 +00:00
Eric Wieser
c4639150c1 fix: do not compile with -fwrapv (#12098)
This PR removes the requirement that libraries compiled against the lean
headers must use `-fwrapv`.

clang
[documents](https://clang.llvm.org/docs/UndefinedBehaviorSanitizer.html#:~:text=Note%20that%20checks%20are%20still%20added%20even%20when%20%2Dfwrapv%20is%20enabled)
that `-fwrapv` does not automatically turn off the integer overflow
sanitizer; and so overflow should still be avoided in normal execution.
2026-01-23 12:14:17 +00:00
Kim Morrison
37870c168b chore: more updates to grind_indexmap test case (#12120) 2026-01-23 10:38:05 +00:00
Kim Morrison
57003e5c79 chore: updates to grind_indexmap test case (#12115)
This PR contains further updates to the `IndexMap` test case in
preparation for a demo at Lean Together.
2026-01-23 06:19:51 +00:00
Kim Morrison
b2f485e352 chore: grind test file demonstrating interactive use (#12114) 2026-01-23 04:55:09 +00:00
Mac Malone
5e29d7660a feat: lake: +mod target keys for modules in deps (#12112)
This PR revives the ability to specify modules in dependencies via the
basic `+mod` target key.

Implementation-wise, this removes deprecation of `BuildKey.module` and
once again uses it for `+mod` target keys. It also adds a test for
depending on a module of a dependency via `needs`.
2026-01-23 02:35:02 +00:00
Kim Morrison
567cf74f1b feat: updates to grind_indexmap test case (#12111)
This PR updates the `grind_indexmap.lean` tests, in preparation for
using them in a Lean Together talk.
2026-01-22 23:54:19 +00:00
Mac Malone
fa2ddf1c56 chore: lake: disable import all check in module disambiguation (#12104)
This PR disables an overlooked check (in #12045) of `import all` during
module disambiguation.
2026-01-22 18:12:14 +00:00
Mac Malone
f9af240bc4 fix: lake: query :deps output (#12105)
This PR fixes the `lake query` output for targets which produce an
`Array` or `List` of a value with a custom `QueryText` or `QueryJson`
instance (e.g., `deps` and `transDeps`).
2026-01-22 17:52:43 +00:00
Joachim Breitner
3bfeb0bc1f refactor: use isRecursiveDefinition when validating macro_inline (#12106)
This PR uses `isRecursiveDefinition` when validating `macro_inline`,
instead of rummaging in the internals of the definition.
2026-01-22 16:31:34 +00:00
Garmelon
8447586fea chore: make bench suite more similar to mathlib's (#12091)
The most important change is that all bench scripts now must always
output to `measurements.jsonl` instead of being allowed to output
results on stdout/err.
2026-01-22 14:20:10 +00:00
Lean stage0 autoupdater
470e3b7fd0 chore: update stage0 2026-01-22 12:59:28 +00:00
Paul Reichert
0a0323734b feat: suggest Int*.toNatClamp for Int*.toNat (#11979)
This PR adds `suggest_for` annotations such that `Int*.toNatClamp` is
suggested for `Int*.toNat`.
2026-01-22 08:51:51 +00:00
Markus Himmel
69b058dc82 feat: Fin and Char ranges (#12058)
This PR implements iteration over ranges for `Fin` and `Char`.

To this end, we introduce machinery for pulling back lawfulness of
`UpwardEnumerable` along an injective map and study the function
`Char.ordinal : Char -> Fin Char.numCodePoints`.
2026-01-22 07:44:55 +00:00
David Thrane Christiansen
2c48ae7dfb chore: make Verso module docstring API more like that for Markdown (#12093)
This PR makes the Verso module docstring API more like the Markdown
module docstring API, enabling downstream consumers to use them the same
way.
2026-01-22 04:45:49 +00:00
Leonardo de Moura
c81a8897a9 feat: improve Sym.simp APIs and new benchmark data (#12101)
This PR improves the the `Sym.simp` APIs. It is now easier to reuse the
simplifier cache between different simplification steps. We use the APIs
to improve the benchmark at #12100.

### Symbolic simulation with simplifier cache reuse (SymM)

Problem size `n` corresponds to a program with `2·n + 2` instructions.

| n   | Tactic time (ms) | Kernel time (ms) |
|-----|------------------|------------------|
| 10  | 4.53  | 4.29  |
| 20  | 5.56  | 6.91  |
| 30  | 6.46  | 8.67  |
| 40  | 8.07  | 11.20 |
| 50  | 9.37  | 13.63 |
| 60  | 11.89 | 15.43 |
| 70  | 12.43 | 18.28 |
| 80  | 14.07 | 20.72 |
| 90  | 15.62 | 23.41 |
| 100 | 17.39 | 24.80 |
| 200 | 30.35 | 48.39 |
| 300 | 45.41 | 72.84 |
| 400 | 59.17 | 97.67 |
| 500 | 79.63 | 138.99 |
| 600 | 100.05 | 173.67 |
| 700 | 119.77 | 208.80 |

<img width="571" height="455" alt="image"
src="https://github.com/user-attachments/assets/70da7ea2-b5d2-405e-985c-bfa358455afc"
/>
2026-01-22 03:37:16 +00:00
Mac Malone
3bc63aefb7 fix: lake: small cache issues (#12037)
This PR fixes two Lake cache issues: a bug where a failed upload would
not produce an error and a mistake in the `--wfail` checks of the cache
commands.
2026-01-22 03:27:30 +00:00
Leonardo de Moura
fa40491c78 test: benchmark MetaM vs SymM (#12100)
This PR adds a comparison between `MetaM` and `SymM` for a benchmark was
proposed during the Lean@Google Hackathon.

### Benchmark description

In this benchmark, we define the semantics of a very simple imperative
language using an inductive predicate

```
Exec prog events mem lctx post
```

The predicate holds if, when executing the program `prog` with an
initial list of events `events`, memory `mem`, and local context `lctx`,
the postcondition `post` holds.

We then consider the following program:

```
input b
a := b
a := a + a
a := a - b
...
a := a + a
a := a - b
```

That is, after reading an input value `b`, the program repeatedly
updates the variable `a` by doubling it and then subtracting `b`.

We prove that, for any initial memory `m` and local context `l`, and
starting from the empty list of events, the following postcondition
holds:

```
fun t' m' l' =>
  m' = m ∧                      -- memory did not change
  ∃ v : Word,
    t' = [IOEvent.IN v] ∧       -- exactly one input event
    l'.get "a" = some v         -- `a` contains the input value
```

In other words, executing the program produces exactly one input event,
leaves the memory unchanged, and ensures that the final value of `a` is
equal to the input value.

### Symbolic simulation benchmark (problem size `n`, with `2·n + 2`
instructions)

| Problem size (n) | MetaM time (ms) | MetaM kernel (ms) | SymM time
(ms) | SymM kernel (ms) | Total speedup |

|------------------|------------------|-------------------|----------------|------------------|---------------|
| 10  | 94.83  | 6.60  | 7.04  | 6.18  | ~13.5× |
| 20  | 218.92 | 13.33 | 14.15 | 13.02 | ~15.5× |
| 30  | 375.10 | 22.95 | 26.51 | 19.81 | ~14.2× |
| 40  | 563.82 | 34.99 | 40.42 | 29.55 | ~14.0× |
| 50  | 815.89 | 53.78 | 60.84 | 42.25 | ~13.4× |
| 60  | 1081.09 | 73.46 | 80.99 | 53.52 | ~13.3× | 
| 70  | 1400.80 | 102.70 | 106.02 | 68.61 | ~13.2× | 
| 80  | 1772.19 | 126.65 | 134.23 | 87.64 | ~13.2× |
| 90  | 2203.41 | 161.68 | 168.26 | 115.52 | ~13.1× | 
| 100 | 2474.09 | 191.23 | 209.13 | 143.86 | ~11.8× |

<img width="580" height="455" alt="image"
src="https://github.com/user-attachments/assets/bc7058fa-e71a-4c2c-be28-860f39166965"
/>

 ### Symbolic simulation with extra simplification (SymM)

Problem size `n` corresponds to a program with `2·n + 2` instructions.

| n   | Total time (ms) | Kernel time (ms) | Non-kernel time (ms) |
|-----|------------------|------------------|----------------------|
| 10  | 6.33  | 3.97 | 2.36 |
| 20  | 10.30 | 5.59 | 4.71 |
| 30  | 13.72 | 7.38 | 6.34 |
| 40  | 17.85 | 8.84 | 9.01 |
| 50  | 21.90 | 10.63 | 11.27 |
| 60  | 27.00 | 12.56 | 14.44 |
| 70  | 32.02 | 14.04 | 17.98 |
| 80  | 37.25 | 15.76 | 21.49 |
| 90  | 42.55 | 17.95 | 24.60 |
| 100 | 49.30 | 20.03 | 29.27 |
| 200 | 125.56 | 38.21 | 87.36 |
| 300 | 293.58 | 66.79 | 226.79 |
| 400 | 361.87 | 78.96 | 282.91 |
| 500 | 518.51 | 102.51 | 416.00 |
| 600 | 716.63 | 122.81 | 593.82 |
2026-01-22 01:38:56 +00:00
Leonardo de Moura
af438425d5 perf: avoid mkAppM in Sym.simp (#12099)
This PR ensures `Sym.simpGoal` does not use `mkAppM`. It also increases
the default number of maximum steps in `Sym.simp`.
2026-01-22 00:01:43 +00:00
Mac Malone
648e1b1877 fix: lake: --no-build failure w/ optional release fetch (#12086)
This PR fixes a bug where a `lake build --no-build` would exit with code
`3` if the optional job to fetch a GitHub or Reservoir release for a
package failed (even if nothing else needed rebuilding).
2026-01-21 23:14:54 +00:00
Leonardo de Moura
f84aa23d6d feat: metavar cleanup in Sym.simp (#12096)
This PR cleanups temporary metavariables generated when applying
rewriting rules in `Sym.simp`.
2026-01-21 21:36:17 +00:00
Rob23oba
6bec8adf16 fix: symbol name for native boxed declarations in the interpreter (#12095)
This PR fixes the procedure for finding the mangled symbol name of boxed
variants of native functions. Previously, the wrong symbol name has been
used for names ending in `_`: For example `test_` mangles to `l_test__`
but `test_._boxed` mangles to `l_test___00__boxed`, not
`l_test_____boxed` which the compiler would previously wrongly use.
This probably didn't affect anybody though since the failure condition
is pretty rare: the name of a native function that the interpreter tries
to execute would've had to end in `_`.
2026-01-21 20:38:29 +00:00
Sebastian Ullrich
16873fb123 chore: modulize: work around unknown initial command (#12080) 2026-01-21 20:25:13 +00:00
Leonardo de Moura
34d8eeb3be chore: fix and rename sym_add_sub_cancel benchmark (#12092) 2026-01-21 17:47:40 +00:00
Sebastian Graf
f1cc85eb19 chore: move test from tests/run to tests/lean/run (#12087) 2026-01-21 17:16:09 +00:00
Leonardo de Moura
08e6f714ca chore: normalize Sym APIs (#12088)
This PR cleanups the Sym APIs for `apply` and `simp`.
2026-01-21 17:02:22 +00:00
Leonardo de Moura
b8f8dde0b3 feat: checkMaxShared (#12083)
This PR adds the debugging helper functions `Expr.checkMaxShared` and
`MVarId.checkMaxShared` to `Sym`, and fixes a bug when visiting
telescopes in `Sym.simp`.
2026-01-21 14:55:46 +00:00
Lean stage0 autoupdater
b09e33f76b chore: update stage0 2026-01-21 15:30:16 +00:00
Sebastian Ullrich
a95227c7d7 perf: make Environment.getModuleIdx? constant-time (#12068)
This array can now be 7000+ items long and `getModuleIdxFor?` has always
been constant-time, possibly creating confusion
2026-01-21 14:38:28 +00:00
Leonardo de Moura
8258cfe2a1 fix: preprocessLCtx (#12081)
This PR fixes a bug in the `Sym.preprocessLCtx` function.
2026-01-21 14:05:43 +00:00
Sebastian Ullrich
94e8fd4845 chore: update script/Modulize.lean (#12079) 2026-01-21 13:22:39 +00:00
Leonardo de Moura
9063adbd51 feat: String and Char simprocs for SymM (#12077)
This PR implements simprocs for `String` and `Char`. It also ensures
reducible definitions are unfolded in `SymM`
2026-01-21 00:05:40 +00:00
Mac Malone
3e16f5332f feat: lake: .nobuild trace file for debugging (#12076)
This PR adds additional debugging information to a run of `lake build
--no-build` via a `.nobuild` trace file. When a build fails due to
needing a rebuild, Lake emits the new expected trace next as `.nobuild`
file next to the build's old `.trace`. The inputs recorded in these
files can then be compared to debug what caused the mismatch.

To help keep the build directory clean, the `.nobuild` trace file is
removed on the next successful build.
2026-01-20 22:22:40 +00:00
David Thrane Christiansen
974fdd85c4 chore: enable let rec tactic completion and docs (#12072)
This PR enables tactic completion and docs for the `let rec` tactic,
which required a stage0 update after #12047.
2026-01-20 13:17:08 +00:00
Sebastian Ullrich
e8a16dfcc8 perf: speed up lake shake (#12069)
Speeds up run time on mathlib4 by ~6x (in combination with #12068)
2026-01-20 12:19:55 +00:00
Joachim Breitner
ad43266357 test: add a big dependent struct test (#12061)
This PR adds a test for a big dependent structure, exhibiting some bad
performance in `injEq` generation.
2026-01-20 12:00:25 +00:00
Lean stage0 autoupdater
9efb2bf35c chore: update stage0 2026-01-20 12:05:41 +00:00
David Thrane Christiansen
9fbbe6554d fix: make first token detection work in modules (#12047)
This PR makes the automatic first token detection in tactic docs much
more robust, in addition to making it work in modules and other contexts
where builtin tactics are not in the environment. It also adds the
ability to override the tactic's first token as the user-visible name.

Previously, first token detection would look up the parser descriptor in
the environment and process its syntax. This would be incorrect for
builtin parsers, as well as for modules in which the definition is not
loaded. Now, it instead consults the Pratt parsing table for the
`tactic` syntax category. Tests are added that ensure this keeps working
in modules, and also that the first token of all tactics that ship with
Lean are either detected unambiguously or annotated to remove ambiguity.

Closes #12038.
2026-01-20 11:12:05 +00:00
Marc Huisinga
db30cf3954 fix: set data? field in all unknown identifier code actions (#12046)
This PR fixes a bug where the unknown identifier code actions were
broken in NeoVim due to the language server not properly setting the
`data?` field for all code action items that it yields.
2026-01-20 10:03:29 +00:00
Leonardo de Moura
e9a1c9ef63 feat: offset terms in Sym (#12053)
This PR adds support for offset terms in `SymM`. This is essential for
handling equational theorems for functions that pattern match on natural
numbers in `Sym.simp`. Without this, it cannot handle simple examples
such as

```lean
def pw (n : Nat) : Nat :=
  match n with
  | 0 => 1
  | n+1 => 2 * pw n

example : pw 4 = 16 := by
  sym_simp [pw.eq_1, pw.eq_2]

example : pw (a + 2) = 2 * (2 * pw a) := by
  sym_simp [pw.eq_2]
```
2026-01-20 04:57:52 +00:00
1995 changed files with 8462 additions and 2054 deletions

View File

@@ -13,12 +13,54 @@ These comments explain the scripts' behavior, which repositories get special han
## Arguments
- `version`: The version to release (e.g., v4.24.0)
## Release Notes (Required for -rc1 releases)
For first release candidates (`-rc1`), you must create release notes BEFORE the reference-manual toolchain bump PR can be merged.
**Steps to create release notes:**
1. Generate the release notes:
```bash
cd /path/to/lean4
python3 script/release_notes.py --since <previous_version> > /tmp/release-notes-<version>.md
```
Replace `<previous_version>` with the last stable release (e.g., `v4.27.0` when releasing `v4.28.0-rc1`).
2. Review `/tmp/release-notes-<version>.md` for common issues:
- **Unterminated code blocks**: Look for code fences that aren't closed. Fetch original PR with `gh pr view <number>` to repair.
- **Truncated descriptions**: Some may end mid-sentence. Complete them from the original PR.
- **Markdown issues**: Other syntax problems that could cause parsing errors.
3. Create the release notes file in the reference-manual repository:
- File path: `Manual/Releases/v<version>.lean` (e.g., `v4_28_0.lean`)
- Use Verso format with proper imports and `#doc (Manual)` block
- **Use `#` for headers, not `##`** (Verso uses level 1 for subsections)
- **Use plain ` ``` ` not ` ```lean `** (the latter executes code)
- **Wrap underscore identifiers in backticks**: `` `bv_decide` `` not `bv_decide`
4. Update `Manual/Releases.lean`:
- Add import: `import Manual.Releases.«v4_28_0»`
- Add include: `{include 0 Manual.Releases.«v4_28_0»}`
5. Build to verify: `lake build Manual.Releases.v4_28_0`
6. Create a **separate PR** for release notes (not bundled with toolchain bump):
```bash
git checkout -b v<version>-release-notes
gh pr create --title "doc: add v<version> release notes"
```
For subsequent RCs (`-rc2`, etc.) and stable releases, just update the version number in the existing release notes file title.
See `doc/dev/release_checklist.md` section "Writing the release notes" for full details.
## Process
1. Run `script/release_checklist.py {version}` to check the current status
2. **CRITICAL: If preliminary lean4 checks fail, STOP immediately and alert the user**
- Check for: release branch exists, CMake version correct, tag exists, release page exists, release notes exist
- Check for: release branch exists, CMake version correct, tag exists, release page exists, release notes file exists
- **IMPORTANT**: The release page is created AUTOMATICALLY by CI after pushing the tag - DO NOT create it manually
- **IMPORTANT**: For -rc1 releases, release notes must be created before proceeding
- Do NOT create any PRs or proceed with repository updates if these checks fail
3. Create a todo list tracking all repositories that need updates
4. **CRITICAL RULE: You can ONLY run `release_steps.py` for a repository if `release_checklist.py` explicitly says to do so**
@@ -61,6 +103,15 @@ Every time you run `release_checklist.py`, you MUST:
This summary should be provided EVERY time you run the checklist, not just after creating new PRs.
The user needs to see the complete picture of what's waiting for review.
## Nightly Infrastructure
The nightly build system uses branches and tags across two repositories:
- `leanprover/lean4` has **branches** `nightly` and `nightly-with-mathlib` tracking the latest nightly builds
- `leanprover/lean4-nightly` has **dated tags** like `nightly-2026-01-23`
When a nightly succeeds with mathlib, all three should point to the same commit. Don't confuse these: branches are in the main lean4 repo, dated tags are in lean4-nightly.
## Error Handling
**CRITICAL**: If something goes wrong or a command fails:

View File

@@ -115,7 +115,7 @@ jobs:
CMAKE_MAJOR=$(grep -E "^set\(LEAN_VERSION_MAJOR " src/CMakeLists.txt | grep -oE '[0-9]+')
CMAKE_MINOR=$(grep -E "^set\(LEAN_VERSION_MINOR " src/CMakeLists.txt | grep -oE '[0-9]+')
CMAKE_PATCH=$(grep -E "^set\(LEAN_VERSION_PATCH " src/CMakeLists.txt | grep -oE '[0-9]+')
CMAKE_IS_RELEASE=$(grep -m 1 -E "^set\(LEAN_VERSION_IS_RELEASE " src/CMakeLists.txt | grep -oE '[0-9]+')
CMAKE_IS_RELEASE=$(grep -m 1 -E "^set\(LEAN_VERSION_IS_RELEASE " src/CMakeLists.txt | sed -nE 's/^set\(LEAN_VERSION_IS_RELEASE ([0-9]+)\).*/\1/p')
# Expected values from tag parsing
TAG_MAJOR="${{ steps.set-release.outputs.LEAN_VERSION_MAJOR }}"

View File

@@ -62,42 +62,56 @@ jobs:
git -C lean4.git remote add pr-releases https://foo:'${{ secrets.PR_RELEASES_TOKEN }}'@github.com/${{ github.repository_owner }}/lean4-pr-releases.git
git -C lean4.git push -f pr-releases pr-release-${{ steps.workflow-info.outputs.pullRequestNumber }}
git -C lean4.git push -f pr-releases pr-release-${{ steps.workflow-info.outputs.pullRequestNumber }}-"${SHORT_SHA}"
- name: Delete existing release if present
- name: Delete existing releases if present
if: ${{ steps.workflow-info.outputs.pullRequestNumber != '' }}
run: |
# Try to delete any existing release for the current PR (just the version without the SHA suffix).
# Delete any existing releases for this PR.
# The short format release is always recreated with the latest commit.
# The SHA-suffixed release should be unique per commit, but delete just in case.
gh release delete --repo ${{ github.repository_owner }}/lean4-pr-releases pr-release-${{ steps.workflow-info.outputs.pullRequestNumber }} -y || true
gh release delete --repo ${{ github.repository_owner }}/lean4-pr-releases pr-release-${{ steps.workflow-info.outputs.pullRequestNumber }}-${{ env.SHORT_SHA }} -y || true
env:
GH_TOKEN: ${{ secrets.PR_RELEASES_TOKEN }}
# Verify artifacts were downloaded (equivalent to fail_on_unmatched_files in the old action).
- name: Verify release artifacts exist
if: ${{ steps.workflow-info.outputs.pullRequestNumber != '' }}
run: |
shopt -s nullglob
files=(artifacts/*/*)
if [ ${#files[@]} -eq 0 ]; then
echo "::error::No artifacts found matching artifacts/*/*"
exit 1
fi
echo "Found ${#files[@]} artifacts to upload:"
printf '%s\n' "${files[@]}"
# We use `gh release create` instead of `softprops/action-gh-release` because
# the latter enumerates all releases to check for existing ones, which fails
# when the repository has more than 10000 releases (GitHub API pagination limit).
# Upstream fix: https://github.com/softprops/action-gh-release/pull/725
- name: Release (short format)
if: ${{ steps.workflow-info.outputs.pullRequestNumber != '' }}
uses: softprops/action-gh-release@a06a81a03ee405af7f2048a818ed3f03bbf83c7b
with:
name: Release for PR ${{ steps.workflow-info.outputs.pullRequestNumber }}
# There are coredumps files here as well, but all in deeper subdirectories.
files: artifacts/*/*
fail_on_unmatched_files: true
draft: false
tag_name: pr-release-${{ steps.workflow-info.outputs.pullRequestNumber }}
repository: ${{ github.repository_owner }}/lean4-pr-releases
run: |
# There are coredump files in deeper subdirectories; artifacts/*/* gets the release archives.
gh release create \
--repo ${{ github.repository_owner }}/lean4-pr-releases \
--title "Release for PR ${{ steps.workflow-info.outputs.pullRequestNumber }}" \
--notes "" \
pr-release-${{ steps.workflow-info.outputs.pullRequestNumber }} \
artifacts/*/*
env:
# The token used here must have `workflow` privileges.
GITHUB_TOKEN: ${{ secrets.PR_RELEASES_TOKEN }}
GH_TOKEN: ${{ secrets.PR_RELEASES_TOKEN }}
- name: Release (SHA-suffixed format)
if: ${{ steps.workflow-info.outputs.pullRequestNumber != '' }}
uses: softprops/action-gh-release@a06a81a03ee405af7f2048a818ed3f03bbf83c7b
with:
name: Release for PR ${{ steps.workflow-info.outputs.pullRequestNumber }} (${{ steps.workflow-info.outputs.sourceHeadSha }})
# There are coredumps files here as well, but all in deeper subdirectories.
files: artifacts/*/*
fail_on_unmatched_files: true
draft: false
tag_name: pr-release-${{ steps.workflow-info.outputs.pullRequestNumber }}-${{ env.SHORT_SHA }}
repository: ${{ github.repository_owner }}/lean4-pr-releases
run: |
gh release create \
--repo ${{ github.repository_owner }}/lean4-pr-releases \
--title "Release for PR ${{ steps.workflow-info.outputs.pullRequestNumber }} (${{ steps.workflow-info.outputs.sourceHeadSha }})" \
--notes "" \
pr-release-${{ steps.workflow-info.outputs.pullRequestNumber }}-${{ env.SHORT_SHA }} \
artifacts/*/*
env:
# The token used here must have `workflow` privileges.
GITHUB_TOKEN: ${{ secrets.PR_RELEASES_TOKEN }}
GH_TOKEN: ${{ secrets.PR_RELEASES_TOKEN }}
- name: Report release status (short format)
if: ${{ steps.workflow-info.outputs.pullRequestNumber != '' }}

View File

@@ -218,6 +218,11 @@ Please read https://leanprover-community.github.io/contribute/tags_and_branches.
# Writing the release notes
Release notes are only needed for the first release candidate (`-rc1`). For subsequent RCs and stable releases,
just update the version number in the title of the existing release notes file.
## Generating the release notes
Release notes are automatically generated from the commit history, using `script/release_notes.py`.
Run this as `script/release_notes.py --since v4.6.0`, where `v4.6.0` is the *previous* release version.
@@ -232,4 +237,113 @@ Some judgement is required here: ignore commits which look minor,
but manually add items to the release notes for significant PRs that were rebase-merged.
There can also be pre-written entries in `./releases_drafts`, which should be all incorporated in the release notes and then deleted from the branch.
## Reviewing and fixing the generated markdown
Before adding the release notes to the reference manual, carefully review the generated markdown for these common issues:
1. **Unterminated code blocks**: PR descriptions sometimes have unclosed code fences. Look for code blocks
that don't have a closing ` ``` `. If found, fetch the original PR description with `gh pr view <number>`
and repair the code block with the complete content.
2. **Truncated descriptions**: Some PR descriptions may end abruptly mid-sentence. Review these and complete
the descriptions based on the original PR.
3. **Markdown syntax issues**: Check for other markdown problems that could cause parsing errors.
## Creating the release notes file
The release notes go in `Manual/Releases/v4_7_0.lean` in the reference-manual repository.
The file structure must follow the Verso format:
```lean
/-
Copyright (c) 2025 Lean FRO LLC. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Author: <Your Name>
-/
import VersoManual
import Manual.Meta
import Manual.Meta.Markdown
open Manual
open Verso.Genre
open Verso.Genre.Manual
open Verso.Genre.Manual.InlineLean
#doc (Manual) "Lean 4.7.0-rc1 (YYYY-MM-DD)" =>
%%%
tag := "release-v4.7.0"
file := "v4.7.0"
%%%
<release notes content here>
```
**Important formatting rules for Verso:**
- Use `#` for section headers inside the document, not `##` (Verso uses header level 1 for subsections)
- Use plain ` ``` ` for code blocks, not ` ```lean ` (the latter will cause Lean to execute the code)
- Identifiers with underscores like `bv_decide` should be wrapped in backticks: `` `bv_decide` ``
(otherwise the underscore may be interpreted as markdown emphasis)
## Updating Manual/Releases.lean
After creating the release notes file, update `Manual/Releases.lean` to include it:
1. Add the import near the top with other version imports:
```lean
import Manual.Releases.«v4_7_0»
```
2. Add the include statement after the other includes:
```lean
{include 0 Manual.Releases.«v4_7_0»}
```
## Building and verifying
Build the release notes to check for errors:
```bash
lake build Manual.Releases.v4_7_0
```
Common errors and fixes:
- "Wrong header nesting - got ## but expected at most #": Change `##` to `#`
- "Tactic 'X' failed" or similar: Code is being executed; change ` ```lean ` to ` ``` `
- "'_'" errors: Underscore in identifier being parsed as emphasis; wrap in backticks
## Creating the PR
**Important: Timing with the reference-manual tag**
The reference-manual repository deploys documentation when a version tag is pushed. If you merge
release notes AFTER the tag is created, the deployed documentation won't include them.
You have two options:
1. **Preferred**: Include the release notes in the same PR as the toolchain bump (or merge the
release notes PR before creating the tag). This ensures the tag includes the release notes.
2. **If release notes are merged after the tag**: You must regenerate the tag to trigger a new deployment:
```bash
cd /path/to/reference-manual
git fetch origin
git tag -d v4.7.0-rc1 # Delete local tag
git tag v4.7.0-rc1 origin/main # Create tag at current main (which has release notes)
git push origin :refs/tags/v4.7.0-rc1 # Delete remote tag
git push origin v4.7.0-rc1 # Push new tag (triggers Deploy workflow)
```
If creating a separate PR for release notes:
```bash
git checkout -b v4.7.0-release-notes
git add Manual/Releases/v4_7_0.lean Manual/Releases.lean
git commit -m "doc: add v4.7.0 release notes"
git push -u origin v4.7.0-release-notes
gh pr create --title "doc: add v4.7.0 release notes" --body "This PR adds the release notes for Lean v4.7.0."
```
See `./releases_drafts/README.md` for more information about pre-written release note entries.
See `./releases_drafts/README.md` for more information.

View File

@@ -29,7 +29,7 @@ def main (args : List String) : IO Unit := do
if !msgs.toList.isEmpty then -- skip this file if there are parse errors
msgs.forM fun msg => msg.toString >>= IO.println
throw <| .userError "parse errors in file"
let `(header| $[module%$moduleTk?]? $imps:import*) := header
let `(header| $[module%$moduleTk?]? $[prelude%$preludeTk?]? $imps:import*) := header
| throw <| .userError s!"unexpected header syntax of {path}"
if moduleTk?.isSome then
continue
@@ -38,11 +38,11 @@ def main (args : List String) : IO Unit := do
let startPos := header.raw.getPos? |>.getD parserState.pos
let dummyEnv mkEmptyEnvironment
let (initCmd, parserState', _) :=
let (initCmd, parserState', msgs') :=
Parser.parseCommand inputCtx { env := dummyEnv, options := {} } parserState msgs
-- insert section if any trailing command
if !initCmd.isOfKind ``Parser.Command.eoi then
-- insert section if any trailing command (or error, which could be from an unknown command)
if !initCmd.isOfKind ``Parser.Command.eoi || msgs'.hasErrors then
let insertPos? :=
-- put below initial module docstring if any
guard (initCmd.isOfKind ``Parser.Command.moduleDoc) *> initCmd.getTailPos? <|>
@@ -57,19 +57,21 @@ def main (args : List String) : IO Unit := do
sec := "\n\n" ++ sec
if insertPos?.isNone then
sec := sec ++ "\n\n"
text := text.extract 0 insertPos ++ sec ++ text.extract insertPos text.rawEndPos
let insertPos := text.pos! insertPos
text := text.extract text.startPos insertPos ++ sec ++ text.extract insertPos text.endPos
-- prepend each import with `public `
for imp in imps.reverse do
let insertPos := imp.raw.getPos?.get!
let prfx := if doMeta then "public meta " else "public "
text := text.extract 0 insertPos ++ prfx ++ text.extract insertPos text.rawEndPos
let insertPos := text.pos! insertPos
text := text.extract text.startPos insertPos ++ prfx ++ text.extract insertPos text.endPos
-- insert `module` header
let mut initText := text.extract 0 startPos
if !initText.trim.isEmpty then
let mut initText := text.extract text.startPos (text.pos! startPos)
if !initText.trimAscii.isEmpty then
-- If there is a header comment, preserve it and put `module` in the line after
initText := initText.trimRight ++ "\n"
text := initText ++ "module\n\n" ++ text.extract startPos text.rawEndPos
initText := initText.trimAsciiEnd.toString ++ "\n"
text := initText ++ "module\n\n" ++ text.extract (text.pos! startPos) text.endPos
IO.FS.writeFile path text

View File

@@ -185,6 +185,30 @@ def get_release_notes(tag_name):
except Exception:
return None
def check_release_notes_file_exists(toolchain, github_token):
"""Check if the release notes file exists in the reference-manual repository.
For -rc1 releases, this checks that the release notes have been created.
For subsequent RCs and stable releases, release notes should already exist.
Returns tuple (exists: bool, is_rc1: bool) where is_rc1 indicates if this is
the first release candidate (when release notes need to be written).
"""
# Determine the release notes file path
# e.g., v4.28.0-rc1 -> Manual/Releases/v4_28_0.lean
base_version = strip_rc_suffix(toolchain.lstrip('v')) # "4.28.0"
file_name = f"v{base_version.replace('.', '_')}.lean" # "v4_28_0.lean"
file_path = f"Manual/Releases/{file_name}"
is_rc1 = toolchain.endswith("-rc1")
repo_url = "https://github.com/leanprover/reference-manual"
# Check if the file exists on main branch
content = get_branch_content(repo_url, "main", file_path, github_token)
return (content is not None, is_rc1)
def get_branch_content(repo_url, branch, file_path, github_token):
api_url = repo_url.replace("https://github.com/", "https://api.github.com/repos/") + f"/contents/{file_path}?ref={branch}"
headers = {'Authorization': f'token {github_token}'} if github_token else {}
@@ -501,6 +525,76 @@ def check_proofwidgets4_release(repo_url, target_toolchain, github_token):
print(f" You will need to create and push a tag v0.0.{next_version}")
return False
def check_reference_manual_release_title(repo_url, toolchain, pr_branch, github_token):
"""Check if the reference-manual release notes title matches the release type.
For RC releases (e.g., v4.27.0-rc1), the title should contain the exact RC suffix.
For final releases (e.g., v4.27.0), the title should NOT contain any "-rc".
Returns True if check passes or is not applicable, False if title needs updating.
"""
is_rc = is_release_candidate(toolchain)
# For RC releases, get the base version and RC suffix
# e.g., "v4.27.0-rc1" -> version="4.27.0", rc_suffix="-rc1"
if is_rc:
parts = toolchain.lstrip('v').split('-', 1)
version = parts[0]
rc_suffix = '-' + parts[1] if len(parts) > 1 else ''
else:
version = toolchain.lstrip('v')
rc_suffix = ''
# Construct the release notes file path (e.g., Manual/Releases/v4_27_0.lean for v4.27.0)
file_name = f"v{version.replace('.', '_')}.lean" # "v4_27_0.lean"
file_path = f"Manual/Releases/{file_name}"
# Try to get the file from the PR branch first, then fall back to main branch
content = get_branch_content(repo_url, pr_branch, file_path, github_token)
if content is None:
# Try the default branch
content = get_branch_content(repo_url, "main", file_path, github_token)
if content is None:
print(f" ⚠️ Could not check release notes file: {file_path}")
return True # Don't block on this
# Look for the #doc line with the title
for line in content.splitlines():
if line.strip().startswith('#doc') and 'Manual' in line:
has_rc_in_title = '-rc' in line.lower()
if is_rc:
# For RC releases, title should contain the exact RC suffix (e.g., "-rc1")
# Use regex to match exact suffix followed by non-digit (to avoid -rc1 matching -rc10)
# Pattern matches the RC suffix followed by a non-digit or end-of-string context
# e.g., "-rc1" followed by space, quote, paren, or similar
exact_match = re.search(rf'{re.escape(rc_suffix)}(?![0-9])', line, re.IGNORECASE)
if exact_match:
print(f" ✅ Release notes title correctly shows {rc_suffix}")
return True
elif has_rc_in_title:
print(f" ❌ Release notes title shows wrong RC version (expected {rc_suffix})")
print(f" Update {file_path} to use '{rc_suffix}' in the title")
return False
else:
print(f" ❌ Release notes title missing RC suffix")
print(f" Update {file_path} to include '{rc_suffix}' in the title")
return False
else:
# For final releases, title should NOT contain -rc
if has_rc_in_title:
print(f" ❌ Release notes title still shows RC version")
print(f" Update {file_path} to remove '-rcN' from the title")
return False
else:
print(f" ✅ Release notes title is updated for final release")
return True
# If we didn't find the #doc line, don't block
print(f" ⚠️ Could not find release notes title in {file_path}")
return True
def run_mathlib_verify_version_tags(toolchain, verbose=False):
"""Run mathlib4's verify_version_tags.py script to validate the release tag.
@@ -644,6 +738,27 @@ def main():
else:
print(f" ✅ Release notes page title looks good ('{actual_title}').")
# Check if release notes file exists in reference-manual repository
# For -rc1 releases, this is when release notes need to be written
# For subsequent RCs and stable releases, they should already exist
release_notes_exists, is_rc1 = check_release_notes_file_exists(toolchain, github_token)
base_version = strip_rc_suffix(toolchain.lstrip('v'))
release_notes_file = f"Manual/Releases/v{base_version.replace('.', '_')}.lean"
if not release_notes_exists:
if is_rc1:
print(f" ❌ Release notes file not found: {release_notes_file}")
print(f" This is an -rc1 release, so release notes need to be written.")
print(f" Run `script/release_notes.py --since <previous_version>` to generate them.")
print(f" See doc/dev/release_checklist.md section 'Writing the release notes' for details.")
lean4_success = False
else:
print(f" ❌ Release notes file not found: {release_notes_file}")
print(f" Release notes should have been created for -rc1. Check the reference-manual repository.")
lean4_success = False
else:
print(f" ✅ Release notes file exists: {release_notes_file}")
repo_status["lean4"] = lean4_success
# If the release page doesn't exist, skip repository checks and master branch checks
@@ -709,6 +824,11 @@ def main():
print(f" ⚠️ CI: {ci_message}")
else:
print(f" ❓ CI: {ci_message}")
# For reference-manual, check that the release notes title has been updated
if name == "reference-manual":
pr_branch = f"bump_to_{toolchain}"
check_reference_manual_release_title(url, toolchain, pr_branch, github_token)
else:
print(f" ❌ PR with title '{pr_title}' does not exist")
print(f" Run `script/release_steps.py {toolchain} {name}` to create it")

View File

@@ -14,13 +14,6 @@ repositories:
bump-branch: true
dependencies: []
- name: verso
url: https://github.com/leanprover/verso
toolchain-tag: true
stable-branch: false
branch: main
dependencies: []
- name: lean4checker
url: https://github.com/leanprover/lean4checker
toolchain-tag: true
@@ -42,6 +35,14 @@ repositories:
branch: main
dependencies: []
- name: verso
url: https://github.com/leanprover/verso
toolchain-tag: true
stable-branch: false
branch: main
dependencies:
- plausible
- name: import-graph
url: https://github.com/leanprover-community/import-graph
toolchain-tag: true

View File

@@ -10,7 +10,7 @@ endif()
include(ExternalProject)
project(LEAN CXX C)
set(LEAN_VERSION_MAJOR 4)
set(LEAN_VERSION_MINOR 28)
set(LEAN_VERSION_MINOR 29)
set(LEAN_VERSION_PATCH 0)
set(LEAN_VERSION_IS_RELEASE 0) # This number is 1 in the release revision, and 0 otherwise.
set(LEAN_SPECIAL_VERSION_DESC "" CACHE STRING "Additional version description like 'nightly-2018-03-11'")

View File

@@ -30,3 +30,4 @@ public import Init.Data.Array.Erase
public import Init.Data.Array.Zip
public import Init.Data.Array.InsertIdx
public import Init.Data.Array.Extract
public import Init.Data.Array.MinMax

View File

@@ -3065,6 +3065,18 @@ theorem foldl_eq_foldlM {f : β → α → β} {b} {xs : Array α} {start stop :
theorem foldr_eq_foldrM {f : α β β} {b} {xs : Array α} {start stop : Nat} :
xs.foldr f b start stop = (xs.foldrM (m := Id) (pure <| f · ·) b start stop).run := rfl
public theorem foldl_eq_foldl_extract {xs : Array α} {f : β α β} {init : β} :
xs.foldl (init := init) (start := start) (stop := stop) f =
(xs.extract start stop).foldl (init := init) f := by
simp only [foldl_eq_foldlM]
rw [foldlM_start_stop]
public theorem foldr_eq_foldr_extract {xs : Array α} {f : α β β} {init : β} :
xs.foldr (init := init) (start := start) (stop := stop) f =
(xs.extract stop start).foldr (init := init) f := by
simp only [foldr_eq_foldrM]
rw [foldrM_start_stop]
@[simp] theorem id_run_foldlM {f : β α Id β} {b} {xs : Array α} {start stop : Nat} :
Id.run (xs.foldlM f b start stop) = xs.foldl (f · · |>.run) b start stop := rfl

View File

@@ -0,0 +1,401 @@
/-
Copyright (c) 2026 Lean FRO, LLC. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Paul Reichert
-/
module
prelude
public import Init.Data.Array.Bootstrap
public import Init.Data.Array.Lemmas
public import Init.Data.Array.DecidableEq
import Init.Data.List.MinMax
import Init.Data.List.ToArray
namespace Array
/-! ## Minima and maxima -/
/-! ### min -/
/--
Returns the smallest element of a non-empty array.
Examples:
* `#[4].min (by decide) = 4`
* `#[1, 4, 2, 10, 6].min (by decide) = 1`
-/
public protected def min [Min α] (arr : Array α) (h : arr #[]) : α :=
haveI : arr.size > 0 := by simp [Array.size_pos_iff, h]
arr.foldl min arr[0] (start := 1)
/-! ### min? -/
/--
Returns the smallest element of the array if it is not empty, or `none` if it is empty.
Examples:
* `#[].min? = none`
* `#[4].min? = some 4`
* `#[1, 4, 2, 10, 6].min? = some 1`
-/
public protected def min? [Min α] (arr : Array α) : Option α :=
if h : arr #[] then
some (arr.min h)
else
none
/-! ### max -/
/--
Returns the largest element of a non-empty array.
Examples:
* `#[4].max (by decide) = 4`
* `#[1, 4, 2, 10, 6].max (by decide) = 10`
-/
public protected def max [Max α] (arr : Array α) (h : arr #[]) : α :=
haveI : arr.size > 0 := by simp [Array.size_pos_iff, h]
arr.foldl max arr[0] (start := 1)
/-! ### max? -/
/--
Returns the largest element of the array if it is not empty, or `none` if it is empty.
Examples:
* `#[].max? = none`
* `#[4].max? = some 4`
* `#[1, 4, 2, 10, 6].max? = some 10`
-/
public protected def max? [Max α] (arr : Array α) : Option α :=
if h : arr #[] then
some (arr.max h)
else
none
/-! ### Compatibility with `List` -/
@[simp, grind =]
public theorem _root_.List.min_toArray [Min α] {l : List α} {h} :
l.toArray.min h = l.min (by simpa [List.ne_nil_iff_length_pos] using h) := by
let h' : l [] := by simpa [List.ne_nil_iff_length_pos] using h
change l.toArray.min h = l.min h'
rw [Array.min]
· induction l
· contradiction
· rename_i x xs
simp only [List.getElem_toArray, List.getElem_cons_zero, List.size_toArray, List.length_cons]
rw [List.toArray_cons, foldl_eq_foldl_extract]
rw [ Array.foldl_toList, Array.toList_extract, List.extract_eq_drop_take]
simp [List.min]
public theorem _root_.List.min_eq_min_toArray [Min α] {l : List α} {h} :
l.min h = l.toArray.min (by simpa [List.ne_nil_iff_length_pos] using h) := by
simp
@[simp, grind =]
public theorem min_toList [Min α] {xs : Array α} {h} :
xs.toList.min h = xs.min (by simpa [List.ne_nil_iff_length_pos] using h) := by
cases xs; simp
public theorem min_eq_min_toList [Min α] {xs : Array α} {h} :
xs.min h = xs.toList.min (by simpa [List.ne_nil_iff_length_pos] using h) := by
simp
@[simp, grind =]
public theorem _root_.List.min?_toArray [Min α] {l : List α} :
l.toArray.min? = l.min? := by
rw [Array.min?]
split
· simp [List.min_toArray, List.min_eq_get_min?, - List.get_min?]
· simp_all
@[simp, grind =]
public theorem min?_toList [Min α] {xs : Array α} :
xs.toList.min? = xs.min? := by
cases xs; simp
@[simp, grind =]
public theorem _root_.List.max_toArray [Max α] {l : List α} {h} :
l.toArray.max h = l.max (by simpa [List.ne_nil_iff_length_pos] using h) := by
let h' : l [] := by simpa [List.ne_nil_iff_length_pos] using h
change l.toArray.max h = l.max h'
rw [Array.max]
· induction l
· contradiction
· rename_i x xs
simp only [List.getElem_toArray, List.getElem_cons_zero, List.size_toArray, List.length_cons]
rw [List.toArray_cons, foldl_eq_foldl_extract]
rw [ Array.foldl_toList, Array.toList_extract, List.extract_eq_drop_take]
simp [List.max]
public theorem _root_.List.max_eq_max_toArray [Max α] {l : List α} {h} :
l.max h = l.toArray.max (by simpa [List.ne_nil_iff_length_pos] using h) := by
simp
@[simp, grind =]
public theorem max_toList [Max α] {xs : Array α} {h} :
xs.toList.max h = xs.max (by simpa [List.ne_nil_iff_length_pos] using h) := by
cases xs; simp
public theorem max_eq_max_toList [Max α] {xs : Array α} {h} :
xs.max h = xs.toList.max (by simpa [List.ne_nil_iff_length_pos] using h) := by
simp
@[simp, grind =]
public theorem _root_.List.max?_toArray [Max α] {l : List α} :
l.toArray.max? = l.max? := by
rw [Array.max?]
split
· simp [List.max_toArray, List.max_eq_get_max?, - List.get_max?]
· simp_all
@[simp, grind =]
public theorem max?_toList [Max α] {xs : Array α} :
xs.toList.max? = xs.max? := by
cases xs; simp
/-! ### Lemmas about `min?` -/
@[simp, grind =]
public theorem min?_empty [Min α] : (#[] : Array α).min? = none :=
(rfl)
@[simp, grind =]
public theorem min?_singleton [Min α] {x : α} : #[x].min? = some x :=
(rfl)
-- We don't put `@[simp]` on `min?_singleton_append'`,
-- because the definition in terms of `foldl` is not useful for proofs.
public theorem min?_singleton_append' [Min α] {xs : Array α} :
(#[x] ++ xs).min? = some (xs.foldl min x) := by
simp [ min?_toList, toList_append, List.min?]
@[simp]
public theorem min?_singleton_append [Min α] [Std.Associative (min : α α α)] {xs : Array α} :
(#[x] ++ xs).min? = some (xs.min?.elim x (min x)) := by
simp [ min?_toList, toList_append, List.min?_cons]
@[simp, grind =]
public theorem min?_eq_none_iff {xs : Array α} [Min α] : xs.min? = none xs = #[] := by
rcases xs with l
simp
@[simp, grind =]
public theorem isSome_min?_iff {xs : Array α} [Min α] : xs.min?.isSome xs #[] := by
rcases xs with l
simp
@[grind .]
public theorem isSome_min?_of_mem {xs : Array α} [Min α] {a : α} (h : a xs) :
xs.min?.isSome := by
rw [ min?_toList]
apply List.isSome_min?_of_mem (a := a)
simpa
public theorem isSome_min?_of_ne_empty [Min α] (xs : Array α) (h : xs #[]) : xs.min?.isSome := by
rw [ min?_toList]
apply List.isSome_min?_of_ne_nil
simpa
public theorem min?_mem [Min α] [Std.MinEqOr α] (xs : Array α) (h : xs.min? = some a) : a xs := by
rw [ min?_toList] at h
simpa using List.min?_mem h
public theorem le_min?_iff [Min α] [LE α] [Std.LawfulOrderInf α] :
{xs : Array α} xs.min? = some a {x}, x a b, b xs x b := by
intro xs h x
simp only [ min?_toList] at h
simpa using List.le_min?_iff h
public theorem min?_eq_some_iff [Min α] [LE α] {xs : Array α} [Std.IsLinearOrder α]
[Std.LawfulOrderMin α] : xs.min? = some a a xs b, b xs a b := by
rcases xs with l
simpa using List.min?_eq_some_iff
public theorem min?_replicate [Min α] [Std.IdempotentOp (min : α α α)] {n : Nat} {a : α} :
(replicate n a).min? = if n = 0 then none else some a := by
rw [ List.toArray_replicate, List.min?_toArray, List.min?_replicate]
@[simp, grind =]
public theorem min?_replicate_of_pos [Min α] [Std.MinEqOr α] {n : Nat} {a : α} (h : 0 < n) :
(replicate n a).min? = some a := by
simp [min?_replicate, Nat.ne_of_gt h]
public theorem foldl_min [Min α] [Std.IdempotentOp (min : α α α)]
[Std.Associative (min : α α α)] {xs : Array α} {a : α} :
xs.foldl (init := a) min = min a (xs.min?.getD a) := by
rcases xs with l
simp [List.foldl_min]
/-! ### Lemmas about `max?` -/
@[simp, grind =]
public theorem max?_empty [Max α] : (#[] : Array α).max? = none :=
(rfl)
@[simp, grind =]
public theorem max?_singleton [Max α] {x : α} : #[x].max? = some x :=
(rfl)
-- We don't put `@[simp]` on `max?_singleton_append'`,
-- because the definition in terms of `foldl` is not useful for proofs.
public theorem max?_singleton_append' [Max α] {xs : Array α} : (#[x] ++ xs).max? = some (xs.foldl max x) := by
simp [ max?_toList, toList_append, List.max?]
@[simp]
public theorem max?_singleton_append [Max α] [Std.Associative (max : α α α)] {xs : Array α} :
(#[x] ++ xs).max? = some (xs.max?.elim x (max x)) := by
simp [ max?_toList, toList_append, List.max?_cons]
@[simp, grind =]
public theorem max?_eq_none_iff {xs : Array α} [Max α] : xs.max? = none xs = #[] := by
rcases xs with l
simp
@[simp, grind =]
public theorem isSome_max?_iff {xs : Array α} [Max α] : xs.max?.isSome xs #[] := by
rcases xs with l
simp
@[grind .]
public theorem isSome_max?_of_mem {xs : Array α} [Max α] {a : α} (h : a xs) :
xs.max?.isSome := by
rw [ max?_toList]
apply List.isSome_max?_of_mem (a := a)
simpa
public theorem isSome_max?_of_ne_empty [Max α] (xs : Array α) (h : xs #[]) : xs.max?.isSome := by
rw [ max?_toList]
apply List.isSome_max?_of_ne_nil
simpa
public theorem max?_mem [Max α] [Std.MaxEqOr α] (xs : Array α) (h : xs.max? = some a) : a xs := by
rw [ max?_toList] at h
simpa using List.max?_mem h
public theorem max?_le_iff [Max α] [LE α] [Std.LawfulOrderSup α] :
{xs : Array α} xs.max? = some a {x}, a x b, b xs b x := by
intro xs h x
simp only [ max?_toList] at h
simpa using List.max?_le_iff h
public theorem max?_eq_some_iff [Max α] [LE α] {xs : Array α} [Std.IsLinearOrder α]
[Std.LawfulOrderMax α] : xs.max? = some a a xs b, b xs b a := by
rcases xs with l
simpa using List.max?_eq_some_iff
public theorem max?_replicate [Max α] [Std.IdempotentOp (max : α α α)] {n : Nat} {a : α} :
(replicate n a).max? = if n = 0 then none else some a := by
rw [ List.toArray_replicate, List.max?_toArray, List.max?_replicate]
@[simp, grind =]
public theorem max?_replicate_of_pos [Max α] [Std.MaxEqOr α] {n : Nat} {a : α} (h : 0 < n) :
(replicate n a).max? = some a := by
simp [max?_replicate, Nat.ne_of_gt h]
public theorem foldl_max [Max α] [Std.IdempotentOp (max : α α α)] [Std.Associative (max : α α α)]
{xs : Array α} {a : α} : xs.foldl (init := a) max = max a (xs.max?.getD a) := by
rcases xs with l
simp [List.foldl_max]
/-! ### Lemmas about `min` -/
@[simp, grind =]
theorem min_singleton [Min α] {x : α} :
#[x].min (ne_empty_of_size_eq_add_one rfl) = x := by
(rfl)
public theorem min?_eq_some_min [Min α] : {xs : Array α} (h : xs #[])
xs.min? = some (xs.min h)
| a::as, _ => by simp [Array.min, Array.min?]
public theorem min_eq_get_min? [Min α] : (xs : Array α) (h : xs #[])
xs.min h = xs.min?.get (xs.isSome_min?_of_ne_empty h)
| a::as, _ => by simp [Array.min, Array.min?]
@[simp, grind =]
public theorem get_min? [Min α] {xs : Array α} {h : xs.min?.isSome} :
xs.min?.get h = xs.min (isSome_min?_iff.mp h) := by
simp [min?_eq_some_min (isSome_min?_iff.mp h)]
@[grind .]
public theorem min_mem [Min α] [Std.MinEqOr α] {xs : Array α} (h : xs #[]) : xs.min h xs :=
xs.min?_mem (min?_eq_some_min h)
@[grind .]
public theorem min_le_of_mem [Min α] [LE α] [Std.IsLinearOrder α] [Std.LawfulOrderMin α]
{xs : Array α} {a : α} (ha : a xs) :
xs.min (ne_empty_of_mem ha) a :=
(Array.min?_eq_some_iff.mp (min?_eq_some_min (ne_empty_of_mem ha))).right a ha
public protected theorem le_min_iff [Min α] [LE α] [Std.LawfulOrderInf α]
{xs : Array α} (h : xs #[]) : {x}, x xs.min h b, b xs x b :=
le_min?_iff (min?_eq_some_min h)
public theorem min_eq_iff [Min α] [LE α] {xs : Array α} [Std.IsLinearOrder α] [Std.LawfulOrderMin α]
(h : xs #[]) : xs.min h = a a xs b, b xs a b := by
simpa [min?_eq_some_min h] using (min?_eq_some_iff (xs := xs))
@[simp, grind =]
public theorem min_replicate [Min α] [Std.MinEqOr α] {n : Nat} {a : α} (h : (replicate n a) #[]) :
(replicate n a).min h = a := by
have n_pos : 0 < n := by simpa [Nat.ne_zero_iff_zero_lt] using h
simpa [min?_eq_some_min h] using (min?_replicate_of_pos (a := a) n_pos)
public theorem foldl_min_eq_min [Min α] [Std.IdempotentOp (min : α α α)]
[Std.Associative (min : α α α)] {xs : Array α} (h : xs #[]) {a : α} :
xs.foldl min a = min a (xs.min h) := by
simpa [min?_eq_some_min h] using foldl_min (xs := xs)
/-! ### Lemmas about `max` -/
@[simp, grind =]
theorem max_singleton [Max α] {x : α} :
#[x].max (ne_empty_of_size_eq_add_one rfl) = x := by
(rfl)
public theorem max?_eq_some_max [Max α] : {xs : Array α} (h : xs #[])
xs.max? = some (xs.max h)
| a::as, _ => by simp [Array.max, Array.max?]
public theorem max_eq_get_max? [Max α] : (xs : Array α) (h : xs #[])
xs.max h = xs.max?.get (xs.isSome_max?_of_ne_empty h)
| a::as, _ => by simp [Array.max, Array.max?]
@[simp, grind =]
public theorem get_max? [Max α] {xs : Array α} {h : xs.max?.isSome} :
xs.max?.get h = xs.max (isSome_max?_iff.mp h) := by
simp [max?_eq_some_max (isSome_max?_iff.mp h)]
@[grind .]
public theorem max_mem [Max α] [Std.MaxEqOr α] {xs : Array α} (h : xs #[]) : xs.max h xs :=
xs.max?_mem (max?_eq_some_max h)
public protected theorem max_le_iff [Max α] [LE α] [Std.LawfulOrderSup α]
{xs : Array α} (h : xs #[]) : {x}, xs.max h x b, b xs b x :=
max?_le_iff (max?_eq_some_max h)
public theorem max_eq_iff [Max α] [LE α] {xs : Array α} [Std.IsLinearOrder α] [Std.LawfulOrderMax α]
(h : xs #[]) : xs.max h = a a xs b, b xs b a := by
simpa [max?_eq_some_max h] using (max?_eq_some_iff (xs := xs))
@[grind .]
public theorem le_max_of_mem [Max α] [LE α] [Std.IsLinearOrder α] [Std.LawfulOrderMax α]
{xs : Array α} {a : α} (ha : a xs) :
a xs.max (ne_empty_of_mem ha) :=
(Array.max?_eq_some_iff.mp (max?_eq_some_max (ne_empty_of_mem ha))).right a ha
@[simp, grind =]
public theorem max_replicate [Max α] [Std.MaxEqOr α] {n : Nat} {a : α} (h : (replicate n a) #[]) :
(replicate n a).max h = a := by
have n_pos : 0 < n := by simpa [Nat.ne_zero_iff_zero_lt] using h
simpa [max?_eq_some_max h] using (max?_replicate_of_pos (a := a) n_pos)
public theorem foldl_max_eq_max [Max α] [Std.IdempotentOp (max : α α α)]
[Std.Associative (max : α α α)] {xs : Array α} (h : xs #[]) {a : α} :
xs.foldl max a = max a (xs.max h) := by
simpa [max?_eq_some_max h] using foldl_max (xs := xs)
end Array

View File

@@ -9,3 +9,4 @@ prelude
public import Init.Data.Char.Basic
public import Init.Data.Char.Lemmas
public import Init.Data.Char.Order
public import Init.Data.Char.Ordinal

View File

@@ -0,0 +1,242 @@
/-
Copyright (c) 2026 Lean FRO, LLC. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Author: Markus Himmel
-/
module
prelude
public import Init.Data.Fin.OverflowAware
public import Init.Data.UInt.Basic
public import Init.Data.Function
import Init.Data.Char.Lemmas
import Init.Data.Char.Order
import Init.Grind
/-!
# Bijection between `Char` and `Fin Char.numCodePoints`
In this file, we construct a bijection between `Char` and `Fin Char.numCodePoints` and show that
it is compatible with various operations. Since `Fin` is simpler than `Char` due to being based
on natural numbers instead of `UInt32` and not having a hole in the middle (surrogate code points),
this is sometimes useful to simplify reasoning about `Char`.
We use these declarations in the construction of `Char` ranges, see the module
`Init.Data.Range.Polymorphic.Char`.
-/
set_option doc.verso true
public section
namespace Char
/-- The number of surrogate code points. -/
abbrev numSurrogates : Nat :=
-- 0xe000 - 0xd800
2048
/-- The size of the {name}`Char` type. -/
abbrev numCodePoints : Nat :=
-- 0x110000 - numSurrogates
1112064
/--
Packs {name}`Char` bijectively into {lean}`Fin Char.numCodePoints` by shifting code points which are
greater than the surrogate code points by the number of surrogate code points.
The inverse of this function is called {name (scope := "Init.Data.Char.Ordinal")}`Char.ofOrdinal`.
-/
def ordinal (c : Char) : Fin Char.numCodePoints :=
if h : c.val < 0xd800 then
c.val.toNat, by grind [UInt32.lt_iff_toNat_lt]
else
c.val.toNat - Char.numSurrogates, by grind [UInt32.lt_iff_toNat_lt]
/--
Unpacks {lean}`Fin Char.numCodePoints` bijectively to {name}`Char` by shifting code points which are
greater than the surrogate code points by the number of surrogate code points.
The inverse of this function is called {name}`Char.ordinal`.
-/
def ofOrdinal (f : Fin Char.numCodePoints) : Char :=
if h : (f : Nat) < 0xd800 then
UInt32.ofNatLT f (by grind), by grind [UInt32.toNat_ofNatLT]
else
UInt32.ofNatLT (f + Char.numSurrogates) (by grind), by grind [UInt32.toNat_ofNatLT]
/--
Computes the next {name}`Char`, skipping over surrogate code points (which are not valid
{name}`Char`s) as necessary.
This function is specified by its interaction with {name}`Char.ordinal`, see
{name (scope := "Init.Data.Char.Ordinal")}`Char.succ?_eq`.
-/
def succ? (c : Char) : Option Char :=
if h₀ : c.val < 0xd7ff then
some c.val + 1, by grind [UInt32.lt_iff_toNat_lt, UInt32.toNat_add]
else if h₁ : c.val = 0xd7ff then
some 0xe000, by decide
else if h₂ : c.val < 0x10ffff then
some c.val + 1, by
simp only [UInt32.lt_iff_toNat_lt, UInt32.reduceToNat, Nat.not_lt, UInt32.toNat_inj,
UInt32.isValidChar, Nat.isValidChar, UInt32.toNat_add, Nat.reducePow] at *
grind
else none
/--
Computes the {name}`m`-th next {name}`Char`, skipping over surrogate code points (which are not
valid {name}`Char`s) as necessary.
This function is specified by its interaction with {name}`Char.ordinal`, see
{name (scope := "Init.Data.Char.Ordinal")}`Char.succMany?_eq`.
-/
def succMany? (m : Nat) (c : Char) : Option Char :=
c.ordinal.addNat? m |>.map Char.ofOrdinal
@[grind =]
theorem coe_ordinal {c : Char} :
(c.ordinal : Nat) =
if c.val < 0xd800 then
c.val.toNat
else
c.val.toNat - Char.numSurrogates := by
grind [Char.ordinal]
@[simp]
theorem ordinal_zero : '\x00'.ordinal = 0 := by
ext
simp [coe_ordinal]
@[grind =]
theorem val_ofOrdinal {f : Fin Char.numCodePoints} :
(Char.ofOrdinal f).val =
if h : (f : Nat) < 0xd800 then
UInt32.ofNatLT f (by grind)
else
UInt32.ofNatLT (f + Char.numSurrogates) (by grind) := by
grind [Char.ofOrdinal]
@[simp]
theorem ofOrdinal_ordinal {c : Char} : Char.ofOrdinal c.ordinal = c := by
ext
simp only [val_ofOrdinal, coe_ordinal, UInt32.ofNatLT_add]
split
· grind [UInt32.lt_iff_toNat_lt, UInt32.ofNatLT_toNat]
· rw [dif_neg]
· simp only [ UInt32.toNat_inj, UInt32.toNat_add, UInt32.toNat_ofNatLT, Nat.reducePow]
grind [UInt32.toNat_lt, UInt32.lt_iff_toNat_lt]
· grind [UInt32.lt_iff_toNat_lt]
@[simp]
theorem ordinal_ofOrdinal {f : Fin Char.numCodePoints} : (Char.ofOrdinal f).ordinal = f := by
ext
simp [coe_ordinal, val_ofOrdinal]
split
· rw [if_pos, UInt32.toNat_ofNatLT]
simpa [UInt32.lt_iff_toNat_lt]
· rw [if_neg, UInt32.toNat_add, UInt32.toNat_ofNatLT, UInt32.toNat_ofNatLT, Nat.mod_eq_of_lt,
Nat.add_sub_cancel]
· grind
· simp only [UInt32.lt_iff_toNat_lt, UInt32.toNat_add, UInt32.toNat_ofNatLT, Nat.reducePow,
UInt32.reduceToNat, Nat.not_lt]
grind
@[simp]
theorem ordinal_comp_ofOrdinal : Char.ordinal Char.ofOrdinal = id := by
ext; simp
@[simp]
theorem ofOrdinal_comp_ordinal : Char.ofOrdinal Char.ordinal = id := by
ext; simp
@[simp]
theorem ordinal_inj {c d : Char} : c.ordinal = d.ordinal c = d :=
fun h => by simpa using congrArg Char.ofOrdinal h, (· rfl)
theorem ordinal_injective : Function.Injective Char.ordinal :=
fun _ _ => ordinal_inj.1
@[simp]
theorem ofOrdinal_inj {f g : Fin Char.numCodePoints} :
Char.ofOrdinal f = Char.ofOrdinal g f = g :=
fun h => by simpa using congrArg Char.ordinal h, (· rfl)
theorem ofOrdinal_injective : Function.Injective Char.ofOrdinal :=
fun _ _ => ofOrdinal_inj.1
theorem ordinal_le_of_le {c d : Char} (h : c d) : c.ordinal d.ordinal := by
simp only [le_def, UInt32.le_iff_toNat_le] at h
simp only [Fin.le_def, coe_ordinal, UInt32.lt_iff_toNat_lt, UInt32.reduceToNat]
grind
theorem ofOrdinal_le_of_le {f g : Fin Char.numCodePoints} (h : f g) :
Char.ofOrdinal f Char.ofOrdinal g := by
simp only [Fin.le_def] at h
simp only [le_def, val_ofOrdinal, UInt32.ofNatLT_add, UInt32.le_iff_toNat_le]
split
· simp only [UInt32.toNat_ofNatLT]
split
· simpa
· simp only [UInt32.toNat_add, UInt32.toNat_ofNatLT, Nat.reducePow]
grind
· simp only [UInt32.toNat_add, UInt32.toNat_ofNatLT, Nat.reducePow]
rw [dif_neg (by grind)]
simp only [UInt32.toNat_add, UInt32.toNat_ofNatLT, Nat.reducePow]
grind
theorem le_iff_ordinal_le {c d : Char} : c d c.ordinal d.ordinal :=
ordinal_le_of_le, fun h => by simpa using ofOrdinal_le_of_le h
theorem le_iff_ofOrdinal_le {f g : Fin Char.numCodePoints} :
f g Char.ofOrdinal f Char.ofOrdinal g :=
ofOrdinal_le_of_le, fun h => by simpa using ordinal_le_of_le h
theorem lt_iff_ordinal_lt {c d : Char} : c < d c.ordinal < d.ordinal := by
simp only [Std.lt_iff_le_and_not_ge, le_iff_ordinal_le]
theorem lt_iff_ofOrdinal_lt {f g : Fin Char.numCodePoints} :
f < g Char.ofOrdinal f < Char.ofOrdinal g := by
simp only [Std.lt_iff_le_and_not_ge, le_iff_ofOrdinal_le]
theorem succ?_eq {c : Char} : c.succ? = (c.ordinal.addNat? 1).map Char.ofOrdinal := by
fun_cases Char.succ? with
| case1 h =>
rw [Fin.addNat?_eq_some]
· simp only [coe_ordinal, Option.map_some, Option.some.injEq, Char.ext_iff, val_ofOrdinal,
UInt32.ofNatLT_add, UInt32.reduceOfNatLT]
split
· simp only [UInt32.ofNatLT_toNat, dite_eq_ite, left_eq_ite_iff, Nat.not_lt,
Nat.reduceLeDiff, UInt32.left_eq_add]
grind [UInt32.lt_iff_toNat_lt]
· grind
· simp [coe_ordinal]
grind [UInt32.lt_iff_toNat_lt]
| case2 =>
rw [Fin.addNat?_eq_some]
· simp [coe_ordinal, *, Char.ext_iff, val_ofOrdinal, numSurrogates]
· simp [coe_ordinal, *, numCodePoints]
| case3 =>
rw [Fin.addNat?_eq_some]
· simp only [coe_ordinal, Option.map_some, Option.some.injEq, Char.ext_iff, val_ofOrdinal,
UInt32.ofNatLT_add, UInt32.reduceOfNatLT]
split
· grind
· rw [dif_neg]
· simp only [ UInt32.toNat_inj, UInt32.toNat_add, UInt32.reduceToNat, Nat.reducePow,
UInt32.toNat_ofNatLT, Nat.mod_add_mod]
grind [UInt32.lt_iff_toNat_lt, UInt32.toNat_inj]
· grind [UInt32.lt_iff_toNat_lt, UInt32.toNat_inj]
· grind [UInt32.lt_iff_toNat_lt]
| case4 =>
rw [eq_comm]
grind [UInt32.lt_iff_toNat_lt]
theorem map_ordinal_succ? {c : Char} : c.succ?.map ordinal = c.ordinal.addNat? 1 := by
simp [succ?_eq]
theorem succMany?_eq {m : Nat} {c : Char} :
c.succMany? m = (c.ordinal.addNat? m).map Char.ofOrdinal := by
rfl
end Char

View File

@@ -11,3 +11,4 @@ public import Init.Data.Fin.Log2
public import Init.Data.Fin.Iterate
public import Init.Data.Fin.Fold
public import Init.Data.Fin.Lemmas
public import Init.Data.Fin.OverflowAware

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.
Author: Markus Himmel
-/
module
prelude
public import Init.Data.Fin.Basic
import Init.Data.Fin.Lemmas
set_option doc.verso true
public section
namespace Fin
/--
Overflow-aware addition of a natural number to an element of {lean}`Fin n`.
Examples:
* {lean}`(2 : Fin 3).addNat? 1 = (none : Option (Fin 3))`
* {lean}`(2 : Fin 4).addNat? 1 = (some 3 : Option (Fin 4))`
-/
@[inline]
protected def addNat? (i : Fin n) (m : Nat) : Option (Fin n) :=
if h : i + m < n then some i + m, h else none
theorem addNat?_eq_some {i : Fin n} (h : i + m < n) : i.addNat? m = some i + m, h := by
simp [Fin.addNat?, h]
theorem addNat?_eq_some_iff {i : Fin n} :
i.addNat? m = some j i + m < n j = i + m := by
simp only [Fin.addNat?]
split <;> simp [Fin.ext_iff, eq_comm, *]
@[simp]
theorem addNat?_eq_none_iff {i : Fin n} : i.addNat? m = none n i + m := by
simp only [Fin.addNat?]
split <;> simp_all [Nat.not_lt]
@[simp]
theorem addNat?_zero {i : Fin n} : i.addNat? 0 = some i := by
simp [addNat?_eq_some_iff]
@[grind =]
theorem addNat?_eq_dif {i : Fin n} :
i.addNat? m = if h : i + m < n then some i + m, h else none := by
rfl
end Fin

View File

@@ -1447,4 +1447,12 @@ instance : LawfulOrderLT Int where
lt_iff := by
simp [ Int.not_le, Decidable.imp_iff_not_or, Std.Total.total]
instance : LawfulOrderLeftLeaningMin Int where
min_eq_left _ _ := Int.min_eq_left
min_eq_right _ _ h := Int.min_eq_right (le_of_lt (not_le.1 h))
instance : LawfulOrderLeftLeaningMax Int where
max_eq_left _ _ := Int.max_eq_left
max_eq_right _ _ h := Int.max_eq_right (le_of_lt (not_le.1 h))
end Int

View File

@@ -29,7 +29,11 @@ open Nat
/-! ### min? -/
@[simp] theorem min?_nil [Min α] : ([] : List α).min? = none := rfl
@[simp, grind =] theorem min?_nil [Min α] : ([] : List α).min? = none := rfl
@[simp, grind =]
public theorem min?_singleton [Min α] {x : α} : [x].min? = some x :=
(rfl)
-- We don't put `@[simp]` on `min?_cons'`,
-- because the definition in terms of `foldl` is not useful for proofs.
@@ -39,9 +43,14 @@ theorem min?_cons' [Min α] {xs : List α} : (x :: xs).min? = some (foldl min x
(x :: xs).min? = some (xs.min?.elim x (min x)) := by
cases xs <;> simp [min?_cons', foldl_assoc]
@[simp] theorem min?_eq_none_iff {xs : List α} [Min α] : xs.min? = none xs = [] := by
@[simp, grind =] theorem min?_eq_none_iff {xs : List α} [Min α] : xs.min? = none xs = [] := by
cases xs <;> simp [min?]
@[simp, grind =]
public theorem isSome_min?_iff {xs : List α} [Min α] : xs.min?.isSome xs [] := by
cases xs <;> simp [min?]
@[grind .]
theorem isSome_min?_of_mem {l : List α} [Min α] {a : α} (h : a l) :
l.min?.isSome := by
cases l <;> simp_all [min?_cons']
@@ -143,7 +152,8 @@ theorem min?_replicate [Min α] [Std.IdempotentOp (min : ααα)] {n :
| zero => rfl
| succ n ih => cases n <;> simp_all [replicate_succ, min?_cons', Std.IdempotentOp.idempotent]
@[simp] theorem min?_replicate_of_pos [Min α] [MinEqOr α] {n : Nat} {a : α} (h : 0 < n) :
@[simp, grind =]
theorem min?_replicate_of_pos [Min α] [MinEqOr α] {n : Nat} {a : α} (h : 0 < n) :
(replicate n a).min? = some a := by
simp [min?_replicate, Nat.ne_of_gt h]
@@ -160,6 +170,11 @@ theorem foldl_min [Min α] [Std.IdempotentOp (min : ααα)] [Std.Asso
/-! ### min -/
@[simp, grind =]
theorem min_singleton [Min α] {x : α} :
[x].min (cons_ne_nil _ _) = x := by
(rfl)
theorem min?_eq_some_min [Min α] : {l : List α} (hl : l [])
l.min? = some (l.min hl)
| a::as, _ => by simp [List.min, List.min?_cons']
@@ -168,15 +183,22 @@ theorem min_eq_get_min? [Min α] : (l : List α) → (hl : l ≠ []) →
l.min hl = l.min?.get (isSome_min?_of_ne_nil hl)
| a::as, _ => by simp [List.min, List.min?_cons']
@[simp, grind =]
theorem get_min? [Min α] {l : List α} {h : l.min?.isSome} :
l.min?.get h = l.min (isSome_min?_iff.mp h) := by
simp [min?_eq_some_min (isSome_min?_iff.mp h)]
theorem min_eq_head {α : Type u} [Min α] {l : List α} (hl : l [])
(h : l.Pairwise (fun a b => min a b = a)) : l.min hl = l.head hl := by
apply Option.some.inj
rw [ min?_eq_some_min, head?_eq_some_head]
exact min?_eq_head? h
@[grind .]
theorem min_mem [Min α] [MinEqOr α] {l : List α} (hl : l []) : l.min hl l :=
min?_mem (min?_eq_some_min hl)
@[grind .]
theorem min_le_of_mem [Min α] [LE α] [Std.IsLinearOrder α] [Std.LawfulOrderMin α]
{l : List α} {a : α} (ha : a l) :
l.min (ne_nil_of_mem ha) a :=
@@ -190,7 +212,7 @@ theorem min_eq_iff [Min α] [LE α] {l : List α} [IsLinearOrder α] [LawfulOrde
l.min hl = a a l b, b l a b := by
simpa [min?_eq_some_min hl] using (min?_eq_some_iff (xs := l))
@[simp] theorem min_replicate [Min α] [MinEqOr α] {n : Nat} {a : α} (h : replicate n a []) :
@[simp, grind =] theorem min_replicate [Min α] [MinEqOr α] {n : Nat} {a : α} (h : replicate n a []) :
(replicate n a).min h = a := by
have n_pos : 0 < n := Nat.pos_of_ne_zero (fun hn => by simp [hn] at h)
simpa [min?_eq_some_min h] using (min?_replicate_of_pos (a := a) n_pos)
@@ -202,7 +224,11 @@ theorem foldl_min_eq_min [Min α] [Std.IdempotentOp (min : ααα)] [S
/-! ### max? -/
@[simp] theorem max?_nil [Max α] : ([] : List α).max? = none := rfl
@[simp, grind =] theorem max?_nil [Max α] : ([] : List α).max? = none := rfl
@[simp, grind =]
public theorem max?_singleton [Max α] {x : α} : [x].max? = some x :=
(rfl)
-- We don't put `@[simp]` on `max?_cons'`,
-- because the definition in terms of `foldl` is not useful for proofs.
@@ -212,9 +238,14 @@ theorem max?_cons' [Max α] {xs : List α} : (x :: xs).max? = some (foldl max x
(x :: xs).max? = some (xs.max?.elim x (max x)) := by
cases xs <;> simp [max?_cons', foldl_assoc]
@[simp] theorem max?_eq_none_iff {xs : List α} [Max α] : xs.max? = none xs = [] := by
@[simp, grind =] theorem max?_eq_none_iff {xs : List α} [Max α] : xs.max? = none xs = [] := by
cases xs <;> simp [max?]
@[simp, grind =]
public theorem isSome_max?_iff {xs : List α} [Max α] : xs.max?.isSome xs [] := by
cases xs <;> simp [max?]
@[grind .]
theorem isSome_max?_of_mem {l : List α} [Max α] {a : α} (h : a l) :
l.max?.isSome := by
cases l <;> simp_all [max?_cons']
@@ -329,7 +360,8 @@ theorem max?_replicate [Max α] [Std.IdempotentOp (max : ααα)] {n :
| zero => rfl
| succ n ih => cases n <;> simp_all [replicate_succ, max?_cons', Std.IdempotentOp.idempotent]
@[simp] theorem max?_replicate_of_pos [Max α] [MaxEqOr α] {n : Nat} {a : α} (h : 0 < n) :
@[simp, grind =]
theorem max?_replicate_of_pos [Max α] [MaxEqOr α] {n : Nat} {a : α} (h : 0 < n) :
(replicate n a).max? = some a := by
simp [max?_replicate, Nat.ne_of_gt h]
@@ -346,6 +378,11 @@ theorem foldl_max [Max α] [Std.IdempotentOp (max : ααα)] [Std.Asso
/-! ### max -/
@[simp, grind =]
theorem max_singleton [Max α] {x : α} :
[x].max (cons_ne_nil _ _) = x := by
(rfl)
theorem max?_eq_some_max [Max α] : {l : List α} (hl : l [])
l.max? = some (l.max hl)
| a::as, _ => by simp [List.max, List.max?_cons']
@@ -354,12 +391,18 @@ theorem max_eq_get_max? [Max α] : (l : List α) → (hl : l ≠ []) →
l.max hl = l.max?.get (isSome_max?_of_ne_nil hl)
| a::as, _ => by simp [List.max, List.max?_cons']
@[simp, grind =]
theorem get_max? [Max α] {l : List α} {h : l.max?.isSome} :
l.max?.get h = l.max (isSome_max?_iff.mp h) := by
simp [max?_eq_some_max (isSome_max?_iff.mp h)]
theorem max_eq_head {α : Type u} [Max α] {l : List α} (hl : l [])
(h : l.Pairwise (fun a b => max a b = a)) : l.max hl = l.head hl := by
apply Option.some.inj
rw [ max?_eq_some_max, head?_eq_some_head]
exact max?_eq_head? h
@[grind .]
theorem max_mem [Max α] [MaxEqOr α] {l : List α} (hl : l []) : l.max hl l :=
max?_mem (max?_eq_some_max hl)
@@ -371,12 +414,13 @@ theorem max_eq_iff [Max α] [LE α] {l : List α} [IsLinearOrder α] [LawfulOrde
l.max hl = a a l b, b l b a := by
simpa [max?_eq_some_max hl] using (max?_eq_some_iff (xs := l))
@[grind .]
theorem le_max_of_mem [Max α] [LE α] [Std.IsLinearOrder α] [Std.LawfulOrderMax α]
{l : List α} {a : α} (ha : a l) :
a l.max (List.ne_nil_of_mem ha) :=
(max?_eq_some_iff.mp (max?_eq_some_max (List.ne_nil_of_mem ha))).right a ha
@[simp] theorem max_replicate [Max α] [MaxEqOr α] {n : Nat} {a : α} (h : replicate n a []) :
@[simp, grind =] theorem max_replicate [Max α] [MaxEqOr α] {n : Nat} {a : α} (h : replicate n a []) :
(replicate n a).max h = a := by
have n_pos : 0 < n := Nat.pos_of_ne_zero (fun hn => by simp [hn] at h)
simpa [max?_eq_some_max h] using (max?_replicate_of_pos (a := a) n_pos)

View File

@@ -15,3 +15,4 @@ public import Init.Data.Option.Attach
public import Init.Data.Option.List
public import Init.Data.Option.Monadic
public import Init.Data.Option.Array
public import Init.Data.Option.Function

View File

@@ -0,0 +1,26 @@
/-
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.Function
import Init.Data.Option.Lemmas
public section
namespace Option
theorem map_injective {f : α β} (hf : Function.Injective f) :
Function.Injective (Option.map f) := by
intros a b hab
cases a <;> cases b
· simp
· simp at hab
· simp at hab
· simp only [map_some, some.injEq] at hab
simpa using hf hab
end Option

View File

@@ -307,12 +307,20 @@ theorem map_id' {x : Option α} : (x.map fun a => a) = x := congrFun map_id x
theorem map_id_apply' {α : Type u} {x : Option α} : Option.map (fun (a : α) => a) x = x := by simp
/-- See `Option.apply_get` for a version that can be rewritten in the reverse direction. -/
@[simp, grind =] theorem get_map {f : α β} {o : Option α} {h : (o.map f).isSome} :
(o.map f).get h = f (o.get (by simpa using h)) := by
cases o with
| none => simp at h
| some a => simp
/-- See `Option.get_map` for a version that can be rewritten in the reverse direction. -/
theorem apply_get {f : α β} {o : Option α} {h} :
f (o.get h) = (o.map f).get (by simp [h]) := by
cases o
· simp at h
· simp
@[simp] theorem map_map (h : β γ) (g : α β) (x : Option α) :
(x.map g).map h = x.map (h g) := by
cases x <;> simp only [map_none, map_some, ··]
@@ -732,6 +740,11 @@ theorem get_merge {o o' : Option α} {f : ααα} {i : α} [Std.Lawful
theorem elim_guard : (guard p a).elim b f = if p a then f a else b := by
cases h : p a <;> simp [*, guard]
@[simp]
theorem Option.elim_map {f : α β} {g' : γ} {g : β γ} (o : Option α) :
(o.map f).elim g' g = o.elim g' (g f) := by
cases o <;> simp
-- I don't see how to construct a good grind pattern to instantiate this.
@[simp] theorem getD_map (f : α β) (x : α) (o : Option α) :
(o.map f).getD (f x) = f (getD o x) := by cases o <;> rfl

View File

@@ -10,7 +10,10 @@ public import Init.Data.Range.Polymorphic.Basic
public import Init.Data.Range.Polymorphic.Iterators
public import Init.Data.Range.Polymorphic.Stream
public import Init.Data.Range.Polymorphic.Lemmas
public import Init.Data.Range.Polymorphic.Map
public import Init.Data.Range.Polymorphic.Fin
public import Init.Data.Range.Polymorphic.Char
public import Init.Data.Range.Polymorphic.Nat
public import Init.Data.Range.Polymorphic.Int
public import Init.Data.Range.Polymorphic.BitVec

View File

@@ -0,0 +1,79 @@
/-
Copyright (c) 2026 Lean FRO, LLC. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Author: Markus Himmel
-/
module
prelude
public import Init.Data.Char.Ordinal
public import Init.Data.Range.Polymorphic.Fin
import Init.Data.Range.Polymorphic.Lemmas
import Init.Data.Range.Polymorphic.Map
import Init.Data.Char.Order
open Std Std.PRange Std.PRange.UpwardEnumerable
namespace Char
public instance : UpwardEnumerable Char where
succ?
succMany?
@[simp]
public theorem pRangeSucc?_eq : PRange.succ? (α := Char) = Char.succ? := rfl
@[simp]
public theorem pRangeSuccMany?_eq : PRange.succMany? (α := Char) = Char.succMany? := rfl
public instance : Rxc.HasSize Char where
size lo hi := Rxc.HasSize.size lo.ordinal hi.ordinal
public instance : Rxo.HasSize Char where
size lo hi := Rxo.HasSize.size lo.ordinal hi.ordinal
public instance : Rxi.HasSize Char where
size hi := Rxi.HasSize.size hi.ordinal
public instance : Least? Char where
least? := some '\x00'
@[simp]
public theorem least?_eq : Least?.least? (α := Char) = some '\x00' := rfl
def map : Map Char (Fin Char.numCodePoints) where
toFun := Char.ordinal
injective := ordinal_injective
succ?_toFun := by simp [succ?_eq]
succMany?_toFun := by simp [succMany?_eq]
@[simp]
theorem toFun_map : map.toFun = Char.ordinal := rfl
instance : Map.PreservesLE map where
le_iff := by simp [le_iff_ordinal_le]
instance : Map.PreservesRxcSize map where
size_eq := rfl
instance : Map.PreservesRxoSize map where
size_eq := rfl
instance : Map.PreservesRxiSize map where
size_eq := rfl
instance : Map.PreservesLeast? map where
map_least? := by simp
public instance : LawfulUpwardEnumerable Char := .ofMap map
public instance : LawfulUpwardEnumerableLE Char := .ofMap map
public instance : LawfulUpwardEnumerableLT Char := .ofMap map
public instance : LawfulUpwardEnumerableLeast? Char := .ofMap map
public instance : Rxc.LawfulHasSize Char := .ofMap map
public instance : Rxc.IsAlwaysFinite Char := .ofMap map
public instance : Rxo.LawfulHasSize Char := .ofMap map
public instance : Rxo.IsAlwaysFinite Char := .ofMap map
public instance : Rxi.LawfulHasSize Char := .ofMap map
public instance : Rxi.IsAlwaysFinite Char := .ofMap map
end Char

View File

@@ -0,0 +1,92 @@
/-
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.Range.Polymorphic.Instances
public import Init.Data.Fin.OverflowAware
import Init.Grind
public section
open Std Std.PRange
namespace Fin
instance : UpwardEnumerable (Fin n) where
succ? i := i.addNat? 1
succMany? m i := i.addNat? m
@[simp, grind =]
theorem pRangeSucc?_eq : PRange.succ? (α := Fin n) = (·.addNat? 1) := rfl
@[simp, grind =]
theorem pRangeSuccMany?_eq : PRange.succMany? m (α := Fin n) = (·.addNat? m) :=
rfl
instance : LawfulUpwardEnumerable (Fin n) where
ne_of_lt a b := by grind [UpwardEnumerable.LT]
succMany?_zero a := by simp
succMany?_add_one m a := by grind
instance : LawfulUpwardEnumerableLE (Fin n) where
le_iff x y := by
simp only [le_def, UpwardEnumerable.LE, pRangeSuccMany?_eq, Fin.addNat?_eq_dif,
Option.dite_none_right_eq_some, Option.some.injEq, val_inj, exists_prop]
exact fun h => y - x, by grind, by grind
instance : Least? (Fin 0) where
least? := none
instance : LawfulUpwardEnumerableLeast? (Fin 0) where
least?_le a := False.elim (Nat.not_lt_zero _ a.isLt)
@[simp]
theorem least?_eq_of_zero : Least?.least? (α := Fin 0) = none := rfl
instance [NeZero n] : Least? (Fin n) where
least? := some 0
instance [NeZero n] : LawfulUpwardEnumerableLeast? (Fin n) where
least?_le a := 0, rfl, (LawfulUpwardEnumerableLE.le_iff 0 a).1 (Fin.zero_le _)
@[simp]
theorem least?_eq [NeZero n] : Least?.least? (α := Fin n) = some 0 := rfl
instance : LawfulUpwardEnumerableLT (Fin n) := inferInstance
instance : Rxc.HasSize (Fin n) where
size lo hi := hi + 1 - lo
@[grind =]
theorem rxcHasSize_eq :
Rxc.HasSize.size (α := Fin n) = fun (lo hi : Fin n) => (hi + 1 - lo : Nat) := rfl
instance : Rxc.LawfulHasSize (Fin n) where
size_eq_zero_of_not_le bound x := by grind
size_eq_one_of_succ?_eq_none lo hi := by grind
size_eq_succ_of_succ?_eq_some lo hi x := by grind
instance : Rxc.IsAlwaysFinite (Fin n) := inferInstance
instance : Rxo.HasSize (Fin n) := .ofClosed
instance : Rxo.LawfulHasSize (Fin n) := inferInstance
instance : Rxo.IsAlwaysFinite (Fin n) := inferInstance
instance : Rxi.HasSize (Fin n) where
size lo := n - lo
@[grind =]
theorem rxiHasSize_eq :
Rxi.HasSize.size (α := Fin n) = fun (lo : Fin n) => (n - lo : Nat) := rfl
instance : Rxi.LawfulHasSize (Fin n) where
size_eq_one_of_succ?_eq_none x := by grind
size_eq_succ_of_succ?_eq_some lo lo' := by grind
instance : Rxi.IsAlwaysFinite (Fin n) := inferInstance
end Fin

View File

@@ -0,0 +1,195 @@
/-
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.Range.Polymorphic.Instances
public import Init.Data.Function
import Init.Data.Order.Lemmas
import Init.Data.Option.Function
public section
/-!
# Mappings between `UpwardEnumerable` types
In this file we build machinery for pulling back lawfulness properties for `UpwardEnumerable` along
injective functions that commute with the relevant operations.
-/
namespace Std
namespace PRange
namespace UpwardEnumerable
/--
An injective mapping between two types implementing `UpwardEnumerable` that commutes with `succ?`
and `succMany?`.
Having such a mapping means that all of the `Prop`-valued lawfulness classes around
`UpwardEnumerable` can be pulled back.
-/
structure Map (α : Type u) (β : Type v) [UpwardEnumerable α] [UpwardEnumerable β] where
toFun : α β
injective : Function.Injective toFun
succ?_toFun (a : α) : succ? (toFun a) = (succ? a).map toFun
succMany?_toFun (n : Nat) (a : α) : succMany? n (toFun a) = (succMany? n a).map toFun
namespace Map
variable [UpwardEnumerable α] [UpwardEnumerable β]
theorem succ?_eq_none_iff (f : Map α β) {a : α} :
succ? a = none succ? (f.toFun a) = none := by
rw [ (Option.map_injective f.injective).eq_iff, Option.map_none, f.succ?_toFun]
theorem succ?_eq_some_iff (f : Map α β) {a b : α} :
succ? a = some b succ? (f.toFun a) = some (f.toFun b) := by
rw [ (Option.map_injective f.injective).eq_iff, Option.map_some, f.succ?_toFun]
theorem le_iff (f : Map α β) {a b : α} :
UpwardEnumerable.LE a b UpwardEnumerable.LE (f.toFun a) (f.toFun b) := by
simp only [UpwardEnumerable.LE, f.succMany?_toFun, Option.map_eq_some_iff]
refine fun n, hn => n, b, by simp [hn], fun n, c, hn => n, ?_
rw [hn.1, Option.some_inj, f.injective hn.2]
theorem lt_iff (f : Map α β) {a b : α} :
UpwardEnumerable.LT a b UpwardEnumerable.LT (f.toFun a) (f.toFun b) := by
simp only [UpwardEnumerable.LT, f.succMany?_toFun, Option.map_eq_some_iff]
refine fun n, hn => n, b, by simp [hn], fun n, c, hn => n, ?_
rw [hn.1, Option.some_inj, f.injective hn.2]
theorem succ?_toFun' (f : Map α β) : succ? f.toFun = Option.map f.toFun succ? := by
ext
simp [f.succ?_toFun]
/-- Compatibility class for `Map` and `≤`. -/
class PreservesLE [LE α] [LE β] (f : Map α β) where
le_iff : a b f.toFun a f.toFun b
/-- Compatibility class for `Map` and `<`. -/
class PreservesLT [LT α] [LT β] (f : Map α β) where
lt_iff : a < b f.toFun a < f.toFun b
/-- Compatibility class for `Map` and `Rxc.HasSize`. -/
class PreservesRxcSize [Rxc.HasSize α] [Rxc.HasSize β] (f : Map α β) where
size_eq : Rxc.HasSize.size a b = Rxc.HasSize.size (f.toFun a) (f.toFun b)
/-- Compatibility class for `Map` and `Rxo.HasSize`. -/
class PreservesRxoSize [Rxo.HasSize α] [Rxo.HasSize β] (f : Map α β) where
size_eq : Rxo.HasSize.size a b = Rxo.HasSize.size (f.toFun a) (f.toFun b)
/-- Compatibility class for `Map` and `Rxi.HasSize`. -/
class PreservesRxiSize [Rxi.HasSize α] [Rxi.HasSize β] (f : Map α β) where
size_eq : Rxi.HasSize.size b = Rxi.HasSize.size (f.toFun b)
/-- Compatibility class for `Map` and `Least?`. -/
class PreservesLeast? [Least? α] [Least? β] (f : Map α β) where
map_least? : Least?.least?.map f.toFun = Least?.least?
end UpwardEnumerable.Map
open UpwardEnumerable
variable [UpwardEnumerable α] [UpwardEnumerable β]
theorem LawfulUpwardEnumerable.ofMap [LawfulUpwardEnumerable β] (f : Map α β) :
LawfulUpwardEnumerable α where
ne_of_lt a b := by
simpa only [f.lt_iff, f.injective.ne_iff] using LawfulUpwardEnumerable.ne_of_lt _ _
succMany?_zero a := by
apply Option.map_injective f.injective
simpa [ f.succMany?_toFun] using LawfulUpwardEnumerable.succMany?_zero _
succMany?_add_one n a := by
apply Option.map_injective f.injective
rw [ f.succMany?_toFun, LawfulUpwardEnumerable.succMany?_add_one,
f.succMany?_toFun, Option.bind_map, Map.succ?_toFun', Option.map_bind]
instance [LE α] [LT α] [LawfulOrderLT α] [LE β] [LT β] [LawfulOrderLT β] (f : Map α β)
[f.PreservesLE] : f.PreservesLT where
lt_iff := by simp [lt_iff_le_and_not_ge, Map.PreservesLE.le_iff (f := f)]
theorem LawfulUpwardEnumerableLE.ofMap [LE α] [LE β] [LawfulUpwardEnumerableLE β] (f : Map α β)
[f.PreservesLE] : LawfulUpwardEnumerableLE α where
le_iff := by simp [Map.PreservesLE.le_iff (f := f), f.le_iff, LawfulUpwardEnumerableLE.le_iff]
theorem LawfulUpwardEnumerableLT.ofMap [LT α] [LT β] [LawfulUpwardEnumerableLT β] (f : Map α β)
[f.PreservesLT] : LawfulUpwardEnumerableLT α where
lt_iff := by simp [Map.PreservesLT.lt_iff (f := f), f.lt_iff, LawfulUpwardEnumerableLT.lt_iff]
theorem LawfulUpwardEnumerableLeast?.ofMap [Least? α] [Least? β] [LawfulUpwardEnumerableLeast? β]
(f : Map α β) [f.PreservesLeast?] : LawfulUpwardEnumerableLeast? α where
least?_le a := by
obtain l, hl, hl' := LawfulUpwardEnumerableLeast?.least?_le (f.toFun a)
have : (Least?.least? (α := α)).isSome := by
rw [ Option.isSome_map (f := f.toFun), Map.PreservesLeast?.map_least?,
hl, Option.isSome_some]
refine Option.get _ this, by simp, ?_
rw [f.le_iff, Option.apply_get (f := f.toFun)]
simpa [Map.PreservesLeast?.map_least?, hl] using hl'
end PRange
open PRange PRange.UpwardEnumerable
variable [UpwardEnumerable α] [UpwardEnumerable β]
theorem Rxc.LawfulHasSize.ofMap [LE α] [LE β] [Rxc.HasSize α] [Rxc.HasSize β] [Rxc.LawfulHasSize β]
(f : Map α β) [f.PreservesLE] [f.PreservesRxcSize] : Rxc.LawfulHasSize α where
size_eq_zero_of_not_le a b := by
simpa [Map.PreservesRxcSize.size_eq (f := f), Map.PreservesLE.le_iff (f := f)] using
Rxc.LawfulHasSize.size_eq_zero_of_not_le _ _
size_eq_one_of_succ?_eq_none lo hi := by
simpa [Map.PreservesRxcSize.size_eq (f := f), Map.PreservesLE.le_iff (f := f),
f.succ?_eq_none_iff] using
Rxc.LawfulHasSize.size_eq_one_of_succ?_eq_none _ _
size_eq_succ_of_succ?_eq_some lo hi lo' := by
simpa [Map.PreservesRxcSize.size_eq (f := f), Map.PreservesLE.le_iff (f := f),
f.succ?_eq_some_iff] using
Rxc.LawfulHasSize.size_eq_succ_of_succ?_eq_some _ _ _
theorem Rxo.LawfulHasSize.ofMap [LT α] [LT β] [Rxo.HasSize α] [Rxo.HasSize β] [Rxo.LawfulHasSize β]
(f : Map α β) [f.PreservesLT] [f.PreservesRxoSize] : Rxo.LawfulHasSize α where
size_eq_zero_of_not_le a b := by
simpa [Map.PreservesRxoSize.size_eq (f := f), Map.PreservesLT.lt_iff (f := f)] using
Rxo.LawfulHasSize.size_eq_zero_of_not_le _ _
size_eq_one_of_succ?_eq_none lo hi := by
simpa [Map.PreservesRxoSize.size_eq (f := f), Map.PreservesLT.lt_iff (f := f),
f.succ?_eq_none_iff] using
Rxo.LawfulHasSize.size_eq_one_of_succ?_eq_none _ _
size_eq_succ_of_succ?_eq_some lo hi lo' := by
simpa [Map.PreservesRxoSize.size_eq (f := f), Map.PreservesLT.lt_iff (f := f),
f.succ?_eq_some_iff] using
Rxo.LawfulHasSize.size_eq_succ_of_succ?_eq_some _ _ _
theorem Rxi.LawfulHasSize.ofMap [Rxi.HasSize α] [Rxi.HasSize β] [Rxi.LawfulHasSize β]
(f : Map α β) [f.PreservesRxiSize] : Rxi.LawfulHasSize α where
size_eq_one_of_succ?_eq_none lo := by
simpa [Map.PreservesRxiSize.size_eq (f := f), f.succ?_eq_none_iff] using
Rxi.LawfulHasSize.size_eq_one_of_succ?_eq_none _
size_eq_succ_of_succ?_eq_some lo lo' := by
simpa [Map.PreservesRxiSize.size_eq (f := f), f.succ?_eq_some_iff] using
Rxi.LawfulHasSize.size_eq_succ_of_succ?_eq_some _ _
theorem Rxc.IsAlwaysFinite.ofMap [LE α] [LE β] [Rxc.IsAlwaysFinite β] (f : Map α β)
[f.PreservesLE] : Rxc.IsAlwaysFinite α where
finite init hi := by
obtain n, hn := Rxc.IsAlwaysFinite.finite (f.toFun init) (f.toFun hi)
exact n, by simpa [f.succMany?_toFun, Map.PreservesLE.le_iff (f := f)] using hn
theorem Rxo.IsAlwaysFinite.ofMap [LT α] [LT β] [Rxo.IsAlwaysFinite β] (f : Map α β)
[f.PreservesLT] : Rxo.IsAlwaysFinite α where
finite init hi := by
obtain n, hn := Rxo.IsAlwaysFinite.finite (f.toFun init) (f.toFun hi)
exact n, by simpa [f.succMany?_toFun, Map.PreservesLT.lt_iff (f := f)] using hn
theorem Rxi.IsAlwaysFinite.ofMap [Rxi.IsAlwaysFinite β] (f : Map α β) : Rxi.IsAlwaysFinite α where
finite init := by
obtain n, hn := Rxi.IsAlwaysFinite.finite (f.toFun init)
exact n, by simpa [f.succMany?_toFun] using hn
end Std

View File

@@ -157,7 +157,7 @@ Converts an 8-bit signed integer to a natural number, mapping all negative numbe
Use `Int8.toBitVec` to obtain the two's complement representation.
-/
@[inline] def Int8.toNatClampNeg (i : Int8) : Nat := i.toInt.toNat
@[suggest_for Int8.toNat, inline] def Int8.toNatClampNeg (i : Int8) : Nat := i.toInt.toNat
/-- Obtains the `Int8` whose 2's complement representation is the given `BitVec 8`. -/
@[inline] def Int8.ofBitVec (b : BitVec 8) : Int8 := b
@@ -510,7 +510,7 @@ Converts a 16-bit signed integer to a natural number, mapping all negative numbe
Use `Int16.toBitVec` to obtain the two's complement representation.
-/
@[inline] def Int16.toNatClampNeg (i : Int16) : Nat := i.toInt.toNat
@[suggest_for Int16.toNat, inline] def Int16.toNatClampNeg (i : Int16) : Nat := i.toInt.toNat
/-- Obtains the `Int16` whose 2's complement representation is the given `BitVec 16`. -/
@[inline] def Int16.ofBitVec (b : BitVec 16) : Int16 := b
@@ -880,7 +880,7 @@ Converts a 32-bit signed integer to a natural number, mapping all negative numbe
Use `Int32.toBitVec` to obtain the two's complement representation.
-/
@[inline] def Int32.toNatClampNeg (i : Int32) : Nat := i.toInt.toNat
@[suggest_for Int32.toNat, inline] def Int32.toNatClampNeg (i : Int32) : Nat := i.toInt.toNat
/-- Obtains the `Int32` whose 2's complement representation is the given `BitVec 32`. -/
@[inline] def Int32.ofBitVec (b : BitVec 32) : Int32 := b
@@ -1270,7 +1270,7 @@ Converts a 64-bit signed integer to a natural number, mapping all negative numbe
Use `Int64.toBitVec` to obtain the two's complement representation.
-/
@[inline] def Int64.toNatClampNeg (i : Int64) : Nat := i.toInt.toNat
@[suggest_for Int64.toNat, inline] def Int64.toNatClampNeg (i : Int64) : Nat := i.toInt.toNat
/-- Obtains the `Int64` whose 2's complement representation is the given `BitVec 64`. -/
@[inline] def Int64.ofBitVec (b : BitVec 64) : Int64 := b
@@ -1637,7 +1637,7 @@ Converts a word-sized signed integer to a natural number, mapping all negative n
Use `ISize.toBitVec` to obtain the two's complement representation.
-/
@[inline] def ISize.toNatClampNeg (i : ISize) : Nat := i.toInt.toNat
@[suggest_for ISize.toNat, inline] def ISize.toNatClampNeg (i : ISize) : Nat := i.toInt.toNat
/-- Obtains the `ISize` whose 2's complement representation is the given `BitVec`. -/
@[inline] def ISize.ofBitVec (b : BitVec System.Platform.numBits) : ISize := b

View File

@@ -4,12 +4,9 @@ Released under Apache 2.0 license as described in the file LICENSE.
Authors: Leonardo de Moura
-/
module
prelude
public import Init.Classical
public section
namespace Lean.Grind
/-- A helper gadget for annotating nested proofs in goals. -/

View File

@@ -360,7 +360,7 @@ recommended_spelling "smul" for "•" in [HSMul.hSMul, «term_•_»]
recommended_spelling "append" for "++" in [HAppend.hAppend, «term_++_»]
/-- when used as a unary operator -/
recommended_spelling "neg" for "-" in [Neg.neg, «term-_»]
recommended_spelling "inv" for "⁻¹" in [Inv.inv]
recommended_spelling "inv" for "⁻¹" in [Inv.inv, «term_⁻¹»]
recommended_spelling "dvd" for "" in [Dvd.dvd, «term__»]
recommended_spelling "shiftLeft" for "<<<" in [HShiftLeft.hShiftLeft, «term_<<<_»]
recommended_spelling "shiftRight" for ">>>" in [HShiftRight.hShiftRight, «term_>>>_»]

View File

@@ -2810,6 +2810,8 @@ structure Char where
/-- The value must be a legal scalar value. -/
valid : val.isValidChar
grind_pattern Char.valid => self.val
private theorem isValidChar_UInt32 {n : Nat} (h : n.isValidChar) : LT.lt n UInt32.size :=
match h with
| Or.inl h => Nat.lt_trans h (of_decide_eq_true rfl)

View File

@@ -44,6 +44,61 @@ theorem implies_congr_left {p₁ p₂ : Sort u} {q : Sort v} (h : p₁ = p₂) :
theorem implies_congr_right {p : Sort u} {q₁ q₂ : Sort v} (h : q₁ = q₂) : (p q₁) = (p q₂) :=
h rfl
namespace Lean
/--
`Arrow α β` is definitionally equal to `α → β`, but represented as a function
application rather than `Expr.forallE`.
This representation is useful for proof automation that builds nested implications
like `pₙ → ... → p₂ → p₁`. With `Expr.forallE`, each nesting level introduces a
binder that bumps de Bruijn indices in subterms, destroying sharing even with
hash-consing. For example, if `p₁` contains `#20`, then at depth 2 it becomes `#21`,
at depth 3 it becomes `#22`, etc., causing quadratic proof growth.
With `arrow`, both arguments are explicit (not under binders), so subterms remain
identical across nesting levels and can be shared, yielding linear-sized proofs.
-/
def Arrow (α : Sort u) (β : Sort v) : Sort (imax u v) := α β
theorem arrow_congr {p₁ p₂ : Sort u} {q₁ q₂ : Sort v} (h₁ : p₁ = p₂) (h₂ : q₁ = q₂) : Arrow p₁ q₁ = Arrow p₂ q₂ :=
h₁ h₂ rfl
theorem arrow_congr_left {p₁ p₂ : Sort u} {q : Sort v} (h : p₁ = p₂) : Arrow p₁ q = Arrow p₂ q :=
h rfl
theorem arrow_congr_right {p : Sort u} {q₁ q₂ : Sort v} (h : q₁ = q₂) : Arrow p q₁ = Arrow p q₂ :=
h rfl
theorem true_arrow (p : Prop) : Arrow True p = p := by
simp [Arrow]; constructor
next => intro h; exact h .intro
next => intros; assumption
theorem true_arrow_congr_left (p q : Prop) : p = True Arrow p q = q := by
intros; subst p; apply true_arrow
theorem true_arrow_congr_right (q q' : Prop) : q = q' Arrow True q = q' := by
intros; subst q; apply true_arrow
theorem true_arrow_congr (p q q' : Prop) : p = True q = q' Arrow p q = q' := by
intros; subst p q; apply true_arrow
theorem false_arrow (p : Prop) : Arrow False p = True := by
simp [Arrow]; constructor
next => intros; exact .intro
next => intros; contradiction
theorem false_arrow_congr (p q : Prop) : p = False Arrow p q = True := by
intros; subst p; apply false_arrow
theorem arrow_true (α : Sort u) : Arrow α True = True := by
simp [Arrow]; constructor <;> intros <;> exact .intro
theorem arrow_true_congr (α : Sort u) (p : Prop) : p = True Arrow α p = True := by
intros; subst p; apply arrow_true
end Lean
theorem iff_congr {p₁ p₂ q₁ q₂ : Prop} (h₁ : p₁ p₂) (h₂ : q₁ q₂) : (p₁ q₁) (p₂ q₂) :=
Iff.of_eq (propext h₁ propext h₂ rfl)

View File

@@ -14,6 +14,8 @@ public section
namespace Lean.Sym
theorem ne_self (a : α) : (a a) = False := by simp
theorem not_true_eq : (¬ True) = False := by simp
theorem not_false_eq : (¬ False) = True := by simp
theorem ite_cond_congr {α : Sort u} (c : Prop) {inst : Decidable c} (a b : α)
(c' : Prop) {inst' : Decidable c'} (h : c = c') : @ite α c inst a b = @ite α c' inst' a b := by
@@ -46,6 +48,8 @@ theorem UInt32.lt_eq_true (a b : UInt32) (h : decide (a < b) = true) : (a < b) =
theorem UInt64.lt_eq_true (a b : UInt64) (h : decide (a < b) = true) : (a < b) = True := by simp_all
theorem Fin.lt_eq_true (a b : Fin n) (h : decide (a < b) = true) : (a < b) = True := by simp_all
theorem BitVec.lt_eq_true (a b : BitVec n) (h : decide (a < b) = true) : (a < b) = True := by simp_all
theorem String.lt_eq_true (a b : String) (h : decide (a < b) = true) : (a < b) = True := by simp_all
theorem Char.lt_eq_true (a b : Char) (h : decide (a < b) = true) : (a < b) = True := by simp_all
theorem Nat.lt_eq_false (a b : Nat) (h : decide (a < b) = false) : (a < b) = False := by simp_all
theorem Int.lt_eq_false (a b : Int) (h : decide (a < b) = false) : (a < b) = False := by simp_all
@@ -60,6 +64,8 @@ theorem UInt32.lt_eq_false (a b : UInt32) (h : decide (a < b) = false) : (a < b)
theorem UInt64.lt_eq_false (a b : UInt64) (h : decide (a < b) = false) : (a < b) = False := by simp_all
theorem Fin.lt_eq_false (a b : Fin n) (h : decide (a < b) = false) : (a < b) = False := by simp_all
theorem BitVec.lt_eq_false (a b : BitVec n) (h : decide (a < b) = false) : (a < b) = False := by simp_all
theorem String.lt_eq_false (a b : String) (h : decide (a < b) = false) : (a < b) = False := by simp_all
theorem Char.lt_eq_false (a b : Char) (h : decide (a < b) = false) : (a < b) = False := by simp_all
theorem Nat.le_eq_true (a b : Nat) (h : decide (a b) = true) : (a b) = True := by simp_all
theorem Int.le_eq_true (a b : Int) (h : decide (a b) = true) : (a b) = True := by simp_all
@@ -74,6 +80,8 @@ theorem UInt32.le_eq_true (a b : UInt32) (h : decide (a ≤ b) = true) : (a ≤
theorem UInt64.le_eq_true (a b : UInt64) (h : decide (a b) = true) : (a b) = True := by simp_all
theorem Fin.le_eq_true (a b : Fin n) (h : decide (a b) = true) : (a b) = True := by simp_all
theorem BitVec.le_eq_true (a b : BitVec n) (h : decide (a b) = true) : (a b) = True := by simp_all
theorem String.le_eq_true (a b : String) (h : decide (a b) = true) : (a b) = True := by simp_all
theorem Char.le_eq_true (a b : Char) (h : decide (a b) = true) : (a b) = True := by simp_all
theorem Nat.le_eq_false (a b : Nat) (h : decide (a b) = false) : (a b) = False := by simp_all
theorem Int.le_eq_false (a b : Int) (h : decide (a b) = false) : (a b) = False := by simp_all
@@ -88,62 +96,8 @@ theorem UInt32.le_eq_false (a b : UInt32) (h : decide (a ≤ b) = false) : (a
theorem UInt64.le_eq_false (a b : UInt64) (h : decide (a b) = false) : (a b) = False := by simp_all
theorem Fin.le_eq_false (a b : Fin n) (h : decide (a b) = false) : (a b) = False := by simp_all
theorem BitVec.le_eq_false (a b : BitVec n) (h : decide (a b) = false) : (a b) = False := by simp_all
theorem Nat.gt_eq_true (a b : Nat) (h : decide (a > b) = true) : (a > b) = True := by simp_all
theorem Int.gt_eq_true (a b : Int) (h : decide (a > b) = true) : (a > b) = True := by simp_all
theorem Rat.gt_eq_true (a b : Rat) (h : decide (a > b) = true) : (a > b) = True := by simp_all
theorem Int8.gt_eq_true (a b : Int8) (h : decide (a > b) = true) : (a > b) = True := by simp_all
theorem Int16.gt_eq_true (a b : Int16) (h : decide (a > b) = true) : (a > b) = True := by simp_all
theorem Int32.gt_eq_true (a b : Int32) (h : decide (a > b) = true) : (a > b) = True := by simp_all
theorem Int64.gt_eq_true (a b : Int64) (h : decide (a > b) = true) : (a > b) = True := by simp_all
theorem UInt8.gt_eq_true (a b : UInt8) (h : decide (a > b) = true) : (a > b) = True := by simp_all
theorem UInt16.gt_eq_true (a b : UInt16) (h : decide (a > b) = true) : (a > b) = True := by simp_all
theorem UInt32.gt_eq_true (a b : UInt32) (h : decide (a > b) = true) : (a > b) = True := by simp_all
theorem UInt64.gt_eq_true (a b : UInt64) (h : decide (a > b) = true) : (a > b) = True := by simp_all
theorem Fin.gt_eq_true (a b : Fin n) (h : decide (a > b) = true) : (a > b) = True := by simp_all
theorem BitVec.gt_eq_true (a b : BitVec n) (h : decide (a > b) = true) : (a > b) = True := by simp_all
theorem Nat.gt_eq_false (a b : Nat) (h : decide (a > b) = false) : (a > b) = False := by simp_all
theorem Int.gt_eq_false (a b : Int) (h : decide (a > b) = false) : (a > b) = False := by simp_all
theorem Rat.gt_eq_false (a b : Rat) (h : decide (a > b) = false) : (a > b) = False := by simp_all
theorem Int8.gt_eq_false (a b : Int8) (h : decide (a > b) = false) : (a > b) = False := by simp_all
theorem Int16.gt_eq_false (a b : Int16) (h : decide (a > b) = false) : (a > b) = False := by simp_all
theorem Int32.gt_eq_false (a b : Int32) (h : decide (a > b) = false) : (a > b) = False := by simp_all
theorem Int64.gt_eq_false (a b : Int64) (h : decide (a > b) = false) : (a > b) = False := by simp_all
theorem UInt8.gt_eq_false (a b : UInt8) (h : decide (a > b) = false) : (a > b) = False := by simp_all
theorem UInt16.gt_eq_false (a b : UInt16) (h : decide (a > b) = false) : (a > b) = False := by simp_all
theorem UInt32.gt_eq_false (a b : UInt32) (h : decide (a > b) = false) : (a > b) = False := by simp_all
theorem UInt64.gt_eq_false (a b : UInt64) (h : decide (a > b) = false) : (a > b) = False := by simp_all
theorem Fin.gt_eq_false (a b : Fin n) (h : decide (a > b) = false) : (a > b) = False := by simp_all
theorem BitVec.gt_eq_false (a b : BitVec n) (h : decide (a > b) = false) : (a > b) = False := by simp_all
theorem Nat.ge_eq_true (a b : Nat) (h : decide (a b) = true) : (a b) = True := by simp_all
theorem Int.ge_eq_true (a b : Int) (h : decide (a b) = true) : (a b) = True := by simp_all
theorem Rat.ge_eq_true (a b : Rat) (h : decide (a b) = true) : (a b) = True := by simp_all
theorem Int8.ge_eq_true (a b : Int8) (h : decide (a b) = true) : (a b) = True := by simp_all
theorem Int16.ge_eq_true (a b : Int16) (h : decide (a b) = true) : (a b) = True := by simp_all
theorem Int32.ge_eq_true (a b : Int32) (h : decide (a b) = true) : (a b) = True := by simp_all
theorem Int64.ge_eq_true (a b : Int64) (h : decide (a b) = true) : (a b) = True := by simp_all
theorem UInt8.ge_eq_true (a b : UInt8) (h : decide (a b) = true) : (a b) = True := by simp_all
theorem UInt16.ge_eq_true (a b : UInt16) (h : decide (a b) = true) : (a b) = True := by simp_all
theorem UInt32.ge_eq_true (a b : UInt32) (h : decide (a b) = true) : (a b) = True := by simp_all
theorem UInt64.ge_eq_true (a b : UInt64) (h : decide (a b) = true) : (a b) = True := by simp_all
theorem Fin.ge_eq_true (a b : Fin n) (h : decide (a b) = true) : (a b) = True := by simp_all
theorem BitVec.ge_eq_true (a b : BitVec n) (h : decide (a b) = true) : (a b) = True := by simp_all
theorem Nat.ge_eq_false (a b : Nat) (h : decide (a b) = false) : (a b) = False := by simp_all
theorem Int.ge_eq_false (a b : Int) (h : decide (a b) = false) : (a b) = False := by simp_all
theorem Rat.ge_eq_false (a b : Rat) (h : decide (a b) = false) : (a b) = False := by simp_all
theorem Int8.ge_eq_false (a b : Int8) (h : decide (a b) = false) : (a b) = False := by simp_all
theorem Int16.ge_eq_false (a b : Int16) (h : decide (a b) = false) : (a b) = False := by simp_all
theorem Int32.ge_eq_false (a b : Int32) (h : decide (a b) = false) : (a b) = False := by simp_all
theorem Int64.ge_eq_false (a b : Int64) (h : decide (a b) = false) : (a b) = False := by simp_all
theorem UInt8.ge_eq_false (a b : UInt8) (h : decide (a b) = false) : (a b) = False := by simp_all
theorem UInt16.ge_eq_false (a b : UInt16) (h : decide (a b) = false) : (a b) = False := by simp_all
theorem UInt32.ge_eq_false (a b : UInt32) (h : decide (a b) = false) : (a b) = False := by simp_all
theorem UInt64.ge_eq_false (a b : UInt64) (h : decide (a b) = false) : (a b) = False := by simp_all
theorem Fin.ge_eq_false (a b : Fin n) (h : decide (a b) = false) : (a b) = False := by simp_all
theorem BitVec.ge_eq_false (a b : BitVec n) (h : decide (a b) = false) : (a b) = False := by simp_all
theorem String.le_eq_false (a b : String) (h : decide (a b) = false) : (a b) = False := by simp_all
theorem Char.le_eq_false (a b : Char) (h : decide (a b) = false) : (a b) = False := by simp_all
theorem Nat.eq_eq_true (a b : Nat) (h : decide (a = b) = true) : (a = b) = True := by simp_all
theorem Int.eq_eq_true (a b : Int) (h : decide (a = b) = true) : (a = b) = True := by simp_all
@@ -158,6 +112,8 @@ theorem UInt32.eq_eq_true (a b : UInt32) (h : decide (a = b) = true) : (a = b) =
theorem UInt64.eq_eq_true (a b : UInt64) (h : decide (a = b) = true) : (a = b) = True := by simp_all
theorem Fin.eq_eq_true (a b : Fin n) (h : decide (a = b) = true) : (a = b) = True := by simp_all
theorem BitVec.eq_eq_true (a b : BitVec n) (h : decide (a = b) = true) : (a = b) = True := by simp_all
theorem String.eq_eq_true (a b : String) (h : decide (a = b) = true) : (a = b) = True := by simp_all
theorem Char.eq_eq_true (a b : Char) (h : decide (a = b) = true) : (a = b) = True := by simp_all
theorem Nat.eq_eq_false (a b : Nat) (h : decide (a = b) = false) : (a = b) = False := by simp_all
theorem Int.eq_eq_false (a b : Int) (h : decide (a = b) = false) : (a = b) = False := by simp_all
@@ -172,34 +128,8 @@ theorem UInt32.eq_eq_false (a b : UInt32) (h : decide (a = b) = false) : (a = b)
theorem UInt64.eq_eq_false (a b : UInt64) (h : decide (a = b) = false) : (a = b) = False := by simp_all
theorem Fin.eq_eq_false (a b : Fin n) (h : decide (a = b) = false) : (a = b) = False := by simp_all
theorem BitVec.eq_eq_false (a b : BitVec n) (h : decide (a = b) = false) : (a = b) = False := by simp_all
theorem Nat.ne_eq_true (a b : Nat) (h : decide (a b) = true) : (a b) = True := by simp_all
theorem Int.ne_eq_true (a b : Int) (h : decide (a b) = true) : (a b) = True := by simp_all
theorem Rat.ne_eq_true (a b : Rat) (h : decide (a b) = true) : (a b) = True := by simp_all
theorem Int8.ne_eq_true (a b : Int8) (h : decide (a b) = true) : (a b) = True := by simp_all
theorem Int16.ne_eq_true (a b : Int16) (h : decide (a b) = true) : (a b) = True := by simp_all
theorem Int32.ne_eq_true (a b : Int32) (h : decide (a b) = true) : (a b) = True := by simp_all
theorem Int64.ne_eq_true (a b : Int64) (h : decide (a b) = true) : (a b) = True := by simp_all
theorem UInt8.ne_eq_true (a b : UInt8) (h : decide (a b) = true) : (a b) = True := by simp_all
theorem UInt16.ne_eq_true (a b : UInt16) (h : decide (a b) = true) : (a b) = True := by simp_all
theorem UInt32.ne_eq_true (a b : UInt32) (h : decide (a b) = true) : (a b) = True := by simp_all
theorem UInt64.ne_eq_true (a b : UInt64) (h : decide (a b) = true) : (a b) = True := by simp_all
theorem Fin.ne_eq_true (a b : Fin n) (h : decide (a b) = true) : (a b) = True := by simp_all
theorem BitVec.ne_eq_true (a b : BitVec n) (h : decide (a b) = true) : (a b) = True := by simp_all
theorem Nat.ne_eq_false (a b : Nat) (h : decide (a b) = false) : (a b) = False := by simp_all
theorem Int.ne_eq_false (a b : Int) (h : decide (a b) = false) : (a b) = False := by simp_all
theorem Rat.ne_eq_false (a b : Rat) (h : decide (a b) = false) : (a b) = False := by simp_all
theorem Int8.ne_eq_false (a b : Int8) (h : decide (a b) = false) : (a b) = False := by simp_all
theorem Int16.ne_eq_false (a b : Int16) (h : decide (a b) = false) : (a b) = False := by simp_all
theorem Int32.ne_eq_false (a b : Int32) (h : decide (a b) = false) : (a b) = False := by simp_all
theorem Int64.ne_eq_false (a b : Int64) (h : decide (a b) = false) : (a b) = False := by simp_all
theorem UInt8.ne_eq_false (a b : UInt8) (h : decide (a b) = false) : (a b) = False := by simp_all
theorem UInt16.ne_eq_false (a b : UInt16) (h : decide (a b) = false) : (a b) = False := by simp_all
theorem UInt32.ne_eq_false (a b : UInt32) (h : decide (a b) = false) : (a b) = False := by simp_all
theorem UInt64.ne_eq_false (a b : UInt64) (h : decide (a b) = false) : (a b) = False := by simp_all
theorem Fin.ne_eq_false (a b : Fin n) (h : decide (a b) = false) : (a b) = False := by simp_all
theorem BitVec.ne_eq_false (a b : BitVec n) (h : decide (a b) = false) : (a b) = False := by simp_all
theorem String.eq_eq_false (a b : String) (h : decide (a = b) = false) : (a = b) = False := by simp_all
theorem Char.eq_eq_false (a b : Char) (h : decide (a = b) = false) : (a = b) = False := by simp_all
theorem Nat.dvd_eq_true (a b : Nat) (h : decide (a b) = true) : (a b) = True := by simp_all
theorem Int.dvd_eq_true (a b : Int) (h : decide (a b) = true) : (a b) = True := by simp_all

View File

@@ -518,14 +518,13 @@ syntax location := withPosition(ppGroup(" at" (locationWildcard <|> locationHyp)
assuming these are definitionally equal.
* `change t' at h` will change hypothesis `h : t` to have type `t'`, assuming
assuming `t` and `t'` are definitionally equal.
-/
syntax (name := change) "change " term (location)? : tactic
/--
* `change a with b` will change occurrences of `a` to `b` in the goal,
assuming `a` and `b` are definitionally equal.
* `change a with b at h` similarly changes `a` to `b` in the type of hypothesis `h`.
-/
syntax (name := change) "change " term (location)? : tactic
@[tactic_alt change]
syntax (name := changeWith) "change " term " with " term (location)? : tactic
/--
@@ -905,8 +904,13 @@ The tactic supports all the same syntax variants and options as the `let` term.
-/
macro "let" c:letConfig d:letDecl : tactic => `(tactic| refine_lift let $c:letConfig $d:letDecl; ?_)
/-- `let rec f : t := e` adds a recursive definition `f` to the current goal.
The syntax is the same as term-mode `let rec`. -/
/--
`let rec f : t := e` adds a recursive definition `f` to the current goal.
The syntax is the same as term-mode `let rec`.
The tactic supports all the same syntax variants and options as the `let` term.
-/
@[tactic_name "let rec"]
syntax (name := letrec) withPosition(atomic("let " &"rec ") letRecDecls) : tactic
macro_rules
| `(tactic| let rec $d) => `(tactic| refine_lift let rec $d; ?_)
@@ -1212,22 +1216,6 @@ while `congr 2` produces the intended `⊢ x + y = y + x`.
syntax (name := congr) "congr" (ppSpace num)? : tactic
/--
In tactic mode, `if h : t then tac1 else tac2` can be used as alternative syntax for:
```
by_cases h : t
· tac1
· tac2
```
It performs case distinction on `h : t` or `h : ¬t` and `tac1` and `tac2` are the subproofs.
You can use `?_` or `_` for either subproof to delay the goal to after the tactic, but
if a tactic sequence is provided for `tac1` or `tac2` then it will require the goal to be closed
by the end of the block.
-/
syntax (name := tacDepIfThenElse)
ppRealGroup(ppRealFill(ppIndent("if " binderIdent " : " term " then") ppSpace matchRhsTacticSeq)
ppDedent(ppSpace) ppRealFill("else " matchRhsTacticSeq)) : tactic
/--
In tactic mode, `if t then tac1 else tac2` is alternative syntax for:
@@ -1236,16 +1224,34 @@ by_cases t
· tac1
· tac2
```
It performs case distinction on `h† : t` or `h† : ¬t`, where `h†` is an anonymous
hypothesis, and `tac1` and `tac2` are the subproofs. (It doesn't actually use
nondependent `if`, since this wouldn't add anything to the context and hence would be
useless for proving theorems. To actually insert an `ite` application use
`refine if t then ?_ else ?_`.)
It performs case distinction on `h† : t` or `h† : ¬t`, where `h†` is an anonymous hypothesis, and
`tac1` and `tac2` are the subproofs. (It doesn't actually use nondependent `if`, since this wouldn't
add anything to the context and hence would be useless for proving theorems. To actually insert an
`ite` application use `refine if t then ?_ else ?_`.)
The assumptions in each subgoal can be named. `if h : t then tac1 else tac2` can be used as
alternative syntax for:
```
by_cases h : t
· tac1
· tac2
```
It performs case distinction on `h : t` or `h : ¬t`.
You can use `?_` or `_` for either subproof to delay the goal to after the tactic, but
if a tactic sequence is provided for `tac1` or `tac2` then it will require the goal to be closed
by the end of the block.
-/
syntax (name := tacIfThenElse)
ppRealGroup(ppRealFill(ppIndent("if " term " then") ppSpace matchRhsTacticSeq)
ppDedent(ppSpace) ppRealFill("else " matchRhsTacticSeq)) : tactic
@[tactic_alt tacIfThenElse]
syntax (name := tacDepIfThenElse)
ppRealGroup(ppRealFill(ppIndent("if " binderIdent " : " term " then") ppSpace matchRhsTacticSeq)
ppDedent(ppSpace) ppRealFill("else " matchRhsTacticSeq)) : tactic
/--
The tactic `nofun` is shorthand for `exact nofun`: it introduces the assumptions, then performs an
empty pattern match, closing the goal if the introduced pattern is impossible.

View File

@@ -27,6 +27,7 @@ public import Lean.Compiler.IR.ToIR
public import Lean.Compiler.IR.ToIRType
public import Lean.Compiler.IR.Meta
public import Lean.Compiler.IR.Toposort
public import Lean.Compiler.IR.SimpleGroundExpr
-- The following imports are not required by the compiler. They are here to ensure that there
-- are no orphaned modules.
@@ -71,6 +72,7 @@ def compile (decls : Array Decl) : CompilerM (Array Decl) := do
logDecls `result decls
checkDecls decls
decls toposortDecls decls
decls.forM Decl.detectSimpleGround
addDecls decls
inferMeta decls
return decls

View File

@@ -186,7 +186,7 @@ def getDecl (n : Name) : CompilerM Decl := do
def findLocalDecl (n : Name) : CompilerM (Option Decl) :=
return declMapExt.getState ( getEnv) |>.find? n
/-- Returns the list of IR declarations in declaration order. -/
/-- Returns the list of IR declarations in reverse declaration order. -/
def getDecls (env : Environment) : List Decl :=
declMapExt.getEntries env

View File

@@ -12,6 +12,7 @@ public import Lean.Compiler.IR.NormIds
public import Lean.Compiler.IR.SimpCase
public import Lean.Compiler.IR.Boxing
public import Lean.Compiler.ModPkgExt
import Lean.Compiler.IR.SimpleGroundExpr
public section
@@ -76,6 +77,26 @@ def toCType : IRType → String
| IRType.struct _ _ => panic! "not implemented yet"
| IRType.union _ _ => panic! "not implemented yet"
def toHexDigit (c : Nat) : String :=
String.singleton c.digitChar
def quoteString (s : String) : String :=
let q := "\"";
let q := s.foldl
(fun q c => q ++
if c == '\n' then "\\n"
else if c == '\r' then "\\r"
else if c == '\t' then "\\t"
else if c == '\\' then "\\\\"
else if c == '\"' then "\\\""
else if c == '?' then "\\?" -- avoid trigraphs
else if c.toNat <= 31 then
"\\x" ++ toHexDigit (c.toNat / 16) ++ toHexDigit (c.toNat % 16)
-- TODO(Leo): we should use `\unnnn` for escaping unicode characters.
else String.singleton c)
q;
q ++ "\""
def throwInvalidExportName {α : Type} (n : Name) : M α :=
throw s!"invalid export name '{n}'"
@@ -101,30 +122,160 @@ def toCInitName (n : Name) : M String := do
def emitCInitName (n : Name) : M Unit :=
toCInitName n >>= emit
def ctorScalarSizeStr (usize : Nat) (ssize : Nat) : String :=
if usize == 0 then toString ssize
else if ssize == 0 then s!"sizeof(size_t)*{usize}"
else s!"sizeof(size_t)*{usize} + {ssize}"
structure GroundState where
auxCounter : Nat := 0
abbrev GroundM := StateT GroundState M
partial def emitGroundDecl (decl : Decl) (cppBaseName : String) : M Unit := do
let some ground := getSimpleGroundExpr ( getEnv) decl.name | unreachable!
discard <| compileGround ground |>.run {}
where
compileGround (e : SimpleGroundExpr) : GroundM Unit := do
let valueName compileGroundToValue e
let declPrefix := if isClosedTermName ( getEnv) decl.name then "static" else "LEAN_EXPORT"
emitLn <| s!"{declPrefix} const lean_object* {cppBaseName} = (const lean_object*)&{valueName};"
compileGroundToValue (e : SimpleGroundExpr) : GroundM String := do
match e with
| .ctor cidx objArgs usizeArgs scalarArgs =>
let val compileCtor cidx objArgs usizeArgs scalarArgs
mkValueCLit "lean_ctor_object" val
| .string data =>
let leanStringTag := 249
let header := mkHeader 0 0 leanStringTag
let size := data.utf8ByteSize + 1 -- null byte
let length := data.length
let data : String := quoteString data
mkValueCLit
"lean_string_object"
s!"\{.m_header = {header}, .m_size = {size}, .m_capacity = {size}, .m_length = {length}, .m_data = {data}}"
| .pap func args =>
let numFixed := args.size
let leanClosureTag := 245
let header := mkHeader s!"sizeof(lean_closure_object) + sizeof(void*)*{numFixed}" 0 leanClosureTag
let funPtr := s!"(void*){← toCName func}"
let arity := ( getDecl func).params.size
let args args.mapM groundArgToCLit
let argArray := String.intercalate "," args.toList
mkValueCLit
"lean_closure_object"
s!"\{.m_header = {header}, .m_fun = {funPtr}, .m_arity = {arity}, .m_num_fixed = {numFixed}, .m_objs = \{{argArray}} }"
| .nameMkStr args =>
let obj groundNameMkStrToCLit args
mkValueCLit "lean_ctor_object" obj
| .reference refDecl => findValueDecl refDecl
mkValueName (name : String) : String :=
name ++ "_value"
mkAuxValueName (name : String) (idx : Nat) : String :=
mkValueName name ++ s!"_aux_{idx}"
mkAuxDecl (type value : String) : GroundM String := do
let idx modifyGet fun s => (s.auxCounter, { s with auxCounter := s.auxCounter + 1 })
let name := mkAuxValueName cppBaseName idx
emitLn <| s!"static const {type} {name} = {value};"
return name
mkValueCLit (type value : String) : GroundM String := do
let valueName := mkValueName cppBaseName
emitLn <| s!"static const {type} {valueName} = {value};"
return valueName
groundNameMkStrToCLit (args : Array (Name × UInt64)) : GroundM String := do
assert! args.size > 0
if args.size == 1 then
let (ref, hash) := args[0]!
let hash := uint64ToByteArrayLE hash
compileCtor 1 #[.tagged 0, .reference ref] #[] hash
else
let (ref, hash) := args.back!
let args := args.pop
let lit groundNameMkStrToCLit args
let auxName mkAuxDecl "lean_ctor_object" lit
let hash := uint64ToByteArrayLE hash
compileCtor 1 #[.rawReference auxName, .reference ref] #[] hash
groundArgToCLit (a : SimpleGroundArg) : GroundM String := do
match a with
| .tagged val => return s!"((lean_object*)(((size_t)({val}) << 1) | 1))"
| .reference decl => return s!"((lean_object*)&{← findValueDecl decl})"
| .rawReference decl => return s!"((lean_object*)&{decl})"
findValueDecl (decl : Name) : GroundM String := do
let mut decl := decl
while true do
if let some (.reference ref) := getSimpleGroundExpr ( getEnv) decl then
decl := ref
else
break
return mkValueName ( toCName decl)
compileCtor (cidx : Nat) (objArgs : Array SimpleGroundArg) (usizeArgs : Array USize)
(scalarArgs : Array UInt8) : GroundM String := do
let header := mkCtorHeader objArgs.size usizeArgs.size scalarArgs.size cidx
let objArgs objArgs.mapM groundArgToCLit
let usizeArgs : Array String := usizeArgs.map fun val => s!"(lean_object*)(size_t)({val}ULL)"
assert! scalarArgs.size % 8 == 0
let scalarArgs : Array String := Id.run do
let chunks := scalarArgs.size / 8
let mut packed := Array.emptyWithCapacity chunks
for idx in 0...chunks do
let b1 := scalarArgs[idx * 8]!
let b2 := scalarArgs[idx * 8 + 1]!
let b3 := scalarArgs[idx * 8 + 2]!
let b4 := scalarArgs[idx * 8 + 3]!
let b5 := scalarArgs[idx * 8 + 4]!
let b6 := scalarArgs[idx * 8 + 5]!
let b7 := scalarArgs[idx * 8 + 6]!
let b8 := scalarArgs[idx * 8 + 7]!
let lit := s!"LEAN_SCALAR_PTR_LITERAL({b1}, {b2}, {b3}, {b4}, {b5}, {b6}, {b7}, {b8})"
packed := packed.push lit
return packed
let argArray := String.intercalate "," (objArgs ++ usizeArgs ++ scalarArgs).toList
return s!"\{.m_header = {header}, .m_objs = \{{argArray}}}"
mkCtorHeader (numObjs : Nat) (usize : Nat) (ssize : Nat) (tag : Nat) : String :=
let size := s!"sizeof(lean_ctor_object) + sizeof(void*)*{numObjs} + {ctorScalarSizeStr usize ssize}"
mkHeader size numObjs tag
mkHeader {α : Type} [ToString α] (csSz : α) (other : Nat) (tag : Nat) : String :=
s!"\{.m_rc = 0, .m_cs_sz = {csSz}, .m_other = {other}, .m_tag = {tag}}"
def emitFnDeclAux (decl : Decl) (cppBaseName : String) (isExternal : Bool) : M Unit := do
let ps := decl.params
let env getEnv
if ps.isEmpty then
if isExternal then emit "extern "
else if isClosedTermName env decl.name then emit "static "
else emit "LEAN_EXPORT "
if isSimpleGroundDecl env decl.name then
emitGroundDecl decl cppBaseName
else
if !isExternal then emit "LEAN_EXPORT "
emit (toCType decl.resultType ++ " " ++ cppBaseName)
unless ps.isEmpty do
emit "("
-- We omit void parameters, note that they are guaranteed not to occur in boxed functions
let ps := ps.filter (fun p => !p.ty.isVoid)
-- We omit erased parameters for extern constants
let ps := if isExternC env decl.name then ps.filter (fun p => !p.ty.isErased) else ps
if ps.size > closureMaxArgs && isBoxedName decl.name then
emit "lean_object**"
if ps.isEmpty then
if isExternal then emit "extern "
else if isClosedTermName env decl.name then emit "static "
else emit "LEAN_EXPORT "
else
ps.size.forM fun i _ => do
if i > 0 then emit ", "
emit (toCType ps[i].ty)
emit ")"
emitLn ";"
if !isExternal then emit "LEAN_EXPORT "
emit (toCType decl.resultType ++ " " ++ cppBaseName)
unless ps.isEmpty do
emit "("
-- We omit void parameters, note that they are guaranteed not to occur in boxed functions
let ps := ps.filter (fun p => !p.ty.isVoid)
-- We omit erased parameters for extern constants
let ps := if isExternC env decl.name then ps.filter (fun p => !p.ty.isErased) else ps
if ps.size > closureMaxArgs && isBoxedName decl.name then
emit "lean_object**"
else
ps.size.forM fun i _ => do
if i > 0 then emit ", "
emit (toCType ps[i].ty)
emit ")"
emitLn ";"
def emitFnDecl (decl : Decl) (isExternal : Bool) : M Unit := do
let cppBaseName toCName decl.name
@@ -137,10 +288,9 @@ def emitExternDeclAux (decl : Decl) (cNameStr : String) : M Unit := do
def emitFnDecls : M Unit := do
let env getEnv
let decls := getDecls env
let decls := getDecls env |>.reverse
let modDecls : NameSet := decls.foldl (fun s d => s.insert d.name) {}
let usedDecls : NameSet := decls.foldl (fun s d => collectUsedDecls env d (s.insert d.name)) {}
let usedDecls := usedDecls.toList
let usedDecls := collectUsedDecls env decls
usedDecls.forM fun n => do
let decl getDecl n;
match getExternNameFor env `c decl.name with
@@ -353,10 +503,8 @@ def emitArgs (ys : Array Arg) : M Unit :=
if i > 0 then emit ", "
emitArg ys[i]
def emitCtorScalarSize (usize : Nat) (ssize : Nat) : M Unit := do
if usize == 0 then emit ssize
else if ssize == 0 then emit "sizeof(size_t)*"; emit usize
else emit "sizeof(size_t)*"; emit usize; emit " + "; emit ssize
def emitCtorScalarSize (usize : Nat) (ssize : Nat) : M Unit :=
emit <| ctorScalarSizeStr usize ssize
def emitAllocCtor (c : CtorInfo) : M Unit := do
emit "lean_alloc_ctor("; emit c.cidx; emit ", "; emit c.size; emit ", "
@@ -435,12 +583,18 @@ def emitExternCall (f : FunId) (ps : Array Param) (extData : ExternAttrData) (ys
| some (ExternEntry.inline _ pat) => do emit (expandExternPattern pat (toStringArgs ys)); emitLn ";"
| _ => throw s!"failed to emit extern application '{f}'"
def emitLeanFunReference (f : FunId) : M Unit := do
if isSimpleGroundDecl ( getEnv) f then
emit s!"((lean_object*)({← toCName f}))"
else
emitCName f
def emitFullApp (z : VarId) (f : FunId) (ys : Array Arg) : M Unit := do
emitLhs z
let decl getDecl f
match decl with
| .fdecl (xs := ps) .. | .extern (xs := ps) (ext := { entries := [.opaque], .. }) .. =>
emitCName f
emitLeanFunReference f
if ys.size > 0 then
let (ys, _) := ys.zip ps |>.filter (fun (_, p) => !p.ty.isVoid) |>.unzip
emit "("; emitArgs ys; emit ")"
@@ -482,26 +636,6 @@ def emitUnbox (z : VarId) (t : IRType) (x : VarId) : M Unit := do
def emitIsShared (z : VarId) (x : VarId) : M Unit := do
emitLhs z; emit "!lean_is_exclusive("; emit x; emitLn ");"
def toHexDigit (c : Nat) : String :=
String.singleton c.digitChar
def quoteString (s : String) : String :=
let q := "\"";
let q := s.foldl
(fun q c => q ++
if c == '\n' then "\\n"
else if c == '\r' then "\\r"
else if c == '\t' then "\\t"
else if c == '\\' then "\\\\"
else if c == '\"' then "\\\""
else if c == '?' then "\\?" -- avoid trigraphs
else if c.toNat <= 31 then
"\\x" ++ toHexDigit (c.toNat / 16) ++ toHexDigit (c.toNat % 16)
-- TODO(Leo): we should use `\unnnn` for escaping unicode characters.
else String.singleton c)
q;
q ++ "\""
def emitNumLit (t : IRType) (v : Nat) : M Unit := do
if t.isObj then
if v < UInt32.size then
@@ -670,7 +804,7 @@ def emitDeclAux (d : Decl) : M Unit := do
let env getEnv
let (_, jpMap) := mkVarJPMaps d
withReader (fun ctx => { ctx with jpMap := jpMap }) do
unless hasInitAttr env d.name do
unless hasInitAttr env d.name || isSimpleGroundDecl env d.name do
match d with
| .fdecl (f := f) (xs := xs) (type := t) (body := b) .. =>
let baseName toCName f;
@@ -749,7 +883,8 @@ def emitDeclInit (d : Decl) : M Unit := do
if getBuiltinInitFnNameFor? env d.name |>.isSome then
emit "}"
| _ =>
emitCName n; emit " = "; emitCInitName n; emitLn "();"; emitMarkPersistent d n
if !isSimpleGroundDecl env d.name then
emitCName n; emit " = "; emitCInitName n; emitLn "();"; emitMarkPersistent d n
def emitInitFn : M Unit := do
let env getEnv

View File

@@ -31,6 +31,7 @@ time. These changes can likely be done similar to the ones in EmitC:
- function decls need to be fixed
- full applications need to be fixed
- tail calls need to be fixed
- closed term static initializers
-/
def leanMainFn := "_lean_main"
@@ -537,14 +538,12 @@ def emitFnDecls : M llvmctx Unit := do
let env getEnv
let decls := getDecls env
let modDecls : NameSet := decls.foldl (fun s d => s.insert d.name) {}
let usedDecls : NameSet := decls.foldl (fun s d => collectUsedDecls env d (s.insert d.name)) {}
let usedDecls := usedDecls.toList
for n in usedDecls do
let decl getDecl n
let usedDecls := collectUsedDecls env decls
usedDecls.forM fun n => do
let decl getDecl n;
match getExternNameFor env `c decl.name with
| some cName => emitExternDeclAux decl cName
| none => emitFnDecl decl (!modDecls.contains n)
return ()
def emitLhsSlot_ (x : VarId) : M llvmctx (LLVM.LLVMType llvmctx × LLVM.Value llvmctx) := do
let state get

View File

@@ -25,10 +25,19 @@ def usesModuleFrom (env : Environment) (modulePrefix : Name) : Bool :=
namespace CollectUsedDecls
abbrev M := ReaderT Environment (StateM NameSet)
structure State where
set : NameSet := {}
order : Array Name := #[]
abbrev M := ReaderT Environment (StateM State)
@[inline] def collect (f : FunId) : M Unit :=
modify fun s => s.insert f
modify fun { set, order } =>
let (contained, set) := set.containsThenInsert f
if !contained then
{ set, order := order.push f }
else
{ set, order }
partial def collectFnBody : FnBody M Unit
| .vdecl _ _ v b =>
@@ -46,14 +55,19 @@ def collectInitDecl (fn : Name) : M Unit := do
| some initFn => collect initFn
| _ => pure ()
def collectDecl : Decl M NameSet
| .fdecl (f := f) (body := b) .. => collectInitDecl f *> CollectUsedDecls.collectFnBody b *> get
| .extern (f := f) .. => collectInitDecl f *> get
def collectDecl : Decl M Unit
| .fdecl (f := f) (body := b) .. => collectInitDecl f *> CollectUsedDecls.collectFnBody b
| .extern (f := f) .. => collectInitDecl f
def collectDeclLoop (decls : List Decl) : M Unit := do
decls.forM fun decl => do
collectDecl decl
collect decl.name
end CollectUsedDecls
def collectUsedDecls (env : Environment) (decl : Decl) (used : NameSet := {}) : NameSet :=
(CollectUsedDecls.collectDecl decl env).run' used
def collectUsedDecls (env : Environment) (decls : List Decl) : Array Name :=
(CollectUsedDecls.collectDeclLoop decls env).run {} |>.snd.order
abbrev VarTypeMap := Std.HashMap VarId IRType
abbrev JPParamsMap := Std.HashMap JoinPointId (Array Param)

View File

@@ -0,0 +1,355 @@
/-
Copyright (c) 2026 Lean FRO, LLC. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Henrik Böving
-/
module
prelude
public import Lean.Compiler.IR.CompilerM
public import Lean.EnvExtension
import Lean.Compiler.ClosedTermCache
/-!
This module contains logic for detecting simple ground expressions that can be extracted into
statically initializable variables. To do this it attempts to compile declarations into
a simple language of expressions, `SimpleGroundExpr`. If this attempt succeeds it stores the result
in an environment extension, accessible through `getSimpleGroundExpr`. Later on the code emission
step can reference this environment extension to generate static initializers for the respective
declaration.
-/
namespace Lean
namespace IR
/--
An argument to a `SimpleGroundExpr`. They get compiled to `lean_object*` in various ways.
-/
public inductive SimpleGroundArg where
/--
A simple tagged literal.
-/
| tagged (val : Nat)
/--
A reference to another declaration that was marked as a simple ground expression. This gets
compiled to a reference to the mangled version of the name.
-/
| reference (n : Name)
/--
A reference directly to a raw C name. This gets compiled to a reference to the name directly.
-/
| rawReference (s : String)
deriving Inhabited
/--
A simple ground expression that can be turned into a static initializer.
-/
public inductive SimpleGroundExpr where
/--
Represents a `lean_ctor_object`. Crucially the `scalarArgs` array must have a size that is a
multiple of 8.
-/
| ctor (cidx : Nat) (objArgs : Array SimpleGroundArg) (usizeArgs : Array USize) (scalarArgs : Array UInt8)
/--
A string literal, represented by a `lean_string_object`.
-/
| string (data : String)
/--
A partial application, represented by a `lean_closure_object`.
-/
| pap (func : FunId) (args : Array SimpleGroundArg)
/--
An application of `Lean.Name.mkStrX`. This expression is represented separately to ensure that
long name literals get extracted into statically initializable constants. The arguments contain
both the name of the string literal it references as well as the hash of the name up to that
point. This is done to make emitting the literal as simple as possible.
-/
| nameMkStr (args : Array (Name × UInt64))
/--
A reference to another declaration that was marked as a simple ground expression. This gets
compiled to a reference to the mangled version of the name.
-/
| reference (n : Name)
deriving Inhabited
public structure SimpleGroundExtState where
constNames : PHashMap Name SimpleGroundExpr := {}
revNames : List Name := []
deriving Inhabited
builtin_initialize simpleGroundDeclExt : EnvExtension SimpleGroundExtState
registerEnvExtension (pure {}) (asyncMode := .sync)
(replay? := some fun oldState newState _ s =>
let newNames := newState.revNames.take (newState.revNames.length - oldState.revNames.length)
newNames.foldl (init := s) fun s n =>
let g := newState.constNames.find! n
{ s with constNames := s.constNames.insert n g, revNames := n :: s.revNames }
)
/--
Record `declName` as mapping to the simple ground expr `expr`.
-/
public def addSimpleGroundDecl (env : Environment) (declName : Name) (expr : SimpleGroundExpr) :
Environment :=
simpleGroundDeclExt.modifyState env fun s =>
{ s with constNames := s.constNames.insert declName expr, revNames := declName :: s.revNames }
/--
Attempt to fetch a `SimpleGroundExpr` associated with `declName` if it exists.
-/
public def getSimpleGroundExpr (env : Environment) (declName : Name) : Option SimpleGroundExpr :=
(simpleGroundDeclExt.getState env).constNames.find? declName
/--
Like `getSimpleGroundExpr` but recursively traverses `reference` exprs to get to actual ground
values.
-/
public def getSimpleGroundExprWithResolvedRefs (env : Environment) (declName : Name) :
Option SimpleGroundExpr := Id.run do
let mut declName := declName
while true do
let val := getSimpleGroundExpr env declName
match val with
| some (.reference ref) => declName := ref
| other => return other
return none
/--
Check if `declName` is recorded as being a `SimpleGroundExpr`.
-/
public def isSimpleGroundDecl (env : Environment) (declName : Name) : Bool :=
(simpleGroundDeclExt.getState env).constNames.contains declName
public def uint64ToByteArrayLE (n : UInt64) : Array UInt8 :=
#[
n.toUInt8,
(n >>> 0x08).toUInt8,
(n >>> 0x10).toUInt8,
(n >>> 0x18).toUInt8,
(n >>> 0x20).toUInt8,
(n >>> 0x28).toUInt8,
(n >>> 0x30).toUInt8,
(n >>> 0x38).toUInt8,
]
inductive SimpleGroundValue where
| arg (arg : SimpleGroundArg)
| uint8 (val : UInt8)
| uint16 (val : UInt16)
| uint32 (val : UInt32)
| uint64 (val : UInt64)
| usize (val : USize)
deriving Inhabited
structure State where
groundMap : Std.HashMap VarId SimpleGroundValue := {}
abbrev M := StateRefT State $ OptionT CompilerM
/--
Attempt to compile `b` into a `SimpleGroundExpr`. If `b` is not compileable return `none`.
The compiler currently supports the following patterns:
- String literals
- Partial applications with other simple expressions
- Constructor calls with other simple expressions
- `Name.mkStrX`, `Name.str._override`, and `Name.num._override`
- references to other declarations marked as simple ground expressions
-/
partial def compileToSimpleGroundExpr (b : FnBody) : CompilerM (Option SimpleGroundExpr) :=
compileFnBody b |>.run' {} |>.run
where
compileFnBody (b : FnBody) : M SimpleGroundExpr := do
match b with
| .vdecl id _ expr (.ret (.var id')) =>
guard <| id == id'
compileFinalExpr expr
| .vdecl id ty expr b => compileNonFinalExpr id ty expr b
| _ => failure
@[inline]
record (id : VarId) (val : SimpleGroundValue) : M Unit :=
modify fun s => { s with groundMap := s.groundMap.insert id val }
compileNonFinalExpr (id : VarId) (ty : IRType) (expr : Expr) (b : FnBody) : M SimpleGroundExpr := do
match expr with
| .fap c #[] =>
guard <| isSimpleGroundDecl ( getEnv) c
record id (.arg (.reference c))
compileFnBody b
| .lit v =>
match v with
| .num v =>
match ty with
| .tagged =>
guard <| v < 2^31
record id (.arg (.tagged v))
| .uint8 => record id (.uint8 (.ofNat v))
| .uint16 => record id (.uint16 (.ofNat v))
| .uint32 => record id (.uint32 (.ofNat v))
| .uint64 => record id (.uint64 (.ofNat v))
| .usize => record id (.usize (.ofNat v))
| _ => failure
compileFnBody b
| .str .. => failure
| .ctor i objArgs =>
if i.isScalar then
record id (.arg (.tagged i.cidx))
compileFnBody b
else
let objArgs compileArgs objArgs
let usizeArgs := Array.replicate i.usize 0
-- Align to 8 bytes for alignment with lean_object*
let align (v a : Nat) : Nat :=
(v / a) * a + a * (if v % a != 0 then 1 else 0)
let alignedSsize := align i.ssize 8
let ssizeArgs := Array.replicate alignedSsize 0
compileSetChain id i objArgs usizeArgs ssizeArgs b
| _ => failure
compileSetChain (id : VarId) (info : CtorInfo) (objArgs : Array SimpleGroundArg) (usizeArgs : Array USize)
(scalarArgs : Array UInt8) (b : FnBody) : M SimpleGroundExpr := do
match b with
| .ret (.var id') =>
guard <| id == id'
return .ctor info.cidx objArgs usizeArgs scalarArgs
| .sset id' i offset y _ b =>
guard <| id == id'
let i := i - objArgs.size - usizeArgs.size
let offset := i * 8 + offset
let scalarArgs
match ( get).groundMap[y]! with
| .uint8 v =>
let scalarArgs := scalarArgs.set! offset v
pure scalarArgs
| .uint16 v =>
let scalarArgs := scalarArgs.set! offset v.toUInt8
let scalarArgs := scalarArgs.set! (offset + 1) (v >>> 0x08).toUInt8
pure scalarArgs
| .uint32 v =>
let scalarArgs := scalarArgs.set! offset v.toUInt8
let scalarArgs := scalarArgs.set! (offset + 1) (v >>> 0x08).toUInt8
let scalarArgs := scalarArgs.set! (offset + 2) (v >>> 0x10).toUInt8
let scalarArgs := scalarArgs.set! (offset + 3) (v >>> 0x18).toUInt8
pure scalarArgs
| .uint64 v =>
let scalarArgs := scalarArgs.set! offset v.toUInt8
let scalarArgs := scalarArgs.set! (offset + 1) (v >>> 0x08).toUInt8
let scalarArgs := scalarArgs.set! (offset + 2) (v >>> 0x10).toUInt8
let scalarArgs := scalarArgs.set! (offset + 3) (v >>> 0x18).toUInt8
let scalarArgs := scalarArgs.set! (offset + 4) (v >>> 0x20).toUInt8
let scalarArgs := scalarArgs.set! (offset + 5) (v >>> 0x28).toUInt8
let scalarArgs := scalarArgs.set! (offset + 6) (v >>> 0x30).toUInt8
let scalarArgs := scalarArgs.set! (offset + 7) (v >>> 0x38).toUInt8
pure scalarArgs
| _ => failure
compileSetChain id info objArgs usizeArgs scalarArgs b
| .uset id' i y b =>
guard <| id == id'
let i := i - objArgs.size
let .usize v := ( get).groundMap[y]! | failure
let usizeArgs := usizeArgs.set! i v
compileSetChain id info objArgs usizeArgs scalarArgs b
| _ => failure
compileFinalExpr (e : Expr) : M SimpleGroundExpr := do
match e with
| .lit v =>
match v with
| .str v => return .string v
| .num .. => failure
| .ctor i args =>
guard <| i.usize == 0 && i.ssize == 0 && !args.isEmpty
return .ctor i.cidx ( compileArgs args) #[] #[]
| .fap ``Name.num._override args =>
let pre compileArg args[0]!
let .tagged i compileArg args[1]! | failure
let name := Name.num ( interpNameLiteral pre) i
let hash := name.hash
return .ctor 2 #[pre, .tagged i] #[] (uint64ToByteArrayLE hash)
| .fap ``Name.str._override args =>
let pre compileArg args[0]!
let (ref, str) compileStrArg args[1]!
let name := Name.str ( interpNameLiteral pre) str
let hash := name.hash
return .ctor 1 #[pre, .reference ref] #[] (uint64ToByteArrayLE hash)
| .fap ``Name.mkStr1 args
| .fap ``Name.mkStr2 args
| .fap ``Name.mkStr3 args
| .fap ``Name.mkStr4 args
| .fap ``Name.mkStr5 args
| .fap ``Name.mkStr6 args
| .fap ``Name.mkStr7 args
| .fap ``Name.mkStr8 args =>
let mut nameAcc := Name.anonymous
let mut processedArgs := Array.emptyWithCapacity args.size
for arg in args do
let (ref, str) compileStrArg arg
nameAcc := .str nameAcc str
processedArgs := processedArgs.push (ref, nameAcc.hash)
return .nameMkStr processedArgs
| .pap c ys => return .pap c ( compileArgs ys)
| .fap c #[] =>
guard <| isSimpleGroundDecl ( getEnv) c
return .reference c
| _ => failure
compileArg (arg : Arg) : M SimpleGroundArg := do
match arg with
| .var var =>
let .arg arg := ( get).groundMap[var]! | failure
return arg
| .erased => return .tagged 0
compileArgs (args : Array Arg) : M (Array SimpleGroundArg) := do
args.mapM compileArg
compileStrArg (arg : Arg) : M (Name × String) := do
let .var var := arg | failure
let (.arg (.reference ref)) := ( get).groundMap[var]! | failure
let some (.string val) := getSimpleGroundExprWithResolvedRefs ( getEnv) ref | failure
return (ref, val)
interpStringLiteral (arg : SimpleGroundArg) : M String := do
let .reference ref := arg | failure
let some (.string val) := getSimpleGroundExprWithResolvedRefs ( getEnv) ref | failure
return val
interpNameLiteral (arg : SimpleGroundArg) : M Name := do
match arg with
| .tagged 0 => return .anonymous
| .reference ref =>
match getSimpleGroundExprWithResolvedRefs ( getEnv) ref with
| some (.ctor 1 #[pre, .reference ref] _ _) =>
let pre interpNameLiteral pre
let str interpStringLiteral (.reference ref)
return .str pre str
| some (.ctor 2 #[pre, .tagged i] _ _) =>
let pre interpNameLiteral pre
return .num pre i
| some (.nameMkStr args) =>
args.foldlM (init := .anonymous) fun acc (ref, _) => do
let part interpStringLiteral (.reference ref)
return .str acc part
| _ => failure
| _ => failure
/--
Detect whether `d` can be compiled to a `SimpleGroundExpr`. If it can record the associated
`SimpleGroundExpr` into the environment for later processing by code emission.
-/
public def Decl.detectSimpleGround (d : Decl) : CompilerM Unit := do
let .fdecl (body := body) (xs := params) (type := type) .. := d | return ()
if type.isPossibleRef && params.isEmpty then
if let some groundExpr compileToSimpleGroundExpr body then
trace[compiler.ir.simple_ground] m!"Marked {d.name} as simple ground expr"
modifyEnv fun env => addSimpleGroundDecl env d.name groundExpr
builtin_initialize registerTraceClass `compiler.ir.simple_ground (inherited := true)
end IR
end Lean

View File

@@ -7,6 +7,7 @@ module
prelude
public import Lean.Attributes
import Lean.Meta.RecExt
public section
@@ -33,14 +34,8 @@ private def isValidMacroInline (declName : Name) : CoreM Bool := do
unless info.all.length = 1 do
-- We do not allow `[macro_inline]` attributes at mutual recursive definitions
return false
let env getEnv
let isRec (declName' : Name) : Bool :=
isBRecOnRecursor env declName' ||
declName' == ``WellFounded.fix ||
declName' == ``WellFounded.Nat.fix ||
declName' == declName ++ `_unary -- Auxiliary declaration created by `WF` module
if Option.isSome <| info.value.find? fun e => e.isConst && isRec e.constName! then
-- It contains a `brecOn` or `WellFounded.fix` application. So, it should be recursvie
if ( Meta.isRecursiveDefinition declName) then
-- It is recursive
return false
return true

View File

@@ -56,9 +56,9 @@ public def Environment.getModulePackageByIdx? (env : Environment) (idx : ModuleI
Returns the standard base of the native symbol for the compiled constant {lean}`declName`.
For many constants, this is the full symbol. However, initializers have an additional prefix
(i.e., {lit}`_init_`) and boxed functions have an additional suffix (i.e., {lit}`___boxed`).
Furthermore, some constants do not use this stem at all (e.g., {lit}`main` and definitions
with {lit}`@[export]`).
(i.e., {lit}`_init_`) and boxed functions have an additional suffix
(see {name}`mkMangledBoxedName`). Furthermore, some constants do not use this stem at all
(e.g., {lit}`main` and definitions with {lit}`@[export]`).
-/
@[export lean_get_symbol_stem]
public def getSymbolStem (env : Environment) (declName : Name) : String :=

View File

@@ -7,7 +7,7 @@ module
prelude
public import Lean.Setup
import Init.Data.String.Termination
import Init.Data.String.TakeDrop
namespace String
@@ -133,6 +133,18 @@ def Name.mangleAux : Name → String
public def Name.mangle (n : Name) (pre : String := "l_") : String :=
pre ++ Name.mangleAux n
/--
Given `s = nm.mangle pre` for some `nm : Name` and `pre : String` with `nm != Name.anonymous`,
returns `(mkBoxedName nm).mangle pre`. This is used in the interpreter to find names of boxed
IR declarations.
-/
@[export lean_mk_mangled_boxed_name]
public def mkMangledBoxedName (s : String) : String :=
if s.endsWith "__" then
s ++ "_00__boxed"
else
s ++ "___boxed"
/--
The mangled name of the name used to create the module initialization function.

View File

@@ -543,12 +543,10 @@ def logSnapshotTask (task : Language.SnapshotTask Language.SnapshotTree) : CoreM
/-- Wraps the given action for use in `EIO.asTask` etc., discarding its final monadic state. -/
def wrapAsync {α : Type} (act : α CoreM β) (cancelTk? : Option IO.CancelToken) :
CoreM (α EIO Exception β) := do
let (childNGen, parentNGen) := ( getNGen).mkChild
setNGen parentNGen
let (childDeclNGen, parentDeclNGen) := ( getDeclNGen).mkChild
setDeclNGen parentDeclNGen
let (childNGen, parentNGen) := ( getDeclNGen).mkChild
setDeclNGen parentNGen
let st get
let st := { st with auxDeclNGen := childDeclNGen, ngen := childNGen }
let st := { st with auxDeclNGen := childNGen }
let ctx read
let ctx := { ctx with cancelTk? }
let heartbeats := ( IO.getNumHeartbeats) - ctx.initHeartbeats

View File

@@ -125,7 +125,7 @@ Parses and elaborates a Verso module docstring.
def versoModDocString
(range : DeclarationRange) (doc : TSyntax ``document) :
TermElabM VersoModuleDocs.Snippet := do
let level := getVersoModuleDocs ( getEnv) |>.terminalNesting |>.map (· + 1)
let level := getMainVersoModuleDocs ( getEnv) |>.terminalNesting |>.map (· + 1)
Doc.elabModSnippet range (doc.raw.getArgs.map (·)) (level.getD 0) |>.execForModule

View File

@@ -409,11 +409,29 @@ private builtin_initialize versoModuleDocExt :
}
def getVersoModuleDocs (env : Environment) : VersoModuleDocs :=
/--
Returns the Verso module docs for the current main module.
During elaboration, this will return the modules docs that have been added thus far, rather than
those for the entire module.
-/
def getMainVersoModuleDocs (env : Environment) : VersoModuleDocs :=
versoModuleDocExt.getState env
@[deprecated getMainVersoModuleDocs (since := "2026-01-21")]
def getVersoModuleDocs := @getMainVersoModuleDocs
/--
Returns all snippets of the Verso module docs from the indicated module, if they exist.
-/
def getVersoModuleDoc? (env : Environment) (moduleName : Name) :
Option (Array VersoModuleDocs.Snippet) :=
env.getModuleIdx? moduleName |>.map fun modIdx =>
versoModuleDocExt.getModuleEntries (level := .server) env modIdx
def addVersoModuleDocSnippet (env : Environment) (snippet : VersoModuleDocs.Snippet) : Except String Environment :=
let docs := getVersoModuleDocs env
let docs := getMainVersoModuleDocs env
if docs.canAdd snippet then
pure <| versoModuleDocExt.addEntry env snippet
else throw s!"Can't add - incorrect nesting {docs.terminalNesting.map (s!"(expected at most {·})") |>.getD ""})"

View File

@@ -21,7 +21,7 @@ namespace Lean.Elab.Command
match stx[1] with
| Syntax.atom _ val =>
if getVersoModuleDocs ( getEnv) |>.isEmpty then
if getMainVersoModuleDocs ( getEnv) |>.isEmpty then
let doc := String.Pos.Raw.extract val 0 (val.rawEndPos.unoffsetBy 2)
modifyEnv fun env => addMainModuleDoc env doc, range
else

View File

@@ -274,12 +274,10 @@ def wrapAsync {α β : Type} (act : α → CommandElabM β) (cancelTk? : Option
CommandElabM (α EIO Exception β) := do
let ctx read
let ctx := { ctx with cancelTk? }
let (childNGen, parentNGen) := ( get).ngen.mkChild
modify fun s => { s with ngen := parentNGen }
let (childDeclNGen, parentDeclNGen) := ( getDeclNGen).mkChild
setDeclNGen parentDeclNGen
let (childNGen, parentNGen) := ( getDeclNGen).mkChild
setDeclNGen parentNGen
let st get
let st := { st with auxDeclNGen := childDeclNGen, ngen := childNGen }
let st := { st with auxDeclNGen := childNGen }
return (act · |>.run ctx |>.run' st)
open Language in

View File

@@ -907,23 +907,26 @@ def lean (name : Option Ident := none) (error warning : flag false) («show» :
(endPos := endPos) (endPos_valid := by simp only [endPos]; split <;> simp [*])
let cctx : Command.Context := {fileName := getFileName, fileMap := text, snap? := none, cancelTk? := none}
let scopes := ( get).scopes
let mut cmdState : Command.State := { env, maxRecDepth := MonadRecDepth.getMaxRecDepth, scopes }
let mut pstate : Parser.ModuleParserState := {pos := pos, recovering := false}
let mut cmds := #[]
repeat
let scope := cmdState.scopes.head!
let pmctx := { env := cmdState.env, options := scope.opts, currNamespace := scope.currNamespace, openDecls := scope.openDecls }
let (cmd, ps', messages) := Parser.parseCommand ictx pmctx pstate cmdState.messages
cmds := cmds.push cmd
pstate := ps'
cmdState := { cmdState with messages := messages }
cmdState runCommand (Command.elabCommand cmd) cmd cctx cmdState
if Parser.isTerminalCommand cmd then break
setEnv cmdState.env
modify fun st => { st with scopes := cmdState.scopes }
let (cmds, cmdState, trees) withSaveInfoContext do
let mut cmdState : Command.State := { env, maxRecDepth := MonadRecDepth.getMaxRecDepth, scopes }
let mut pstate : Parser.ModuleParserState := {pos := pos, recovering := false}
let mut cmds := #[]
repeat
let scope := cmdState.scopes.head!
let pmctx := { env := cmdState.env, options := scope.opts, currNamespace := scope.currNamespace, openDecls := scope.openDecls }
let (cmd, ps', messages) := Parser.parseCommand ictx pmctx pstate cmdState.messages
cmds := cmds.push cmd
pstate := ps'
cmdState := { cmdState with messages := messages }
cmdState runCommand (Command.elabCommand cmd) cmd cctx cmdState
if Parser.isTerminalCommand cmd then break
setEnv cmdState.env
modify fun st => { st with scopes := cmdState.scopes }
for t in cmdState.infoState.trees do
pushInfoTree t
for t in cmdState.infoState.trees do
pushInfoTree t
let trees := ( getInfoTrees)
pure (cmds, cmdState, trees)
let mut output := #[]
for msg in cmdState.messages.toArray do
@@ -937,14 +940,13 @@ def lean (name : Option Ident := none) (error warning : flag false) («show» :
let hint flagHint m!"The `+error` flag indicates that errors are expected:" #[" +error"]
logErrorAt msgStx m!"Unexpected error:{indentD msg.data}{hint.getD m!""}"
if msg.severity == .warning && !warning then
let hint flagHint m!"The `+error` flag indicates that warnings are expected:" #[" +warning"]
let hint flagHint m!"The `+warning` flag indicates that warnings are expected:" #[" +warning"]
logErrorAt msgStx m!"Unexpected warning:{indentD msg.data}{hint.getD m!""}"
else
withRef msgStx <| log msg.data (severity := .information) (isSilent := true)
if let some x := name then
modifyEnv (leanOutputExt.modifyState · (·.insert x.getId output))
if «show» then
let trees := ( getInfoTrees)
if h : trees.size > 0 then
let hl := Data.LeanBlock.mk ( highlightSyntax trees (mkNullNode cmds))
return .other {name := ``Data.LeanBlock, val := .mk hl} #[.code code.getString]

View File

@@ -20,10 +20,12 @@ structure LetRecDeclView where
declName : Name
parentName? : Option Name
binderIds : Array Syntax
binders : Syntax -- binder syntax for docstring elaboration
type : Expr
mvar : Expr -- auxiliary metavariable used to lift the 'let rec'
valStx : Syntax
termination : TerminationHints
docString? : Option (TSyntax ``Parser.Command.docComment × Bool) := none
structure LetRecView where
decls : Array LetRecDeclView
@@ -32,8 +34,9 @@ structure LetRecView where
/- group ("let " >> nonReservedSymbol "rec ") >> sepBy1 (group (optional «attributes» >> letDecl)) ", " >> "; " >> termParser -/
private def mkLetRecDeclView (letRec : Syntax) : TermElabM LetRecView := do
let mut decls : Array LetRecDeclView := #[]
let isVerso := doc.verso.get ( getOptions)
for attrDeclStx in letRec[1][0].getSepArgs do
let docStr? := attrDeclStx[0].getOptional?.map TSyntax.mk
let docStr? := attrDeclStx[0].getOptional?.map (TSyntax.mk ·, isVerso)
let attrOptStx := attrDeclStx[1]
let attrs if attrOptStx.isNone then pure #[] else elabDeclAttrs attrOptStx[0]
let decl := attrDeclStx[2][0]
@@ -45,16 +48,21 @@ private def mkLetRecDeclView (letRec : Syntax) : TermElabM LetRecView := do
throwErrorAt declId "'let rec' expressions must be named"
let shortDeclName := declId.getId
let parentName? getDeclName?
let declName := parentName?.getD Name.anonymous ++ shortDeclName
let mut declName := parentName?.getD Name.anonymous ++ shortDeclName
let env getEnv
if env.header.isModule && !env.isExporting then
declName := mkPrivateName env declName
if decls.any fun decl => decl.declName == declName then
withRef declId do
throwError "`{.ofConstName declName}` has already been declared"
let binders := decl[1]
let binderStx := decl[1]
checkNotAlreadyDeclared declName
applyAttributesAt declName attrs AttributeApplicationTime.beforeElaboration
addDocString' declName binders docStr?
-- Docstring processing is deferred until the declaration is added to the environment.
-- This is necessary for Verso docstrings to work correctly, as they may reference the
-- declaration being defined.
addDeclarationRangesFromSyntax declName decl declId
let binders := binders.getArgs
let binders := binderStx.getArgs
let typeStx := expandOptType declId decl[2]
let (type, binderIds) elabBindersEx binders fun xs => do
let type elabType typeStx
@@ -70,7 +78,7 @@ private def mkLetRecDeclView (letRec : Syntax) : TermElabM LetRecView := do
let termination elabTerminationHints attrDeclStx[3]
decls := decls.push {
ref := declId, attrs, shortDeclName, declName, parentName?,
binderIds, type, mvar, valStx, termination
binderIds, binders := binderStx, type, mvar, valStx, termination, docString? := docStr?
}
else
throwUnsupportedSyntax
@@ -111,15 +119,12 @@ private def registerLetRecsToLift (views : Array LetRecDeclView) (fvars : Array
let toLift views.mapIdxM fun i view => do
let value := values[i]!
let termination := view.termination.rememberExtraParams view.binderIds.size value
let env getEnv
pure {
ref := view.ref
fvarId := fvars[i]!.fvarId!
attrs := view.attrs
shortDeclName := view.shortDeclName
declName :=
if env.isExporting || !env.header.isModule then view.declName
else mkPrivateName env view.declName
declName := view.declName
parentName? := view.parentName?
lctx
localInstances
@@ -127,6 +132,8 @@ private def registerLetRecsToLift (views : Array LetRecDeclView) (fvars : Array
val := value
mvarId := view.mvar.mvarId!
termination
binders := view.binders
docString? := view.docString?
}
modify fun s => { s with letRecsToLift := toLift.toList ++ s.letRecsToLift }

View File

@@ -1092,8 +1092,8 @@ def pushLetRecs (preDefs : Array PreDefinition) (letRecClosures : List LetRecClo
ref := c.ref
declName := c.toLift.declName
levelParams := [] -- we set it later
binders := mkNullNode -- No docstrings, so we don't need these
modifiers := { modifiers with attrs := c.toLift.attrs }
binders := c.toLift.binders
modifiers := { modifiers with attrs := c.toLift.attrs, docString? := c.toLift.docString? }
kind, type, value,
termination := c.toLift.termination
}

View File

@@ -29,6 +29,10 @@ def addPreDefsFromUnary (docCtx : LocalContext × LocalInstances) (preDefs : Arr
let preDefNonRec := unaryPreDefNonRec.filterAttrs fun attr => attr.name != `implemented_by
let declNames := preDefs.toList.map (·.declName)
preDefs.forM fun preDef =>
unless preDef.kind.isTheorem do
markAsRecursive preDef.declName
-- Do not complain if the user sets @[semireducible], which usually is a noop,
-- we recognize that below and then do not set @[irreducible]
withOptions (allowUnsafeReducibility.set · true) do
@@ -53,8 +57,6 @@ def cleanPreDef (preDef : PreDefinition) (cacheProofs := true) : MetaM PreDefini
Assign final attributes to the definitions. Assumes the EqnInfos to be already present.
-/
def addPreDefAttributes (preDefs : Array PreDefinition) : TermElabM Unit := do
for preDef in preDefs do
markAsRecursive preDef.declName
for preDef in preDefs.reverse do
-- must happen before `generateEagerEqns`
-- must happen in reverse order so that constants realized as part of the first decl

View File

@@ -140,6 +140,8 @@ def structuralRecursion
preDefsNonRec.forM fun preDefNonRec => do
let preDefNonRec eraseRecAppSyntax preDefNonRec
prependError m!"structural recursion failed, produced type incorrect term" do
unless preDefNonRec.kind.isTheorem do
markAsRecursive preDefNonRec.declName
-- We create the `_unsafe_rec` before we abstract nested proofs.
-- Reason: the nested proofs may be referring to the _unsafe_rec.
addNonRec docCtx preDefNonRec (applyAttrAfterCompilation := false) (all := names.toList)
@@ -157,7 +159,6 @@ def structuralRecursion
-/
registerEqnsInfo preDef (preDefs.map (·.declName)) recArgPos fixedParamPerms
addSmartUnfoldingDef docCtx preDef recArgPos
markAsRecursive preDef.declName
for preDef in preDefs do
-- must happen in separate loop so realizations can see eqnInfos of all other preDefs
enableRealizationsForConst preDef.declName

View File

@@ -82,13 +82,27 @@ def elabMPureIntro : Tactic
replaceMainGoal [mv]
| _ => throwUnsupportedSyntax
private def extractPureProp (e : Expr) : MetaM (Option Expr) := do
let e instantiateMVarsIfMVarApp e
let some (_, e) := e.app2? ``ULift.down | return none
let f := e.getAppFn
unless f.isConstOf ``SPred.pure do return none
let args := e.getAppArgs
if args.size < 2 then return none
let σs := args[0]!
let n TypeList.length σs
unless n = args.size - 2 do return none
let p := args[1]!
return p
partial def _root_.Lean.MVarId.applyRflAndAndIntro (mvar : MVarId) : MetaM Unit := do
-- The target might look like `(⌜?n = n ∧ ?m = b⌝ s).down`, which we reduce to
-- `?n = n ∧ ?m = b` by `whnfD`.
-- The target might look like `(⌜n = ?n ∧ ?m = b⌝ s).down`, which we reduce to
-- `n = ?n ∧ ?m = b` with `extractPureProp`.
-- (Recall that `⌜s = 4⌝ s` is `SPred.pure (σs:=[Nat]) (s = 4) s` and `SPred.pure` is
-- semi-reducible.)
let ty whnfD ( mvar.getType)
trace[Elab.Tactic.Do.spec] "whnf: {ty}"
let ty mvar.getType >>= instantiateMVarsIfMVarApp
let ty (·.getD ty) <$> extractPureProp ty
trace[Elab.Tactic.Do.spec] "pure Prop: {ty}"
if ty.isAppOf ``True then
mvar.assign (mkConst ``True.intro)
else if let some (lhs, rhs) := ty.app2? ``And then
@@ -127,16 +141,3 @@ def MGoal.pureTrivial (goal : MGoal) : OptionT MetaM Expr := do
return ((), m)
return prf
catch _ => failure
/-
def MGoal.pureRfl (goal : MGoal) : OptionT MetaM Expr := do
let mv ← mkFreshExprMVar goal.toExpr
let ([], _) ← try runTactic mv.mvarId! (← `(tactic| apply $(mkIdent ``Std.Do.SPred.Tactic.Pure.intro); rfl)) catch _ => failure
| failure
return mv
def MGoal.pureRfl (goal : MGoal) : OptionT MetaM Expr := do
let mv ← mkFreshExprMVar goal.toExpr
let ([], _) ← try runTactic mv.mvarId! (← `(tactic| apply $(mkIdent ``Std.Do.SPred.Tactic.Pure.intro); rfl)) catch _ => failure
| failure
return mv
-/

View File

@@ -209,8 +209,8 @@ def SuccessPoint.clause (p : SuccessPoint) : Expr :=
/-- The last syntactic element of a `FailureCond`. -/
inductive ExceptCondsDefault where
/-- `()`. This means we can suggest `post⟨...⟩`. -/
| unit
/-- `PUnit.unit`. This means we can suggest `post⟨...⟩`. -/
| punit
/-- `ExceptConds.false`. This means we can suggest `⇓ _ => _`. -/
| false
/-- `ExceptConds.true`. This means we can suggest `⇓? _ => _`. -/
@@ -229,7 +229,7 @@ When the default is not defeq to `ExceptConds.false`, we use it as the default.
-/
structure FailureCondHints where
points : Array Expr := #[]
default : ExceptCondsDefault := .unit
default : ExceptCondsDefault := .punit
/-- Look at how `inv` is used in the `vcs` and collect hints about how `inv` should be instantiated.
In case it succeeds, there will be
@@ -293,8 +293,8 @@ def collectInvariantHints (vcs : Array MVarId) (inv : MVarId) (xs : Expr) (letMu
-- Just overwrite the existing entry. Computing a join here is overkill for the few cases
-- where this is going to be used.
failureConds := { failureConds with points := points }
if conds.isConstOf ``Unit.unit then
failureConds := { failureConds with default := .unit }
if conds.isConstOf ``PUnit.unit then
failureConds := { failureConds with default := .punit }
else if conds.isAppOfArity ``ExceptConds.false 1 then
failureConds := { failureConds with default := .false }
else if conds.isAppOfArity ``ExceptConds.true 1 then
@@ -402,8 +402,8 @@ public def suggestInvariant (vcs : Array MVarId) (inv : MVarId) : TacticM Term :
-- 2. However, on early return we want to suggest something using `Invariant.withEarlyReturn`.
-- 3. When there are non-`False` failure conditions, we cannot suggest `⇓ ⟨xs, letMuts⟩ => ...`.
-- We might be able to suggest `⇓? ⟨xs, letMuts⟩ => ...` (`True` failure condition),
-- or `post⟨...⟩` (more than 0 failure handlers, but ending in `()`), and fall back to
-- `by exact ⟨...⟩` (not ending in `()`).
-- or `post⟨...⟩` (more than 0 failure handlers, but ending in `PUnit.unit`), and fall back to
-- `by exact ⟨...⟩` (not ending in `PUnit.unit`).
-- 4. Similarly for the `onExcept` argument of `Invariant.withEarlyReturn`.
-- Hence the spaghetti code.
--
@@ -429,7 +429,7 @@ public def suggestInvariant (vcs : Array MVarId) (inv : MVarId) : TacticM Term :
-- Now the configuration mess.
if failureConds.points.isEmpty then
match failureConds.default with
| .false | .unit =>
| .false | .punit =>
`(Invariant.withEarlyReturn (onReturn := fun r letMuts => $onReturn) (onContinue := fun xs letMuts => $onContinue))
-- we handle the following two cases here rather than through
-- `postCondWithMultipleConditions` below because that would insert a superfluous `by exact _`.
@@ -469,7 +469,7 @@ where
postCondWithMultipleConditions (handlers : Array Term) (default : ExceptCondsDefault) : MetaM Term := do
let handlers := Syntax.TSepArray.ofElems (sep := ",") handlers
match default with
| .unit => `(post$handlers,*)
| .punit => `(post$handlers,*)
-- See the comment in `post⟨_⟩` syntax for why we emit `by exact` here.
| .false => `(by exact $handlers,*, ExceptConds.false)
| .true => `(by exact $handlers,*, ExceptConds.true)

View File

@@ -8,6 +8,7 @@ module
prelude
import Lean.DocString
public import Lean.Elab.Command
public import Lean.Parser.Tactic.Doc
public section
@@ -38,30 +39,42 @@ open Lean.Parser.Command
| _ => throwError "Malformed 'register_tactic_tag' command"
/--
Gets the first string token in a parser description. For example, for a declaration like
`syntax "squish " term " with " term : tactic`, it returns `some "squish "`, and for a declaration
like `syntax tactic " <;;;> " tactic : tactic`, it returns `some " <;;;> "`.
Returns `none` for syntax declarations that don't contain a string constant.
Computes a table that heuristically maps parser syntax kinds to their first tokens by inspecting the
Pratt parsing tables for the `tactic syntax kind. If a custom name is provided for the tactic, then
it is returned instead.
-/
private partial def getFirstTk (e : Expr) : MetaM (Option String) := do
match ( Meta.whnf e).getAppFnArgs with
| (``ParserDescr.node, #[_, _, p]) => getFirstTk p
| (``ParserDescr.trailingNode, #[_, _, _, p]) => getFirstTk p
| (``ParserDescr.unary, #[.app _ (.lit (.strVal "withPosition")), p]) => getFirstTk p
| (``ParserDescr.unary, #[.app _ (.lit (.strVal "atomic")), p]) => getFirstTk p
| (``ParserDescr.binary, #[.app _ (.lit (.strVal "andthen")), p, _]) => getFirstTk p
| (``ParserDescr.nonReservedSymbol, #[.lit (.strVal tk), _]) => pure (some tk)
| (``ParserDescr.symbol, #[.lit (.strVal tk)]) => pure (some tk)
| (``Parser.withAntiquot, #[_, p]) => getFirstTk p
| (``Parser.leadingNode, #[_, _, p]) => getFirstTk p
| (``HAndThen.hAndThen, #[_, _, _, _, p1, p2]) =>
if let some tk getFirstTk p1 then pure (some tk)
else getFirstTk (.app p2 (.const ``Unit.unit []))
| (``Parser.nonReservedSymbol, #[.lit (.strVal tk), _]) => pure (some tk)
| (``Parser.symbol, #[.lit (.strVal tk)]) => pure (some tk)
| _ => pure none
def firstTacticTokens [Monad m] [MonadEnv m] : m (NameMap String) := do
let env getEnv
let some tactics := (Lean.Parser.parserExtension.getState env).categories.find? `tactic
| return {}
let mut firstTokens : NameMap String :=
tacticNameExt.toEnvExtension.getState env
|>.importedEntries
|>.push (tacticNameExt.exportEntriesFn env (tacticNameExt.getState env) .exported)
|>.foldl (init := {}) fun names inMods =>
inMods.foldl (init := names) fun names (k, n) =>
names.insert k n
firstTokens := addFirstTokens tactics tactics.tables.leadingTable firstTokens
firstTokens := addFirstTokens tactics tactics.tables.trailingTable firstTokens
return firstTokens
where
addFirstTokens tactics table firsts : NameMap String := Id.run do
let mut firsts := firsts
for (tok, ps) in table do
-- Skip antiquotes
if tok == `«$» then continue
for (p, _) in ps do
for (k, ()) in p.info.collectKinds {} do
if tactics.kinds.contains k then
let tok := tok.toString (escape := false)
-- It's important here that the already-existing mapping is preserved, because it will
-- contain any user-provided custom name, and these shouldn't be overridden.
firsts := firsts.alter k (·.getD tok)
return firsts
/--
Creates some `MessageData` for a parser name.
@@ -71,18 +84,14 @@ identifiable leading token, then that token is shown. Otherwise, the underlying
without an `@`. The name includes metadata that makes infoview hovers and the like work. This
only works for global constants, as the local context is not included.
-/
private def showParserName (n : Name) : MetaM MessageData := do
private def showParserName [Monad m] [MonadEnv m] (firsts : NameMap String) (n : Name) : m MessageData := do
let env getEnv
let params :=
env.constants.find?' n |>.map (·.levelParams.map Level.param) |>.getD []
let tok
if let some descr := env.find? n |>.bind (·.value?) then
if let some tk getFirstTk descr then
pure <| Std.Format.text tk.trimAscii.copy
else pure <| format n
else pure <| format n
let tok := (( customTacticName n) <|> firsts.get? n).map Std.Format.text |>.getD (format n)
pure <| .ofFormatWithInfos {
fmt := "'" ++ .tag 0 tok ++ "'",
fmt := "`" ++ .tag 0 tok ++ "`",
infos :=
.ofList [(0, .ofTermInfo {
lctx := .empty,
@@ -93,7 +102,6 @@ private def showParserName (n : Name) : MetaM MessageData := do
})] _
}
/--
Displays all available tactic tags, with documentation.
-/
@@ -106,20 +114,22 @@ Displays all available tactic tags, with documentation.
for (tac, tag) in arr do
mapping := mapping.insert tag (mapping.getD tag {} |>.insert tac)
let firsts firstTacticTokens
let showDocs : Option String MessageData
| none => .nil
| some d => Format.line ++ MessageData.joinSep ((d.split '\n').map (toMessageData String.Slice.copy)).toList Format.line
let showTactics (tag : Name) : MetaM MessageData := do
let showTactics (tag : Name) : CommandElabM MessageData := do
match mapping.find? tag with
| none => pure .nil
| some tacs =>
if tacs.isEmpty then pure .nil
else
let tacs := tacs.toArray.qsort (·.toString < ·.toString) |>.toList
pure (Format.line ++ MessageData.joinSep ( tacs.mapM showParserName) ", ")
pure (Format.line ++ MessageData.joinSep ( tacs.mapM (showParserName firsts)) ", ")
let tagDescrs liftTermElabM <| ( allTagsWithInfo).mapM fun (name, userName, docs) => do
let tagDescrs ( allTagsWithInfo).mapM fun (name, userName, docs) => do
pure <| m!"" ++
MessageData.nestD (m!"`{name}`" ++
(if name.toString != userName then m!"\"{userName}\"" else MessageData.nil) ++
@@ -146,13 +156,13 @@ structure TacticDoc where
/-- Any docstring extensions that have been specified -/
extensionDocs : Array String
def allTacticDocs : MetaM (Array TacticDoc) := do
def allTacticDocs (includeUnnamed : Bool := true) : MetaM (Array TacticDoc) := do
let env getEnv
let all :=
tacticTagExt.toEnvExtension.getState ( getEnv)
|>.importedEntries |>.push (tacticTagExt.exportEntriesFn ( getEnv) (tacticTagExt.getState ( getEnv)) .exported)
let allTags :=
tacticTagExt.toEnvExtension.getState env |>.importedEntries
|>.push (tacticTagExt.exportEntriesFn env (tacticTagExt.getState env) .exported)
let mut tacTags : NameMap NameSet := {}
for arr in all do
for arr in allTags do
for (tac, tag) in arr do
tacTags := tacTags.insert tac (tacTags.getD tac {} |>.insert tag)
@@ -160,15 +170,18 @@ def allTacticDocs : MetaM (Array TacticDoc) := do
let some tactics := (Lean.Parser.parserExtension.getState env).categories.find? `tactic
| return #[]
let firstTokens firstTacticTokens
for (tac, _) in tactics.kinds do
-- Skip noncanonical tactics
if let some _ := alternativeOfTactic env tac then continue
let userName : String
if let some descr := env.find? tac |>.bind (·.value?) then
if let some tk getFirstTk descr then
pure tk.trimAscii.copy
else pure tac.toString
else pure tac.toString
let userName? : Option String := firstTokens.get? tac
let userName
if let some n := userName? then pure n
else if includeUnnamed then pure tac.toString
else continue
docs := docs.push {
internalName := tac,

View File

@@ -16,6 +16,7 @@ open Meta
structure Context extends Tactic.Context where
ctx : Meta.Grind.Context
sctx : Meta.Sym.Context
methods : Grind.Methods
params : Grind.Params
@@ -289,7 +290,7 @@ open Grind
def liftGrindM (k : GrindM α) : GrindTacticM α := do
let ctx read
let s get
let ((a, grindState), symState) liftMetaM <| StateRefT'.run ((Grind.withGTransparency k) ctx.methods.toMethodsRef ctx.ctx |>.run s.grindState) s.symState
let ((a, grindState), symState) liftMetaM <| StateRefT'.run (((Grind.withGTransparency k) ctx.methods.toMethodsRef ctx.ctx |>.run s.grindState) ctx.sctx) s.symState
modify fun s => { s with grindState, symState }
return a
@@ -358,12 +359,13 @@ def mkEvalTactic' (elaborator : Name) (params : Params) : TermElabM (Goal → TS
let eval (goal : Goal) (stx : TSyntax `grind) : GrindM (List Goal) := do
let methods getMethods
let grindCtx readThe Meta.Grind.Context
let symCtx readThe Meta.Sym.Context
let grindState get
let symState getThe Sym.State
-- **Note**: we discard changes to `Term.State`
let (subgoals, grindState', symState') Term.TermElabM.run' (ctx := termCtx) (s := termState) do
let (_, s) GrindTacticM.run
(ctx := { recover := false, methods, ctx := grindCtx, params, elaborator })
(ctx := { recover := false, methods, ctx := grindCtx, sctx := symCtx, params, elaborator })
(s := { grindState, symState, goals := [goal] }) do
evalGrindTactic stx.raw
pruneSolvedGoals
@@ -383,7 +385,7 @@ def GrindTacticM.runAtGoal (mvarId : MVarId) (params : Params) (k : GrindTacticM
Reconsider the option `useSorry`.
-/
let params' := { params with config.useSorry := false }
let (methods, ctx, state) liftMetaM <| GrindM.runAtGoal mvarId params' (evalTactic? := some evalTactic) fun goal => do
let (methods, ctx, sctx, state) liftMetaM <| GrindM.runAtGoal mvarId params' (evalTactic? := some evalTactic) fun goal => do
let a : Action := Action.intros 0 >> Action.assertAll
let goals match ( a.run goal) with
| .closed _ => pure []
@@ -392,10 +394,11 @@ def GrindTacticM.runAtGoal (mvarId : MVarId) (params : Params) (k : GrindTacticM
let ctx readThe Meta.Grind.Context
/- Restore original config -/
let ctx := { ctx with config := params.config }
let sctx readThe Meta.Sym.Context
let grindState get
let symState getThe Sym.State
return (methods, ctx, { grindState, symState, goals })
return (methods, ctx, sctx, { grindState, symState, goals })
let tctx read
k { tctx with methods, ctx, params } |>.run state
k { tctx with methods, ctx, sctx, params } |>.run state
end Lean.Elab.Tactic.Grind

View File

@@ -167,6 +167,11 @@ structure LetRecToLift where
val : Expr
mvarId : MVarId
termination : TerminationHints
/-- The binders syntax for the declaration, used for docstring elaboration. -/
binders : Syntax := .missing
/-- The docstring, if present, and whether it's Verso. Docstring processing is deferred until the
declaration is added to the environment (needed for Verso docstrings to work). -/
docString? : Option (TSyntax ``Lean.Parser.Command.docComment × Bool) := none
deriving Inhabited
/--

View File

@@ -179,6 +179,13 @@ structure EnvironmentHeader where
`ModuleIdx` for the same module.
-/
modules : Array EffectiveImport := #[]
/-- For `getModuleIdx?` -/
private moduleName2Idx : Std.HashMap Name ModuleIdx := Id.run do
let mut m := {}
for _h : idx in [0:modules.size] do
let mod := modules[idx]
m := m.insert mod.module idx
return m
/--
Subset of `modules` for which `importAll` is `true`. This is assumed to be a much smaller set so
we precompute it instead of iterating over all of `modules` multiple times. However, note that
@@ -267,7 +274,7 @@ structure Environment where
-/
private irBaseExts : Array EnvExtensionState
/-- The header contains additional information that is set at import time. -/
header : EnvironmentHeader := {}
header : EnvironmentHeader := private_decl% {}
deriving Nonempty
/-- Exceptions that can be raised by the kernel when type checking new declarations. -/
@@ -1174,7 +1181,7 @@ def isSafeDefinition (env : Environment) (declName : Name) : Bool :=
| _ => false
def getModuleIdx? (env : Environment) (moduleName : Name) : Option ModuleIdx :=
env.header.modules.findIdx? (·.module == moduleName)
env.header.moduleName2Idx[moduleName]?
end Environment

View File

@@ -66,7 +66,7 @@ unsafe def fold {α : Type} (f : Name → α → MetaM α) (e : Expr) (acc : α)
| .app f a =>
let fi getFunInfo f (some 1)
if fi.paramInfo[0]!.isInstImplicit then
-- Don't visit implicit arguments.
-- Don't visit instance implicit arguments.
visit f acc
else
visit a ( visit f acc)

View File

@@ -8,6 +8,7 @@ module
prelude
public import Lean.Meta.Match.MatcherInfo
public import Lean.DefEqAttrib
public import Lean.Meta.RecExt
public import Lean.Meta.LetToHave
import Lean.Meta.AppBuilder
@@ -40,26 +41,6 @@ This is implemented by
-/
def eqnAffectingOptions : Array (Lean.Option Bool) := #[backward.eqns.nonrecursive, backward.eqns.deepRecursiveSplit]
/--
Environment extension for storing which declarations are recursive.
This information is populated by the `PreDefinition` module, but the simplifier
uses when unfolding declarations.
-/
builtin_initialize recExt : TagDeclarationExtension
mkTagDeclarationExtension `recExt (asyncMode := .async .asyncEnv)
/--
Marks the given declaration as recursive.
-/
def markAsRecursive (declName : Name) : CoreM Unit :=
modifyEnv (recExt.tag · declName)
/--
Returns `true` if `declName` was defined using well-founded recursion, or structural recursion.
-/
def isRecursiveDefinition (declName : Name) : CoreM Bool :=
return recExt.isTagged ( getEnv) declName
def eqnThmSuffixBase := "eq"
def eqnThmSuffixBasePrefix := eqnThmSuffixBase ++ "_"
def eqn1ThmSuffix := eqnThmSuffixBasePrefix ++ "1"

View File

@@ -139,13 +139,14 @@ private partial def andProjections (e : Expr) : MetaM (Array Expr) := do
return acc.push e
go e ( inferType e) #[]
private def mkInjectiveEqTheoremValue (ctorName : Name) (targetType : Expr) : MetaM Expr := do
private def mkInjectiveEqTheoremValue (ctorVal : ConstructorVal) (targetType : Expr) : MetaM Expr := do
forallTelescopeReducing targetType fun xs type => do
let mvar mkFreshExprSyntheticOpaqueMVar type
let [mvarId₁, mvarId₂] mvar.mvarId!.apply (mkConst ``Eq.propIntro)
| throwError "unexpected number of subgoals when proving injective theorem for constructor `{ctorName}`"
let (h, mvarId₁) mvarId₁.intro1
solveEqOfCtorEq ctorName mvarId₁ h
| throwError "unexpected number of subgoals when proving injective theorem for constructor `{ctorVal.name}`"
let injPrf := mkConst (mkInjectiveTheoremNameFor ctorVal.name) (ctorVal.levelParams.map mkLevelParam)
let injPrf := mkAppN injPrf xs
mvarId₁.assign injPrf
let mut mvarId₂ := mvarId₂
while true do
let t mvarId₂.getType
@@ -158,7 +159,7 @@ private def mkInjectiveEqTheoremValue (ctorName : Name) (targetType : Expr) : Me
| _ => pure ()
let (h, mvarId₂') mvarId₂.intro1
(_, mvarId₂) substEq mvarId₂' h
try mvarId₂.refl catch _ => throwError (injTheoremFailureHeader ctorName)
try mvarId₂.refl catch _ => throwError (injTheoremFailureHeader ctorVal.name)
mkLambdaFVars xs mvar
private def mkInjectiveEqTheorem (ctorVal : ConstructorVal) : MetaM Unit := do
@@ -167,7 +168,7 @@ private def mkInjectiveEqTheorem (ctorVal : ConstructorVal) : MetaM Unit := do
let some type mkInjectiveEqTheoremType? ctorVal
| return ()
trace[Meta.injective] "type: {type}"
let value mkInjectiveEqTheoremValue ctorVal.name type
let value mkInjectiveEqTheoremValue ctorVal type
addDecl <| Declaration.thmDecl {
name
levelParams := ctorVal.levelParams

View File

@@ -292,9 +292,8 @@ def transform
let aux1 := mkAppN (mkConst matcherApp.matcherName matcherLevels.toList) params'
let aux1 := mkApp aux1 motive'
let aux1 := mkAppN aux1 discrs'
unless ( isTypeCorrect aux1) do
prependError m!"failed to transform matcher, type error when constructing new pre-splitter motive:{indentExpr aux1}\nfailed with" do
check aux1
prependError m!"failed to transform matcher, type error when constructing new pre-splitter motive:{indentExpr aux1}\nfailed with" do
check aux1
let origAltTypes inferArgumentTypesN matcherApp.alts.size aux1
-- We replace the matcher with the splitter
@@ -304,9 +303,8 @@ def transform
let aux2 := mkAppN (mkConst splitter matcherLevels.toList) params'
let aux2 := mkApp aux2 motive'
let aux2 := mkAppN aux2 discrs'
unless ( isTypeCorrect aux2) do
prependError m!"failed to transform matcher, type error when constructing splitter motive:{indentExpr aux2}\nfailed with" do
check aux2
prependError m!"failed to transform matcher, type error when constructing splitter motive:{indentExpr aux2}\nfailed with" do
check aux2
let altTypes inferArgumentTypesN matcherApp.alts.size aux2
let mut alts' := #[]
@@ -359,8 +357,7 @@ def transform
let aux := mkAppN (mkConst matcherApp.matcherName matcherLevels.toList) params'
let aux := mkApp aux motive'
let aux := mkAppN aux discrs'
unless ( isTypeCorrect aux) do
logError m!"failed to transform matcher, type error when constructing new motive:{indentExpr aux}"
prependError m!"failed to transform matcher, type error when constructing new motive:{indentExpr aux}" do
check aux
let altTypes inferArgumentTypesN matcherApp.alts.size aux

33
src/Lean/Meta/RecExt.lean Normal file
View File

@@ -0,0 +1,33 @@
/-
Copyright (c) 2021 Microsoft Corporation. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Leonardo de Moura
-/
module
prelude
public import Lean.Attributes
public section
namespace Lean.Meta
/--
Environment extension for storing which declarations are recursive.
This information is populated by the `PreDefinition` module, but the simplifier
uses when unfolding declarations.
-/
builtin_initialize recExt : TagDeclarationExtension
mkTagDeclarationExtension `recExt (asyncMode := .async .asyncEnv)
/--
Marks the given declaration as recursive.
-/
def markAsRecursive (declName : Name) : CoreM Unit :=
modifyEnv (recExt.tag · declName)
/--
Returns `true` if `declName` was defined using well-founded recursion, or structural recursion.
-/
def isRecursiveDefinition (declName : Name) : CoreM Bool :=
return recExt.isTagged ( getEnv) declName

View File

@@ -23,13 +23,15 @@ public import Lean.Meta.Sym.Apply
public import Lean.Meta.Sym.InferType
public import Lean.Meta.Sym.Simp
public import Lean.Meta.Sym.Util
public import Lean.Meta.Sym.Eta
public import Lean.Meta.Sym.Grind
/-!
# Symbolic simulation support.
# Symbolic computation support.
This module provides `SymM`, a monad for implementing symbolic simulators (e.g., verification condition generators)
using Lean. The monad addresses performance issues found in symbolic simulators built on top of user-facing
tactics (e.g., `apply` and `intros`).
This module provides `SymM`, a monad for implementing symbolic computation (e.g., decision procedures and
verification condition generators) using Lean. The monad addresses performance issues found in symbolic
computation engines built on top of user-facing tactics (e.g., `apply` and `intros`).
## Overview
@@ -65,14 +67,14 @@ whether `maxFVar[e]` is in `?m.lctx` — a single hash lookup, O(1).
**The problem:** The `isDefEq` predicate in `MetaM` is designed for elaboration and user-facing tactics.
It supports reduction, type-class resolution, and many other features that can be expensive or have
unpredictable running time. For symbolic simulation, where pattern matching is called frequently on
unpredictable running time. For symbolic computation, where pattern matching is called frequently on
large ground terms, these features become performance bottlenecks.
**The solution:** In `SymM`, pattern matching and definitional equality are restricted to a more syntactic,
predictable subset. Key design choices:
1. **Reducible declarations are abbreviations.** Reducible declarations are eagerly expanded when indexing
terms and when entering symbolic simulation mode. During matching, we assume abbreviations have already
terms and when entering symbolic computation mode. During matching, we assume abbreviations have already
been expanded.
**Why `MetaM` `simp` cannot make this assumption**: The simplifier in `MetaM` is designed for interactive use,
@@ -99,7 +101,7 @@ predictable subset. Key design choices:
4. **Types must be indexed.** Unlike proofs and instances, types cannot be ignored, without indexing them,
pattern matching produces too many candidates. Like other abbreviations, type abbreviations are expanded.
Note that given `def Foo : Type := Bla`, the terms `Foo` and `Bla` are *not* considered structurally
equal in the symbolic simulator framework.
equal in the symbolic computation framework.
### Skipping type checks on assignment
@@ -117,7 +119,7 @@ so the check is almost always skipped.
### `GrindM` state
**The problem:** In symbolic simulation, we often want to discharge many goals using proof automation such
**The problem:** In symbolic computation, we often want to discharge many goals using proof automation such
as `grind`. Many of these goals share very similar local contexts. If we invoke `grind` on each goal
independently, we repeatedly reprocess the same hypotheses.

View File

@@ -44,8 +44,11 @@ first because solving it often solves `?w`.
def mkResultPos (pattern : Pattern) : List Nat := Id.run do
let auxPrefix := `_sym_pre
-- Initialize "found" mask with arguments that can be synthesized by type class resolution.
let mut found := pattern.isInstance
let numArgs := pattern.varTypes.size
let mut found := if let some varInfos := pattern.varInfos? then
varInfos.argsInfo.map fun info : ProofInstArgInfo => info.isInstance
else
Array.replicate numArgs false
let auxVars := pattern.varTypes.mapIdx fun i _ => mkFVar .num auxPrefix i
-- Collect arguments that occur in the pattern
for fvarId in collectFVars {} (pattern.pattern.instantiateRev auxVars) |>.fvarIds do
@@ -96,6 +99,10 @@ def mkValue (expr : Expr) (pattern : Pattern) (result : MatchUnifyResult) : Expr
else
mkAppN (expr.instantiateLevelParams pattern.levelParams result.us) result.args
public inductive ApplyResult where
| failed
| goals (mvarIds : List MVarId)
/--
Applies a backward rule to a goal, returning new subgoals.
@@ -103,27 +110,23 @@ Applies a backward rule to a goal, returning new subgoals.
2. Assigns the goal metavariable to the theorem application
3. Returns new goals for unassigned arguments (per `resultPos`)
Returns `none` if unification fails.
Returns `.notApplicable` if unification fails.
-/
public def BackwardRule.apply? (mvarId : MVarId) (rule : BackwardRule) : SymM (Option (List MVarId)) := mvarId.withContext do
public def BackwardRule.apply (mvarId : MVarId) (rule : BackwardRule) : SymM ApplyResult := mvarId.withContext do
let decl mvarId.getDecl
if let some result rule.pattern.unify? decl.type then
mvarId.assign (mkValue rule.expr rule.pattern result)
return some <| rule.resultPos.map fun i =>
return .goals <| rule.resultPos.map fun i =>
result.args[i]!.mvarId!
else
return none
return .failed
/--
Similar to `BackwardRule.apply?`, but throws an error if unification fails.
Similar to `BackwardRule.apply', but throws an error if unification fails.
-/
public def BackwardRule.apply (mvarId : MVarId) (rule : BackwardRule) : SymM (List MVarId) := mvarId.withContext do
let decl mvarId.getDecl
if let some result rule.pattern.unify? decl.type then
mvarId.assign (mkValue rule.expr rule.pattern result)
return rule.resultPos.map fun i =>
result.args[i]!.mvarId!
else
throwError "rule is not applicable to goal{mvarId}rule:{indentExpr rule.expr}"
public def BackwardRule.apply' (mvarId : MVarId) (rule : BackwardRule) : SymM (List MVarId) := do
let .goals mvarIds rule.apply mvarId
| throwError "rule is not applicable to goal{mvarId}rule:{indentExpr rule.expr}"
return mvarIds
end Lean.Meta.Sym

View File

@@ -0,0 +1,53 @@
/-
Copyright (c) 2026 Amazon.com, Inc. or its affiliates. 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.ExprPtr
public import Lean.Meta.Basic
import Lean.Meta.Transform
namespace Lean.Meta.Sym
/--
Checks if `body` is eta-expanded with `n` applications: `f (.bvar (n-1)) ... (.bvar 0)`.
Returns `f` if so and `f` has no loose bvars; otherwise returns `default`.
- `n`: number of remaining applications to check
- `i`: expected bvar index (starts at 0, increments with each application)
- `default`: returned when not eta-reducible (enables pointer equality check)
-/
def etaReduceAux (body : Expr) (n : Nat) (i : Nat) (default : Expr) : Expr := Id.run do
match n with
| 0 => if body.hasLooseBVars then default else body
| n+1 =>
let .app f (.bvar j) := body | default
if j == i then etaReduceAux f n (i+1) default else default
/--
If `e` is of the form `(fun x₁ ... xₙ => f x₁ ... xₙ)` and `f` does not contain `x₁`, ..., `xₙ`,
then returns `f`. Otherwise, returns `e`.
Returns the original expression when not reducible to enable pointer equality checks.
-/
public def etaReduce (e : Expr) : Expr :=
go e 0
where
go (body : Expr) (n : Nat) : Expr :=
match body with
| .lam _ _ b _ => go b (n+1)
| _ => if n == 0 then e else etaReduceAux body n 0 e
/-- Returns `true` if `e` can be eta-reduced. Uses pointer equality for efficiency. -/
public def isEtaReducible (e : Expr) : Bool :=
!isSameExpr e (etaReduce e)
/-- Applies `etaReduce` to all subexpressions. Returns `e` unchanged if no subexpression is eta-reducible. -/
public def etaReduceAll (e : Expr) : MetaM Expr := do
unless Option.isSome <| e.find? isEtaReducible do return e
let pre (e : Expr) : MetaM TransformStep := do
let e' := etaReduce e
if isSameExpr e e' then return .continue
else return .visit e'
Meta.transform e (pre := pre)
end Lean.Meta.Sym

View File

@@ -0,0 +1,129 @@
/-
Copyright (c) 2026 Amazon.com, Inc. or its affiliates. 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.Tactic.Grind.Types
public import Lean.Meta.Sym.Simp.SimpM
public import Lean.Meta.Sym.Apply
import Lean.Meta.Tactic.Grind.Main
import Lean.Meta.Sym.Simp.Goal
import Lean.Meta.Sym.Intro
import Lean.Meta.Sym.Util
import Lean.Meta.Tactic.Grind.Solve
import Lean.Meta.Tactic.Assumption
namespace Lean.Meta.Grind
/-!
# Grind Goal API for Symbolic Simulation
This module provides an API for building symbolic simulation engines and
verification condition generators on top of `grind`. It wraps `Sym` operations
to work with `grind`'s `Goal` type, enabling users to carry `grind` state
through symbolic execution while using lightweight `Sym` operations for
the main loop.
## Typical usage pattern
```
let goal ← mkGoal mvarId
let .goal xs goal ← goal.introN 2 | failure
let .goal goal ← goal.simp methods | failure
let goal ← goal.internalizeAll
-- ... symbolic execution loop using goal.apply ...
let .closed ← goal.grind | failure
```
## Design
Operations like `introN`, `apply`, and `simp` run in `SymM` for performance.
`internalize` and `grind` run in `GrindM` to access the E-graph.
-/
/--
Creates a `Goal` from an `MVarId`, applying `Sym` preprocessing.
Preprocessing ensures the goal is compatible with `Sym` operations.
-/
public def mkGoal (mvarId : MVarId) : GrindM Goal := do
let mvarId Sym.preprocessMVar mvarId
mkGoalCore mvarId
open Sym (SymM)
public inductive IntrosResult where
| failed
| goal (newDecls : Array FVarId) (goal : Goal)
/-- Introduces `num` binders from the goal's target. -/
public def Goal.introN (goal : Goal) (num : Nat) : SymM IntrosResult := do
let .goal xs mvarId Sym.introN goal.mvarId num | return .failed
return .goal xs { goal with mvarId }
/-- Introduces binders with the specified names. -/
public def Goal.intros (goal : Goal) (names : Array Name) : SymM IntrosResult := do
let .goal xs mvarId Sym.intros goal.mvarId names | return .failed
return .goal xs { goal with mvarId }
public inductive ApplyResult where
| failed
| goals (subgoals : List Goal)
/-- Applies a backward rule, returning subgoals on success. -/
public def Goal.apply (goal : Goal) (rule : Sym.BackwardRule) : SymM ApplyResult := do
let .goals mvarIds rule.apply goal.mvarId | return .failed
return .goals <| mvarIds.map fun mvarId => { goal with mvarId }
public inductive SimpGoalResult where
| noProgress
| closed
| goal (goal : Goal)
/-- Simplifies the goal using the given methods. -/
public def Goal.simp (goal : Goal) (methods : Sym.Simp.Methods := {}) (config : Sym.Simp.Config := {}) : SymM SimpGoalResult := do
match ( Sym.simpGoal goal.mvarId methods config) with
| .goal mvarId => return .goal { goal with mvarId }
| .noProgress => return .noProgress
| .closed => return .closed
/-- Like `simp`, but returns the original goal unchanged when no progress is made. -/
public def Goal.simpIgnoringNoProgress (goal : Goal) (methods : Sym.Simp.Methods := {}) (config : Sym.Simp.Config := {}) : SymM SimpGoalResult := do
match ( Sym.simpGoal goal.mvarId methods config) with
| .goal mvarId => return .goal { goal with mvarId }
| .noProgress => return .goal goal
| .closed => return .closed
/--
Internalizes the next `num` hypotheses from the local context into the `grind` state (e.g., its E-graph).
-/
public def Goal.internalize (goal : Goal) (num : Nat) : GrindM Goal := do
Grind.processHypotheses goal (some num)
/-- Internalizes all (un-internalized) hypotheses from the local context into the `grind` state. -/
public def Goal.internalizeAll (goal : Goal) : GrindM Goal := do
Grind.processHypotheses goal none
public inductive GrindResult where
| failed (goal : Goal)
| closed
/--
Attempts to close the goal using `grind`.
Returns `.closed` on success, or `.failed` with the first subgoal that failed to be closed.
-/
public def Goal.grind (goal : Goal) : GrindM GrindResult := do
if let some failure solve goal then
return .failed failure
else
return .closed
/--
Closes the goal if its target matches a hypothesis.
Returns `true` on success.
-/
public def Goal.assumption (goal : Goal) : MetaM Bool := do
-- **TODO**: add indexing
goal.mvarId.assumptionCore
end Lean.Meta.Grind

View File

@@ -96,48 +96,39 @@ def introCore (mvarId : MVarId) (max : Nat) (names : Array Name) : SymM (Array F
def hugeNat := 1000000
public inductive IntrosResult where
| failed
| goal (newDecls : Array FVarId) (mvarId : MVarId)
/--
Introduces leading binders (universal quantifiers and let-expressions) from the goal's target type.
If `names` is non-empty, introduces (at most) `names.size` binders using the provided names.
If `names` is empty, introduces all leading binders using inaccessible names.
Returns the introduced free variable Ids and the updated goal.
Throws an error if the target type does not have a leading binder.
Returns `.goal newDecls mvarId` with new introduced free variable Ids and the updated goal.
Returns `.failed` if no new declaration was introduced.
-/
public def intros (mvarId : MVarId) (names : Array Name := #[]) : SymM (Array FVarId × MVarId) := do
public def intros (mvarId : MVarId) (names : Array Name := #[]) : SymM IntrosResult := do
let result if names.isEmpty then
introCore mvarId hugeNat #[]
else
introCore mvarId names.size names
if result.1.isEmpty then
throwError "`intros` failed, binder expected"
return result
/--
Introduces a single binder from the goal's target type with the given name.
Returns the introduced free variable ID and the updated goal.
Throws an error if the target type does not have a leading binder.
-/
public def intro (mvarId : MVarId) (name : Name) : SymM (FVarId × MVarId) := do
let (fvarIds, goal') introCore mvarId 1 #[name]
if h : 0 < fvarIds.size then
return (fvarIds[0], goal')
else
throwError "`intro` failed, binder expected"
return .failed
return .goal result.1 result.2
/--
Introduces exactly `num` binders from the goal's target type.
Returns the introduced free variable IDs and the updated goal.
Throws an error if the target type has fewer than `num` leading binders.
Returns `.goal newDecls mvarId` if successful where `newDecls` are the introduced free variable IDs,
`mvarId` the updated goal.
Returns `.failed` if it was not possible to introduce `num` new local declarations.
-/
public def introN (mvarId : MVarId) (num : Nat) : SymM (Array FVarId × MVarId) := do
public def introN (mvarId : MVarId) (num : Nat) : SymM IntrosResult := do
let result introCore mvarId num #[]
unless result.1.size == num do
throwError "`introN` failed, insufficient number of binders"
return result
return .failed
return .goal result.1 result.2
end Lean.Meta.Sym

View File

@@ -73,4 +73,14 @@ def getFinValue? (e : Expr) : OptionT Id FinValue := do
let : NeZero n := h
return { n, val := Fin.ofNat n v }
def getCharValue? (e : Expr) : OptionT Id Char := do
let_expr Char.ofNat n := e | failure
let .lit (.natVal n) := n | failure
return Char.ofNat n
def getStringValue? (e : Expr) : Option String :=
match e with
| .lit (.strVal s) => some s
| _ => none
end Lean.Meta.Sym

View File

@@ -18,6 +18,7 @@ import Lean.Meta.Sym.ProofInstInfo
import Lean.Meta.Sym.AlphaShareBuilder
import Lean.Meta.Sym.LitValues
import Lean.Meta.Sym.Offset
import Lean.Meta.Sym.Eta
namespace Lean.Meta.Sym
open Internal
@@ -64,7 +65,11 @@ def mkProofInstInfoMapFor (pattern : Expr) : MetaM (AssocList Name ProofInstInfo
public structure Pattern where
levelParams : List Name
varTypes : Array Expr
isInstance : Array Bool
/--
If `some argsInfo`, `argsInfo` stores whether the pattern variables are instances/proofs.
It is `none` if no pattern variables are instance/proof.
-/
varInfos? : Option ProofInstInfo
pattern : Expr
fnInfos : AssocList Name ProofInstInfo
/--
@@ -78,6 +83,16 @@ public structure Pattern where
def uvarPrefix : Name := `_uvar
/-- Returns `true` if the `i`th argument / pattern variable is an instance. -/
def Pattern.isInstance (p : Pattern) (i : Nat) : Bool := Id.run do
let some varInfos := p.varInfos? | return false
varInfos.argsInfo[i]!.isInstance
/-- Returns `true` if the `i`th argument / pattern variable is a proof. -/
def Pattern.isProof (p : Pattern) (i : Nat) : Bool := Id.run do
let some varInfos := p.varInfos? | return false
varInfos.argsInfo[i]!.isProof
def isUVar? (n : Name) : Option Nat := Id.run do
let .num p idx := n | return none
unless p == uvarPrefix do return none
@@ -144,12 +159,13 @@ where
else
mask
def mkPatternCore (levelParams : List Name) (varTypes : Array Expr) (isInstance : Array Bool)
(pattern : Expr) : MetaM Pattern := do
def mkPatternCore (type : Expr) (levelParams : List Name) (varTypes : Array Expr) (pattern : Expr) : MetaM Pattern := do
let fnInfos mkProofInstInfoMapFor pattern
let checkTypeMask := mkCheckTypeMask pattern varTypes.size
let checkTypeMask? := if checkTypeMask.all (· == false) then none else some checkTypeMask
return { levelParams, varTypes, isInstance, pattern, fnInfos, checkTypeMask? }
let varInfos? forallBoundedTelescope type varTypes.size fun xs _ =>
mkProofInstArgInfo? xs
return { levelParams, varTypes, pattern, fnInfos, varInfos?, checkTypeMask? }
/--
Creates a `Pattern` from the type of a theorem.
@@ -168,12 +184,12 @@ public def mkPatternFromDecl (declName : Name) (num? : Option Nat := none) : Met
let (levelParams, type) preprocessPattern declName
let hugeNumber := 10000000
let num := num?.getD hugeNumber
let rec go (i : Nat) (type : Expr) (varTypes : Array Expr) (isInstance : Array Bool) : MetaM Pattern := do
let rec go (i : Nat) (pattern : Expr) (varTypes : Array Expr) : MetaM Pattern := do
if i < num then
if let .forallE _ d b _ := type then
return ( go (i+1) b (varTypes.push d) (isInstance.push (isClass? ( getEnv) d).isSome))
mkPatternCore levelParams varTypes isInstance type
go 0 type #[] #[]
if let .forallE _ d b _ := pattern then
return ( go (i+1) b (varTypes.push d))
mkPatternCore type levelParams varTypes pattern
go 0 type #[]
/--
Creates a `Pattern` from an equational theorem, using the left-hand side of the equation.
@@ -188,14 +204,14 @@ Throws an error if the theorem's conclusion is not an equality.
-/
public def mkEqPatternFromDecl (declName : Name) : MetaM (Pattern × Expr) := do
let (levelParams, type) preprocessPattern declName
let rec go (type : Expr) (varTypes : Array Expr) (isInstance : Array Bool) : MetaM (Pattern × Expr) := do
if let .forallE _ d b _ := type then
return ( go b (varTypes.push d) (isInstance.push (isClass? ( getEnv) d).isSome))
let rec go (pattern : Expr) (varTypes : Array Expr) : MetaM (Pattern × Expr) := do
if let .forallE _ d b _ := pattern then
return ( go b (varTypes.push d))
else
let_expr Eq _ lhs rhs := type | throwError "resulting type for `{.ofConstName declName}` is not an equality"
let pattern mkPatternCore levelParams varTypes isInstance lhs
let_expr Eq _ lhs rhs := pattern | throwError "resulting type for `{.ofConstName declName}` is not an equality"
let pattern mkPatternCore type levelParams varTypes lhs
return (pattern, rhs)
go type #[] #[]
go type #[]
structure UnifyM.Context where
pattern : Pattern
@@ -308,7 +324,11 @@ def isAssignedMVar (e : Expr) : MetaM Bool :=
| _ => return false
partial def process (p : Expr) (e : Expr) : UnifyM Bool := do
match p with
let e' := etaReduce e
if !isSameExpr e e' then
-- **Note**: We eagerly eta reduce patterns
process p e'
else match p with
| .bvar bidx => assignExpr bidx e
| .mdata _ p => process p e
| .const declName us =>
@@ -708,7 +728,12 @@ def isDefEqApp (tFn : Expr) (t : Expr) (s : Expr) (_ : tFn = t.getAppFn) : DefEq
@[export lean_sym_def_eq]
def isDefEqMainImpl (t : Expr) (s : Expr) : DefEqM Bool := do
if isSameExpr t s then return true
match t, s with
-- **Note**: `etaReduce` is supposed to be fast, and does not allocate memory
let t' := etaReduce t
let s' := etaReduce s
if !isSameExpr t t' || !isSameExpr s s' then
isDefEqMain t' s'
else match t, s with
| .lit l₁, .lit l₂ => return l₁ == l₂
| .sort u, .sort v => isLevelDefEqS u v
| .lam .., .lam .. => isDefEqBindingS t s
@@ -799,7 +824,6 @@ def mkPreResult : UnifyM MkPreResultResult := do
| none => mkFreshLevelMVar
let pattern := ( read).pattern
let varTypes := pattern.varTypes
let isInstance := pattern.isInstance
let eAssignment := ( get).eAssignment
let tPending := ( get).tPending
let mut args := #[]
@@ -820,7 +844,7 @@ def mkPreResult : UnifyM MkPreResultResult := do
let type := varTypes[i]!
let type instantiateLevelParamsS type pattern.levelParams us
let type instantiateRevBetaS type args
if isInstance[i]! then
if pattern.isInstance i then
if let .some val trySynthInstance type then
args := args.push ( shareCommon val)
continue

View File

@@ -7,16 +7,39 @@ module
prelude
public import Lean.Meta.Sym.SymM
import Lean.Meta.Sym.IsClass
import Lean.Meta.Tactic.Grind.Util
import Lean.Meta.Sym.Util
import Lean.Meta.Transform
import Lean.Meta.Sym.Eta
namespace Lean.Meta.Sym
/--
Preprocesses types that used for pattern matching and unification.
-/
public def preprocessType (type : Expr) : MetaM Expr := do
let type Grind.unfoldReducible type
let type Sym.unfoldReducible type
let type Core.betaReduce type
zetaReduce type
let type zetaReduce type
etaReduceAll type
/--
Analyzes whether the given free variables (aka arguments) are proofs or instances.
Returns `none` if no arguments are proofs or instances.
-/
public def mkProofInstArgInfo? (xs : Array Expr) : MetaM (Option ProofInstInfo) := do
let env getEnv
let mut argsInfo := #[]
let mut found := false
for x in xs do
let type Meta.inferType x
let isInstance := isClass? env type |>.isSome
let isProof isProp type
if isInstance || isProof then
found := true
argsInfo := argsInfo.push { isInstance, isProof }
if found then
return some { argsInfo }
else
return none
/--
Analyzes the type signature of `declName` and returns information about which arguments
@@ -25,21 +48,7 @@ are proofs or instances. Returns `none` if no arguments are proofs or instances.
public def mkProofInstInfo? (declName : Name) : MetaM (Option ProofInstInfo) := do
let info getConstInfo declName
let type preprocessType info.type
forallTelescopeReducing type fun xs _ => do
let env getEnv
let mut argsInfo := #[]
let mut found := false
for x in xs do
let type Meta.inferType x
let isInstance := isClass? env type |>.isSome
let isProof isProp type
if isInstance || isProof then
found := true
argsInfo := argsInfo.push { isInstance, isProof }
if found then
return some { argsInfo }
else
return none
forallTelescopeReducing type fun xs _ => mkProofInstArgInfo? xs
/--
Returns information about the type signature of `declName`. It contains information about which arguments

View File

@@ -21,3 +21,5 @@ public import Lean.Meta.Sym.Simp.Debug
public import Lean.Meta.Sym.Simp.EvalGround
public import Lean.Meta.Sym.Simp.Discharger
public import Lean.Meta.Sym.Simp.ControlFlow
public import Lean.Meta.Sym.Simp.Goal
public import Lean.Meta.Sym.Simp.Telescope

View File

@@ -224,7 +224,7 @@ position. However, the type is only meaningful (non-`default`) when `Result` is
`.step`, since we only need types for constructing congruence proofs. This avoids
unnecessary type inference when no rewriting occurs.
-/
def simpFixedPrefix (e : Expr) (prefixSize : Nat) (suffixSize : Nat) : SimpM Result := do
public def simpFixedPrefix (e : Expr) (prefixSize : Nat) (suffixSize : Nat) : SimpM Result := do
let numArgs := e.getAppNumArgs
if numArgs prefixSize then
-- Nothing to be done
@@ -274,7 +274,7 @@ Uses `rewritable[i]` to determine whether argument `i` should be simplified.
For rewritable arguments, calls `simp` and uses `congrFun'`, `congrArg`, and `congr`; for fixed arguments,
uses `congrFun` to propagate changes from earlier arguments.
-/
def simpInterlaced (e : Expr) (rewritable : Array Bool) : SimpM Result := do
public def simpInterlaced (e : Expr) (rewritable : Array Bool) : SimpM Result := do
let numArgs := e.getAppNumArgs
if h : numArgs = 0 then
-- Nothing to be done

View File

@@ -27,16 +27,16 @@ def simpIte : Simproc := fun e => do
let_expr f@ite α c _ a b := e | return .rfl
match ( simp c) with
| .rfl _ =>
if c.isTrue then
if ( isTrueExpr c) then
return .step a <| mkApp3 (mkConst ``ite_true f.constLevels!) α a b
else if c.isFalse then
else if ( isFalseExpr c) then
return .step b <| mkApp3 (mkConst ``ite_false f.constLevels!) α a b
else
return .rfl (done := true)
| .step c' h _ =>
if c'.isTrue then
if ( isTrueExpr c') then
return .step a <| mkApp (e.replaceFn ``ite_cond_eq_true) h
else if c'.isFalse then
else if ( isFalseExpr c') then
return .step b <| mkApp (e.replaceFn ``ite_cond_eq_false) h
else
let .some inst' trySynthInstance (mkApp (mkConst ``Decidable) c') | return .rfl
@@ -56,20 +56,20 @@ def simpDIte : Simproc := fun e => do
let_expr f@dite α c _ a b := e | return .rfl
match ( simp c) with
| .rfl _ =>
if c.isTrue then
if ( isTrueExpr c) then
let a' share <| a.betaRev #[mkConst ``True.intro]
return .step a' <| mkApp3 (mkConst ``dite_true f.constLevels!) α a b
else if c.isFalse then
else if ( isFalseExpr c) then
let b' share <| b.betaRev #[mkConst ``not_false]
return .step b' <| mkApp3 (mkConst ``dite_false f.constLevels!) α a b
else
return .rfl (done := true)
| .step c' h _ =>
if c'.isTrue then
if ( isTrueExpr c') then
let h' shareCommon <| mkOfEqTrueCore c h
let a share <| a.betaRev #[h']
return .step a <| mkApp (e.replaceFn ``dite_cond_eq_true) h
else if c'.isFalse then
else if ( isFalseExpr c') then
let h' shareCommon <| mkOfEqFalseCore c h
let b share <| b.betaRev #[h']
return .step b <| mkApp (e.replaceFn ``dite_cond_eq_false) h
@@ -94,16 +94,16 @@ def simpCond : Simproc := fun e => do
let_expr f@cond α c a b := e | return .rfl
match ( simp c) with
| .rfl _ =>
if c.isConstOf ``true then
if isSameExpr c ( getBoolTrueExpr) then
return .step a <| mkApp3 (mkConst ``cond_true f.constLevels!) α a b
else if c.isConstOf ``false then
else if isSameExpr c ( getBoolFalseExpr) then
return .step b <| mkApp3 (mkConst ``cond_false f.constLevels!) α a b
else
return .rfl (done := true)
| .step c' h _ =>
if c'.isConstOf ``true then
if isSameExpr c' ( getBoolTrueExpr) then
return .step a <| mkApp (e.replaceFn ``Sym.cond_cond_eq_true) h
else if c'.isConstOf ``false then
else if isSameExpr c' ( getBoolFalseExpr) then
return .step b <| mkApp (e.replaceFn ``Sym.cond_cond_eq_false) h
else
let e' := e.getBoundedAppFn 3

View File

@@ -9,6 +9,7 @@ public import Lean.Meta.Sym.Simp.SimpM
public import Lean.Meta.Sym.Simp.Discharger
import Lean.Meta.Sym.Simp.Theorems
import Lean.Meta.Sym.Simp.Rewrite
import Lean.Meta.Sym.Simp.Goal
import Lean.Meta.Sym.Util
import Lean.Meta.Tactic.Util
import Lean.Meta.AppBuilder
@@ -27,24 +28,9 @@ public def mkSimprocFor (declNames : Array Name) (d : Discharger := dischargeNon
public def mkMethods (declNames : Array Name) : MetaM Methods := do
return { post := ( mkSimprocFor declNames) }
public def simpWith (k : Expr SymM Result) (mvarId : MVarId) : MetaM (Option MVarId) := SymM.run do
let mvarId preprocessMVar mvarId
let decl mvarId.getDecl
let target := decl.type
match ( k target) with
| .rfl _ => throwError "`Sym.simp` made no progress "
| .step target' h _ =>
let mvarNew mkFreshExprSyntheticOpaqueMVar target' decl.userName
let h mkAppM ``Eq.mpr #[h, mvarNew]
mvarId.assign h
if target'.isTrue then
mvarNew.mvarId!.assign (mkConst ``True.intro)
return none
else
return some mvarNew.mvarId!
public def simpGoal (declNames : Array Name) (mvarId : MVarId) : MetaM (Option MVarId) := SymM.run do mvarId.withContext do
public def simpGoalUsing (declNames : Array Name) (mvarId : MVarId) : MetaM (Option MVarId) := SymM.run do
let methods mkMethods declNames
simpWith (simp · methods) mvarId
let mvarId preprocessMVar mvarId
( simpGoal mvarId methods).toOption
end Lean.Meta.Sym

View File

@@ -9,6 +9,7 @@ public import Lean.Meta.Sym.Simp.SimpM
import Init.Sym.Lemmas
import Init.Data.Int.Gcd
import Lean.Meta.Sym.LitValues
import Lean.Meta.Sym.AlphaShareBuilder
namespace Lean.Meta.Sym.Simp
/-!
@@ -343,10 +344,10 @@ abbrev evalBinPred (toValue? : Expr → Option α) (trueThm falseThm : Expr) (op
let some va := toValue? a | return .rfl
let some vb := toValue? b | return .rfl
if op va vb then
let e share <| mkConst ``True
let e getTrueExpr
return .step e (mkApp3 trueThm a b eagerReflBoolTrue) (done := true)
else
let e share <| mkConst ``False
let e getFalseExpr
return .step e (mkApp3 falseThm a b eagerReflBoolFalse) (done := true)
def evalBitVecPred (n : Expr) (trueThm falseThm : Expr) (op : {n : Nat} BitVec n BitVec n Bool) (a b : Expr) : SimpM Result := do
@@ -354,10 +355,10 @@ def evalBitVecPred (n : Expr) (trueThm falseThm : Expr) (op : {n : Nat} → BitV
let some vb := getBitVecValue? b | return .rfl
if h : va.n = vb.n then
if op va.val (h vb.val) then
let e share <| mkConst ``True
let e getTrueExpr
return .step e (mkApp4 trueThm n a b eagerReflBoolTrue) (done := true)
else
let e share <| mkConst ``False
let e getFalseExpr
return .step e (mkApp4 falseThm n a b eagerReflBoolFalse) (done := true)
else
return .rfl
@@ -367,10 +368,10 @@ def evalFinPred (n : Expr) (trueThm falseThm : Expr) (op : {n : Nat} → Fin n
let some vb := getFinValue? b | return .rfl
if h : va.n = vb.n then
if op va.val (h vb.val) then
let e share <| mkConst ``True
let e getTrueExpr
return .step e (mkApp4 trueThm n a b eagerReflBoolTrue) (done := true)
else
let e share <| mkConst ``False
let e getFalseExpr
return .step e (mkApp4 falseThm n a b eagerReflBoolFalse) (done := true)
else
return .rfl
@@ -392,6 +393,8 @@ def evalLT (α : Expr) (a b : Expr) : SimpM Result :=
| UInt64 => evalBinPred getUInt64Value? (mkConst ``UInt64.lt_eq_true) (mkConst ``UInt64.lt_eq_false) (. < .) a b
| Fin n => evalFinPred n (mkConst ``Fin.lt_eq_true) (mkConst ``Fin.lt_eq_false) (. < .) a b
| BitVec n => evalBitVecPred n (mkConst ``BitVec.lt_eq_true) (mkConst ``BitVec.lt_eq_false) (. < .) a b
| String => evalBinPred getStringValue? (mkConst ``String.lt_eq_true) (mkConst ``String.lt_eq_false) (. < .) a b
| Char => evalBinPred getCharValue? (mkConst ``Char.lt_eq_true) (mkConst ``Char.lt_eq_false) (. < .) a b
| _ => return .rfl
def evalLE (α : Expr) (a b : Expr) : SimpM Result :=
@@ -409,45 +412,13 @@ def evalLE (α : Expr) (a b : Expr) : SimpM Result :=
| UInt64 => evalBinPred getUInt64Value? (mkConst ``UInt64.le_eq_true) (mkConst ``UInt64.le_eq_false) (. .) a b
| Fin n => evalFinPred n (mkConst ``Fin.le_eq_true) (mkConst ``Fin.le_eq_false) (. .) a b
| BitVec n => evalBitVecPred n (mkConst ``BitVec.le_eq_true) (mkConst ``BitVec.le_eq_false) (. .) a b
| _ => return .rfl
def evalGT (α : Expr) (a b : Expr) : SimpM Result :=
match_expr α with
| Nat => evalBinPred getNatValue? (mkConst ``Nat.gt_eq_true) (mkConst ``Nat.gt_eq_false) (. > .) a b
| Int => evalBinPred getIntValue? (mkConst ``Int.gt_eq_true) (mkConst ``Int.gt_eq_false) (. > .) a b
| Rat => evalBinPred getRatValue? (mkConst ``Rat.gt_eq_true) (mkConst ``Rat.gt_eq_false) (. > .) a b
| Int8 => evalBinPred getInt8Value? (mkConst ``Int8.gt_eq_true) (mkConst ``Int8.gt_eq_false) (. > .) a b
| Int16 => evalBinPred getInt16Value? (mkConst ``Int16.gt_eq_true) (mkConst ``Int16.gt_eq_false) (. > .) a b
| Int32 => evalBinPred getInt32Value? (mkConst ``Int32.gt_eq_true) (mkConst ``Int32.gt_eq_false) (. > .) a b
| Int64 => evalBinPred getInt64Value? (mkConst ``Int64.gt_eq_true) (mkConst ``Int64.gt_eq_false) (. > .) a b
| UInt8 => evalBinPred getUInt8Value? (mkConst ``UInt8.gt_eq_true) (mkConst ``UInt8.gt_eq_false) (. > .) a b
| UInt16 => evalBinPred getUInt16Value? (mkConst ``UInt16.gt_eq_true) (mkConst ``UInt16.gt_eq_false) (. > .) a b
| UInt32 => evalBinPred getUInt32Value? (mkConst ``UInt32.gt_eq_true) (mkConst ``UInt32.gt_eq_false) (. > .) a b
| UInt64 => evalBinPred getUInt64Value? (mkConst ``UInt64.gt_eq_true) (mkConst ``UInt64.gt_eq_false) (. > .) a b
| Fin n => evalFinPred n (mkConst ``Fin.gt_eq_true) (mkConst ``Fin.gt_eq_false) (. > .) a b
| BitVec n => evalBitVecPred n (mkConst ``BitVec.gt_eq_true) (mkConst ``BitVec.gt_eq_false) (. > .) a b
| _ => return .rfl
def evalGE (α : Expr) (a b : Expr) : SimpM Result :=
match_expr α with
| Nat => evalBinPred getNatValue? (mkConst ``Nat.ge_eq_true) (mkConst ``Nat.ge_eq_false) (. .) a b
| Int => evalBinPred getIntValue? (mkConst ``Int.ge_eq_true) (mkConst ``Int.ge_eq_false) (. .) a b
| Rat => evalBinPred getRatValue? (mkConst ``Rat.ge_eq_true) (mkConst ``Rat.ge_eq_false) (. .) a b
| Int8 => evalBinPred getInt8Value? (mkConst ``Int8.ge_eq_true) (mkConst ``Int8.ge_eq_false) (. .) a b
| Int16 => evalBinPred getInt16Value? (mkConst ``Int16.ge_eq_true) (mkConst ``Int16.ge_eq_false) (. .) a b
| Int32 => evalBinPred getInt32Value? (mkConst ``Int32.ge_eq_true) (mkConst ``Int32.ge_eq_false) (. .) a b
| Int64 => evalBinPred getInt64Value? (mkConst ``Int64.ge_eq_true) (mkConst ``Int64.ge_eq_false) (. .) a b
| UInt8 => evalBinPred getUInt8Value? (mkConst ``UInt8.ge_eq_true) (mkConst ``UInt8.ge_eq_false) (. .) a b
| UInt16 => evalBinPred getUInt16Value? (mkConst ``UInt16.ge_eq_true) (mkConst ``UInt16.ge_eq_false) (. .) a b
| UInt32 => evalBinPred getUInt32Value? (mkConst ``UInt32.ge_eq_true) (mkConst ``UInt32.ge_eq_false) (. .) a b
| UInt64 => evalBinPred getUInt64Value? (mkConst ``UInt64.ge_eq_true) (mkConst ``UInt64.ge_eq_false) (. .) a b
| Fin n => evalFinPred n (mkConst ``Fin.ge_eq_true) (mkConst ``Fin.ge_eq_false) (. .) a b
| BitVec n => evalBitVecPred n (mkConst ``BitVec.ge_eq_true) (mkConst ``BitVec.ge_eq_false) (. .) a b
| String => evalBinPred getStringValue? (mkConst ``String.le_eq_true) (mkConst ``String.le_eq_false) (. .) a b
| Char => evalBinPred getCharValue? (mkConst ``Char.le_eq_true) (mkConst ``Char.le_eq_false) (. .) a b
| _ => return .rfl
def evalEq (α : Expr) (a b : Expr) : SimpM Result :=
if isSameExpr a b then do
let e share <| mkConst ``True
let e getTrueExpr
let u getLevel α
return .step e (mkApp2 (mkConst ``eq_self [u]) α a) (done := true)
else match_expr α with
@@ -464,27 +435,8 @@ def evalEq (α : Expr) (a b : Expr) : SimpM Result :=
| UInt64 => evalBinPred getUInt64Value? (mkConst ``UInt64.eq_eq_true) (mkConst ``UInt64.eq_eq_false) (. = .) a b
| Fin n => evalFinPred n (mkConst ``Fin.eq_eq_true) (mkConst ``Fin.eq_eq_false) (. = .) a b
| BitVec n => evalBitVecPred n (mkConst ``BitVec.eq_eq_true) (mkConst ``BitVec.eq_eq_false) (. = .) a b
| _ => return .rfl
def evalNe (α : Expr) (a b : Expr) : SimpM Result :=
if isSameExpr a b then do
let e share <| mkConst ``False
let u getLevel α
return .step e (mkApp2 (mkConst ``ne_self [u]) α a) (done := true)
else match_expr α with
| Nat => evalBinPred getNatValue? (mkConst ``Nat.ne_eq_true) (mkConst ``Nat.ne_eq_false) (. .) a b
| Int => evalBinPred getIntValue? (mkConst ``Int.ne_eq_true) (mkConst ``Int.ne_eq_false) (. .) a b
| Rat => evalBinPred getRatValue? (mkConst ``Rat.ne_eq_true) (mkConst ``Rat.ne_eq_false) (. .) a b
| Int8 => evalBinPred getInt8Value? (mkConst ``Int8.ne_eq_true) (mkConst ``Int8.ne_eq_false) (. .) a b
| Int16 => evalBinPred getInt16Value? (mkConst ``Int16.ne_eq_true) (mkConst ``Int16.ne_eq_false) (. .) a b
| Int32 => evalBinPred getInt32Value? (mkConst ``Int32.ne_eq_true) (mkConst ``Int32.ne_eq_false) (. .) a b
| Int64 => evalBinPred getInt64Value? (mkConst ``Int64.ne_eq_true) (mkConst ``Int64.ne_eq_false) (. .) a b
| UInt8 => evalBinPred getUInt8Value? (mkConst ``UInt8.ne_eq_true) (mkConst ``UInt8.ne_eq_false) (. .) a b
| UInt16 => evalBinPred getUInt16Value? (mkConst ``UInt16.ne_eq_true) (mkConst ``UInt16.ne_eq_false) (. .) a b
| UInt32 => evalBinPred getUInt32Value? (mkConst ``UInt32.ne_eq_true) (mkConst ``UInt32.ne_eq_false) (. .) a b
| UInt64 => evalBinPred getUInt64Value? (mkConst ``UInt64.ne_eq_true) (mkConst ``UInt64.ne_eq_false) (. .) a b
| Fin n => evalFinPred n (mkConst ``Fin.ne_eq_true) (mkConst ``Fin.ne_eq_false) (. .) a b
| BitVec n => evalBitVecPred n (mkConst ``BitVec.ne_eq_true) (mkConst ``BitVec.ne_eq_false) (. .) a b
| Char => evalBinPred getCharValue? (mkConst ``Char.eq_eq_true) (mkConst ``Char.eq_eq_false) (. = .) a b
| String => evalBinPred getStringValue? (mkConst ``String.eq_eq_true) (mkConst ``String.eq_eq_false) (. = .) a b
| _ => return .rfl
def evalDvd (α : Expr) (a b : Expr) : SimpM Result :=
@@ -554,6 +506,16 @@ macro "declare_eval_bin_bool_pred" id:ident op:term : command =>
declare_eval_bin_bool_pred evalBEq (· == ·)
declare_eval_bin_bool_pred evalBNe (· != ·)
open Internal in
def evalNot (a : Expr) : SimpM Result :=
/-
**Note**: We added `evalNot` because some abbreviations expanded into `Not`s.
-/
match_expr a with
| True => return .step ( getFalseExpr) (mkConst ``Sym.not_true_eq) (done := true)
| False => return .step ( getTrueExpr) (mkConst ``Sym.not_false_eq) (done := true)
| _ => return .rfl
public structure EvalStepConfig where
maxExponent := 255
@@ -594,14 +556,12 @@ public def evalGround (config : EvalStepConfig := {}) : Simproc := fun e =>
| Int.fmod a b => evalBinInt Int.fmod a b
| Int.bmod a b => evalIntBMod a b
| LE.le α _ a b => evalLE α a b
| GE.ge α _ a b => evalGE α a b
| LT.lt α _ a b => evalLT α a b
| GT.gt α _ a b => evalGT α a b
| Dvd.dvd α _ a b => evalDvd α a b
| Eq α a b => evalEq α a b
| Ne α a b => evalNe α a b
| BEq.beq α _ a b => evalBEq α a b
| bne α _ a b => evalBNe α a b
| Not a => evalNot a
| _ => return .rfl
end Lean.Meta.Sym.Simp

View File

@@ -7,6 +7,8 @@ module
prelude
public import Lean.Meta.Sym.Simp.SimpM
import Lean.Meta.Sym.AlphaShareBuilder
import Lean.Meta.Sym.InferType
import Lean.Meta.Sym.Simp.Result
namespace Lean.Meta.Sym.Simp
/--
@@ -25,7 +27,7 @@ The proof uses the approach used in `mkFunextFor` followed by an `Eq.ndrec`.
def mkForallCongrFor (xs : Array Expr) : MetaM Expr := do
let prop := mkSort 0
let type mkForallFVars xs prop
let w getLevel type
let w Meta.getLevel type
withLocalDeclD `p type fun p =>
withLocalDeclD `q type fun q => do
let eq := mkApp3 (mkConst ``Eq [1]) prop (mkAppN p xs) (mkAppN q xs)
@@ -53,6 +55,119 @@ def mkForallCongrFor (xs : Array Expr) : MetaM Expr := do
open Internal
structure ArrowInfo where
binderName : Name
binderInfo : BinderInfo
u : Level
v : Level
structure ToArrowResult where
arrow : Expr
infos : List ArrowInfo
v : Level
def toArrow (e : Expr) : SymM ToArrowResult := do
if let .forallE n α β bi := e then
if !β.hasLooseBVars then
let { arrow, infos, v } toArrow β
let u getLevel α
let arrow mkAppS₂ ( mkConstS ``Arrow [u, v]) α arrow
let info := { binderName := n, binderInfo := bi, u, v }
return { arrow, v := mkLevelIMax' u v, infos := info :: infos }
return { arrow := e, infos := [], v := ( getLevel e) }
def toForall (e : Expr) (infos : List ArrowInfo) : SymM Expr := do
let { binderName, binderInfo, .. } :: infos := infos | return e
let_expr Arrow α β := e | return e
mkForallS binderName binderInfo α ( toForall β infos)
/--
Recursively simplifies an `Arrow` telescope, applying telescope-specific simplifications:
- **False hypothesis**: `False → q` simplifies to `True` (via `false_arrow`)
- **True hypothesis**: `True → q` simplifies to `q` (via `true_arrow`)
- **True conclusion**: `p → True` simplifies to `True` (via `arrow_true`)
The first two are applicable only if `q` is in `Prop` (checked via `info.v.isZero`).
Returns the simplified result paired with the remaining `ArrowInfo` list. When a telescope
collapses (e.g., to `True`), the returned `infos` list is empty, signaling to `toForall`
that no reconstruction is needed.
-/
partial def simpArrows (e : Expr) (infos : List ArrowInfo) (simpBody : Simproc) : SimpM (Result × List ArrowInfo) := do
match infos with
| [] => return (( simpBody e), [])
| info :: infos' =>
let_expr f@Arrow p q := e | return (( simpBody e), infos)
let p_r simp p
if ( isFalseExpr (p_r.getResultExpr p)) && info.v.isZero then
match p_r with
| .rfl _ => return (.step ( getTrueExpr) (mkApp (mkConst ``false_arrow) q), [])
| .step _ h _ => return (.step ( getTrueExpr) (mkApp3 (mkConst ``false_arrow_congr) p q h), [])
let (q_r, infos') simpArrows q infos' simpBody
if ( isTrueExpr (q_r.getResultExpr q)) then
match q_r with
| .rfl _ => return (.step ( getTrueExpr) (mkApp (mkConst ``arrow_true [info.u]) p), [])
| .step _ h _ => return (.step ( getTrueExpr) (mkApp3 (mkConst ``arrow_true_congr [info.u]) p q h), [])
match p_r, q_r with
| .rfl _, .rfl _ =>
if ( isTrueExpr p) && info.v.isZero then
return (.step q (mkApp (mkConst ``true_arrow) q), infos')
else
return (.rfl, infos)
| .step p' h _, .rfl _ =>
if ( isTrueExpr p') && info.v.isZero then
return (.step q (mkApp3 (mkConst ``true_arrow_congr_left) p q h), infos')
else
let e' mkAppS₂ f p' q
return (.step e' <| mkApp4 (mkConst ``arrow_congr_left f.constLevels!) p p' q h, info :: infos')
| .rfl _, .step q' h _ =>
if ( isTrueExpr p) && info.v.isZero then
return (.step q' (mkApp3 (mkConst ``true_arrow_congr_right) q q' h), infos')
else
let e' mkAppS₂ f p q'
return (.step e' <| mkApp4 (mkConst ``arrow_congr_right f.constLevels!) p q q' h, info :: infos')
| .step p' h₁ _, .step q' h₂ _ =>
if ( isTrueExpr p') && info.v.isZero then
return (.step q' (mkApp5 (mkConst ``true_arrow_congr) p q q' h₁ h₂), infos')
else
let e' mkAppS₂ f p' q'
return (.step e' <| mkApp6 (mkConst ``arrow_congr f.constLevels!) p p' q q' h₁ h₂, info :: infos')
/--
Simplifies a telescope of non-dependent arrows `p₁ → p₂ → ... → pₙ → q` by:
1. Converting to `Arrow p₁ (Arrow p₂ (... (Arrow pₙ q)))` (see `toArrow`)
2. Simplifying each `pᵢ` and `q` (see `simpArrows`)
3. Converting back to `→` form (see `toForall`)
Using `Arrow` (a definitional wrapper around `→`) avoids the quadratic proof growth that
occurs with `Expr.forallE`. With `forallE`, each nesting level bumps de Bruijn indices in
subterms, destroying sharing. For example, if each `pᵢ` contains a free variable `x`, the
de Bruijn representation of `x` differs at each depth, preventing hash-consing from
recognizing them as identical.
With `Arrow`, both arguments are explicit (not under binders), so subterms remain identical
across nesting levels and can be shared, yielding linear-sized proofs.
**Tradeoff**: This function simplifies each `pᵢ` and `q` individually, but misses
simplifications that depend on the arrow structure itself. For example, `q → p → p`
won't be simplified to `True` (when `p : Prop`) because the simplifier does not have
a chance to apply `post` methods to the intermediate arrow `p → p`.
Thus, this is a simproc that is meant to be used as a pre-method and marks the
result as fully simplified to prevent `simpArrow` from being applied.
-/
public def simpArrowTelescope (simpBody : Simproc := simp) : Simproc := fun e => do
unless e.isArrow do return .rfl -- not applicable
let { arrow, infos, v } toArrow e
let (.step arrow' h _, infos) simpArrows arrow infos simpBody | return .rfl (done := true)
let e' toForall arrow' infos
let α := mkSort v
let v1 := v.succ
let h := mkApp6 (mkConst ``Eq.trans [v1]) α e arrow arrow' (mkApp2 (mkConst ``Eq.refl [v1]) α arrow) h
let h := mkApp6 (mkConst ``Eq.trans [v1]) α e arrow' e' h (mkApp2 (mkConst ``Eq.refl [v1]) α e')
return .step e' h (done := true)
public def simpArrow (e : Expr) : SimpM Result := do
let p := e.bindingDomain!
let q := e.bindingBody!
@@ -75,22 +190,22 @@ public def simpArrow (e : Expr) : SimpM Result := do
let e' e.updateForallS! p' q'
return .step e' <| mkApp6 (mkConst ``implies_congr [u, v]) p p' q q' h₁ h₂
public def simpForall (e : Expr) : SimpM Result := do
public def simpForall' (simpArrow : Simproc) (simpBody : Simproc) (e : Expr) : SimpM Result := do
if e.isArrow then
simpArrow e
else if ( isProp e) then
let n := getForallTelescopeSize e.bindingBody! 1
forallBoundedTelescope e n fun xs b => withoutModifyingCacheIfNotWellBehaved do
main xs b
main xs ( shareCommon b)
else
return .rfl
where
main (xs : Array Expr) (b : Expr) : SimpM Result := do
match ( simp b) with
match ( simpBody b) with
| .rfl _ => return .rfl
| .step b' h _ =>
let h mkLambdaFVars xs h
let e' shareCommonInc ( mkForallFVars xs b')
let e' shareCommon ( mkForallFVars xs b')
-- **Note**: consider caching the forall-congr theorems
let hcongr mkForallCongrFor xs
return .step e' (mkApp3 hcongr ( mkLambdaFVars xs b) ( mkLambdaFVars xs b') h)
@@ -101,4 +216,7 @@ where
| .forallE _ _ b _ => if b.hasLooseBVar 0 then getForallTelescopeSize b (n+1) else n
| _ => n
public def simpForall : Simproc :=
simpForall' simpArrow simp
end Lean.Meta.Sym.Simp

View File

@@ -0,0 +1,81 @@
/-
Copyright (c) 2026 Amazon.com, Inc. or its affiliates. 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.Meta.Tactic.Util
import Lean.Meta.AppBuilder
import Lean.Meta.Sym.InferType
namespace Lean.Meta.Sym
/-!
# Goal simplification
Applies `Sym.simp` to a goal's target type, producing a simplified goal or closing it if
the result is `True`.
-/
/-- Result of simplifying a goal with `Sym.simp`. -/
public inductive SimpGoalResult where
/-- No simplification was possible. -/
| noProgress
/-- The goal was closed (simplified to `True`). -/
| closed
/-- The goal was simplified to a new goal. -/
| goal (mvarId : MVarId)
/--
Converts a `SimpGoalResult` to an optional goal.
Returns `none` if closed, `some mvarId` if simplified, or throws an error if no progress.
-/
public def SimpGoalResult.toOption : SimpGoalResult CoreM (Option MVarId)
| .noProgress => throwError "`Sym.simp` made no progress "
| .closed => return none
| .goal mvarId => return some mvarId
public def SimpGoalResult.ignoreNoProgress : SimpGoalResult MVarId SimpGoalResult
| .noProgress, mvarId => .goal mvarId
| r, _ => r
/--
Converts a `Simp.Result` value into `SimpGoalResult`.
-/
public def Simp.Result.toSimpGoalResult (result : Simp.Result) (mvarId : MVarId) : SymM SimpGoalResult := do
let decl mvarId.getDecl
match result with
| .rfl _ => return .noProgress
| .step target' h _ =>
let mvarNew mkFreshExprSyntheticOpaqueMVar target' decl.userName
let u getLevel decl.type
let h := mkApp4 (mkConst ``Eq.mpr [u]) decl.type target' h mvarNew
mvarId.assign h
if target'.isTrue then
mvarNew.mvarId!.assign (mkConst ``True.intro)
return .closed
else
return .goal mvarNew.mvarId!
/--
Simplifies the target of `mvarId` using `Sym.simp`.
Returns `.closed` if the target simplifies to `True`, `.simp mvarId'` if simplified
to a new goal, or `.noProgress` if no simplification occurred.
This function assumed the input goal is a valid `Sym` goal (e.g., expressions are maximally shared).
-/
public def simpGoal (mvarId : MVarId) (methods : Simp.Methods := {}) (config : Simp.Config := {})
: SymM SimpGoalResult := mvarId.withContext do
let decl mvarId.getDecl
( simp decl.type methods config).toSimpGoalResult mvarId
/--
Similar to `simpGoal`, but returns `.goal mvarId` if no progress was made.
-/
public def simpGoalIgnoringNoProgress (mvarId : MVarId) (methods : Simp.Methods := {}) (config : Simp.Config := {})
: SymM SimpGoalResult := do
match ( simpGoal mvarId methods config) with
| .noProgress => return .goal mvarId
| r => return r
end Lean.Meta.Sym

View File

@@ -6,7 +6,7 @@ Authors: Leonardo de Moura
module
prelude
public import Lean.Meta.Sym.Simp.SimpM
import Lean.Meta.Sym.Simp.Lambda
public import Lean.Meta.Sym.Simp.Lambda
import Lean.Meta.Sym.AlphaShareBuilder
import Lean.Meta.Sym.InstantiateS
import Lean.Meta.Sym.ReplaceS
@@ -316,7 +316,8 @@ For each application `f a`:
- If only `a` changed: use `congrArg : a = a' → f a = f a'`
- If neither changed: return `.rfl`
-/
def simpBetaApp (e : Expr) (fType : Expr) (fnUnivs argUnivs : Array Level) : SimpM Result := do
def simpBetaApp (e : Expr) (fType : Expr) (fnUnivs argUnivs : Array Level)
(simpBody : Simproc) : SimpM Result := do
return ( go e 0).1
where
go (e : Expr) (i : Nat) : SimpM (Result × Expr) := do
@@ -339,7 +340,7 @@ where
let h := mkApp6 ( mkCongrPrefix ``congr fType i) f f' a a' hf ha
pure <| .step e' h
return (r, fType.bindingBody!)
| .lam .. => return ( simpLambda e, fType)
| .lam .. => return ( simpBody e, fType)
| _ => unreachable!
mkCongrPrefix (declName : Name) (fType : Expr) (i : Nat) : SymM Expr := do
@@ -375,12 +376,12 @@ e₃ = e₄ (by rfl, definitional equality from toHave)
e₁ = e₄ (by transitivity)
```
-/
def simpHaveCore (e : Expr) : SimpM SimpHaveResult := do
def simpHaveCore (e : Expr) (simpBody : Simproc) : SimpM SimpHaveResult := do
let e₁ := e
let r toBetaApp e₁
let e₂ := r.e
let { fnUnivs, argUnivs } getUnivs r.fType
match ( simpBetaApp e₂ r.fType fnUnivs argUnivs) with
match ( simpBetaApp e₂ r.fType fnUnivs argUnivs simpBody) with
| .rfl _ => return { result := .rfl, α := r.α, u := r.u }
| .step e₃ h _ =>
let h₁ := mkApp6 (mkConst ``Eq.trans [r.u]) r.α e₁ e₂ e₃ r.h h
@@ -397,8 +398,8 @@ Simplify a `have`-telescope.
This is the main entry point for `have`-telescope simplification in `Sym.simp`.
See module documentation for the algorithm overview.
-/
public def simpHave (e : Expr) : SimpM Result := do
return ( simpHaveCore e).result
public def simpHave (e : Expr) (simpBody : Simproc) : SimpM Result := do
return ( simpHaveCore e simpBody).result
/--
Simplify a `have`-telescope and eliminate unused bindings.
@@ -406,8 +407,8 @@ Simplify a `have`-telescope and eliminate unused bindings.
This combines simplification with dead variable elimination in a single pass,
avoiding quadratic behavior from multiple passes.
-/
public def simpHaveAndZetaUnused (e₁ : Expr) : SimpM Result := do
let r simpHaveCore e₁
public def simpHaveAndZetaUnused (e₁ : Expr) (simpBody : Simproc) : SimpM Result := do
let r simpHaveCore e₁ simpBody
match r.result with
| .rfl _ =>
let e₂ zetaUnused e₁
@@ -425,7 +426,7 @@ public def simpHaveAndZetaUnused (e₁ : Expr) : SimpM Result := do
(mkApp2 (mkConst ``Eq.refl [r.u]) r.α e₃)
return .step e₃ h
public def simpLet (e : Expr) : SimpM Result := do
public def simpLet' (simpBody : Simproc) (e : Expr) : SimpM Result := do
if !e.letNondep! then
/-
**Note**: We don't do anything if it is a dependent `let`.
@@ -433,6 +434,9 @@ public def simpLet (e : Expr) : SimpM Result := do
-/
return .rfl
else
simpHaveAndZetaUnused e
simpHaveAndZetaUnused e simpBody
public def simpLet : Simproc :=
simpLet' simpLambda
end Lean.Meta.Sym.Simp

View File

@@ -46,16 +46,16 @@ def mkFunextFor (xs : Array Expr) (β : Expr) : MetaM Expr := do
let result mkLambdaFVars #[f, g, h] result
return result
public def simpLambda (e : Expr) : SimpM Result := do
public def simpLambda' (simpBody : Simproc) (e : Expr) : SimpM Result := do
lambdaTelescope e fun xs b => withoutModifyingCacheIfNotWellBehaved do
main xs b
main xs ( shareCommon b)
where
main (xs : Array Expr) (b : Expr) : SimpM Result := do
match ( simp b) with
match ( simpBody b) with
| .rfl _ => return .rfl
| .step b' h _ =>
let h mkLambdaFVars xs h
let e' shareCommonInc ( mkLambdaFVars xs b')
let e' shareCommon ( mkLambdaFVars xs b')
let funext getFunext xs b
return .step e' (mkApp3 funext e e' h)
@@ -69,4 +69,7 @@ where
modify fun s => { s with funext := s.funext.insert { expr := key } h }
return h
public def simpLambda : Simproc :=
simpLambda' simp
end Lean.Meta.Sym.Simp

View File

@@ -22,4 +22,12 @@ public abbrev mkEqTransResult (e₁ : Expr) (e₂ : Expr) (h₁ : Expr) (r₂ :
| .rfl done => return .step e₂ h₁ done
| .step e₃ h₂ done => return .step e₃ ( mkEqTrans e₁ e₂ h₁ e₃ h₂) done
public def Result.markAsDone : Result Result
| .rfl _ => .rfl true
| .step e h _ => .step e h true
public def Result.getResultExpr : Expr Result Expr
| e, .rfl _ => e
| _, .step e _ _ => e
end Lean.Meta.Sym.Simp

View File

@@ -11,6 +11,7 @@ public import Lean.Meta.Sym.Simp.Theorems
public import Lean.Meta.Sym.Simp.App
public import Lean.Meta.Sym.Simp.Discharger
import Lean.Meta.Sym.InstantiateS
import Lean.Meta.Sym.InstantiateMVarsS
import Lean.Meta.Sym.Simp.DiscrTree
namespace Lean.Meta.Sym.Simp
open Grind
@@ -20,31 +21,48 @@ Creates proof term for a rewriting step.
Handles both constant expressions (common case, avoids `instantiateLevelParams`)
and general expressions.
-/
def mkValue (expr : Expr) (pattern : Pattern) (result : MatchUnifyResult) : Expr :=
def mkValue (expr : Expr) (pattern : Pattern) (us : List Level) (args : Array Expr) : Expr :=
if let .const declName [] := expr then
mkAppN (mkConst declName result.us) result.args
mkAppN (mkConst declName us) args
else
mkAppN (expr.instantiateLevelParams pattern.levelParams result.us) result.args
mkAppN (expr.instantiateLevelParams pattern.levelParams us) args
/--
Tries to rewrite `e` using the given theorem.
-/
public def Theorem.rewrite (thm : Theorem) (e : Expr) (d : Discharger := dischargeNone) : SimpM Result := do
public def Theorem.rewrite (thm : Theorem) (e : Expr) (d : Discharger := dischargeNone) : SimpM Result :=
/-
**Note**: We use `withNewMCtxDepth` to ensure auxiliary metavariables used during the `match?`
do not pollute the metavariable context.
Thus, we must ensure that all assigned variables have be instantiate.
-/
withNewMCtxDepth do
if let some result thm.pattern.match? e then
-- **Note**: Potential optimization: check whether pattern covers all variables.
for arg in result.args do
let .mvar mvarId := arg | pure ()
unless ( mvarId.isAssigned) do
let decl mvarId.getDecl
if let some val d decl.type then
mvarId.assign val
let mut args := result.args.toVector
let us result.us.mapM instantiateLevelMVars
for h : i in *...args.size do
let arg := args[i]
if let .mvar mvarId := arg then
if ( mvarId.isAssigned) then
let arg instantiateMVarsS arg
args := args.set i arg
else
-- **Note**: Failed to discharge hypothesis.
return .rfl
let proof := mkValue thm.expr thm.pattern result
let rhs := thm.rhs.instantiateLevelParams thm.pattern.levelParams result.us
let rhs shareCommonInc rhs
let expr instantiateRevBetaS rhs result.args
let decl mvarId.getDecl
if let some val d decl.type then
let val instantiateMVarsS val
mvarId.assign val
args := args.set i val
else
-- **Note**: Failed to discharge hypothesis.
return .rfl
else if arg.hasMVar then
let arg instantiateMVarsS arg
args := args.set i arg
let proof := mkValue thm.expr thm.pattern us args.toArray
let rhs := thm.rhs.instantiateLevelParams thm.pattern.levelParams us
let rhs share rhs
let expr instantiateRevBetaS rhs args.toArray
if isSameExpr e expr then
return .rfl
else

View File

@@ -101,7 +101,7 @@ invalidating the cache and causing O(2^n) behavior on conditional trees.
/-- Configuration options for the structural simplifier. -/
structure Config where
/-- Maximum number of steps that can be performed by the simplifier. -/
maxSteps : Nat := 1000
maxSteps : Nat := 100_000
/--
Maximum depth of reentrant simplifier calls through dischargers.
Prevents infinite loops when conditional rewrite rules trigger recursive discharge attempts.
@@ -173,16 +173,13 @@ abbrev Cache := PHashMap ExprPtr Result
/-- Mutable state for the simplifier. -/
structure State where
/-- Number of steps performed so far. -/
numSteps := 0
/--
Cache of previously simplified expressions to avoid redundant work.
**Note**: Consider moving to `SymM.State`
-/
cache : Cache := {}
/-- Stack of free variables available for reuse when re-entering binders.
Each entry is (type pointer, fvarId). -/
binderStack : List (ExprPtr × FVarId) := []
/-- Number of steps performed so far. -/
numSteps := 0
/-- Cache for generated funext theorems -/
funext : PHashMap ExprPtr Expr := {}
@@ -221,8 +218,13 @@ opaque MethodsRef.toMethods (m : MethodsRef) : Methods
def getMethods : SimpM Methods :=
return MethodsRef.toMethods ( read)
/-- Runs a `SimpM` computation with the given theorems, configuration, and initial state -/
def SimpM.run (x : SimpM α) (methods : Methods := {}) (config : Config := {}) (s : State := {}) : SymM (α × State) := do
let initialLCtxSize := ( getLCtx).decls.size
x methods.toMethodsRef { initialLCtxSize, config } |>.run s
/-- Runs a `SimpM` computation with the given theorems and configuration. -/
def SimpM.run (x : SimpM α) (methods : Methods := {}) (config : Config := {}) : SymM α := do
def SimpM.run' (x : SimpM α) (methods : Methods := {}) (config : Config := {}) : SymM α := do
let initialLCtxSize := ( getLCtx).decls.size
x methods.toMethodsRef { initialLCtxSize, config } |>.run' {}
@@ -243,7 +245,8 @@ abbrev post : Simproc := fun e => do
abbrev withoutModifyingCache (k : SimpM α) : SimpM α := do
let cache getCache
try k finally modify fun s => { s with cache }
let funext := ( get).funext
try k finally modify fun s => { s with cache, funext }
abbrev withoutModifyingCacheIfNotWellBehaved (k : SimpM α) : SimpM α := do
if ( getMethods).wellBehavedMethods then k else withoutModifyingCache k
@@ -251,6 +254,6 @@ abbrev withoutModifyingCacheIfNotWellBehaved (k : SimpM α) : SimpM α := do
end Simp
abbrev simp (e : Expr) (methods : Simp.Methods := {}) (config : Simp.Config := {}) : SymM Simp.Result := do
Simp.SimpM.run (Simp.simp e) methods config
Simp.SimpM.run' (Simp.simp e) methods config
end Lean.Meta.Sym

View File

@@ -0,0 +1,25 @@
/-
Copyright (c) 2026 Amazon.com, Inc. or its affiliates. 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.Meta.Sym.Simp.Have
import Lean.Meta.Sym.Simp.Forall
namespace Lean.Meta.Sym.Simp
/--
Simplify telescope binders (`have`-expression values, and arrow hypotheses)
but not the final body. This simproc is useful to simplify target before
introducing.
-/
public partial def simpTelescope : Simproc := fun e => do
match e with
| .letE .. =>
simpLet' (simpLambda' simpTelescope) e
| .forallE .. =>
simpForall' (simpArrow := simpArrowTelescope simpTelescope) (simpBody := simpLambda' simpTelescope) e
| _ => return .rfl
end Lean.Meta.Sym.Simp

View File

@@ -83,7 +83,21 @@ inductive CongrInfo where
-/
congrTheorem (thm : CongrTheorem)
/-- Mutable state for the symbolic simulator framework. -/
/-- Pre-shared expressions for commonly used terms. -/
structure SharedExprs where
trueExpr : Expr
falseExpr : Expr
natZExpr : Expr
btrueExpr : Expr
bfalseExpr : Expr
ordEqExpr : Expr
intExpr : Expr
/-- Readonly context for the symbolic computation framework. -/
structure Context where
sharedExprs : SharedExprs
/-- Mutable state for the symbolic computation framework. -/
structure State where
/-- `ShareCommon` (aka `Hash-consing`) state. -/
share : AlphaShareCommon.State := {}
@@ -120,11 +134,45 @@ structure State where
congrInfo : PHashMap ExprPtr CongrInfo := {}
debug : Bool := false
abbrev SymM := StateRefT State MetaM
abbrev SymM := ReaderT Context <| StateRefT State MetaM
private def mkSharedExprs : AlphaShareCommonM SharedExprs := do
let falseExpr shareCommonAlphaInc <| mkConst ``False
let trueExpr shareCommonAlphaInc <| mkConst ``True
let bfalseExpr shareCommonAlphaInc <| mkConst ``Bool.false
let btrueExpr shareCommonAlphaInc <| mkConst ``Bool.true
let natZExpr shareCommonAlphaInc <| mkNatLit 0
let ordEqExpr shareCommonAlphaInc <| mkConst ``Ordering.eq
let intExpr shareCommonAlphaInc <| Int.mkType
return { falseExpr, trueExpr, bfalseExpr, btrueExpr, natZExpr, ordEqExpr, intExpr }
def SymM.run (x : SymM α) : MetaM α := do
let (sharedExprs, share) := mkSharedExprs |>.run {}
let debug := sym.debug.get ( getOptions)
x |>.run' { debug }
x { sharedExprs } |>.run' { debug, share }
/-- Returns maximally shared commonly used terms -/
def getSharedExprs : SymM SharedExprs :=
return ( read).sharedExprs
/-- Returns the internalized `True` constant. -/
def getTrueExpr : SymM Expr := return ( getSharedExprs).trueExpr
/-- Returns `true` if `e` is the internalized `True` expression. -/
def isTrueExpr (e : Expr) : SymM Bool := return isSameExpr e ( getTrueExpr)
/-- Returns the internalized `False` constant. -/
def getFalseExpr : SymM Expr := return ( getSharedExprs).falseExpr
/-- Returns `true` if `e` is the internalized `False` expression. -/
def isFalseExpr (e : Expr) : SymM Bool := return isSameExpr e ( getFalseExpr)
/-- Returns the internalized `Bool.true`. -/
def getBoolTrueExpr : SymM Expr := return ( getSharedExprs).btrueExpr
/-- Returns the internalized `Bool.false`. -/
def getBoolFalseExpr : SymM Expr := return ( getSharedExprs).bfalseExpr
/-- Returns the internalized `0 : Nat` numeral. -/
def getNatZeroExpr : SymM Expr := return ( getSharedExprs).natZExpr
/-- Returns the internalized `Ordering.eq`. -/
def getOrderingEqExpr : SymM Expr := return ( getSharedExprs).ordEqExpr
/-- Returns the internalized `Int`. -/
def getIntExpr : SymM Expr := return ( getSharedExprs).intExpr
/--
Applies hash-consing to `e`. Recall that all expressions in a `grind` goal have

View File

@@ -6,13 +6,56 @@ Authors: Leonardo de Moura
module
prelude
public import Lean.Meta.Sym.SymM
public import Lean.Meta.Transform
import Init.Grind.Util
import Lean.Meta.WHNF
import Lean.Util.ForEachExpr
namespace Lean.Meta.Sym
open Grind
/--
Returns `true` if `declName` is the name of a grind helper declaration that
should not be unfolded by `unfoldReducible`.
-/
def isGrindGadget (declName : Name) : Bool :=
declName == ``Grind.EqMatch
/--
Auxiliary function for implementing `unfoldReducible` and `unfoldReducibleSimproc`.
Performs a single step.
-/
public def unfoldReducibleStep (e : Expr) : MetaM TransformStep := do
let .const declName _ := e.getAppFn | return .continue
unless ( isReducible declName) do return .continue
if isGrindGadget declName then return .continue
-- See comment at isUnfoldReducibleTarget.
if ( getEnv).isProjectionFn declName then return .continue
let some v unfoldDefinition? e | return .continue
return .visit v
def isUnfoldReducibleTarget (e : Expr) : CoreM Bool := do
let env getEnv
return Option.isSome <| e.find? fun e => Id.run do
let .const declName _ := e | return false
if getReducibilityStatusCore env declName matches .reducible then
-- Remark: it is wasteful to unfold projection functions since
-- kernel projections are folded again in the `foldProjs` preprocessing step.
return !isGrindGadget declName && !env.isProjectionFn declName
else
return false
/--
Unfolds all `reducible` declarations occurring in `e`.
This is meant as a preprocessing step. It does **not** guarantee maximally shared terms
-/
public def unfoldReducible (e : Expr) : MetaM Expr := do
if !( isUnfoldReducibleTarget e) then return e
Meta.transform e (pre := unfoldReducibleStep)
/--
Instantiates metavariables, unfold reducible, and applies `shareCommon`.
-/
def preprocessExpr (e : Expr) : SymM Expr := do
shareCommon ( instantiateMVars e)
shareCommon ( unfoldReducible ( instantiateMVars e))
/--
Helper function that removes gaps, instantiate metavariables, and applies `shareCommon`.
@@ -32,6 +75,7 @@ def preprocessLCtx (lctx : LocalContext) : SymM LocalContext := do
let type preprocessExpr type
let value preprocessExpr value
pure <| LocalDecl.ldecl index fvarId userName type value nondep kind
index := index + 1
decls := decls.push (some decl)
fvarIdToDecl := fvarIdToDecl.insert decl.fvarId decl
return { fvarIdToDecl, decls, auxDeclToFullName }
@@ -48,4 +92,21 @@ public def preprocessMVar (mvarId : MVarId) : SymM MVarId := do
mvarId.assign mvarNew
return mvarNew.mvarId!
/-- Debug helper: throws if any subexpression of `e` is not in the table of maximally shared terms. -/
public def _root_.Lean.Expr.checkMaxShared (e : Expr) (msg := "") : SymM Unit := do
e.forEach fun e => do
if let some prev := ( get).share.set.find? { expr := e } then
unless isSameExpr prev.expr e do
throwNotMaxShared e
else
throwNotMaxShared e
where
throwNotMaxShared (e : Expr) : SymM Unit := do
let msg := if msg == "" then msg else s!"[{msg}] "
throwError "{msg}term is not in the maximally shared table{indentExpr e}"
/-- Debug helper: throws if any subexpression of the goal's target type is not in the table of maximally shared. -/
public def _root_.Lean.MVarId.checkMaxShared (mvarId : MVarId) (msg := "") : SymM Unit := do
( mvarId.getDecl).type.checkMaxShared msg
end Lean.Meta.Sym

View File

@@ -658,7 +658,6 @@ partial def buildInductionBody (toErase toClear : Array FVarId) (goal : Expr)
return mkApp4 (mkConst ``Bool.dcond [u]) goal c' t' f'
| _ =>
-- Check for unreachable cases. We look for the kind of expressions that `by contradiction`
-- produces
if e.isAppOf ``False.elim && 1 < e.getAppNumArgs then
@@ -846,7 +845,7 @@ where doRealize (inductName : Name) := do
throwError "Function {name} defined via WellFounded.fix with unexpected arity {funBody.getAppNumArgs}:{indentExpr funBody}"
else
throwError "Function {name} not defined via WellFounded.fix:{indentExpr funBody}"
check e'
let (body', mvars) M2.run do
forallTelescope ( inferType e').bindingDomain! fun xs goal => do
if xs.size 2 then
@@ -876,10 +875,6 @@ where doRealize (inductName : Name) := do
let e' instantiateMVars e'
return (e', paramMask)
unless ( isTypeCorrect e') do
logError m!"failed to derive a type-correct induction principle:{indentExpr e'}"
check e'
let eTyp inferType e'
let eTyp elimTypeAnnotations eTyp
let eTyp letToHave eTyp
@@ -1066,13 +1061,9 @@ where doRealize inductName := do
let value mkLambdaFVars alts value
let value mkLambdaFVars motives value
let value mkLambdaFVars params value
check value
let value cleanPackedArgs eqnInfo value
return value
unless isTypeCorrect value do
logError m!"final term is type incorrect:{indentExpr value}"
check value
let type inferType value
let type elimOptParam type
let type letToHave type
@@ -1302,10 +1293,6 @@ where doRealize inductName := do
trace[Meta.FunInd] "complete body of mutual induction principle:{indentExpr e'}"
pure (e', paramMask, motiveArities)
unless ( isTypeCorrect e') do
logError m!"constructed induction principle is not type correct:{indentExpr e'}"
check e'
let eTyp inferType e'
let eTyp elimTypeAnnotations eTyp
let eTyp letToHave eTyp
@@ -1444,9 +1431,6 @@ def deriveCases (unfolding : Bool) (name : Name) : MetaM Unit := do
let e' mkLambdaFVars #[motive] e'
mkLambdaFVarsMasked params e'
mapError (f := (m!"constructed functional cases principle is not type correct:{indentExpr e'}\n{indentD ·}")) do
check e'
let eTyp inferType e'
let eTyp elimTypeAnnotations eTyp
let eTyp letToHave eTyp

View File

@@ -11,6 +11,7 @@ import Lean.Util.ForEachExpr
import Lean.Meta.Tactic.Grind.Util
import Lean.Meta.Match.Basic
import Lean.Meta.Tactic.TryThis
import Lean.Meta.Sym.Util
public section
namespace Lean.Meta.Grind
/-!
@@ -281,7 +282,7 @@ private theorem normConfig_zetaDelta : normConfig.zetaDelta = true := rfl
def preprocessPattern (pat : Expr) (normalizePattern := true) : MetaM Expr := do
let pat instantiateMVars pat
let pat unfoldReducible pat
let pat Sym.unfoldReducible pat
let pat if normalizePattern then normalize pat normConfig else pure pat
let pat detectOffsets pat
let pat foldProjs pat

View File

@@ -107,13 +107,6 @@ private def discharge? (e : Expr) : SimpM (Option Expr) := do
open Sym
def GrindM.run (x : GrindM α) (params : Params) (evalTactic? : Option EvalTactic := none) : MetaM α := Sym.SymM.run do
let falseExpr share <| mkConst ``False
let trueExpr share <| mkConst ``True
let bfalseExpr share <| mkConst ``Bool.false
let btrueExpr share <| mkConst ``Bool.true
let natZExpr share <| mkNatLit 0
let ordEqExpr share <| mkConst ``Ordering.eq
let intExpr share <| Int.mkType
/- **Note**: Consider using `Sym.simp` in the future. -/
let simprocs := params.normProcs
let simpMethods := Simp.mkMethods simprocs discharge? (wellBehavedDischarge := true)
@@ -124,9 +117,7 @@ def GrindM.run (x : GrindM α) (params : Params) (evalTactic? : Option EvalTacti
let anchorRefs? := params.anchorRefs?
let debug := grind.debug.get ( getOptions)
x ( mkMethods evalTactic?).toMethodsRef
{ config, anchorRefs?, simpMethods, simp, extensions, symPrios
trueExpr, falseExpr, natZExpr, btrueExpr, bfalseExpr, ordEqExpr, intExpr
debug }
{ config, anchorRefs?, simpMethods, simp, extensions, symPrios, debug }
|>.run' {}
private def mkCleanState (mvarId : MVarId) : GrindM Clean.State := mvarId.withContext do
@@ -155,7 +146,7 @@ private def initENodeCore (e : Expr) (interpreted ctor : Bool) : GoalM Unit := d
mkENodeCore e interpreted ctor (generation := 0) (funCC := false)
/-- Returns a new goal for the given metavariable. -/
public def mkGoal (mvarId : MVarId) : GrindM Goal := do
public def mkGoalCore (mvarId : MVarId) : GrindM Goal := do
let config getConfig
let mvarId if config.clean then mvarId.exposeNames else pure mvarId
let trueExpr getTrueExpr
@@ -288,7 +279,7 @@ private def initCore (mvarId : MVarId) : GrindM Goal := do
let mvarId mvarId.unfoldReducible
let mvarId mvarId.betaReduce
appendTagSuffix mvarId `grind
let goal mkGoal mvarId
let goal mkGoalCore mvarId
if config.revert then
return goal
else

View File

@@ -8,6 +8,7 @@ prelude
public import Lean.Meta.Tactic.Grind.Types
import Init.Grind.Util
import Lean.Meta.Sym.ExprPtr
import Lean.Meta.Sym.Util
import Lean.Meta.Tactic.Grind.Util
public section
namespace Lean.Meta.Grind
@@ -103,7 +104,7 @@ where
-/
/- We must also apply beta-reduction to improve the effectiveness of the congruence closure procedure. -/
let e Core.betaReduce e
let e unfoldReducible e
let e Sym.unfoldReducible e
/- We must mask proofs occurring in `prop` too. -/
let e visit e
let e eraseIrrelevantMData e

View File

@@ -11,6 +11,7 @@ public import Lean.Meta.Tactic.Grind.Types
import Lean.Meta.Tactic.Grind.Util
import Lean.Meta.Tactic.Grind.MatchDiscrOnly
import Lean.Meta.Tactic.Grind.MarkNestedSubsingletons
import Lean.Meta.Sym.Util
public section
namespace Lean.Meta.Grind
@@ -57,7 +58,7 @@ def preprocessImpl (e : Expr) : GoalM Simp.Result := do
let e' instantiateMVars r.expr
-- Remark: `simpCore` unfolds reducible constants, but it does not consistently visit all possible subterms.
-- So, we must use the following `unfoldReducible` step. It is non-op in most cases
let e' unfoldReducible e'
let e' Sym.unfoldReducible e'
let e' abstractNestedProofs e'
let e' markNestedSubsingletons e'
let e' eraseIrrelevantMData e'
@@ -97,6 +98,6 @@ but ensures assumptions made by `grind` are satisfied.
-/
def preprocessLight (e : Expr) : GoalM Expr := do
let e instantiateMVars e
shareCommon ( canon ( normalizeLevels ( foldProjs ( eraseIrrelevantMData ( markNestedSubsingletons ( unfoldReducible e))))))
shareCommon ( canon ( normalizeLevels ( foldProjs ( eraseIrrelevantMData ( markNestedSubsingletons ( Sym.unfoldReducible e))))))
end Lean.Meta.Grind

View File

@@ -14,6 +14,7 @@ import Lean.Meta.Tactic.Grind.Arith.Simproc
import Lean.Meta.Tactic.Simp.BuiltinSimprocs.List
import Lean.Meta.Tactic.Simp.BuiltinSimprocs.Core
import Lean.Meta.Tactic.Grind.Util
import Lean.Meta.Sym.Util
import Init.Grind.Norm
public section
namespace Lean.Meta.Grind
@@ -136,7 +137,7 @@ builtin_simproc_decl reduceCtorEqCheap (_ = _) := fun e => do
return .done { expr := mkConst ``False, proof? := ( withDefault <| mkEqFalse' ( mkLambdaFVars #[h] ( mkNoConfusion (mkConst ``False) h))) }
builtin_dsimproc_decl unfoldReducibleSimproc (_) := fun e => do
unfoldReducibleStep e
Sym.unfoldReducibleStep e
/-- Returns the array of simprocs used by `grind`. -/
protected def getSimprocs : MetaM (Array Simprocs) := do

View File

@@ -160,15 +160,10 @@ structure Context where
/-- Symbol priorities for inferring E-matching patterns -/
symPrios : SymbolPriorities
extensions : ExtensionStateArray := #[]
trueExpr : Expr
falseExpr : Expr
natZExpr : Expr
btrueExpr : Expr
bfalseExpr : Expr
ordEqExpr : Expr -- `Ordering.eq`
intExpr : Expr -- `Int`
debug : Bool -- Cached `grind.debug (← getOptions)`
export Sym (getTrueExpr getFalseExpr getBoolTrueExpr getBoolFalseExpr getNatZeroExpr getOrderingEqExpr getIntExpr isTrueExpr isFalseExpr)
/-- Key for the congruence theorem cache. -/
structure CongrTheoremCacheKey where
f : Expr
@@ -305,34 +300,6 @@ abbrev withGTransparency [MonadControlT MetaM n] [MonadLiftT GrindM n] [Monad n]
let m := if ( getConfig).reducible then .reducible else .default
withTransparency m k
/-- Returns the internalized `True` constant. -/
def getTrueExpr : GrindM Expr := do
return ( readThe Context).trueExpr
/-- Returns the internalized `False` constant. -/
def getFalseExpr : GrindM Expr := do
return ( readThe Context).falseExpr
/-- Returns the internalized `Bool.true`. -/
def getBoolTrueExpr : GrindM Expr := do
return ( readThe Context).btrueExpr
/-- Returns the internalized `Bool.false`. -/
def getBoolFalseExpr : GrindM Expr := do
return ( readThe Context).bfalseExpr
/-- Returns the internalized `0 : Nat` numeral. -/
def getNatZeroExpr : GrindM Expr := do
return ( readThe Context).natZExpr
/-- Returns the internalized `Ordering.eq`. -/
def getOrderingEqExpr : GrindM Expr := do
return ( readThe Context).ordEqExpr
/-- Returns the internalized `Int`. -/
def getIntExpr : GrindM Expr := do
return ( readThe Context).intExpr
/-- Returns the anchor references (if any) being used to restrict the search. -/
def getAnchorRefs : GrindM (Option (Array AnchorRef)) := do
return ( readThe Context).anchorRefs?
@@ -412,14 +379,6 @@ Abstracts nested proofs in `e`. This is a preprocessing step performed before in
def abstractNestedProofs (e : Expr) : GrindM Expr :=
Meta.abstractNestedProofs e
/-- Returns `true` if `e` is the internalized `True` expression. -/
def isTrueExpr (e : Expr) : GrindM Bool :=
return isSameExpr e ( getTrueExpr)
/-- Returns `true` if `e` is the internalized `False` expression. -/
def isFalseExpr (e : Expr) : GrindM Bool :=
return isSameExpr e ( getFalseExpr)
/--
Creates a congruence theorem for a `f`-applications with `numArgs` arguments.
-/
@@ -1148,11 +1107,11 @@ def getGeneration (e : Expr) : GoalM Nat :=
/-- Returns `true` if `e` is in the equivalence class of `True`. -/
def isEqTrue (e : Expr) : GoalM Bool := do
return isSameExpr ( getENode e).root ( getTrueExpr)
return ( isTrueExpr ( getENode e).root)
/-- Returns `true` if `e` is in the equivalence class of `False`. -/
def isEqFalse (e : Expr) : GoalM Bool := do
return isSameExpr ( getENode e).root ( getFalseExpr)
return ( isFalseExpr ( getENode e).root)
/-- Returns `true` if `e` is in the equivalence class of `Bool.true`. -/
def isEqBoolTrue (e : Expr) : GoalM Bool := do

View File

@@ -11,6 +11,7 @@ import Lean.ProjFns
import Lean.Meta.WHNF
import Lean.Meta.AbstractNestedProofs
import Lean.Meta.Tactic.Clear
import Lean.Meta.Sym.Util
public section
namespace Lean.Meta.Grind
/--
@@ -55,49 +56,11 @@ def _root_.Lean.MVarId.transformTarget (mvarId : MVarId) (f : Expr → MetaM Exp
mvarId.assign mvarNew
return mvarNew.mvarId!
/--
Returns `true` if `declName` is the name of a grind helper declaration that
should not be unfolded by `unfoldReducible`.
-/
def isGrindGadget (declName : Name) : Bool :=
declName == ``Grind.EqMatch
def isUnfoldReducibleTarget (e : Expr) : CoreM Bool := do
let env getEnv
return Option.isSome <| e.find? fun e => Id.run do
let .const declName _ := e | return false
if getReducibilityStatusCore env declName matches .reducible then
-- Remark: it is wasteful to unfold projection functions since
-- kernel projections are folded again in the `foldProjs` preprocessing step.
return !isGrindGadget declName && !env.isProjectionFn declName
else
return false
/--
Auxiliary function for implementing `unfoldReducible` and `unfoldReducibleSimproc`.
Performs a single step.
-/
def unfoldReducibleStep (e : Expr) : MetaM TransformStep := do
let .const declName _ := e.getAppFn | return .continue
unless ( isReducible declName) do return .continue
if isGrindGadget declName then return .continue
-- See comment at isUnfoldReducibleTarget.
if ( getEnv).isProjectionFn declName then return .continue
let some v unfoldDefinition? e | return .continue
return .visit v
/--
Unfolds all `reducible` declarations occurring in `e`.
-/
def unfoldReducible (e : Expr) : MetaM Expr := do
if !( isUnfoldReducibleTarget e) then return e
Meta.transform e (pre := unfoldReducibleStep)
/--
Unfolds all `reducible` declarations occurring in the goal's target.
-/
def _root_.Lean.MVarId.unfoldReducible (mvarId : MVarId) : MetaM MVarId :=
mvarId.transformTarget Grind.unfoldReducible
mvarId.transformTarget Sym.unfoldReducible
/--
Beta-reduces the goal's target.

View File

@@ -188,6 +188,12 @@ def applyEqLemma (e : Expr → EqResult) (lemmaName : Name) (args : Array Expr)
return .some (e (mkAppN (mkConst lemmaName) args))
def reduceNatEqExpr (x y : Expr) : SimpM (Option EqResult):= do
/-
**TODO**: These proofs rely too much on definitional equality.
Example:
`x + 1 + 1 + ... + 1 = x + 1 + ... + 1`
It will treat both sides as `x + n = x + n`.
-/
let some xno NatOffset.fromExpr? x | return none
let some yno NatOffset.fromExpr? y | return none
match xno, yno with

View File

@@ -54,7 +54,7 @@ def externEntry := leading_parser
nonReservedSymbol "extern" >> many (ppSpace >> externEntry)
/--
Declare this tactic to be an alias or alternative form of an existing tactic.
Declares this tactic to be an alias or alternative form of an existing tactic.
This has the following effects:
* The alias relationship is saved
@@ -64,13 +64,26 @@ This has the following effects:
"tactic_alt" >> ppSpace >> ident
/--
Add one or more tags to a tactic.
Adds one or more tags to a tactic.
Tags should be applied to the canonical names for tactics.
-/
@[builtin_attr_parser] def «tactic_tag» := leading_parser
"tactic_tag" >> many1 (ppSpace >> ident)
/--
Sets the tactic's name.
Ordinarily, tactic names are automatically set to the first token in the tactic's parser. If this
process fails, or if the tactic's name should be multiple tokens (e.g. `let rec`), then this
attribute can be used to provide a name.
The tactic's name is used in documentation as well as in completion. Thus, the name should be a
valid prefix of the tactic's syntax.
-/
@[builtin_attr_parser] def «tactic_name» := leading_parser
"tactic_name" >> ppSpace >> (ident <|> strLit)
end Attr
end Lean.Parser

View File

@@ -52,24 +52,7 @@ example (n : Nat) : n = n := by
optional Term.motive >> sepBy1 Term.matchDiscr ", " >>
" with " >> ppDedent matchAlts
/--
The tactic
```
intro
| pat1 => tac1
| pat2 => tac2
```
is the same as:
```
intro x
match x with
| pat1 => tac1
| pat2 => tac2
```
That is, `intro` can be followed by match arms and it introduces the values while
doing a pattern match. This is equivalent to `fun` with match arms in term mode.
-/
@[builtin_tactic_parser] def introMatch := leading_parser
@[builtin_tactic_parser, tactic_alt intro] def introMatch := leading_parser
nonReservedSymbol "intro" >> matchAlts
builtin_initialize

View File

@@ -191,12 +191,13 @@ builtin_initialize
unless kind == AttributeKind.global do throwAttrMustBeGlobal name kind
let `(«tactic_tag»|tactic_tag $tags*) := stx
| throwError "Invalid `[{name}]` attribute syntax"
if ( getEnv).find? decl |>.isSome then
if !(isTactic ( getEnv) decl) then
throwErrorAt stx "`{decl}` is not a tactic"
throwErrorAt stx "`{.ofConstName decl}` is not a tactic"
if let some tgt' := alternativeOfTactic ( getEnv) decl then
throwErrorAt stx "`{decl}` is an alternative form of `{tgt'}`"
throwErrorAt stx "`{.ofConstName decl}` is an alternative form of `{.ofConstName tgt'}`"
for t in tags do
let tagName := t.getId
@@ -271,14 +272,81 @@ where
| [l] => " * " ++ l ++ "\n\n"
| l::ls => " * " ++ l ++ "\n" ++ String.join (ls.map indentLine) ++ "\n\n"
/--
The mapping between tactics and their custom names.
The first projection in each pair is the tactic name, and the second is the custom name.
-/
builtin_initialize tacticNameExt
: PersistentEnvExtension
(Name × String)
(Name × String)
(NameMap String)
registerPersistentEnvExtension {
mkInitial := pure {},
addImportedFn := fun _ => pure {},
addEntryFn := fun as (src, tgt) => as.insert src tgt,
exportEntriesFn := fun es =>
es.foldl (fun a src tgt => a.push (src, tgt)) #[] |>.qsort (Name.quickLt ·.1 ·.1)
}
/--
Finds the custom name assigned to `tac`, or returns `none` if there is no such custom name.
-/
def customTacticName [Monad m] [MonadEnv m] (tac : Name) : m (Option String) := do
let env getEnv
match env.getModuleIdxFor? tac with
| some modIdx =>
match (tacticNameExt.getModuleEntries env modIdx).binSearch (tac, default) (Name.quickLt ·.1 ·.1) with
| some (_, val) => return some val
| none => return none
| none => return tacticNameExt.getState env |>.find? tac
builtin_initialize
let name := `tactic_name
registerBuiltinAttribute {
name := name,
ref := by exact decl_name%,
add := fun decl stx kind => do
unless kind == AttributeKind.global do throwAttrMustBeGlobal name kind
let name
match stx with
| `(«tactic_name»|tactic_name $name:str) =>
pure name.getString
| `(«tactic_name»|tactic_name $name:ident) =>
pure (name.getId.toString (escape := false))
| _ => throwError "Invalid `[{name}]` attribute syntax"
if ( getEnv).find? decl |>.isSome then
if !(isTactic ( getEnv) decl) then
throwErrorAt stx m!"`{.ofConstName decl}` is not a tactic"
if let some idx := ( getEnv).getModuleIdxFor? decl then
if let some mod := ( getEnv).allImportedModuleNames[idx]? then
throwErrorAt stx m!"`{.ofConstName decl}` is defined in `{mod}`, but custom names can only be added in the tactic's defining module."
else
throwErrorAt stx m!"`{.ofConstName decl}` is defined in an imported module, but custom names can only be added in the tactic's defining module."
if let some tgt' := alternativeOfTactic ( getEnv) decl then
throwErrorAt stx "`{.ofConstName decl}` is an alternative form of `{.ofConstName tgt'}`"
if let some n customTacticName decl then
throwError m!"The tactic `{.ofConstName decl}` already has the custom name `{n}`"
modifyEnv fun env => tacticNameExt.addEntry env (decl, name)
descr :=
"Registers a custom name for a tactic. This custom name should be a prefix of the " ++
"tactic's syntax, because it is used in completion.",
applicationTime := .beforeElaboration
}
-- Note: this error handler doesn't prevent all cases of non-tactics being added to the data
-- structure. But the module will throw errors during elaboration, and there doesn't seem to be
-- another way to implement this, because the category parser extension attribute runs *after* the
-- attributes specified before a `syntax` command.
/--
Validates that a tactic alternative is actually a tactic and that syntax tagged as tactics are
tactics.
Validates that a tactic alternative is actually a tactic, that syntax tagged as tactics are
tactics, and that syntax with tactic names are tactics.
-/
private def tacticDocsOnTactics : ParserAttributeHook where
postAdd (catName declName : Name) (_builtIn : Bool) := do
@@ -291,6 +359,8 @@ private def tacticDocsOnTactics : ParserAttributeHook where
if let some tags := tacticTagExt.getState ( getEnv) |>.find? declName then
if !tags.isEmpty then
throwError m!"`{.ofConstName declName}` is not a tactic"
if let some n := tacticNameExt.getState ( getEnv) |>.find? declName then
throwError m!"`{MessageData.ofConstName declName}` is not a tactic, but it was assigned a tactic name `{n}`"
builtin_initialize
registerParserAttributeHook tacticDocsOnTactics

View File

@@ -224,17 +224,22 @@ def computeQueries
break
return queries
def importAllUnknownIdentifiersProvider : Name := `unknownIdentifiers
def importAllUnknownIdentifiersProvider : Name := `allUnknownIdentifiers
def importUnknownIdentifiersProvider : Name := `unknownIdentifiers
def mkUnknownIdentifierCodeActionData (params : CodeActionParams)
(name := importUnknownIdentifiersProvider) : CodeActionResolveData := {
params,
providerName := name
providerResultIndex := 0
: CodeActionResolveData
}
def importAllUnknownIdentifiersCodeAction (params : CodeActionParams) (kind : String) : CodeAction := {
title := "Import all unambiguous unknown identifiers"
kind? := kind
data? := some <| toJson {
params,
providerName := importAllUnknownIdentifiersProvider
providerResultIndex := 0
: CodeActionResolveData
}
data? := some <| toJson <|
mkUnknownIdentifierCodeActionData params importAllUnknownIdentifiersProvider
}
private def mkImportText (ctx : Elab.ContextInfo) (mod : Name) :
@@ -311,6 +316,7 @@ def handleUnknownIdentifierCodeAction
insertion.edit
]
}
data? := some <| toJson <| mkUnknownIdentifierCodeActionData params
}
if isExactMatch then
hasUnambiguousImportCodeAction := true
@@ -322,6 +328,7 @@ def handleUnknownIdentifierCodeAction
textDocument := doc.versionedIdentifier
edits := #[insertion.edit]
}
data? := some <| toJson <| mkUnknownIdentifierCodeActionData params
}
if hasUnambiguousImportCodeAction then
unknownIdentifierCodeActions := unknownIdentifierCodeActions.push <|

View File

@@ -597,7 +597,8 @@ def tacticCompletion
(completionInfoPos : Nat)
(ctx : ContextInfo)
: IO (Array ResolvableCompletionItem) := ctx.runMetaM .empty do
let allTacticDocs Tactic.Doc.allTacticDocs
-- Don't include tactics that are identified only by their internal parser name
let allTacticDocs Tactic.Doc.allTacticDocs (includeUnnamed := false)
let items : Array ResolvableCompletionItem := allTacticDocs.map fun tacticDoc => {
label := tacticDoc.userName
detail? := none

View File

@@ -793,21 +793,24 @@ section MessageHandling
rpcEncode resp st.objects |>.map (·) ({st with objects := ·})
return some <| .pure { response? := resp, serialized := resp.compress, isComplete := true }
| "codeAction/resolve" =>
let jsonParams := params
let params RequestM.parseRequestParams CodeAction params
let some data := params.data?
| throw (RequestError.invalidParams "Expected a data field on CodeAction.")
let data RequestM.parseRequestParams CodeActionResolveData data
if data.providerName != importAllUnknownIdentifiersProvider then
return none
return some <| RequestM.asTask do
let unknownIdentifierRanges waitAllUnknownIdentifierMessageRanges st.doc
if unknownIdentifierRanges.isEmpty then
let p := toJson params
return { response? := p, serialized := p.compress, isComplete := true }
let action? handleResolveImportAllUnknownIdentifiersCodeAction? id params unknownIdentifierRanges
let action := action?.getD params
let action := toJson action
return { response? := action, serialized := action.compress, isComplete := true }
if data.providerName == importUnknownIdentifiersProvider then
return some <| RequestTask.pure { response? := jsonParams, serialized := jsonParams.compress, isComplete := true }
if data.providerName == importAllUnknownIdentifiersProvider then
return some <| RequestM.asTask do
let unknownIdentifierRanges waitAllUnknownIdentifierMessageRanges st.doc
if unknownIdentifierRanges.isEmpty then
let p := toJson params
return { response? := p, serialized := p.compress, isComplete := true }
let action? handleResolveImportAllUnknownIdentifiersCodeAction? id params unknownIdentifierRanges
let action := action?.getD params
let action := toJson action
return { response? := action, serialized := action.compress, isComplete := true }
return none
| _ =>
return none

View File

@@ -270,9 +270,9 @@ withTraceNode `isPosTrace (msg := (return m!"{ExceptToEmoji.toEmoji ·} checking
The `cls`, `collapsed`, and `tag` arguments are forwarded to the constructor of `TraceData`.
-/
@[inline]
def withTraceNode [always : MonadAlwaysExcept ε m] [MonadLiftT BaseIO m] (cls : Name)
(msg : Except ε α m MessageData) (k : m α) (collapsed := true) (tag := "") : m α := do
let _ := always.except
let opts getOptions
if !opts.hasTrace then
return ( k)
@@ -280,21 +280,27 @@ def withTraceNode [always : MonadAlwaysExcept ε m] [MonadLiftT BaseIO m] (cls :
unless clsEnabled || trace.profiler.get opts do
return ( k)
let oldTraces getResetTraces
let (res, start, stop) withStartStop opts <| observing k
let aboveThresh := trace.profiler.get opts &&
stop - start > trace.profiler.threshold.unitAdjusted opts
unless clsEnabled || aboveThresh do
modifyTraces (oldTraces ++ ·)
return ( MonadExcept.ofExcept res)
let ref getRef
let mut m try msg res catch _ => pure m!"<exception thrown while producing trace node message>"
let mut data := { cls, collapsed, tag }
if trace.profiler.get opts then
data := { data with startTime := start, stopTime := stop }
addTraceNode oldTraces data ref m
MonadExcept.ofExcept res
let resStartStop withStartStop opts <| let _ := always.except; observing k
postCallback opts clsEnabled oldTraces msg resStartStop
where
postCallback (opts : Options) (clsEnabled oldTraces msg resStartStop) : m α := do
let _ := always.except
let (res, start, stop) := resStartStop
let aboveThresh := trace.profiler.get opts &&
stop - start > trace.profiler.threshold.unitAdjusted opts
unless clsEnabled || aboveThresh do
modifyTraces (oldTraces ++ ·)
return ( MonadExcept.ofExcept res)
let ref getRef
let mut m try msg res catch _ => pure m!"<exception thrown while producing trace node message>"
let mut data := { cls, collapsed, tag }
if trace.profiler.get opts then
data := { data with startTime := start, stopTime := stop }
addTraceNode oldTraces data ref m
MonadExcept.ofExcept res
/-- A version of `Lean.withTraceNode` which allows generating the message within the computation. -/
@[inline]
def withTraceNode' [MonadAlwaysExcept Exception m] [MonadLiftT BaseIO m] (cls : Name)
(k : m (α × MessageData)) (collapsed := true) (tag := "") : m α :=
let msg := fun
@@ -380,10 +386,10 @@ the result produced by `k` into an emoji (e.g., `💥️`, `✅️`, `❌️`).
TODO: find better name for this function.
-/
@[inline]
def withTraceNodeBefore [MonadRef m] [AddMessageContext m] [MonadOptions m]
[always : MonadAlwaysExcept ε m] [MonadLiftT BaseIO m] [ExceptToEmoji ε α] (cls : Name)
(msg : Unit m MessageData) (k : m α) (collapsed := true) (tag := "") : m α := do
let _ := always.except
let opts getOptions
if !opts.hasTrace then
return ( k)
@@ -394,18 +400,23 @@ def withTraceNodeBefore [MonadRef m] [AddMessageContext m] [MonadOptions m]
let ref getRef
-- make sure to preserve context *before* running `k`
let msg withRef ref do addMessageContext ( msg ())
let (res, start, stop) withStartStop opts <| observing k
let aboveThresh := trace.profiler.get opts &&
stop - start > trace.profiler.threshold.unitAdjusted opts
unless clsEnabled || aboveThresh do
modifyTraces (oldTraces ++ ·)
return ( MonadExcept.ofExcept res)
let mut msg := m!"{ExceptToEmoji.toEmoji res} {msg}"
let mut data := { cls, collapsed, tag }
if trace.profiler.get opts then
data := { data with startTime := start, stopTime := stop }
addTraceNode oldTraces data ref msg
MonadExcept.ofExcept res
let resStartStop withStartStop opts <| let _ := always.except; observing k
postCallback opts clsEnabled oldTraces ref msg resStartStop
where
postCallback (opts : Options) (clsEnabled oldTraces ref msg resStartStop) : m α := do
let _ := always.except
let (res, start, stop) := resStartStop
let aboveThresh := trace.profiler.get opts &&
stop - start > trace.profiler.threshold.unitAdjusted opts
unless clsEnabled || aboveThresh do
modifyTraces (oldTraces ++ ·)
return ( MonadExcept.ofExcept res)
let mut msg := m!"{ExceptToEmoji.toEmoji res} {msg}"
let mut data := { cls, collapsed, tag }
if trace.profiler.get opts then
data := { data with startTime := start, stopTime := stop }
addTraceNode oldTraces data ref msg
MonadExcept.ofExcept res
def addTraceAsMessages [Monad m] [MonadRef m] [MonadLog m] [MonadTrace m] : m Unit := do
if trace.profiler.output.get? ( getOptions) |>.isSome then

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