Compare commits

...

159 Commits

Author SHA1 Message Date
Henrik Böving
bac33518dc perf: inline Level Data accessor 2026-02-19 09:53:01 +00:00
Lean stage0 autoupdater
6410de4726 chore: update stage0 2026-02-19 09:50:52 +00:00
Henrik Böving
88ecacea4e feat: make computed_fields respect inline (#12580)
This PR makes `computed_field` respect the inline attributes on the
function for computing the
field. This means we can inline the accessor for the field, allowing
quicker access.
2026-02-19 09:00:13 +00:00
Sebastian Graf
14c973db4e test: use Sym.Patterns for discrimination tree matching in Sym VCGen (#12579) 2026-02-19 08:08:53 +00:00
Sebastian Graf
172c5c3ba8 test: flag to use Sym.simp in Sym mvcgen benchmark driver (#12578) 2026-02-19 07:37:01 +00:00
Sebastian Graf
2c68952694 chore: refactor match elaborator to be used from the do elaborator (#12451)
This PR provides the necessary hooks for the new do elaborator to call
into the let and match elaborator.

The `do match` elaborator needs access to a couple of functions from the
term `match` elaborator to implement its own `elabMatchAlt`. In
particular, `withEqs`, `withPatternVars` and `checkNumPatterns` need to
be exposed. Furthermore, I think it makes sense to share
`instantiateAltLHSs`.
2026-02-19 07:33:30 +00:00
Sebastian Graf
63f7776390 feat: add Sym.mkPatternFrom{Decl,Expr}WithKey (#12576)
This PR adds `Sym.mkPatternFromDeclWithKey` to the Sym API to generalize
and implement `Sym.mkEqPatternFromDecl`. This is useful to implement
custom rewrite-like tactics that want to use `Pattern`s for
discrimination tree lookup.
2026-02-19 07:20:31 +00:00
Sebastian Graf
e639b66d62 chore: rename SpecTheorems.add to SpecTheorems.insert, add SpecProof.getProof (#12574)
This PR renames `SpecTheorems.add` to `SpecTheorems.insert`
2026-02-19 07:04:27 +00:00
Kyle Miller
309f44d007 feat: more reliable universe level inference in inductive/structure commands (#12514)
This PR improves universe level inference for the `inductive` and
`structure` commands to be more reliable and to produce better error
messages. Recall that the main constraint for inductive types is that if
`u` is the universe level for the type and `u > 0`, then each
constructor field's universe level `v` satisfies `v ≤ u`, where a
*constructor field* is an argument that is not one of the type's
*parameters* (recall: the type's parameters are a prefix of the
parameters shared by the type former and all the constructors). Given
this constraint, the `inductive` elaborator attempts to find reasonable
assignments to metavariables that may be present:
- For the universe level `u`, choosing an assignment that makes this
level least is reasonable, provided it is unique.
- For constructor fields, choosing the unique assignment is usually
reasonable.
- For the type's parameters, promoting level metavariables to new
universe level parameters is reasonable.

The order of these steps led to somewhat convoluted error messages; for
example, metavariable->parameter promotion was done early, leading to
errors mentioning `u_1`, `u_2`, etc. instead of metavariables, as well
as extraneous level constraint errors. Furthermore, early parameter
promotion meant it was too late to perform certain kinds of inferences.

Now there is a straightforward order of inference:
1. If the type's universe level could be zero, it checks that the type
is an "obvious `Prop` candidate", which means it's non-recursive, has
one constructor with at least one field, and all the fields are proofs.
If it's a `Prop` candidate, the level is set to zero and we skip to step
4.
2. If the type's simplified universe level is of the form `?u + k`, it
will accumulate level constraints to find a least upper bound solution
for `?u`. To avoid sort polymorphism, it adds `1 ≤ ?u + k`, ensuring the
result stays in `Type _`, or at least `Sort (max 1 _)`. It allows other
metavariables to appear in the assignment for `?u`, provided they appear
in the type former, or for `structure` in the `extends` clause.
3. If the type's simplified universe level is then of the form `r + k`,
where `r` is a parameter, metavariable, or zero, then for every
constructor field it will take the `v ≤ r + k` constraint and extract
`?v ≤ r + k'` constraints. It will also *weakly* extract `1 ≤ ?v`
constraints, using the observation that it's surprising if fields are
automatically inferred to be proofs. Once the constraints are collected,
each metavariable is solved for independently. Heuristically, if there
is a unique non-constant solution we take that, or else a unique
constant solution.
4. Any remaining level metavariables in the type former (or `extends`
clause) become level parameters.
5. Remaining level metavariables in the constructor fields are reported
as errors.
6. Then, the elaborator checks that the level constraints actually hold
and reports an error if they don't.

In 2 and 3, there are procedures to simplify universe levels. You can
write `Sort (max 1 _)` for the resulting type now and it will solve for
`_`.

The "accidentally higher universe" error is now a warning. The
constraint solving is also done in a more careful way, which keeps it
from being reported erroneously. There are still some erroneous reports,
but these ones are hard for the checker to reject. As before, the
warning can be turned off by giving an explicit universe.

Note about `extends` clauses: in testing, there were examples where it
was surprising if the universe polymorphism of parent structures didn't
carry over to the type being defined, even though parent structures are
actually constructor fields.

**Breaking change.** Universe level metavariables present only in
constructor fields are no longer promoted to be universe level
parameters: use explicit universe level parameters. This promotion was
inconsistently done depending on whether the inductive type's universe
level had a metavariable, and also it caused confusion for users, since
these universe levels are not constrained by the type former's
parameters.

**Breaking change.** Now recursive types do not count as "obvious `Prop`
candidates". Use an explicit `Prop` type former annotation on recursive
inductive predicates.

Additional changes:
- level metavariable errors are now localized to constructors, and
`structure` fields have such errors localized to fields
- adds module docs for the index promotion algorithm and the universe
level inference algorithm for inductives
- factors out `Lean.Elab.Term.forEachExprWithExposedLevelMVars` for
printing out the context of an expression with universe level
metavariables
- makes universe level metavariable exposure more effective at exposing
level metavariables (with an exception of `sorry` terms, which are too
noisy to expose)

Supersedes #11513 and #11524.
2026-02-18 23:46:12 +00:00
Wojciech Różowski
0a849003b2 refactor: remove dead matcher code from Cbv/Main.lean (#12568)
This PR removes `tryMatchEquations` and `tryMatcher` from
`Lean.Meta.Tactic.Cbv.Main`, as both are already defined and used in
`Lean.Meta.Tactic.Cbv.ControlFlow`. The copies in `Main.lean` were
unreachable dead code.

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

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-18 22:49:25 +00:00
Lean stage0 autoupdater
c67dd2386a chore: update stage0 2026-02-18 23:11:53 +00:00
Leonardo de Moura
b668a18a9d refactor: rename instance_reducible to implicit_reducible (#12567)
This PR renames `instance_reducible` to `implicit_reducible` and adds a
new
`backward.isDefEq.implicitBump` option to prepare for treating all
implicit
arguments uniformly during definitional equality checking.

## Changes

**Rename `instance_reducible` → `implicit_reducible`:**
- Rename `ReducibilityStatus.instanceReducible` constructor to
`implicitReducible`
- Register new `[implicit_reducible]` attribute, keep
`[instance_reducible]` as alias
- Rename `isInstanceReducible` → `isImplicitReducible` (with deprecated
aliases)
- Update all references across src/ and tests/

The rename reflects that this reducibility level is used not just for
instances
but for any definition that needs unfolding during implicit argument
resolution
(e.g., `Nat.add`, `Array.size`).

**Add `backward.isDefEq.implicitBump` option:**
- When `true` (+ `respectTransparency`), bumps transparency to
`.instances` for
ALL implicit arguments in `isDefEqArgs`, not just instance-implicit ones
- Defaults to `false` for staging compatibility — will be flipped to
`true` after
  stage0 update
- Adds `// update me!` to `stage0/src/stdlib_flags.h` to trigger CI
stage0 update

## Follow-up (after stage0 update)
- Flip `backward.isDefEq.implicitBump` default to `true`
- Fix resulting test/module failures

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

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-18 22:19:16 +00:00
Leonardo de Moura
f1934c8d52 doc: add design rationale comments for transparency settings (#12561)
## Summary

This PR adds documentation comments to the key transparency-related
definitions explaining the design rationale behind the transparency
hierarchy and the v4.29 changes:

- `TransparencyMode`: explains "try-hard" vs "speculative" `isDefEq`,
the transparency hierarchy (`none < reducible < instances < default <
all`), instance diamonds, and why implicit arguments received special
treatment
- `ReducibilityStatus`: documents each status with its corresponding
`TransparencyMode` level and typical use case
- `@[instance_reducible]`: explains decoupling of instance registration
from transparency
- `backward.isDefEq.respectTransparency`: explains the original
motivation for bumping transparency on implicit arguments, and why it
became a performance bottleneck
- `backward.whnf.reducibleClassField`: explains why `[reducible]` class
fields need special handling when the instance is only
`[instance_reducible]`
- `canUnfoldDefault` and `isDefEqArgs`: brief inline comments linking to
the design rationale

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

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-18 17:32:33 +00:00
Joachim Breitner
7fa7ec1887 fix: instantiate mvars after synthesizing inductive type header (#12558)
This PR fixes a `(kernel) declaration has metavariables` error that
occurred when a `by` tactic was used in a dependent inductive type index
that refers to a previous index:

```lean
axiom P : Prop
axiom Q : P → Prop
-- Previously gave: (kernel) declaration has metavariables 'Foo'
inductive Foo : (h : P) → (Q (by exact h)) → Prop
```

The root cause: `elabDepArrow` calls `mkForallFVars [h_fvar] body`
before the `by` tactic's metavariable `?m` is resolved. Since `h_fvar`
is in `?m`'s local context, `elimMVarDeps` creates a delayed assignment
`?newMVar #[h_fvar] := ?m`. After `synthesizeSyntheticMVarsNoPostponing`
assigns `?m := h_fvar`, `instantiateMVars` can resolve the delayed
assignment (substituting `h_fvar` with the actual argument, `bvar 0`, in
the pending value), yielding the correct type `∀ (h : P), Q (bvar 0) →
Prop`. The fix is to call `instantiateMVars` on the header type right
after `synthesizeSyntheticMVarsNoPostponing` in `elabHeadersAux`.

Fixes #12543.

🤖 This PR was created with [Claude Code](https://claude.ai/claude-code).

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-18 15:29:33 +00:00
Paul Reichert
043b8a765a feat: verification of Nat.toDigits, Nat.repr and ToString Nat (#12445)
This PR provides lemmas characterizing `Nat.toDigits`, `Nat.repr` and
`ToString Nat`.
2026-02-18 12:33:01 +00:00
Paul Reichert
ac7c8e157e feat: upstream List.scanl and List.scanr (#12452)
This PR upstreams `List.scanl`, `List.scanr` and their lemmas from
batteries into the standard library.
2026-02-18 12:15:18 +00:00
Sebastian Ullrich
481f6b6d64 fix: cancellation of non-incremental commands (#12553)
This PR fixes an issue where commands that do not support incrementality
did not have their elaboration interrupted when a relevant edit is made
by the user. As all built-in variants of def/theorem share a common
incremental elaborator, this likely had negligible impact on standard
Lean files but could affect other use cases heavily relying on custom
commands such as Verso.
2026-02-18 12:12:47 +00:00
Sebastian Graf
c0b9ff1148 test: measure VC discharging separately in Sym mvcgen benchmarks (#12551)
This PR refactors the benchmark driver of the Sym mvcgen benchmarks such
that time spent for discharging VCs and instantiation of MVars is
measured separately from VC generation.

Example output:

```
baseline_add_sub_cancel
goal_100: 57 ms, 0 VCs, kernel: 22 ms
goal_500: 353 ms, 0 VCs, kernel: 160 ms
goal_1000: 755 ms, 0 VCs, kernel: 437 ms

vcgen_add_sub_cancel
goal_100: 36 ms, 1 VCs by grind: 21 ms, kernel: 35 ms
goal_500: 149 ms, 1 VCs by grind: 115 ms, kernel: 214 ms
goal_1000: 314 ms, 1 VCs by grind: 249 ms, kernel: 478 ms

vcgen_deep_add_sub_cancel
goal_100: 65 ms, 1 VCs by grind: 23 ms, kernel: 82 ms
goal_500: 262 ms, 1 VCs by grind: 123 ms, kernel: 539 ms
goal_1000: 611 ms, 1 VCs by grind: 292 ms, kernel: 1075 ms

vcgen_get_throw_set
goal_100: 87 ms, 101 VCs by sorry: 16 ms, kernel: 93 ms
goal_500: 332 ms, 501 VCs by sorry: 289 ms, instantiate > 1000ms: 23363 ms, kernel: 770 ms
goal_1000: 794 ms, 1001 VCs by sorry: 1332 ms, instantiate > 1000ms: 334614 ms, kernel: 1882 ms
```
2026-02-18 12:03:47 +00:00
Henrik Böving
ad64f7c1ba feat: LCNF inc/dec instructions (#12550)
This PR adds `inc`/`dec` instructions to LCNF. It should be a functional
no-op.
2026-02-18 10:55:16 +00:00
Sebastian Graf
6c671ffe6f chore: add mvcgen regression test case (#12546) 2026-02-18 09:04:26 +00:00
Lean stage0 autoupdater
c7457fc219 chore: update stage0 2026-02-18 07:56:34 +00:00
Mac Malone
170eaf719a fix: lake: do not cache files already in the cache (#12537)
This PR fixes a bug where Lake recached artifacts already present within
the cache. As a result, Lake would attempt to overwrite the read-only
artifacts, causing a permission denied error.
2026-02-18 02:36:54 +00:00
Kim Morrison
f3cbdca6e2 chore: add module/prelude guidance to CLAUDE.md (#12542)
This PR adds guidance to `.claude/CLAUDE.md` about the `module` +
`prelude` convention required for files in `src/Lean/`, `src/Std/`, and
`src/lake/Lake/`. CI enforces that these files contain `prelude`, but
with `prelude` nothing is auto-imported, so explicit `Init.*` imports
are needed for standard library features like `while`,
`String.startsWith`, etc.

🤖 Prepared with Claude Code

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-18 00:57:20 +00:00
Kim Morrison
12d3ffc15b chore: add copyright header guidance to CLAUDE.md (#12541)
This PR adds a "Copyright Headers" section to `.claude/CLAUDE.md`
instructing Claude to:
- Always use `date +%Y` for the copyright year instead of relying on
memory
- Match the copyright holder to what the author uses in other recent
files in the repo
- Skip copyright headers for test files in `tests/`

🤖 Prepared with Claude Code

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-18 00:44:02 +00:00
Kim Morrison
e9cc84b7c9 chore: improve release command PR status checking (#12536)
This PR adds guidance to the release slash command to check actual PR
merge state (using `gh pr view`) when reporting status, rather than
relying on cached CI results. This prevents incorrectly reporting
already-merged PRs as still needing review.

🤖 Prepared with Claude Code

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 21:21:30 +00:00
Kim Morrison
abc96e9ead chore: remove batteries dependency from ProofWidgets4 in release_repos.yml (#12535)
This PR removes the batteries dependency from ProofWidgets4 in
`release_repos.yml`. ProofWidgets4 no longer has any `require`
statements in its lakefile, so it doesn't depend on batteries.

🤖 Prepared with Claude Code

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 20:35:20 +00:00
Kim Morrison
f2d9161780 chore: make doc-gen4 release depend on mathlib4 (#12516)
This PR reorders doc-gen4 after mathlib4 in the release process.
Previously doc-gen4 was processed before mathlib4, but its benchmarks
reference the mathlib tag which doesn't exist yet at that point, causing
CI failures
(https://lean-fro.zulipchat.com/#narrow/channel/530199-rss/topic/Significant.20commits.20to.20doc-gen4/near/574125422).

🤖 Prepared with Claude Code

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 20:19:02 +00:00
Kim Morrison
91a150939f chore: remove stale release draft notes (#12518)
The `releases_drafts/` folder contained two entries that were already
covered in earlier releases:

- `module-system.md` — the module system was stabilized in v4.27.0
(https://github.com/leanprover/lean4/pull/11637)
- `environment.md` — the `importModules`/`finalizeImport` `loadExts`
change landed in v4.20.0 (https://github.com/leanprover/lean4/pull/6325)

Discovered while preparing the v4.29.0-rc1 release notes.

🤖 Prepared with Claude Code

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 19:56:23 +00:00
Kim Morrison
ede936c20f chore: begin development cycle for v4.30.0 (#12526)
This PR begins the development cycle for v4.30.0 by updating
`LEAN_VERSION_MINOR` to 30 in `src/CMakeLists.txt`.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 19:56:15 +00:00
Leonardo de Moura
a79c6c44ff feat: add Lean name demangling to runtime backtraces (#12533)
This PR adds human-friendly demangling of Lean symbol names in runtime
backtraces. When a Lean program panics, stack traces now show readable
names instead of mangled C identifiers.

**Before:**
```
3   libleanshared.dylib  0x1119afab4 l_Lean_Meta_Grind_main___redArg___lam__0___boxed + 52
5   libleanshared.dylib  0x10db232fc l_Lean_profileitIOUnsafe___redArg___lam__0 + 20
14  libleanshared.dylib  0x11204ec80 l___private_Lean_Meta_Tactic_Grind_Main_0__Lean_Meta_Grind_withProtectedMCtx_main___at___00Lean_Meta_Grind_withProtectedMCtx___at___00Lean_Elab_Tactic_grind_spec__1_spec__1___redArg___lam__0 + 516
17  libleanshared.dylib  0x10de2aa24 l___private_Lean_Meta_Basic_0__Lean_Meta_withNewMCtxDepthImp___redArg + 648
```

**After:**
```
3   libleanshared.dylib  0x1119afab4 Lean.Meta.Grind.main [boxed, λ, arity↓] + 52
5   libleanshared.dylib  0x10db232fc Lean.profileitIOUnsafe [λ, arity↓] + 20
14  libleanshared.dylib  0x11204ec80 Lean.Meta.Grind.withProtectedMCtx.main [private] spec at Lean.Meta.Grind.withProtectedMCtx spec at Lean.Elab.Tactic.grind[arity↓, λ] + 516
17  libleanshared.dylib  0x10de2aa24 Lean.Meta.withNewMCtxDepthImp [arity↓, private] + 648
```

The demangler is a C++ port of `Name.demangleAux` from
`NameMangling.lean` with human-friendly postprocessing:
- Suffix folding: `_redArg` → `[arity↓]`, `_boxed` → `[boxed]`, `_lam_N`
→ `[λ]`, `_closed_N` → `[closed]`, `_jp_N` → `[jp]`
- Private name cleanup: `_private.Module.0.Name.foo` → `Name.foo
[private]`
- Specialization context: `_at_`/`_spec` → `spec at ...`
- Hygienic suffix stripping: `_@` onward removed
- Runtime helpers: `lean_apply_N` → `<apply/N>`
- LLVM artifacts: `.cold.N` suffix preserved

Supports both macOS and Linux `backtrace_symbols` formats.

Set `LEAN_BACKTRACE_RAW=1` to disable demangling and get raw symbol
names.

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

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 19:20:54 +00:00
Mac Malone
e2407589ff fix: lake: cache clean should succeed w/ no cache dir (#12532)
This PR fixes a bug with `cache clean` where it would fail if the cache
directory does not exist.

This introduces a `removeDirAllIfExists` utility which is also now used
in `lake clean`. While `lake clean` did previously check for a
nonexistent build directory, this version should be more robust to
racing runs of `lake clean` as well.
2026-02-17 19:06:37 +00:00
Markus Himmel
61e09dd57e feat: convenience lemmas about hash maps (#12531)
This PR bundles some lemmas about hash maps into equivalences for easier
rewriting.

It still makes sense to have the individual directions since they
sometimes have weaker typeclass assumptions.
2026-02-17 18:14:21 +00:00
Wojciech Różowski
4dd6a99fec feat: improve decide_cbv error reporting (#12500)
This PR improves the error messages produced by the `decide_cbv` tactic
by only reducing the left-hand side of the equality introduced by
`of_decide_eq_true`, rather than attempting to reduce both sides via
`cbvGoal`.

Previously, `evalDecideCbv` called `cbvGoalCore` which would try to
reduce both sides of `decide P = true` and leave a remaining goal on
failure, resulting in a generic error showing the mvar ID. Now, a
dedicated `cbvDecideGoal` function in `Cbv/Main.lean`:

- closes the goal immediately when the LHS reduces to `Bool.true`
- reports a clear error when the LHS reduces to `Bool.false`, telling
the user the proposition is false
- reports a clear error with the stuck expression when reduction cannot
complete

Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-17 16:45:38 +00:00
Sebastian Graf
7272e761be fix: improve error message when mvcgen cannot resolve spec theorem (#12530)
This PR improves the error message when `mvcgen` cannot resolve the name
of a spec theorem.

Example:
```lean
/-- error: Could not resolve spec theorem `abc` -/
#guard_msgs (error) in
example : True := by mvcgen [abc]
```

This used to print the syntax object representing the ident "abc".
2026-02-17 16:37:34 +00:00
Copilot
63675d29d1 feat: add declaration name to leanchecker error messages (#12525)
This PR adds declaration names to leanchecker error messages to make
debugging easier when the kernel rejects a declaration.

Previously, leanchecker would only show the kernel error without
identifying which declaration failed:
```
uncaught exception: (kernel) type checker does not support loose bound variables
```

Now it includes the declaration name:
```
uncaught exception: while replaying declaration 'myDecl':
(kernel) type checker does not support loose bound variables
```

Fixes: #11937

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: nomeata <148037+nomeata@users.noreply.github.com>
2026-02-17 16:08:00 +00:00
Sebastian Graf
b491d868ed chore: respect transparency in restoreM and Std.Do related proofs (#12529) 2026-02-17 16:04:15 +00:00
David Thrane Christiansen
c5b58092e3 fix: allow checked self-references in Verso docstring module role (#12523)
This PR allows the `module` role in Verso docstrings to refer to the
current module without requiring the `-checked` flag.
2026-02-17 14:02:23 +00:00
Markus Himmel
50ca285237 feat: Std.Iter.toHashSet (#12524)
This PR adds `Std.Iter.toHashSet` and variants.

Included: variants starting from both monadic and non-monadic iterators,
producing extensional and non-extensional hash sets and tree sets.

Lemmas are included, showing that `it.toHashSet ~m HashSet.ofList
it.toList` (equivalence of hash sets) and `it.toExtHashSet =
ExtHashSet.ofList it.toList` (equality of extensional hash sets).
2026-02-17 13:53:15 +00:00
Wojciech Różowski
424fbbdf26 feat: add support to cbv_eval attribute (#12506)
This PR adds the ability to register theorems with the `cbv_eval`
attribute in the reverse direction using the `←` modifier, mirroring the
existing `simp` attribute behavior. When `@[cbv_eval ←]` is used, the
equation `lhs = rhs` is inverted to `rhs = lhs`, allowing `cbv` to
rewrite occurrences of `rhs` to `lhs`.

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

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 12:23:17 +00:00
Markus Himmel
200f65649a feat: relate HashSet.ofList to a fold (#12521)
This PR shows `HashSet.ofList l ~m l.foldl (init := ∅) fun acc a =>
acc.insert a` (which is "just" the definition).

We also include the analogous statement about `insertMany`, and prove
this lemmas for dependent hash maps, normal hash maps, hash sets, as
well as the raw and extensional versions, and of course we also give the
corresponding tree map statements.
2026-02-17 12:03:01 +00:00
Lean stage0 autoupdater
bfc5d43ad3 chore: update stage0 2026-02-17 12:18:17 +00:00
Henrik Böving
a5d0ab510e fix: regression caused by LCNF boxing (#12522)
This PR fixes a regression caused by the porting of the IR boxing to the
LCNF boxing pass.
2026-02-17 11:28:25 +00:00
Leonardo de Moura
91bd6e19a7 feat: add Lean name demangler and profiling pipeline (#12517)
This PR adds tooling for profiling Lean programs with human-readable
function names in Firefox Profiler:

- **`script/lean_profile.sh`** — One-command pipeline: record with
samply, symbolicate, demangle, and open in Firefox Profiler
- **`script/profiler/lean_demangle.py`** — Faithful port of
`Name.demangleAux` from `NameMangling.lean`, with a postprocessor that
folds compiler suffixes into compact annotations (`[λ, arity↓]`, `spec
at context[flags]`)
- **`script/profiler/symbolicate_profile.py`** — Resolves raw addresses
via samply's symbolication API
- **`script/profiler/serve_profile.py`** — Serves demangled profiles to
Firefox Profiler without re-symbolication
- **`PROFILER_README.md`** — Documentation including a guide to reading
demangled names

### Example output in Firefox Profiler

| Raw C symbol | Demangled |
|---|---|
| `l_Lean_Meta_Sym_main` | `Lean.Meta.Sym.main` |
| `l_Lean_Meta_foo___redArg___lam__0` | `Lean.Meta.foo [λ, arity↓]` |
| `l_Lean_MVarId_withContext___at__...___spec__2___boxed` |
`Lean.MVarId.withContext [boxed] spec at Lean.Meta.bar[λ, arity↓]` |

Example:

<img width="1145" height="570" alt="image"
src="https://github.com/user-attachments/assets/8d23cc6a-1b89-4c60-9f4a-9f9f0f6e7697"
/>


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

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 03:27:58 +00:00
Kim Morrison
642bcdf55a fix: handle universe polymorphism in ground grind theorems (#12226)
This PR fixes a bug where `grind [foo]` fails when the theorem `foo` has
a different universe variable name than the goal, even though universe
polymorphism should allow the universes to unify.

The issue was in `instantiateGroundTheorem` (used for theorems with no
quantified parameters), which was passing `thm.proof` directly instead
of calling `getProofWithFreshMVarLevels`. This meant ground theorems
retained their original universe level params instead of getting fresh
level metavariables that could unify with the goal's universe levels.

Fixes
https://leanprover.zulipchat.com/#narrow/channel/270676-lean4/topic/grind.20fails.20because.20of.20universe.20variable.20name

🤖 Prepared with Claude Code

Co-authored-by: Claude <noreply@anthropic.com>
2026-02-17 01:51:37 +00:00
Kim Morrison
2a8650f975 fix: gate reference-manual tagging on release notes title correctness (#12512)
This PR fixes a release workflow bug where the reference-manual
repository would get tagged with a stale release notes title (e.g.,
still showing "-rc1" for a stable release).

The root cause was a sequencing issue: `release_steps.py` didn't update
the release notes title when bumping the reference-manual toolchain, and
`release_checklist.py` only checked the title while the bump PR was
open. Once merged, it went straight to tagging without rechecking.

Two fixes:
- `release_checklist.py`: add a title correctness check before tagging
reference-manual (blocks tagging if the title is wrong)
- `release_steps.py`: automatically update the `#doc` title line in the
release notes file when bumping reference-manual (handles both
RC-to-stable and RC-to-RC transitions)

🤖 Prepared with Claude Code

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 01:20:36 +00:00
Nicolas Rouquette
69393c4d9e fix: add mem_eraseDups lemma for List deduplication (#11811)
This PR proves that membership is preserved by eraseDups: an element
exists in the deduplicated list iff it was in the original.

Includes a helper lemma for the loop invariant of eraseDupsBy.loop to
establish the relationship between membership in the result, remaining
list, and accumulator.

The proof changed compared to the proposal discussed on Zulip:
https://leanprover.zulipchat.com/#narrow/channel/348111-batteries/topic/Where.20should.20List.2Emem_eraseDup.20and.20List.2Emem_eraseDups.20l.2E.2E.2E

Specifically, I could not apply @Rob23oba 's short proof suggestion
because it is located in `src/Init/Data`, a context where the `grind`
strategy is not yet available.

In the Zulip thread, there is a discussion about the
similarities/differences between Lean's `List.eraseDups` and Batteries'
`List.eraseDup`; whether it makes sense to keep both (perhaps with a
suitable renaming of Batterie's definition) or deprecate one (if any, it
would be Batteries' since it is currently unused whereas Lean's is used
across the board in Lean, Batteries, and Mathlib). See the Batteries PR:
https://github.com/leanprover-community/batteries/pull/1580

changelog-library

Closes https://github.com/leanprover/lean4/issues/11786

---------

Co-authored-by: Kim Morrison <477956+kim-em@users.noreply.github.com>
2026-02-16 23:11:14 +00:00
Osman Yasar
6833b6dba8 feat: add BitVec.signExtend_extractLsb_setWidth theorem (#11943)
This PR introduces the theorem
`BitVec.sshiftRight_eq_setWidth_extractLsb_signExtend` theorem, proving
`x.sshiftRight n` is equivalent to first sign-extending `x`, extracting
the appropriate least significant bits, and then setting the width back
to `w`.

---------

Co-authored-by: Tobias Grosser <github@grosser.es>
2026-02-16 22:50:10 +00:00
Eric Paul
76c95a085b chore: remove unused variable in FileMap.ofString (#11986)
Removes the unused `line` variable in `FileMap.ofString`
2026-02-16 22:49:16 +00:00
Henrik Böving
0a19fe7d98 perf: strip unneeded symbols from libleanshared* (#12060)
This PR strips unneeded symbol names from libleanshared.so on Linux. It
appears that on other platforms the symbols names we are interested in
here are already removed by the linker.
2026-02-16 22:48:20 +00:00
Violeta Hernández Palacios
52db0be2b0 feat: define Squash as a Quotient (#12281)
This PR changes the definition of `Squash` to use `Quotient` by
upstreaming
[`true_equivalence`](https://leanprover-community.github.io/mathlib4_docs/Mathlib/Data/Quot.html#true_equivalence)
(now `equivalence_true`) and
[`trueSetoid`](https://leanprover-community.github.io/mathlib4_docs/Mathlib/Data/Quot.html#trueSetoid)
(now `Setoid.trivial`). The new definition is def-eq to the old one, but
ensures that `Squash` can be used whenever a `Quotient` argument is
expected without having to explicitly provide the setoid.

Besides being useful functionality, this makes Mathlib's
[`Trunc`](https://leanprover-community.github.io/mathlib4_docs/Mathlib/Data/Quot.html#Trunc)
completely equivalent to `Squash`. A future Mathlib PR will deprecate
the former in favor of the latter.

Reopened from #6642.

---------

Co-authored-by: David Thrane Christiansen <david@davidchristiansen.dk>
2026-02-16 22:46:43 +00:00
Paul Reichert
af7b3866b2 feat: prove xs.extract start stop = (xs.take stop).drop start for lists (#12359)
This PR deprecates `extract_eq_drop_take` in favor of the more correct
name `extract_eq_take_drop`, so that we'll be able to use the old name
for a lemma `xs.extract start stop = (xs.take stop).drop start`. Until
the deprecation deadline has passed, this new lemma will be called
`extract_eq_drop_take'`.
2026-02-16 22:43:44 +00:00
Paul Reichert
bf8ca518e7 feat: isSome_find? and isSome_findSome? (#12432)
This PR adds the lemmas `isSome_find?` and `isSome_findSome?` to the API
of lists, arrays and vectors.
2026-02-16 22:42:26 +00:00
Kim Morrison
8059477292 fix: auto-update ProofWidgets4 pin in mathlib4 during releases (#12503)
This PR adds automatic ProofWidgets4 version pin updates to
`release_steps.py` when processing mathlib4. ProofWidgets4 uses
sequential version tags (`v0.0.X`) rather than toolchain-based tags
(`v4.X.Y`), so the existing regex that updates dependency versions in
lakefiles doesn't match it. This has caused CI failures in two
consecutive releases where the mathlib4 PR was created with a stale
ProofWidgets4 pin.

Changes:
- `script/release_steps.py`: Add `find_proofwidgets_tag()` to look up
the latest ProofWidgets4 tag compatible with the target toolchain, and
use it to update mathlib4's lakefile automatically
- `doc/dev/release_checklist.md`: Document the ProofWidgets4 pin update
step for mathlib4

🤖 Prepared with Claude Code

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-16 22:39:22 +00:00
Joachim Breitner
2f8c85af89 fix: ensure etaStruct is enabled during type inference (#12507)
This PR fixes #12495 where equational theorem generation fails for
structurally recursive definitions using a Box-like wrapper around
nested inductives.

## Root Cause

`withInferTypeConfig` (in `InferType.lean`) ensures various MetaM config
settings (`beta`, `iota`, `zeta`, `zetaHave`, `zetaDelta`, `proj`) are
enabled during type inference, but was missing `etaStruct`. When
`inferType` is called from a context where `etaStruct` is disabled —
such as inside `simpMatch` (which sets `etaStruct := .none` via
`SimpM.run` → `withSimpContext`) — `whnf` cannot eta-expand structure
values needed for recursor iota reduction.

Concretely, projecting from a type like `Rec.rec_2 ... base` (where
`base : Box Rec`) requires eta-expanding `base` to `Box.mk base.data` so
the `Box` recursor can reduce. With `etaStruct := .none`,
`toCtorWhenStructure` skips the eta-expansion, leaving `whnf` stuck and
`inferProjType` unable to recognize the resulting type as a structure.

## Fix

Add `etaStruct := .all` to the config settings ensured by
`withInferTypeConfig`, alongside the existing `beta`, `iota`, `zeta`,
`zetaHave`, `zetaDelta`, and `proj` settings. This also allows reverting
the workaround (`try/catch` around `simpMatch?`) that was added in the
first commit.

## Test plan

- [x] Existing test `tests/lean/run/issue12495.lean` passes
- [x] Full test suite (3561 tests) passes with 0 failures

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

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-16 20:27:57 +00:00
Henrik Böving
b2446552b4 chore: cleanup backwards compat annotations in the LRAT checker (#12511) 2026-02-16 20:23:27 +00:00
Sebastian Ullrich
440d686313 perf: do not export environment extensions without entries (#12508)
Avoids wasted work in `setImportedEntries`. This is still not ideal as
exts that are set but never/rarely read still have a cost proportional
to the number of imported modules but it's an easy step forward.
2026-02-16 17:34:41 +00:00
Lean stage0 autoupdater
a166d6ee20 chore: update stage0 2026-02-16 16:51:43 +00:00
Sebastian Graf
c5c0ddcc56 test: remove let handling from Sym mvcgen (#12505)
This PR removes the unnecessary and potentially broken handling of
`let`s by zeta-reduction in Sym-based `mvcgen`.
It turns out to be unnecessary for the benchmarks so far, so there is a
lack of motivation to publicize `betaRevS` which would be needed to fix
it.
2026-02-16 15:58:36 +00:00
Leonardo de Moura
9a032cd261 feat: backward.isDefEq.respectTransparency (#12179)
This PR ensures `isDefEq` does not increase the transparency mode to
`.default` when checking whether implicit arguments are definitionally
equal. The previous behavior was creating scalability problems in
Mathlib. That said, this is a very disruptive change. The previous
behavior can be restored using the command
```
set_option backward.isDefEq.respectTransparency false
```
2026-02-16 15:57:21 +00:00
Kim Morrison
4979fa8415 chore: make Rat.abs lemmas protected (#12504)
This PR makes the `Rat.abs_*` lemmas (`abs_zero`, `abs_nonneg`,
`abs_of_nonneg`, `abs_of_nonpos`, `abs_neg`, `abs_sub_comm`,
`abs_eq_zero_iff`, `abs_pos_iff`) protected, so they don't shadow the
general `abs_*` lemmas when the `Rat` namespace is opened in downstream
projects.

All internal references already use the fully qualified `Rat.abs_*`
form, so this is a no-op within lean4 itself.

Suggested by @Rob23oba in
https://github.com/leanprover-community/mathlib4-nightly-testing/pull/177#discussion_r2812925068.

🤖 Prepared with Claude Code

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-16 15:49:04 +00:00
Paul Reichert
4a9a3eaf6b feat: Rxx.nodup_toList lemmas and slice/foldl lemmas (#12438)
This PR provides (1) lemmas showing that lists obtained from ranges have
no duplicates and (2) lemmas about `forIn` and `foldl` on slices.
2026-02-16 13:55:11 +00:00
Henrik Böving
620ef3bb86 fix: bring lengthTR lemma back into scope at toArray (#12502)
This PR brings the `length = lengthTR` lemma back into scope after shake
mistakenly removed it.
2026-02-16 13:33:10 +00:00
Sebastian Graf
f084ce1497 test: share benchmark driver for Sym mvcgen; don't measure unfolding (#12501)
This PR shares the driver code from the Sym-based mvcgen benchmarks. It
also moves the `simp only [loop, step]` call out of the measured
section, so that we measure purely the overhead of VC generation.

The new benchmark results are as follows. All measurements for n=1000:

```
baseline_add_sub_cancel:   719.318425 ms, kernel: 382.708178 ms
vcgen_add_sub_cancel:      306.883079 ms, kernel: 455.050825 ms
vcgen_deep_add_sub_cancel: 543.350543 ms, kernel: 896.926298 ms
vcgen_get_throw_set:       669.566541 ms, kernel: 60754.202714 ms
```

Note that `vcgen_add_sub_cancel` sped up by 100% because we no longer
measure unfolding `loop` and `step`. The baseline didn't speed up as
much because it unfolded in the same `Sym.simp` call that also does
other rewrites, so there was no `simp` pass that could be eliminated.
2026-02-16 13:17:00 +00:00
Henrik Böving
838ff5e850 chore: delete commented code (#12498) 2026-02-16 10:27:46 +00:00
Henrik Böving
efcfd967e0 perf: try to inline mix_hash (#12472)
This PR inlines `mix_hash` from C++ which provides general speedups for
hash functions.
2026-02-16 09:02:32 +00:00
Kim Morrison
5c7a508e21 fix: use target type's instances in delta deriving (#12339)
This PR fixes a diamond problem in delta deriving where
instance-implicit class parameters in the derived instance type were
using instances synthesized for the underlying type, not the alias type.

When deriving an instance for a type alias (e.g., `def ENat := WithTop
ℕ`), this caused a diamond when the alias has its own instance for a
dependency class (e.g., `AddMonoidWithOne` from `CommSemiring`) that
differs from the underlying type's instance (e.g.,
`WithTop.addMonoidWithOne`). Instance search would fail because it
expected the alias's instance but the derived instance used the
underlying's.

The fix: after synthesis succeeds, for each instance-implicit class
parameter, re-synthesize for the target type and use that instance if
it's defeq to what we synthesized for the underlying type.

### Example

```lean
class MyBase (α : Type) where value : Nat := 42
class MyHigher (α : Type) [MyBase α] : Prop where prop : True

instance instBaseNat : MyBase Nat := {}
def MyAlias := Nat
instance instBaseMyAlias : MyBase MyAlias := {}  -- Different expression, but defeq

instance instHigherNat : MyHigher Nat where prop := trivial
deriving instance MyHigher for MyAlias
```

**Before**: `instMyHigherMyAlias : @MyHigher MyAlias instBaseNat` →
instance search fails
**After**: `instMyHigherMyAlias : @MyHigher MyAlias instBaseMyAlias` →
instance search succeeds

### Motivation

This fixes the `CharZero ℕ∞` diamond in Mathlib under #12179 where the
derived instance was using `WithTop.addMonoidWithOne` instead of the
`AddMonoidWithOne` from `CommSemiring ℕ∞`.

🤖 Prepared with Claude Code

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-02-16 00:35:59 +00:00
Kim Morrison
bda15f6c25 chore: revert "feat: add higher-order Miller pattern support in e-matching (#12483)" (#12492)
This PR temporarily reverts #12483, which broke many proofs in Batteries
and Mathlib. We'll restore this once we have fixes in place.
2026-02-15 09:53:55 +00:00
Lean stage0 autoupdater
fb13783f5c chore: update stage0 2026-02-15 03:57:22 +00:00
Kim Morrison
8051e39a17 doc: expand docstring for @[univ_out_params] (#12487)
This PR expands the docstring for `@[univ_out_params]` to explain:

- How universe output parameters affect the typeclass resolution cache
(they are erased from cache keys, so queries differing only in output
universes share entries)
- When a universe parameter should be considered an output (determined
by inputs) vs. not (part of the question being asked)

This came up while adapting Mathlib for lean4#12286 and lean4#12423. We
needed `@[univ_out_params]` on ~19 classes (`Category`,
`HasLimitsOfSize`, `PreservesLimitsOfSize`, `Functor.IsContinuous`,
`UCompactlyGeneratedSpace`, etc.)

🤖 Prepared with Claude Code

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-15 03:22:42 +00:00
Leonardo de Moura
0d2a511bde fix: type class resolution cache (#12286)
This PR ensures the type resolution cache properly caches results for
type classe containing output parameters.

It ensures the cache key for a query like
```
HAppend.{0, 0, ?u} (BitVec 8) (BitVec 8) ?m
```
should be independent of the specific metavariable IDs in output
parameter positions. To achieve this, output parameter arguments are
erased from the cache key. Universe levels that only appear in output
parameter types (e.g., ?u corresponding to the result type's universe)
must also be erased to avoid cache misses when the same query is issued
with different universe metavariable IDs.

---------

Co-authored-by: Kim Morrison <kim@tqft.net>
2026-02-15 03:07:15 +00:00
Lean stage0 autoupdater
6a283751b9 chore: update stage0 2026-02-15 02:57:23 +00:00
Leonardo de Moura
fadb1e6b9b perf: cache for isDefEqI in Sym (#12486)
This PR caches `isDefEqI` results in `Sym`. During symbolic computation
(e.g., VC generators), we find the same instances over and over again.

Performance numbers before/after fr the benchmark
`tests/bench/mvcgen/sym/vcgen_deep_add_sub_cancel.lean`.

| Benchmark | Before (ms) | After (ms) | Speedup |
|-----------|------------|------------|---------|
| goal_100  | 192.1      | 134.1      | 30%     |
| goal_200  | 325.6      | 225.4      | 31%     |
| goal_300  | 490.2      | 333.5      | 32%     |
| goal_400  | 655.6      | 441.9      | 33%     |
| goal_500  | 861.9      | 553.1      | 36%     |
| goal_600  | 1060.6     | 664.5      | 37%     |
| goal_700  | 1166.8     | 818.7      | 30%     |
| goal_800  | 1393.3     | 919.0      | 34%     |
| goal_900  | 1524.2     | 1025.0     | 33%     |
| goal_1000 | 1663.1     | 1141.9     | 31%     |
2026-02-15 02:22:16 +00:00
Leonardo de Moura
97a7fed6c5 chore: add test for issue #11738 (#12485)
Closes #11738
2026-02-15 00:48:34 +00:00
Leonardo de Moura
0721313dcc chore: add test for issue #11930 (#12484)
Issue has already been fixed

Closes #11930
2026-02-15 00:41:28 +00:00
Leonardo de Moura
022bf2f822 feat: add higher-order Miller pattern support in e-matching (#12483)
This PR adds support for higher-order Miller patterns in `grind`'s
e-matching engine.

Previously, lambda arguments in e-matching patterns were always treated
as `dontCare`, meaning
they could not contribute to matching or bind pattern variables. This
was a significant limitation
for theorems where lambda arguments carry essential structure, such as
`List.foldl`, `List.foldrM`,
or any combinator that takes a function argument.

With this change, when a pattern argument is a lambda whose body
satisfies the **Miller pattern
condition** — i.e., pattern variables are applied only to distinct
lambda-bound variables — the
lambda is preserved as an `ho[...]` pattern. At instantiation time,
these higher-order patterns
are matched via `isDefEq` after all first-order pattern variables have
been assigned by the E-graph.

### Example

```lean
@[grind =] theorem applyFlip_spec (f : Nat → Nat → Nat) (a b : Nat)
    : applyFlip (fun x y => f y x) a b = f b a := sorry
```

The pattern `applyFlip ho[fun x => fun y => #2 y x] #1 #0` captures the
lambda argument
structurally: `#2` (the pattern variable for `f`) is applied to distinct
lambda-bound
variables `y` and `x`. When `grind` encounters `applyFlip (fun x y =>
Nat.add y x) 3 4`,
it binds `f := Nat.add` via `isDefEq` and fires the rewrite.

### Key design decisions

- **Miller condition check**: Only lambdas where at least one pattern
variable appears
in applied Miller position (applied to distinct lambda-bound vars) are
promoted to
  `ho[...]`. Other lambdas remain `dontCare`.
- **Redundancy elimination**: A post-processing pass demotes `ho[...]`
patterns to `dontCare`
if all their pattern variables already appear in non-HO positions of the
same pattern. This
avoids unnecessary `isDefEq` calls when the lambda doesn't contribute
new variable bindings.
- **E-graph bypass**: HO patterns are not internalized into the E-graph.
They are accumulated
during matching and checked via `isDefEq` after the first-order
assignment is complete.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-15 00:28:03 +00:00
Henrik Böving
c12c783f62 perf: remove the additional relabeling step during AIG to CNF conversion (#12480)
This PR skips the relabeling step during AIG to CNF conversion, reducing
memory pressure.
2026-02-14 17:08:07 +00:00
Leonardo de Moura
57a2dc0146 fix: handle heterogeneous equality in closeGoalWithValuesEq (#12477)
This PR fixes an internal `grind` error where `mkEqProof` is invoked
with terms of different types. When equivalence classes contain
heterogeneous equalities (e.g., `0 : Fin 3` and `0 : Fin 2` merged via
`HEq`), `closeGoalWithValuesEq` would call `mkEqProof` on terms with
incompatible types, triggering an internal error.

Closes #12140

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

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-14 07:12:01 +00:00
Lean stage0 autoupdater
d77efe6a5f chore: update stage0 2026-02-14 05:47:52 +00:00
Leonardo de Moura
cce6ce9577 fix: treat outParam arguments as support in e-matching patterns (#12476)
This PR fixes #12245 where `grind` works on `Fin n` but fails on `Fin (n
+ 1)`.

The `outParam` argument (e.g., the `range` parameter of `ToInt`) was
included as a relevant position in the e-matching pattern. The `grind`
normalizer rewrites `↑(n + 1)` to `↑n + 1` inside the range expression,
causing the pattern to no longer match. Since `outParam` arguments are
uniquely determined by type class resolution, they can be safely
wildcarded in patterns — the same reasoning that already applies to
instance-implicit arguments.

Reproducer from the issue:
```lean
example {n : Nat} {a : Fin (n + 1)} {b : Nat} (hb : b < n + 1)
    (h : (a : Nat) < b) : a < ⟨b, hb⟩ := by grind -- fails without fix
```

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

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-14 05:05:56 +00:00
Leonardo de Moura
103ed5b54b fix: handle delayed mvar assignments in grind when hypotheses contain mvars (#12475)
This PR fixes `grind` failing when hypotheses contain metavariables
(e.g., after `refine`). The root cause was that `abstractMVars` in
`withProtectedMCtx` only abstracted metavariables in the target, not in
hypotheses, creating a disconnect in grind's e-graph.

The fix removes `abstractMVars` and instead resolves delayed
metavariable assignments before exiting `withNewMCtxDepth`.
`instantiateMVars` refuses to resolve a delayed assignment when the
pending assignment is non-ground (contains unassigned expression
metavariables). This function converts such delayed assignments to
regular ones using `LocalContext.mkLambda`, allowing `instantiateMVars`
to resolve them via beta reduction. The mvar internalization warning is
also removed since grind now handles mvars.

Closes #12242

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-14 04:28:06 +00:00
Leonardo de Moura
187a8c1ce3 fix: handle non-internalized terms in semiring reifier (#12474)
This PR fixes a panic in `grind` where `sreifyCore?` could encounter
power subterms not yet internalized in the E-graph during nested
propagation. The ring reifier (`reifyCore?`) already had a defensive
`alreadyInternalized` check before creating variables, but the semiring
reifier (`sreifyCore?`) was missing this guard. When `propagatePower`
decomposed `a ^ (b₁ + b₂)` into `a^b₁ * a^b₂` and the resulting terms
triggered further propagation, the semiring reifier could be called on
subterms not yet in the E-graph, causing `markTerm` to fail.

Closes #12428

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

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-14 01:08:42 +00:00
Leonardo de Moura
e579dfdb14 fix: assertion violation in mkEqProofImpl (#12473)
This PR fixes an assertion violation in `grind` reported at #12246 This
assertion fails when in examples containing heterogenous equalities with
elements of different types (e.g., `Fin n` and `Fin m`) attached to the
same theory solver.

Closes #12246
2026-02-14 00:43:15 +00:00
Kim Morrison
c1ad6aa0db fix: disable order and funCC modules in NoopConfig (#11744)
This PR fixes a bug where `lia` was incorrectly solving goals involving
ordered types like `Rat` that it shouldn't handle. The `lia` tactic is
intended for linear integer arithmetic only.

The fix adds `order := false` and `funCC := false` to `NoopConfig`,
which is the base configuration for `CutsatConfig` (used by `lia`).

Closes
https://leanprover.zulipchat.com/#narrow/channel/113488-general/topic/releases.20of.20new.20Lean.20versions/near/564688881

🤖 Prepared with Claude Code

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-13 20:41:12 +00:00
Paul Reichert
c6f4fa8678 fix: inherit correct docstring (#12468)
This PR fixes the docstring of `HashMap.diff`.
2026-02-13 16:20:41 +00:00
Paul Reichert
db9293ee3b fix: remove redundant namespace (#12454)
This PR removes several duplicated namespaces such as in
`Vector.Vector.toList_zip`.
2026-02-13 14:51:59 +00:00
Wojciech Różowski
dae150a976 fix: handle AppBuilderException in cbv tactic if the projection function is dependent (#12460)
This PR fixes an `AppBuilder` exception in the `cbv` tactic when
simplifying projections whose projection function is dependent (closes
#12457).

Previously, `handleProj` unconditionally used `mkCongrArg` to prove `e.i
= e'.i` from `e = e'`, but `mkCongrArg` requires a non-dependent
function. For dependent projections (e.g., `fun x => x.2 : (x :
String.Slice) → x.1.Pos`), this would fail.

Now, `handleProj` first checks whether the projection function type is
non-dependent (a simple arrow). If so, it proceeds with `mkCongrArg` as
before. Otherwise, it falls back to:
1. Attempting to reduce the projection directly.
2. If reduction fails, using a heterogeneous congruence lemma
(`mkHCongr`) converted to an equality via `mkEqOfHEq`, provided the
original and rewritten struct are definitionally equal.
2026-02-13 14:21:13 +00:00
Wojciech Różowski
f3b8f76ec4 test: add cbv benchmark for evaluating Decidable.decide (#12467)
This PR adds a benchmark for `cbv` tactic for evaluating
`Decidable.decide` for a `Decidable` instance for a problem of checking
if a number is not a prime power.

The test has been inspired by a recent discussion on the [leanprover
zulip](https://leanprover.zulipchat.com/#narrow/channel/287929-mathlib4/topic/Can't.20decide.20if.2015.20is.20a.20prime.20power/with/572513330).
2026-02-13 13:25:35 +00:00
Henrik Böving
6ae413a56a perf: make proper use of deletes in bv_decide LRAT checking (#12406)
This PR implements two changes to LRAT checking in `bv_decide`:
1. The LRAT trimmer previously used to drop delete instructions as we
did not act upon them in a meaningful way (as explained in 2). Now it
figures out the earliest point after which a clause may be deleted in
the trimmed LRAT proof and inserts a deletion there.
2. The LRAT checker takes in an `Array IntAction` and explodes it into
an `Array DefaultClauseAction` before passing it into the checking loop.
`DefaultClauseAction` has a much larger memory footprint compared to
`IntAction`. Thus materializing the entire proof as
`DefaultClauseAction` upfront consumes a lot of memory. In the adapted
LRAT checker we take in an `Array IntAction` and only ever convert the
step we are currently working on to a `DefaultClauseAction`. In
combination with the fact that we now insert deletion instructions this
can drastically reduce memory consumption.

In SMT-LIB's 20210312-Bouvier/vlsat3_a11.smt2 memory consumption went
from 8GB+ to 3.7GB through this combination of changes.
2026-02-13 13:11:51 +00:00
Henrik Böving
92aec45057 perf: boxing a uint64 yields and object not a tobject (#12465)
This PR changes the boxed type of `uint64` from `tobject` to `object` to
allow for more precise reference counting.
2026-02-13 12:14:37 +00:00
Henrik Böving
c8462354c6 fix: handle 0 sized reads from handles correctly (#12466)
This PR handles zero-sized reads on handles correctly by returning an
empty array before the syscall
is even attempted.

Closes: #12138
2026-02-13 10:56:00 +00:00
Henrik Böving
4eabd86604 chore: put compiler off critical path (#12464) 2026-02-13 10:47:43 +00:00
Henrik Böving
9f64f53fef refactor: port Boxing from IR to LCNF (#12458)
This PR ports the IR pass for box/unbox insertion to LCNF.
2026-02-13 09:56:50 +00:00
Kim Morrison
6ca23a7b8b fix: nightly revision date logic and mathlib trigger auth (#12463)
This PR fixes two issues discovered during the first test of the revised
nightly release workflow
(https://github.com/leanprover/lean4/pull/12461):

**1. Date logic:** The `workflow_dispatch` path used `date -u +%F`
(current UTC date) to find the base nightly to revise. If the most
recent nightly was from yesterday (e.g. `nightly-2026-02-12`) but UTC
has rolled over to Feb 13, the code would look for `nightly-2026-02-13`,
not find it, and create a fresh nightly instead of a revision. Now finds
the latest `nightly-*` tag via `sort -rV` and creates a revision of
that.

**2. Mathlib trigger:** The "Update toolchain on mathlib4's
nightly-testing branch" step was broken in two ways:
- Workflow renamed: `nightly_bump_toolchain.yml` →
`nightly_bump_and_merge.yml` (leanprover-community/mathlib4#34827)
- `MATHLIB4_BOT` PAT expired after mathlib migrated to GitHub Apps
(leanprover-community/mathlib4#34751)
- Replace with `actions/create-github-app-token` using the
`mathlib-nightly-testing` app, matching the pattern used in mathlib4's
own workflows.

🤖 Prepared with Claude Code

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-13 06:03:15 +00:00
Kim Morrison
e760b8ddf5 doc: add IJCAR 2026 grind paper examples (#12462)
This adds the ancillary materials for the IJCAR 2026 grind paper to
`doc/examples/IJCAR2026/`.

- `examples.lean`: interactive examples from the paper
- `analyze_grind_loc.py`: script used for the evaluation section
(analyzing grind adoption LoC changes in mathlib)

🤖 Prepared with Claude Code

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-13 04:02:11 +00:00
Kim Morrison
d7e57b66d5 feat: support revised nightly releases (nightly-YYYY-MM-DD-revK) (#12461)
This PR adds support for manually re-releasing nightlies when a build
issue or critical fix requires it. When a `workflow_dispatch` triggers
the nightly release job and a `nightly-YYYY-MM-DD` tag already exists,
the CI now creates `nightly-YYYY-MM-DD-rev1` (then `-rev2`, etc.)
instead of silently skipping.

### Lake `ToolchainVer`

- Extend `ToolchainVer.nightly` with an optional `rev : Option Nat`
field
- Parse `-revK` suffixes from nightly tags in `ofString`
- Ordering: `nightly-YYYY-MM-DD` < `nightly-YYYY-MM-DD-rev1` < `-rev2` <
`nightly-YYYY-MM-DD+1`
- Round-trip: `toString (ofString s) == s` for both variants

### CI workflow

- "Set Nightly" step probes existing tags on `workflow_dispatch` to find
next available `-revK`
- Scheduled nightlies retain existing behavior (skip if commit already
tagged)
- Changelog grep updated from `nightly-[-0-9]*` to `nightly-[^ ,)]*` to
match `-revK` suffixes

### `lean-bisect`

- Updated `NIGHTLY_PATTERN` regex, sort key, error messages, and help
text

### Companion PRs

- https://github.com/leanprover-community/mathlib4/pull/35220: update
`nightly_bump_and_merge.yml` tag grep and `nightly_detect_failure.yml`
warning message
-
https://github.com/leanprover-community/leanprover-community.github.io/pull/787:
update `tags_and_branches.md` documentation

🤖 Prepared with Claude Code

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-13 00:41:04 +00:00
Kim Morrison
56bd8cd0c2 refactor: remove unnecessary use of constNames in LazyDiscrTree (#12422)
This PR bounds-checks `constants.size` directly instead of
`constNames.size` in `LazyDiscrTree.loadImportedModule`, eliminating the
unsafe `constants[i]!` access and the intermediate `constNames[i]`
lookup. The constant name is obtained from `constInfo.name` instead.

🤖 Prepared with Claude Code

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-12 23:52:48 +00:00
Markus Himmel
6cbaada1bf feat: verification of String.positions, String.chars, String.revPositions, String.revChars, ForIn m String Char (#12456)
This PR verifies all of the `String` iterators except for the bytes
iterator by relating them to `String.toList`.

Along the way we define `String.posLE` and `String.posLT` analogously to
`String.posGE` and `String.posGT` and redefine `String.prev` to go
through `String.posLT`.

We also define and verify `String.positionsFrom` and
`String.revPositionsFrom`, which are the obvious generaliziations of
`String.positions` and `String.revPositions` starting at a positions
other than the start/end.

Finally, we get various lemmas about strings and positions, including
some nice induction principles `String.Pos.next_induction` and
`String.Pos.prev_induction`.

Of course, we also have all of the analogous results for `String.Slice`.
2026-02-12 15:32:44 +00:00
Henrik Böving
db12e64845 chore: mitigate noncomputable section issues in the code generator (#12453)
This is a mitigation for the fact that the upfront noncomputable checker
currently doesn't error out early enough in certain situations so we
violate invariants later on.
2026-02-12 12:34:49 +00:00
Wojciech Różowski
3b2944205b doc: improve docstrings for cbv and decide_cbv (#12439)
This PR improves docstrings for `cbv` and `decide_cbv` tactics
2026-02-12 10:31:17 +00:00
Henrik Böving
d9cea67e24 perf: fold Task.get (Task.pure x) to just x (#12446)
This PR adds a simplification rule for `Task.get (Task.pure x) = x` into
the LCNF simplifier. This
ensures that we avoid touching the runtime for a `Task` that instantly
gets destructed anyways.
2026-02-12 08:29:52 +00:00
Markus Himmel
01173b195f chore: move string iteration to a new file (#12450)
This PR moves the `String.Slice`/`String` iterators out into their own
file, in preparation for verification.
2026-02-12 06:56:53 +00:00
Markus Himmel
7b29425361 chore: simplify Char.toString to String.singleton (#12449)
This PR marks `String.toString_eq_singleton` as a `simp` lemma.
2026-02-12 06:10:36 +00:00
Mac Malone
9073ad37bb feat: lake: hard link cache artifacts (#12203)
This PR changes the way artifacts are transferred from the local Lake
cache to a local build path. Now, Lake will first attempt to hard link
the local build path to artifact in the cache. If this fails (e.g.,
because the cache is on a different file system or drive), it will
fallback to pre-existing approach of copying the artifact. Lake also now
marks cache artifacts as read-only to avoid corrupting the cache by
writing to a hard linked artifact.

Lake will also hard link binary artifacts into the cache. If this fails,
it will similarly fall back to copying them. Text artifacts are always
copied, not linked, as the line endings in the cache copy are
normalized.
2026-02-12 01:26:16 +00:00
Mac Malone
cddacacb46 feat: lake: lake cache clean (#12444)
This PR adds the Lake CLI command `lake cache clean`, which deletes the
Lake cache directory.
2026-02-11 23:33:09 +00:00
Sebastian Graf
204555ba83 test: add Sym vcgen benchmarks get_throw_set and deep_add_sub_cancel (#12447)
This PR adds two more benchmarks for the Sym-based mvcgen prototype in
the style of `add_sub_cancel`.

The first is `deep_add_sub_cancel`, which is like `add_sub_cancel` but
with a much deeper monad stack:
```lean
abbrev M := ExceptT String <| ReaderT String <| ExceptT Nat <| StateT Nat <| ExceptT Unit <| StateM Unit
```
By specializing the specs for `get` and `set`, we get competitive
performance:
```
goal_100: 180.365086 ms, kernel: 79.634989 ms
goal_200: 313.465611 ms, kernel: 187.808631 ms
goal_300: 478.278585 ms, kernel: 270.210634 ms
goal_400: 638.884320 ms, kernel: 380.381127 ms
goal_500: 759.802772 ms, kernel: 472.662882 ms
goal_600: 933.575180 ms, kernel: 649.040746 ms
goal_700: 1174.367200 ms, kernel: 759.470010 ms
goal_800: 1298.866482 ms, kernel: 864.420171 ms
goal_900: 1475.315552 ms, kernel: 1008.662783 ms
goal_1000: 1627.957444 ms, kernel: 1078.627830 ms
```
Recall that `add_sub_cancel` had `goal_1000: 824.476962 ms, kernel:
477.069045 ms`, but that doesn't need to repeatedly unwrap 3 layers of
the monad.

The second benchmark is `get_throw_set`. Its kernel is
```lean
def step (lim : Nat) : ExceptT String (StateM Nat) Unit := do
  let s ← get
  if s > lim then
    throw "s is too large"
  set (s + 1)

def loop (n : Nat) : ExceptT String (StateM Nat) Unit := do
  match n with
  | 0 => pure ()
  | n+1 => loop n; step n

def Goal (n : Nat) : Prop := ⦃fun s => ⌜s = 0⌝⦄ loop n ⦃⇓_ s => ⌜s = n⌝⦄
```
It will generate `n+1` VCs. We get `n` VCs of the form 
```
s✝ : Nat
_ : ¬0 < s✝
...
_ : n < s✝ + 1 ...<n times>... + 1
⊢ ⌜s✝ = 0⌝ ⊢ₛ ⌜False⌝ (s✝ + ...<n times>...)
```
and one VC of the form
```
⌜s✝ = 0⌝ ⊢ₛ ⌜s✝ + 1 + <n times> ... + 1 = n⌝
```
which can be discharged by `grind`, but presently are discharged with
`sorry`.
Statistics:
```
goal_100: 209.435869 ms, kernel: 128.768919 ms
goal_200: 386.639441 ms, kernel: 482.244717 ms
goal_300: 559.795137 ms, kernel: 1251.777405 ms
goal_400: 753.243978 ms, kernel: 3020.878177 ms
goal_500: 1014.939522 ms, kernel: 5182.120327 ms
goal_600: 1229.173622 ms, kernel: 9296.551442 ms
goal_700: 1410.024180 ms, kernel: 16655.954682 ms
goal_800: 1684.059305 ms, kernel: 32065.951705 ms
goal_900: 1905.602401 ms, kernel: 55299.942894 ms
goal_1000: 2172.823244 ms, kernel: 84082.492485 ms
```

Need to look at kernel times here, but tactic time looks about alright.

Using `grind` to discharge just `n=100` goals took 8s.
2026-02-11 19:54:23 +00:00
Paul Reichert
03ffde3fde feat: DecidableEq instances for range types (#12442)
This PR derives `DecidableEq` instances for the types of ranges such as
`a...b` (in this case, `Std.Rco`).
2026-02-11 18:41:29 +00:00
Henrik Böving
975f81d560 fix: use getImpureSignature? everywhere (#12436)
This PR uses `getImpureSignature?` instead of the `findEnvDecl` from IR
in the LCNF compiler. We
were previously still relying on the IR function because only IR
contained proper borrow
annotations. Now we infer the borrow annotations on the LCNF level and
can thus use the LCNF
signatures.
2026-02-11 17:10:25 +00:00
Lean stage0 autoupdater
6a1550d3be chore: update stage0 2026-02-11 16:33:52 +00:00
Leonardo de Moura
483cad5fd6 feat: add [univ_out_params] (#12423)
This PR adds the attribute `@[univ_out_params]` for specifying which
universe levels should be treated as output parameters. By default, any
universe level that does not occur in any input parameter is considered
an output parameter.
2026-02-11 15:42:00 +00:00
Markus Himmel
6f51ec27ed feat: verification of String.Slice.splitToSublice (#12437)
This PR verifies the `String.Slice.splitToSubslice` function by relating
it to a model implementation `Model.split` based on a
`ForwardPatternModel`.

The proof is generic, so it works for splitting by characters, strings
etc.

From this, we will be able to give user-facing API lemmas for
`String.split` and friends in future PRs.

We also move the verification of string patterns from
`String.Slice.Pattern` to `String.Slice.Pattern.Model` to achieve better
separation between code that users run in their programs and code that
only supports the theory.
2026-02-11 14:29:26 +00:00
Rob23oba
964d6a3082 chore: make proof for Char.toUpper easier to typecheck (#12431)
This PR changes the proof for `Char.toUpper` to make it easier to
typecheck for external checkers (nanoda in particular).

---------

Co-authored-by: Joachim Breitner <mail@joachim-breitner.de>
2026-02-11 14:00:00 +00:00
Paul Reichert
8f0b67a92e feat: add array lemmas about sum/min/max (#12249)
This PR adds some lemmas about the interaction of `sum`, `min` and `max`
about arrays that already exist for lists.
2026-02-11 13:08:18 +00:00
Henrik Böving
818a0ec6a7 refactor: don't use shared_timed_mutex when not required anymore (#12434)
This PR removes the uses of `shared_timed_mutex` that were introduced
because we were stuck on C++14
with the `shared_mutex` available from C++17 and above.
2026-02-11 12:53:42 +00:00
Lean stage0 autoupdater
94deb89556 chore: update stage0 2026-02-11 12:59:14 +00:00
Henrik Böving
cad960267b refactor: port borrow inference to LCNF (#12413)
This PR ports the IR borrow pass to LCNF.
2026-02-11 12:08:17 +00:00
Paul Reichert
20de0531e0 feat: add lemma Acc.inv_of_transGen (#12426)
This PR adds the lemma `Acc.inv_of_transGen`, a generalization of
`Acc.inv`. While `Acc.inv` shows that `Acc r x` implies `Acc r y` given
that `r y x`, the new lemma shows that this also holds if `y` is only
*transitively* related to `x`.
2026-02-11 11:51:11 +00:00
Joachim Breitner
f20cae3729 fix: no defeq equations for irreducible definitions (#12429)
This PR sets the `irreducible` attribute before generating the equations
for recursive definitions. This prevents these equations to be marked as
`defeq`, which could lead to `simp` generation proofs that do not type
check at default transparency.

This issue is surfacing more easily since well-founded recursion on
`Nat` is implemented with a dedicated fix point operator (#7965). Before
that, `WellFounded.fix` was used, which is inherently not reducing, so
we did get the desired result even without the explicit reducibility
setting.

Fixes #12398.
2026-02-11 11:49:10 +00:00
Wojciech Różowski
9bfd16ef5e refactor: main loop of the cbv tactic (#12417)
This PR refactors the main loop of the `cbv` tactic. Rather than using
multiple simprocs, a central pre simproc is introduced. Moreover, let
expressions are no longer immediately zeta-reduced due to performance on
one of the benchmarks (`leroy.lean`).

Stacked on top of #12416
2026-02-11 11:47:18 +00:00
Paul Reichert
d4af4e26f9 feat: upstream Rat.abs and Int/Rat lemmas from human-eval-lean (#12412)
This PR introduces `Rat.abs` and adds missing lemmas about `Int` and
`Rat`.

For `Int`:
- Lemmas about the interaction of negation and order: `neg_le_iff`,
`le_neg_iff`, `neg_lt_iff`, `lt_neg_iff`

For `Rat`:
- Declaration of `Rat.abs`
- Lemmas for `Rat.abs`, `Rat.floor` and `Rat.ceil`
- More basic lemmas that would all be provable with `grind` but might
still be good to have in the library
- Type class instances: `Std.Associative`, `Std.Commutative`,
`Std.LawfulIdentity` for addition
2026-02-11 11:07:08 +00:00
Wojciech Różowski
c0df714935 feat: add decide_cbv tactic (#12411)
This PR adds a finishing `decide_cbv` tactic, which applies
`of_decide_eq_true` and then tries to discharge the remaining goal using
`cbv`.


Stacked on top of #12408.

---------

Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-11 10:12:23 +00:00
Rob23oba
be442e9bb3 perf: cache results in replaceRecApps (#12420)
This PR adds caching to `replaceRecApps`, the procedure responsible for
replacing recursive applications for wellfounded recursion, improving
performance when many references to the same recursive call exist, e.g.
when recursive calls exist in proof terms.

Closes #12404

---------

Co-authored-by: Joachim Breitner <mail@joachim-breitner.de>
2026-02-11 09:47:34 +00:00
Sebastian Graf
655cc1178c fix: make mvcgen suggest -trivial on recursion depth error (#12427)
This PR makes `mvcgen` suggest to use `-trivial` when doing so avoids a
recursion depth error.
2026-02-11 09:10:27 +00:00
Wojciech Różowski
64bd08e1f9 feat: add non-conv, user-facing cbv tactic (#12408)
This PR adds a user facing `cbv` tactic that can be used outside of the
`conv` mode.

Example usage:
```lean4
example : "hello" ++ " " ++ "world" = "hello world" := by cbv
```

---------

Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-11 09:04:11 +00:00
Markus Himmel
61cef96cd7 feat: verification of our KMP implementation (#12424)
This PR gives a proof of `LawfulToForwardSearcherModel` for `Slice`
patterns, which amounts to proving that our implementation of KMP is
correct.

Note that this PR also changes the KMP implementation to make it
slightly more efficient and easier to verify. I also have a correctness
proof for the old implementation, so there were no bugs in the old
implementation.
2026-02-11 08:20:20 +00:00
Sebastian Graf
99c83b9c76 fix: mvcgen support for match splitting on excess state args (#12425)
This PR fixes a bug in `mvcgen` caused by incomplete `match` splitting. 

In particular, if a program `match s with ...` matches on a state
variable `s` (presumably the result of a call to `get`), then `s` will
also occur in the stateful goal `H ⊢ₛ wp⟦match s with ...⟧ Q s`
*outside* the program expression; this was not anticipated before.
2026-02-11 08:12:25 +00:00
Paul Reichert
6056dee22f feat: add missing order instances and lemmas (#12419)
This PR adds `LawfulOrderOrd` instances for `Nat`, `Int`, and all
fixed-width integer types (`Int8`, `Int16`, `Int32`, `Int64`, `ISize`,
`UInt8`, `UInt16`, `UInt32`, `UInt64`, `USize`). These instances
establish that the `Ord` instances for these types are compatible with
their `LE` instances. Additionally, this PR adds a few missing lemmas
and `grind` patterns.
2026-02-11 07:38:00 +00:00
Mac Malone
9da8f5dce4 feat: lake: record cache service in outputs (#12113)
This PR changes the alters the file format of outputs stored in the
local Lake cache to include an identifier indicating the service (if
any) the output came from. This will be used to enable lazily
downloading artifacts on-demand during builds.
2026-02-11 04:29:45 +00:00
Henrik Böving
e5f0d6283a chore: update to c++20 (#12117)
This PR upgrades Lean's internal toolchain to use C++20 as a preparatory
step for #12044.
2026-02-11 01:17:40 +00:00
Lean stage0 autoupdater
d886be121e chore: update stage0 2026-02-10 22:17:26 +00:00
Kim Morrison
5115b8f361 doc: add test suite documentation to .claude/CLAUDE.md (#12421)
This PR adds comprehensive test running instructions to
`.claude/CLAUDE.md`,
replacing the single `test_single.sh` example with the full range of
test
commands documented in `doc/dev/testing.md`: full suite, filtered by
name,
rerun failures, and direct ctest usage.

🤖 Prepared with Claude Code

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-10 21:42:06 +00:00
Eric Wieser
c5d2796069 fix: avoid unaligned pointer dereference (#12318)
This PR avoids undefined behavior in `String.Slice.hash` on unaligned
substrings.
This could produce a SIGILL on some Arm platforms.

Closes #12317
2026-02-10 20:40:24 +00:00
Leonardo de Moura
c4d85b7622 chore: revert reducibility change PartialOrder.rel (#12407)
This PR is similar to #12403.

We previously conjectured that "all type class fields that are types
should be marked as reducible." The problem is that propositions are
types, but they are also used as data (with `Decidable`). For example,
we often see the proposition `x <= y` as a Boolean. So, we refined the
conjecture to:

"All type class fields that are types (and not propositions) should be
marked as reducible."
2026-02-10 19:46:36 +00:00
Leonardo de Moura
14b595e952 feat: better support for eta expanded terms in grind (#12415)
This PR improves the support for eta expanded terms in `grind` patterns.

Closes #12390
2026-02-10 19:46:00 +00:00
crStiv
80257a6901 doc: fix typos (#12418)
Nothing fancy, just fixed some typos of different importance
2026-02-10 19:26:49 +00:00
Wojciech Różowski
a6ba7312cc chore: make toBetaApp public (#12416)
This PR makes `Sym.Simp.toBetaApp` public. This is necessary for the
refactor of the main `cbv` simproc in #12417.
2026-02-10 19:02:01 +00:00
Sebastian Graf
fc4f51d759 feat: pick up specs from the local context in mvcgen (#12395)
This PR adds `mvcgen` support for specifications in the local context.
Example:

```lean
import Std.Tactic.Do

open Std.Do

set_option mvcgen.warning false

def foo (x : Id Nat → Id Nat) : Id Nat := do
  let r₁ ← x (pure 42)
  let r₂ ← x (pure 26)
  pure (r₁ + r₂)

theorem foo_spec
    (x : Id Nat → Id Nat)
    (x_spec : ∀ (k : Id Nat) (_ : ⦃⌜True⌝⦄ k ⦃⇓r => ⌜r % 2 = 0⌝⦄), ⦃⌜True⌝⦄ x k ⦃⇓r => ⌜r % 2 = 0⌝⦄) :
    ⦃⌜True⌝⦄ foo x ⦃⇓r => ⌜r % 2 = 0⌝⦄ := by
  mvcgen [foo, x_spec] <;> grind

def bar (k : Id Nat) : Id Nat := do
  let r ← k
  if r > 30 then return 12 else return r

example : ⦃⌜True⌝⦄ foo bar ⦃⇓r => ⌜r % 2 = 0⌝⦄ := by
  mvcgen [foo_spec, bar] -- unfold `bar` and automatically apply the spec for the higher-order argument `k`
```
2026-02-10 15:51:26 +00:00
Wojciech Różowski
7d32030729 feat: add cbv_eval attribute (#12296)
This PR adds `cbv_eval` attribute that allows to evaluate functions in
`cbv` tactic using pre-registered theorems.
2026-02-10 15:41:42 +00:00
Sebastian Ullrich
bc30710c67 fix: LCNF.getImpureSignature? on imported decls (#12410) 2026-02-10 14:43:31 +00:00
Garmelon
d29407b481 chore: remove outdated trust0 test (#12401) 2026-02-10 13:07:10 +00:00
Paul Reichert
df26bea7c1 feat: upstream slice API improvements from human-eval-lean (#12352)
This PR improves the slice API with lemmas for `drop`/`take` operations
on `Subarray` and more lemmas about `Std.Slice.fold`, `Std.Slice.foldM`
and `Std.Slice.forIn`. It also changes the `simp` and `grind`
annotations for `Slice`-related lemmas. Lemmas converting between slices
of different shapes are no longer `simp`/`grind`-annotated because they
often complicated lemmas and hindered automation.
2026-02-10 10:54:07 +00:00
Wojciech Różowski
82d90b4cdc fix: force unfolding of the Decidable instace in Decidable.rec (#12399)
This PR adds a custom simproc to handle `Decidable.rec`, where we force
the rewrite in the argument of the `Decidable` type, that normally is
not rewritten due to being a subsingleton.

Closes #12386
2026-02-10 10:49:19 +00:00
Henrik Böving
611337ecee doc: the different chunks of the pass manager (#12400) 2026-02-10 08:48:36 +00:00
Henrik Böving
7488201604 refactor: port IR.SimpCase to LCNF (#12384)
This PR ports the IR SimpCase pass to LCNF.
2026-02-10 08:35:31 +00:00
Kim Morrison
4cdc199f77 chore: revert reducibility change to HasSSubset for now (#12403)
This PR reverts `attribute [reducible] HasSSubset.SSubset` from #12368,
as it is not immediately needed, and causes breakages in Mathlib.
2026-02-10 06:33:38 +00:00
Wojciech Różowski
af2444a140 perf: always zeta reduce let expressions in cbv (#12397)
This PR adds zeta reduction simproc to the pre step of `cbv`.
2026-02-09 18:45:58 +00:00
Wojciech Różowski
57c5efe309 fix: handling of ite/dite expressions in cbv tactic (#12361)
This PR develops custom simprocs for dealing with `ite`/`dite`
expressions in `cbv` tactics, based on equivalent simprocs from
`Sym.simp`, with the difference that if the condition is not reduced to
`True`/`False`, we make use of the decidable instance and calculate to
what the condition reduces to.

Stacked on top of #12391.
2026-02-09 15:00:10 +00:00
Mac Malone
919721c758 feat: IO.FS.Metadata.numLinks (#12277)
This PR adds `IO.FS.Metadata.numLinks`, which contains the number of
hard links to a file.

This changes the implementation of `System.FilePath.metadata` and
`System.FilePath.symlinkMetadata` to use libuv. Otherwise, `st_nlink`
was not properly set on Windows. This also has the side benefit of
provided sub-second precision for file times on Windows (fulfilling an
old TODO). Also, while libuv supports `lstat` for Windows, enabling that
is left to a future PR.
2026-02-09 14:28:56 +00:00
Mac Malone
9a15df5e28 fix: IO.FS.removeFile should delete read-only files on Windows (#12282)
This PR fixes a platform inconsistency in `IO.FS.removeFile` where it
could not delete read-only files on Windows.

The implementation now uses `uv_fs_unlink` instead of `std::remove`, as
libuv can delete read-only files. The PR also fixes a inconsistency in
`IO_test.lean` where it would generate files in the wrong directory when
run interactively.

---------

Co-authored-by: Markus Himmel <markus@himmel-villmar.de>
2026-02-09 14:28:31 +00:00
Wojciech Różowski
4401117a6a chore: make simpCond public (#12391)
This PR makes `simpCond` public. It is needed to avoid code duplication
in #12361
2026-02-09 13:54:36 +00:00
Sebastian Graf
7a8e011603 test: support ite splitting and lifting through ExceptT to Sym mvcgen (#12392) 2026-02-09 13:41:35 +00:00
Henrik Böving
892475dcac fix: LCNF casesOnCtor can only act if type correct (#12387)
This PR fixes an issue in LCNF simp where it would attempt to act on
type incorrect `cases`
statements and look for a branch, otherwise panic. This issue did not
yet manifest in production as
various other invariants upheld by LCNF simp help mask it but will start
to become an issue with the
upcoming changes.

This is the proper fix for #6957.
2026-02-09 12:44:01 +00:00
Sebastian Ullrich
b4a254de69 chore: revert "chore: CI: avoid fetching full repo in PR Release (#12309)"
This reverts commit 12ad957bd5.
2026-02-09 13:12:35 +00:00
Markus Himmel
5ba467d920 feat: LawfulForwardPatternModel for string patterns (#12360)
This PR provides a `LawfulForwardPatternModel` instance for string
patterns, i.e., it proves correctness of the `dropPrefix?` and
`startsWith` functions for string patterns.

Note that this is "just" the correctness proof; there isn't a way to
actually use it yet. API lemmas will follow.
2026-02-09 09:27:47 +00:00
Paul Reichert
d2c738c4bd feat: vector iterator (#12363)
This PR introduces iterators for vectors via `Vector.iter` and
`Vector.iterM`, together with the usual lemmas.
2026-02-09 07:45:42 +00:00
Mac Malone
a31e68715c fix: lake: include module identifiers in trace (#12377)
This PR adds identifying information about a module available to `lean`
(e.g., its name and package identifier) to the module's dependency
trace. This ensures modules with different identification have different
input hashes even if their source files and imports are identical.
2026-02-09 05:59:51 +00:00
Sebastian Ullrich
12ad957bd5 chore: CI: avoid fetching full repo in PR Release (#12309) 2026-02-09 05:14:33 +00:00
Leonardo de Moura
690648d943 chore: missing annotations (#12368)
This PR adds missing annotations for feature
[#12179](https://github.com/leanprover/lean4/pull/12179). PR #12179 is
quite challenging, and we are breaking it into smaller pieces.
2026-02-09 04:52:13 +00:00
Leonardo de Moura
6f4e711171 feat: some unification hints (#12341)
This PR adds a few unification hints that we will need after
`backward.isDefEq.respectTransparency` is `true` by default.

See #12338
It was part of #12179.
2026-02-09 04:51:13 +00:00
Sebastian Ullrich
23dc467ef5 chore: do not rely on Name.lt for ordering fvars in acLt (#12306)
Also relands #12000, fixing #12150
2026-02-08 14:25:31 +00:00
1292 changed files with 16776 additions and 3776 deletions

View File

@@ -1,6 +1,29 @@
To build Lean you should use `make -j -C build/release`.
To run a test you should use `cd tests/lean/run && ./test_single.sh example_test.lean`.
## Running Tests
See `doc/dev/testing.md` for full documentation. Quick reference:
```bash
# Full test suite (use after builds to verify correctness)
make -j -C build/release test ARGS="-j$(nproc)"
# Specific test by name (supports regex via ctest -R)
make -j -C build/release test ARGS='-R grind_ematch --output-on-failure'
# Rerun only previously failed tests
make -j -C build/release test ARGS='--rerun-failed --output-on-failure'
# Single test from tests/lean/run/ (quick check during development)
cd tests/lean/run && ./test_single.sh example_test.lean
# ctest directly (from stage1 build dir)
cd build/release/stage1 && ctest -j$(nproc) --output-on-failure --timeout 300
```
The full test suite includes `tests/lean/`, `tests/lean/run/`, `tests/lean/interactive/`,
`tests/compiler/`, `tests/pkg/`, Lake tests, and more. Using `make test` or `ctest` runs
all of them; `test_single.sh` in `tests/lean/run/` only covers that one directory.
## New features
@@ -61,6 +84,27 @@ leading quantifiers are stripped when creating a pattern.
If you're unsure which label applies, it's fine to omit the label and let reviewers add it.
## Module System for `src/` Files
Files in `src/Lean/`, `src/Std/`, and `src/lake/Lake/` must have both `module` and `prelude` (CI enforces `^prelude$` on its own line). With `prelude`, nothing is auto-imported — you must explicitly import `Init.*` modules for standard library features. Check existing files in the same directory for the pattern, e.g.:
```lean
module
prelude
import Init.While -- needed for while/repeat
import Init.Data.String.TakeDrop -- needed for String.startsWith
public import Lean.Compiler.NameMangling -- public if types are used in public signatures
```
Files outside these directories (e.g. `tests/`, `script/`) use just `module`.
## CI Log Retrieval
When CI jobs fail, investigate immediately - don't wait for other jobs to complete. Individual job logs are often available even while other jobs are still running. Try `gh run view <run-id> --log` or `gh run view <run-id> --log-failed`, or use `gh run view <run-id> --job=<job-id>` to target the specific failed job. Sleeping is fine when asked to monitor CI and no failures exist yet, but once any job fails, investigate that failure immediately.
## Copyright Headers
New files require a copyright header. To get the year right, always run `date +%Y` rather than relying on memory. The copyright holder should be the author or their current employer — check other recent files by the same author in the repository to determine the correct entity (e.g., "Lean FRO, LLC", "Amazon.com, Inc. or its affiliates").
Test files (in `tests/`) do not need copyright headers.

View File

@@ -103,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.
## Checking PR Status When Asked
When the user asks for "status" or you need to report on PRs between checklist runs:
- **ALWAYS check actual PR state** using `gh pr view <number> --repo <repo> --json state,mergedAt`
- Do NOT rely on cached CI results or previous checklist output
- The user may have merged PRs since your last check
- Report which PRs are MERGED, which are OPEN with CI status, and which are still pending
- After discovering merged PRs, rerun `release_checklist.py` to advance the release process
## Nightly Infrastructure
The nightly build system uses branches and tags across two repositories:

View File

@@ -60,10 +60,23 @@ jobs:
if [[ -n '${{ secrets.PUSH_NIGHTLY_TOKEN }}' ]]; then
git remote add nightly https://foo:'${{ secrets.PUSH_NIGHTLY_TOKEN }}'@github.com/${{ github.repository_owner }}/lean4-nightly.git
git fetch nightly --tags
LEAN_VERSION_STRING="nightly-$(date -u +%F)"
# do nothing if commit already has a different tag
if [[ "$(git name-rev --name-only --tags --no-undefined HEAD 2> /dev/null || echo "$LEAN_VERSION_STRING")" == "$LEAN_VERSION_STRING" ]]; then
if [[ '${{ github.event_name }}' == 'workflow_dispatch' ]]; then
# Manual re-release: create a revision of the most recent nightly
BASE_NIGHTLY=$(git tag -l 'nightly-*' | sort -rV | head -1)
# Strip any existing -revK suffix to get the base date tag
BASE_NIGHTLY="${BASE_NIGHTLY%%-rev*}"
REV=1
while git rev-parse "refs/tags/${BASE_NIGHTLY}-rev${REV}" >/dev/null 2>&1; do
REV=$((REV + 1))
done
LEAN_VERSION_STRING="${BASE_NIGHTLY}-rev${REV}"
echo "nightly=$LEAN_VERSION_STRING" >> "$GITHUB_OUTPUT"
else
# Scheduled: do nothing if commit already has a different tag
LEAN_VERSION_STRING="nightly-$(date -u +%F)"
if [[ "$(git name-rev --name-only --tags --no-undefined HEAD 2> /dev/null || echo "$LEAN_VERSION_STRING")" == "$LEAN_VERSION_STRING" ]]; then
echo "nightly=$LEAN_VERSION_STRING" >> "$GITHUB_OUTPUT"
fi
fi
fi
@@ -260,7 +273,7 @@ jobs:
{
"name": "Linux fsanitize",
// Always run on large if available, more reliable regarding timeouts
"os": large ? "nscloud-ubuntu-22.04-amd64-8x16-with-cache" : "ubuntu-latest",
"os": large ? "nscloud-ubuntu-22.04-amd64-16x32-with-cache" : "ubuntu-latest",
"enabled": level >= 2,
// do not fail nightlies on this for now
"secondary": level <= 2,
@@ -277,7 +290,7 @@ jobs:
// * `grind_guide` always times out.
// * `pkg/|lake/` tests sometimes time out (likely even hang), related to Lake CI
// failures?
"CTEST_OPTIONS": "-E 'StackOverflow|reverse-ffi|interactive|async_select_channel|9366|run/bv_|grind_guide|pkg/|lake/'"
"CTEST_OPTIONS": "-E 'StackOverflow|reverse-ffi|interactive|async_select_channel|9366|run/bv_|grind_guide|grind_bitvec2|grind_constProp|grind_indexmap|grind_list|grind_lint|grind_array_attach|grind_ite_trace|pkg/|lake/'"
},
{
"name": "macOS",
@@ -475,7 +488,7 @@ jobs:
git tag "${{ needs.configure.outputs.nightly }}"
git push nightly "${{ needs.configure.outputs.nightly }}"
git push -f origin refs/tags/${{ needs.configure.outputs.nightly }}:refs/heads/nightly
last_tag="$(git log HEAD^ --simplify-by-decoration --pretty="format:%d" | grep -o "nightly-[-0-9]*" | head -n 1)"
last_tag="$(git log HEAD^ --simplify-by-decoration --pretty="format:%d" | grep -o "nightly-[^ ,)]*" | head -n 1)"
echo -e "*Changes since ${last_tag}:*\n\n" > diff.md
git show "$last_tag":RELEASES.md > old.md
#./script/diff_changelogs.py old.md doc/changes.md >> diff.md
@@ -498,8 +511,18 @@ jobs:
gh workflow -R leanprover/release-index run update-index.yml
env:
GITHUB_TOKEN: ${{ secrets.RELEASE_INDEX_TOKEN }}
- name: Generate mathlib nightly-testing app token
id: mathlib-app-token
uses: actions/create-github-app-token@29824e69f54612133e76f7eaac726eef6c875baf # v2.2.1
continue-on-error: true
with:
app-id: ${{ secrets.MATHLIB_NIGHTLY_TESTING_APP_ID }}
private-key: ${{ secrets.MATHLIB_NIGHTLY_TESTING_PRIVATE_KEY }}
owner: leanprover-community
repositories: mathlib4-nightly-testing
- name: Update toolchain on mathlib4's nightly-testing branch
if: steps.mathlib-app-token.outcome == 'success'
run: |
gh workflow -R leanprover-community/mathlib4-nightly-testing run nightly_bump_toolchain.yml
gh workflow -R leanprover-community/mathlib4-nightly-testing run nightly_bump_and_merge.yml
env:
GITHUB_TOKEN: ${{ secrets.MATHLIB4_BOT }}
GITHUB_TOKEN: ${{ steps.mathlib-app-token.outputs.token }}

View File

@@ -6,7 +6,7 @@ building Lean itself - which is needed to again build those parts. This cycle is
broken by using pre-built C files checked into the repository (which ultimately
go back to a point where the Lean compiler was not written in Lean) in place of
these Lean inputs and then compiling everything in multiple stages up to a fixed
point. The build directory is organized in these stages:
point. The build directory is organized into these stages:
```bash
stage0/
@@ -79,7 +79,7 @@ with the contents of `src/stdlib_flags.h`, bringing them back in sync.
NOTE: A full rebuild of stage 1 will only be triggered when the *committed* contents of `stage0/` are changed.
Thus if you change files in it manually instead of through `update-stage0-commit` (see below) or fetching updates from git, you either need to commit those changes first or run `make -C build/release clean-stdlib`.
The same is true for further stages except that a rebuild of them is retriggered on any committed change, not just to a specific directory.
Thus when debugging e.g. stage 2 failures, you can resume the build from these failures on but may want to explicitly call `clean-stdlib` to either observe changes from `.olean` files of modules that built successfully or to check that you did not break modules that built successfully at some prior point.
Thus when debugging e.g. stage 2 failures, you can resume the build from these failures on but you may want to explicitly call `clean-stdlib` to either observe changes from `.olean` files of modules that built successfully or to check that you did not break modules that built successfully at some prior point.
If you have write access to the lean4 repository, you can also manually
trigger that process, for example to be able to use new features in the compiler itself.
@@ -101,7 +101,7 @@ The script `script/rebase-stage0.sh` can be used for that.
The CI should prevent PRs with changes to stage0 (besides `stdlib_flags.h`)
from entering `master` through the (squashing!) merge queue, and label such PRs
with the `changes-stage0` label. Such PRs should have a cleaned up history,
with the `changes-stage0` label. Such PRs should have a cleaned-up history,
with separate stage0 update commits; then coordinate with the admins to merge
your PR using rebase merge, bypassing the merge queue.

View File

@@ -30,7 +30,7 @@ We'll use `v4.6.0` as the intended release version as a running example.
run `script/release_notes.py --since v4.5.0` on the `releases/v4.6.0` branch,
and see the section "Writing the release notes" below for more information.
- Release notes live in https://github.com/leanprover/reference-manual, in e.g. `Manual/Releases/v4.6.0.lean`.
It's best if you update these at the same time as a you update the `lean-toolchain` for the `reference-manual` repository, see below.
It's best if you update these at the same time as you update the `lean-toolchain` for the `reference-manual` repository, see below.
- Go to https://github.com/leanprover/lean4/releases and verify that the `v4.6.0` release appears.
- Verify on Github that "Set as the latest release" is checked.
- Next, we will move a curated list of downstream repos to the latest stable release.
@@ -54,7 +54,7 @@ We'll use `v4.6.0` as the intended release version as a running example.
- `verso`:
- The `subverso` dependency is unusual in that it needs to be compatible with _every_ Lean release simultaneously.
Usually you don't need to do anything.
If you think something is wrong here please contact David Thrane Christiansen (@david-christiansen)
If you think something is wrong here, please contact David Thrane Christiansen (@david-christiansen)
- Warnings during `lake update` and `lake build` are expected.
- `reference-manual`: the release notes generated by `script/release_notes.py` as described above must be included in
`Manual/Releases/v4.6.0.lean`, and `import` and `include` statements adding in `Manual/Releases.lean`.
@@ -65,7 +65,14 @@ We'll use `v4.6.0` as the intended release version as a running example.
- The `lakefile.toml` should always refer to dependencies via their `main` or `master` branch,
not a toolchain tag
(with the exception of `ProofWidgets4`, which *must* use a sequential version tag).
- **Important:** After creating and pushing the ProofWidgets4 tag (see above),
the mathlib4 lakefile must be updated to reference the new tag (e.g. `v0.0.87`).
The `release_steps.py` script handles this automatically by looking up the latest
ProofWidgets4 tag compatible with the target toolchain.
- Push the PR branch to the main Mathlib repository rather than a fork, or CI may not work reliably
- The "Verify Transient and Automated Commits" CI check on toolchain bump PRs can be ignored —
it often fails on automated commits (`x:` prefixed) from the nightly-testing history that can't be
reproduced in CI. This does not block merging.
- `repl`:
There are two copies of `lean-toolchain`/`lakefile.lean`:
in the root, and in `test/Mathlib/`. Edit both, and run `lake update` in both directories.
@@ -146,6 +153,9 @@ We'll use `v4.7.0-rc1` as the intended release version in this example.
* The repository does not need any changes to move to the new version.
* Note that sometimes there are *unreviewed* but necessary changes on the `nightly-testing` branch of the repository.
If so, you will need to merge these into the `bump_to_v4.7.0-rc1` branch manually.
* The `nightly-testing` branch may also contain temporary fix scripts (e.g. `fix_backward_defeq.py`,
`fix_deprecations.py`) that were used to adapt to breaking changes during the nightly cycle.
These should be reviewed and removed if no longer needed, as they can interfere with CI checks.
- For each of the repositories listed in `script/release_repos.yml`,
- Run `script/release_steps.py v4.7.0-rc1 <repo>` (e.g. replacing `<repo>` with `batteries`), which will walk you through the following steps:
- Create a new branch off `master`/`main` (as specified in the `branch` field), called `bump_to_v4.7.0-rc1`.

View File

@@ -0,0 +1,6 @@
# IJCAR 2026: `grind`, An SMT-Inspired Tactic for Lean 4
Ancillary materials for the paper.
- `examples.lean`: interactive examples from the paper
- `analyze_grind_loc.py`: script used for the evaluation section, analyzing `grind` adoption and lines-of-code changes in Mathlib

View File

@@ -0,0 +1,401 @@
#!/usr/bin/env python3
"""
Analyze grind adoption LoC changes in mathlib.
For each theorem/lemma in master that uses grind, find the most recent
commit where it didn't use grind, and measure the LoC change.
This script was used in preparing the "Evaluation" section of the grind paper.
"""
import subprocess
import re
import csv
import sys
from pathlib import Path
from dataclasses import dataclass
from concurrent.futures import ThreadPoolExecutor, as_completed
from typing import Iterator
from functools import lru_cache
@dataclass
class GrindUsage:
file: str
line_no: int
decl_name: str
decl_type: str # theorem, lemma, def, example, etc.
@dataclass
class LocChange:
file: str
decl_name: str
decl_type: str
old_loc: int
new_loc: int
loc_saved: int
commit_sha: str
commit_date: str
def run_git(args: list[str], repo: str = ".") -> str:
"""Run a git command and return stdout."""
result = subprocess.run(
["git", "-C", repo] + args,
capture_output=True, text=True, check=True
)
return result.stdout
def run_git_safe(args: list[str], repo: str = ".") -> str | None:
"""Run a git command, return None on failure."""
result = subprocess.run(
["git", "-C", repo] + args,
capture_output=True, text=True
)
if result.returncode != 0:
return None
return result.stdout
@lru_cache(maxsize=4096)
def get_file_at_commit(repo: str, commit: str, file_path: str) -> str | None:
"""Get file contents at a specific commit (cached)."""
return run_git_safe(["show", f"{commit}:{file_path}"], repo)
def find_grind_usages(repo: str = ".") -> tuple[list[GrindUsage], int, int]:
"""Find all declarations using grind in current master.
Returns (usages, total_grind_calls, grind_in_decls) where:
- total_grind_calls is the count of grind tactic calls (after filtering comments/attrs)
- grind_in_decls is the count of those that are inside named declarations
"""
# Use git grep to find lines containing 'grind' (excludes lake packages)
result = run_git(["grep", "-n", "grind", "master", "--", "Mathlib/"], repo)
usages = []
seen = set() # (file, decl_name) to dedupe
total_grind_calls = 0
grind_in_decls = 0
for line in result.strip().split('\n'):
if not line:
continue
# Format: master:path/to/file.lean:123:line content
match = re.match(r'^master:(.+\.lean):(\d+):(.*)$', line)
if not match:
continue
file_path, line_no_str, content = match.groups()
line_no = int(line_no_str)
# Skip comments and attributes (not tactic calls)
content_stripped = content.strip()
if content_stripped.startswith('--') or content_stripped.startswith('/-'):
continue
if content_stripped.startswith('attribute'):
continue
if '@[' in content and 'grind' in content:
# Could be an attribute like @[grind =], skip
if 'by' not in content and ':=' not in content:
continue
total_grind_calls += 1
# Find the declaration this grind belongs to
decl_name, decl_type = find_decl_at_line(repo, file_path, line_no)
if decl_name is None:
continue
grind_in_decls += 1
key = (file_path, decl_name)
if key in seen:
continue
seen.add(key)
usages.append(GrindUsage(
file=file_path,
line_no=line_no,
decl_name=decl_name,
decl_type=decl_type
))
return usages, total_grind_calls, grind_in_decls
def find_decl_at_line(repo: str, file_path: str, grind_line: int) -> tuple[str | None, str | None]:
"""
Find the declaration name and type that contains the grind at the given line.
Search backwards from grind_line to find the most recent declaration.
"""
# Get file content at master
content = get_file_at_commit(repo, "master", file_path)
if content is None:
return None, None
lines = content.split('\n')
# Search backwards from grind_line for a declaration
# Match declarations with optional leading modifiers and attributes
decl_pattern = re.compile(r'^(?:@\[.*?\]\s*)*(?:private\s+|protected\s+|noncomputable\s+|scoped\s+)*(theorem|lemma|def|example|instance|abbrev|structure|class)\s+(\w+)')
for i in range(grind_line - 1, -1, -1):
if i >= len(lines):
continue
line = lines[i]
match = decl_pattern.match(line)
if match:
return match.group(2), match.group(1)
return None, None
def find_grind_introduction_commit(repo: str, file_path: str, decl_name: str) -> str | None:
"""
Find the commit that introduced grind to this declaration.
Returns None if the declaration was born with grind.
"""
# First, find the line range of the declaration in master
content = get_file_at_commit(repo, "master", file_path)
if content is None:
return None
lines = content.split('\n')
decl_start = None
decl_end = None
# Find declaration start
decl_pattern = re.compile(rf'^(?:@\[.*?\]\s*)*(?:private\s+|protected\s+|noncomputable\s+|scoped\s+)*(theorem|lemma|def|example|instance|abbrev|structure|class)\s+{re.escape(decl_name)}\b')
for i, line in enumerate(lines):
if decl_pattern.match(line):
decl_start = i
break
if decl_start is None:
return None
# Find declaration end (next top-level declaration or EOF)
end_patterns = re.compile(r'^(?:private\s+|protected\s+|noncomputable\s+|scoped\s+)*(theorem|lemma|def|example|instance|abbrev|structure|class|namespace|section|end\s|@\[|#|/-)')
for i in range(decl_start + 1, len(lines)):
line = lines[i]
if line and not line[0].isspace() and end_patterns.match(line):
decl_end = i
break
if decl_end is None:
decl_end = len(lines)
# Find grind line within declaration
grind_line = None
for i in range(decl_start, decl_end):
if 'grind' in lines[i]:
grind_line = i + 1 # 1-indexed
break
if grind_line is None:
return None
# Use git blame to find when that grind line was added
blame_result = run_git_safe(["blame", "-L", f"{grind_line},{grind_line}", "--porcelain", "master", "--", file_path], repo)
if blame_result is None:
return None
# First line of porcelain output is the commit SHA
first_line = blame_result.split('\n')[0]
commit_sha = first_line.split()[0]
# Check if this declaration existed before this commit (without grind)
parent_sha = run_git_safe(["rev-parse", f"{commit_sha}^"], repo)
if parent_sha is None:
return None # Initial commit, born with grind
parent_sha = parent_sha.strip()
# Check if declaration existed in parent
parent_content = get_file_at_commit(repo, parent_sha, file_path)
if parent_content is None:
# File didn't exist in parent - might be new file or renamed
return None
# Check if declaration existed and didn't have grind
if decl_name not in parent_content:
return None # Declaration didn't exist - born with grind
# Check if it already had grind in parent
parent_lines = parent_content.split('\n')
in_decl = False
for line in parent_lines:
if decl_pattern.match(line):
in_decl = True
elif in_decl:
if line and not line[0].isspace() and end_patterns.match(line):
break
if 'grind' in line:
# Already had grind in parent — not the introduction commit
return None
return commit_sha
def extract_proof_loc(repo: str, file_path: str, decl_name: str, commit: str) -> int | None:
"""
Extract the number of lines in a declaration's proof at a given commit.
Returns None if the declaration doesn't exist at that commit.
"""
content = get_file_at_commit(repo, commit, file_path)
if content is None:
return None
lines = content.split('\n')
# Find declaration start
decl_pattern = re.compile(rf'^(?:@\[.*?\]\s*)*(?:private\s+|protected\s+|noncomputable\s+|scoped\s+)*(theorem|lemma|def|example|instance|abbrev|structure|class)\s+{re.escape(decl_name)}\b')
decl_start = None
for i, line in enumerate(lines):
if decl_pattern.match(line):
decl_start = i
break
if decl_start is None:
return None
# Find declaration end
end_patterns = re.compile(r'^(?:private\s+|protected\s+|noncomputable\s+|scoped\s+)*(theorem|lemma|def|example|instance|abbrev|structure|class|namespace|section|end\s|@\[|#|/-)')
decl_end = None
for i in range(decl_start + 1, len(lines)):
line = lines[i]
if line and not line[0].isspace() and end_patterns.match(line):
decl_end = i
break
if decl_end is None:
decl_end = len(lines)
# Count non-empty lines in declaration
loc = sum(1 for i in range(decl_start, decl_end) if lines[i].strip())
return loc
def get_commit_date(repo: str, sha: str) -> str:
"""Get the date of a commit."""
result = run_git(["log", "-1", "--format=%ci", sha], repo)
return result.strip().split()[0] # Just the date part
def analyze_usage_detailed(repo: str, usage: GrindUsage) -> tuple[LocChange | None, str]:
"""Analyze a single grind usage, returning (result, skip_reason)."""
commit = find_grind_introduction_commit(repo, usage.file, usage.decl_name)
if commit is None:
return None, "born_with_grind"
parent = run_git_safe(["rev-parse", f"{commit}^"], repo)
if parent is None:
return None, "no_parent"
parent = parent.strip()
old_loc = extract_proof_loc(repo, usage.file, usage.decl_name, parent)
new_loc = extract_proof_loc(repo, usage.file, usage.decl_name, "master")
if old_loc is None:
return None, "old_loc_failed"
if new_loc is None:
return None, "new_loc_failed"
commit_date = get_commit_date(repo, commit)
return LocChange(
file=usage.file,
decl_name=usage.decl_name,
decl_type=usage.decl_type,
old_loc=old_loc,
new_loc=new_loc,
loc_saved=old_loc - new_loc,
commit_sha=commit[:12],
commit_date=commit_date
), "success"
def main(repo: str = "."):
print("Finding grind usages in master...", file=sys.stderr)
usages, total_grind_calls, grind_in_decls = find_grind_usages(repo)
print(f"Found {len(usages)} declarations using grind ({grind_in_decls}/{total_grind_calls} grind calls)", file=sys.stderr)
print("Analyzing git history (this may take a while)...", file=sys.stderr)
results: list[LocChange] = []
skip_reasons: dict[str, int] = {}
with ThreadPoolExecutor(max_workers=64) as executor:
futures = {executor.submit(analyze_usage_detailed, repo, usage): usage for usage in usages}
for i, future in enumerate(as_completed(futures)):
if (i + 1) % 50 == 0:
print(f" Progress: {i + 1}/{len(usages)}", file=sys.stderr, flush=True)
result, reason = future.result()
if result:
results.append(result)
else:
skip_reasons[reason] = skip_reasons.get(reason, 0) + 1
total_skipped = sum(skip_reasons.values())
print(f"\nAnalyzed {len(results)} declarations, skipped {total_skipped}:", file=sys.stderr)
for reason, count in sorted(skip_reasons.items(), key=lambda x: -x[1]):
print(f" - {reason}: {count}", file=sys.stderr)
# Sort by LoC saved (descending)
results.sort(key=lambda r: r.loc_saved, reverse=True)
# Output CSV
writer = csv.writer(sys.stdout)
writer.writerow(["file", "declaration", "type", "old_loc", "new_loc", "loc_saved", "commit", "date"])
for r in results:
writer.writerow([r.file, r.decl_name, r.decl_type, r.old_loc, r.new_loc, r.loc_saved, r.commit_sha, r.commit_date])
# Summary stats to stderr
total_old = sum(r.old_loc for r in results) if results else 0
total_new = sum(r.new_loc for r in results) if results else 0
total_saved = sum(r.loc_saved for r in results) if results else 0
avg_saved = total_saved / len(results) if results else 0
print("\n" + "=" * 60, file=sys.stderr)
print("GRIND ADOPTION LOC ANALYSIS", file=sys.stderr)
print("=" * 60, file=sys.stderr)
print("\n## Declaration Counts\n", file=sys.stderr)
print(f" Total grind tactic calls: {total_grind_calls}", file=sys.stderr)
print(f" In named declarations: {grind_in_decls} ({total_grind_calls - grind_in_decls} in anonymous/other)", file=sys.stderr)
print(f" Unique declarations: {len(usages)}", file=sys.stderr)
print(f" Converted to grind: {len(results)}", file=sys.stderr)
print(f" Born with grind: {skip_reasons.get('born_with_grind', 0)}", file=sys.stderr)
if skip_reasons.get('old_loc_failed', 0) > 0:
print(f" Could not trace history: {skip_reasons.get('old_loc_failed', 0)}", file=sys.stderr)
print("\n## Lines of Code Impact\n", file=sys.stderr)
print(f" Total LoC before grind: {total_old}", file=sys.stderr)
print(f" Total LoC after grind: {total_new}", file=sys.stderr)
print(f" Total LoC saved: {total_saved}", file=sys.stderr)
print(f" Average LoC saved per theorem: {avg_saved:.1f}", file=sys.stderr)
big_savings = sum(1 for r in results if r.loc_saved >= 10)
print(f" Declarations shrunk by 10+ lines: {big_savings}", file=sys.stderr)
if results:
print("\n## Top 10 Biggest LoC Savings\n", file=sys.stderr)
for r in results[:10]:
print(f" {r.loc_saved:+4d} lines: {r.decl_name} ({r.file})", file=sys.stderr)
# Show any that got bigger (negative savings)
got_bigger = [r for r in results if r.loc_saved < 0]
if got_bigger:
print(f"\n## Declarations That Got Bigger ({len(got_bigger)} total)\n", file=sys.stderr)
print(" (showing 5 worst):", file=sys.stderr)
for r in got_bigger[-5:]: # Show worst 5
print(f" {r.loc_saved:+4d} lines: {r.decl_name} ({r.file})", file=sys.stderr)
print("\n" + "=" * 60, file=sys.stderr)
if __name__ == "__main__":
import argparse
parser = argparse.ArgumentParser(description="Analyze grind LoC savings")
parser.add_argument("--repo", "-r", default=".", help="Repository path")
args = parser.parse_args()
main(args.repo)

View File

@@ -0,0 +1,127 @@
/- Examples from the paper "grind: An SMT-Inspired Tactic for Lean 4" -/
open Lean Grind
/- Congruence closure. -/
example (f : Nat Nat) (h : a = b) : f (f b) = f (f a) := by grind
/-
E-matching.
Any `f` that is the left inverse of `g` would work on this example.
-/
def f (x : Nat) := x - 1
def g (x : Nat) := x + 1
@[grind =] theorem fg : f (g x) = x := by simp [f, g]
example : f a = b a = g c b = c := by grind
/-
Any `R` that is transitive and symmetric would work on this example.
-/
def R : Nat Nat Prop := (· % 7 = · % 7)
@[grind ] theorem Rtrans : R x y R y z R x z := by grind [R]
@[grind ] theorem Rsymm : R x y R y x := by grind [R]
example : R a b R c b R d c R a d := by grind
/- Big step operational semantics example. -/
abbrev Variable := String
def State := Variable Nat
inductive Stmt : Type where
| skip : Stmt
| assign : Variable (State Nat) Stmt
| seq : Stmt Stmt Stmt
| ifThenElse : (State Prop) Stmt Stmt Stmt
| whileDo : (State Prop) Stmt Stmt
infix:60 ";; " => Stmt.seq
export Stmt (skip assign seq ifThenElse whileDo)
set_option quotPrecheck false in
notation s:70 "[" x:70 "" n:70 "]" => (fun v if v = x then n else s v)
inductive BigStep : Stmt State State Prop where
| skip (s : State) : BigStep skip s s
| assign (x : Variable) (a : State Nat) (s : State) : BigStep (assign x a) s (s[x a s])
| seq {S T : Stmt} {s t u : State} (hS : BigStep S s t) (hT : BigStep T t u) :
BigStep (S;; T) s u
| if_true {B : State Prop} {s t : State} (hcond : B s) (S T : Stmt) (hbody : BigStep S s t) :
BigStep (ifThenElse B S T) s t
| if_false {B : State Prop} {s t : State} (hcond : ¬ B s) (S T : Stmt) (hbody : BigStep T s t) :
BigStep (ifThenElse B S T) s t
| while_true {B S s t u} (hcond : B s) (hbody : BigStep S s t) (hrest : BigStep (whileDo B S) t u) :
BigStep (whileDo B S) s u
| while_false {B S s} (hcond : ¬ B s) : BigStep (whileDo B S) s s
notation:55 "(" S:55 "," s:55 ")" " ==> " t:55 => BigStep S s t
example {B S T s t} (hcond : B s) : (ifThenElse B S T, s) ==> t (S, s) ==> t := by
grind [cases BigStep]
theorem cases_if_of_true {B S T s t} (hcond : B s) : (ifThenElse B S T, s) ==> t (S, s) ==> t := by
grind [cases BigStep]
theorem cases_if_of_false {B S T s t} (hcond : ¬ B s) : (ifThenElse B S T, s) ==> t (T, s) ==> t := by
grind [cases BigStep]
example {B S T s t} : (ifThenElse B S T, s) ==> t (B s (S, s) ==> t) (¬ B s (T, s) ==> t) := by
grind [BigStep] -- shortcut for `cases BigStep` and `intro BigStep`
attribute [grind] BigStep
theorem if_iff {B S T s t} : (ifThenElse B S T, s) ==>
t (B s (S, s) ==> t) (¬ B s (T, s) ==> t) := by grind
/- Dependent pattern matching. -/
inductive Vec (α : Type u) : Nat Type u
| nil : Vec α 0
| cons : α Vec α n Vec α (n+1)
@[grind =] def Vec.head : Vec α (n+1) α
| .cons a _ => a
example (as bs : Vec Int (n+1)) : as.head = bs.head
(match as, bs with
| .cons a _, .cons b _ => a + b) = 2 * as.head := by grind
/- Theory solvers. -/
example [CommRing α] (a b c : α) :
a + b + c = 3
a^2 + b^2 + c^2 = 5
a^3 + b^3 + c^3 = 7
a^4 + b^4 + c^4 = 9 := by grind
example (x : BitVec 8) : (x - 16) * (x + 16) = x^2 := by grind
example [CommSemiring α] [AddRightCancel α] (x y : α) :
x^2*y = 1 x*y^2 = y y*x = 1 := by grind
example (a b : UInt32) : a 2 b 3 a + b 5 := by grind
example [LE α] [Std.IsLinearPreorder α] (a b c d : α) :
a b ¬ (c b) ¬ (d c) a d := by grind
/- Theory combination. -/
example [CommRing α] [NoNatZeroDivisors α]
(a b c : α) (f : α Nat) :
a + b + c = 3 a^2 + b^2 + c^2 = 5 a^3 + b^3 + c^3 = 7
f (a^4 + b^4) + f (9 - c^4) 1 := by grind
/- Interactive mode. -/
-- Remark: Mathlib contains the definition of `Real`, `sin`, and `cos`.
axiom Real : Type
instance : Lean.Grind.CommRing Real := sorry
axiom cos : Real Real
axiom sin : Real Real
axiom trig_identity : x, (cos x)^2 + (sin x)^2 = 1
-- Manually specify the patterns for `trig_identity`
grind_pattern trig_identity => cos x
grind_pattern trig_identity => sin x
example : (cos x + sin x)^2 = 2 * cos x * sin x + 1 := by
grind? -- Provides code action
example : (cos x + sin x)^2 = 2 * cos x * sin x + 1 := by
grind =>
instantiate only [trig_identity]
ring

8
flake.lock generated
View File

@@ -2,11 +2,11 @@
"nodes": {
"nixpkgs": {
"locked": {
"lastModified": 1745636243,
"narHash": "sha256-kbNvlQZf8wwok3d2X1kM/TlXH/MZ+03ZNv+IPPBx+DM=",
"rev": "f771eb401a46846c1aebd20552521b233dd7e18b",
"lastModified": 1769018530,
"narHash": "sha256-S/5RU76BdQ32bbE99a+G9gMuatpVWEvIfeSjEqyoFS4=",
"rev": "88d3861acdd3d2f0e361767018218e51810df8a1",
"type": "tarball",
"url": "https://releases.nixos.org/nixos/unstable/nixos-25.05pre789333.f771eb401a46/nixexprs.tar.xz"
"url": "https://releases.nixos.org/nixos/unstable/nixos-26.05pre931542.88d3861acdd3/nixexprs.tar.xz"
},
"original": {
"type": "tarball",

View File

@@ -18,7 +18,7 @@
# An old nixpkgs for creating releases with an old glibc
pkgsDist-old-aarch = import inputs.nixpkgs-old { localSystem.config = "aarch64-unknown-linux-gnu"; };
llvmPackages = pkgs.llvmPackages_15;
llvmPackages = pkgs.llvmPackages_19;
devShellWithDist = pkgsDist: pkgs.mkShell.override {
stdenv = pkgs.overrideCC pkgs.stdenv llvmPackages.clang;

View File

@@ -1,6 +0,0 @@
**Breaking Changes**
* The functions `Lean.Environment.importModules` and `Lean.Environment.finalizeImport` have been extended with a new parameter `loadExts : Bool := false` that enables environment extension state loading.
Their previous behavior corresponds to setting the flag to `true` but is only safe to do in combination with `enableInitializersExecution`; see also the `importModules` docstring.
The new default value `false` ensures the functions can be used correctly multiple times within the same process when environment extension access is not needed.
The wrapper function `Lean.Environment.withImportModules` now always calls `importModules` with `loadExts := false` as it is incompatible with extension loading.

View File

@@ -1,54 +0,0 @@
This release introduces the Lean module system, which allows files to
control the visibility of their contents for other files. In previous
releases, this feature was available as a preview when the option
`experimental.module` was set to `true`; it is now a fully supported
feature of Lean.
# Benefits
Because modules reduce the amount of information exposed to other
code, they speed up rebuilds because irrelevant changes can be
ignored, they make it possible to be deliberate about API evolution by
hiding details that may change from clients, they help proofs be
checked faster by avoiding accidentally unfolding definitions, and
they lead to smaller executable files through improved dead code
elimination.
# Visibility
A source file is a module if it begins with the `module` keyword. By
default, declarations in a module are private; the `public` modifier
exports them. Proofs of theorems and bodies of definitions are private
by default even when their signatures are public; the bodies of
definitions can be made public by adding the `@[expose]`
attribute. Theorems and opaque constants never expose their bodies.
`public section` and `@[expose] section` change the default visibility
of declarations in the section.
# Imports
Modules may only import other modules. By default, `import` adds the
public information of the imported module to the private scope of the
current module. Adding the `public` modifier to an import places the
imported modules's public information in the public scope of the
current module, exposing it in turn to the current module's clients.
Within a package, `import all` can be used to import another module's
private scope into the current module; this can be used to separate
lemmas or tests from definition modules without exposing details to
downstream clients.
# Meta Code
Code used in metaprograms must be marked `meta`. This ensures that the
code is compiled and available for execution when it is needed during
elaboration. Meta code may only reference other meta code. A whole
module can be made available in the meta phase using `meta import`;
this allows code to be shared across phases by importing the module in
each phase. Code that is reachable from public metaprograms must be
imported via `public meta import`, while local metaprograms can use
plain `meta import` for their dependencies.
The module system is described in detail in [the Lean language reference](https://lean-reference-manual-review.netlify.app/find/?domain=Verso.Genre.Manual.section&name=files).

178
script/PROFILER_README.md Normal file
View File

@@ -0,0 +1,178 @@
# Lean Profiler
Profile Lean programs with demangled names using
[samply](https://github.com/mstange/samply) and
[Firefox Profiler](https://profiler.firefox.com).
Python 3, no external dependencies.
## Quick start
```bash
# One command: record, symbolicate, demangle, and open in Firefox Profiler
script/lean_profile.sh ./my_lean_binary [args...]
# See all options
script/lean_profile.sh --help
```
Requirements: `samply` (`cargo install samply`), `python3`.
## Reading demangled names
The demangler transforms low-level C symbol names into readable Lean names
and annotates them with compact modifiers.
### Basic names
| Raw symbol | Demangled |
|---|---|
| `l_Lean_Meta_Sym_main` | `Lean.Meta.Sym.main` |
| `lp_std_List_map` | `List.map (std)` |
| `_init_l_Foo_bar` | `[init] Foo.bar` |
| `initialize_Init_Data` | `[module_init] Init.Data` |
| `_lean_main` | `[lean] main` |
### Modifier flags `[...]`
Compiler-generated suffixes are folded into a bracket annotation after the
name. These indicate *how* the function was derived from the original source
definition.
| Flag | Meaning | Compiler suffix |
|---|---|---|
| `arity`&darr; | Reduced-arity specialization | `_redArg` |
| `boxed` | Boxed calling-convention wrapper | `_boxed` |
| `impl` | Implementation detail | `_impl` |
| &lambda; | Lambda-lifted closure | `_lam_N`, `_lambda_N`, `_elam_N` |
| `jp` | Join point | `_jp_N` |
| `closed` | Extracted closed subterm | `_closed_N` |
| `private` | Private (module-scoped) definition | `_private.Module.0.` prefix |
Examples:
```
Lean.Meta.Simp.simpLambda [boxed, λ] -- boxed wrapper of a lambda-lifted closure
Lean.Meta.foo [arity↓, private] -- reduced-arity version of a private def
```
Multiple flags are comma-separated. Order reflects how they were collected
(innermost suffix first).
### Specializations `spec at ...`
When the compiler specializes a function at a particular call site, the
demangled name shows `spec at <context>` after the base name and its flags.
The context names the function whose body triggered the specialization, and
may carry its own modifier flags:
```
<base-name> [<base-flags>] spec at <context>[<context-flags>]
```
Examples:
```
-- foo specialized at call site in bar
Lean.Meta.foo spec at Lean.Meta.bar
-- foo (with a lambda closure) specialized at bar (with reduced arity and a lambda)
Lean.Meta.foo [λ] spec at Lean.Meta.bar[λ, arity↓]
-- chained specialization: foo specialized at bar, then at baz
Lean.Meta.foo spec at Lean.Meta.bar spec at Lean.Meta.baz[arity↓]
```
Context flags use the same symbols as base flags. When a context has no
flags, the brackets are omitted.
### Other annotations
| Pattern | Meaning |
|---|---|
| `<apply/N>` | Lean runtime apply function (N arguments) |
| `.cold.N` suffix | LLVM cold-path clone (infrequently executed) |
| `(pkg)` suffix | Function from package `pkg` |
## Tools
### `script/lean_profile.sh` -- Full profiling pipeline
Records a profile, symbolicates it via samply's API, demangles Lean names,
and opens the result in Firefox Profiler. This is the recommended workflow.
```bash
script/lean_profile.sh ./build/release/stage1/bin/lean src/Lean/Elab/Term.lean
```
Environment variables:
| Variable | Default | Description |
|---|---|---|
| `SAMPLY_RATE` | 1000 | Sampling rate in Hz |
| `SAMPLY_PORT` | 3756 | Port for samply symbolication server |
| `SERVE_PORT` | 3757 | Port for serving the demangled profile |
| `PROFILE_KEEP` | 0 | Set to 1 to keep the temp directory |
### `script/profiler/lean_demangle.py` -- Name demangler
Demangles individual symbol names. Works as a stdin filter (like `c++filt`)
or with arguments.
```bash
echo "l_Lean_Meta_Sym_main" | python3 script/profiler/lean_demangle.py
# Lean.Meta.Sym.main
python3 script/profiler/lean_demangle.py --raw l_foo___redArg
# foo._redArg (exact name, no postprocessing)
```
As a Python module:
```python
from lean_demangle import demangle_lean_name, demangle_lean_name_raw
demangle_lean_name("l_foo___redArg") # "foo [arity↓]"
demangle_lean_name_raw("l_foo___redArg") # "foo._redArg"
```
### `script/profiler/symbolicate_profile.py` -- Profile symbolicator
Calls samply's symbolication API to resolve raw addresses into symbol names,
then demangles them. Used internally by `lean_profile.sh`.
### `script/profiler/serve_profile.py` -- Profile server
Serves a profile JSON file to Firefox Profiler without re-symbolication
(which would overwrite demangled names). Used internally by `lean_profile.sh`.
### `script/profiler/lean_demangle_profile.py` -- Standalone profile rewriter
Demangles names in an already-symbolicated profile file (if you have one
from another source).
```bash
python3 script/profiler/lean_demangle_profile.py profile.json.gz -o demangled.json.gz
```
## Tests
```bash
cd script/profiler && python3 -m unittest test_demangle -v
```
## How it works
The demangler is a faithful port of Lean 4's `Name.demangleAux` from
`src/Lean/Compiler/NameMangling.lean`. It reverses the encoding used by
`Name.mangle` / `Name.mangleAux` which turns hierarchical Lean names into
valid C identifiers:
- `_` separates name components (`Lean.Meta` -> `Lean_Meta`)
- `__` encodes a literal underscore in a component name
- `_xHH`, `_uHHHH`, `_UHHHHHHHH` encode special characters
- `_N_` encodes numeric name components
- `_00` is a disambiguation prefix for ambiguous patterns
After demangling, a postprocessing pass folds compiler-generated suffixes
into human-readable annotations (see [Reading demangled names](#reading-demangled-names)).

View File

@@ -36,7 +36,7 @@ sys.path.insert(0, str(Path(__file__).parent))
import build_artifact
# Constants
NIGHTLY_PATTERN = re.compile(r'^nightly-(\d{4})-(\d{2})-(\d{2})$')
NIGHTLY_PATTERN = re.compile(r'^nightly-(\d{4})-(\d{2})-(\d{2})(-rev(\d+))?$')
VERSION_PATTERN = re.compile(r'^v4\.(\d+)\.(\d+)(-rc\d+)?$')
# Accept short SHAs (7+ chars) - we'll resolve to full SHA later
SHA_PATTERN = re.compile(r'^[0-9a-f]{7,40}$')
@@ -158,7 +158,7 @@ def parse_identifier(s: str) -> Tuple[str, str]:
return ('sha', full_sha)
error(f"Invalid identifier format: '{s}'\n"
f"Expected one of:\n"
f" - nightly-YYYY-MM-DD (e.g., nightly-2024-06-15)\n"
f" - nightly-YYYY-MM-DD or nightly-YYYY-MM-DD-revK (e.g., nightly-2024-06-15, nightly-2024-06-15-rev1)\n"
f" - v4.X.Y or v4.X.Y-rcK (e.g., v4.8.0, v4.9.0-rc1)\n"
f" - commit SHA (short or full)")
@@ -244,8 +244,13 @@ def fetch_nightly_tags() -> List[str]:
if len(data) < 100:
break
# Sort by date (nightly-YYYY-MM-DD format sorts lexicographically)
tags.sort()
# Sort by date and revision (nightly-YYYY-MM-DD-revK needs numeric comparison on rev)
def nightly_sort_key(tag):
m = NIGHTLY_PATTERN.match(tag)
if m:
return (int(m.group(1)), int(m.group(2)), int(m.group(3)), int(m.group(5) or 0))
return (0, 0, 0, 0)
tags.sort(key=nightly_sort_key)
return tags
def get_commit_for_nightly(nightly: str) -> str:
@@ -1024,6 +1029,7 @@ Range Syntax:
Identifier Formats:
nightly-YYYY-MM-DD Nightly build date (e.g., nightly-2024-06-15)
nightly-YYYY-MM-DD-revK Revised nightly (e.g., nightly-2024-06-15-rev1)
Uses pre-built toolchains from leanprover/lean4-nightly.
Fast: downloads via elan (~30s each).
@@ -1151,9 +1157,9 @@ Examples:
# Validate --nightly-only
if args.nightly_only:
if from_val is not None and from_type != 'nightly':
error("--nightly-only requires FROM to be a nightly identifier (nightly-YYYY-MM-DD)")
error("--nightly-only requires FROM to be a nightly identifier (nightly-YYYY-MM-DD or nightly-YYYY-MM-DD-revK)")
if to_type != 'nightly':
error("--nightly-only requires TO to be a nightly identifier (nightly-YYYY-MM-DD)")
error("--nightly-only requires TO to be a nightly identifier (nightly-YYYY-MM-DD or nightly-YYYY-MM-DD-revK)")
if from_val:
info(f"From: {from_val} ({from_type})")

133
script/lean_profile.sh Executable file
View File

@@ -0,0 +1,133 @@
#!/bin/bash
# Profile a Lean binary with demangled names.
#
# Usage:
# script/lean_profile.sh ./my_lean_binary [args...]
#
# Records a profile with samply, symbolicates via samply's API,
# demangles Lean symbol names, and opens the result in Firefox Profiler.
#
# Requirements: samply (cargo install samply), python3
#
# Options (via environment variables):
# SAMPLY_RATE — sampling rate in Hz (default: 1000)
# SAMPLY_PORT — port for samply symbolication server (default: 3756)
# SERVE_PORT — port for serving the demangled profile (default: 3757)
# PROFILE_KEEP — set to 1 to keep the raw profile after demangling
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
PROFILER_DIR="$SCRIPT_DIR/profiler"
SYMBOLICATE="$PROFILER_DIR/symbolicate_profile.py"
SERVE_PROFILE="$PROFILER_DIR/serve_profile.py"
usage() {
cat >&2 <<EOF
Usage: $0 [options] <lean-binary> [args...]
Profile a Lean binary and view the results in Firefox Profiler
with demangled Lean names.
Requirements:
samply cargo install samply
python3 (included with macOS / most Linux distros)
Environment variables:
SAMPLY_RATE sampling rate in Hz (default: 1000)
SAMPLY_PORT port for samply symbolication server (default: 3756)
SERVE_PORT port for serving the demangled profile (default: 3757)
PROFILE_KEEP set to 1 to keep the temp directory after profiling
Reading demangled names:
Compiler suffixes are shown as modifier flags after the name:
[arity↓] reduced-arity specialization (_redArg)
[boxed] boxed calling-convention wrapper (_boxed)
[λ] lambda-lifted closure (_lam_N, _lambda_N, _elam_N)
[jp] join point (_jp_N)
[closed] extracted closed subterm (_closed_N)
[private] private (module-scoped) def (_private.Module.0. prefix)
[impl] implementation detail (_impl)
Specializations appear after the flags:
Lean.Meta.foo [λ] spec at Lean.Meta.bar[λ, arity↓]
= foo (with lambda closure), specialized at bar (lambda, reduced arity)
Multiple "spec at" entries indicate chained specializations.
See script/PROFILER_README.md for full documentation.
EOF
exit "${1:-0}"
}
if [ $# -eq 0 ]; then
usage 1
fi
case "${1:-}" in
-h|--help) usage 0 ;;
esac
if ! command -v samply &>/dev/null; then
echo "error: samply not found. Install with: cargo install samply" >&2
exit 1
fi
RATE="${SAMPLY_RATE:-1000}"
PORT="${SAMPLY_PORT:-3756}"
SERVE="${SERVE_PORT:-3757}"
TMPDIR=$(mktemp -d /tmp/lean-profile-XXXXXX)
TMPFILE="$TMPDIR/profile.json.gz"
DEMANGLED="$TMPDIR/profile-demangled.json.gz"
SAMPLY_LOG="$TMPDIR/samply.log"
SAMPLY_PID=""
cleanup() {
if [ -n "$SAMPLY_PID" ]; then
kill "$SAMPLY_PID" 2>/dev/null || true
wait "$SAMPLY_PID" 2>/dev/null || true
fi
# Safety net: kill anything still on the symbolication port
lsof -ti :"$PORT" 2>/dev/null | xargs kill 2>/dev/null || true
[ "${PROFILE_KEEP:-0}" = "1" ] || rm -rf "$TMPDIR"
}
trap cleanup EXIT
# Step 1: Record
echo "Recording profile (rate=${RATE} Hz)..." >&2
samply record --save-only -o "$TMPFILE" -r "$RATE" "$@"
# Step 2: Start samply server for symbolication
echo "Starting symbolication server..." >&2
samply load --no-open -P "$PORT" "$TMPFILE" > "$SAMPLY_LOG" 2>&1 &
SAMPLY_PID=$!
# Wait for server to be ready
for i in $(seq 1 30); do
if grep -q "Local server listening" "$SAMPLY_LOG" 2>/dev/null; then
break
fi
sleep 0.2
done
# Extract the token from samply's output
TOKEN=$(grep -oE '[a-z0-9]{30,}' "$SAMPLY_LOG" | head -1)
if [ -z "$TOKEN" ]; then
echo "error: could not get samply server token" >&2
exit 1
fi
SERVER_URL="http://127.0.0.1:${PORT}/${TOKEN}"
# Step 3: Symbolicate + demangle
echo "Symbolicating and demangling..." >&2
python3 "$SYMBOLICATE" --server "$SERVER_URL" "$TMPFILE" -o "$DEMANGLED"
# Step 4: Kill symbolication server
kill "$SAMPLY_PID" 2>/dev/null || true
wait "$SAMPLY_PID" 2>/dev/null || true
SAMPLY_PID=""
# Step 5: Serve the demangled profile directly (without samply's re-symbolication)
echo "Opening in Firefox Profiler..." >&2
python3 "$SERVE_PROFILE" "$DEMANGLED" -P "$SERVE"

View File

@@ -0,0 +1,779 @@
#!/usr/bin/env python3
"""
Lean name demangler.
Demangles C symbol names produced by the Lean 4 compiler back into
readable Lean hierarchical names.
Usage as a filter (like c++filt):
echo "l_Lean_Meta_Sym_main" | python lean_demangle.py
Usage as a module:
from lean_demangle import demangle_lean_name
print(demangle_lean_name("l_Lean_Meta_Sym_main"))
"""
import sys
# ---------------------------------------------------------------------------
# String.mangle / unmangle
# ---------------------------------------------------------------------------
def _is_ascii_alnum(ch):
"""Check if ch is an ASCII letter or digit (matching Lean's isAlpha/isDigit)."""
return ('a' <= ch <= 'z') or ('A' <= ch <= 'Z') or ('0' <= ch <= '9')
def mangle_string(s):
"""Port of Lean's String.mangle: escape a single string for C identifiers."""
result = []
for ch in s:
if _is_ascii_alnum(ch):
result.append(ch)
elif ch == '_':
result.append('__')
else:
code = ord(ch)
if code < 0x100:
result.append('_x' + format(code, '02x'))
elif code < 0x10000:
result.append('_u' + format(code, '04x'))
else:
result.append('_U' + format(code, '08x'))
return ''.join(result)
def _parse_hex(s, pos, n):
"""Parse n lowercase hex digits at pos. Returns (new_pos, value) or None."""
if pos + n > len(s):
return None
val = 0
for i in range(n):
c = s[pos + i]
if '0' <= c <= '9':
val = (val << 4) | (ord(c) - ord('0'))
elif 'a' <= c <= 'f':
val = (val << 4) | (ord(c) - ord('a') + 10)
else:
return None
return (pos + n, val)
# ---------------------------------------------------------------------------
# Name mangling (for round-trip verification)
# ---------------------------------------------------------------------------
def _check_disambiguation(m):
"""Port of Lean's checkDisambiguation: does mangled string m need a '00' prefix?"""
pos = 0
while pos < len(m):
ch = m[pos]
if ch == '_':
pos += 1
continue
if ch == 'x':
return _parse_hex(m, pos + 1, 2) is not None
if ch == 'u':
return _parse_hex(m, pos + 1, 4) is not None
if ch == 'U':
return _parse_hex(m, pos + 1, 8) is not None
if '0' <= ch <= '9':
return True
return False
# all underscores or empty
return True
def _need_disambiguation(prev_component, mangled_next):
"""Port of Lean's needDisambiguation."""
# Check if previous component (as a string) ends with '_'
prev_ends_underscore = (isinstance(prev_component, str) and
len(prev_component) > 0 and
prev_component[-1] == '_')
return prev_ends_underscore or _check_disambiguation(mangled_next)
def mangle_name(components, prefix="l_"):
"""
Mangle a list of name components (str or int) into a C symbol.
Port of Lean's Name.mangle.
"""
if not components:
return prefix
parts = []
prev = None
for i, comp in enumerate(components):
if isinstance(comp, int):
if i == 0:
parts.append(str(comp) + '_')
else:
parts.append('_' + str(comp) + '_')
else:
m = mangle_string(comp)
if i == 0:
if _check_disambiguation(m):
parts.append('00' + m)
else:
parts.append(m)
else:
if _need_disambiguation(prev, m):
parts.append('_00' + m)
else:
parts.append('_' + m)
prev = comp
return prefix + ''.join(parts)
# ---------------------------------------------------------------------------
# Name demangling
# ---------------------------------------------------------------------------
def demangle_body(s):
"""
Demangle a string produced by Name.mangleAux (without prefix).
Returns a list of components (str or int).
This is a faithful port of Lean's Name.demangleAux from NameMangling.lean.
"""
components = []
length = len(s)
def emit(comp):
components.append(comp)
def decode_num(pos, n):
"""Parse remaining digits, emit numeric component, continue."""
while pos < length:
ch = s[pos]
if '0' <= ch <= '9':
n = n * 10 + (ord(ch) - ord('0'))
pos += 1
else:
# Expect '_' (trailing underscore of numeric encoding)
pos += 1 # skip '_'
emit(n)
if pos >= length:
return pos
# Skip separator '_' and go to name_start
pos += 1
return name_start(pos)
# End of string
emit(n)
return pos
def name_start(pos):
"""Start parsing a new name component."""
if pos >= length:
return pos
ch = s[pos]
pos += 1
if '0' <= ch <= '9':
# Check for '00' disambiguation
if ch == '0' and pos < length and s[pos] == '0':
pos += 1
return demangle_main(pos, "", 0)
else:
return decode_num(pos, ord(ch) - ord('0'))
elif ch == '_':
return demangle_main(pos, "", 1)
else:
return demangle_main(pos, ch, 0)
def demangle_main(pos, acc, ucount):
"""Main demangling loop."""
while pos < length:
ch = s[pos]
pos += 1
if ch == '_':
ucount += 1
continue
if ucount % 2 == 0:
# Even underscores: literal underscores in component name
acc += '_' * (ucount // 2) + ch
ucount = 0
continue
# Odd ucount: separator or escape
if '0' <= ch <= '9':
# End current str component, start number
emit(acc + '_' * (ucount // 2))
if ch == '0' and pos < length and s[pos] == '0':
pos += 1
return demangle_main(pos, "", 0)
else:
return decode_num(pos, ord(ch) - ord('0'))
# Try hex escapes
if ch == 'x':
result = _parse_hex(s, pos, 2)
if result is not None:
new_pos, val = result
acc += '_' * (ucount // 2) + chr(val)
pos = new_pos
ucount = 0
continue
if ch == 'u':
result = _parse_hex(s, pos, 4)
if result is not None:
new_pos, val = result
acc += '_' * (ucount // 2) + chr(val)
pos = new_pos
ucount = 0
continue
if ch == 'U':
result = _parse_hex(s, pos, 8)
if result is not None:
new_pos, val = result
acc += '_' * (ucount // 2) + chr(val)
pos = new_pos
ucount = 0
continue
# Name separator
emit(acc)
acc = '_' * (ucount // 2) + ch
ucount = 0
# End of string
acc += '_' * (ucount // 2)
if acc:
emit(acc)
return pos
name_start(0)
return components
# ---------------------------------------------------------------------------
# Prefix handling for lp_ (package prefix)
# ---------------------------------------------------------------------------
def _is_valid_string_mangle(s):
"""Check if s is a valid output of String.mangle (no trailing bare _)."""
pos = 0
length = len(s)
while pos < length:
ch = s[pos]
if _is_ascii_alnum(ch):
pos += 1
elif ch == '_':
if pos + 1 >= length:
return False # trailing bare _
nch = s[pos + 1]
if nch == '_':
pos += 2
elif nch == 'x' and _parse_hex(s, pos + 2, 2) is not None:
pos = _parse_hex(s, pos + 2, 2)[0]
elif nch == 'u' and _parse_hex(s, pos + 2, 4) is not None:
pos = _parse_hex(s, pos + 2, 4)[0]
elif nch == 'U' and _parse_hex(s, pos + 2, 8) is not None:
pos = _parse_hex(s, pos + 2, 8)[0]
else:
return False
else:
return False
return True
def _skip_string_mangle(s, pos):
"""
Skip past a String.mangle output in s starting at pos.
Returns the position after the mangled string (where we expect the separator '_').
This is a greedy scan.
"""
length = len(s)
while pos < length:
ch = s[pos]
if _is_ascii_alnum(ch):
pos += 1
elif ch == '_':
if pos + 1 < length:
nch = s[pos + 1]
if nch == '_':
pos += 2
elif nch == 'x' and _parse_hex(s, pos + 2, 2) is not None:
pos = _parse_hex(s, pos + 2, 2)[0]
elif nch == 'u' and _parse_hex(s, pos + 2, 4) is not None:
pos = _parse_hex(s, pos + 2, 4)[0]
elif nch == 'U' and _parse_hex(s, pos + 2, 8) is not None:
pos = _parse_hex(s, pos + 2, 8)[0]
else:
return pos # bare '_': separator
else:
return pos
else:
return pos
return pos
def _find_lp_body(s):
"""
Given s = everything after 'lp_' in a symbol, find where the declaration
body (Name.mangleAux output) starts.
Returns the start index of the body within s, or None.
Strategy: try all candidate split points where the package part is a valid
String.mangle output and the body round-trips. Prefer the longest valid
package name (most specific match).
"""
length = len(s)
# Collect candidate split positions: every '_' that could be the separator
candidates = []
pos = 0
while pos < length:
if s[pos] == '_':
candidates.append(pos)
pos += 1
# Try each candidate; collect all valid splits
valid_splits = []
for split_pos in candidates:
pkg_part = s[:split_pos]
if not pkg_part:
continue
if not _is_valid_string_mangle(pkg_part):
continue
body = s[split_pos + 1:]
if not body:
continue
components = demangle_body(body)
if not components:
continue
remangled = mangle_name(components, prefix="")
if remangled == body:
first = components[0]
# Score: prefer first component starting with uppercase
has_upper = isinstance(first, str) and first and first[0].isupper()
valid_splits.append((split_pos, has_upper))
if valid_splits:
# Among splits where first decl component starts uppercase, pick longest pkg.
# Otherwise pick shortest pkg.
upper_splits = [s for s in valid_splits if s[1]]
if upper_splits:
best = max(upper_splits, key=lambda x: x[0])
else:
best = min(valid_splits, key=lambda x: x[0])
return best[0] + 1
# Fallback: greedy String.mangle scan
greedy_pos = _skip_string_mangle(s, 0)
if greedy_pos < length and s[greedy_pos] == '_':
return greedy_pos + 1
return None
# ---------------------------------------------------------------------------
# Format name components for display
# ---------------------------------------------------------------------------
def format_name(components):
"""Format a list of name components as a dot-separated string."""
return '.'.join(str(c) for c in components)
# ---------------------------------------------------------------------------
# Human-friendly postprocessing
# ---------------------------------------------------------------------------
# Compiler-generated suffix components — exact match
_SUFFIX_FLAGS_EXACT = {
'_redArg': 'arity\u2193',
'_boxed': 'boxed',
'_impl': 'impl',
}
# Compiler-generated suffix prefixes — match with optional _N index
# e.g., _lam, _lam_0, _lam_3, _lambda_0, _closed_2
_SUFFIX_FLAGS_PREFIX = {
'_lam': '\u03bb',
'_lambda': '\u03bb',
'_elam': '\u03bb',
'_jp': 'jp',
'_closed': 'closed',
}
def _match_suffix(component):
"""
Check if a string component is a compiler-generated suffix.
Returns the flag label or None.
Handles both exact matches (_redArg, _boxed) and indexed suffixes
(_lam_0, _lambda_2, _closed_0) produced by appendIndexAfter.
"""
if not isinstance(component, str):
return None
if component in _SUFFIX_FLAGS_EXACT:
return _SUFFIX_FLAGS_EXACT[component]
if component in _SUFFIX_FLAGS_PREFIX:
return _SUFFIX_FLAGS_PREFIX[component]
# Check for indexed suffix: prefix + _N
for prefix, label in _SUFFIX_FLAGS_PREFIX.items():
if component.startswith(prefix + '_'):
rest = component[len(prefix) + 1:]
if rest.isdigit():
return label
return None
def _strip_private(components):
"""Strip _private.Module.0. prefix. Returns (stripped_parts, is_private)."""
if (len(components) >= 3 and isinstance(components[0], str) and
components[0] == '_private'):
for i in range(1, len(components)):
if components[i] == 0:
if i + 1 < len(components):
return components[i + 1:], True
break
return components, False
def _strip_spec_suffixes(components):
"""Strip trailing spec_N components (from appendIndexAfter)."""
parts = list(components)
while parts and isinstance(parts[-1], str) and parts[-1].startswith('spec_'):
rest = parts[-1][5:]
if rest.isdigit():
parts.pop()
else:
break
return parts
def _is_spec_index(component):
"""Check if a component is a spec_N index (from appendIndexAfter)."""
return (isinstance(component, str) and
component.startswith('spec_') and component[5:].isdigit())
def _parse_spec_entries(rest):
"""Parse _at_..._spec pairs into separate spec context entries.
Given components starting from the first _at_, returns:
- entries: list of component lists, one per _at_..._spec block
- remaining: components after the last _spec N (trailing suffixes)
"""
entries = []
current_ctx = None
remaining = []
skip_next = False
for p in rest:
if skip_next:
skip_next = False
continue
if isinstance(p, str) and p == '_at_':
if current_ctx is not None:
entries.append(current_ctx)
current_ctx = []
continue
if isinstance(p, str) and p == '_spec':
if current_ctx is not None:
entries.append(current_ctx)
current_ctx = None
skip_next = True
continue
if isinstance(p, str) and p.startswith('_spec'):
if current_ctx is not None:
entries.append(current_ctx)
current_ctx = None
continue
if current_ctx is not None:
current_ctx.append(p)
else:
remaining.append(p)
if current_ctx is not None:
entries.append(current_ctx)
return entries, remaining
def _process_spec_context(components):
"""Process a spec context into a clean name and its flags.
Returns (name_parts, flags) where name_parts are the cleaned components
and flags is a deduplicated list of flag labels from compiler suffixes.
"""
parts = list(components)
parts, _ = _strip_private(parts)
name_parts = []
ctx_flags = []
seen = set()
for p in parts:
flag = _match_suffix(p)
if flag is not None:
if flag not in seen:
ctx_flags.append(flag)
seen.add(flag)
elif _is_spec_index(p):
pass
else:
name_parts.append(p)
return name_parts, ctx_flags
def postprocess_name(components):
"""
Transform raw demangled components into a human-friendly display string.
Applies:
- Private name cleanup: _private.Module.0.Name.foo -> Name.foo [private]
- Hygienic name cleanup: strips _@.module._hygCtx._hyg.N
- Suffix folding: _redArg, _boxed, _lam_0, etc. -> [flags]
- Specialization: f._at_.g._spec.N -> f spec at g
Shown after base [flags], with context flags: spec at g[ctx_flags]
"""
if not components:
return ""
parts = list(components)
flags = []
spec_entries = []
# --- Strip _private prefix ---
parts, is_private = _strip_private(parts)
# --- Strip hygienic suffixes: everything from _@ onward ---
at_idx = None
for i, p in enumerate(parts):
if isinstance(p, str) and p.startswith('_@'):
at_idx = i
break
if at_idx is not None:
parts = parts[:at_idx]
# --- Handle specialization: _at_ ... _spec N ---
at_positions = [i for i, p in enumerate(parts)
if isinstance(p, str) and p == '_at_']
if at_positions:
first_at = at_positions[0]
base = parts[:first_at]
rest = parts[first_at:]
entries, remaining = _parse_spec_entries(rest)
for ctx_components in entries:
ctx_name, ctx_flags = _process_spec_context(ctx_components)
if ctx_name or ctx_flags:
spec_entries.append((ctx_name, ctx_flags))
parts = base + remaining
# --- Collect suffix flags from the end ---
while parts:
last = parts[-1]
flag = _match_suffix(last)
if flag is not None:
flags.append(flag)
parts.pop()
elif isinstance(last, int) and len(parts) >= 2:
prev_flag = _match_suffix(parts[-2])
if prev_flag is not None:
flags.append(prev_flag)
parts.pop() # remove the number
parts.pop() # remove the suffix
else:
break
else:
break
if is_private:
flags.append('private')
# --- Format result ---
name = '.'.join(str(c) for c in parts) if parts else '?'
result = name
if flags:
flag_str = ', '.join(flags)
result += f' [{flag_str}]'
for ctx_name, ctx_flags in spec_entries:
ctx_str = '.'.join(str(c) for c in ctx_name) if ctx_name else '?'
if ctx_flags:
ctx_flag_str = ', '.join(ctx_flags)
result += f' spec at {ctx_str}[{ctx_flag_str}]'
else:
result += f' spec at {ctx_str}'
return result
# ---------------------------------------------------------------------------
# Main demangling entry point
# ---------------------------------------------------------------------------
def demangle_lean_name_raw(mangled):
"""
Demangle a Lean C symbol, preserving all internal name components.
Returns the exact demangled name with all compiler-generated suffixes
intact. Use demangle_lean_name() for human-friendly output.
"""
try:
return _demangle_lean_name_inner(mangled, human_friendly=False)
except Exception:
return mangled
def demangle_lean_name(mangled):
"""
Demangle a C symbol name produced by the Lean 4 compiler.
Returns a human-friendly demangled name with compiler suffixes folded
into readable flags. Use demangle_lean_name_raw() to preserve all
internal components.
"""
try:
return _demangle_lean_name_inner(mangled, human_friendly=True)
except Exception:
return mangled
def _demangle_lean_name_inner(mangled, human_friendly=True):
"""Inner demangle that may raise on malformed input."""
if mangled == "_lean_main":
return "[lean] main"
# Handle lean_ runtime functions
if human_friendly and mangled.startswith("lean_apply_"):
rest = mangled[11:]
if rest.isdigit():
return f"<apply/{rest}>"
# Strip .cold.N suffix (LLVM linker cold function clones)
cold_suffix = ""
core = mangled
dot_pos = core.find('.cold.')
if dot_pos >= 0:
cold_suffix = " " + core[dot_pos:]
core = core[:dot_pos]
elif core.endswith('.cold'):
cold_suffix = " .cold"
core = core[:-5]
result = _demangle_core(core, human_friendly)
if result is None:
return mangled
return result + cold_suffix
def _demangle_core(mangled, human_friendly=True):
"""Demangle a symbol without .cold suffix. Returns None if not a Lean name."""
fmt = postprocess_name if human_friendly else format_name
# _init_ prefix
if mangled.startswith("_init_"):
rest = mangled[6:]
body, pkg_display = _strip_lean_prefix(rest)
if body is None:
return None
components = demangle_body(body)
if not components:
return None
name = fmt(components)
if pkg_display:
return f"[init] {name} ({pkg_display})"
return f"[init] {name}"
# initialize_ prefix (module init functions)
if mangled.startswith("initialize_"):
rest = mangled[11:]
# With package: initialize_lp_{pkg}_{body} or initialize_l_{body}
body, pkg_display = _strip_lean_prefix(rest)
if body is not None:
components = demangle_body(body)
if components:
name = fmt(components)
if pkg_display:
return f"[module_init] {name} ({pkg_display})"
return f"[module_init] {name}"
# Without package: initialize_{Name.mangleAux(moduleName)}
if rest:
components = demangle_body(rest)
if components:
return f"[module_init] {fmt(components)}"
return None
# l_ or lp_ prefix
body, pkg_display = _strip_lean_prefix(mangled)
if body is None:
return None
components = demangle_body(body)
if not components:
return None
name = fmt(components)
if pkg_display:
return f"{name} ({pkg_display})"
return name
def _strip_lean_prefix(s):
"""
Strip the l_ or lp_ prefix from a mangled symbol.
Returns (body, pkg_display) where body is the Name.mangleAux output
and pkg_display is None or a string describing the package.
Returns (None, None) if the string doesn't have a recognized prefix.
"""
if s.startswith("l_"):
return (s[2:], None)
if s.startswith("lp_"):
after_lp = s[3:]
body_start = _find_lp_body(after_lp)
if body_start is not None:
pkg_mangled = after_lp[:body_start - 1]
# Unmangle the package name
pkg_components = demangle_body(pkg_mangled)
if pkg_components and len(pkg_components) == 1 and isinstance(pkg_components[0], str):
pkg_display = pkg_components[0]
else:
pkg_display = pkg_mangled
return (after_lp[body_start:], pkg_display)
# Fallback: treat everything after lp_ as body
return (after_lp, "?")
return (None, None)
# ---------------------------------------------------------------------------
# CLI
# ---------------------------------------------------------------------------
def main():
"""Filter stdin or arguments, demangling Lean names."""
import argparse
parser = argparse.ArgumentParser(
description="Demangle Lean 4 C symbol names (like c++filt for Lean)")
parser.add_argument('names', nargs='*',
help='Names to demangle (reads stdin if none given)')
parser.add_argument('--raw', action='store_true',
help='Output exact demangled names without postprocessing')
args = parser.parse_args()
demangle = demangle_lean_name_raw if args.raw else demangle_lean_name
if args.names:
for name in args.names:
print(demangle(name))
else:
for line in sys.stdin:
print(demangle(line.rstrip('\n')))
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,117 @@
#!/usr/bin/env python3
"""
Lean name demangler for samply / Firefox Profiler profiles.
Reads a profile JSON (plain or gzipped), demangles Lean function names
in the string table, and writes the result back.
Usage:
python lean_demangle_profile.py profile.json -o profile-demangled.json
python lean_demangle_profile.py profile.json.gz -o profile-demangled.json.gz
"""
import argparse
import gzip
import json
import sys
from lean_demangle import demangle_lean_name
def _demangle_string_array(string_array):
"""Demangle Lean names in a string array in-place. Returns count."""
count = 0
for i, s in enumerate(string_array):
if not isinstance(s, str):
continue
demangled = demangle_lean_name(s)
if demangled != s:
string_array[i] = demangled
count += 1
return count
def rewrite_profile(profile):
"""
Demangle Lean names in a Firefox Profiler profile dict (in-place).
Handles two profile formats:
- Newer: shared.stringArray (single shared string table)
- Older/samply: per-thread stringArray (each thread has its own)
"""
count = 0
# Shared string table (newer Firefox Profiler format)
shared = profile.get("shared")
if shared is not None:
sa = shared.get("stringArray")
if sa is not None:
count += _demangle_string_array(sa)
# Per-thread string tables (samply format)
for thread in profile.get("threads", []):
sa = thread.get("stringArray")
if sa is not None:
count += _demangle_string_array(sa)
return count
def process_profile_file(input_path, output_path):
"""Read a profile, demangle names, write it back."""
is_gzip = input_path.endswith('.gz')
if is_gzip:
with gzip.open(input_path, 'rt', encoding='utf-8') as f:
profile = json.load(f)
else:
with open(input_path, 'r', encoding='utf-8') as f:
profile = json.load(f)
count = rewrite_profile(profile)
out_gzip = output_path.endswith('.gz') if output_path else is_gzip
if output_path:
if out_gzip:
with gzip.open(output_path, 'wt', encoding='utf-8') as f:
json.dump(profile, f, ensure_ascii=False)
else:
with open(output_path, 'w', encoding='utf-8') as f:
json.dump(profile, f, ensure_ascii=False)
else:
json.dump(profile, sys.stdout, ensure_ascii=False)
sys.stdout.write('\n')
return count
def main():
parser = argparse.ArgumentParser(
description="Demangle Lean names in samply/Firefox Profiler profiles")
parser.add_argument('input', help='Input profile (JSON or .json.gz)')
parser.add_argument('-o', '--output',
help='Output path (default: stdout for JSON, '
'or input with -demangled suffix)')
args = parser.parse_args()
output = args.output
if output is None and not sys.stdout.isatty():
output = None # write to stdout
elif output is None:
# Generate output filename
inp = args.input
if inp.endswith('.json.gz'):
output = inp[:-8] + '-demangled.json.gz'
elif inp.endswith('.json'):
output = inp[:-5] + '-demangled.json'
else:
output = inp + '-demangled'
count = process_profile_file(args.input, output)
if output:
print(f"Demangled {count} names, wrote {output}", file=sys.stderr)
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,94 @@
#!/usr/bin/env python3
"""
Serve a Firefox Profiler JSON file and open it in the browser.
Unlike `samply load`, this does NOT provide a symbolication API,
so Firefox Profiler will use the names already in the profile as-is.
"""
import argparse
import gzip
import http.server
import io
import sys
import threading
import webbrowser
import urllib.parse
class ProfileHandler(http.server.BaseHTTPRequestHandler):
"""Serve the profile JSON and handle CORS for Firefox Profiler."""
profile_data = None # set by main()
def do_GET(self):
if self.path == "/profile.json":
self.send_response(200)
self.send_header("Content-Type", "application/json")
self.send_header("Content-Encoding", "gzip")
self.send_header("Access-Control-Allow-Origin", "*")
self.end_headers()
self.wfile.write(self.profile_data)
else:
self.send_response(404)
self.end_headers()
def do_OPTIONS(self):
# CORS preflight
self.send_response(200)
self.send_header("Access-Control-Allow-Origin", "*")
self.send_header("Access-Control-Allow-Methods", "GET")
self.send_header("Access-Control-Allow-Headers", "Content-Type")
self.end_headers()
def log_message(self, format, *args):
pass # suppress request logs
def main():
parser = argparse.ArgumentParser(
description="Serve a profile JSON for Firefox Profiler")
parser.add_argument("profile", help="Profile file (.json or .json.gz)")
parser.add_argument("-P", "--port", type=int, default=3457,
help="Port to serve on (default: 3457)")
parser.add_argument("-n", "--no-open", action="store_true",
help="Do not open the browser")
args = parser.parse_args()
# Read the profile data (keep it gzipped for efficient serving)
if args.profile.endswith(".gz"):
with open(args.profile, "rb") as f:
ProfileHandler.profile_data = f.read()
else:
with open(args.profile, "rb") as f:
raw = f.read()
buf = io.BytesIO()
with gzip.GzipFile(fileobj=buf, mode="wb") as gz:
gz.write(raw)
ProfileHandler.profile_data = buf.getvalue()
http.server.HTTPServer.allow_reuse_address = True
server = http.server.HTTPServer(("127.0.0.1", args.port), ProfileHandler)
profile_url = f"http://127.0.0.1:{args.port}/profile.json"
encoded = urllib.parse.quote(profile_url, safe="")
viewer_url = f"https://profiler.firefox.com/from-url/{encoded}"
if not args.no_open:
# Open browser after a short delay to let server start
def open_browser():
webbrowser.open(viewer_url)
threading.Timer(0.5, open_browser).start()
print(f"Serving profile at {profile_url}", file=sys.stderr)
print(f"Firefox Profiler: {viewer_url}", file=sys.stderr)
print("Press Ctrl+C to stop.", file=sys.stderr)
try:
server.serve_forever()
except KeyboardInterrupt:
print("\nStopped.", file=sys.stderr)
server.server_close()
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,198 @@
#!/usr/bin/env python3
"""
Symbolicate a raw samply profile using samply's symbolication API,
then demangle Lean names.
Usage:
python symbolicate_profile.py --server http://127.0.0.1:3000/TOKEN \
raw-profile.json.gz -o symbolicated-demangled.json.gz
"""
import argparse
import gzip
import json
import sys
import urllib.request
from lean_demangle import demangle_lean_name
def symbolicate_and_demangle(profile, server_url):
"""
Symbolicate a raw samply profile via the symbolication API,
then demangle Lean names. Modifies the profile in-place.
Returns the number of names resolved.
"""
libs = profile.get("libs", [])
memory_map = [[lib["debugName"], lib["breakpadId"]] for lib in libs]
count = 0
for thread in profile.get("threads", []):
count += _process_thread(thread, libs, memory_map, server_url)
return count
def _process_thread(thread, libs, memory_map, server_url):
"""Symbolicate and demangle one thread. Returns count of resolved names."""
sa = thread.get("stringArray")
ft = thread.get("frameTable")
func_t = thread.get("funcTable")
rt = thread.get("resourceTable")
if not all([sa, ft, func_t, rt]):
return 0
# Build mapping: func_index -> (lib_index, address)
# A function may be referenced by multiple frames; pick any address.
func_info = {} # func_idx -> (lib_idx, address)
for i in range(ft.get("length", 0)):
addr = ft["address"][i]
func_idx = ft["func"][i]
if func_idx in func_info:
continue
res_idx = func_t["resource"][func_idx]
if res_idx < 0 or res_idx >= rt.get("length", 0):
continue
lib_idx = rt["lib"][res_idx]
if lib_idx < 0 or lib_idx >= len(libs):
continue
func_info[func_idx] = (lib_idx, addr)
if not func_info:
return 0
# Batch symbolication: group by lib, send all addresses at once
frames_to_symbolicate = []
func_order = [] # track which func each frame corresponds to
for func_idx, (lib_idx, addr) in func_info.items():
frames_to_symbolicate.append([lib_idx, addr])
func_order.append(func_idx)
# Call the symbolication API
symbols = _call_symbolication_api(
server_url, memory_map, frames_to_symbolicate)
if not symbols:
return 0
# Update stringArray with demangled names
count = 0
for func_idx, symbol_name in zip(func_order, symbols):
if symbol_name is None:
continue
demangled = demangle_lean_name(symbol_name)
name_idx = func_t["name"][func_idx]
if name_idx < len(sa):
sa[name_idx] = demangled
count += 1
return count
def _call_symbolication_api(server_url, memory_map, frames):
"""
Call the Firefox Profiler symbolication API v5.
frames: list of [lib_index, address]
Returns: list of symbol names (or None for unresolved frames).
"""
url = server_url.rstrip("/") + "/symbolicate/v5"
# Send all frames as one "stack" in one job
req_body = json.dumps({
"memoryMap": memory_map,
"stacks": [frames],
}).encode()
req = urllib.request.Request(
url,
data=req_body,
headers={"Content-Type": "application/json"},
)
try:
with urllib.request.urlopen(req, timeout=60) as resp:
result = json.loads(resp.read())
except Exception as e:
print(f"Symbolication API error: {e}", file=sys.stderr)
return None
if "error" in result:
print(f"Symbolication API error: {result['error']}", file=sys.stderr)
return None
# Extract symbol names from result
results = result.get("results", [])
if not results:
return None
stacks = results[0].get("stacks", [[]])
if not stacks:
return None
symbols = []
for frame_result in stacks[0]:
if isinstance(frame_result, dict):
symbols.append(frame_result.get("function"))
elif isinstance(frame_result, str):
symbols.append(frame_result)
else:
symbols.append(None)
return symbols
def process_file(input_path, output_path, server_url):
"""Read a raw profile, symbolicate + demangle, write it back."""
is_gzip = input_path.endswith('.gz')
if is_gzip:
with gzip.open(input_path, 'rt', encoding='utf-8') as f:
profile = json.load(f)
else:
with open(input_path, 'r', encoding='utf-8') as f:
profile = json.load(f)
count = symbolicate_and_demangle(profile, server_url)
out_gzip = output_path.endswith('.gz') if output_path else is_gzip
if output_path:
if out_gzip:
with gzip.open(output_path, 'wt', encoding='utf-8') as f:
json.dump(profile, f, ensure_ascii=False)
else:
with open(output_path, 'w', encoding='utf-8') as f:
json.dump(profile, f, ensure_ascii=False)
else:
json.dump(profile, sys.stdout, ensure_ascii=False)
sys.stdout.write('\n')
return count
def main():
parser = argparse.ArgumentParser(
description="Symbolicate a raw samply profile and demangle Lean names")
parser.add_argument('input', help='Raw profile (JSON or .json.gz)')
parser.add_argument('-o', '--output', help='Output path')
parser.add_argument('--server', required=True,
help='Samply server URL (e.g., http://127.0.0.1:3000/TOKEN)')
args = parser.parse_args()
output = args.output
if output is None:
inp = args.input
if inp.endswith('.json.gz'):
output = inp[:-8] + '-demangled.json.gz'
elif inp.endswith('.json'):
output = inp[:-5] + '-demangled.json'
else:
output = inp + '-demangled'
count = process_file(args.input, output, args.server)
print(f"Symbolicated and demangled {count} names, wrote {output}",
file=sys.stderr)
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,670 @@
#!/usr/bin/env python3
"""Tests for the Lean name demangler."""
import unittest
import json
import gzip
import tempfile
import os
from lean_demangle import (
mangle_string, mangle_name, demangle_body, format_name,
demangle_lean_name, demangle_lean_name_raw, postprocess_name,
_parse_hex, _check_disambiguation,
)
class TestStringMangle(unittest.TestCase):
"""Test String.mangle (character-level escaping)."""
def test_alphanumeric(self):
self.assertEqual(mangle_string("hello"), "hello")
self.assertEqual(mangle_string("abc123"), "abc123")
def test_underscore(self):
self.assertEqual(mangle_string("a_b"), "a__b")
self.assertEqual(mangle_string("_"), "__")
self.assertEqual(mangle_string("__"), "____")
def test_special_chars(self):
self.assertEqual(mangle_string("."), "_x2e")
self.assertEqual(mangle_string("a.b"), "a_x2eb")
def test_unicode(self):
self.assertEqual(mangle_string("\u03bb"), "_u03bb")
self.assertEqual(mangle_string("\U0001d55c"), "_U0001d55c")
def test_empty(self):
self.assertEqual(mangle_string(""), "")
class TestNameMangle(unittest.TestCase):
"""Test Name.mangle (hierarchical name mangling)."""
def test_simple(self):
self.assertEqual(mangle_name(["Lean", "Meta", "Sym", "main"]),
"l_Lean_Meta_Sym_main")
def test_single_component(self):
self.assertEqual(mangle_name(["main"]), "l_main")
def test_numeric_component(self):
self.assertEqual(
mangle_name(["_private", "Lean", "Meta", "Basic", 0,
"Lean", "Meta", "withMVarContextImp"]),
"l___private_Lean_Meta_Basic_0__Lean_Meta_withMVarContextImp")
def test_component_with_underscore(self):
self.assertEqual(mangle_name(["a_b"]), "l_a__b")
self.assertEqual(mangle_name(["a_b", "c"]), "l_a__b_c")
def test_disambiguation_digit_start(self):
self.assertEqual(mangle_name(["0foo"]), "l_000foo")
def test_disambiguation_escape_start(self):
self.assertEqual(mangle_name(["a", "x27"]), "l_a_00x27")
def test_numeric_root(self):
self.assertEqual(mangle_name([42]), "l_42_")
self.assertEqual(mangle_name([42, "foo"]), "l_42__foo")
def test_component_ending_with_underscore(self):
self.assertEqual(mangle_name(["a_", "b"]), "l_a___00b")
def test_custom_prefix(self):
self.assertEqual(mangle_name(["foo"], prefix="lp_pkg_"),
"lp_pkg_foo")
class TestDemangleBody(unittest.TestCase):
"""Test demangle_body (the core Name.demangleAux algorithm)."""
def test_simple(self):
self.assertEqual(demangle_body("Lean_Meta_Sym_main"),
["Lean", "Meta", "Sym", "main"])
def test_single(self):
self.assertEqual(demangle_body("main"), ["main"])
def test_empty(self):
self.assertEqual(demangle_body(""), [])
def test_underscore_in_component(self):
self.assertEqual(demangle_body("a__b"), ["a_b"])
self.assertEqual(demangle_body("a__b_c"), ["a_b", "c"])
def test_numeric_component(self):
self.assertEqual(demangle_body("foo_42__bar"), ["foo", 42, "bar"])
def test_numeric_root(self):
self.assertEqual(demangle_body("42_"), [42])
def test_numeric_at_end(self):
self.assertEqual(demangle_body("foo_42_"), ["foo", 42])
def test_disambiguation_00(self):
self.assertEqual(demangle_body("a_00x27"), ["a", "x27"])
def test_disambiguation_00_at_root(self):
self.assertEqual(demangle_body("000foo"), ["0foo"])
def test_hex_escape_x(self):
self.assertEqual(demangle_body("a_x2eb"), ["a.b"])
def test_hex_escape_u(self):
self.assertEqual(demangle_body("_u03bb"), ["\u03bb"])
def test_hex_escape_U(self):
self.assertEqual(demangle_body("_U0001d55c"), ["\U0001d55c"])
def test_private_name(self):
body = "__private_Lean_Meta_Basic_0__Lean_Meta_withMVarContextImp"
self.assertEqual(demangle_body(body),
["_private", "Lean", "Meta", "Basic", 0,
"Lean", "Meta", "withMVarContextImp"])
def test_boxed_suffix(self):
body = "foo___boxed"
self.assertEqual(demangle_body(body), ["foo", "_boxed"])
def test_redArg_suffix(self):
body = "foo_bar___redArg"
self.assertEqual(demangle_body(body), ["foo", "bar", "_redArg"])
def test_component_ending_underscore_disambiguation(self):
self.assertEqual(demangle_body("a___00b"), ["a_", "b"])
class TestRoundTrip(unittest.TestCase):
"""Test that mangle(demangle(x)) == x for various names."""
def _check_roundtrip(self, components):
mangled = mangle_name(components, prefix="")
demangled = demangle_body(mangled)
self.assertEqual(demangled, components,
f"Round-trip failed: {components} -> '{mangled}' -> {demangled}")
mangled_with_prefix = mangle_name(components, prefix="l_")
self.assertTrue(mangled_with_prefix.startswith("l_"))
body = mangled_with_prefix[2:]
demangled2 = demangle_body(body)
self.assertEqual(demangled2, components)
def test_simple_names(self):
self._check_roundtrip(["Lean", "Meta", "main"])
self._check_roundtrip(["a"])
self._check_roundtrip(["Foo", "Bar", "baz"])
def test_numeric(self):
self._check_roundtrip(["foo", 0, "bar"])
self._check_roundtrip([42])
self._check_roundtrip(["a", 1, "b", 2, "c"])
def test_underscores(self):
self._check_roundtrip(["_private"])
self._check_roundtrip(["a_b", "c_d"])
self._check_roundtrip(["_at_", "_spec"])
def test_private_name(self):
self._check_roundtrip(["_private", "Lean", "Meta", "Basic", 0,
"Lean", "Meta", "withMVarContextImp"])
def test_boxed(self):
self._check_roundtrip(["Lean", "Meta", "foo", "_boxed"])
def test_redArg(self):
self._check_roundtrip(["Lean", "Meta", "foo", "_redArg"])
def test_specialization(self):
self._check_roundtrip(["List", "map", "_at_", "Foo", "bar", "_spec", 3])
def test_lambda(self):
self._check_roundtrip(["Foo", "bar", "_lambda", 0])
self._check_roundtrip(["Foo", "bar", "_lambda", 2])
def test_closed(self):
self._check_roundtrip(["myConst", "_closed", 0])
def test_special_chars(self):
self._check_roundtrip(["a.b"])
self._check_roundtrip(["\u03bb"])
self._check_roundtrip(["a", "b\u2192c"])
def test_disambiguation_cases(self):
self._check_roundtrip(["a", "x27"])
self._check_roundtrip(["0foo"])
self._check_roundtrip(["a_", "b"])
def test_complex_real_names(self):
"""Names modeled after real Lean compiler output."""
self._check_roundtrip(
["Lean", "MVarId", "withContext", "_at_",
"_private", "Lean", "Meta", "Sym", 0,
"Lean", "Meta", "Sym", "BackwardRule", "apply",
"_spec", 2, "_redArg", "_lambda", 0, "_boxed"])
class TestDemangleRaw(unittest.TestCase):
"""Test demangle_lean_name_raw (exact demangling, no postprocessing)."""
def test_l_prefix(self):
self.assertEqual(
demangle_lean_name_raw("l_Lean_Meta_Sym_main"),
"Lean.Meta.Sym.main")
def test_l_prefix_private(self):
result = demangle_lean_name_raw(
"l___private_Lean_Meta_Basic_0__Lean_Meta_withMVarContextImp")
self.assertEqual(result,
"_private.Lean.Meta.Basic.0.Lean.Meta.withMVarContextImp")
def test_l_prefix_boxed(self):
result = demangle_lean_name_raw("l_foo___boxed")
self.assertEqual(result, "foo._boxed")
def test_l_prefix_redArg(self):
result = demangle_lean_name_raw(
"l___private_Lean_Meta_Basic_0__Lean_Meta_withMVarContextImp___redArg")
self.assertEqual(
result,
"_private.Lean.Meta.Basic.0.Lean.Meta.withMVarContextImp._redArg")
def test_lean_main(self):
self.assertEqual(demangle_lean_name_raw("_lean_main"), "[lean] main")
def test_non_lean_names(self):
self.assertEqual(demangle_lean_name_raw("printf"), "printf")
self.assertEqual(demangle_lean_name_raw("malloc"), "malloc")
self.assertEqual(demangle_lean_name_raw("lean_apply_5"), "lean_apply_5")
self.assertEqual(demangle_lean_name_raw(""), "")
def test_init_prefix(self):
result = demangle_lean_name_raw("_init_l_Lean_Meta_foo")
self.assertEqual(result, "[init] Lean.Meta.foo")
def test_lp_prefix_simple(self):
mangled = mangle_name(["Lean", "Meta", "foo"], prefix="lp_std_")
self.assertEqual(mangled, "lp_std_Lean_Meta_foo")
result = demangle_lean_name_raw(mangled)
self.assertEqual(result, "Lean.Meta.foo (std)")
def test_lp_prefix_underscore_pkg(self):
pkg_mangled = mangle_string("my_pkg")
self.assertEqual(pkg_mangled, "my__pkg")
mangled = mangle_name(["Lean", "Meta", "foo"],
prefix=f"lp_{pkg_mangled}_")
self.assertEqual(mangled, "lp_my__pkg_Lean_Meta_foo")
result = demangle_lean_name_raw(mangled)
self.assertEqual(result, "Lean.Meta.foo (my_pkg)")
def test_lp_prefix_private_decl(self):
mangled = mangle_name(
["_private", "X", 0, "Y", "foo"], prefix="lp_pkg_")
self.assertEqual(mangled, "lp_pkg___private_X_0__Y_foo")
result = demangle_lean_name_raw(mangled)
self.assertEqual(result, "_private.X.0.Y.foo (pkg)")
def test_complex_specialization(self):
components = [
"Lean", "MVarId", "withContext", "_at_",
"_private", "Lean", "Meta", "Sym", 0,
"Lean", "Meta", "Sym", "BackwardRule", "apply",
"_spec", 2, "_redArg", "_lambda", 0, "_boxed"
]
mangled = mangle_name(components)
result = demangle_lean_name_raw(mangled)
expected = format_name(components)
self.assertEqual(result, expected)
def test_cold_suffix(self):
result = demangle_lean_name_raw("l_Lean_Meta_foo___redArg.cold.1")
self.assertEqual(result, "Lean.Meta.foo._redArg .cold.1")
def test_cold_suffix_plain(self):
result = demangle_lean_name_raw("l_Lean_Meta_foo.cold")
self.assertEqual(result, "Lean.Meta.foo .cold")
def test_initialize_no_pkg(self):
result = demangle_lean_name_raw("initialize_Init_Control_Basic")
self.assertEqual(result, "[module_init] Init.Control.Basic")
def test_initialize_with_l_prefix(self):
result = demangle_lean_name_raw("initialize_l_Lean_Meta_foo")
self.assertEqual(result, "[module_init] Lean.Meta.foo")
def test_never_crashes(self):
"""Demangling should never raise, just return the original."""
weird_inputs = [
"", "l_", "lp_", "lp_x", "_init_", "initialize_",
"l_____", "lp____", "l_00", "l_0",
"some random string", "l_ space",
]
for inp in weird_inputs:
result = demangle_lean_name_raw(inp)
self.assertIsInstance(result, str)
class TestPostprocess(unittest.TestCase):
"""Test postprocess_name (human-friendly suffix folding, etc.)."""
def test_no_change(self):
self.assertEqual(postprocess_name(["Lean", "Meta", "main"]),
"Lean.Meta.main")
def test_boxed(self):
self.assertEqual(postprocess_name(["foo", "_boxed"]),
"foo [boxed]")
def test_redArg(self):
self.assertEqual(postprocess_name(["foo", "bar", "_redArg"]),
"foo.bar [arity\u2193]")
def test_lambda_separate(self):
# _lam as separate component + numeric index
self.assertEqual(postprocess_name(["foo", "_lam", 0]),
"foo [\u03bb]")
def test_lambda_indexed(self):
# _lam_0 as single string (appendIndexAfter)
self.assertEqual(postprocess_name(["foo", "_lam_0"]),
"foo [\u03bb]")
self.assertEqual(postprocess_name(["foo", "_lambda_2"]),
"foo [\u03bb]")
def test_lambda_boxed(self):
# _lam_0 followed by _boxed
self.assertEqual(
postprocess_name(["Lean", "Meta", "Simp", "simpLambda",
"_lam_0", "_boxed"]),
"Lean.Meta.Simp.simpLambda [boxed, \u03bb]")
def test_closed(self):
self.assertEqual(postprocess_name(["myConst", "_closed", 3]),
"myConst [closed]")
def test_closed_indexed(self):
self.assertEqual(postprocess_name(["myConst", "_closed_0"]),
"myConst [closed]")
def test_multiple_suffixes(self):
self.assertEqual(postprocess_name(["foo", "_redArg", "_boxed"]),
"foo [boxed, arity\u2193]")
def test_redArg_lam(self):
# _redArg followed by _lam_0 (issue #4)
self.assertEqual(
postprocess_name(["Lean", "profileitIOUnsafe",
"_redArg", "_lam_0"]),
"Lean.profileitIOUnsafe [\u03bb, arity\u2193]")
def test_private_name(self):
self.assertEqual(
postprocess_name(["_private", "Lean", "Meta", "Basic", 0,
"Lean", "Meta", "withMVarContextImp"]),
"Lean.Meta.withMVarContextImp [private]")
def test_private_with_suffix(self):
self.assertEqual(
postprocess_name(["_private", "Lean", "Meta", "Basic", 0,
"Lean", "Meta", "foo", "_redArg"]),
"Lean.Meta.foo [arity\u2193, private]")
def test_hygienic_strip(self):
self.assertEqual(
postprocess_name(["Lean", "Meta", "foo", "_@", "Lean", "Meta",
"_hyg", 42]),
"Lean.Meta.foo")
def test_specialization(self):
self.assertEqual(
postprocess_name(["List", "map", "_at_", "Foo", "bar",
"_spec", 3]),
"List.map spec at Foo.bar")
def test_specialization_with_suffix(self):
# Base suffix _boxed appears in [flags] before spec at
self.assertEqual(
postprocess_name(["Lean", "MVarId", "withContext", "_at_",
"Foo", "bar", "_spec", 2, "_boxed"]),
"Lean.MVarId.withContext [boxed] spec at Foo.bar")
def test_spec_context_with_flags(self):
# Compiler suffixes in spec context become context flags
self.assertEqual(
postprocess_name(["Lean", "Meta", "foo", "_at_",
"Lean", "Meta", "bar", "_elam_1", "_redArg",
"_spec", 2]),
"Lean.Meta.foo spec at Lean.Meta.bar[\u03bb, arity\u2193]")
def test_spec_context_flags_dedup(self):
# Duplicate flag labels are deduplicated
self.assertEqual(
postprocess_name(["f", "_at_",
"g", "_lam_0", "_elam_1", "_redArg",
"_spec", 1]),
"f spec at g[\u03bb, arity\u2193]")
def test_multiple_at(self):
# Multiple _at_ entries become separate spec at clauses
self.assertEqual(
postprocess_name(["f", "_at_", "g", "_spec", 1,
"_at_", "h", "_spec", 2]),
"f spec at g spec at h")
def test_multiple_at_with_flags(self):
# Multiple spec at with flags on base and contexts
self.assertEqual(
postprocess_name(["f", "_at_", "g", "_redArg", "_spec", 1,
"_at_", "h", "_lam_0", "_spec", 2,
"_boxed"]),
"f [boxed] spec at g[arity\u2193] spec at h[\u03bb]")
def test_base_flags_before_spec(self):
# Base trailing suffixes appear in [flags] before spec at
self.assertEqual(
postprocess_name(["f", "_at_", "g", "_spec", 1, "_lam_0"]),
"f [\u03bb] spec at g")
def test_spec_context_strip_spec_suffixes(self):
# spec_0 in context should be stripped
self.assertEqual(
postprocess_name(["Lean", "Meta", "transformWithCache", "visit",
"_at_",
"_private", "Lean", "Meta", "Transform", 0,
"Lean", "Meta", "transform",
"Lean", "Meta", "Sym", "unfoldReducible",
"spec_0", "spec_0",
"_spec", 1]),
"Lean.Meta.transformWithCache.visit "
"spec at Lean.Meta.transform.Lean.Meta.Sym.unfoldReducible")
def test_spec_context_strip_private(self):
# _private in spec context should be stripped
self.assertEqual(
postprocess_name(["Array", "mapMUnsafe", "map", "_at_",
"_private", "Lean", "Meta", "Transform", 0,
"Lean", "Meta", "transformWithCache", "visit",
"_spec", 1]),
"Array.mapMUnsafe.map "
"spec at Lean.Meta.transformWithCache.visit")
def test_empty(self):
self.assertEqual(postprocess_name([]), "")
class TestDemangleHumanFriendly(unittest.TestCase):
"""Test demangle_lean_name (human-friendly output)."""
def test_simple(self):
self.assertEqual(demangle_lean_name("l_Lean_Meta_main"),
"Lean.Meta.main")
def test_boxed(self):
self.assertEqual(demangle_lean_name("l_foo___boxed"),
"foo [boxed]")
def test_redArg(self):
self.assertEqual(demangle_lean_name("l_foo___redArg"),
"foo [arity\u2193]")
def test_private(self):
self.assertEqual(
demangle_lean_name(
"l___private_Lean_Meta_Basic_0__Lean_Meta_foo"),
"Lean.Meta.foo [private]")
def test_private_with_redArg(self):
self.assertEqual(
demangle_lean_name(
"l___private_Lean_Meta_Basic_0__Lean_Meta_foo___redArg"),
"Lean.Meta.foo [arity\u2193, private]")
def test_cold_with_suffix(self):
self.assertEqual(
demangle_lean_name("l_Lean_Meta_foo___redArg.cold.1"),
"Lean.Meta.foo [arity\u2193] .cold.1")
def test_lean_apply(self):
self.assertEqual(demangle_lean_name("lean_apply_5"), "<apply/5>")
self.assertEqual(demangle_lean_name("lean_apply_12"), "<apply/12>")
def test_lean_apply_raw_unchanged(self):
self.assertEqual(demangle_lean_name_raw("lean_apply_5"),
"lean_apply_5")
def test_init_private(self):
self.assertEqual(
demangle_lean_name(
"_init_l___private_X_0__Y_foo"),
"[init] Y.foo [private]")
def test_complex_specialization(self):
components = [
"Lean", "MVarId", "withContext", "_at_",
"_private", "Lean", "Meta", "Sym", 0,
"Lean", "Meta", "Sym", "BackwardRule", "apply",
"_spec", 2, "_redArg", "_lambda", 0, "_boxed"
]
mangled = mangle_name(components)
result = demangle_lean_name(mangled)
# Base: Lean.MVarId.withContext with trailing _redArg, _lambda 0, _boxed
# Spec context: Lean.Meta.Sym.BackwardRule.apply (private stripped)
self.assertEqual(
result,
"Lean.MVarId.withContext [boxed, \u03bb, arity\u2193] "
"spec at Lean.Meta.Sym.BackwardRule.apply")
def test_non_lean_unchanged(self):
self.assertEqual(demangle_lean_name("printf"), "printf")
self.assertEqual(demangle_lean_name("malloc"), "malloc")
self.assertEqual(demangle_lean_name(""), "")
class TestDemangleProfile(unittest.TestCase):
"""Test the profile rewriter."""
def _make_profile_shared(self, strings):
"""Create a profile with shared.stringArray (newer format)."""
return {
"meta": {"version": 28},
"libs": [],
"shared": {
"stringArray": list(strings),
},
"threads": [{
"name": "main",
"pid": "1",
"tid": 1,
"funcTable": {
"name": list(range(len(strings))),
"isJS": [False] * len(strings),
"relevantForJS": [False] * len(strings),
"resource": [-1] * len(strings),
"fileName": [None] * len(strings),
"lineNumber": [None] * len(strings),
"columnNumber": [None] * len(strings),
"length": len(strings),
},
"frameTable": {"length": 0},
"stackTable": {"length": 0},
"samples": {"length": 0},
"markers": {"length": 0},
"resourceTable": {"length": 0},
"nativeSymbols": {"length": 0},
}],
"pages": [],
"counters": [],
}
def _make_profile_per_thread(self, strings):
"""Create a profile with per-thread stringArray (samply format)."""
return {
"meta": {"version": 28},
"libs": [],
"threads": [{
"name": "main",
"pid": "1",
"tid": 1,
"stringArray": list(strings),
"funcTable": {
"name": list(range(len(strings))),
"isJS": [False] * len(strings),
"relevantForJS": [False] * len(strings),
"resource": [-1] * len(strings),
"fileName": [None] * len(strings),
"lineNumber": [None] * len(strings),
"columnNumber": [None] * len(strings),
"length": len(strings),
},
"frameTable": {"length": 0},
"stackTable": {"length": 0},
"samples": {"length": 0},
"markers": {"length": 0},
"resourceTable": {"length": 0},
"nativeSymbols": {"length": 0},
}],
"pages": [],
"counters": [],
}
def test_profile_rewrite_shared(self):
from lean_demangle_profile import rewrite_profile
strings = [
"l_Lean_Meta_Sym_main",
"printf",
"lean_apply_5",
"l___private_Lean_Meta_Basic_0__Lean_Meta_foo",
]
profile = self._make_profile_shared(strings)
rewrite_profile(profile)
sa = profile["shared"]["stringArray"]
self.assertEqual(sa[0], "Lean.Meta.Sym.main")
self.assertEqual(sa[1], "printf")
self.assertEqual(sa[2], "<apply/5>")
self.assertEqual(sa[3], "Lean.Meta.foo [private]")
def test_profile_rewrite_per_thread(self):
from lean_demangle_profile import rewrite_profile
strings = [
"l_Lean_Meta_Sym_main",
"printf",
"lean_apply_5",
"l___private_Lean_Meta_Basic_0__Lean_Meta_foo",
]
profile = self._make_profile_per_thread(strings)
count = rewrite_profile(profile)
sa = profile["threads"][0]["stringArray"]
self.assertEqual(sa[0], "Lean.Meta.Sym.main")
self.assertEqual(sa[1], "printf")
self.assertEqual(sa[2], "<apply/5>")
self.assertEqual(sa[3], "Lean.Meta.foo [private]")
self.assertEqual(count, 3)
def test_profile_json_roundtrip(self):
from lean_demangle_profile import process_profile_file
strings = ["l_Lean_Meta_main", "malloc"]
profile = self._make_profile_shared(strings)
with tempfile.NamedTemporaryFile(mode='w', suffix='.json',
delete=False) as f:
json.dump(profile, f)
inpath = f.name
outpath = inpath.replace('.json', '-demangled.json')
try:
process_profile_file(inpath, outpath)
with open(outpath) as f:
result = json.load(f)
self.assertEqual(result["shared"]["stringArray"][0],
"Lean.Meta.main")
self.assertEqual(result["shared"]["stringArray"][1], "malloc")
finally:
os.unlink(inpath)
if os.path.exists(outpath):
os.unlink(outpath)
def test_profile_gzip_roundtrip(self):
from lean_demangle_profile import process_profile_file
strings = ["l_Lean_Meta_main", "malloc"]
profile = self._make_profile_shared(strings)
with tempfile.NamedTemporaryFile(suffix='.json.gz',
delete=False) as f:
with gzip.open(f, 'wt') as gz:
json.dump(profile, gz)
inpath = f.name
outpath = inpath.replace('.json.gz', '-demangled.json.gz')
try:
process_profile_file(inpath, outpath)
with gzip.open(outpath, 'rt') as f:
result = json.load(f)
self.assertEqual(result["shared"]["stringArray"][0],
"Lean.Meta.main")
finally:
os.unlink(inpath)
if os.path.exists(outpath):
os.unlink(outpath)
if __name__ == '__main__':
unittest.main()

View File

@@ -836,6 +836,14 @@ def main():
continue
print(f" ✅ On compatible toolchain (>= {toolchain})")
# For reference-manual, check that the release notes title is correct BEFORE tagging.
# This catches the case where the toolchain bump PR was merged without updating
# the release notes title (e.g., still showing "-rc1" for a stable release).
if name == "reference-manual":
if not check_reference_manual_release_title(url, toolchain, branch, github_token):
repo_status[name] = False
continue
# Special handling for ProofWidgets4
if name == "ProofWidgets4":
if not check_proofwidgets4_release(url, toolchain, github_token):

View File

@@ -65,13 +65,6 @@ repositories:
branch: master
dependencies: [lean4-unicode-basic]
- name: doc-gen4
url: https://github.com/leanprover/doc-gen4
toolchain-tag: true
stable-branch: false
branch: main
dependencies: [lean4-cli, BibtexQuery]
- name: reference-manual
url: https://github.com/leanprover/reference-manual
toolchain-tag: true
@@ -84,8 +77,7 @@ repositories:
toolchain-tag: false
stable-branch: false
branch: main
dependencies:
- batteries
dependencies: []
- name: aesop
url: https://github.com/leanprover-community/aesop
@@ -107,10 +99,16 @@ repositories:
- lean4checker
- batteries
- lean4-cli
- doc-gen4
- import-graph
- plausible
- name: doc-gen4
url: https://github.com/leanprover/doc-gen4
toolchain-tag: true
stable-branch: false
branch: main
dependencies: [lean4-cli, BibtexQuery, mathlib4]
- name: cslib
url: https://github.com/leanprover/cslib
toolchain-tag: true

View File

@@ -24,6 +24,7 @@ What this script does:
- Safety checks for repositories using bump branches
- Custom build and test procedures
- lean-fro.org: runs scripts/update.sh to regenerate site content
- mathlib4: updates ProofWidgets4 pin (v0.0.X sequential tags, not v4.X.Y)
6. Commits the changes with message "chore: bump toolchain to {version}"
@@ -59,6 +60,8 @@ import re
import subprocess
import shutil
import json
import requests
import base64
from pathlib import Path
# Color functions for terminal output
@@ -115,6 +118,60 @@ def find_repo(repo_name, config):
sys.exit(1)
return matching_repos[0]
def get_github_token():
try:
result = subprocess.run(['gh', 'auth', 'token'], capture_output=True, text=True)
if result.returncode == 0:
return result.stdout.strip()
except FileNotFoundError:
pass
return None
def find_proofwidgets_tag(version):
"""Find the latest ProofWidgets4 tag that uses the given toolchain version.
ProofWidgets4 uses sequential version tags (v0.0.X) rather than toolchain-based tags.
This function finds the most recent tag whose lean-toolchain matches the target version
exactly, checking the 20 most recent tags.
"""
github_token = get_github_token()
api_base = "https://api.github.com/repos/leanprover-community/ProofWidgets4"
headers = {'Authorization': f'token {github_token}'} if github_token else {}
response = requests.get(f"{api_base}/git/matching-refs/tags/v0.0.", headers=headers, timeout=30)
if response.status_code != 200:
return None
tags = response.json()
tag_names = []
for tag in tags:
ref = tag['ref']
if ref.startswith('refs/tags/v0.0.'):
tag_name = ref.replace('refs/tags/', '')
try:
version_num = int(tag_name.split('.')[-1])
tag_names.append((version_num, tag_name))
except (ValueError, IndexError):
continue
if not tag_names:
return None
# Sort by version number (descending) and check recent tags
tag_names.sort(reverse=True)
target = f"leanprover/lean4:{version}"
for _, tag_name in tag_names[:20]:
# Fetch lean-toolchain for this tag
api_url = f"{api_base}/contents/lean-toolchain?ref={tag_name}"
resp = requests.get(api_url, headers=headers, timeout=30)
if resp.status_code != 200:
continue
content = base64.b64decode(resp.json().get("content", "").replace("\n", "")).decode('utf-8').strip()
if content == target:
return tag_name
return None
def setup_downstream_releases_dir():
"""Create the downstream_releases directory if it doesn't exist."""
downstream_dir = Path("downstream_releases")
@@ -426,6 +483,62 @@ def execute_release_steps(repo, version, config):
run_command(f'perl -pi -e \'s/"v4\\.[0-9]+(\\.[0-9]+)?(-rc[0-9]+)?"/"' + version + '"/g\' lakefile.*', cwd=repo_path)
run_command("lake update", cwd=repo_path, stream_output=True)
# For reference-manual, update the release notes title to match the target version.
# e.g., for a stable release, change "Lean 4.28.0-rc1 (date)" to "Lean 4.28.0 (date)"
# e.g., for rc2, change "Lean 4.28.0-rc1 (date)" to "Lean 4.28.0-rc2 (date)"
if repo_name == "reference-manual":
base_version = version.lstrip('v').split('-')[0] # "4.28.0"
file_name = f"v{base_version.replace('.', '_')}.lean"
release_notes_file = repo_path / "Manual" / "Releases" / file_name
if release_notes_file.exists():
is_rc = "-rc" in version
if is_rc:
# For RC releases, update to the exact RC version
display_version = version.lstrip('v') # "4.28.0-rc2"
else:
# For stable releases, strip any RC suffix
display_version = base_version # "4.28.0"
print(blue(f"Updating release notes title in {file_name}..."))
content = release_notes_file.read_text()
# Match the #doc line title: "Lean X.Y.Z-rcN (date)" or "Lean X.Y.Z (date)"
new_content = re.sub(
r'(#doc\s+\(Manual\)\s+"Lean\s+)\d+\.\d+\.\d+(-rc\d+)?(\s+\([^)]*\)"\s*=>)',
rf'\g<1>{display_version}\3',
content
)
if new_content != content:
release_notes_file.write_text(new_content)
print(green(f"Updated release notes title to Lean {display_version}"))
else:
print(green("Release notes title already correct"))
else:
print(yellow(f"Release notes file {file_name} not found, skipping title update"))
# For mathlib4, update ProofWidgets4 pin (it uses sequential v0.0.X tags, not v4.X.Y)
if repo_name == "mathlib4":
print(blue("Checking ProofWidgets4 version pin..."))
pw_tag = find_proofwidgets_tag(version)
if pw_tag:
print(blue(f"Updating ProofWidgets4 pin to {pw_tag}..."))
for lakefile in repo_path.glob("lakefile.*"):
content = lakefile.read_text()
# Only update the ProofWidgets4 dependency line, not other v0.0.X pins
new_content = re.sub(
r'(require\s+"leanprover-community"\s*/\s*"proofwidgets"\s*@\s*git\s+"v)0\.0\.\d+(")',
rf'\g<1>{pw_tag.removeprefix("v")}\2',
content
)
if new_content != content:
lakefile.write_text(new_content)
print(green(f"Updated ProofWidgets4 pin in {lakefile.name}"))
run_command("lake update proofwidgets", cwd=repo_path, stream_output=True)
print(green(f"Updated ProofWidgets4 to {pw_tag}"))
else:
print(yellow(f"Could not find a ProofWidgets4 tag for toolchain {version}"))
print(yellow("You may need to update the ProofWidgets4 pin manually"))
# Commit changes (only if there are changes)
print(blue("Checking for changes to commit..."))
try:

View File

@@ -10,7 +10,7 @@ endif()
include(ExternalProject)
project(LEAN CXX C)
set(LEAN_VERSION_MAJOR 4)
set(LEAN_VERSION_MINOR 29)
set(LEAN_VERSION_MINOR 30)
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'")
@@ -250,7 +250,7 @@ else()
endif()
if(NOT MSVC)
set(CMAKE_CXX_FLAGS "-Wall -Wextra -std=c++14 ${CMAKE_CXX_FLAGS}")
set(CMAKE_CXX_FLAGS "-Wall -Wextra -std=c++20 ${CMAKE_CXX_FLAGS}")
set(CMAKE_CXX_FLAGS_DEBUG "-g3 ${CMAKE_CXX_FLAGS_DEBUG}")
if(CMAKE_SYSTEM_NAME MATCHES "Emscripten")
# smallest+slower | -Oz .. -Os .. -O3 | largest+faster

View File

@@ -322,6 +322,8 @@ class MonadControl (m : semiOutParam (Type u → Type v)) (n : Type u → Type w
-/
restoreM : {α : Type u} m (stM α) n α
attribute [reducible] MonadControl.stM
/--
A way to lift a computation from one monad to another while providing the lifted computation with a
means of interpreting computations from the outer monad. This provides a means of lifting
@@ -349,6 +351,8 @@ class MonadControlT (m : Type u → Type v) (n : Type u → Type w) where
-/
restoreM {α : Type u} : stM α n α
attribute [reducible] MonadControlT.stM
export MonadControlT (stM liftWith restoreM)
@[always_inline]

View File

@@ -30,6 +30,8 @@ namespace ExceptT
simp [run] at h
assumption
@[simp] theorem stM_eq [Monad m] : stM m (ExceptT ε m) α = Except ε α := rfl
@[simp, grind =] theorem run_mk (x : m (Except ε α)) : run (mk x : ExceptT ε m α) = x := rfl
@[simp, grind =] theorem run_pure [Monad m] (x : α) : run (pure x : ExceptT ε m α) = pure (Except.ok x) := rfl
@@ -118,7 +120,7 @@ instance [Monad m] [LawfulMonad m] : LawfulMonad (ExceptT ε m) where
@[simp] theorem run_controlAt [Monad m] [LawfulMonad m] (f : ({β : Type u} ExceptT ε m β m (stM m (ExceptT ε m) β)) m (stM m (ExceptT ε m) α)) :
ExceptT.run (controlAt m f) = f fun x => x.run := by
simp [controlAt, run_bind, bind_map_left]
simp [controlAt, run_bind]
@[simp] theorem run_control [Monad m] [LawfulMonad m] (f : ({β : Type u} ExceptT ε m β m (stM m (ExceptT ε m) β)) m (stM m (ExceptT ε m) α)) :
ExceptT.run (control f) = f fun x => x.run := run_controlAt f
@@ -256,6 +258,7 @@ instance [Monad m] [LawfulMonad m] : LawfulMonad (OptionT m) where
rw [ bind_pure_comp]
rfl
set_option backward.isDefEq.respectTransparency false in
@[simp] theorem run_controlAt [Monad m] [LawfulMonad m] (f : ({β : Type u} OptionT m β m (stM m (OptionT m) β)) m (stM m (OptionT m) α)) :
OptionT.run (controlAt m f) = f fun x => x.run := by
simp [controlAt, Option.elimM, Option.elim]
@@ -343,6 +346,7 @@ instance [Monad m] [LawfulMonad m] : LawfulMonad (ReaderT ρ m) where
ReaderT.run (liftWith f) ctx = (f fun x => x.run ctx) :=
rfl
set_option backward.isDefEq.respectTransparency false in
@[simp] theorem run_controlAt [Monad m] [LawfulMonad m] (f : ({β : Type u} ReaderT ρ m β m (stM m (ReaderT ρ m) β)) m (stM m (ReaderT ρ m) α)) (ctx : ρ) :
ReaderT.run (controlAt m f) ctx = f fun x => x.run ctx := by
simp [controlAt]
@@ -443,6 +447,7 @@ instance [Monad m] [LawfulMonad m] : LawfulMonad (StateT σ m) where
StateT.run (liftWith f) s = ((·, s) <$> f fun x => x.run s) := by
simp [liftWith, MonadControl.liftWith, Function.comp_def]
set_option backward.isDefEq.respectTransparency false in
@[simp] theorem run_controlAt [Monad m] [LawfulMonad m] (f : ({β : Type u} StateT σ m β m (stM m (StateT σ m) β)) m (stM m (StateT σ m) α)) (s : σ) :
StateT.run (controlAt m f) s = f fun x => x.run s := by
simp [controlAt]

View File

@@ -15,7 +15,8 @@ public import Init.Ext
public instance [Monad m] [LawfulMonad m] [MonadAttach m] [WeaklyLawfulMonadAttach m] :
WeaklyLawfulMonadAttach (ReaderT ρ m) where
map_attach := by
simp only [Functor.map, MonadAttach.attach, Functor.map_map, WeaklyLawfulMonadAttach.map_attach]
simp only [Functor.map, MonadAttach.attach, Functor.map_map, WeaklyLawfulMonadAttach.map_attach,
MonadAttach.CanReturn]
intros; rfl
public instance [Monad m] [LawfulMonad m] [MonadAttach m] [LawfulMonadAttach m] :
@@ -30,7 +31,7 @@ public instance [Monad m] [LawfulMonad m] [MonadAttach m] [WeaklyLawfulMonadAtta
map_attach := by
intro α x
simp only [Functor.map, StateT, funext_iff, StateT.map, bind_pure_comp, MonadAttach.attach,
Functor.map_map]
Functor.map_map, MonadAttach.CanReturn]
exact fun s => WeaklyLawfulMonadAttach.map_attach
public instance [Monad m] [LawfulMonad m] [MonadAttach m] [LawfulMonadAttach m] :
@@ -45,7 +46,7 @@ public instance [Monad m] [LawfulMonad m] [MonadAttach m] [LawfulMonadAttach m]
public instance [Monad m] [LawfulMonad m] [MonadAttach m] [WeaklyLawfulMonadAttach m] :
WeaklyLawfulMonadAttach (ExceptT ε m) where
map_attach {α} x := by
simp only [Functor.map, MonadAttach.attach, ExceptT.map]
simp only [Functor.map, MonadAttach.attach, ExceptT.map, MonadAttach.CanReturn]
simp
conv => rhs; rw [ WeaklyLawfulMonadAttach.map_attach (m := m) (x := x)]
simp only [map_eq_pure_bind]
@@ -83,6 +84,6 @@ attribute [local instance] MonadAttach.trivial
public instance [Monad m] [LawfulMonad m] :
WeaklyLawfulMonadAttach m where
map_attach := by simp [MonadAttach.attach]
map_attach := by simp [MonadAttach.attach, MonadAttach.CanReturn]
end

View File

@@ -51,8 +51,19 @@ scoped syntax (name := withAnnotateState)
/-- `skip` does nothing. -/
syntax (name := skip) "skip" : conv
/-- `cbv` performs simplification that closely mimics call-by-value evaluation,
using equations associated with definitions and the matchers. -/
/--
`cbv` performs simplification that closely mimics call-by-value evaluation.
It reduces the target term by unfolding definitions using their defining equations and
applying matcher equations. The unfolding is propositional, so `cbv` also works
with functions defined via well-founded recursion or partial fixpoints.
The proofs produced by `cbv` only use the three standard axioms.
In particular, they do not require trust in the correctness of the code
generator.
This tactic is experimental and its behavior is likely to change in upcoming
releases of Lean.
-/
syntax (name := cbv) "cbv" : conv
/--

View File

@@ -489,6 +489,8 @@ class HasEquiv (α : Sort u) where
the notion of equivalence is type-dependent. -/
Equiv : α α Sort v
attribute [reducible] HasEquiv.Equiv
@[inherit_doc] infix:50 "" => HasEquiv.Equiv
recommended_spelling "equiv" for "" in [HasEquiv.Equiv, «term__»]
@@ -2311,6 +2313,13 @@ instance Pi.instSubsingleton {α : Sort u} {β : α → Sort v} [∀ a, Subsingl
/-! # Squash -/
theorem equivalence_true (α : Sort u) : Equivalence fun _ _ : α => True :=
fun _ => trivial, fun _ => trivial, fun _ _ => trivial
/-- Always-true relation as a `Setoid`. -/
protected def Setoid.trivial (α : Sort u) : Setoid α :=
_, equivalence_true α
/--
The quotient of `α` by the universal relation. The elements of `Squash α` are those of `α`, but all
of them are equal and cannot be distinguished.
@@ -2324,8 +2333,11 @@ and its representation in compiled code is identical to that of `α`.
Consequently, `Squash.lift` may extract an `α` value into any subsingleton type `β`, while
`Nonempty.rec` can only do the same when `β` is a proposition.
`Squash` is defined in terms of `Quotient`, so `Squash` can be used when a `Quotient` argument is
expected.
-/
def Squash (α : Sort u) := Quot (fun (_ _ : α) => True)
def Squash (α : Sort u) := Quotient (Setoid.trivial α)
/--
Places a value into its squash type, in which it cannot be distinguished from any other.
@@ -2581,3 +2593,11 @@ class Trichotomous (r : αα → Prop) : Prop where
trichotomous (a b : α) : ¬ r a b ¬ r b a a = b
end Std
@[simp] theorem flip_flip {α : Sort u} {β : Sort v} {φ : Sort w} {f : α β φ} :
flip (flip f) = f := by
apply funext
intro a
apply funext
intro b
rw [flip, flip]

View File

@@ -93,7 +93,7 @@ theorem ext' {xs ys : Array α} (h : xs.toList = ys.toList) : xs = ys := by
@[simp, grind =] theorem getElem?_toList {xs : Array α} {i : Nat} : xs.toList[i]? = xs[i]? := by
simp only [getElem?_def, getElem_toList]
simp only [Array.size]
simp only [Array.size]; rfl
/-- `a ∈ as` is a predicate which asserts that `a` is in the array `as`. -/
-- NB: This is defined as a structure rather than a plain def so that a lemma
@@ -2125,7 +2125,7 @@ Examples:
/-! ### Repr and ToString -/
protected def Array.repr {α : Type u} [Repr α] (xs : Array α) : Std.Format :=
protected def repr {α : Type u} [Repr α] (xs : Array α) : Std.Format :=
let _ : Std.ToFormat α := repr
if xs.size == 0 then
"#[]"

View File

@@ -52,7 +52,9 @@ theorem foldrM_eq_reverse_foldlM_toList.aux [Monad m]
unfold foldrM.fold
match i with
| 0 => simp
| i+1 => rw [ List.take_concat_get h]; simp [ aux]
| i+1 =>
set_option backward.isDefEq.respectTransparency false in
rw [ List.take_concat_get h]; simp [ aux]
theorem foldrM_eq_reverse_foldlM_toList [Monad m] {f : α β m β} {init : β} {xs : Array α} :
xs.foldrM f init = xs.toList.reverse.foldlM (fun x y => f y x) init := by

View File

@@ -117,11 +117,13 @@ grind_pattern Std.Internal.Array.not_of_countP_eq_zero_of_mem => xs.countP p, x
theorem countP_replicate {a : α} {n : Nat} : countP p (replicate n a) = if p a then n else 0 := by
simp [ List.toArray_replicate, List.countP_replicate]
set_option backward.isDefEq.respectTransparency false in
theorem boole_getElem_le_countP {xs : Array α} {i : Nat} (h : i < xs.size) :
(if p xs[i] then 1 else 0) xs.countP p := by
rcases xs with xs
simp [List.boole_getElem_le_countP]
set_option backward.isDefEq.respectTransparency false in
@[grind =]
theorem countP_set {xs : Array α} {i : Nat} {a : α} (h : i < xs.size) :
(xs.set i a).countP p = xs.countP p - (if p xs[i] then 1 else 0) + (if p a then 1 else 0) := by

View File

@@ -76,7 +76,7 @@ theorem isEqv_eq_decide (xs ys : Array α) (r) :
simpa [isEqv_iff_rel] using h'
@[simp, grind =] theorem isEqv_toList [BEq α] (xs ys : Array α) : (xs.toList.isEqv ys.toList r) = (xs.isEqv ys r) := by
simp [isEqv_eq_decide, List.isEqv_eq_decide, Array.size]
simp [isEqv_eq_decide, List.isEqv_eq_decide, Array.size]; rfl
theorem eq_of_isEqv [DecidableEq α] (xs ys : Array α) (h : Array.isEqv xs ys (fun x y => x = y)) : xs = ys := by
have h, h' := rel_of_isEqv h
@@ -87,6 +87,7 @@ private theorem isEqvAux_self (r : αα → Bool) (hr : ∀ a, r a a) (xs :
induction i with
| zero => simp [Array.isEqvAux]
| succ i ih =>
set_option backward.isDefEq.respectTransparency false in
simp_all only [isEqvAux, Bool.and_self]
theorem isEqv_self_beq [BEq α] [ReflBEq α] (xs : Array α) : Array.isEqv xs xs (· == ·) = true := by
@@ -153,7 +154,7 @@ theorem beq_eq_decide [BEq α] (xs ys : Array α) :
simp [BEq.beq, isEqv_eq_decide]
@[simp, grind =] theorem beq_toList [BEq α] (xs ys : Array α) : (xs.toList == ys.toList) = (xs == ys) := by
simp [beq_eq_decide, List.beq_eq_decide, Array.size]
simp [beq_eq_decide, List.beq_eq_decide, Array.size]; rfl
end Array

View File

@@ -329,7 +329,7 @@ theorem eraseIdx_eq_take_drop_succ {xs : Array α} {i : Nat} (h) :
rcases xs with xs
simp only [List.size_toArray] at h
simp only [List.eraseIdx_toArray, List.eraseIdx_eq_take_drop_succ, take_eq_extract,
List.extract_toArray, List.extract_eq_drop_take, Nat.sub_zero, List.drop_zero, drop_eq_extract,
List.extract_toArray, List.extract_eq_take_drop, Nat.sub_zero, List.drop_zero, drop_eq_extract,
List.size_toArray, List.append_toArray, mk.injEq, List.append_cancel_left_eq]
rw [List.take_of_length_le]
simp

View File

@@ -83,6 +83,10 @@ theorem findSome?_eq_some_iff {f : α → Option β} {xs : Array α} {b : β} :
· rintro xs, a, ys, h₀, h₁, h₂
exact xs.toList, a, ys.toList, by simpa using congrArg toList h₀, h₁, by simpa
theorem isSome_findSome? {xs : Array α} {f : α Option β} :
(xs.findSome? f).isSome = xs.any (f · |>.isSome) := by
simp [ findSome?_toList, List.isSome_findSome?]
@[simp, grind =] theorem findSome?_guard {xs : Array α} : findSome? (Option.guard p) xs = find? p xs := by
cases xs; simp
@@ -197,6 +201,10 @@ theorem find?_eq_some_iff_append {xs : Array α} :
exact as.toList, l, by simpa using congrArg Array.toList h',
by simpa using h
theorem isSome_find? {xs : Array α} {f : α Bool} :
(xs.find? f).isSome = xs.any (f ·) := by
simp [ find?_toList, List.isSome_find?]
theorem find?_push {xs : Array α} : (xs.push a).find? p = (xs.find? p).or (if p a then some a else none) := by
cases xs; simp
@@ -425,6 +433,7 @@ theorem lt_findIdx_of_not {p : α → Bool} {xs : Array α} {i : Nat} (h : i < x
simp only [Nat.not_lt] at f
exact absurd (@findIdx_getElem _ p xs (Nat.lt_of_le_of_lt f h)) (h2 (xs.findIdx p) f)
set_option backward.isDefEq.respectTransparency false in
/-- `xs.findIdx p = i` iff `p xs[i]` and `¬ p xs [j]` for all `j < i`. -/
theorem findIdx_eq {p : α Bool} {xs : Array α} {i : Nat} (h : i < xs.size) :
xs.findIdx p = i p xs[i] j (hji : j < i), p (xs[j]'(Nat.lt_trans hji h)) = false := by
@@ -613,12 +622,12 @@ theorem findIdx?_eq_some_le_of_findIdx?_eq_some {xs : Array α} {p q : α → Bo
/-! ### findFinIdx? -/
@[grind =]
theorem findFinIdx?_empty {p : α Bool} : findFinIdx? p #[] = none := by simp
theorem findFinIdx?_empty {p : α Bool} : findFinIdx? p #[] = none := by simp; rfl
@[grind =]
theorem findFinIdx?_singleton {a : α} {p : α Bool} :
#[a].findFinIdx? p = if p a then some 0, by simp else none := by
simp
simp; rfl
-- We can't mark this as a `@[congr]` lemma since the head of the RHS is not `findFinIdx?`.
theorem findFinIdx?_congr {p : α Bool} {xs ys : Array α} (w : xs = ys) :
@@ -714,6 +723,7 @@ theorem findFinIdx?_eq_bind_find?_finIdxOf? [BEq α] [LawfulBEq α] {xs : Array
xs.findFinIdx? p = (xs.find? p).bind (xs.finIdxOf? ·) := by
cases xs
simp [List.findFinIdx?_eq_bind_find?_finIdxOf?]
rfl
theorem findIdx_eq_getD_bind_find?_idxOf? [BEq α] [LawfulBEq α] {xs : Array α} {p : α Bool} :
xs.findIdx p = ((xs.find? p).bind (xs.idxOf? ·)).getD xs.size := by
@@ -792,7 +802,7 @@ theorem idxOf?_eq_map_finIdxOf?_val [BEq α] {xs : Array α} {a : α} :
xs.idxOf? a = (xs.finIdxOf? a).map (·.val) := by
simp [idxOf?, finIdxOf?]
@[grind =] theorem finIdxOf?_empty [BEq α] : (#[] : Array α).finIdxOf? a = none := by simp
@[grind =] theorem finIdxOf?_empty [BEq α] : (#[] : Array α).finIdxOf? a = none := by simp; rfl
@[simp, grind =] theorem finIdxOf?_eq_none_iff [BEq α] [LawfulBEq α] {xs : Array α} {a : α} :
xs.finIdxOf? a = none a xs := by

View File

@@ -6,10 +6,9 @@ Authors: Kim Morrison, Sebastian Graf, Paul Reichert
module
prelude
public import Init.Data.Array.Basic
import Init.Data.Array.Lemmas
public import Init.Data.List.Int.Sum
public import Init.Data.Array.MinMax
import Init.Data.Int.Lemmas
import Init.Data.List.Int.Sum
public section
@@ -31,4 +30,48 @@ theorem sum_reverse_int (xs : Array Int) : xs.reverse.sum = xs.sum := by
theorem sum_eq_foldl_int {xs : Array Int} : xs.sum = xs.foldl (init := 0) (· + ·) := by
simp only [foldl_eq_foldr_reverse, Int.add_comm, sum_eq_foldr, sum_reverse_int]
theorem min_mul_length_le_sum_int {xs : Array Int} (h : xs #[]) :
xs.min h * xs.size xs.sum := by
rcases xs with l
simpa [List.min_toArray, List.sum_toArray] using List.min_mul_length_le_sum_int (by simpa using h)
theorem mul_length_le_sum_of_min?_eq_some_int {xs : Array Int} (h : xs.min? = some x) :
x * xs.size xs.sum := by
rcases xs with l
simpa [List.min?_toArray, List.sum_toArray] using
List.mul_length_le_sum_of_min?_eq_some_int (by simpa using h)
theorem min_le_sum_div_length_int {xs : Array Int} (h : xs #[]) :
xs.min h xs.sum / xs.size := by
rcases xs with l
simpa [List.min_toArray, List.sum_toArray] using List.min_le_sum_div_length_int (by simpa using h)
theorem le_sum_div_length_of_min?_eq_some_int {xs : Array Int} (h : xs.min? = some x) :
x xs.sum / xs.size := by
rcases xs with l
simpa [List.min?_toArray, List.sum_toArray] using
List.le_sum_div_length_of_min?_eq_some_int (by simpa using h)
theorem sum_le_max_mul_length_int {xs : Array Int} (h : xs #[]) :
xs.sum xs.max h * xs.size := by
rcases xs with l
simpa [List.max_toArray, List.sum_toArray] using List.sum_le_max_mul_length_int (by simpa using h)
theorem sum_le_max_mul_length_of_max?_eq_some_int {xs : Array Int} (h : xs.max? = some x) :
xs.sum x * xs.size := by
rcases xs with l
simpa [List.max?_toArray, List.sum_toArray] using
List.sum_le_max_mul_length_of_max?_eq_some_int (by simpa using h)
theorem sum_div_length_le_max_int {xs : Array Int} (h : xs #[]) :
xs.sum / xs.size xs.max h := by
rcases xs with l
simpa [List.max_toArray, List.sum_toArray] using List.sum_div_length_le_max_int (by simpa using h)
theorem sum_div_length_le_max_of_max?_eq_some_int {xs : Array Int} (h : xs.max? = some x) :
xs.sum / xs.size x := by
rcases xs with l
simpa [List.max?_toArray, List.sum_toArray] using
List.sum_div_length_le_max_of_max?_eq_some_int (by simpa using h)
end Array

View File

@@ -170,6 +170,7 @@ theorem getD_getElem? {xs : Array α} {i : Nat} {d : α} :
@[simp] theorem getElem?_empty {i : Nat} : (#[] : Array α)[i]? = none := rfl
set_option backward.isDefEq.respectTransparency false in
theorem getElem_push_lt {xs : Array α} {x : α} {i : Nat} (h : i < xs.size) :
have : i < (xs.push x).size := by simp [*, Nat.lt_succ_of_le, Nat.le_of_lt]
(xs.push x)[i] = xs[i] := by
@@ -895,7 +896,7 @@ theorem all_push {xs : Array α} {a : α} {p : α → Bool} :
@[simp] theorem getElem_set_ne {xs : Array α} {i : Nat} (h' : i < xs.size) {v : α} {j : Nat}
(pj : j < xs.size) (h : i j) :
(xs.set i v)[j]'(by simp [*]) = xs[j] := by
simp only [set, getElem_toList, List.getElem_set_ne h]
simp only [set, getElem_toList, List.getElem_set_ne h]; rfl
@[simp] theorem getElem?_set_ne {xs : Array α} {i : Nat} (h : i < xs.size) {v : α} {j : Nat}
(ne : i j) : (xs.set i v)[j]? = xs[j]? := by
@@ -2854,7 +2855,7 @@ theorem getElem?_extract {xs : Array α} {start stop : Nat} :
· simp only [length_toList, size_extract, List.length_take, List.length_drop]
omega
· intro n h₁ h₂
simp
simp; rfl
@[simp] theorem extract_size {xs : Array α} : xs.extract 0 xs.size = xs := by
apply ext
@@ -3974,6 +3975,7 @@ theorem all_filterMap {xs : Array α} {f : α → Option β} {p : β → Bool} :
· simp only [Id.run_pure]
rw [if_neg (mt (by rintro rfl; exact h) (by simp_all))]
set_option backward.isDefEq.respectTransparency false in
@[simp, grind =] theorem toList_modify {xs : Array α} {f : α α} {i : Nat} :
(xs.modify i f).toList = xs.toList.modify i f := by
apply List.ext_getElem
@@ -4146,7 +4148,7 @@ variable [LawfulBEq α]
(xs.replace a b)[i]? = if xs[i]? == some a then if a xs.take i then some a else some b else xs[i]? := by
rcases xs with xs
simp only [List.replace_toArray, List.getElem?_toArray, List.getElem?_replace, take_eq_extract,
List.extract_toArray, List.extract_eq_drop_take, Nat.sub_zero, List.drop_zero, List.mem_toArray]
List.extract_toArray, List.extract_eq_take_drop, Nat.sub_zero, List.drop_zero, List.mem_toArray]
theorem getElem?_replace_of_ne {xs : Array α} {i : Nat} (h : xs[i]? some a) :
(xs.replace a b)[i]? = xs[i]? := by
@@ -4259,6 +4261,7 @@ private theorem getElem_ofFn_go {f : Fin n → α} {acc i k} (h : i ≤ n) (w₁
· simp
omega
set_option backward.isDefEq.respectTransparency false in
@[simp] theorem getElem_ofFn {f : Fin n α} {i : Nat} (h : i < (ofFn f).size) :
(ofFn f)[i] = f i, size_ofFn (f := f) h := by
unfold ofFn
@@ -4490,11 +4493,13 @@ theorem getElem?_push_eq {xs : Array α} {x : α} : (xs.push x)[xs.size]? = some
cases xs
simp
set_option backward.isDefEq.respectTransparency false in
@[simp, grind =] theorem finIdxOf?_toList [BEq α] {a : α} {xs : Array α} :
xs.toList.finIdxOf? a = (xs.finIdxOf? a).map (Fin.cast (by simp)) := by
cases xs
simp
set_option backward.isDefEq.respectTransparency false in
@[simp, grind =] theorem findFinIdx?_toList {p : α Bool} {xs : Array α} :
xs.toList.findFinIdx? p = (xs.findFinIdx? p).map (Fin.cast (by simp)) := by
cases xs
@@ -4619,6 +4624,7 @@ namespace List
as.toArray.unzip = Prod.map List.toArray List.toArray as.unzip := by
ext1 <;> simp
set_option backward.isDefEq.respectTransparency false in
@[simp, grind =] theorem firstM_toArray [Alternative m] {as : List α} {f : α m β} :
as.toArray.firstM f = as.firstM f := by
unfold Array.firstM

View File

@@ -72,6 +72,7 @@ theorem mapFinIdx_spec {xs : Array α} {f : (i : Nat) → α → (h : i < xs.siz
simp only [getElem?_def, size_mapFinIdx, getElem_mapFinIdx]
split <;> simp_all
set_option backward.isDefEq.respectTransparency false in
@[simp, grind =] theorem toList_mapFinIdx {xs : Array α} {f : (i : Nat) α (h : i < xs.size) β} :
(xs.mapFinIdx f).toList = xs.toList.mapFinIdx (fun i a h => f i a (by simpa)) := by
apply List.ext_getElem <;> simp
@@ -105,6 +106,7 @@ theorem mapIdx_spec {f : Nat → α → β} {xs : Array α}
xs[i]?.map (f i) := by
simp [getElem?_def, size_mapIdx, getElem_mapIdx]
set_option backward.isDefEq.respectTransparency false in
@[simp, grind =] theorem toList_mapIdx {f : Nat α β} {xs : Array α} :
(xs.mapIdx f).toList = xs.toList.mapIdx (fun i a => f i a) := by
apply List.ext_getElem <;> simp

View File

@@ -89,7 +89,7 @@ public theorem _root_.List.min_toArray [Min α] {l : List α} {h} :
· 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]
rw [ Array.foldl_toList, Array.toList_extract, List.extract_eq_take_drop]
simp [List.min]
public theorem _root_.List.min_eq_min_toArray [Min α] {l : List α} {h} :
@@ -129,7 +129,7 @@ public theorem _root_.List.max_toArray [Max α] {l : List α} {h} :
· 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]
rw [ Array.foldl_toList, Array.toList_extract, List.extract_eq_take_drop]
simp [List.max]
public theorem _root_.List.max_eq_max_toArray [Max α] {l : List α} {h} :

View File

@@ -6,10 +6,8 @@ Authors: Kim Morrison, Sebastian Graf, Paul Reichert
module
prelude
public import Init.Data.Array.MinMax
import Init.Data.List.Nat.Sum
public import Init.BinderPredicates
public import Init.Data.Array.Basic
public import Init.NotationExtra
import Init.Data.Array.Lemmas
public section
@@ -39,4 +37,48 @@ theorem sum_reverse_nat (xs : Array Nat) : xs.reverse.sum = xs.sum := by
theorem sum_eq_foldl_nat {xs : Array Nat} : xs.sum = xs.foldl (init := 0) (· + ·) := by
simp only [foldl_eq_foldr_reverse, Nat.add_comm, sum_eq_foldr, sum_reverse_nat]
theorem min_mul_length_le_sum_nat {xs : Array Nat} (h : xs #[]) :
xs.min h * xs.size xs.sum := by
rcases xs with l
simpa [List.min_toArray, List.sum_toArray] using List.min_mul_length_le_sum_nat (by simpa using h)
theorem mul_length_le_sum_of_min?_eq_some_nat {xs : Array Nat} (h : xs.min? = some x) :
x * xs.size xs.sum := by
rcases xs with l
simpa [List.min?_toArray, List.sum_toArray] using
List.mul_length_le_sum_of_min?_eq_some_nat (by simpa using h)
theorem min_le_sum_div_length_nat {xs : Array Nat} (h : xs #[]) :
xs.min h xs.sum / xs.size := by
rcases xs with l
simpa [List.min_toArray, List.sum_toArray] using List.min_le_sum_div_length_nat (by simpa using h)
theorem le_sum_div_length_of_min?_eq_some_nat {xs : Array Nat} (h : xs.min? = some x) :
x xs.sum / xs.size := by
rcases xs with l
simpa [List.min?_toArray, List.sum_toArray] using
List.le_sum_div_length_of_min?_eq_some_nat (by simpa using h)
theorem sum_le_max_mul_length_nat {xs : Array Nat} (h : xs #[]) :
xs.sum xs.max h * xs.size := by
rcases xs with l
simpa [List.max_toArray, List.sum_toArray] using List.sum_le_max_mul_length_nat (by simpa using h)
theorem sum_le_max_mul_length_of_max?_eq_some_nat {xs : Array Nat} (h : xs.max? = some x) :
xs.sum x * xs.size := by
rcases xs with l
simpa [List.max?_toArray, List.sum_toArray] using
List.sum_le_max_mul_length_of_max?_eq_some_nat (by simpa using h)
theorem sum_div_length_le_max_nat {xs : Array Nat} (h : xs #[]) :
xs.sum / xs.size xs.max h := by
rcases xs with l
simpa [List.max_toArray, List.sum_toArray] using List.sum_div_length_le_max_nat (by simpa using h)
theorem sum_div_length_le_max_of_max?_eq_some_nat {xs : Array Nat} (h : xs.max? = some x) :
xs.sum / xs.size x := by
rcases xs with l
simpa [List.max?_toArray, List.sum_toArray] using
List.sum_div_length_le_max_of_max?_eq_some_nat (by simpa using h)
end Array

View File

@@ -41,6 +41,7 @@ theorem ofFn_succ {f : Fin (n+1) → α} :
intro h₃
simp only [show i = n by omega]
set_option backward.isDefEq.respectTransparency false in
theorem ofFn_add {n m} {f : Fin (n + m) α} :
ofFn f = (ofFn (fun i => f (i.castLE (Nat.le_add_right n m)))) ++ (ofFn (fun i => f (i.natAdd n))) := by
induction m with
@@ -107,6 +108,7 @@ theorem ofFnM_succ {n} [Monad m] [LawfulMonad m] {f : Fin (n + 1) → m α} :
pure (as.push a)) := by
simp [ofFnM, Fin.foldlM_succ_last]
set_option backward.isDefEq.respectTransparency false in
theorem ofFnM_add {n m} [Monad m] [LawfulMonad m] {f : Fin (n + k) m α} :
ofFnM f = (do
let as ofFnM fun i : Fin n => f (i.castLE (Nat.le_add_right n k))

View File

@@ -135,7 +135,7 @@ theorem extract {xs ys : Array α} (h : xs ~ ys) {lo hi : Nat}
rcases xs with xs
rcases ys with ys
simp_all only [perm_iff_toList_perm, List.getElem?_toArray, List.extract_toArray,
List.extract_eq_drop_take]
List.extract_eq_take_drop]
apply List.Perm.take_of_getElem? (w := fun i h => by simpa using whi (lo + i) (by omega))
apply List.Perm.drop_of_getElem? (w := wlo)
exact h

View File

@@ -210,7 +210,7 @@ protected def toHex {n : Nat} (x : BitVec n) : String :=
String.Internal.append t s
/-- `BitVec` representation. -/
protected def BitVec.repr (a : BitVec n) : Std.Format :=
protected def repr (a : BitVec n) : Std.Format :=
"0x" ++ (a.toHex : Std.Format) ++ "#" ++ repr n
instance : Repr (BitVec n) where

View File

@@ -2192,6 +2192,7 @@ def uppcRec {w} (x : BitVec w) (s : Nat) (hs : s < w) : Bool :=
| 0 => x.msb
| i + 1 => x[w - 1 - i] || uppcRec x i (by omega)
set_option backward.isDefEq.respectTransparency false in
/-- The unsigned parallel prefix of `x` at `s` is `true` if and only if x interpreted
as a natural number is greater or equal than `2 ^ (w - 1 - (s - 1))`. -/
@[simp]

View File

@@ -1198,7 +1198,7 @@ let x' = x.extractLsb' 7 5 = _ _ 9 8 7
(decide (0 < len) &&
(decide (start + len w) &&
x.getMsbD (w - (start + len)))) := by
simp [BitVec.msb, getMsbD_extractLsb']
simp [BitVec.msb, getMsbD_extractLsb']; rfl
@[simp, grind =] theorem getElem_extract {hi lo : Nat} {x : BitVec n} {i : Nat} (h : i < hi - lo + 1) :
(extractLsb hi lo x)[i] = getLsbD x (lo+i) := by
@@ -1234,7 +1234,7 @@ let x' = x.extractLsb' 7 5 = _ _ 9 8 7
@[simp, grind =] theorem msb_extractLsb {hi lo : Nat} {x : BitVec w} :
(extractLsb hi lo x).msb = (decide (max hi lo < w) && x.getMsbD (w - 1 - max hi lo)) := by
simp [BitVec.msb]
simp [BitVec.msb]; rfl
theorem extractLsb'_eq_extractLsb {w : Nat} (x : BitVec w) (start len : Nat) (h : len > 0) :
x.extractLsb' start len = (x.extractLsb (len - 1 + start) start).cast (by omega) := by
@@ -2581,6 +2581,19 @@ theorem msb_signExtend {x : BitVec w} :
· simp [h, BitVec.msb, getMsbD_signExtend, show v - w = 0 by omega]
· simp [h, BitVec.msb, getMsbD_signExtend, show ¬ (v - w = 0) by omega]
/-- Sign-extending to `w + n` bits, extracting bits `[w - 1 + n..n]`, and setting width
back to `w` is equivalent to arithmetic right shift by `n`, since both sides discard the `n`
least significant bits and replicate the sign bit into the upper bits. -/
@[simp]
theorem signExtend_extractLsb_setWidth {x : BitVec w} {n : Nat} :
((x.signExtend (w + n)).extractLsb (w - 1 + n) n).setWidth w = x.sshiftRight n := by
ext i hi
simp only [getElem_sshiftRight, getElem_setWidth, getLsbD_extract,
Nat.add_sub_cancel, show i w - 1 by omega, decide_true, getLsbD_signExtend,
Bool.true_and]
by_cases hni : n + i < w
<;> (simp [hni]; omega)
/-- Sign extending to a width smaller than the starting width is a truncation. -/
theorem signExtend_eq_setWidth_of_le (x : BitVec w) {v : Nat} (hv : v w) :
x.signExtend v = x.setWidth v := by
@@ -2771,8 +2784,9 @@ theorem msb_append {x : BitVec w} {y : BitVec v} :
@[simp] theorem append_zero_width (x : BitVec w) (y : BitVec 0) : x ++ y = x := by
ext i ih
rw [getElem_append] -- Why does this not work with `simp [getElem_append]`?
simp
simp; rfl
set_option backward.isDefEq.respectTransparency false in
@[grind =]
theorem toInt_append {x : BitVec n} {y : BitVec m} :
(x ++ y).toInt = if n == 0 then y.toInt else (2 ^ m) * x.toInt + y.toNat := by
@@ -5278,6 +5292,7 @@ theorem and_one_eq_setWidth_ofBool_getLsbD {x : BitVec w} :
theorem replicate_zero {x : BitVec w} : x.replicate 0 = 0#0 := by
simp [replicate]
set_option backward.isDefEq.respectTransparency false in
@[simp, grind =]
theorem replicate_one {w : Nat} {x : BitVec w} :
(x.replicate 1) = x.cast (by rw [Nat.mul_one]) := by
@@ -5329,6 +5344,7 @@ theorem append_assoc {x₁ : BitVec w₁} {x₂ : BitVec w₂} {x₃ : BitVec w
theorem append_assoc' {x₁ : BitVec w₁} {x₂ : BitVec w₂} {x₃ : BitVec w₃} :
(x₁ ++ (x₂ ++ x₃)) = ((x₁ ++ x₂) ++ x₃).cast (by omega) := by simp [append_assoc]
set_option backward.isDefEq.respectTransparency false in
theorem replicate_append_self {x : BitVec w} :
x ++ x.replicate n = (x.replicate n ++ x).cast (by omega) := by
induction n with

View File

@@ -111,13 +111,13 @@ theorem getElem_eq_getElem_data {a : ByteArray} {i : Nat} {h : i < a.size} :
theorem getElem_append_left {i : Nat} {a b : ByteArray} {h : i < (a ++ b).size}
(hlt : i < a.size) : (a ++ b)[i] = a[i] := by
simp only [getElem_eq_getElem_data, data_append]
rw [Array.getElem_append_left (by simpa)]
rw [Array.getElem_append_left (by simpa)]; rfl
theorem getElem_append_right {i : Nat} {a b : ByteArray} {h : i < (a ++ b).size}
(hle : a.size i) : (a ++ b)[i] = b[i - a.size]'(by simp_all; omega) := by
simp only [getElem_eq_getElem_data, data_append]
rw [Array.getElem_append_right (by simpa)]
simp
simp; rfl
@[simp]
theorem _root_.List.getElem_toByteArray {l : List UInt8} {i : Nat} {h : i < l.toByteArray.size} :
@@ -223,7 +223,7 @@ theorem getElem_extract_aux {xs : ByteArray} {start stop : Nat} (h : i < (xs.ext
theorem getElem_extract {i : Nat} {b : ByteArray} {start stop : Nat}
(h) : (b.extract start stop)[i]'h = b[start + i]'(getElem_extract_aux h) := by
simp [getElem_eq_getElem_data]
simp [getElem_eq_getElem_data]; rfl
theorem extract_eq_extract_left {a : ByteArray} {i i' j : Nat} :
a.extract i j = a.extract i' j min j a.size - i = min j a.size - i' := by
@@ -236,25 +236,25 @@ theorem extract_add_one {a : ByteArray} {i : Nat} (ha : i + 1 ≤ a.size) :
omega
· rename_i j hj hj'
obtain rfl : j = 0 := by simpa using hj'
simp [ByteArray.getElem_eq_getElem_data]
simp [ByteArray.getElem_eq_getElem_data]; rfl
theorem extract_add_two {a : ByteArray} {i : Nat} (ha : i + 2 a.size) :
a.extract i (i + 2) = [a[i], a[i + 1]].toByteArray := by
rw [extract_eq_extract_append_extract (i + 1) (by simp) (by omega),
extract_add_one (by omega), extract_add_one (by omega)]
simp [ List.toByteArray_append]
simp [ List.toByteArray_append]; rfl
theorem extract_add_three {a : ByteArray} {i : Nat} (ha : i + 3 a.size) :
a.extract i (i + 3) = [a[i], a[i + 1], a[i + 2]].toByteArray := by
rw [extract_eq_extract_append_extract (i + 1) (by simp) (by omega),
extract_add_one (by omega), extract_add_two (by omega)]
simp [ List.toByteArray_append]
simp [ List.toByteArray_append]; rfl
theorem extract_add_four {a : ByteArray} {i : Nat} (ha : i + 4 a.size) :
a.extract i (i + 4) = [a[i], a[i + 1], a[i + 2], a[i + 3]].toByteArray := by
rw [extract_eq_extract_append_extract (i + 1) (by simp) (by omega),
extract_add_one (by omega), extract_add_three (by omega)]
simp [ List.toByteArray_append]
simp [ List.toByteArray_append]; rfl
theorem append_assoc {a b c : ByteArray} : a ++ b ++ c = a ++ (b ++ c) := by
ext1

View File

@@ -163,16 +163,19 @@ The lowercase ASCII letters are the following: `abcdefghijklmnopqrstuvwxyz`.
-/
@[inline]
def toUpper (c : Char) : Char :=
if h : c.val 'a'.val c.val 'z'.val then
if h : 'a'.val c.val c.val 'z'.val then
c.val + ('A'.val - 'a'.val), ?_
else
c
where finally
have h₁ : 2^32 c.val.toNat + ('A'.val - 'a'.val).toNat :=
@Nat.add_le_add 'a'.val.toNat _ (2^32 - 'a'.val.toNat) _ h.1 (by decide)
have h : c.val.toBitVec.toNat + ('A'.val - 'a'.val).toNat < 2^32 + 0xd800 :=
Nat.add_lt_add_right (Nat.lt_of_le_of_lt h.2 (by decide)) _
have add_eq {x y : UInt32} : (x + y).toNat = (x.toNat + y.toNat) % 2^32 := rfl
-- This expression is a ground non-value; generalize for better
-- control on where it is evaluated.
generalize hx : 'A'.val - 'a'.val = x
have h₁ : 2^32 c.val.toNat + x.toNat :=
@Nat.add_le_add 'a'.val.toNat _ (2^32 - 'a'.val.toNat) _ h.1 (by rw [ hx]; decide)
have h₂ : c.val.toBitVec.toNat + x.toNat < 2^32 + 0xd800 :=
Nat.add_lt_of_lt_sub (Nat.lt_of_le_of_lt h.2 (by rw [ hx]; decide))
have add_eq {x y : UInt32} : (x + y).toNat = (x.toNat + y.toNat) % 2^32 := id rfl
replace h₂ := Nat.sub_lt_left_of_lt_add h₁ h₂
exact .inl <| lt_of_eq_of_lt (add_eq.trans (Nat.mod_eq_sub_mod h₁) |>.trans
(Nat.mod_eq_of_lt (Nat.lt_trans h₂ (by decide)))) h₂

View File

@@ -83,6 +83,7 @@ def notLTTotal : Std.Total (¬ · < · : Char → Char → Prop) where
@[simp]
theorem toUInt8_val {c : Char} : c.val.toUInt8 = c.toUInt8 := rfl
@[simp]
theorem toString_eq_singleton {c : Char} : c.toString = String.singleton c := rfl
end Char

View File

@@ -4,7 +4,6 @@ Released under Apache 2.0 license as described in the file LICENSE.
Authors: François G. Dorais
-/
module
prelude
public import Init.Control.Lawful.Basic
public import Init.Ext
@@ -13,7 +12,7 @@ import Init.Data.Nat.Lemmas
import Init.Omega
import Init.TacticsExtra
import Init.WFTactics
import Init.Hints
public section
namespace Fin
@@ -165,7 +164,7 @@ theorem foldlM_add [Monad m] [LawfulMonad m] (f : α → Fin (n + k) → m α) :
simp
| succ k ih =>
funext x
simp [foldlM_succ_last, Nat.add_assoc, ih]
simp [foldlM_succ_last, Nat.add_assoc, ih]; rfl
/-! ### foldrM -/
@@ -223,7 +222,7 @@ theorem foldrM_add [Monad m] [LawfulMonad m] (f : Fin (n + k) → α → m α) :
simp
| succ k ih =>
funext x
simp [foldrM_succ_last, Nat.add_assoc, ih]
simp [foldrM_succ_last, Nat.add_assoc, ih]; rfl
/-! ### foldl -/
@@ -269,7 +268,7 @@ theorem foldl_add (f : α → Fin (n + m) → α) (x) :
(foldl n (fun x i => f x (i.castLE (Nat.le_add_right n m))) x):= by
induction m with
| zero => simp
| succ m ih => simp [foldl_succ_last, ih, Nat.add_assoc]
| succ m ih => simp [foldl_succ_last, ih, Nat.add_assoc]; rfl
theorem foldl_eq_foldlM (f : α Fin n α) (x) :
foldl n f x = (foldlM (m := Id) n (pure <| f · ·) x).run := by
@@ -322,7 +321,7 @@ theorem foldr_add (f : Fin (n + m) → αα) (x) :
(foldr m (fun i => f (i.natAdd n)) x) := by
induction m generalizing x with
| zero => simp
| succ m ih => simp [foldr_succ_last, ih, Nat.add_assoc]
| succ m ih => simp [foldr_succ_last, ih, Nat.add_assoc]; rfl
theorem foldr_eq_foldrM (f : Fin n α α) (x) :
foldr n f x = (foldrM (m := Id) n (pure <| f · ·) x).run := by

View File

@@ -4,14 +4,12 @@ Released under Apache 2.0 license as described in the file LICENSE.
Authors: Joe Hendrix
-/
module
prelude
public import Init.Data.Fin.Basic
import Init.PropLemmas
import Init.WFTactics
import Init.Hints
public section
namespace Fin
/--

View File

@@ -4,7 +4,6 @@ Released under Apache 2.0 license as described in the file LICENSE.
Authors: Mario Carneiro, Leonardo de Moura
-/
module
prelude
public import Init.Ext
public import Init.Data.Nat.Div.Basic
@@ -15,7 +14,7 @@ import Init.Data.Nat.Lemmas
import Init.Data.Nat.Linear
import Init.Omega
import Init.TacticsExtra
import Init.Hints
@[expose] public section
open Std
@@ -998,7 +997,7 @@ For the induction:
@[simp, grind =] theorem reverseInduction_last {n : Nat} {motive : Fin (n + 1) Sort _} {zero succ} :
(reverseInduction zero succ (Fin.last n) : motive (Fin.last n)) = zero := by
rw [reverseInduction, reverseInduction.go]; simp
rw [reverseInduction, reverseInduction.go]; simp; rfl
private theorem reverseInduction_castSucc_aux {n : Nat} {motive : Fin (n + 1) Sort _} {succ}
(i : Fin n) (j : Nat) (h) (h2 : i.1 < j) (zero : motive j, h) :
@@ -1009,9 +1008,9 @@ private theorem reverseInduction_castSucc_aux {n : Nat} {motive : Fin (n + 1)
| succ j ih =>
rw [reverseInduction.go, dif_neg (by exact Nat.ne_of_lt h2)]
by_cases hij : i = j
· subst hij; simp [reverseInduction.go]
dsimp only
rw [ih _ _ (by omega), eq_comm, reverseInduction.go, dif_neg (by change i.1 + 1 _; omega)]
· subst hij; simp [reverseInduction.go]; rfl
· dsimp only
rw [ih _ _ (by omega), eq_comm, reverseInduction.go, dif_neg (by change i.1 + 1 _; omega)]
@[simp, grind =] theorem reverseInduction_castSucc {n : Nat} {motive : Fin (n + 1) Sort _} {zero succ}
(i : Fin n) : reverseInduction (motive := motive) zero succ (castSucc i) =

View File

@@ -7,6 +7,7 @@ module
prelude
public import Init.Data.Ord.Basic
public import Init.Data.Order.ClassesExtra
import all Init.Data.Ord.Basic
import Init.Omega
@@ -73,4 +74,8 @@ protected theorem isGE_compare {a b : Int} :
rw [ Int.compare_swap, Ordering.isGE_swap]
exact Int.isLE_compare
public instance : Std.LawfulOrderOrd Int where
isLE_compare _ _ := Int.isLE_compare
isGE_compare _ _ := Int.isGE_compare
end Int

View File

@@ -149,17 +149,20 @@ protected theorem one_ne_zero : (1 : Int) ≠ 0 := by decide
protected theorem one_nonneg : 0 (1 : Int) := Int.le_of_lt Int.zero_lt_one
protected theorem lt_iff_le_not_le {a b : Int} : a < b a b ¬b a := by
protected theorem lt_iff_le_and_not_ge {a b : Int} : a < b a b ¬b a := by
rw [Int.lt_iff_le_and_ne]
constructor <;> refine fun h, h' => h, h'.imp fun h' => ?_
· exact Int.le_antisymm h h'
· subst h'; apply Int.le_refl
@[deprecated Int.lt_iff_le_and_not_ge (since := "2026-02-11")]
protected def lt_iff_le_not_le := @Int.lt_iff_le_and_not_ge
protected theorem lt_of_not_ge {a b : Int} (h : ¬a b) : b < a :=
Int.lt_iff_le_not_le.mpr (Int.le_total ..).resolve_right h, h
Int.lt_iff_le_and_not_ge.mpr (Int.le_total ..).resolve_right h, h
protected theorem not_le_of_gt {a b : Int} (h : b < a) : ¬a b :=
(Int.lt_iff_le_not_le.mp h).right
(Int.lt_iff_le_and_not_ge.mp h).right
@[simp] protected theorem not_le {a b : Int} : ¬a b b < a :=
Iff.intro Int.lt_of_not_ge Int.not_le_of_gt
@@ -306,6 +309,12 @@ protected theorem le_of_neg_le_neg {a b : Int} (h : -b ≤ -a) : a ≤ b :=
suffices - -a - -b by simp [Int.neg_neg] at this; assumption
Int.neg_le_neg h
protected theorem neg_le_iff {x y : Int} : -x y -y x := by
rw [ Int.neg_neg y, Int.neg_le_neg_iff, Int.neg_neg y]
protected theorem le_neg_iff {x y : Int} : x -y y -x := by
rw [ Int.neg_neg x, Int.neg_le_neg_iff, Int.neg_neg x]
protected theorem neg_nonpos_of_nonneg {a : Int} (h : 0 a) : -a 0 := by
have : -a -0 := Int.neg_le_neg h
rwa [Int.neg_zero] at this
@@ -328,6 +337,12 @@ protected theorem neg_lt_neg {a b : Int} (h : a < b) : -b < -a := by
@[simp] protected theorem zero_lt_neg_iff {a : Int} : 0 < -a a < 0 := by
rw [ Int.neg_zero, Int.neg_lt_neg_iff, Int.neg_zero]
protected theorem neg_lt_iff {x y : Int} : -x < y -y < x := by
rw [ Int.neg_neg y, Int.neg_lt_neg_iff, Int.neg_neg y]
protected theorem lt_neg_iff {x y : Int} : x < -y y < -x := by
rw [ Int.neg_neg x, Int.neg_lt_neg_iff, Int.neg_neg x]
protected theorem neg_neg_of_pos {a : Int} (h : 0 < a) : -a < 0 :=
Int.neg_lt_zero_iff.2 h
@@ -541,7 +556,7 @@ protected theorem mul_le_mul_of_nonneg_left {a b c : Int}
simp [Int.le_antisymm hc0 h₂, Int.zero_mul]
else by
exact Int.le_of_lt <| Int.mul_lt_mul_of_pos_left
(Int.lt_iff_le_not_le.2 h₁, hba) (Int.lt_iff_le_not_le.2 h₂, hc0)
(Int.lt_iff_le_and_not_ge.2 h₁, hba) (Int.lt_iff_le_and_not_ge.2 h₂, hc0)
protected theorem mul_le_mul_of_nonneg_right {a b c : Int}
(h₁ : a b) (h₂ : 0 c) : a * c b * c := by

View File

@@ -750,6 +750,7 @@ theorem Iter.anyM_filterMapM {α β β' : Type w} {m : Type w → Type w'}
simp only [filterMapM_eq_toIter_filterMapM_toIterM, IterM.anyM_filterMapM]
rfl
set_option backward.isDefEq.respectTransparency false in
-- There is hope to generalize the following theorem as soon there is a `Shrink` type.
/--
This lemma expresses `Iter.anyM` in terms of `IterM.anyM`.

View File

@@ -232,6 +232,7 @@ public theorem Iter.toArray_flatMapM {α α₂ β γ : Type w} {m : Type w → T
(it₁.flatMapM f).toArray = Array.flatten <$> (it₁.mapM fun b => do ( f b).toArray).toArray := by
simp [flatMapM, toArray_flatMapAfterM]
set_option backward.isDefEq.respectTransparency false in
public theorem Iter.toList_flatMapAfter {α α₂ β γ : Type w} [Iterator α Id β] [Iterator α₂ Id γ]
[Finite α Id] [Finite α₂ Id]
{f : β Iter (α := α₂) γ} {it₁ : Iter (α := α) β} {it₂ : Option (Iter (α := α₂) γ)} :
@@ -242,6 +243,7 @@ public theorem Iter.toList_flatMapAfter {α α₂ β γ : Type w} [Iterator α I
simp only [flatMapAfter, Iter.toList, toIterM_toIter, IterM.toList_flatMapAfter]
cases it₂ <;> simp [map, IterM.toList_map_eq_toList_mapM, - IterM.toList_map]
set_option backward.isDefEq.respectTransparency false in
public theorem Iter.toArray_flatMapAfter {α α₂ β γ : Type w} [Iterator α Id β] [Iterator α₂ Id γ]
[Finite α Id] [Finite α₂ Id]
{f : β Iter (α := α₂) γ} {it₁ : Iter (α := α) β} {it₂ : Option (Iter (α := α₂) γ)} :

View File

@@ -600,6 +600,7 @@ theorem IterM.toList_map_mapM {α β γ δ : Type w}
toList_filterMapM_mapM]
congr <;> simp
set_option backward.isDefEq.respectTransparency false in
@[simp]
theorem IterM.toList_filterMapWithPostcondition {α β γ : Type w} {m : Type w Type w'}
[Monad m] [LawfulMonad m]
@@ -623,6 +624,7 @@ theorem IterM.toList_filterMapWithPostcondition {α β γ : Type w} {m : Type w
· simp [ihs _, heq]
· simp [heq]
set_option backward.isDefEq.respectTransparency false in
@[simp]
theorem IterM.toList_mapWithPostcondition {α β γ : Type w} {m : Type w Type w'}
[Monad m] [LawfulMonad m] [Iterator α Id β] [Finite α Id]
@@ -643,6 +645,7 @@ theorem IterM.toList_mapWithPostcondition {α β γ : Type w} {m : Type w → Ty
· simp [ihs _, heq]
· simp [heq]
set_option backward.isDefEq.respectTransparency false in
@[simp]
theorem IterM.toList_filterMapM {α β γ : Type w} {m : Type w Type w'}
[Monad m] [MonadAttach m] [LawfulMonad m] [WeaklyLawfulMonadAttach m]
@@ -652,6 +655,7 @@ theorem IterM.toList_filterMapM {α β γ : Type w} {m : Type w → Type w'}
simp [toList_filterMapM_eq_toList_filterMapWithPostcondition, toList_filterMapWithPostcondition,
PostconditionT.attachLift, PostconditionT.run_eq_map, WeaklyLawfulMonadAttach.map_attach]
set_option backward.isDefEq.respectTransparency false in
@[simp]
theorem IterM.toList_mapM {α β γ : Type w} {m : Type w Type w'}
[Monad m] [MonadAttach m] [LawfulMonad m] [WeaklyLawfulMonadAttach m]
@@ -1297,6 +1301,7 @@ theorem IterM.forIn_filterMap
rw [filterMap, forIn_filterMapWithPostcondition]
simp [PostconditionT.run_eq_map]
set_option backward.isDefEq.respectTransparency false in
theorem IterM.forIn_mapWithPostcondition
[Monad m] [LawfulMonad m] [Monad n] [LawfulMonad n] [Monad o] [LawfulMonad o]
[MonadLiftT m n] [LawfulMonadLiftT m n] [MonadLiftT n o] [LawfulMonadLiftT n o]

View File

@@ -36,7 +36,7 @@ theorem IterM.step_flattenAfter {α α₂ β : Type w} {m : Type w → Type w'}
cases it₂
all_goals
· apply bind_congr; intro step
cases step.inflate using PlausibleIterStep.casesOn <;> simp [IterM.flattenAfter]
cases step.inflate using PlausibleIterStep.casesOn <;> simp [IterM.flattenAfter] <;> rfl
namespace Iterators.Types

View File

@@ -29,7 +29,7 @@ theorem IterM.step_uLift [Iterator α m β] [Monad n] {it : IterM (α := α) m
| .done h => return .deflate (.done _, h, rfl)) := by
simp only [IterM.step, Iterator.step, IterM.uLift]
apply bind_congr; intro step
split <;> simp [Types.ULiftIterator.Monadic.modifyStep, *]
split <;> simp [Types.ULiftIterator.Monadic.modifyStep, *] <;> rfl
@[simp]
theorem IterM.toList_uLift [Iterator α m β] [Monad m] [Monad n] {it : IterM (α := α) m β}

View File

@@ -26,6 +26,7 @@ theorem Iter.uLift_eq_toIter_uLift_toIterM {it : Iter (α := α) β} :
it.uLift = (it.toIterM.uLift Id).toIter :=
rfl
set_option backward.isDefEq.respectTransparency false in
theorem Iter.step_uLift [Iterator α Id β] {it : Iter (α := α) β} :
it.uLift.step = match it.step with
| .yield it' out h => .yield it'.uLift (.up out) _, h, rfl
@@ -38,6 +39,7 @@ theorem Iter.step_uLift [Iterator α Id β] {it : Iter (α := α) β} :
PlausibleIterStep.done, pure_bind]
cases it.toIterM.step.run.inflate using PlausibleIterStep.casesOn <;> simp
set_option backward.isDefEq.respectTransparency false in
@[simp]
theorem Iter.toList_uLift [Iterator α Id β] {it : Iter (α := α) β}
[Finite α Id] :
@@ -59,6 +61,7 @@ theorem Iter.toArray_uLift [Iterator α Id β] {it : Iter (α := α) β}
rw [ toArray_toList, toArray_toList, toList_uLift]
simp [-toArray_toList]
set_option backward.isDefEq.respectTransparency false in
@[simp]
theorem Iter.length_uLift [Iterator α Id β] {it : Iter (α := α) β}
[Finite α Id] [IteratorLoop α Id Id] [LawfulIteratorLoop α Id Id] :

View File

@@ -59,6 +59,12 @@ theorem Iter.toListRev_ensureTermination_eq_toListRev {α β} [Iterator α Id β
{it : Iter (α := α) β} : it.ensureTermination.toListRev = it.toListRev :=
(rfl)
@[simp]
theorem IterM.toArray_toIter {α β} [Iterator α Id β] [Finite α Id]
{it : IterM (α := α) Id β} :
it.toIter.toArray = it.toArray.run :=
(rfl)
@[simp]
theorem IterM.toList_toIter {α β} [Iterator α Id β] [Finite α Id]
{it : IterM (α := α) Id β} :

View File

@@ -398,7 +398,7 @@ theorem Iter.fold_eq_fold_toIterM {α β : Type w} {γ : Type w} [Iterator α Id
[Finite α Id] [IteratorLoop α Id Id]
{f : γ β γ} {init : γ} {it : Iter (α := α) β} :
it.fold (init := init) f = (it.toIterM.fold (init := init) f).run := by
rw [fold_eq_foldM, foldM_eq_foldM_toIterM, IterM.fold_eq_foldM]
rw [fold_eq_foldM, foldM_eq_foldM_toIterM, IterM.fold_eq_foldM]; rfl
@[simp]
theorem Iter.forIn_pure_yield_eq_fold {α β : Type w} {γ : Type x} [Iterator α Id β]

View File

@@ -23,6 +23,7 @@ open Std Std.Iterators
variable {β : Type w}
set_option backward.isDefEq.respectTransparency false in
@[simp]
theorem List.step_iter_nil :
(([] : List β).iter).step = .done, rfl := by
@@ -31,7 +32,7 @@ theorem List.step_iter_nil :
@[simp]
theorem List.step_iter_cons {x : β} {xs : List β} :
((x :: xs).iter).step = .yield xs.iter x, rfl := by
simp [List.iter, List.iterM, IterM.toIter, Iter.step_eq]
simp [List.iter, List.iterM, IterM.toIter, Iter.step_eq]; rfl
@[simp, grind =]
theorem List.toArray_iter {l : List β} :

View File

@@ -31,7 +31,7 @@ theorem List.step_iterM_nil :
@[simp]
theorem List.step_iterM_cons {x : β} {xs : List β} :
((x :: xs).iterM m).step = pure (.deflate .yield (xs.iterM m) x, rfl) := by
simp only [List.iterM, IterM.step, Iterator.step]
simp only [List.iterM, IterM.step, Iterator.step]; rfl
theorem List.step_iterM {l : List β} :
(l.iterM m).step = match l with

View File

@@ -35,3 +35,4 @@ public import Init.Data.List.OfFn
public import Init.Data.List.FinRange
public import Init.Data.List.Lex
public import Init.Data.List.Range
public import Init.Data.List.Scan

View File

@@ -955,9 +955,13 @@ Examples:
abbrev extract (l : List α) (start : Nat := 0) (stop : Nat := l.length) : List α :=
(l.drop start).take (stop - start)
@[simp] theorem extract_eq_drop_take {l : List α} {start stop : Nat} :
@[simp] theorem extract_eq_take_drop {l : List α} {start stop : Nat} :
l.extract start stop = (l.drop start).take (stop - start) := rfl
set_option linter.missingDocs false in
@[deprecated extract_eq_take_drop (since := "2026-02-06")]
def extract_eq_drop_take := @extract_eq_take_drop
/-! ### takeWhile -/
/--

View File

@@ -132,7 +132,9 @@ theorem boole_getElem_le_countP {p : α → Bool} {l : List α} {i : Nat} (h : i
| nil => simp at h
| cons x l ih =>
cases i with
| zero => simp [countP_cons]
| zero =>
set_option backward.isDefEq.respectTransparency false in
simp [countP_cons]
| succ i =>
simp only [length_cons, add_one_lt_add_one_iff] at h
simp only [getElem_cons_succ, countP_cons]
@@ -263,7 +265,9 @@ theorem count_eq_length_filter {a : α} {l : List α} : count a l = (filter (·
theorem count_tail : {l : List α} {a : α},
l.tail.count a = l.count a - if l.head? == some a then 1 else 0
| [], a => by simp
| _ :: _, a => by simp [count_cons]
| _ :: _, a => by
set_option backward.isDefEq.respectTransparency false in
simp [count_cons]
theorem count_le_length {a : α} {l : List α} : count a l l.length := countP_le_length

View File

@@ -97,6 +97,12 @@ theorem findSome?_eq_some_iff {f : α → Option β} {l : List α} {b : β} :
obtain rfl, rfl, rfl := h₁
exact l₁, a, l₂, rfl, h₂, fun a' w => h₃ a' (mem_cons_of_mem p w)
theorem isSome_findSome? {xs : List α} {f : α Option β} :
(xs.findSome? f).isSome = xs.any (f · |>.isSome) := by
rw [Bool.eq_iff_iff]
simp only [Option.isSome_iff_ne_none, ne_eq, findSome?_eq_none_iff, Classical.not_forall]
simp [ Option.isSome_iff_ne_none]
@[simp, grind =] theorem findSome?_guard {l : List α} : findSome? (Option.guard p) l = find? p l := by
induction l with
| nil => simp
@@ -270,6 +276,11 @@ theorem find?_eq_some_iff_append :
cases h₁
simp
theorem isSome_find? {xs : List α} {f : α Bool} :
(xs.find? f).isSome = xs.any (f ·) := by
rw [Bool.eq_iff_iff]
simp [Option.isSome_iff_ne_none, ne_eq, find?_eq_none, Classical.not_forall]
@[simp]
theorem find?_cons_eq_some : (a :: xs).find? p = some b (p a a = b) (!p a xs.find? p = some b) := by
rw [find?_cons]
@@ -654,6 +665,7 @@ theorem lt_findIdx_of_not {p : α → Bool} {xs : List α} {i : Nat} (h : i < xs
simp only [Nat.not_lt] at f
exact absurd (@findIdx_getElem _ p xs (Nat.lt_of_le_of_lt f h)) (h2 (xs.findIdx p) f)
set_option backward.isDefEq.respectTransparency false in
/-- `xs.findIdx p = i` iff `p xs[i]` and `¬ p xs [j]` for all `j < i`. -/
theorem findIdx_eq {p : α Bool} {xs : List α} {i : Nat} (h : i < xs.length) :
xs.findIdx p = i p xs[i] j (hji : j < i), p (xs[j]'(Nat.lt_trans hji h)) = false := by
@@ -1038,7 +1050,7 @@ theorem findFinIdx?_append {xs ys : List α} {p : α → Bool} :
@[simp, grind =] theorem findFinIdx?_singleton {a : α} {p : α Bool} :
[a].findFinIdx? p = if p a then some 0, by simp else none := by
simp [findFinIdx?_cons, findFinIdx?_nil]
simp [findFinIdx?_cons, findFinIdx?_nil]; rfl
@[simp, grind =] theorem findFinIdx?_eq_none_iff {l : List α} {p : α Bool} :
l.findFinIdx? p = none x l, ¬ p x := by
@@ -1080,6 +1092,7 @@ theorem isNone_findFinIdx? {l : List α} {p : α → Bool} :
induction l with
| nil => simp
| cons a l ih =>
set_option backward.isDefEq.respectTransparency false in
simp [hf, findFinIdx?_cons]
split <;> simp [ih, Function.comp_def]

View File

@@ -2542,6 +2542,9 @@ grind_pattern flatMap_reverse => l.reverse.flatMap f where
by rw [length_reverse, length_replicate],
fun _ h => eq_of_mem_replicate (mem_reverse.1 h)
theorem reverse_singleton {a : α} : [a].reverse = [a] := by
simp
@[simp]
theorem append_singleton_inj {as bs : List α} : as ++ [a] = bs ++ [b] as = bs a = b := by
rw [ List.reverse_inj, And.comm]; simp
@@ -3522,7 +3525,7 @@ theorem getElem?_insert_succ {l : List α} {a : α} {i : Nat} :
· split
· rfl
· have h' : i - 1 < l.length := Nat.lt_of_le_of_lt (Nat.pred_le _) h
simp [h']
simp [h']; rfl
theorem head?_insert {l : List α} {a : α} :
(l.insert a).head? = some (if h : a l then l.head (ne_nil_of_mem h) else a) := by
@@ -3645,6 +3648,40 @@ theorem eraseDups_append [BEq α] [LawfulBEq α] {as bs : List α} :
simp [removeAll_cons]
termination_by as.length
/-- Loop invariant for `eraseDupsBy.loop`: membership in the result equals
membership in the remaining list or the accumulator. -/
private theorem mem_eraseDupsBy_loop [BEq α] [LawfulBEq α] {a : α} {l acc : List α} :
a eraseDupsBy.loop (· == ·) l acc a l a acc := by
induction l generalizing acc with
| nil => simp [eraseDupsBy.loop]
| cons x xs ih =>
unfold eraseDupsBy.loop; split
· next h =>
rw [ih]; simp only [mem_cons]
apply Iff.intro (fun
| .inl hxs => Or.inl (Or.inr hxs)
| .inr hacc => Or.inr hacc) (fun
| .inl (.inl rfl) =>
have y, hy, heq := any_eq_true.mp h
.inr (LawfulBEq.eq_of_beq heq hy)
| .inl (.inr hxs) => .inl hxs
| .inr hacc => .inr hacc)
· rw [ih]; simp only [mem_cons]
apply Iff.intro (fun
| .inl hxs => Or.inl (Or.inr hxs)
| .inr (.inl rfl) => Or.inl (Or.inl rfl)
| .inr (.inr hacc) => Or.inr hacc) (fun
| .inl (.inl rfl) => Or.inr (Or.inl rfl)
| .inl (.inr hxs) => .inl hxs
| .inr hacc => Or.inr (Or.inr hacc))
/-- Membership is preserved by `eraseDups`: an element is in the deduplicated list
iff it was in the original list. -/
@[simp]
theorem mem_eraseDups [BEq α] [LawfulBEq α] {a : α} {l : List α} :
a l.eraseDups a l := by
simp only [eraseDups, eraseDupsBy, mem_eraseDupsBy_loop, not_mem_nil, or_false]
/-! ### Legacy lemmas about `get`, `get?`, and `get!`.
Hopefully these should not be needed, in favour of lemmas about `xs[i]`, `xs[i]?`, and `xs[i]!`,
@@ -3676,11 +3713,13 @@ theorem get_of_eq {l l' : List α} (h : l = l') (i : Fin l.length) :
theorem getElem!_nil [Inhabited α] {n : Nat} : ([] : List α)[n]! = default := rfl
theorem getElem!_cons_zero [Inhabited α] {l : List α} : (a::l)[0]! = a := by
rw [getElem!_pos] <;> simp
rw [getElem!_pos]; rfl; simp
theorem getElem!_cons_succ [Inhabited α] {l : List α} : (a::l)[i+1]! = l[i]! := by
by_cases h : i < l.length
· rw [getElem!_pos, getElem!_pos] <;> simp_all [Nat.succ_lt_succ_iff]
· rw [getElem!_pos, getElem!_pos]
· rfl
· simp; apply Nat.succ_lt_succ; assumption
· rw [getElem!_neg, getElem!_neg] <;> simp_all [Nat.succ_lt_succ_iff]
theorem getElem!_of_getElem? [Inhabited α] : {l : List α} {i : Nat}, l[i]? = some a l[i]! = a

View File

@@ -350,6 +350,7 @@ theorem getElem?_mapIdx_go : ∀ {l : List α} {acc : Array β} {i : Nat},
| [], acc, i => by
simp only [mapIdx.go, getElem?_def, Array.length_toList,
Array.getElem_toList, length_nil, Nat.not_lt_zero, reduceDIte, Option.map_none]
rfl
| a :: l, acc, i => by
rw [mapIdx.go, getElem?_mapIdx_go]
simp only [Array.size_push]

View File

@@ -410,6 +410,7 @@ private theorem minIdxOn_append_aux [LE β] [DecidableLE β]
match xs with
| [] => simp [minIdxOn_cons_aux (xs := ys) _]
| z :: zs =>
set_option backward.isDefEq.respectTransparency false in
simp +singlePass only [cons_append]
simp only [minIdxOn_cons_aux (xs := z :: zs ++ ys) (by simp), ih (by simp),
minIdxOn_cons_aux (xs := z :: zs) (by simp), combineMinIdxOn_assoc]

View File

@@ -25,6 +25,7 @@ namespace List
open Nat
set_option backward.isDefEq.respectTransparency false in
@[grind =]
theorem countP_set {p : α Bool} {l : List α} {i : Nat} {a : α} (h : i < l.length) :
(l.set i a).countP p = l.countP p - (if p l[i] then 1 else 0) + (if p a then 1 else 0) := by

View File

@@ -53,10 +53,12 @@ theorem sublist_eq_map_getElem {l l' : List α} (h : l' <+ l) : ∃ is : List (F
| cons _ _ IH =>
let is, IH := IH
refine is.map (·.succ), ?_
set_option backward.isDefEq.respectTransparency false in
simpa [Function.comp_def, pairwise_map]
| cons₂ _ _ IH =>
rcases IH with is,IH
refine 0, by simp [Nat.zero_lt_succ] :: is.map (·.succ), ?_
set_option backward.isDefEq.respectTransparency false in
simp [Function.comp_def, pairwise_map, IH, get_eq_getElem, get_cons_zero, get_cons_succ']
set_option linter.listVariables false in

View File

@@ -387,6 +387,22 @@ theorem drop_take : ∀ {i j : Nat} {l : List α}, drop i (take j l) = take (j -
rw [drop_take]
simp
set_option doc.verso true in
/--
This lemma will be renamed to {lit}`List.extract_eq_drop_take` as soon as the current deprecated
lemma {name}`List.extract_eq_drop_take` has been removed.
-/
theorem extract_eq_drop_take' {l : List α} {start stop : Nat} :
l.extract start stop = (l.take stop).drop start := by
simp only [take_drop]
by_cases start stop
· rw [add_sub_of_le _]
· have h₁ : stop - start = 0 := by omega
have h₂ : min stop l.length stop := by omega
simp only [Nat.add_zero, List.drop_take_self, List.nil_eq, List.drop_eq_nil_iff,
List.length_take, ge_iff_le, h₁]
omega
@[simp]
theorem drop_eq_drop_iff :
{l : List α} {i j : Nat}, l.drop i = l.drop j min i l.length = min j l.length

View File

@@ -98,7 +98,9 @@ theorem ofFn_add {n m} {f : Fin (n + m) → α} :
ofFn f = (ofFn fun i => f (i.castLE (Nat.le_add_right n m))) ++ (ofFn fun i => f (i.natAdd n)) := by
induction m with
| zero => simp
| succ m ih => simp [-ofFn_succ, ofFn_succ_last, ih]
| succ m ih =>
set_option backward.isDefEq.respectTransparency false in
simp [-ofFn_succ, ofFn_succ_last, ih]
@[simp]
theorem ofFn_eq_nil_iff {f : Fin n α} : ofFn f = [] n = 0 := by
@@ -154,8 +156,9 @@ theorem ofFnM_add {n m} [Monad m] [LawfulMonad m] {f : Fin (n + k) → m α} :
pure (as ++ bs)) := by
induction k with
| zero => simp
| succ k ih => simp [ofFnM_succ_last, ih]
| succ k ih =>
set_option backward.isDefEq.respectTransparency false in
simp [ofFnM_succ_last, ih]
end List

View File

@@ -0,0 +1,10 @@
/-
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.List.Scan.Basic
public import Init.Data.List.Scan.Lemmas

View File

@@ -0,0 +1,62 @@
/-
Copyright (c) 2026 Lean FRO, LLC. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Mario Carneiro, Chad Sharp
-/
module
prelude
public import Init.Data.List.Basic
public import Init.Control.Id
public section
namespace List
/-- Tail-recursive helper function for `scanlM` and `scanrM` -/
@[inline]
private def scanAuxM [Monad m] (f : β α m β) (init : β) (l : List α) : m (List β) :=
go l init []
where
/-- Auxiliary for `scanAuxM` -/
@[specialize] go : List α β List β m (List β)
| [], last, acc => pure <| last :: acc
| x :: xs, last, acc => do go xs ( f last x) (last :: acc)
/--
Folds a monadic function over a list from the left, accumulating partial results starting with
`init`. The accumulated values are combined with the each element of the list in order, using `f`.
-/
@[inline]
def scanlM [Monad m] (f : β α m β) (init : β) (l : List α) : m (List β) :=
List.reverse <$> scanAuxM f init l
/--
Folds a monadic function over a list from the right, accumulating partial results starting with
`init`. The accumulated values are combined with the each element of the list in order, using `f`.
-/
@[inline]
def scanrM [Monad m] (f : α β m β) (init : β) (xs : List α) : m (List β) :=
scanAuxM (flip f) init xs.reverse
/--
Fold a function `f` over the list from the left, returning the list of partial results.
```
scanl (+) 0 [1, 2, 3] = [0, 1, 3, 6]
```
-/
@[inline]
def scanl (f : β α β) (init : β) (as : List α) : List β :=
Id.run <| as.scanlM (pure <| f · ·) init
/--
Fold a function `f` over the list from the right, returning the list of partial results.
```
scanr (+) 0 [1, 2, 3] = [6, 5, 3, 0]
```
-/
@[inline]
def scanr (f : α β β) (init : β) (as : List α) : List β :=
Id.run <| as.scanrM (pure <| f · ·) init
end List

View File

@@ -0,0 +1,339 @@
/-
Copyright (c) 2026 Lean FRO, LLC. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Parikshit Khanna, Jeremy Avigad, Leonardo de Moura, Floris van Doorn, Mario Carneiro, Chad Sharp
-/
module
prelude
public import Init.Data.List.Scan.Basic
public import Init.Data.List.Lemmas
import all Init.Data.List.Scan.Basic
import Init.Data.List.TakeDrop
import Init.Data.Option.Lemmas
import Init.Data.Nat.Lemmas
public section
/-!
# List scan
Prove basic results about `List.scanl`, `List.scanr`, `List.scanlM` and `List.scanrM`.
-/
namespace List
/-! ### `List.scanlM` and `List.scanrM` -/
@[local simp]
private theorem scanAuxM.go_eq_append_map [Monad m] [LawfulMonad m] {f : α β m α} :
go f xs last acc = (· ++ acc) <$> scanAuxM f last xs := by
unfold scanAuxM
induction xs generalizing last acc with
| nil => simp [scanAuxM.go]
| cons _ _ ih => simp [scanAuxM.go, ih (acc := last :: acc), ih (acc := [last])]
private theorem scanAuxM_nil [Monad m] {f : α β m α} :
scanAuxM f init [] = return [init] := rfl
private theorem scanAuxM_cons [Monad m] [LawfulMonad m] {f : α β m α} :
scanAuxM f init (x :: xs) = return ( scanAuxM f ( f init x) xs) ++ [init] := by
rw [scanAuxM, scanAuxM.go]
simp
@[simp, grind =]
theorem scanlM_nil [Monad m] [LawfulMonad m] {f : α β m α} :
scanlM f init [] = return [init] := by
simp [scanlM, scanAuxM_nil]
@[simp, grind =]
theorem scanlM_cons [Monad m] [LawfulMonad m] {f : α β m α} :
scanlM f init (x :: xs) = return init :: ( scanlM f ( f init x) xs) := by
simp [scanlM, scanAuxM_cons]
@[simp, grind =]
theorem scanrM_concat [Monad m] [LawfulMonad m] {f : α β m β} :
scanrM f init (xs ++ [x]) = return ( scanrM f ( f x init) xs) ++ [init] := by
simp [scanrM, flip, scanAuxM_cons]
@[simp, grind =]
theorem scanrM_nil [Monad m] {f : α β m β} :
scanrM f init [] = return [init] :=
(rfl)
theorem scanlM_eq_scanrM_reverse [Monad m] {f : β α m β} :
scanlM f init as = reverse <$> (scanrM (flip f) init as.reverse) := by
simp only [scanrM, reverse_reverse]
rfl
theorem scanrM_eq_scanlM_reverse [Monad m] [LawfulMonad m] {f : α β m β} :
scanrM f init as = reverse <$> (scanlM (flip f) init as.reverse) := by
simp only [scanlM_eq_scanrM_reverse, reverse_reverse, id_map', Functor.map_map]
rfl
@[simp, grind =]
theorem scanrM_reverse [Monad m] [LawfulMonad m] {f : α β m β} :
scanrM f init as.reverse = reverse <$> (scanlM (flip f) init as) := by
simp [scanrM_eq_scanlM_reverse (as := as.reverse)]
@[simp, grind =]
theorem scanlM_reverse [Monad m] {f : β α m β} :
scanlM f init as.reverse = reverse <$> (scanrM (flip f) init as) := by
simp [scanlM_eq_scanrM_reverse (as := as.reverse)]
theorem scanlM_pure [Monad m] [LawfulMonad m] {f: β α β} {as : List α} :
as.scanlM (m := m) (pure <| f · ·) init = pure (as.scanl f init) := by
induction as generalizing init with simp_all [scanlM_cons, scanl]
theorem scanrM_pure [Monad m] [LawfulMonad m] {f : α β β} {as : List α} :
as.scanrM (m := m) (pure <| f · · ) init = pure (as.scanr f init) := by
simp only [scanrM_eq_scanlM_reverse]
unfold flip
simp only [scanlM_pure, map_pure, scanr, scanrM_eq_scanlM_reverse]
rfl
theorem idRun_scanlM {f : β α Id β} {as : List α} :
(as.scanlM f init).run = as.scanl (f · · |>.run) init :=
scanlM_pure
theorem idRun_scanrM {f : α β Id β} {as : List α} :
(as.scanrM f init).run = as.scanr (f · · |>.run) init :=
scanrM_pure
@[simp, grind =]
theorem scanlM_map [Monad m] [LawfulMonad m]
{f : α₁ α₂} {g: β α₂ m β} {as : List α₁} :
(as.map f).scanlM g init = as.scanlM (g · <| f ·) init := by
induction as generalizing g init with simp [*]
@[simp, grind =]
theorem scanrM_map [Monad m] [LawfulMonad m]
{f : α₁ α₂} {g: α₂ β m β} {as : List α₁} :
(as.map f).scanrM g init = as.scanrM (fun a b => g (f a) b) init := by
simp only [ map_reverse, scanlM_map, scanrM_eq_scanlM_reverse]
rfl
/-! ### `List.scanl` and `List.scanr` -/
@[simp]
theorem length_scanl {f : β α β} : (scanl f init as).length = as.length + 1 := by
induction as generalizing init <;> simp_all [scanl, pure, bind, Id.run]
grind_pattern length_scanl => scanl f init as
@[simp, grind =]
theorem scanl_nil {f : β α β} : scanl f init [] = [init] := by simp [scanl]
@[simp, grind =]
theorem scanl_cons {f : β α β} : scanl f b (a :: l) = b :: scanl f (f b a) l := by
simp [scanl]
theorem scanl_singleton {f : β α β} : scanl f b [a] = [b, f b a] := by simp
@[simp]
theorem scanl_ne_nil {f : β α β} : scanl f b l [] := by
cases l <;> simp
@[simp]
theorem scanl_iff_nil {f : β α β} (c : β) : scanl f b l = [c] c = b l = [] := by
cases l
· simp [eq_comm]
· simp
@[simp, grind =]
theorem getElem_scanl {f : α β α} (h : i < (scanl f a l).length) :
(scanl f a l)[i] = foldl f a (l.take i) := by
induction l generalizing a i
· simp
· cases i <;> simp [*]
@[grind =]
theorem getElem?_scanl {f : α β α} :
(scanl f a l)[i]? = if i l.length then some (foldl f a (l.take i)) else none := by
split
· rw [getElem?_pos _ _ (by simpa using Nat.lt_add_one_iff.mpr _), getElem_scanl]
· rw [getElem?_neg]
simpa only [length_scanl, Nat.lt_add_one_iff]
@[grind _=_]
theorem take_scanl {f : β α β} (init : β) (as : List α) (i : Nat) :
(scanl f init as).take (i + 1) = scanl f init (as.take i) := by
induction as generalizing init i
· simp
· cases i
· simp
· simp [*]
theorem getElem?_scanl_zero {f : β α β} : (scanl f b l)[0]? = some b := by
simp
theorem getElem_scanl_zero {f : β α β} : (scanl f b l)[0] = b := by
simp
@[simp]
theorem head_scanl {f : β α β} (h : scanl f b l []) : (scanl f b l).head h = b := by
simp [head_eq_getElem]
@[simp]
theorem head?_scanl {f : β α β} : (scanl f b l).head? = some b := by
simp [head?_eq_getElem?]
theorem getLast_scanl {f : β α β} (h : scanl f b l []) :
(scanl f b l).getLast h = foldl f b l := by
simp [getLast_eq_getElem]
theorem getLast?_scanl {f : β α β} : (scanl f b l).getLast? = some (foldl f b l) := by
simp [getLast?_eq_getElem?]
@[grind =]
theorem tail_scanl {f : β α β} (h : 0 < l.length) :
(scanl f b l).tail = scanl f (f b (l.head (ne_nil_of_length_pos h))) l.tail := by
induction l
· simp at h
· simp
theorem getElem?_succ_scanl {f : β α β} :
(scanl f b l)[i + 1]? = (scanl f b l)[i]?.bind fun x => l[i]?.map fun y => f x y := by
simp only [getElem?_scanl, take_add_one]
split
· have : i < l.length := Nat.add_one_le_iff.mp _
have : i l.length := Nat.le_of_lt _
simp [*, - take_append_getElem]
· split
· apply Eq.symm
simpa using Nat.lt_add_one_iff.mp (Nat.not_le.mp _)
· simp
theorem getElem_succ_scanl {f : β α β} (h : i + 1 < (scanl f b l).length) :
(scanl f b l)[i + 1] = f ((l.scanl f b)[i]'(Nat.lt_trans (Nat.lt_add_one _) h)) (l[i]'(by simpa using h)) := by
simp only [length_scanl, Nat.add_lt_add_iff_right] at h
simp [take_add_one, *, - take_append_getElem]
@[grind =]
theorem scanl_append {f : β α β} {l₁ l₂ : List α} :
scanl f b (l₁ ++ l₂) = scanl f b l₁ ++ (scanl f (foldl f b l₁) l₂).tail := by
induction l₁ generalizing b
case nil => cases l₂ <;> simp
case cons head tail ih => simp [ih]
@[grind =]
theorem scanl_map {f : β γ β} {g : α γ} {as : List α} :
scanl f init (as.map g) = scanl (fun acc x => f acc (g x)) init as := by
induction as generalizing init with simp [*]
theorem scanl_eq_scanr_reverse {f : β α β} :
scanl f init as = reverse (scanr (flip f) init as.reverse) := by
simp only [scanl, scanr, Id.run, scanrM_reverse, Functor.map, reverse_reverse]
rfl
theorem scanr_eq_scanl_reverse {f : α β β} :
scanr f init as = reverse (scanl (flip f) init as.reverse) := by
simp only [scanl_eq_scanr_reverse, reverse_reverse]
rfl
@[simp, grind =]
theorem scanl_reverse {f : β α β} {as : List α} :
scanl f init as.reverse = reverse (scanr (flip f) init as) := by
simp [scanr_eq_scanl_reverse]
@[simp, grind =]
theorem scanr_reverse {f : α β β} {as : List α} :
scanr f init as.reverse = reverse (scanl (flip f) init as) := by
simp [scanl_eq_scanr_reverse]
@[simp, grind =]
theorem scanr_nil {f : α β β} : scanr f init [] = [init] := by simp [scanr]
@[simp, grind =]
theorem scanr_cons {f : α β β} :
scanr f b (a :: l) = foldr f b (a :: l) :: scanr f b l := by
simp [scanr_eq_scanl_reverse, reverse_cons, scanl_append, flip, - scanl_reverse]
@[simp]
theorem scanr_ne_nil {f : α β β} : scanr f b l [] := by
simp [scanr_eq_scanl_reverse, - scanl_reverse]
theorem scanr_singleton {f : α β β} : scanr f b [a] = [f a b, b] := by
simp
@[simp]
theorem length_scanr {f : α β β} {as : List α} :
length (scanr f init as) = as.length + 1 := by
simp [scanr_eq_scanl_reverse, - scanl_reverse]
grind_pattern length_scanr => scanr f init as
@[simp]
theorem scanr_iff_nil {f : α β β} (c : β) : scanr f b l = [c] c = b l = [] := by
simp [scanr_eq_scanl_reverse, - scanl_reverse]
@[grind =]
theorem scanr_append {f : α β β} (l₁ l₂ : List α) :
scanr f b (l₁ ++ l₂) = (scanr f (foldr f b l₂) l₁) ++ (scanr f b l₂).tail := by
induction l₁ <;> induction l₂ <;> simp [*]
@[simp]
theorem head_scanr {f : α β β} (h : scanr f b l []) :
(scanr f b l).head h = foldr f b l := by
simp [scanr_eq_scanl_reverse, - scanl_reverse, getLast_scanl, flip]
@[grind =]
theorem getLast_scanr {f : α β β} (h : scanr f b l []) :
(scanr f b l).getLast h = b := by
simp [scanr_eq_scanl_reverse, - scanl_reverse]
theorem getLast?_scanr {f : α β β} : (scanr f b l).getLast? = some b := by
simp [scanr_eq_scanl_reverse, - scanl_reverse]
@[grind =]
theorem tail_scanr {f : α β β} (h : 0 < l.length) :
(scanr f b l).tail = scanr f b l.tail := by
induction l with simp_all
@[grind _=_]
theorem drop_scanr {f : α β β} (h : i l.length) :
(scanr f b l).drop i = scanr f b (l.drop i) := by
induction i generalizing l
· simp
· rename_i i ih
rw [drop_add_one_eq_tail_drop (i := i), drop_add_one_eq_tail_drop (i := i), ih]
· rw [tail_scanr]
simpa [length_drop, Nat.lt_sub_iff_add_lt]
· exact Nat.le_of_lt (Nat.add_one_le_iff.mp _)
@[simp, grind =]
theorem getElem_scanr {f : α β β} (h : i < (scanr f b l).length) :
(scanr f b l)[i] = foldr f b (l.drop i) := by
induction l generalizing b i
· simp
· cases i <;> simp [*]
@[grind =]
theorem getElem?_scanr {f : α β β} :
(scanr f b l)[i]? = if i < l.length + 1 then some (foldr f b (l.drop i)) else none := by
split
· rw [getElem?_pos _ _ (by simpa), getElem_scanr]
· rename_i h
simpa [getElem?_neg, length_scanr] using h
@[simp]
theorem head?_scanr {f : α β β} : (scanr f b l).head? = some (foldr f b l) := by
simp [head?_eq_getElem?]
theorem getElem_scanr_zero {f : α β β} : (scanr f b l)[0] = foldr f b l := by
simp
theorem getElem?_scanr_zero {f : α β β} : (scanr f b l)[0]? = some (foldr f b l) := by
simp
theorem getElem?_scanr_of_lt {f : α β β} (h : i < l.length + 1) :
(scanr f b l)[i]? = some (foldr f b (l.drop i)) := by
simp [h]
@[grind =]
theorem scanr_map {f : α β β} {g : γ α} (b : β) (l : List γ) :
scanr f b (l.map g) = scanr (fun x acc => f (g x) acc) b l := by
suffices l, foldr f b (l.map g) = foldr (fun x acc => f (g x) acc) b l from by
induction l generalizing b with simp [*]
intro l
induction l with simp [*]

View File

@@ -64,6 +64,7 @@ def MergeSort.Internal.splitInTwo (l : { l : List α // l.length = n }) :
open MergeSort.Internal in
set_option linter.unusedVariables false in
set_option backward.isDefEq.respectTransparency false in
/--
A stable merge sort.

View File

@@ -182,14 +182,14 @@ private theorem mergeSortTR_run_eq_mergeSort : {n : Nat} → (l : { l : List α
simp only [mergeSortTR.run, mergeSortTR.run, mergeSort]
rw [merge_eq_mergeTR]
rw [mergeSortTR_run_eq_mergeSort, mergeSortTR_run_eq_mergeSort]
rfl
-- We don't make this a `@[csimp]` lemma because `mergeSort_eq_mergeSortTR₂` is faster.
theorem mergeSort_eq_mergeSortTR : @mergeSort = @mergeSortTR := by
funext
rw [mergeSortTR, mergeSortTR_run_eq_mergeSort]
-- This mutual block is unfortunately quite slow to elaborate.
set_option maxHeartbeats 400000 in
set_option backward.isDefEq.respectTransparency false in
mutual
private theorem mergeSortTR₂_run_eq_mergeSort : {n : Nat} (l : { l : List α // l.length = n }) mergeSortTR₂.run le l = mergeSort l.1 le
| 0, [], _

View File

@@ -268,7 +268,7 @@ theorem drop_eq_extract {l : List α} {k : Nat} :
| 0 => simp
| _ + 1 =>
simp only [List.drop_succ_cons, List.length_cons, ih]
simp only [List.extract_eq_drop_take, List.drop_succ_cons, Nat.succ_sub_succ]
simp only [List.extract_eq_take_drop, List.drop_succ_cons, Nat.succ_sub_succ]
/-! ### takeWhile and dropWhile -/

View File

@@ -280,6 +280,7 @@ theorem findRevM?_toArray [Monad m] [LawfulMonad m] (f : α → m Bool) (l : Lis
simp only [forIn_cons, find?]
by_cases f a <;> simp_all
set_option backward.isDefEq.respectTransparency false in
private theorem findFinIdx?_loop_toArray (w : l' = l.drop j) :
Array.findFinIdx?.loop p l.toArray j = List.findFinIdx?.go p l l' j h := by
unfold findFinIdx?.loop
@@ -316,6 +317,7 @@ termination_by l.length - j
rw [Array.findIdx?_eq_map_findFinIdx?_val, findIdx?_eq_map_findFinIdx?_val]
simp [Array.size]
set_option backward.isDefEq.respectTransparency false in
private theorem idxAuxOf_toArray [BEq α] (a : α) (l : List α) (j : Nat) (w : l' = l.drop j) (h) :
l.toArray.idxOfAux a j = findFinIdx?.go (fun x => x == a) l l' j h := by
unfold idxOfAux
@@ -361,6 +363,7 @@ termination_by l.length - j
as.toArray.idxOf a = as.idxOf a := by
rw [Array.idxOf, findIdx_toArray, idxOf]
set_option backward.isDefEq.respectTransparency false in
theorem isPrefixOfAux_toArray_succ [BEq α] (l₁ l₂ : List α) (hle : l₁.length l₂.length) (i : Nat) :
Array.isPrefixOfAux l₁.toArray l₂.toArray hle (i + 1) =
Array.isPrefixOfAux l₁.tail.toArray l₂.tail.toArray (by simp; omega) i := by
@@ -586,7 +589,7 @@ theorem flatMap_toArray_cons {β} (f : α → Array β) (a : α) (as : List α)
@[simp, grind =] theorem swap_toArray (l : List α) (i j : Nat) {hi hj}:
l.toArray.swap i j hi hj = ((l.set i l[j]).set j l[i]).toArray := by
apply ext'
simp
simp; rfl
@[simp, grind =] theorem eraseIdx_toArray (l : List α) (i : Nat) (h : i < l.toArray.size) :
l.toArray.eraseIdx i h = (l.eraseIdx i).toArray := by
@@ -616,13 +619,13 @@ decreasing_by
@[simp, grind =] theorem eraseP_toArray {as : List α} {p : α Bool} :
as.toArray.eraseP p = (as.eraseP p).toArray := by
rw [Array.eraseP, List.eraseP_eq_eraseIdx, findFinIdx?_toArray]
split <;> simp [*, findIdx?_eq_map_findFinIdx?_val]
split <;> simp [*, findIdx?_eq_map_findFinIdx?_val] <;> rfl
@[simp, grind =] theorem erase_toArray [BEq α] {as : List α} {a : α} :
as.toArray.erase a = (as.erase a).toArray := by
rw [Array.erase, finIdxOf?_toArray, List.erase_eq_eraseIdx]
rw [idxOf?_eq_map_finIdxOf?_val]
split <;> simp_all
split <;> simp_all <;> rfl
private theorem insertIdx_loop_toArray (i : Nat) (l : List α) (j : Nat) (hj : j < l.toArray.size) (h : i j) :
insertIdx.loop i l.toArray j, hj = (l.take i ++ l[j] :: (l.take j).drop i ++ l.drop (j + 1)).toArray := by
@@ -639,10 +642,10 @@ private theorem insertIdx_loop_toArray (i : Nat) (l : List α) (j : Nat) (hj : j
getElem_set_self, take_set_of_le (j := j - 1) (by omega),
take_set_of_le (j := j - 1) (by omega), take_eq_append_getElem_of_pos (by omega) hj,
drop_append_of_le_length (by simp; omega)]
simp only [append_assoc, cons_append, nil_append, append_cancel_right_eq]
simp only [append_assoc, cons_append, nil_append]
cases i with
| zero => simp
| succ i => rw [take_set_of_le (by omega)]
| zero => simp; rfl
| succ i => rw [take_set_of_le (by omega)]; rfl
· simp only [Nat.not_lt] at h'
have : i = j := by omega
subst this

View File

@@ -7,6 +7,7 @@ module
prelude
public import Init.Prelude
import Init.Data.List.Basic
public section

View File

@@ -26,3 +26,4 @@ public import Init.Data.Nat.Compare
public import Init.Data.Nat.Simproc
public import Init.Data.Nat.Fold
public import Init.Data.Nat.Order
public import Init.Data.Nat.ToString

View File

@@ -546,10 +546,12 @@ protected abbrev not_lt_of_gt := @Nat.lt_asymm
/-- Alias for `Nat.lt_asymm`. -/
protected abbrev not_lt_of_lt := @Nat.lt_asymm
protected theorem lt_iff_le_not_le {m n : Nat} : m < n m n ¬ n m :=
protected theorem lt_iff_le_and_not_ge {m n : Nat} : m < n m n ¬ n m :=
fun h => Nat.le_of_lt h, Nat.not_le_of_gt h, fun _, h => Nat.lt_of_not_ge h
/-- Alias for `Nat.lt_iff_le_not_le`. -/
protected abbrev lt_iff_le_and_not_ge := @Nat.lt_iff_le_not_le
/-- Deprecated alias for `Nat.lt_iff_le_and_not_ge`. -/
@[deprecated Nat.lt_iff_le_and_not_ge (since := "2026-02-11")]
protected abbrev lt_iff_le_not_le := @Nat.lt_iff_le_and_not_ge
protected theorem lt_iff_le_and_ne {m n : Nat} : m < n m n m n :=
fun h => Nat.le_of_lt h, Nat.ne_of_lt h, fun h => Nat.lt_of_le_of_ne h.1 h.2

View File

@@ -6,7 +6,7 @@ Authors: Leonardo de Moura, Jeremy Avigad, Mario Carneiro
module
prelude
public import Init.Data.Ord.Basic
public import Init.Data.Order.ClassesExtra
import all Init.Data.Ord.Basic
public section
@@ -70,4 +70,8 @@ protected theorem isGE_compare {a b : Nat} :
rw [ Nat.compare_swap, Ordering.isGE_swap]
exact Nat.isLE_compare
public instance : Std.LawfulOrderOrd Nat where
isLE_compare _ _ := Nat.isLE_compare
isGE_compare _ _ := Nat.isGE_compare
end Nat

View File

@@ -400,6 +400,7 @@ theorem dfold_add
induction m with
| zero => simp; rfl
| succ m ih =>
set_option backward.isDefEq.respectTransparency false in
simp [dfold_congr (Nat.add_assoc n m 1).symm, ih]
@[simp] theorem dfoldRev_zero
@@ -434,7 +435,9 @@ theorem dfoldRev_add
(dfoldRev m (α := fun i h => α (n + i)) (fun i h => f (n + i) (by omega)) init) := by
induction m with
| zero => simp; rfl
| succ m ih => simp [ Nat.add_assoc, ih]
| succ m ih =>
set_option backward.isDefEq.respectTransparency false in
simp [ Nat.add_assoc, ih]
end Nat

View File

@@ -0,0 +1,197 @@
/-
Copyright (c) 2026 Lean FRO, LLC. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Marcus Rossel, Paul Reichert
-/
module
prelude
public import Init.Data.Repr
public import Init.Data.Char.Basic
public import Init.Data.ToString.Basic
public import Init.Data.String.Basic
import Init.NotationExtra
import all Init.Data.Repr
import Init.Omega
import Init.RCases
import Init.Data.Nat.Lemmas
import Init.Data.Nat.Bitwise
import Init.Data.Nat.Simproc
import Init.WFTactics
import Init.Data.Char.Lemmas
public section
-- todo: lemmas about `ToString Nat` and `ToString Int`
namespace Nat
variable {b : Nat}
@[simp]
theorem isDigit_digitChar : n.digitChar.isDigit = decide (n < 10) :=
match n with
| 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 => by simp [digitChar]
| _ + 10 => by
simp only [digitChar, reduceIte, Nat.reduceEqDiff]
(repeat' split) <;> simp
private theorem isDigit_of_mem_toDigitsCore
(hc : c cs c.isDigit) (hb₁ : 0 < b) (hb₂ : b 10) (h : c toDigitsCore b fuel n cs) :
c.isDigit := by
induction fuel generalizing n cs with rw [toDigitsCore] at h
| zero => exact hc h
| succ _ ih =>
split at h
case' isFalse => apply ih (fun h => ?_) h
all_goals
cases h with
| head => simp [Nat.lt_of_lt_of_le (mod_lt _ hb₁) hb₂]
| tail _ hm => exact hc hm
theorem isDigit_of_mem_toDigits (hb₁ : 0 < b) (hb₂ : b 10) (hc : c toDigits b n) : c.isDigit :=
isDigit_of_mem_toDigitsCore (fun _ => by contradiction) hb₁ hb₂ hc
private theorem toDigitsCore_of_lt_base (hb : n < b) (hf : n < fuel) :
toDigitsCore b fuel n cs = n.digitChar :: cs := by
unfold toDigitsCore
split <;> simp_all [mod_eq_of_lt]
theorem toDigits_of_lt_base (h : n < b) : toDigits b n = [n.digitChar] :=
toDigitsCore_of_lt_base h (lt_succ_self _)
@[simp, grind =]
theorem toDigits_zero : (b : Nat) toDigits b 0 = ['0']
| 0 => rfl
| _ + 1 => toDigits_of_lt_base (zero_lt_succ _)
private theorem toDigitsCore_append :
toDigitsCore b fuel n cs₁ ++ cs₂ = toDigitsCore b fuel n (cs₁ ++ cs₂) := by
induction fuel generalizing n cs₁ with simp only [toDigitsCore]
| succ => split <;> simp_all
private theorem toDigitsCore_eq_toDigitsCore_nil_append :
toDigitsCore b fuel n cs₁ = toDigitsCore b fuel n [] ++ cs₁ := by
simp [toDigitsCore_append]
private theorem toDigitsCore_eq_of_lt_fuel (hb : 1 < b) (h₁ : n < fuel₁) (h₂ : n < fuel₂) :
toDigitsCore b fuel₁ n cs = toDigitsCore b fuel₂ n cs := by
cases fuel₁ <;> cases fuel₂ <;> try contradiction
simp only [toDigitsCore, Nat.div_eq_zero_iff]
split
· simp
· have := Nat.div_lt_self (by omega : 0 < n) hb
exact toDigitsCore_eq_of_lt_fuel hb (by omega) (by omega)
private theorem toDigitsCore_toDigitsCore
(hb : 1 < b) (hn : 0 < n) (hd : d < b) (hf : b * n + d < fuel) (hnf : n < nf) (hdf : d < df) :
toDigitsCore b nf n (toDigitsCore b df d cs) = toDigitsCore b fuel (b * n + d) cs := by
cases fuel with
| zero => contradiction
| succ fuel =>
rw [toDigitsCore]
split
case isTrue h =>
have : b b * n + d := Nat.le_trans (Nat.le_mul_of_pos_right _ hn) (le_add_right _ _)
cases Nat.div_eq_zero_iff.mp h <;> omega
case isFalse =>
have h : (b * n + d) / b = n := by
rw [mul_add_div (by omega), Nat.div_eq_zero_iff.mpr (.inr hd), Nat.add_zero]
have := (Nat.lt_mul_iff_one_lt_left hn).mpr hb
simp only [toDigitsCore_of_lt_base hd hdf, mul_add_mod_self_left, mod_eq_of_lt hd, h]
apply toDigitsCore_eq_of_lt_fuel hb hnf (by omega)
theorem toDigits_append_toDigits (hb : 1 < b) (hn : 0 < n) (hd : d < b) :
(toDigits b n) ++ (toDigits b d) = toDigits b (b * n + d) := by
rw [toDigits, toDigitsCore_append]
exact toDigitsCore_toDigitsCore hb hn hd (lt_succ_self _) (lt_succ_self _) (lt_succ_self _)
theorem toDigits_of_base_le (hb : 1 < b) (h : b n) :
toDigits b n = toDigits b (n / b) ++ [digitChar (n % b)] := by
have := Nat.div_add_mod n b
rw (occs := [1]) [ Nat.div_add_mod n b,
toDigits_append_toDigits (by omega) (Nat.div_pos_iff.mpr (by omega)) (Nat.mod_lt n (by omega))]
rw [toDigits_of_lt_base (n := n % b) (Nat.mod_lt n (by omega))]
theorem toDigits_eq_if (hb : 1 < b) :
toDigits b n = if n < b then [digitChar n] else toDigits b (n / b) ++ [digitChar (n % b)] := by
split
· rw [toDigits_of_lt_base _]
· rw [toDigits_of_base_le hb (by omega)]
theorem length_toDigits_pos {b n : Nat} :
0 < (Nat.toDigits b n).length := by
simp [toDigits]
rw [toDigitsCore]
split
· simp
· rw [toDigitsCore_eq_toDigitsCore_nil_append]
simp
theorem length_toDigits_le_iff {n k : Nat} (hb : 1 < b) (h : 0 < k) :
(Nat.toDigits b n).length k n < b ^ k := by
match k with
| 0 => contradiction
| k + 1 =>
induction k generalizing n
· rw [toDigits_eq_if hb]
split <;> simp [*, length_toDigits_pos, Nat.pos_iff_ne_zero, - List.length_eq_zero_iff]
· rename_i ih
rw [toDigits_eq_if hb]
split
· rename_i hlt
simp [Nat.pow_add]
refine Nat.lt_of_lt_of_le hlt ?_
apply Nat.le_mul_of_pos_left
apply Nat.mul_pos
· apply Nat.pow_pos
omega
· omega
· simp [ih (n := n / b) (by omega), Nat.div_lt_iff_lt_mul (k := b) (by omega), Nat.pow_add]
theorem repr_eq_ofList_toDigits {n : Nat} :
n.repr = .ofList (toDigits 10 n) :=
(rfl)
theorem toString_eq_ofList_toDigits {n : Nat} :
toString n = .ofList (toDigits 10 n) :=
(rfl)
@[simp, grind norm]
theorem toString_eq_repr {n : Nat} :
toString n = n.repr :=
(rfl)
@[simp, grind norm]
theorem reprPrec_eq_repr {n i : Nat} :
reprPrec n i = n.repr :=
(rfl)
@[simp, grind norm]
theorem repr_eq_repr {n : Nat} :
repr n = n.repr :=
(rfl)
theorem repr_of_lt {n : Nat} (h : n < 10) :
n.repr = .singleton (digitChar n) := by
rw [repr_eq_ofList_toDigits, toDigits_of_lt_base h, String.singleton_eq_ofList]
theorem repr_of_ge {n : Nat} (h : 10 n) :
n.repr = (n / 10).repr ++ .singleton (digitChar (n % 10)) := by
simp [repr_eq_ofList_toDigits, toDigits_of_base_le (by omega) h, String.singleton_eq_ofList,
String.ofList_append]
theorem repr_eq_repr_append_repr {n : Nat} (h : 10 n) :
n.repr = (n / 10).repr ++ (n % 10).repr := by
rw [repr_of_ge h, repr_of_lt (n := n % 10) (by omega)]
theorem length_repr_pos {n : Nat} :
0 < n.repr.length := by
simpa [repr_eq_ofList_toDigits] using length_toDigits_pos
theorem length_repr_le_iff {n k : Nat} (h : 0 < k) :
n.repr.length k n < 10 ^ k := by
simpa [repr_eq_ofList_toDigits] using length_toDigits_le_iff (by omega) h
end Nat

View File

@@ -444,6 +444,7 @@ instance : MonadAttach Option where
CanReturn x a := x = some a
attach x := x.attach
set_option backward.isDefEq.respectTransparency false in
public instance : LawfulMonadAttach Option where
map_attach {α} x := by simp [MonadAttach.attach]
canReturn_map_imp {α P x a} := by
@@ -455,6 +456,7 @@ end Option
namespace OptionT
set_option backward.isDefEq.respectTransparency false in
public instance [Monad m] [MonadAttach m] [LawfulMonad m] [WeaklyLawfulMonadAttach m] :
WeaklyLawfulMonadAttach (OptionT m) where
map_attach {α} x := by

View File

@@ -744,7 +744,7 @@ 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 α) :
theorem elim_map {f : α β} {g' : γ} {g : β γ} (o : Option α) :
(o.map f).elim g' g = o.elim g' (g f) := by
cases o <;> simp

View File

@@ -437,6 +437,14 @@ theorem isLE_compareOfLessAndEq
· exact Or.inr <| antisymm hle hge
· exact Or.inl <| not_le.mp hge
theorem isGE_compareOfLessAndEq
{α : Type u} [LT α] [LE α] [DecidableLT α] [DecidableLE α] [DecidableEq α]
(antisymm : {x y : α}, x y y x x = y)
(not_le : {x y : α}, ¬ x y y < x) (total : (x y : α), x y y x) {x y : α} :
(compareOfLessAndEq x y).isGE y x := by
rw [compareOfLessAndEq_eq_swap antisymm total not_le, Ordering.isGE_swap,
isLE_compareOfLessAndEq antisymm not_le total]
end Lemmas
/--
@@ -637,7 +645,7 @@ instance [Ord α] : DecidableRel (@LT.lt α ltOfOrd) := fun a b =>
Constructs an `LE` instance from an `Ord` instance that asserts that the result of `compare`
satisfies `Ordering.isLE`.
-/
@[expose] def leOfOrd [Ord α] : LE α where
@[expose, instance_reducible] def leOfOrd [Ord α] : LE α where
le a b := (compare a b).isLE
@[inline]
@@ -669,7 +677,7 @@ Inverts the order of an `Ord` instance.
The result is an `Ord α` instance that returns `Ordering.lt` when `ord` would return `Ordering.gt`
and that returns `Ordering.gt` when `ord` would return `Ordering.lt`.
-/
@[expose] protected def opposite (ord : Ord α) : Ord α where
@[expose, instance_reducible] protected def opposite (ord : Ord α) : Ord α where
compare x y := ord.compare y x
/--
@@ -680,7 +688,7 @@ In particular, `ord.on f` compares `x` and `y` by comparing `f x` and `f y` acco
The function `compareOn` can be used to perform this comparison without constructing an intermediate
`Ord` instance.
-/
@[expose] protected def on (_ : Ord β) (f : α β) : Ord α where
@[expose, instance_reducible] protected def on (_ : Ord β) (f : α β) : Ord α where
compare := compareOn f
/--
@@ -699,7 +707,7 @@ The function `compareLex` can be used to perform this comparison without constru
intermediate `Ord` instance. `Ordering.then` can be used to lexicographically combine the results of
comparisons.
-/
@[expose] protected def lex' (ord₁ ord₂ : Ord α) : Ord α where
@[expose, instance_reducible] protected def lex' (ord₁ ord₂ : Ord α) : Ord α where
compare := compareLex ord₁.compare ord₂.compare
end Ord

View File

@@ -7,8 +7,10 @@ module
prelude
public import Init.Data.Order.Ord
public import Init.Data.Order.ClassesExtra
public import Init.Data.SInt.Basic
import Init.Data.SInt.Lemmas
import Init.Data.Order.Lemmas
public section
@@ -34,6 +36,10 @@ instance : TransOrd Int8 :=
instance : LawfulEqOrd Int8 where
eq_of_compare h := compareOfLessAndEq_eq_eq Int8.le_refl Int8.not_le |>.mp h
instance : LawfulOrderOrd Int8 where
isLE_compare _ _ := isLE_compareOfLessAndEq Std.le_antisymm Std.not_le (fun _ _ => Std.le_total)
isGE_compare _ _ := isGE_compareOfLessAndEq Std.le_antisymm Std.not_le (fun _ _ => Std.le_total)
end Int8
namespace Int16
@@ -48,6 +54,10 @@ instance : TransOrd Int16 :=
instance : LawfulEqOrd Int16 where
eq_of_compare h := compareOfLessAndEq_eq_eq Int16.le_refl Int16.not_le |>.mp h
instance : LawfulOrderOrd Int16 where
isLE_compare _ _ := isLE_compareOfLessAndEq Std.le_antisymm Std.not_le (fun _ _ => Std.le_total)
isGE_compare _ _ := isGE_compareOfLessAndEq Std.le_antisymm Std.not_le (fun _ _ => Std.le_total)
end Int16
namespace Int32
@@ -62,6 +72,10 @@ instance : TransOrd Int32 :=
instance : LawfulEqOrd Int32 where
eq_of_compare h := compareOfLessAndEq_eq_eq Int32.le_refl Int32.not_le |>.mp h
instance : LawfulOrderOrd Int32 where
isLE_compare _ _ := isLE_compareOfLessAndEq Std.le_antisymm Std.not_le (fun _ _ => Std.le_total)
isGE_compare _ _ := isGE_compareOfLessAndEq Std.le_antisymm Std.not_le (fun _ _ => Std.le_total)
end Int32
namespace Int64
@@ -76,6 +90,10 @@ instance : TransOrd Int64 :=
instance : LawfulEqOrd Int64 where
eq_of_compare h := compareOfLessAndEq_eq_eq Int64.le_refl Int64.not_le |>.mp h
instance : LawfulOrderOrd Int64 where
isLE_compare _ _ := isLE_compareOfLessAndEq Std.le_antisymm Std.not_le (fun _ _ => Std.le_total)
isGE_compare _ _ := isGE_compareOfLessAndEq Std.le_antisymm Std.not_le (fun _ _ => Std.le_total)
end Int64
namespace ISize
@@ -90,4 +108,8 @@ instance : TransOrd ISize :=
instance : LawfulEqOrd ISize where
eq_of_compare h := compareOfLessAndEq_eq_eq ISize.le_refl ISize.not_le |>.mp h
instance : LawfulOrderOrd ISize where
isLE_compare _ _ := isLE_compareOfLessAndEq Std.le_antisymm Std.not_le (fun _ _ => Std.le_total)
isGE_compare _ _ := isGE_compareOfLessAndEq Std.le_antisymm Std.not_le (fun _ _ => Std.le_total)
end ISize

View File

@@ -7,8 +7,10 @@ module
prelude
public import Init.Data.Order.Ord
public import Init.Data.Order.ClassesExtra
public import Init.Data.UInt.Basic
import Init.Data.UInt.Lemmas
import Init.Data.Order.Lemmas
public section
@@ -34,6 +36,10 @@ instance : TransOrd UInt8 :=
instance : LawfulEqOrd UInt8 where
eq_of_compare h := compareOfLessAndEq_eq_eq UInt8.le_refl UInt8.not_le |>.mp h
instance : LawfulOrderOrd UInt8 where
isLE_compare _ _ := isLE_compareOfLessAndEq Std.le_antisymm Std.not_le (fun _ _ => Std.le_total)
isGE_compare _ _ := isGE_compareOfLessAndEq Std.le_antisymm Std.not_le (fun _ _ => Std.le_total)
end UInt8
namespace UInt16
@@ -48,6 +54,10 @@ instance : TransOrd UInt16 :=
instance : LawfulEqOrd UInt16 where
eq_of_compare h := compareOfLessAndEq_eq_eq UInt16.le_refl UInt16.not_le |>.mp h
instance : LawfulOrderOrd UInt16 where
isLE_compare _ _ := isLE_compareOfLessAndEq Std.le_antisymm Std.not_le (fun _ _ => Std.le_total)
isGE_compare _ _ := isGE_compareOfLessAndEq Std.le_antisymm Std.not_le (fun _ _ => Std.le_total)
end UInt16
namespace UInt32
@@ -62,6 +72,10 @@ instance : TransOrd UInt32 :=
instance : LawfulEqOrd UInt32 where
eq_of_compare h := compareOfLessAndEq_eq_eq UInt32.le_refl UInt32.not_le |>.mp h
instance : LawfulOrderOrd UInt32 where
isLE_compare _ _ := isLE_compareOfLessAndEq Std.le_antisymm Std.not_le (fun _ _ => Std.le_total)
isGE_compare _ _ := isGE_compareOfLessAndEq Std.le_antisymm Std.not_le (fun _ _ => Std.le_total)
end UInt32
namespace UInt64
@@ -76,6 +90,10 @@ instance : TransOrd UInt64 :=
instance : LawfulEqOrd UInt64 where
eq_of_compare h := compareOfLessAndEq_eq_eq UInt64.le_refl UInt64.not_le |>.mp h
instance : LawfulOrderOrd UInt64 where
isLE_compare _ _ := isLE_compareOfLessAndEq Std.le_antisymm Std.not_le (fun _ _ => Std.le_total)
isGE_compare _ _ := isGE_compareOfLessAndEq Std.le_antisymm Std.not_le (fun _ _ => Std.le_total)
end UInt64
namespace USize
@@ -90,4 +108,8 @@ instance : TransOrd USize :=
instance : LawfulEqOrd USize where
eq_of_compare h := compareOfLessAndEq_eq_eq USize.le_refl USize.not_le |>.mp h
instance : LawfulOrderOrd USize where
isLE_compare _ _ := isLE_compareOfLessAndEq Std.le_antisymm Std.not_le (fun _ _ => Std.le_total)
isGE_compare _ _ := isGE_compareOfLessAndEq Std.le_antisymm Std.not_le (fun _ _ => Std.le_total)
end USize

View File

@@ -69,6 +69,27 @@ public theorem compare_eq_eq {α : Type u} [Ord α] [BEq α] [LE α] [LawfulOrde
rw [LawfulOrderBEq.beq_iff_le_and_ge, isLE_compare, isGE_compare]
cases compare a b <;> simp
public theorem compare_ne_eq {α : Type u} [Ord α] [BEq α] [LE α] [LawfulOrderOrd α] [LawfulOrderBEq α]
{a b : α} :
compare a b .eq ¬ a == b := by
simp [compare_eq_eq]
public theorem compare_ne_eq_iff_ne {α : Type u} [Ord α] [LawfulEqOrd α] {a b : α} :
compare a b .eq a b := by
simp
grind_pattern compare_eq_lt => compare a b, Ordering.lt where
guard compare a b = .lt
grind_pattern compare_eq_eq => compare a b, Ordering.eq where
guard compare a b = .eq
grind_pattern compare_eq_gt => compare a b, Ordering.gt where
guard compare a b = .gt
grind_pattern compare_ne_eq => compare a b, Ordering.eq where
guard compare a b .eq
/--
Creates a `DecidableLT α` instance using a well-behaved `Ord α` instance.
-/

View File

@@ -169,7 +169,7 @@ automatically. If it fails, it is necessary to provide some of the fields manual
* Other proof obligations, namely `le_refl` and `le_trans`, can be omitted if `Refl` and `Trans`
instances can be synthesized.
-/
@[expose]
@[expose, instance_reducible]
public def PreorderPackage.ofLE (α : Type u)
(args : Packages.PreorderOfLEArgs α := by exact {}) : PreorderPackage α where
toLE := args.le
@@ -254,7 +254,7 @@ automatically. If it fails, it is necessary to provide some of the fields manual
* Other proof obligations, namely `le_refl`, `le_trans` and `le_antisymm`, can be omitted if `Refl`,
`Trans` and `Antisymm` instances can be synthesized.
-/
@[expose]
@[expose, instance_reducible]
public def PartialOrderPackage.ofLE (α : Type u)
(args : Packages.PartialOrderOfLEArgs α := by exact {}) : PartialOrderPackage α where
toPreorderPackage := .ofLE α args.toPreorderOfLEArgs
@@ -383,7 +383,7 @@ automatically. If it fails, it is necessary to provide some of the fields manual
* Other proof obligations, namely `le_total` and `le_trans`, can be omitted if `Total` and `Trans`
instances can be synthesized.
-/
@[expose]
@[expose, instance_reducible]
public def LinearPreorderPackage.ofLE (α : Type u)
(args : Packages.LinearPreorderOfLEArgs α := by exact {}) : LinearPreorderPackage α where
toPreorderPackage := .ofLE α args.toPreorderOfLEArgs
@@ -485,7 +485,7 @@ automatically. If it fails, it is necessary to provide some of the fields manual
* Other proof obligations, namely `le_total`, `le_trans` and `le_antisymm`, can be omitted if
`Total`, `Trans` and `Antisymm` instances can be synthesized.
-/
@[expose]
@[expose, instance_reducible]
public def LinearOrderPackage.ofLE (α : Type u)
(args : Packages.LinearOrderOfLEArgs α := by exact {}) : LinearOrderPackage α where
toLinearPreorderPackage := .ofLE α args.toLinearPreorderOfLEArgs
@@ -794,6 +794,7 @@ automatically. If it fails, it is necessary to provide some of the fields manual
@[expose]
public def LinearOrderPackage.ofOrd (α : Type u)
(args : Packages.LinearOrderOfOrdArgs α := by exact {}) : LinearOrderPackage α :=
set_option backward.isDefEq.respectTransparency false in
letI := LinearPreorderPackage.ofOrd α args.toLinearPreorderOfOrdArgs
haveI : LawfulEqOrd α := args.eq_of_compare _ _
letI : Min α := args.min

View File

@@ -535,6 +535,14 @@ public theorem Rxc.Iterator.pairwise_toList_upwardEnumerableLt [LE α] [Decidabl
· apply ihy (out := a)
simp_all [Rxc.Iterator.isPlausibleStep_iff, Rxc.Iterator.step]
theorem Rxc.Iterator.nodup_toList [LE α] [DecidableLE α]
[PRange.UpwardEnumerable α] [Rxc.IsAlwaysFinite α] [PRange.LawfulUpwardEnumerable α]
[PRange.LawfulUpwardEnumerableLE α]
{it : Iter (α := Rxc.Iterator α) α} :
it.toList.Nodup := by
apply (Rxc.Iterator.pairwise_toList_upwardEnumerableLt it).imp
apply PRange.UpwardEnumerable.ne_of_lt
public theorem Rxo.Iterator.pairwise_toList_upwardEnumerableLt [LT α] [DecidableLT α]
[UpwardEnumerable α] [LawfulUpwardEnumerable α] [LawfulUpwardEnumerableLT α]
[Rxo.IsAlwaysFinite α]
@@ -558,6 +566,14 @@ public theorem Rxo.Iterator.pairwise_toList_upwardEnumerableLt [LT α] [Decidabl
· apply ihy (out := a)
simp_all [Rxo.Iterator.isPlausibleStep_iff, Rxo.Iterator.step]
theorem Rxo.Iterator.nodup_toList [LT α] [DecidableLT α]
[PRange.UpwardEnumerable α] [Rxo.IsAlwaysFinite α] [PRange.LawfulUpwardEnumerable α]
[PRange.LawfulUpwardEnumerableLT α]
{it : Iter (α := Rxo.Iterator α) α} :
it.toList.Nodup := by
apply (Rxo.Iterator.pairwise_toList_upwardEnumerableLt it).imp
apply PRange.UpwardEnumerable.ne_of_lt
public theorem Rxi.Iterator.pairwise_toList_upwardEnumerableLt
[UpwardEnumerable α] [LawfulUpwardEnumerable α]
[Rxi.IsAlwaysFinite α]
@@ -581,6 +597,13 @@ public theorem Rxi.Iterator.pairwise_toList_upwardEnumerableLt
· apply ihy (out := a)
simp_all [Rxi.Iterator.isPlausibleStep_iff, Rxi.Iterator.step]
theorem Rxi.Iterator.nodup_toList
[PRange.UpwardEnumerable α] [Rxi.IsAlwaysFinite α] [PRange.LawfulUpwardEnumerable α]
{it : Iter (α := Rxi.Iterator α) α} :
it.toList.Nodup := by
apply (Rxi.Iterator.pairwise_toList_upwardEnumerableLt it).imp
apply PRange.UpwardEnumerable.ne_of_lt
namespace Rcc
variable {r : Rcc α}
@@ -658,6 +681,13 @@ public theorem pairwise_toList_upwardEnumerableLt [LE α] [DecidableLE α]
rw [Internal.toList_eq_toList_iter]
apply Rxc.Iterator.pairwise_toList_upwardEnumerableLt
public theorem nodup_toList [LE α] [DecidableLE α]
[PRange.UpwardEnumerable α] [Rxc.IsAlwaysFinite α] [PRange.LawfulUpwardEnumerable α]
[PRange.LawfulUpwardEnumerableLE α]
{a b : α} :
(a...=b).toList.Nodup := by
simpa [Internal.toList_eq_toList_iter] using Std.Rxc.Iterator.nodup_toList
public theorem pairwise_toList_ne [LE α] [DecidableLE α]
[UpwardEnumerable α] [LawfulUpwardEnumerable α] [LawfulUpwardEnumerableLE α]
[Rxc.IsAlwaysFinite α] :
@@ -913,6 +943,13 @@ public theorem pairwise_toList_upwardEnumerableLt [LE α] [LT α] [DecidableLT
rw [Internal.toList_eq_toList_iter]
apply Rxo.Iterator.pairwise_toList_upwardEnumerableLt
public theorem nodup_toList [LT α] [DecidableLT α]
[PRange.UpwardEnumerable α] [Rxo.IsAlwaysFinite α] [PRange.LawfulUpwardEnumerable α]
[PRange.LawfulUpwardEnumerableLT α]
{a b : α} :
(a...b).toList.Nodup := by
simpa [Internal.toList_eq_toList_iter] using Std.Rxo.Iterator.nodup_toList
public theorem pairwise_toList_ne [LE α] [LT α] [DecidableLT α]
[UpwardEnumerable α] [LawfulUpwardEnumerable α] [LawfulUpwardEnumerableLT α]
[Rxo.IsAlwaysFinite α] :
@@ -1124,6 +1161,11 @@ public theorem pairwise_toList_upwardEnumerableLt [LE α]
rw [Internal.toList_eq_toList_iter]
apply Rxi.Iterator.pairwise_toList_upwardEnumerableLt
public theorem nodup_toList
[PRange.UpwardEnumerable α] [Rxi.IsAlwaysFinite α] [PRange.LawfulUpwardEnumerable α]
{a : α} : (a...*).toList.Nodup := by
simpa [Internal.toList_eq_toList_iter] using Std.Rxi.Iterator.nodup_toList
public theorem pairwise_toList_ne [LE α]
[UpwardEnumerable α] [LawfulUpwardEnumerable α] [Rxi.IsAlwaysFinite α] :
r.toList.Pairwise (fun a b => a b) :=
@@ -1363,6 +1405,13 @@ public theorem pairwise_toList_upwardEnumerableLt [LE α] [DecidableLE α]
rw [Internal.toList_eq_toList_iter]
apply Rxc.Iterator.pairwise_toList_upwardEnumerableLt
public theorem nodup_toList [LE α] [DecidableLE α]
[PRange.UpwardEnumerable α] [Rxc.IsAlwaysFinite α] [PRange.LawfulUpwardEnumerable α]
[PRange.LawfulUpwardEnumerableLE α]
{a b : α} :
(a<...=b).toList.Nodup := by
simpa [Internal.toList_eq_toList_iter] using Std.Rxc.Iterator.nodup_toList
public theorem pairwise_toList_ne [LE α] [DecidableLE α]
[UpwardEnumerable α] [LawfulUpwardEnumerable α] [LawfulUpwardEnumerableLE α]
[Rxc.IsAlwaysFinite α] :
@@ -1588,6 +1637,13 @@ public theorem pairwise_toList_upwardEnumerableLt [LT α] [DecidableLT α]
rw [Internal.toList_eq_toList_iter]
apply Rxo.Iterator.pairwise_toList_upwardEnumerableLt
public theorem nodup_toList [LT α] [DecidableLT α]
[PRange.UpwardEnumerable α] [Rxo.IsAlwaysFinite α] [PRange.LawfulUpwardEnumerable α]
[PRange.LawfulUpwardEnumerableLT α]
{a b : α} :
(a<...b).toList.Nodup := by
simpa [Internal.toList_eq_toList_iter] using Std.Rxo.Iterator.nodup_toList
public theorem pairwise_toList_ne [LT α] [DecidableLT α]
[UpwardEnumerable α] [LawfulUpwardEnumerable α] [LawfulUpwardEnumerableLT α]
[Rxo.IsAlwaysFinite α] :
@@ -1823,6 +1879,11 @@ public theorem pairwise_toList_upwardEnumerableLt
rw [Internal.toList_eq_toList_iter]
apply Rxi.Iterator.pairwise_toList_upwardEnumerableLt
public theorem nodup_toList
[PRange.UpwardEnumerable α] [Rxi.IsAlwaysFinite α] [PRange.LawfulUpwardEnumerable α]
{a : α} : (a<...*).toList.Nodup := by
simpa [Internal.toList_eq_toList_iter] using Std.Rxi.Iterator.nodup_toList
public theorem pairwise_toList_ne
[UpwardEnumerable α] [LawfulUpwardEnumerable α] [Rxi.IsAlwaysFinite α] :
r.toList.Pairwise (fun a b => a b) :=
@@ -2072,6 +2133,13 @@ public theorem pairwise_toList_upwardEnumerableLt [LE α] [DecidableLE α] [Leas
r.toList.Pairwise (fun a b => UpwardEnumerable.LT a b) := by
simp [toList_eq_toList_rcc, Rcc.pairwise_toList_upwardEnumerableLt]
public theorem nodup_toList [LE α] [DecidableLE α] [Least? α]
[PRange.UpwardEnumerable α] [Rxc.IsAlwaysFinite α] [PRange.LawfulUpwardEnumerable α]
[PRange.LawfulUpwardEnumerableLE α]
{a : α} :
(*...=a).toList.Nodup := by
simpa [Internal.toList_eq_toList_iter] using Std.Rxc.Iterator.nodup_toList
public theorem pairwise_toList_ne [LE α] [DecidableLE α] [Least? α]
[UpwardEnumerable α] [LawfulUpwardEnumerable α] [LawfulUpwardEnumerableLE α]
[LawfulUpwardEnumerableLeast? α] [Rxc.IsAlwaysFinite α] :
@@ -2395,6 +2463,13 @@ public theorem pairwise_toList_upwardEnumerableLt [LT α] [DecidableLT α] [Leas
· exact Roo.pairwise_toList_upwardEnumerableLt
· simp
public theorem nodup_toList [LT α] [DecidableLT α] [Least? α]
[PRange.UpwardEnumerable α] [Rxo.IsAlwaysFinite α] [PRange.LawfulUpwardEnumerable α]
[PRange.LawfulUpwardEnumerableLT α]
{a : α} :
(*...a).toList.Nodup := by
simpa [Internal.toList_eq_toList_iter] using Std.Rxo.Iterator.nodup_toList
public theorem pairwise_toList_ne [LT α] [DecidableLT α] [Least? α]
[UpwardEnumerable α] [LawfulUpwardEnumerable α] [LawfulUpwardEnumerableLT α]
[LawfulUpwardEnumerableLeast? α] [Rxo.IsAlwaysFinite α] :
@@ -2688,6 +2763,11 @@ public theorem pairwise_toList_upwardEnumerableLt [Least? α]
· simp
· exact Rci.pairwise_toList_upwardEnumerableLt
public theorem nodup_toList [Least? α]
[PRange.UpwardEnumerable α] [Rxi.IsAlwaysFinite α] [PRange.LawfulUpwardEnumerable α] :
(*...* : Std.Rii α).toList.Nodup := by
simpa [Internal.toList_eq_toList_iter] using Std.Rxi.Iterator.nodup_toList
public theorem pairwise_toList_ne [Least? α]
[UpwardEnumerable α] [LawfulUpwardEnumerable α]
[LawfulUpwardEnumerableLeast? α] [Rxi.IsAlwaysFinite α] :

View File

@@ -32,6 +32,7 @@ structure Rcc (α : Type u) where
The upper bound of the range. {name (full := Rcc.upper)}`upper` is included in the range.
-/
upper : α
deriving DecidableEq
/--
A range of elements of {given}`α` with a closed lower bound and an open upper bound.
@@ -48,6 +49,7 @@ structure Rco (α : Type u) where
The upper bound of the range. {name (full := Rco.upper)}`upper` is not included in the range.
-/
upper : α
deriving DecidableEq
/--
An upward-unbounded range of elements of {given}`α` with a closed lower bound.
@@ -60,6 +62,7 @@ structure Rci (α : Type u) where
The lower bound of the range. {name (full := Rci.lower)}`lower` is included in the range.
-/
lower : α
deriving DecidableEq
/--
A range of elements of {given}`α` with an open lower bound and a closed upper bound.
@@ -76,6 +79,7 @@ structure Roc (α : Type u) where
The upper bound of the range. {name (full := Roc.upper)}`upper` is included in the range.
-/
upper : α
deriving DecidableEq
/--
A range of elements of {given}`α` with an open lower and upper bounds.
@@ -92,6 +96,7 @@ structure Roo (α : Type u) where
The upper bound of the range. {name (full := Roo.upper)}`upper` is not included in the range.
-/
upper : α
deriving DecidableEq
/--
An upward-unbounded range of elements of {given}`α` with an open lower bound.
@@ -104,6 +109,7 @@ structure Roi (α : Type u) where
The lower bound of the range. {name (full := Roi.lower)}`lower` is not included in the range.
-/
lower : α
deriving DecidableEq
/--
A downward-unbounded range of elements of {given}`α` with a closed upper bound.
@@ -116,6 +122,7 @@ structure Ric (α : Type u) where
The upper bound of the range. {name (full := Ric.upper)}`upper` is included in the range.
-/
upper : α
deriving DecidableEq
/--
A downward-unbounded range of elements of {given}`α` with an open upper bound.
@@ -128,12 +135,14 @@ structure Rio (α : Type u) where
The upper bound of the range. {name (full := Rio.upper)}`upper` is not included in the range.
-/
upper : α
deriving DecidableEq
/--
A full range of all elements of {lit}`α`. Its only inhabitant is the range {lit}`*...*`, which is
notation for {lean}`Rii.mk`.
-/
structure Rii (α : Type u) where
structure Rii (α : Type u)
deriving DecidableEq
/-- `a...*` is the range of elements greater than or equal to {lit}`a`. See also {name}`Std.Rci`. -/
syntax:max (term "...*") : term

View File

@@ -102,7 +102,7 @@ theorem Iterator.Monadic.isPlausibleStep_iff [UpwardEnumerable α] [LE α] [Deci
theorem Iterator.Monadic.step_eq_step [UpwardEnumerable α] [LE α] [DecidableLE α]
{it : IterM (α := Rxc.Iterator α) Id α} :
Std.Iterator.step it = pure (.deflate Iterator.Monadic.step it, isPlausibleStep_iff.mpr rfl) := by
simp [Std.Iterator.step]
simp [Std.Iterator.step]; rfl
theorem Iterator.isPlausibleStep_iff [UpwardEnumerable α] [LE α] [DecidableLE α]
{it : Iter (α := Rxc.Iterator α) α} {step} :
@@ -535,6 +535,7 @@ private theorem Iterator.instIteratorLoop.loop_eq_wf [UpwardEnumerable α] [LE
· rw [WellFounded.fix_eq]
simp_all
set_option backward.isDefEq.respectTransparency false in
private theorem Iterator.instIteratorLoop.loopWf_eq [UpwardEnumerable α] [LE α] [DecidableLE α]
[LawfulUpwardEnumerable α] [LawfulUpwardEnumerableLE α]
{n : Type u Type w} [Monad n] [LawfulMonad n] (γ : Type u)
@@ -586,6 +587,7 @@ termination_by IteratorLoop.WithWF.mk ⟨⟨some next, upperBound⟩⟩ acc (hwf
decreasing_by
simp [IteratorLoop.rel, Monadic.isPlausibleStep_iff, Monadic.step, *]
set_option backward.isDefEq.respectTransparency false in
instance Iterator.instLawfulIteratorLoop [UpwardEnumerable α] [LE α] [DecidableLE α]
[LawfulUpwardEnumerable α] [LawfulUpwardEnumerableLE α]
{n : Type u Type w} [Monad n] [LawfulMonad n] :
@@ -678,7 +680,7 @@ theorem Iterator.Monadic.isPlausibleStep_iff [UpwardEnumerable α] [LT α] [Deci
theorem Iterator.Monadic.step_eq_step [UpwardEnumerable α] [LT α] [DecidableLT α]
{it : IterM (α := Rxo.Iterator α) Id α} :
Std.Iterator.step it = pure (.deflate Iterator.Monadic.step it, isPlausibleStep_iff.mpr rfl) := by
simp [Std.Iterator.step]
simp [Std.Iterator.step]; rfl
theorem Iterator.isPlausibleStep_iff [UpwardEnumerable α] [LT α] [DecidableLT α]
{it : Iter (α := Rxo.Iterator α) α} {step} :
@@ -1107,6 +1109,7 @@ private theorem Iterator.instIteratorLoop.loop_eq_wf [UpwardEnumerable α] [LT
· rw [WellFounded.fix_eq]
simp_all
set_option backward.isDefEq.respectTransparency false in
private theorem Iterator.instIteratorLoop.loopWf_eq [UpwardEnumerable α] [LT α] [DecidableLT α]
[LawfulUpwardEnumerable α] [LawfulUpwardEnumerableLT α]
{n : Type u Type w} [Monad n] [LawfulMonad n] (γ : Type u)
@@ -1158,6 +1161,7 @@ termination_by IteratorLoop.WithWF.mk ⟨⟨some next, upperBound⟩⟩ acc (hwf
decreasing_by
simp [IteratorLoop.rel, Monadic.isPlausibleStep_iff, Monadic.step, *]
set_option backward.isDefEq.respectTransparency false in
instance Iterator.instLawfulIteratorLoop [UpwardEnumerable α] [LT α] [DecidableLT α]
[LawfulUpwardEnumerable α] [LawfulUpwardEnumerableLT α]
{n : Type u Type w} [Monad n] [LawfulMonad n] :
@@ -1240,7 +1244,7 @@ theorem Iterator.Monadic.isPlausibleStep_iff [UpwardEnumerable α]
theorem Iterator.Monadic.step_eq_step [UpwardEnumerable α]
{it : IterM (α := Rxi.Iterator α) Id α} :
it.step = pure (.deflate Iterator.Monadic.step it, isPlausibleStep_iff.mpr rfl) := by
simp [IterM.step, Std.Iterator.step]
simp [IterM.step, Std.Iterator.step]; rfl
theorem Iterator.isPlausibleStep_iff [UpwardEnumerable α]
{it : Iter (α := Rxi.Iterator α) α} {step} :

View File

@@ -322,4 +322,8 @@ protected def ceil (a : Rat) : Int :=
else
a.num / a.den + 1
/-- The absolute value of a rational number `a` is `a` if `a ≥ 0` and `-a` if `a ≤ 0`. -/
protected def abs (a : Rat) : Rat :=
if 0 a then a else -a
end Rat

View File

@@ -34,6 +34,8 @@ theorem ext : {p q : Rat} → p.num = q.num → p.den = q.den → p = q
@[simp] theorem one_num : (1 : Rat).num = 1 := rfl
@[simp] theorem one_den : (1 : Rat).den = 1 := rfl
@[simp] theorem neg_zero : -(0 : Rat) = 0 := rfl
@[simp] theorem maybeNormalize_eq {num den g} (dvd_num dvd_den den_nz reduced) :
maybeNormalize num den g dvd_num dvd_den den_nz reduced =
{ num := num.divExact g dvd_num, den := den / g, den_nz, reduced } := by
@@ -357,7 +359,7 @@ theorem neg_normalize (n d z) : -normalize n d z = normalize (-n) d z := by
rfl
theorem neg_mkRat (n d) : -mkRat n d = mkRat (-n) d := by
if z : d = 0 then simp [z]; rfl else simp [ normalize_eq_mkRat z, neg_normalize]
if z : d = 0 then simp [z]; else simp [ normalize_eq_mkRat z, neg_normalize]
@[simp]
theorem neg_divInt (n d) : -(n /. d) = -n /. d := by
@@ -372,6 +374,15 @@ protected theorem add_neg_cancel (a : Rat) : a + -a = 0 := by
protected theorem add_right_cancel {a b : Rat} (c : Rat) (h : a + c = b + c) : a = b := by
simpa only [Rat.add_assoc, Rat.add_zero, Rat.add_neg_cancel] using congrArg (· + -c) h
protected theorem add_left_cancel (a : Rat) {b c : Rat} (h : a + b = a + c) : b = c := by
simp only [Rat.add_comm a] at h
exact Rat.add_right_cancel a h
protected theorem neg_add {a b : Rat} : -(a + b) = -a + -b := by
apply Rat.add_left_cancel (a := a + b)
rw [Rat.add_neg_cancel, Rat.add_comm a, Rat.add_assoc, Rat.add_assoc b,
Rat.add_neg_cancel, Rat.add_zero, Rat.add_neg_cancel]
theorem sub_def (a b : Rat) :
a - b = normalize (a.num * b.den - b.num * a.den) (a.den * b.den)
(Nat.mul_ne_zero a.den_nz b.den_nz) := by
@@ -393,6 +404,15 @@ theorem sub_def' (a b : Rat) : a - b = mkRat (a.num * b.den - b.num * a.den) (a.
protected theorem sub_eq_add_neg (a b : Rat) : a - b = a + -b := by
simp [add_def, sub_def, Int.neg_mul, Int.sub_eq_add_neg]
protected theorem sub_self {a : Rat} : a - a = 0 := by
simp only [Rat.sub_eq_add_neg, Rat.add_neg_cancel]
protected theorem add_sub_cancel {a b : Rat} : a + b - b = a := by
simp [Rat.sub_eq_add_neg, Rat.add_assoc, Rat.add_neg_cancel]
protected theorem sub_add_cancel {a b : Rat} : a - b + b = a := by
simp [Rat.sub_eq_add_neg, Rat.add_assoc, Rat.neg_add_cancel]
protected theorem neg_sub (a b : Rat) : -(a - b) = b - a := by
apply Rat.add_right_cancel (a - b)
rw [Rat.neg_add_cancel, Rat.sub_eq_add_neg, Rat.sub_eq_add_neg, Rat.add_assoc, Rat.add_assoc b,
@@ -747,6 +767,15 @@ protected theorem le_antisymm {a b : Rat} (hab : a ≤ b) (hba : b ≤ a) : a =
have := congrArg (· + b) (Rat.nonneg_antisymm hba hab)
simpa only [Rat.add_assoc, Rat.neg_add_cancel, Rat.zero_add, Rat.add_zero] using this
protected theorem le_antisymm_iff {a b : Rat} :
a = b a b b a := by
apply Iff.intro
· simp +contextual [Rat.le_refl]
· exact fun h => Rat.le_antisymm h.1 h.2
theorem eq_iff_mul_eq_mul {p q : Rat} : p = q p.num * q.den = q.num * p.den := by
simp [Rat.le_antisymm_iff, Rat.le_iff, Int.le_antisymm_iff]
protected theorem le_of_lt {a b : Rat} (ha : a < b) : a b :=
Rat.le_total.resolve_left (Rat.not_le.mpr ha)
@@ -764,6 +793,18 @@ protected theorem ne_of_gt {a b : Rat} (ha : a < b) : b ≠ a :=
protected theorem lt_of_le_of_ne {a b : Rat} (ha : a b) (hb : a b) : a < b :=
Rat.not_le.mp fun h => hb (Rat.le_antisymm ha h)
protected theorem lt_iff_le_and_not_ge {a b : Rat} : a < b a b ¬ b a := by
simpa [Rat.le_iff, Rat.lt_iff] using Int.le_of_lt
protected theorem lt_iff_le_and_ne {a b : Rat} : a < b a b a b := by
simp [Rat.lt_iff, Rat.le_iff, Rat.eq_iff_mul_eq_mul, Int.lt_iff_le_and_ne]
protected theorem le_iff_lt_or_eq {a b : Rat} : a b a < b a = b := by
simp [Rat.le_iff, Rat.lt_iff, Rat.eq_iff_mul_eq_mul, Int.le_iff_lt_or_eq]
protected theorem le_iff_eq_or_lt {a b : Rat} : a b a < b a = b := by
simp [Rat.le_iff_lt_or_eq, or_comm]
protected theorem add_le_add_left {a b c : Rat} : c + a c + b a b := by
rw [Rat.le_iff_sub_nonneg, Rat.le_iff_sub_nonneg a, propext_iff]
congr 1
@@ -775,6 +816,12 @@ protected theorem add_le_add_left {a b c : Rat} : c + a ≤ c + b ↔ a ≤ b :=
protected theorem add_le_add_right {a b c : Rat} : a + c b + c a b := by
rw [Rat.add_comm _ c, Rat.add_comm _ c, Rat.add_le_add_left]
protected theorem add_lt_add_left {a b c : Rat} : c + a < c + b a < b := by
simp [Rat.lt_iff_le_and_not_ge, Rat.add_le_add_left]
protected theorem add_lt_add_right {a b c : Rat} : a + c < b + c a < b := by
simp [Rat.lt_iff_le_and_not_ge, Rat.add_le_add_right]
protected theorem lt_iff_sub_pos (a b : Rat) : a < b 0 < b - a := by
simp only [ Rat.not_le]
apply not_congr
@@ -786,6 +833,36 @@ protected theorem lt_iff_sub_pos (a b : Rat) : a < b ↔ 0 < b - a := by
simpa [Rat.sub_eq_add_neg, Rat.add_left_comm a, Rat.add_neg_cancel]
using (Rat.add_le_add_left (c := a)).mpr h
@[simp]
protected theorem neg_neg (a : Rat) : -(-a) = a := by
apply Rat.le_antisymm <;> simp [Rat.le_iff]
@[simp]
protected theorem neg_le_neg_iff {a b : Rat} : -a -b b a := by
simp [Rat.le_iff, Int.neg_mul]
protected theorem neg_le_neg {a b : Rat} (h : a b) : -b -a := by
simpa
protected theorem neg_le_iff {a b : Rat} : -a b -b a := by
rw [ Rat.neg_neg a, Rat.neg_le_neg_iff, Rat.neg_neg a]
protected theorem le_neg_iff {a b : Rat} : a -b b -a := by
rw [ Rat.neg_neg a, Rat.neg_le_neg_iff, Rat.neg_neg a]
@[simp]
protected theorem neg_lt_neg_iff {a b : Rat} : -a < -b b < a := by
simp [Rat.lt_iff, Int.neg_mul]
protected theorem neg_lt_neg {a b : Rat} (h : a < b) : -b < -a := by
simpa
protected theorem neg_lt_iff {a b : Rat} : -a < b -b < a := by
rw [ Rat.neg_neg a, Rat.neg_lt_neg_iff, Rat.neg_neg a]
protected theorem lt_neg_iff {a b : Rat} : a < -b b < -a := by
rw [ Rat.neg_neg a, Rat.neg_lt_neg_iff, Rat.neg_neg a]
protected theorem mul_pos {a b : Rat} (ha : 0 < a) (hb : 0 < b) : 0 < a * b := by
refine Rat.lt_of_le_of_ne (Rat.mul_nonneg (Rat.le_of_lt ha) (Rat.le_of_lt hb)) ?_
simp [eq_comm, Rat.mul_eq_zero, Rat.ne_of_gt ha, Rat.ne_of_gt hb]
@@ -1115,6 +1192,56 @@ theorem le_floor_iff {x : Int} {a : Rat} : x ≤ a.floor ↔ (x : Rat) ≤ a :=
theorem floor_lt_iff {a : Rat} {x : Int} : a.floor < x a < (x : Rat) := by
rw [ Decidable.not_iff_not, Int.not_lt, le_floor_iff, Rat.not_lt]
theorem floor_add_le_floor_add_intCast {x : Rat} {y : Int} :
x.floor + y (x + y).floor := by
simpa [Rat.le_floor_iff, Rat.add_le_add_right] using Rat.floor_le x
protected theorem add_le_iff_le_sub {a b c : Rat} : a + b c a c - b := by
conv => rhs; rw [ Rat.add_le_add_right (c := b)]
simp [Rat.sub_add_cancel]
protected theorem le_sub_iff {a b c : Rat} : a c - b a + b c :=
Rat.add_le_iff_le_sub.symm
protected theorem le_add_iff_sub_le {a b c : Rat} : a b + c a - c b := by
conv => rhs; rw [ Rat.add_le_add_right (c := c)]
simp [Rat.sub_add_cancel]
protected theorem sub_right_le_iff_le_add {a b c : Rat} : a - c b a b + c :=
Rat.le_add_iff_sub_le.symm
protected theorem lt_sub_right_iff_add_lt {a b c : Rat} : a < c - b a + b < c := by
conv => lhs; rw [ Rat.add_lt_add_right (c := b)]
simp [Rat.sub_add_cancel]
protected theorem sub_lt_iff {a b c : Rat} : a - c < b a < b + c := by
conv => lhs; rw [ Rat.add_lt_add_right (c := c)]
simp [Rat.sub_add_cancel]
theorem floor_add_intCast {x : Rat} {y : Int} :
(x + y).floor = x.floor + y := by
apply Std.le_antisymm
· have := floor_add_le_floor_add_intCast (x := x + y) (y := - y)
simpa [ Rat.sub_eq_add_neg, Rat.add_sub_cancel, Int.le_add_iff_sub_le]
· apply floor_add_le_floor_add_intCast
theorem floor_add_one {x : Rat} :
(x + 1).floor = x.floor + 1 := by
simpa using floor_add_intCast
theorem floor_sub_one {x : Rat} :
(x - 1).floor = x.floor - 1 := by
simpa [Rat.sub_eq_add_neg] using floor_add_intCast
theorem lt_floor {x : Rat} :
x - 1 < x.floor := by
rw [ Rat.floor_lt_iff, Rat.floor_sub_one]
simp [Int.sub_lt_iff, Int.lt_add_one_iff]
/-!
# ceil
-/
theorem ceil_eq_neg_floor_neg (a : Rat) : a.ceil = -((-a).floor) := by
rw [Rat.ceil, Rat.floor]
simp only [neg_den, neg_num]
@@ -1129,4 +1256,125 @@ theorem ceil_eq_neg_floor_neg (a : Rat) : a.ceil = -((-a).floor) := by
@[simp]
theorem ceil_intCast (a : Int) : (a : Rat).ceil = a := rfl
-- TODO: reproduce the `floor` inequalities above for `ceil`
theorem ceil_le_iff {x : Rat} {y : Int} :
x.ceil y x y := by
simp [Rat.ceil_eq_neg_floor_neg, Int.neg_le_iff, Rat.le_floor_iff]
theorem lt_ceil_iff {x : Rat} {y : Int} :
y < x.ceil y < x := by
simp [Rat.ceil_eq_neg_floor_neg, Int.lt_neg_iff, Rat.floor_lt_iff]
theorem le_ceil {x : Rat} :
x x.ceil := by
simp only [Rat.ceil_eq_neg_floor_neg, Rat.intCast_neg, Rat.le_neg_iff, Rat.floor_le]
theorem ceil_add_intCast_le_ceil_add {x : Rat} {y : Int} :
(x + y).ceil x.ceil + y := by
simpa [Rat.ceil_eq_neg_floor_neg, Int.neg_le_iff, Rat.neg_add, Int.neg_add] using
floor_add_le_floor_add_intCast
theorem ceil_add_intCast {x : Rat} {y : Int} :
(x + y).ceil = x.ceil + y := by
simp [Rat.ceil_eq_neg_floor_neg, Rat.neg_add, Rat.intCast_neg, floor_add_intCast, Int.neg_add]
theorem ceil_add_one {x : Rat} :
(x + 1).ceil = x.ceil + 1 := by
simp [Rat.ceil_eq_neg_floor_neg, Rat.neg_add, Rat.sub_eq_add_neg, floor_sub_one,
Int.sub_eq_add_neg, Int.neg_add]
theorem ceil_sub_one {x : Rat} :
(x - 1).ceil = x.ceil - 1 := by
simp [Rat.ceil_eq_neg_floor_neg, Rat.sub_eq_add_neg, Rat.neg_add, floor_add_one,
Int.neg_add, Int.sub_eq_add_neg]
theorem ceil_lt {x : Rat} :
x.ceil < x + 1 := by
simpa [Rat.ceil_eq_neg_floor_neg, Rat.neg_lt_iff, Rat.neg_add, Rat.sub_eq_add_neg] using lt_floor
/-!
# abs
-/
@[simp, grind =]
protected theorem abs_zero :
(0 : Rat).abs = 0 := by
simp [Rat.abs]
@[simp]
protected theorem abs_nonneg {x : Rat} :
0 x.abs := by
simp only [Rat.abs]
split <;> rename_i hle
· assumption
· apply Rat.le_of_lt
simp only [Rat.not_le] at hle
rwa [Rat.lt_neg_iff, Rat.neg_zero]
protected theorem abs_of_nonneg {x : Rat} (h : 0 x) :
x.abs = x := by
rw [Rat.abs, if_pos h]
protected theorem abs_of_nonpos {x : Rat} (h : x 0) :
x.abs = -x := by
rw [Rat.abs]
split
· simp [show x = 0 from Rat.le_antisymm _ _]
· rfl
@[simp, grind =]
protected theorem abs_neg {x : Rat} :
(-x).abs = x.abs := by
simp only [Rat.abs]
split <;> split
· rw [Rat.le_neg_iff, Rat.neg_zero] at *
simp [show x = 0 from Rat.le_antisymm _ _]
· simp
· simp
· have : x < 0 := Rat.not_le.mp _
rw [Rat.le_neg_iff, Rat.neg_zero] at *
have : ¬ x 0 := _
apply this.elim
apply Rat.le_of_lt
assumption
protected theorem abs_sub_comm {x y : Rat} :
(x - y).abs = (y - x).abs := by
rw [ Rat.neg_sub, Rat.abs_neg]
@[simp]
protected theorem abs_eq_zero_iff {x : Rat} :
x.abs = 0 x = 0 := by
simp only [Rat.abs]
split
· rfl
· apply Iff.intro
· intro h
rw [ Rat.neg_neg (a := x), h, Rat.neg_zero]
· simp +contextual
protected theorem abs_pos_iff {x : Rat} :
0 < x.abs x 0 := by
apply Iff.intro
· intro hpos
by_cases h : 0 x
· rw [Rat.abs_of_nonneg h] at hpos
exact Rat.ne_of_gt hpos
· exact Rat.ne_of_lt (Rat.not_le.mp h)
· intro hne
simp only [ne_eq] at hne
rw [ Rat.abs_eq_zero_iff] at hne
apply Rat.lt_of_le_of_ne
· apply Rat.abs_nonneg
· exact .symm hne
/-!
# instances
-/
instance instAssociativeHAdd : Std.Associative (α := Rat) (· + ·) := Rat.add_assoc
instance instCommutativeHAdd : Std.Commutative (α := Rat) (· + ·) := Rat.add_comm
instance : Std.LawfulIdentity (· + ·) (0 : Rat) where
left_id := Rat.zero_add
right_id := Rat.add_zero
end Rat

View File

@@ -6,10 +6,10 @@ Authors: Paul Reichert
module
prelude
public import Init.Data.Slice.Array.Iterator
import all Init.Data.Array.Subarray
import all Init.Data.Slice.Array.Basic
import Init.Data.Slice.Lemmas
public import Init.Data.Slice.Array.Iterator
import all Init.Data.Slice.Array.Iterator
import all Init.Data.Slice.Operations
import all Init.Data.Range.Polymorphic.Iterators
@@ -20,11 +20,15 @@ public import Init.Data.Nat.MinMax
public import Init.Data.Slice.Array.Basic
import Init.Data.List.Nat.TakeDrop
import Init.Data.List.TakeDrop
public import Init.Data.Array.Subarray.Split
import all Init.Data.Array.Subarray.Split
import Init.Data.Slice.InternalLemmas
open Std Std.Iterators Std.PRange Std.Slice
namespace SubarrayIterator
set_option backward.isDefEq.respectTransparency false in
theorem step_eq {it : Iter (α := SubarrayIterator α) α} :
it.step = if h : it.1.xs.start < it.1.xs.stop then
haveI := it.1.xs.start_le_stop
@@ -63,6 +67,7 @@ theorem val_step_eq {it : Iter (α := SubarrayIterator α) α} :
simp only [step_eq]
split <;> simp
set_option backward.isDefEq.respectTransparency false in
theorem toList_eq {α : Type u} {it : Iter (α := SubarrayIterator α) α} :
it.toList =
(it.internalState.xs.array.toList.take it.internalState.xs.stop).drop it.internalState.xs.start := by
@@ -97,15 +102,17 @@ end SubarrayIterator
namespace Subarray
theorem internalIter_eq {α : Type u} {s : Subarray α} :
theorem Internal.iter_eq {α : Type u} {s : Subarray α} :
Internal.iter s = s :=
rfl
theorem toList_internalIter {α : Type u} {s : Subarray α} :
set_option backward.isDefEq.respectTransparency false in
theorem Internal.toList_iter {α : Type u} {s : Subarray α} :
(Internal.iter s).toList =
(s.array.toList.take s.stop).drop s.start := by
simp [SubarrayIterator.toList_eq, Internal.iter_eq_toIteratorIter, ToIterator.iter_eq]
set_option backward.isDefEq.respectTransparency false in
public instance : LawfulSliceSize (Internal.SubarrayData α) where
lawful s := by
simp [SliceSize.size, ToIterator.iter_eq,
@@ -137,6 +144,24 @@ public theorem forIn_toArray {α : Type u} {s : Subarray α}
ForIn.forIn s.toArray init f = ForIn.forIn s init f :=
Slice.forIn_toArray
public theorem sliceFoldlM_eq_foldlM {m} [Monad m] {α : Type u} {s : Subarray α}
{f : β α m β} :
s.foldlM (init := init) f = Slice.foldlM (s := s) (init := init) f :=
(rfl)
public theorem sliceFoldl_eq_foldl {α : Type u} {s : Subarray α} {f : β α β} :
s.foldl (init := init) f = Slice.foldl (s := s) (init := init) f :=
(rfl)
public theorem foldlM_toList {m} [Monad m] {α : Type u} {s : Subarray α} {f}
[LawfulMonad m] :
s.toList.foldlM (init := init) f = s.foldlM (m := m) (init := init) f := by
simp [Std.Slice.foldlM_toList, sliceFoldlM_eq_foldlM]
public theorem foldl_toList {α : Type u} {s : Subarray α} {f} :
s.toList.foldl (init := init) f = s.foldl (init := init) f := by
simp [Std.Slice.foldl_toList, sliceFoldl_eq_foldl]
end Subarray
public theorem Array.toSubarray_eq_toSubarray_of_min_eq_min {xs : Array α}
@@ -202,7 +227,7 @@ public theorem Subarray.toList_eq {xs : Subarray α} :
change aslice.toList = _
have : aslice.toList = lslice.toList := by
rw [ListSlice.toList_eq]
simp +instances only [aslice, lslice, Std.Slice.toList, toList_internalIter]
simp +instances only [aslice, lslice, Std.Slice.toList, Internal.toList_iter]
apply List.ext_getElem
· have : stop - start array.size - start := by omega
simp [Subarray.start, Subarray.stop, *, Subarray.array]
@@ -210,11 +235,65 @@ public theorem Subarray.toList_eq {xs : Subarray α} :
simp [Subarray.array, Subarray.start, Subarray.stop]
simp +instances [this, ListSlice.toList_eq, lslice]
-- TODO: The current `List.extract_eq_drop_take` should be called `List.extract_eq_take_drop`
private theorem Std.Internal.List.extract_eq_drop_take' {l : List α} {start stop : Nat} :
l.extract start stop = (l.take stop).drop start := by
simp [List.take_drop]
by_cases start stop
· simp [*]
· have h₁ : stop - start = 0 := by omega
have h₂ : min stop l.length stop := by omega
simp only [Nat.add_zero, List.drop_take_self, List.nil_eq, List.drop_eq_nil_iff,
List.length_take, ge_iff_le, h₁]
omega
public theorem Subarray.toList_eq_drop_take {xs : Subarray α} :
xs.toList = (xs.array.toList.take xs.stop).drop xs.start := by
rw [Subarray.toList_eq, Array.toList_extract, Std.Internal.List.extract_eq_drop_take']
@[grind =]
public theorem Subarray.size_eq {xs : Subarray α} :
xs.size = xs.stop - xs.start := by
simp [Subarray.size]
@[simp, grind =]
public theorem Subarray.size_drop {xs : Subarray α} :
(xs.drop i).size = xs.size - i := by
simp only [size, stop, drop, start]
omega
@[simp, grind =]
public theorem Subarray.size_take {xs : Subarray α} :
(xs.take i).size = min i xs.size := by
simp only [size, stop, take, start]
omega
public theorem Subarray.sliceSize_eq_size {xs : Subarray α} :
Std.Slice.size xs = xs.size := by
rfl
public theorem Subarray.getElem_eq_getElem_array {xs : Subarray α} {h : i < xs.size} :
xs[i] = xs.array[xs.start + i]'(by simp only [size] at h; have := xs.stop_le_array_size; omega) := by
rfl
public theorem Subarray.getElem_toList {xs : Subarray α} {h : i < xs.toList.length} :
xs.toList[i]'h = xs[i]'(by simpa using h) := by
simp [getElem_eq_getElem_array, toList_eq_drop_take]; rfl
public theorem Subarray.getElem_eq_getElem_toList {xs : Subarray α} {h : i < xs.size} :
xs[i]'h = xs.toList[i]'(by simpa using h) := by
rw [getElem_toList]
@[simp, grind =]
public theorem Subarray.toList_drop {xs : Subarray α} :
(xs.drop n).toList = xs.toList.drop n := by
simp [Subarray.toList_eq_drop_take, drop, start, stop, array]
@[simp, grind =]
public theorem Subarray.toList_take {xs : Subarray α} :
(xs.take n).toList = xs.toList.take n := by
simp [Subarray.toList_eq_drop_take, take, start, stop, array, List.take_drop, List.take_take]
@[simp, grind =]
public theorem Subarray.toArray_toList {xs : Subarray α} :
xs.toList.toArray = xs.toArray := by
@@ -269,7 +348,7 @@ public theorem toList_mkSlice_rco {xs : Array α} {lo hi : Nat} :
public theorem toArray_mkSlice_rco {xs : Array α} {lo hi : Nat} :
xs[lo...hi].toArray = xs.extract lo hi := by
simp only [ Subarray.toArray_toList, toList_mkSlice_rco]
rw [show xs = xs.toList.toArray by simp, List.extract_toArray, List.extract_eq_drop_take]
rw [show xs = xs.toList.toArray by simp, List.extract_toArray, List.extract_eq_take_drop]
simp only [List.take_drop, mk.injEq]
by_cases h : lo hi
· congr 1
@@ -297,169 +376,164 @@ public theorem array_mkSlice_rcc {xs : Array α} {lo hi : Nat} :
xs[lo...=hi].array = xs := by
simp [Std.Rcc.Sliceable.mkSlice, Array.toSubarray, apply_dite, Subarray.array]
@[simp]
@[simp, grind =]
public theorem start_mkSlice_rcc {xs : Array α} {lo hi : Nat} :
xs[lo...=hi].start = min lo (min (hi + 1) xs.size) := by
simp [Std.Rco.Sliceable.mkSlice]
@[simp]
@[simp, grind =]
public theorem stop_mkSlice_rcc {xs : Array α} {lo hi : Nat} :
xs[lo...=hi].stop = min (hi + 1) xs.size := by
simp [Std.Rco.Sliceable.mkSlice]
@[simp]
@[simp, grind =]
public theorem toList_mkSlice_rcc {xs : Array α} {lo hi : Nat} :
xs[lo...=hi].toList = (xs.toList.take (hi + 1)).drop lo := by
simp
@[simp]
@[simp, grind =]
public theorem toArray_mkSlice_rcc {xs : Array α} {lo hi : Nat} :
xs[lo...=hi].toArray = xs.extract lo (hi + 1) := by
simp
@[simp]
@[simp, grind =]
public theorem size_mkSlice_rcc {xs : Array α} {lo hi : Nat} :
xs[lo...=hi].size = min (hi + 1) xs.size - lo := by
simp [ Subarray.length_toList]
@[simp]
@[simp, grind =]
public theorem array_mkSlice_rci {xs : Array α} {lo : Nat} :
xs[lo...*].array = xs := by
simp [Std.Rci.Sliceable.mkSlice, Array.toSubarray, apply_dite, Subarray.array]
@[simp]
@[simp, grind =]
public theorem start_mkSlice_rci {xs : Array α} {lo : Nat} :
xs[lo...*].start = min lo xs.size := by
simp [Std.Rci.Sliceable.mkSlice, Std.Rci.HasRcoIntersection.intersection]
@[simp]
@[simp, grind =]
public theorem stop_mkSlice_rci {xs : Array α} {lo : Nat} :
xs[lo...*].stop = xs.size := by
simp [Std.Rci.Sliceable.mkSlice, Std.Rci.HasRcoIntersection.intersection]
@[simp, grind =]
public theorem mkSlice_rci_eq_mkSlice_rco {xs : Array α} {lo : Nat} :
xs[lo...*] = xs[lo...xs.size] := by
simp [Std.Rci.Sliceable.mkSlice, Std.Rco.Sliceable.mkSlice, Std.Rci.HasRcoIntersection.intersection]
public theorem mkSlice_rci_eq_mkSlice_rco_min {xs : Array α} {lo : Nat} :
xs[lo...*] = xs[(min lo xs.size)...xs.size] := by
simp [mkSlice_rco_eq_mkSlice_rco_min]
simp [mkSlice_rci_eq_mkSlice_rco, mkSlice_rco_eq_mkSlice_rco_min]
@[simp]
@[simp, grind =]
public theorem toList_mkSlice_rci {xs : Array α} {lo : Nat} :
xs[lo...*].toList = xs.toList.drop lo := by
rw [mkSlice_rci_eq_mkSlice_rco, toList_mkSlice_rco, Array.length_toList, List.take_length]
@[simp]
@[simp, grind =]
public theorem toArray_mkSlice_rci {xs : Array α} {lo : Nat} :
xs[lo...*].toArray = xs.extract lo := by
simp
simp [mkSlice_rci_eq_mkSlice_rco]
@[simp, grind =]
public theorem size_mkSlice_rci {xs : Array α} {lo : Nat} :
xs[lo...*].size = xs.size - lo := by
simp [ Subarray.length_toList]
@[simp]
@[simp, grind =]
public theorem array_mkSlice_roo {xs : Array α} {lo hi : Nat} :
xs[lo<...hi].array = xs := by
simp [Std.Roo.Sliceable.mkSlice, Array.toSubarray, apply_dite, Subarray.array]
@[simp]
@[simp, grind =]
public theorem start_mkSlice_roo {xs : Array α} {lo hi : Nat} :
xs[lo<...hi].start = min (lo + 1) (min hi xs.size) := by
simp [Std.Roo.Sliceable.mkSlice]
@[simp]
@[simp, grind =]
public theorem stop_mkSlice_roo {xs : Array α} {lo hi : Nat} :
xs[lo<...hi].stop = min hi xs.size := by
simp [Std.Roo.Sliceable.mkSlice]
@[simp, grind =]
public theorem mkSlice_roo_eq_mkSlice_rco {xs : Array α} {lo hi : Nat} :
xs[lo<...hi] = xs[(lo + 1)...hi] := by
simp [Std.Roo.Sliceable.mkSlice, Std.Rco.Sliceable.mkSlice]
public theorem mkSlice_roo_eq_mkSlice_roo_min {xs : Array α} {lo hi : Nat} :
xs[lo<...hi] = xs[(min (lo + 1) (min hi xs.size))...(min hi xs.size)] := by
simp [mkSlice_rco_eq_mkSlice_rco_min]
simp [mkSlice_roo_eq_mkSlice_rco, mkSlice_rco_eq_mkSlice_rco_min]
@[simp]
@[simp, grind =]
public theorem toList_mkSlice_roo {xs : Array α} {lo hi : Nat} :
xs[lo<...hi].toList = (xs.toList.take hi).drop (lo + 1) := by
simp
simp [mkSlice_roo_eq_mkSlice_rco]
@[simp]
@[simp, grind =]
public theorem toArray_mkSlice_roo {xs : Array α} {lo hi : Nat} :
xs[lo<...hi].toArray = xs.extract (lo + 1) hi := by
simp
simp [mkSlice_roo_eq_mkSlice_rco]
@[simp]
@[simp, grind =]
public theorem size_mkSlice_roo {xs : Array α} {lo hi : Nat} :
xs[lo<...hi].size = min hi xs.size - (lo + 1) := by
simp [ Subarray.length_toList]
@[simp]
@[simp, grind =]
public theorem array_mkSlice_roc {xs : Array α} {lo hi : Nat} :
xs[lo<...=hi].array = xs := by
simp [Std.Roc.Sliceable.mkSlice, Array.toSubarray, apply_dite, Subarray.array]
@[simp]
@[simp, grind =]
public theorem start_mkSlice_roc {xs : Array α} {lo hi : Nat} :
xs[lo<...=hi].start = min (lo + 1) (min (hi + 1) xs.size) := by
simp [Std.Roc.Sliceable.mkSlice]
@[simp]
@[simp, grind =]
public theorem stop_mkSlice_roc {xs : Array α} {lo hi : Nat} :
xs[lo<...=hi].stop = min (hi + 1) xs.size := by
simp [Std.Roc.Sliceable.mkSlice]
@[simp]
public theorem mkSlice_roc_eq_mkSlice_roo {xs : Array α} {lo hi : Nat} :
xs[lo<...=hi] = xs[lo<...(hi + 1)] := by
simp [Std.Roc.Sliceable.mkSlice, Std.Roo.Sliceable.mkSlice]
@[grind =]
public theorem mkSlice_roc_eq_mkSlice_rco {xs : Array α} {lo hi : Nat} :
xs[lo<...=hi] = xs[(lo + 1)...(hi + 1)] := by
simp
simp [mkSlice_roc_eq_mkSlice_roo, mkSlice_roo_eq_mkSlice_rco]
public theorem mkSlice_roc_eq_mkSlice_roo_min {xs : Array α} {lo hi : Nat} :
xs[lo<...=hi] = xs[(min (lo + 1) (min (hi + 1) xs.size))...(min (hi + 1) xs.size)] := by
simp [mkSlice_rco_eq_mkSlice_rco_min]
simp [mkSlice_roc_eq_mkSlice_rco, mkSlice_rco_eq_mkSlice_rco_min]
@[simp]
@[simp, grind =]
public theorem toList_mkSlice_roc {xs : Array α} {lo hi : Nat} :
xs[lo<...=hi].toList = (xs.toList.take (hi + 1)).drop (lo + 1) := by
simp
simp [mkSlice_roc_eq_mkSlice_rco]
@[simp]
@[simp, grind =]
public theorem toArray_mkSlice_roc {xs : Array α} {lo hi : Nat} :
xs[lo<...=hi].toArray = xs.extract (lo + 1) (hi + 1) := by
simp
simp [mkSlice_roc_eq_mkSlice_rco]
@[simp]
@[simp, grind =]
public theorem size_mkSlice_roc {xs : Array α} {lo hi : Nat} :
xs[lo<...=hi].size = min (hi + 1) xs.size - (lo + 1) := by
simp [ Subarray.length_toList]
@[simp]
@[simp, grind =]
public theorem array_mkSlice_roi {xs : Array α} {lo : Nat} :
xs[lo<...*].array = xs := by
simp [Std.Roi.Sliceable.mkSlice, Array.toSubarray, apply_dite, Subarray.array]
@[simp]
@[simp, grind =]
public theorem start_mkSlice_roi {xs : Array α} {lo : Nat} :
xs[lo<...*].start = min (lo + 1) xs.size := by
simp [Std.Roi.Sliceable.mkSlice, Std.Roi.HasRcoIntersection.intersection]
@[simp]
@[simp, grind =]
public theorem stop_mkSlice_roi {xs : Array α} {lo : Nat} :
xs[lo...*].stop = xs.size := by
simp [Std.Rco.Sliceable.mkSlice]
xs[lo<...*].stop = xs.size := by
simp [Std.Roi.Sliceable.mkSlice, Std.Roi.HasRcoIntersection.intersection]
@[simp]
public theorem mkSlice_roi_eq_mkSlice_rci {xs : Array α} {lo : Nat} :
xs[lo<...*] = xs[(lo + 1)...*] := by
simp [Std.Roi.Sliceable.mkSlice, Std.Roi.HasRcoIntersection.intersection,
@@ -467,33 +541,33 @@ public theorem mkSlice_roi_eq_mkSlice_rci {xs : Array α} {lo : Nat} :
public theorem mkSlice_roi_eq_mkSlice_roo {xs : Array α} {lo : Nat} :
xs[lo<...*] = xs[lo<...xs.size] := by
simp [mkSlice_rci_eq_mkSlice_rco]
simp [mkSlice_roi_eq_mkSlice_rci, mkSlice_rci_eq_mkSlice_rco,
mkSlice_roo_eq_mkSlice_rco]
@[grind =]
public theorem mkSlice_roi_eq_mkSlice_rco {xs : Array α} {lo : Nat} :
xs[lo<...*] = xs[(lo + 1)...xs.size] := by
simp [mkSlice_rci_eq_mkSlice_rco]
simp [mkSlice_roi_eq_mkSlice_rci, mkSlice_rci_eq_mkSlice_rco]
public theorem mkSlice_roi_eq_mkSlice_roo_min {xs : Array α} {lo : Nat} :
xs[lo<...*] = xs[(min (lo + 1) xs.size)...xs.size] := by
simp [mkSlice_rco_eq_mkSlice_rco_min]
simp [mkSlice_roi_eq_mkSlice_rco, mkSlice_rco_eq_mkSlice_rco_min]
@[simp]
@[simp, grind =]
public theorem toList_mkSlice_roi {xs : Array α} {lo : Nat} :
xs[lo<...*].toList = xs.toList.drop (lo + 1) := by
rw [mkSlice_roi_eq_mkSlice_rci, toList_mkSlice_rci]
simp only [mkSlice_roi_eq_mkSlice_rci, toList_mkSlice_rci]
@[simp]
@[simp, grind =]
public theorem toArray_mkSlice_roi {xs : Array α} {lo : Nat} :
xs[lo<...*].toArray = xs.drop (lo + 1) := by
simp
simp [mkSlice_roi_eq_mkSlice_rci]
@[simp]
@[simp, grind =]
public theorem size_mkSlice_roi {xs : Array α} {lo : Nat} :
xs[lo<...*].size = xs.size - (lo + 1) := by
simp [ Subarray.length_toList]
@[simp]
@[simp, grind =]
public theorem array_mkSlice_rio {xs : Array α} {hi : Nat} :
xs[*...hi].array = xs := by
simp [Std.Rio.Sliceable.mkSlice, Array.toSubarray, apply_dite, Subarray.array]
@@ -503,36 +577,35 @@ public theorem start_mkSlice_rio {xs : Array α} {hi : Nat} :
xs[*...hi].start = 0 := by
simp [Std.Rio.Sliceable.mkSlice]
@[simp]
@[simp, grind =]
public theorem stop_mkSlice_rio {xs : Array α} {hi : Nat} :
xs[*...hi].stop = min hi xs.size := by
simp [Std.Rio.Sliceable.mkSlice]
@[simp, grind =]
public theorem mkSlice_rio_eq_mkSlice_rco {xs : Array α} {hi : Nat} :
xs[*...hi] = xs[0...hi] := by
simp [Std.Rio.Sliceable.mkSlice, Std.Rco.Sliceable.mkSlice]
public theorem mkSlice_rio_eq_mkSlice_rio_min {xs : Array α} {hi : Nat} :
xs[*...hi] = xs[*...(min hi xs.size)] := by
simp [mkSlice_rco_eq_mkSlice_rco_min]
simp [mkSlice_rio_eq_mkSlice_rco, mkSlice_rco_eq_mkSlice_rco_min]
@[simp]
@[simp, grind =]
public theorem toList_mkSlice_rio {xs : Array α} {hi : Nat} :
xs[*...hi].toList = xs.toList.take hi := by
simp
simp [mkSlice_rio_eq_mkSlice_rco]
@[simp]
@[simp, grind =]
public theorem toArray_mkSlice_rio {xs : Array α} {hi : Nat} :
xs[*...hi].toArray = xs.extract 0 hi := by
simp
simp [mkSlice_rio_eq_mkSlice_rco]
@[simp]
@[simp, grind =]
public theorem size_mkSlice_rio {xs : Array α} {hi : Nat} :
xs[*...hi].size = min hi xs.size := by
simp [ Subarray.length_toList]
@[simp]
@[simp, grind =]
public theorem array_mkSlice_ric {xs : Array α} {hi : Nat} :
xs[*...=hi].array = xs := by
simp [Std.Ric.Sliceable.mkSlice, Array.toSubarray, apply_dite, Subarray.array]
@@ -542,41 +615,39 @@ public theorem start_mkSlice_ric {xs : Array α} {hi : Nat} :
xs[*...=hi].start = 0 := by
simp [Std.Ric.Sliceable.mkSlice]
@[simp]
@[simp, grind =]
public theorem stop_mkSlice_ric {xs : Array α} {hi : Nat} :
xs[*...=hi].stop = min (hi + 1) xs.size := by
simp [Std.Ric.Sliceable.mkSlice]
@[simp]
public theorem mkSlice_ric_eq_mkSlice_rio {xs : Array α} {hi : Nat} :
xs[*...=hi] = xs[*...(hi + 1)] := by
simp [Std.Ric.Sliceable.mkSlice, Std.Rio.Sliceable.mkSlice]
@[grind =]
public theorem mkSlice_ric_eq_mkSlice_rco {xs : Array α} {hi : Nat} :
xs[*...=hi] = xs[0...(hi + 1)] := by
simp
simp [mkSlice_ric_eq_mkSlice_rio, mkSlice_rio_eq_mkSlice_rco]
public theorem mkSlice_ric_eq_mkSlice_rio_min {xs : Array α} {hi : Nat} :
xs[*...=hi] = xs[*...(min (hi + 1) xs.size)] := by
simp [mkSlice_rco_eq_mkSlice_rco_min]
simp [mkSlice_ric_eq_mkSlice_rco, mkSlice_rco_eq_mkSlice_rco_min,
mkSlice_rio_eq_mkSlice_rco]
@[simp]
@[simp, grind =]
public theorem toList_mkSlice_ric {xs : Array α} {hi : Nat} :
xs[*...=hi].toList = xs.toList.take (hi + 1) := by
simp
simp [mkSlice_ric_eq_mkSlice_rco]
@[simp]
@[simp, grind =]
public theorem toArray_mkSlice_ric {xs : Array α} {hi : Nat} :
xs[*...=hi].toArray = xs.extract 0 (hi + 1) := by
simp
simp [mkSlice_ric_eq_mkSlice_rco]
@[simp]
@[simp, grind =]
public theorem size_mkSlice_ric {xs : Array α} {hi : Nat} :
xs[*...=hi].size = min (hi + 1) xs.size := by
simp [ Subarray.length_toList]
@[simp]
public theorem mkSlice_rii_eq_mkSlice_rci {xs : Array α} :
xs[*...*] = xs[0...*] := by
simp [Std.Rii.Sliceable.mkSlice, Std.Rci.Sliceable.mkSlice,
@@ -584,41 +655,40 @@ public theorem mkSlice_rii_eq_mkSlice_rci {xs : Array α} :
public theorem mkSlice_rii_eq_mkSlice_rio {xs : Array α} :
xs[*...*] = xs[*...xs.size] := by
simp [mkSlice_rci_eq_mkSlice_rco]
simp [mkSlice_rii_eq_mkSlice_rci, mkSlice_rci_eq_mkSlice_rco, mkSlice_rio_eq_mkSlice_rco]
@[grind =]
public theorem mkSlice_rii_eq_mkSlice_rco {xs : Array α} :
xs[*...*] = xs[0...xs.size] := by
simp
simp [mkSlice_rii_eq_mkSlice_rio, mkSlice_rio_eq_mkSlice_rco]
public theorem mkSlice_rii_eq_mkSlice_rio_min {xs : Array α} :
xs[*...*] = xs[*...xs.size] := by
simp [mkSlice_rco_eq_mkSlice_rco_min]
simp [mkSlice_rii_eq_mkSlice_rco, mkSlice_rco_eq_mkSlice_rco_min, mkSlice_rio_eq_mkSlice_rco]
@[simp, grind =]
public theorem toList_mkSlice_rii {xs : Array α} :
xs[*...*].toList = xs.toList := by
rw [mkSlice_rii_eq_mkSlice_rci, toList_mkSlice_rci, List.drop_zero]
@[simp]
@[simp, grind =]
public theorem toArray_mkSlice_rii {xs : Array α} :
xs[*...*].toArray = xs := by
simp
simp [mkSlice_rii_eq_mkSlice_rco]
@[simp, grind =]
public theorem size_mkSlice_rii {xs : Array α} :
xs[*...*].size = xs.size := by
simp [ Subarray.length_toList]
@[simp]
@[simp, grind =]
public theorem array_mkSlice_rii {xs : Array α} :
xs[*...*].array = xs := by
simp
simp [mkSlice_rii_eq_mkSlice_rco]
@[simp, grind =]
public theorem start_mkSlice_rii {xs : Array α} :
xs[*...*].start = 0 := by
simp
simp [mkSlice_rii_eq_mkSlice_rco]
@[simp, grind =]
public theorem stop_mkSlice_rii {xs : Array α} :
@@ -643,161 +713,180 @@ public theorem toList_mkSlice_rco {xs : Subarray α} {lo hi : Nat} :
@[simp, grind =]
public theorem toArray_mkSlice_rco {xs : Subarray α} {lo hi : Nat} :
xs[lo...hi].toArray = xs.toArray.extract lo hi := by
simp [ Subarray.toArray_toList, List.drop_take]
simp [ Subarray.toArray_toList, Std.Internal.List.extract_eq_drop_take']
@[simp, grind =]
public theorem size_mkSlice_rco {xs : Subarray α} {lo hi : Nat} :
xs[lo...hi].size = min hi xs.size - lo := by
simp [ Subarray.length_toList]
public theorem mkSlice_rcc_eq_mkSlice_rco {xs : Subarray α} {lo hi : Nat} :
xs[lo...=hi] = xs[lo...(hi + 1)] := by
simp [Std.Rcc.Sliceable.mkSlice, Std.Rco.Sliceable.mkSlice,
Std.Rcc.HasRcoIntersection.intersection, Std.Rco.HasRcoIntersection.intersection]
@[simp]
public theorem toList_mkSlice_rcc {xs : Subarray α} {lo hi : Nat} :
xs[lo...=hi].toList = (xs.toList.take (hi + 1)).drop lo := by
simp
@[simp]
public theorem toArray_mkSlice_rcc {xs : Subarray α} {lo hi : Nat} :
xs[lo...=hi].toArray = xs.toArray.extract lo (hi + 1) := by
simp
simp [Rcc.Sliceable.mkSlice, Rco.Sliceable.mkSlice,
Rcc.HasRcoIntersection.intersection, Rco.HasRcoIntersection.intersection]
@[simp, grind =]
public theorem toList_mkSlice_rcc {xs : Subarray α} {lo hi : Nat} :
xs[lo...=hi].toList = (xs.toList.take (hi + 1)).drop lo := by
simp [mkSlice_rcc_eq_mkSlice_rco]
@[simp, grind =]
public theorem toArray_mkSlice_rcc {xs : Subarray α} {lo hi : Nat} :
xs[lo...=hi].toArray = xs.toArray.extract lo (hi + 1) := by
simp [mkSlice_rcc_eq_mkSlice_rco]
@[simp, grind =]
public theorem size_mkSlice_rcc {xs : Subarray α} {lo hi : Nat} :
xs[lo...=hi].size = min (hi + 1) xs.size - lo := by
simp [ Subarray.length_toList]
public theorem mkSlice_rci_eq_mkSlice_rco {xs : Subarray α} {lo : Nat} :
xs[lo...*] = xs[lo...xs.size] := by
simp [Std.Rci.Sliceable.mkSlice, Std.Rco.Sliceable.mkSlice,
Std.Rci.HasRcoIntersection.intersection, Std.Rco.HasRcoIntersection.intersection]
simp [Rci.Sliceable.mkSlice, Rco.Sliceable.mkSlice,
Rci.HasRcoIntersection.intersection, Rco.HasRcoIntersection.intersection]
@[simp]
@[simp, grind =]
public theorem toList_mkSlice_rci {xs : Subarray α} {lo : Nat} :
xs[lo...*].toList = xs.toList.drop lo := by
rw [mkSlice_rci_eq_mkSlice_rco, toList_mkSlice_rco, Subarray.length_toList, List.take_length]
@[simp]
@[simp, grind =]
public theorem toArray_mkSlice_rci {xs : Subarray α} {lo : Nat} :
xs[lo...*].toArray = xs.toArray.extract lo := by
simp
simp [mkSlice_rci_eq_mkSlice_rco]
@[simp, grind =]
public theorem size_mkSlice_rci {xs : Subarray α} {lo : Nat} :
xs[lo...*].size = xs.size - lo := by
simp [ Subarray.length_toList]
@[simp]
public theorem mkSlice_roc_eq_mkSlice_roo {xs : Subarray α} {lo hi : Nat} :
xs[lo<...=hi] = xs[lo<...(hi + 1)] := by
simp [Std.Roc.Sliceable.mkSlice, Std.Roo.Sliceable.mkSlice,
Std.Roc.HasRcoIntersection.intersection, Std.Roo.HasRcoIntersection.intersection]
@[simp, grind =]
public theorem mkSlice_roo_eq_mkSlice_rco {xs : Subarray α} {lo hi : Nat} :
xs[lo<...hi] = xs[(lo + 1)...hi] := by
simp [Std.Roo.Sliceable.mkSlice, Std.Rco.Sliceable.mkSlice,
Std.Roo.HasRcoIntersection.intersection, Std.Rco.HasRcoIntersection.intersection]
@[grind =]
public theorem mkSlice_roc_eq_mkSlice_rco {xs : Subarray α} {lo hi : Nat} :
xs[lo<...=hi] = xs[(lo + 1)...(hi + 1)] := by
simp
simp [mkSlice_roc_eq_mkSlice_roo, mkSlice_roo_eq_mkSlice_rco]
@[simp]
@[simp, grind =]
public theorem toList_mkSlice_roo {xs : Subarray α} {lo hi : Nat} :
xs[lo<...hi].toList = (xs.toList.take hi).drop (lo + 1) := by
simp
simp [mkSlice_roo_eq_mkSlice_rco]
@[simp]
@[simp, grind =]
public theorem toArray_mkSlice_roo {xs : Subarray α} {lo hi : Nat} :
xs[lo<...hi].toArray = xs.toArray.extract (lo + 1) hi := by
simp
simp [mkSlice_roo_eq_mkSlice_rco]
@[simp, grind =]
public theorem size_mkSlice_roo {xs : Subarray α} {lo hi : Nat} :
xs[lo<...hi].size = min hi xs.size - (lo + 1) := by
simp [ Subarray.length_toList]
@[simp]
public theorem mkSlice_roc_eq_mkSlice_rcc {xs : Subarray α} {lo hi : Nat} :
xs[lo<...=hi] = xs[(lo + 1)...=hi] := by
simp
simp [mkSlice_roc_eq_mkSlice_rco, mkSlice_rcc_eq_mkSlice_rco]
@[simp]
@[simp, grind =]
public theorem toList_mkSlice_roc {xs : Subarray α} {lo hi : Nat} :
xs[lo<...=hi].toList = (xs.toList.take (hi + 1)).drop (lo + 1) := by
simp
simp [mkSlice_roc_eq_mkSlice_rco]
@[simp]
@[simp, grind =]
public theorem toArray_mkSlice_roc {xs : Subarray α} {lo hi : Nat} :
xs[lo<...=hi].toArray = xs.toArray.extract (lo + 1) (hi + 1) := by
simp
simp [mkSlice_roc_eq_mkSlice_rco]
@[simp, grind =]
public theorem size_mkSlice_roc {xs : Subarray α} {lo hi : Nat} :
xs[lo<...=hi].size = min (hi + 1) xs.size - (lo + 1) := by
simp [ Subarray.length_toList]
@[simp]
public theorem mkSlice_roi_eq_mkSlice_rci {xs : Subarray α} {lo : Nat} :
xs[lo<...*] = xs[(lo + 1)...*] := by
simp [Std.Roi.Sliceable.mkSlice, Std.Rci.Sliceable.mkSlice,
Std.Roi.HasRcoIntersection.intersection, Std.Rci.HasRcoIntersection.intersection]
@[grind =]
public theorem mkSlice_roi_eq_mkSlice_rco {xs : Subarray α} {lo : Nat} :
xs[lo<...*] = xs[(lo + 1)...xs.size] := by
simp
simp [mkSlice_roi_eq_mkSlice_rci, mkSlice_rci_eq_mkSlice_rco]
@[simp]
@[simp, grind =]
public theorem toList_mkSlice_roi {xs : Subarray α} {lo : Nat} :
xs[lo<...*].toList = xs.toList.drop (lo + 1) := by
rw [mkSlice_roi_eq_mkSlice_rci, toList_mkSlice_rci]
@[simp]
@[simp, grind =]
public theorem toArray_mkSlice_roi {xs : Subarray α} {lo : Nat} :
xs[lo<...*].toArray = xs.toArray.extract (lo + 1) := by
simp
simp [mkSlice_roi_eq_mkSlice_rco]
@[simp, grind =]
public theorem size_mkSlice_roi {xs : Subarray α} {lo : Nat} :
xs[lo<...*].size = xs.size - (lo + 1) := by
simp [ Subarray.length_toList]
@[simp]
public theorem mkSlice_ric_eq_mkSlice_rio {xs : Subarray α} {hi : Nat} :
xs[*...=hi] = xs[*...(hi + 1)] := by
simp [Std.Ric.Sliceable.mkSlice, Std.Rio.Sliceable.mkSlice,
Std.Ric.HasRcoIntersection.intersection, Std.Rio.HasRcoIntersection.intersection]
@[simp, grind =]
public theorem mkSlice_rio_eq_mkSlice_rco {xs : Subarray α} {hi : Nat} :
xs[*...hi] = xs[0...hi] := by
simp [Std.Rio.Sliceable.mkSlice, Std.Rco.Sliceable.mkSlice,
Std.Rio.HasRcoIntersection.intersection, Std.Rco.HasRcoIntersection.intersection]
@[grind =]
public theorem mkSlice_ric_eq_mkSlice_rco {xs : Subarray α} {hi : Nat} :
xs[*...=hi] = xs[0...(hi + 1)] := by
simp
simp [mkSlice_ric_eq_mkSlice_rio, mkSlice_rio_eq_mkSlice_rco]
@[simp]
@[simp, grind =]
public theorem toList_mkSlice_rio {xs : Subarray α} {hi : Nat} :
xs[*...hi].toList = xs.toList.take hi := by
simp
simp [mkSlice_rio_eq_mkSlice_rco]
@[simp]
@[simp, grind =]
public theorem toArray_mkSlice_rio {xs : Subarray α} {hi : Nat} :
xs[*...hi].toArray = xs.toArray.extract 0 hi := by
simp
simp [mkSlice_rio_eq_mkSlice_rco]
@[simp, grind =]
public theorem size_mkSlice_rio {xs : Subarray α} {hi : Nat} :
xs[*...hi].size = min hi xs.size := by
simp [ Subarray.length_toList]
@[simp]
public theorem mkSlice_ric_eq_mkSlice_rcc {xs : Subarray α} {hi : Nat} :
xs[*...=hi] = xs[0...=hi] := by
simp [Std.Ric.Sliceable.mkSlice, Std.Rco.Sliceable.mkSlice,
Std.Ric.HasRcoIntersection.intersection, Std.Rco.HasRcoIntersection.intersection]
@[simp]
public theorem toList_mkSlice_ric {xs : Subarray α} {hi : Nat} :
xs[*...=hi].toList = xs.toList.take (hi + 1) := by
simp
@[simp]
public theorem toArray_mkSlice_ric {xs : Subarray α} {hi : Nat} :
xs[*...=hi].toArray = xs.toArray.extract 0 (hi + 1) := by
simp
Std.Ric.HasRcoIntersection.intersection, Std.Rcc.HasRcoIntersection.intersection,
Rcc.Sliceable.mkSlice]
@[simp, grind =]
public theorem toList_mkSlice_ric {xs : Subarray α} {hi : Nat} :
xs[*...=hi].toList = xs.toList.take (hi + 1) := by
simp [mkSlice_ric_eq_mkSlice_rco]
@[simp, grind =]
public theorem toArray_mkSlice_ric {xs : Subarray α} {hi : Nat} :
xs[*...=hi].toArray = xs.toArray.extract 0 (hi + 1) := by
simp [mkSlice_ric_eq_mkSlice_rco]
@[simp, grind =]
public theorem size_mkSlice_ric {xs : Subarray α} {hi : Nat} :
xs[*...=hi].size = min (hi + 1) xs.size := by
simp [ Subarray.length_toList]
@[simp, grind =, grind =]
public theorem mkSlice_rii {xs : Subarray α} :
xs[*...*] = xs := by
simp [Std.Rii.Sliceable.mkSlice]
@[simp]
public theorem toList_mkSlice_rii {xs : Subarray α} :
xs[*...*].toList = xs.toList := by
rw [mkSlice_rii]
@[simp]
public theorem toArray_mkSlice_rii {xs : Subarray α} :
xs[*...*].toArray = xs.toArray := by
rw [mkSlice_rii]
end Subarray
end SubarraySlices

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