Compare commits

...

282 Commits

Author SHA1 Message Date
Leonardo de Moura
567fae227a test: and fix 2025-12-25 12:05:33 -08:00
Leonardo de Moura
771743b897 chore: rename test 2025-12-25 11:54:53 -08:00
Leonardo de Moura
73e45cd271 feat: add replaceS, lowerLooseBVarsS, and liftLooseBVarsS 2025-12-25 11:52:34 -08:00
Leonardo de Moura
0e5b352709 feat: generalize AlphaShareBuilder.lean 2025-12-25 11:52:34 -08:00
Leonardo de Moura
58420f9416 refactor: simplify AlphaShareCommon.State (#11797)
This PR simplifies `AlphaShareCommon.State` by separating the persistent
and transient parts of the state.

The `map` field caches visited sub-expressions during a single
`shareCommonAlpha` call to handle DAGs efficiently, the input expression
may contain shared sub-expressions that are not yet maximally shared.
However, this cache does not need to persist between different
`shareCommonAlpha` calls.

**Changes:**
- Moved `map` from the persistent `AlphaShareCommon.State` to a private
`State` used only within individual `shareCommonAlpha` calls.
- Replaced `PHashMap ExprPtr Expr` with (the more efficient)
`Std.HashMap ExprPtr Expr` for `map`, since it is now local to each call
and does not need persistence.
- The public `AlphaShareCommon.State` now only contains the `set` of
alpha-equivalent expressions that should persist
2025-12-25 18:06:34 +00:00
Leonardo de Moura
b3b33e85d3 feat: add Sym.getMaxFVar? (#11794)
This PR implements the function `getMaxFVar?` for implementing `SymM`
primitives.
2025-12-25 02:24:00 +00:00
Leonardo de Moura
723acce2a7 feat: add AlphaShareBuilder (#11793)
This PR adds functions for creating maximally shared terms from
maximally shared terms. It is more efficient than creating an expression
and then invoking `shareCommon`. We are going to use these functions for
implementing the symbolic simulation primitives.
2025-12-25 00:05:03 +00:00
Leonardo de Moura
e765138bb4 chore: add isDebugEnabled to grind (#11792)
This PR adds `isDebugEnabled` for checking whether `grind.debug` is set
to `true` when `grind` was initialized.
2025-12-24 23:32:27 +00:00
Leonardo de Moura
501375f340 feat: add SymM monad (#11788)
This PR introduces `SymM`, a new monad for implementing symbolic
simulators (e.g., verification condition generators) in Lean. The monad
addresses performance issues found in symbolic simulators built on top
of user-facing tactics like `apply` and `intros`.

**Key features:**
- Goals are represented by `Grind.Goal` objects, enabling incremental
hypothesis processing
- No `revert` or `clear` operations, allowing O(1) local context checks
instead of O(n log n)
- Carries `GrindM` state across goals to avoid reprocessing shared
hypotheses
- Provides `mkGoal` for creating new goals within the monad

This is the foundational infrastructure for `SymM`. Future PRs will add
operations like `intro`, `apply`, and the optimized definitional
equality test.
2025-12-24 04:05:14 +00:00
Leonardo de Moura
ce56e2139e feat: support for incrementally processing hypotheses in grind (#11787)
This PR adds support for incrementally processing local declarations in
`grind`. Instead of processing all hypotheses at once during goal
initialization, `grind` now tracks which local declarations have been
processed via `Goal.nextDeclIdx` and provides APIs to process new
hypotheses incrementally.
This feature will be used by the new `SymM` monad for efficient symbolic
simulation.
2025-12-24 02:50:22 +00:00
Henrik Böving
c34e4cf0f7 perf: disable closed term extraction in bv_decide (#11785)
This PR disables closed term extraction in the reflection terms used by
`bv_decide`. These terms do
not profit at all from closed term extraction but can in practice cause
thousands of new closed term
declarations which in turn slows down the compiler.
2025-12-23 23:22:12 +00:00
Leonardo de Moura
f2c9fcc0b2 feat: add optional start position to PersistentArray.forM (#11784)
This PR just adds an optional start position argument to
`PersistentArray.forM`
2025-12-23 22:12:02 +00:00
Sebastian Ullrich
950a2b7896 chore: ensure every pkg/ test has a correct lean-toolchain file (#11782) 2025-12-23 17:17:22 +00:00
Henrik Böving
88f17dee71 perf: tune the behavior of and flattening in bv_decide (#11781)
This PR improves the performance of and flattening in `bv_decide`.

The two main insights of this PR are:
1. When embedded constraint substitution is disabled it makes no sense
to have and flattening on in
   the first place, given that we do not profit from it in any way.
2. The new fvars produced by and flattening can also be inserted into
the rewriting caches of the
preprocessing pipeline if the fvar they were derived from is already in
the cache. This
drastically decreases the amount of work we have to do in the second
rewriting pass after running
   and flattening.
2025-12-23 13:08:31 +00:00
Henrik Böving
4d2647f9c7 fix: foldlM mismatch part 2 (#11779)
This PR fixes an oversight in the initial #11772 PR.

Closes #11778.
2025-12-23 10:29:20 +00:00
Leonardo de Moura
a471f005d6 feat: add [grind norm] and [grind unfold] attributes (#11776)
This PR adds the attributes `[grind norm]` and `[grind unfold]` for
controlling the `grind` normalizer/preprocessor.

The `norm` modifier instructs `grind` to use a theorem as a
normalization rule. That is, the theorem is applied during the
preprocessing step. This feature is meant for advanced users who
understand how the preprocessor and `grind`'s search procedure interact
with each other.
New users can still benefit from this feature by restricting its use to
theorems that completely eliminate a symbol from the goal. Example:
```lean
theorem max_def : max n m = if n ≤ m then m else n
```
For a negative example, consider:
```lean
opaque f : Int → Int → Int → Int
theorem fax1 : f x 0 1 = 1 := sorry
theorem fax2 : f 1 x 1 = 1 := sorry
attribute [grind norm] fax1
attribute [grind =] fax2

example (h : c = 1) : f c 0 c = 1 := by
  grind -- fails
```
In this example, `fax1` is a normalization rule, but it is not
applicable to the input goal since `f c 0 c` is not an instance of `f x
0 1`. However, `f c 0 c` matches the pattern `f 1 x 1` modulo the
equality `c = 1`. Thus, `grind` instantiates `fax2` with `x := 0`,
producing the equality `f 1 0 1 = 1`, which the normalizer simplifies to
`True`. As a result, nothing useful is learned. In the future, we plan
to include linters to automatically detect issues like these. Example:
```lean
opaque f : Nat → Nat
opaque g : Nat → Nat

@[grind norm] axiom fax : f x = x + 2
@[grind norm ←] axiom fg : f x = g x

example : f x ≥ 2 := by grind
example : f x ≥ g x := by grind
example : f x + g x ≥ 4 := by grind
```

The `unfold` modifier instructs `grind` to unfold the given definition
during the preprocessing step. Example:
```lean
@[grind unfold] def h (x : Nat) := 2 * x
example : 6 ∣ 3*h x := by grind
```
2025-12-23 03:54:35 +00:00
Leonardo de Moura
f6a25b13b9 chore: grind cleanup (#11775) 2025-12-22 23:49:14 +00:00
Henrik Böving
a847b13b1a fix: implemented_by Array.foldlM behavior when stop > start (#11774)
This PR fixes a mismatch between the behavior of `foldlM` and
`foldlMUnsafe` in the three array
types. This mismatch is only exposed when manually specifying a `stop`
value greater than the size
of the array and only exploitable through `native_decide`.

The mismatch was introduced as part of
4ba21ea10c which introduced
`foldlMUnsafe` and thus likely a mistake when building the `unsafe`
implementation instead of a
specification mistake.

Closes #11773
2025-12-22 23:46:45 +00:00
Leonardo de Moura
186a81627b fix: Array.foldlMUnsafe bug (#11772)
This PR a bug in the optimized and unsafe implementation of
`Array.foldlM`.

Issue was reported here:

https://leanprover.zulipchat.com/#narrow/channel/113488-general/topic/Array.2Efoldl.20bug.20.28can.20prove.20False.29/near/565077432
2025-12-22 23:00:16 +00:00
Lean stage0 autoupdater
0df74178d8 chore: update stage0 2025-12-22 20:55:00 +00:00
Leonardo de Moura
72f9b725aa feat: user attribute at grind_pattern (#11770)
This PR implements support for user-defined attributes at
`grind_pattern`. Suppose we have declared the `grind` attribute

```lean
register_grind_attr my_grind
```

Then, we can now write

```lean
opaque f : Nat → Nat
opaque g : Nat → Nat
axiom fg : g (f x) = x

grind_pattern [my_grind] fg => g (f x)
```
2025-12-22 20:07:02 +00:00
Leonardo de Moura
dc53fac626 chore: use extensible grind attribute framework to implement [grind] itself (#11769)
This PR uses the new support for user-defined `grind` attributes to
implement the default `[grind]` attribute.

A manual update-stage0 is required because it affects the .olean files.
2025-12-22 10:07:30 -08:00
Lean stage0 autoupdater
13c88f960f chore: update stage0 2025-12-22 03:42:18 +00:00
Leonardo de Moura
0d2a574f96 feat: user-defined grind attributes (#11765)
This PR implements user-defined `grind` attributes. They are useful for
users that want to implement tactics using the `grind` infrastructure
(e.g., `progress*` in Aeneas). New `grind` attributes are declared using
the command
```lean
register_grind_attr my_grind
```
The command is similar to `register_simp_attr`. After the new attribute
is declared. Recall that similar to `register_simp_attr`, the new
attribute cannot be used in the same file it is declared.
```lean
opaque f : Nat → Nat
opaque g : Nat → Nat

@[my_grind] theorem fax : f (f x) = f x := sorry

example theorem fax2 : f (f (f x)) = f x := by
  fail_if_success grind
  grind [my_grind]
```

TODO: remove leftovers after update stage0
2025-12-22 02:57:25 +00:00
Kim Morrison
a7562bc578 feat: add guarded grind_pattern to List.eq_nil_of_length_eq_zero (#11760)
This PR allows `grind` to use `List.eq_nil_of_length_eq_zero` (and
`Array.eq_empty_of_size_eq_zero`), but only when it has already proved
the length is zero.
2025-12-22 00:05:58 +00:00
Kim Morrison
c86b10d141 chore: add grind pattern guide for Sublist.eq_of_length_le (#11762)
This PR moves the grind pattern from `Sublist.eq_of_length` to the
slightly more general `Sublist.eq_of_length_le`, and adds a grind
pattern guard so it only activates if we have a proof of the hypothesis.
2025-12-22 00:01:33 +00:00
Kim Morrison
54a88e941f chore: followup tests for #11745 (#11764)
This PR adds additional test coverage for #11758 (fix for #11745:
nonstandard instances in grind and simp +arith).

The existing test `grind_11745.lean` only covers Int LE with `grind
-order` and `lia -order`. This adds tests for:

- LT instances (Int and Nat)
- Nat LE instances
- Mixed canonical and non-canonical instances in the same goal
- Equality derived from two LE constraints
- `simp +arith` with non-canonical instances

🤖 Prepared with Claude Code

Co-authored-by: Claude <noreply@anthropic.com>
2025-12-21 22:31:53 +00:00
Kim Morrison
b87d2c0fb9 feat: add lean-bisect script for bisecting toolchain regressions (#11727)
This PR adds a Python script that helps find which commit introduced a
behavior change in Lean. It supports multiple bisection modes and
automatically downloads CI artifacts when available.

- [x] depends on: #11735

## Usage

```
usage: lean-bisect [-h] [--timeout SEC] [--ignore-messages] [--verbose]
                   [--selftest] [--clear-cache] [--nightly-only]
                   [file] [RANGE]

Bisect Lean toolchain versions to find where behavior changes.

positional arguments:
  file               Lean file to test (must only import Lean.* or Std.*)
  RANGE              Range to bisect: FROM..TO, FROM, or ..TO

options:
  -h, --help         show this help message and exit
  --timeout SEC      Timeout in seconds for each test run
  --ignore-messages  Compare only exit codes, ignore stdout/stderr differences
  --verbose, -v      Show stdout/stderr from each test
  --selftest         Run built-in selftest to verify lean-bisect works
  --clear-cache      Clear CI artifact cache (~600MB per commit) and exit
  --nightly-only     Stop after finding nightly range (don't bisect individual
                     commits)

Range Syntax:

  FROM..TO                Bisect between FROM and TO
  FROM                    Start from FROM, bisect to latest nightly
  ..TO                    Bisect to TO, search backwards for regression start

  If no range given, searches backwards from latest nightly to find regression.

Identifier Formats:

  nightly-YYYY-MM-DD    Nightly build date (e.g., nightly-2024-06-15)
                        Uses pre-built toolchains from leanprover/lean4-nightly.
                        Fast: downloads via elan (~30s each).

  v4.X.Y or v4.X.Y-rcN  Version tag (e.g., v4.8.0, v4.9.0-rc1)
                        Converts to equivalent nightly range.

  Commit SHA            Git commit hash (short or full, e.g., abc123def)
                        Bisects individual commits between two points.
                        Tries CI artifacts first (~30s), falls back to building (~2-5min).
                        Commits with failed CI builds are automatically skipped.
                        Artifacts cached in ~/.cache/lean-bisect/artifacts/

Bisection Modes:

  Nightly mode:   Both endpoints are nightly dates.
                  Binary search through nightlies to find the day behavior changed.
                  Then automatically continues to bisect individual commits.
                  Use --nightly-only to stop after finding the nightly range.

  Version mode:   Either endpoint is a version tag.
                  Converts to equivalent nightly range and bisects.

  Commit mode:    Both endpoints are commit SHAs.
                  Binary search through individual commits on master.
                  Output: "Behavior change introduced in commit abc123"

Examples:

  # Simplest: just provide the file, finds the regression automatically
  lean-bisect test.lean

  # Specify an endpoint if you know roughly when it broke
  lean-bisect test.lean ..nightly-2024-06-01

  # Full manual control over the range
  lean-bisect test.lean nightly-2024-01-01..nightly-2024-06-01

  # Only find the nightly range, don't continue to commit bisection
  lean-bisect test.lean nightly-2024-01-01..nightly-2024-06-01 --nightly-only

  # Add a timeout (kills slow/hanging tests)
  lean-bisect test.lean --timeout 30

  # Bisect commits directly (if you already know the commit range)
  lean-bisect test.lean abc1234..def5678

  # Only compare exit codes, ignore output differences
  lean-bisect test.lean --ignore-messages

  # Clear downloaded CI artifacts to free disk space
  lean-bisect --clear-cache
```

🤖 Prepared with Claude Code

---------

Co-authored-by: Claude <noreply@anthropic.com>
2025-12-21 20:04:47 +00:00
Kim Morrison
eb990538ae fix: allow exact? to suggest local private declarations (part 2) (#11759)
This PR contains changes that were meant to be part of #11736, but I
accidentally merged without pushing my final local changes.
2025-12-21 20:03:10 +00:00
Joachim Breitner
4c0765fc07 fix: grind using congr equation of private imported matcher (#11756)
This PR fixes an issue where `grind` fails when trying to unfold a
definition by pattern matching imported by `import all` (or from a
non-`module`).

Fixes #11715

---------

Co-authored-by: Sebastian Ullrich <sebasti@nullri.ch>
2025-12-21 17:59:52 +00:00
Leonardo de Moura
5e24120dba fix: nonstandard instances in grind and simp +arith (#11758)
This PR improves support for nonstandard `Int`/`Nat` instances in
`grind` and `simp +arith`.

Closes #11745
2025-12-21 17:56:49 +00:00
Sebastian Ullrich
f317e28d84 fix: realizeValue should default to the private scope (#11748)
This PR fixes an edge case where some tactics did not allow access to
private declarations inside private proofs under the module system

Fixes #11747
2025-12-21 01:22:19 +00:00
Eric Paul
bb8e6801f0 chore: fix typo in parser docstring (#11753)
Fix a typo in the docstring for checking the `lhsPrec`
2025-12-20 23:17:47 +00:00
Leonardo de Moura
5440bf724d fix: case-splitting selection in grind (#11749)
This PR fixes a bug in the function `selectNextSplit?` used in `grind`.
It was incorrectly computing the generation of each candidate.

Closes #11697
2025-12-20 20:17:09 +00:00
Henrik Böving
c88ec35c0d perf: turn more commonly used bv_decide theorems into simprocs (#11739)
This PR turns even more commonly used bv_decide theorems that require
unification into fast simprocs
using syntactic equality. This pushes the overall performance across
sage/app7 to <= 1min10s for
every problem.
2025-12-19 18:09:32 +00:00
David Thrane Christiansen
73ff198d11 doc: replace ffi.md with links to the reference manual (#11737)
This PR replaces `ffi.md` with links to the corresponding sections of
the manual, so we don't have to keep two documents up to date.

A corresponding reference manual PR re-synchronizes them:
https://github.com/leanprover/reference-manual/pull/714
2025-12-19 07:23:06 +00:00
Kim Morrison
cee149cc1f feat: add #import_path, assert_not_exists, assert_not_imported commands (#11726)
This PR upstreams dependency-management commands from Mathlib:

- `#import_path Foo` prints the transitive import chain that brings
`Foo` into scope
- `assert_not_exists Foo` errors if declaration `Foo` exists (for
dependency management)
- `assert_not_imported Module` warns if `Module` is transitively
imported
- `#check_assertions` verifies all pending assertions are eventually
satisfied

These commands help maintain the independence of different parts of a
library by catching unintended transitive dependencies early.

### Example usage

```lean
-- Find out how Nat got into scope
#import_path Nat
-- Declaration Nat is imported via
-- Init.Prelude,
--   which is imported by Init.Coe,
--   which is imported by Init.Notation,
--   ...
--   which is imported by this file.

-- Assert that a declaration should not be in scope yet
assert_not_exists SomeAdvancedType

-- Assert that a module should not be imported
assert_not_imported Some.Heavy.Module

-- Verify all assertions are eventually satisfied
#check_assertions
```

Addresses
https://lean-fro.zulipchat.com/#narrow/channel/398861-general/topic/path.20of.20an.20import

🤖 Prepared with Claude Code

---------

Co-authored-by: Claude <noreply@anthropic.com>
2025-12-19 04:09:33 +00:00
Kim Morrison
2236122411 feat: add build_artifact.py for downloading CI artifacts (#11735)
This PR adds a standalone script to download pre-built CI artifacts from
GitHub Actions. This allows us to quickly switch commits without
rebuilding.

**Features:**
- Downloads artifacts for current HEAD or specified commit (`--sha`)
- Caches in `~/.cache/lean_build_artifact/` for reuse
- Platform detection (Linux/macOS, x86_64/aarch64)

**Usage:**
```
build_artifact.py                   # Download for current HEAD
build_artifact.py --sha abc1234     # Download for specific commit
build_artifact.py --clear-cache     # Clear cache
```

This is extracted to be shared with `lean-bisect`.

🤖 Prepared with Claude Code

Co-authored-by: Claude <noreply@anthropic.com>
2025-12-19 04:09:23 +00:00
Kim Morrison
c74d24aaaa fix: allow exact? to suggest local private declarations (#11736)
This PR fixes an issue where `exact?` would not suggest private
declarations defined in the current module.

## Problem

When using `exact?` in a file with private declarations, those private
declarations were not being suggested even though they are valid and
accessible:

```lean
module

axiom P : Prop
private axiom p : P
example : P := by exact? -- error: could not find lemma
```

The problem was that `blacklistInsertion` in `LazyDiscrTree` was
filtering out all declarations whose names matched `isInternalDetail`,
which includes private names due to their `_private.Module.0.name`
structure.

## Solution

The fix adds a helper function `isPrivateNameOf` that checks if a
private declaration belongs to a specific module. The
`blacklistInsertion` function now allows private declarations belonging
to the current module (`env.header.mainModule`) to pass through the
filter.

Private declarations from imported modules are still filtered out, as
they may reference internal declarations that aren't accessible (which
would cause processing errors).

Zulip discussion:
https://leanprover.zulipchat.com/#narrow/channel/270676-lean4/topic/.60exact.3F.60.20and.20private.20declarations/near/564586152

🤖 Prepared with Claude Code

---------

Co-authored-by: Claude <noreply@anthropic.com>
2025-12-19 04:05:54 +00:00
Henrik Böving
34d619bf93 perf: use lean::unordered_set for expr_eq_fn (#11731)
This PR makes the cache in expr_eq_fn use mimalloc for a small
performance win across the board.
2025-12-18 14:24:50 +00:00
Luisa Cicolini
eb11ccb234 feat: lemmas around BitVec.extractLsb' and BitVec.extractLsb (#11728)
This PR introduces some additional lemmas around `BitVec.extractLsb'`
and `BitVec.extractLsb`.

---------

Co-authored-by: Tobias Grosser <github@grosser.es>
Co-authored-by: Tobias Grosser <tobias@grosser.es>
2025-12-18 11:27:27 +00:00
Henrik Böving
2db0a98b7c fix: internalize all arguments to Quot.lift during LCNF conversion (#11729)
This PR internalizes all arguments of Quot.lift during LCNF conversion,
preventing panics in certain
non trivial programs that use quotients.

Fixes #11719.
2025-12-18 09:31:48 +00:00
Henrik Böving
6cabf59099 perf: avoid locally nameless overhead in congruence functions (#11721)
This PR improves the performance of the functions for generating
congruence lemmas, used by `simp`
and a few other components.

It is a followup to (though not dependent on) #11717 and improves the
performance of `bv_decide` on the benchmark
in question further down to 20 seconds (from 1min 23s in #11717 and 8min
originally). We are thus at approximately a 24x speedup from the
original run.
2025-12-18 08:29:08 +00:00
Henrik Böving
89bbe804a5 perf: turn more bv_normalize rules into simprocs (#11717)
This PR improves the performance of `bv_decide`'s rewriter on large
problems.

The baseline for this PR is `QF_BV/sage/app7/bench_1222.smt2` on
`chonk3` at 8 minutes. After this
PR it takes about 1min and 23 seconds. This improvement is achieved by
turning frequently used simp
rules into simprocs in order to avoid spending time performing
unification to see if they are
applicable.
2025-12-18 08:20:16 +00:00
Paul Reichert
4e656ea8e9 refactor: move Std.Range to Std.Legacy.Range (#11438)
This PR renames the namespace `Std.Range` to `Std.Legacy.Range`. Instead
of using `Std.Range` and `[a:b]` notation, the new range type `Std.Rco`
and its corresponding `a...b` notation should be used. There are also
other ranges with open/closed/infinite boundary shapes in
`Std.Data.Range.Polymorphic` and the new range notation also works for
`Int`, `Int8`, `UInt8`, `Fin` etc.
2025-12-18 02:07:33 +00:00
Lean stage0 autoupdater
aa9f7ab14b chore: update stage0 2025-12-17 23:51:56 +00:00
Paul Reichert
5ef0207a85 refactor: remove IteratorCollect (#11706)
This PR removes the `IteratorCollect` type class and hereby simplifies
the iterator API. Its limited advantages did not justify the complexity
cost.
2025-12-17 23:02:33 +00:00
Paul Reichert
a1b8ffe31b feat: improve MPL support for loops over iterators, fix MPL spec priorities (#11716)
This PR adds more MPL spec lemmas for all combinations of `for` loops,
`fold(M)` and the `filter(M)/filterMap(M)/map(M)` iterator combinators.
These kinds of loops over these combinators (e.g. `it.mapM`) are first
transformed into loops over their base iterators (`it`), and if the base
iterator is of type `Iter _` or `IterM Id _`, then another spec lemma
exists for proving Hoare triples about it using an invariant and the
underlying list (`it.toList`). The PR also fixes a bug that MPL always
assigns the default priority to spec lemmas if `Std.Tactic.Do.Syntax` is
not imported and a bug that low-priority lemmas are preferred about
high-priority ones.

For context, the MPL bug was related to the fact that the `Attr.spec`
syntax is not built-in. Therefore, Lean falls back to the `Attr.simple`
syntax, which *basically* also works, but which stores the priority at a
different position. The routine to extract the priority does not
consider this and so it falls back to the default priority given an
`Attr.simple` syntax object.
2025-12-17 22:49:42 +00:00
Henrik Böving
f21f8d96f9 perf: improve auto completion and fuzzy matching (#11630)
This PR improves the performance of autocompletion and fuzzy matching by
introducing an ASCII fast path into one of their core loops and making
Char.toLower/toUpper more efficient.

Co-authored-by: Rob23oba <152706811+Rob23oba@users.noreply.github.com>
2025-12-17 16:04:05 +00:00
Joachim Breitner
1918d4f0dc chore: add test for #11655 (#11718)
This PR adds a test for issue #11655, which it seems was fixed by #11695

Fixes #11655
2025-12-17 15:54:16 +00:00
Robert J. Simmons
08c87b2ad3 feat: focused error messages for named examples (#11714)
This PR gives a focused error message when a user tries to name an
example, and tweaks error messages for attempts to define multiple
opaque names at once.

## Example errors

```
example x : 1 == 1 := by grind
```

Current message:
```
Failed to infer type of binder `x`

Note: Because this declaration's type has been explicitly provided, all parameter types and holes (e.g., `_`) in its header are resolved before its body is processed; information from the declaration body cannot be used to infer what these values should be
```

New message:
```
Failed to infer type of binder `x`

Note: Examples don't have names. The identifier `x` is being interpreted as a parameter `(x : _)`.
```

## Plural-aware identifier lists

Both the example errors and opaque errors understand pluralization and
use oxford commas.

```
opaque a b c : Nat
```

Current message:
```
Failed to infer type of binder `c`

Note: Multiple constants cannot be declared in a single declaration. The identifier(s) `b`, `c` are being interpreted as parameters `(b : _)`, `(c : _)`.
```

New message:
```
Failed to infer type of binder `c`

Note: Multiple constants cannot be declared in a single declaration. The identifiers `b` and `c` are being interpreted as parameters `(b : _)` and `(c : _)`.```
2025-12-17 14:54:41 +00:00
Paul Reichert
489f8acd77 feat: get-elem tactic support for subarrays (#11710)
This PR extends the get-elem tactic for ranges so that it supports
subarrays. Example:
```lean
example {a : Array Nat} (h : a.size = 28) : Id Unit := do
  let mut x := 0
  for h : i in *...(3 : Nat) do
    x := a[1...4][i]
```
2025-12-17 13:44:17 +00:00
Henrik Böving
3e61514ce4 perf: partially evaluate bv_decide simprocs to avoid instance synthesis (#11712)
This PR avoids invoking TC synthesis and other inference mechanisms in
the simprocs of bv_decide. This can give significant speedups on
problems that pressure these simprocs.
2025-12-17 11:52:57 +00:00
Lean stage0 autoupdater
f63c2363ee chore: update stage0 2025-12-17 11:51:52 +00:00
Henrik Böving
fe96911368 feat: proper recursive specialization (#11479)
This PR enables the specializer to also recursively specialize in some
non trivial higher order situations.

The main motivation for this change is the upcoming changes to do
notation by sgraf. In there he uses combinators such as
```lean
@[specialize, expose]
def List.newForIn {α β γ} (l : List α) (b : β) (kcons : α → (β → γ) → β → γ) (knil : β → γ) : γ :=
  match l with
  | []     => knil b
  | a :: l => kcons a (l.newForIn · kcons knil) b
```
in programs such as
```lean
def testing :=
  let x := 42;
  List.newForIn (β := Nat) (γ := Id Nat)
    [1,2,3]
    x
    (fun i kcontinue s =>
      let x := s;
      List.newForIn
        [i:10].toList x
        (fun j kcontinue s =>
          let x := s;
          let x := x + i + j;
          kcontinue x)
        kcontinue)
    pure
```
inspecting this IR right before we get to the specializer in the current
compiler we get:
```
[Compiler.eagerLambdaLifting] size: 22
    def testing : Nat :=
      fun _f.1 _y.2 : Nat :=
        return _y.2;
      let x := 42;
      let _x.3 := 1;
      fun _f.4 i kcontinue s : Nat :=
        fun _f.5 j kcontinue s : Nat :=
          let _x.6 := Nat.add s i;
          let x := Nat.add _x.6 j;
          let _x.7 := kcontinue x;
          return _x.7;
        let _x.8 := 10;
        let _x.9 := Nat.sub _x.8 i;
        let _x.10 := Nat.add _x.9 _x.3;
        let _x.11 := 1;
        let _x.12 := Nat.sub _x.10 _x.11;
        let _x.13 := Nat.mul _x.3 _x.12;
        let _x.14 := Nat.add i _x.13;
        let _x.15 := @List.nil _;
        let _x.16 := List.range'TR.go _x.3 _x.12 _x.14 _x.15;
        let _x.17 := @List.newForIn _ _ _ _x.16 s _f.5 kcontinue;
        return _x.17;
      let _x.18 := 2;
      let _x.19 := 3;
      let _x.20 := @List.nil _;
      let _x.21 := @List.cons _ _x.19 _x.20;
      let _x.22 := @List.cons _ _x.18 _x.21;
      let _x.23 := @List.cons _ _x.3 _x.22;
      let _x.24 := @List.newForIn _ _ _ _x.23 x _f.4 _f.1;
      return _x.24 
```
Here the `kcontinue` higher order functions pose a special challenge
because they delay the discovery of new specialization opportunities.
Inspecting the IR after the current specializer (and a cleanup simp
step) we get functions that look as follows:
```
 [simp] size: 7
      def List.newForIn._at_.testing.spec_0 i kcontinue l b : Nat :=
        cases l : Nat
        | List.nil =>
          let _x.1 := kcontinue b;
          return _x.1
        | List.cons head.2 tail.3 =>
          let _x.4 := Nat.add b i;
          let x := Nat.add _x.4 head.2;
          let _x.5 := List.newForIn._at_.testing.spec_0 i kcontinue tail.3 x;
          return _x.5 
  [simp] size: 14
      def List.newForIn._at_.List.newForIn._at_.testing.spec_1.spec_1 _x.1 l b : Nat :=
        cases l : Nat
        | List.nil =>
          return b
        | List.cons head.2 tail.3 =>
          fun _f.4 x.5 : Nat :=
            let _x.6 := List.newForIn._at_.List.newForIn._at_.testing.spec_1.spec_1 _x.1 tail.3 x.5;
            return _x.6;
          let _x.7 := 10;
          let _x.8 := Nat.sub _x.7 head.2;
          let _x.9 := Nat.add _x.8 _x.1;
          let _x.10 := 1;
          let _x.11 := Nat.sub _x.9 _x.10;
          let _x.12 := Nat.mul _x.1 _x.11;
          let _x.13 := Nat.add head.2 _x.12;
          let _x.14 := @List.nil _;
          let _x.15 := List.range'TR.go _x.1 _x.11 _x.13 _x.14;
          let _x.16 := List.newForIn._at_.testing.spec_0 head.2 _f.4 _x.15 b;
          return _x.16
```
Observe that the specializer decided to abstract over `kcontinue`
instead of specializing further recursively. Thus this tight loop is now
going through an indirect call.

This PR now changes the specializer somewhat fundamentally to handle
situations like this. The most notable change is going to a fixpoint
loop of:
1. Specialize all current declarations in the worklist
2. If a declaration
- succeeded in specializing run the simplifier on it and put it back
onto the worklist
    - if it didn't don't put it back onto the worklist anymore
3. Put all newly generated specialisations on the worklist
4. Recompute fixed parameters for the current SCC
5. Repeat until the worklist is empty

Furthermore, declarations that were already specialized:
- only consider `fixedHO` parameters for specialization, in order to
avoid termination issues with repeated specialization and abstraction of
type class parameters under binders
- recursively specialized declarations only allow specialization if at
least one of their fixedHO arguments is not a parameter itself. The
reason for allowing this in first generation specialization is that we
refrain from specializing inside the body of a declaration marked as
`@[specialize]`. Thus we need to specialize them even if their arguments
don't actually contain anything of interest in order to ensure that type
classes etc. are correctly cleaned up within their bodies.

There is one last trade-off to consider. When specializing code
generated by the new do elaborator we sometimes generate intermediate
specializations that are not actually part of any call graph after we
are done specializing. We could in principle detect these functions and
delete them but having them in cache is potentially helpful for further
specializations later. Once the new do elaborator lands we plan to test
this trade-off.

Closes #10924
2025-12-17 11:05:24 +00:00
Paul Reichert
08f0d12ffb feat: add lemmas about Int ranges (#11705)
This PR provides many lemmas about `Int` ranges, in analogy to those
about `Nat` ranges. A few necessary basic `Int` lemmas are added. The PR
also removes `simp` annotations on `Rcc.toList_eq_toList_rco`,
`Nat.toList_rcc_eq_toList_rco` and consorts.
2025-12-17 10:04:28 +00:00
Luisa Cicolini
06d2390fb3 feat: add BitVec.cpop and lemmas (#11257)
This PR adds the definition of `BitVec.cpop`, which relies on the more
general `BitVec.cpopNatRec`, and build some theory around it. The name
`cpop` aligns with the [RISCV ISA
nomenclature](https://msyksphinz-self.github.io/riscv-isadoc/#_cpop).

Co-authored-by: @tobiasgrosser, @bollu

---------

Co-authored-by: Tobias Grosser <tobias@grosser.es>
Co-authored-by: Tobias Grosser <github@grosser.es>
Co-authored-by: Siddharth <siddu.druid@gmail.com>
2025-12-17 09:51:24 +00:00
Paul Reichert
3ac9bbb3d8 feat: MPL specs for loops over iterators (#11693)
This PR makes it possible to verify loops over iterators. It provides
MPL spec lemmas about `for` loops over pure iterators. It also provides
spec lemmas that rewrite loops over `mapM`, `filterMapM` or `filterM`
iterator combinators into loops over their base iterator.
2025-12-17 09:36:44 +00:00
Joachim Breitner
118160bf07 refactor: handle irrefutable patterns in match compilation individually (#11695)
This PR refactors match compilation, to handle “side-effect free”
patterns (`.var`, `.inaccessible`, `.as`) eagerly and for each
alternative separately. The idea is that there should be less interplay
between different alternatives, and prepares the ground for #11105.

This may cause some corner case match statements to compiler or fail
compile that behaved differently before. For example, it can now use a
sparse case where previously was using a full case, and pattern
completeness may not be clear to lean now. On the other hand, using a
sparse case can mean that match statements mixing matching in indicies
with matching on the indexed datatype can work.
2025-12-17 09:02:17 +00:00
Kim Morrison
c1bc886d98 fix: remove batteries tag check from PR mathlib CI (#11707)
This PR removes the unnecessary check for the batteries
`nightly-testing-YYYY-MM-DD` tag that blocks mathlib CI from running.

## Problem

Currently, when fixing mathlib's nightly-testing branch, the workflow
requires BOTH batteries and mathlib to have `nightly-testing-YYYY-MM-DD`
tags before mathlib CI can run on lean4 PRs. This creates a false
dependency:

1. Fix mathlib nightly-testing (including fixing batteries build)
2. Mathlib CI succeeds → creates mathlib tag → advances
`nightly-with-mathlib`
3. But batteries test suite fails → no batteries tag created
4. lean4 PR can't run mathlib CI because batteries tag doesn't exist
5. Bot suggests rebasing onto `nightly-with-mathlib`, but this doesn't
help

## Solution

Remove the batteries tag check because:
- Mathlib CI already depends on batteries (builds it as a dependency)
- If batteries is broken, mathlib CI will detect it
- The batteries testing branch creation already has fallback logic
(falls back to `nightly-testing` branch if tag doesn't exist)

This allows mathlib CI to run as soon as mathlib is ready, which is the
actual blocker.

See discussion at
https://leanprover.zulipchat.com/#narrow/channel/428973-nightly-testing/topic/Mathlib.20status.20updates/near/564136025

🤖 Prepared with Claude Code

Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-17 08:53:04 +00:00
Kim Morrison
0708024c46 fix: support dot notation on declarations in grind lemma list (#11691)
This PR fixes `grind` to support dot notation on declarations in the
lemma list.

When using `grind only [foo.le]` where `foo.le` is dot notation applying
`LT.lt.le` to a theorem `foo`, grind previously failed with "Unknown
constant `foo.le`" because it tried to look up `foo.le` as a constant
name rather than elaborating it as a term.

The fix adds a fallback in `processParam`: when constant lookup fails,
it now falls back to `processTermParam` which elaborates the identifier
as a term. This allows dot notation expressions like `log_two_lt_d9.le`
to work correctly.

Closes #11690

🤖 Prepared with Claude Code

---------

Co-authored-by: Claude <noreply@anthropic.com>
2025-12-17 03:17:46 +00:00
Kim Morrison
2d9571563a chore: add missing repositories to release_repos.yml (#11668)
This PR adds the following repositories to the release configuration:
- lean4-unicode-basic
- BibtexQuery (depends on lean4-unicode-basic)
- verso-web-components (depends on verso)

It also updates dependencies:
- doc-gen4 now depends on BibtexQuery
- lean-fro.org now depends on verso-web-components

🤖 Prepared with Claude Code
2025-12-17 02:33:53 +00:00
Paul Reichert
e2617903f8 feat: MonadAttach (#11532)
This PR adds the new operation `MonadAttach.attach` that attaches a
proof that a postcondition holds to the return value of a monadic
operation. Most non-CPS monads in the standard library support this
operation in a nontrivial way. The PR also changes the `filterMapM`,
`mapM` and `flatMapM` combinators so that they attach postconditions to
the user-provided monadic functions passed to them. This makes it
possible to prove termination for some of these for which it wasn't
possible before. Additionally, the PR adds many missing lemmas about
`filterMap(M)` and `map(M)` that were needed in the course of this PR.
2025-12-16 18:57:00 +00:00
Markus Himmel
7ba21c4d1b fix: incorrect inherit_doc (#11704)
This PR fixes an incorrect `inherit_doc` in `Init.System.IO`.
2025-12-16 17:30:01 +00:00
Sebastian Ullrich
b7f1cf9ba7 chore: shake: fix handling of meta structure etc (#11701) 2025-12-16 16:28:39 +00:00
Leonardo de Moura
12c282b1e9 doc: add link to reference manual in grind docstring (#11700)
This PR adds a link to the `grind` docstring. The link directs users to
the section describing `grind` in the reference manual.

<img width="840" height="354" alt="image"
src="https://github.com/user-attachments/assets/8efedc53-26cd-4f2c-8d47-a9d3a324e579"
/>
2025-12-16 15:35:55 +00:00
Sebastian Graf
5f4d724c2d feat: abstract metavariables when generalizing match motives (#8099) (#11696)
This PR improves `match` generalization such that it abstracts
metavariables in types of local variables and in the result type of the
match over the match discriminants. Previously, a metavariable in the
result type would silently default to the behavior of `generalizing :=
false`, and a metavariable in the type of a free variable would lead to
an error (#8099). Example of a `match` that elaborates now but
previously wouldn't:
```lean
example (a : Nat) (ha : a = 37) :=
    (match a with | 42 => by contradiction | n => n) = 37
```
This is because the result type of the `match` is a metavariable that
was not abstracted over `a` and hence generalization failed; the result
is that `contradiction` cannot pick up the proof `ha : 42 = 37`.
The old behavior can be recovered by passing `(generalizing := false)`
to the `match`.

Furthermore, programs such as the following can now be elaborated:
```lean
example (n : Nat) : Id (Fin (n + 1)) :=
  have jp : ?m := ?rhs
  match n with
  | 0 => ?jmp1
  | n + 1 => ?jmp2
  where finally
  case m => exact Fin (n + 1) → Id (Fin (n + 1))
  case jmp1 => exact jp ⟨0, by decide⟩
  case jmp2 => exact jp ⟨n, by omega⟩
  case rhs => exact pure
```
This is useful for the `do` elaborator.

Fixes #8099.
2025-12-16 14:34:29 +00:00
Sebastian Graf
98616529fd fix: early return after simplifying discriminants in mvcgen (#11687) (#11698)
This PR makes `mvcgen` early return after simplifying discriminants,
avoiding a rewrite on an ill-formed `match`.

Closes #11687.
2025-12-16 11:36:45 +00:00
maxwell3025
8f80d2c2e0 fix: add table variant for require.git field in lakefile.toml schema (#11536)
This PR corrects the JSON Schema at
`src/lake/schemas/lakefile-toml-schema.json` to allow the table variant
of the `require.git` field in `lakefile.toml` as specified in the
[reference](https://lean-lang.org/doc/reference/latest/Build-Tools-and-Distribution/Lake/#Lake___Dependency-git).

Closes #11535
2025-12-16 10:47:33 +00:00
Joachim Breitner
fd0a65f312 refactor: make simpH proof-producing (#11553)
This PR makes `simpH`, used in the match equation generator, produce a
proof term. This is in preparation for a bigger refactoring in #11512.

This removes some cases, these are no longer necessary since #11196.
2025-12-16 09:37:17 +00:00
Henrik Böving
bd5d750780 chore: fix BitVec docstring typo (#11694)
Closes #11680
2025-12-16 08:54:14 +00:00
Mac Malone
49d4752bfd fix: lake: meta import transitivity (#11683)
This PR fixes an inconsistency in the way Lake and Lean view the
transitivity of a `meta import`. Lake now works as Lean expects and
includes the meta segment of all transitive imports of a `meta import`
in its transitive trace.
2025-12-16 08:28:52 +00:00
Sofia Rodrigues
95a7c769d8 feat: introduce CancellationContext type for cancellation with context propagation (#11499)
This PR adds the `Context` type for cancellation with context
propagation. It works by storing a tree of forks of the main context,
providing a way to control cancellation.
2025-12-15 21:20:11 +00:00
Robert J. Simmons
7b8e51e025 fix: missing word in inductionWithNoAlts error message (#11684)
This PR adds a missing word ("be") to the error message catching
natural-numbers-game-like uses of induction that was introduced in
#11347.
2025-12-15 17:23:25 +00:00
Alok Singh
949cf69246 chore: use backticks for sorry in diagnostic messages (#11608)
This PR changes the "declaration uses 'sorry'" warning to use backticks
instead of single quotes, consistent with Lean's conventions for
formatting code identifiers in diagnostic messages.
2025-12-15 14:30:21 +00:00
Alok Singh
e02f229305 chore: fix typo "Unkown" -> "Unknown" in role error message (#11682)
Fix a typo in the error message when an unknown role is used in a
docstring.

- Changes "Unkown role" to "Unknown role" in
`src/Lean/Elab/DocString.lean`
2025-12-15 14:29:11 +00:00
Joachim Breitner
9b49b6b68d fix: let grind handle Nat.ctorIdx (#11670)
This PR fixes the `grind` support for `Nat.ctorIdx`. Nat constructors
appear in `grind` as offsets or literals, and not as a node marked
`.constr`, so handle that case as well.
2025-12-15 10:26:16 +00:00
Paul Reichert
eb20c07b4a fix: fix broken benchmarks from #11446 (#11681)
This PR fixes benchmarks that were broken by #11446.
2025-12-15 09:35:42 +00:00
Lean stage0 autoupdater
3fdde57e7b chore: update stage0 2025-12-15 08:59:34 +00:00
Paul Reichert
c79d74d9a1 refactor: move Iter and others from Std.Iterators to Std (#11446)
This PR moves many constants of the iterator API from `Std.Iterators` to
the `Std` namespace in order to make them more convenient to use. These
constants include, but are not limited to, `Iter`, `IterM` and
`IteratorLoop`. This is a breaking change. If something breaks, try
adding `open Std` in order to make these constants available again. If
some constants in the `Std.Iterators` namespace cannot be found, they
can be found directly in `Std` now.
2025-12-15 08:24:12 +00:00
Markus Himmel
082c65f226 chore: ci: check for changes to src/stdlib_flags.h (#11679)
This PR adds a CI step that fails if the `src/stdlib_flags.h` file was
modified, to alert PR authors that they most likely wanted to modify
`stage0/src/stdlib_flags.h` instead.
2025-12-15 07:17:12 +00:00
Leonardo de Moura
6a0b0c8273 fix: grind lia distracted by nonlinearity (#11678)
This PR fixes a bug in `registerNonlinearOccsAt` used to implement
`grind lia`. This issue was originally reported at:
https://leanprover.zulipchat.com/#narrow/channel/113489-new-members/topic/Weirdness.20with.20cutsat/near/562099515

Example that was failing:
```lean
example {a : Nat} (ha : 1 ≤ a) (H : a ^ 2 = 2 ^ a)
    : a = 1 ∨ a = 2 ∨ 3 ≤ a := by
  grind
```
2025-12-14 23:18:12 +00:00
Leonardo de Moura
62b900e8ef feat: basic equality propagation for IntModule in grind (#11677)
This PR adds basic support for equality propagation in `grind linarith`
for the `IntModule` case. This covers only the basic case. See note in
the code.
We remark this feature is irrelevant for `CommRing` since `grind ring`
already has much better support for equality propagation.
2025-12-14 22:40:11 +00:00
Evan Chen
429e09cd82 doc: trivial typo in Grind/Attr.lean (#11676)
This PR fixes typo "the the custom pattern" -> "the custom pattern".
2025-12-14 20:12:47 +00:00
Sebastian Ullrich
c4d67c22e6 chore: CI: disable problematic bv_decide tests under fsanitize (#11675) 2025-12-14 19:02:20 +00:00
Sebastian Ullrich
923d7e1ed6 fix: ensure by uses expected instead of given type for modsys aux decl (#11673)
This PR fixes an issue where a `by` in the public scope could create an
auxiliary theorem for the proof whose type does not match the expected
type in the public scope.

Fixes #11672
2025-12-14 17:44:38 +00:00
Joachim Breitner
5db865ea2f fix: make grind support for ctorIdx debug.grind-safe (#11669)
This PR makes sure that proofs about `ctorIdx` passed to `grind` pass
the `debug.grind` checks, despite reducing a `semireducible` definition.
2025-12-14 14:59:57 +00:00
Joachim Breitner
0f2ac0b099 feat: sparse sparse casesOn splitting in match equations (#11666)
This PR makes sure that when a matcher is compiled using a sparse cases,
that equation generation also uses sparse cases to split.
This fixes #11665.
2025-12-14 14:59:45 +00:00
Kim Morrison
b7ff463358 chore: begin development cycle for v4.28.0 (#11667)
This PR bumps the version from v4.27.0 to v4.28.0-pre to begin the next
development cycle.

🤖 Prepared with Claude Code
2025-12-14 12:42:12 +00:00
Leonardo de Moura
799c6b5ff8 feat: add support for OrderedRing.natCast_nonneg in grind (#11664)
This PR adds support for `Nat.cast` in `grind linarith`. It now uses
`Grind.OrderedRing.natCast_nonneg`. Example:
```lean
open Lean Grind Std
attribute [instance] Semiring.natCast

variable [Lean.Grind.CommRing R] [LE R] [LT R] [LawfulOrderLT R] [IsLinearOrder R] [OrderedRing R]

example (a : Nat) : 0 ≤ (a : R) := by grind
example (a b : Nat) : 0 ≤ (a : R) + (b : R) := by grind
example (a : Nat) : 0 ≤ 2 * (a : R) := by grind
example (a : Nat) : 0 ≥ -3 * (a : R) := by grind
```
2025-12-14 09:09:42 +00:00
Leonardo de Moura
2d0c62c767 fix: grind pattern validation (#11663)
This PR fixes the `grind` pattern validator. It covers the case where an
instance is not tagged with the implicit instance binder. This happens
in declarations such as
```lean
ZeroMemClass.zero_mem {S : Type} {M : outParam Type} {inst1 : Zero M} {inst2 : SetLike S M}
  [self : @ZeroMemClass S M inst1 inst2] (s : S) : 0 ∈ s
```
2025-12-14 07:41:19 +00:00
Junyan Xu
fb6c96e54b chore: expose Nat.log2 (#11401)
Necessary for kernel reduction of `binaryRec`, see
leanprover-community/mathlib4#30144
2025-12-14 05:19:29 +00:00
Kim Morrison
c20378682e chore: add guidance to not merge PRs autonomously in release command (#11661)
This PR adds explicit guidance to the `/release` command that Claude
should never merge PRs autonomously during the release process - always
wait for the user to do it.

🤖 Prepared with Claude Code
2025-12-14 05:17:42 +00:00
Kim Morrison
b7e6862163 fix: remove obsolete docs directory handling for cslib in release_steps.py (#11649)
This PR updates the release checklist script. The cslib repository no
longer has a docs subdirectory, so the release script was failing when
trying to update lakefile.toml and lean-toolchain in that nonexistent
directory.
2025-12-14 05:15:38 +00:00
Leonardo de Moura
983d64395a fix: theorem activation in grind (#11660)
This PR fixes another theorem activation issue in `grind`.
2025-12-13 18:35:30 +00:00
Leonardo de Moura
6db52f0aa9 fix: use naming context in grind pattern suggestions (#11659)
This PR adds `MessageData.withNamingContext` when generating pattern
suggestions at `@[grind]`. It fixes another issue reported during
ItaLean.
2025-12-13 18:15:23 +00:00
Leonardo de Moura
e489c342d7 fix: literal internalization in grind (#11658)
This PR fixes a bug in the internalization of parametric literals in
`grind`. That is, literals whose type is `BitVec _` or `Fin _`.

Closes #11545
2025-12-13 17:47:23 +00:00
Joachim Breitner
0bfbb71796 perf: restore kernel-reduction friendly Nat.hasNotBit definition (#11657)
This PR improves upon #11652 by keeping the kernel-reduction-optimized
definition.
2025-12-13 17:00:33 +00:00
Leonardo de Moura
38c401cf3b feat: new Int operations in grind (#11656)
This PR adds support for `Int.sign`, `Int.fdiv`, `Int.tdiv`, `Int.fmod`,
`Int.tmod`, and `Int.bmod` to `grind`. These operations are just
preprocessed away. We assume that they are not very common in practice.
Examples:
```lean
example {x y : Int} : y = 0 → (x.fdiv y) = 0 := by grind
example {x y : Int} : y = 0 → (x.tdiv y) = 0 := by grind
example {x y : Int} : y = 0 → (x.fmod y) = x := by grind
example {x y : Int} : y = 1 → (x.fdiv (2 - y)) = x := by grind
example {x : Int} : x > 0 → x.sign = 1 := by grind
example {x : Int} : x < 0 → x.sign = -1 := by grind
example {x y : Int} : x.sign = 0 → x*y = 0 := by grind
```

See #11622
2025-12-13 14:55:34 +00:00
Leonardo de Moura
a2ceebe200 feat: semiring * propagators in grind (#11653)
This PR adds propagation rules corresponding to the `Semiring`
normalization rules introduced in #11628. The new rules apply only to
non-commutative semirings, since support for them in `grind` is limited.
The normalization rules introduced unexpected behavior in Mathlib
because they neutralize parameters such as `one_mul`: any theorem
instance associated with such a parameter is reduced to `True` by the
normalizer.
2025-12-13 14:32:34 +00:00
Leonardo de Moura
292b74b0a4 chore: update grind docstring (#11654)
This PR updates the `grind` docstring. It was still mentioning `cutsat`
which has been renamed to `lia`. This issue was reported during ItaLean.
2025-12-13 14:32:30 +00:00
Joachim Breitner
d76752ffb8 feat: grind support for .ctorIdx (#11652)
This PR teaches `grind` how to reduce `.ctorIdx` applied to
constructors. It can also handle tasks like
```
xs ≍ Vec.cons x xs' → xs.ctorIdx = 1
```
thanks to a `.ctorIdx.hinj` theorem (generated on demand).
2025-12-13 13:32:19 +00:00
Kim Morrison
d4463ce549 chore: fix CMakeLists.txt CI check (#11650) 2025-12-13 11:49:20 +00:00
Kim Morrison
074dc60bea chore: update release docs/scripts for lean-fro.org (#11648) 2025-12-13 11:06:17 +00:00
Lean stage0 autoupdater
6d8a16f137 chore: update stage0 2025-12-13 09:57:36 +00:00
Joachim Breitner
f0e594d5db refactor: make .ctorIdx not an abbrev (#11644)
This PR makes `.ctorIdx` not an abbrev; we don't want `grind` to unfold
it.
2025-12-13 09:14:59 +00:00
Joachim Breitner
32d22075dc doc: fix docstring of propagateForallPropUp (#11645)
This PR fixes the docstring of `propagateForallPropUp`. It was
copy’n’pasta before.
2025-12-13 08:14:04 +00:00
Lean stage0 autoupdater
902226642f chore: update stage0 2025-12-13 03:28:09 +00:00
Kim Morrison
67ba4da71f fix: avoid SIGFPE on x86_64 for signed integer division overflow (#11624)
This PR fixes a SIGFPE crash on x86_64 when evaluating `INT_MIN / -1` or
`INT_MIN % -1` for signed integer types.

On x86_64, the `idiv` instruction traps when the quotient overflows the
destination register. For signed integers, `INT_MIN / -1` produces a
result that overflows (e.g., `-2147483648 / -1 = 2147483648` which
doesn't fit in Int32). ARM64's `sdiv` instruction wraps instead of
trapping.

The fix:
- For Int8/Int16/Int32: widen to the next larger type before
dividing/modding, then truncate back
- For Int64: explicitly check for the overflow case and return the
wrapped result

Fixes #11612

🤖 Prepared with Claude Code
2025-12-13 02:42:33 +00:00
Thomas R. Murrills
0eed450b86 chore: fix typo LeanLib.sharedLibLeanLib.sharedFacet (#11641)
This PR fixes a typo in the docstring of `LeanLibConfig.defaultFacets`
and the Lake README that erroneously referred to `LeanLib.sharedLib`
instead of `LeanLib.sharedFacet`.

See e.g. `tests/lake/tests/targets/lakefile.lean` to verify that
`LeanLib.sharedFacet` is correct usage; `LeanLib.sharedLib` does not
exist.
2025-12-12 23:17:33 +00:00
Joachim Breitner
834886bca2 chore: remove comment from wrong stdlib_flags.h (#11646)
This PR again removes a comment from wrong `stdlib_flags.h`. Only the
one in `stage0/` should be edited.
2025-12-12 22:59:38 +00:00
Henrik Böving
5339c47555 chore: benchmark for charactersIn (#11643) 2025-12-12 22:23:51 +00:00
Sebastian Ullrich
1f80b3ffbe feat: module system is no longer experimental (#11637)
This PR declares the module system as no longer experimental and makes
the `experimental.module` option a no-op, to be removed.
2025-12-12 21:20:26 +00:00
Sebastian Ullrich
de388a7e6d feat: unknown identifier code action and the module system (#11164)
This PR ensures that the code action provided on unknown identifiers
correctly inserts `public` and/or `meta` in `module`s
2025-12-12 21:19:34 +00:00
Sebastian Ullrich
5fff9fb228 perf: remove obsolete old codegen workaround (#9311) 2025-12-12 20:51:34 +00:00
Henrik Böving
59045c6227 chore: make workspaceSymbols benchmark independent of sorry search (#11642) 2025-12-12 20:10:27 +00:00
Joe Hendrix
ac7b95da86 feat: port Batteries.WF for executable well-founded fixpoints (#11620)
This PR ports Batteries.WF to Init.WFC for executable well-founded
fixpoints. It introduces `csimp` theorems to replace the recursors and
non-executable definitions with executable definitions.

This ocassionally comes up on Zulip as it prevents admiting definitions
generated from well-founded induction. (e.g., [#lean4 > Computable
WellFounded.fix](https://leanprover.zulipchat.com/#narrow/channel/270676-lean4/topic/Computable.20WellFounded.2Efix/with/529347861)
and [#mathlib4 > Why Nat.find is computable, when Wellfounded.fix
isn't?](https://leanprover.zulipchat.com/#narrow/channel/287929-mathlib4/topic/Why.20Nat.2Efind.20is.20computable.2C.20when.20Wellfounded.2Efix.20isn't.3F/with/545143617)).

This was motivated by running into poor elaboration performance with
recursive definitions involving complex inductive types generated by a
custom elaborator. It would be helpful to explore bypassing the
elaborator and generating elaborated terms directly, but this requires
an executable fixpoint (such as `WellFounded.fixC` introduced in
batteries).
2025-12-12 18:22:54 +00:00
Leonardo de Moura
bb264e1ff0 feat: BitVec.ofNat in grind lia (#11640)
This PR adds support for `BitVec.ofNat` in `grind lia`. Example:

```lean
example (x y : BitVec 8) : y < 254#8 → x > 2#8 + y → x > 1#8 + y := by
  grind
```
2025-12-12 17:50:38 +00:00
Leonardo de Moura
28fca70cb7 feat: BitVec.ofNat in grind ring (#11639)
This PR adds support for `BitVec.ofNat` in `grind ring`. Example:

```lean
example (x : BitVec 8) : (x - 16#8)*(x + 272#8) = x^2 := by
  grind
```
2025-12-12 17:41:32 +00:00
Leonardo de Moura
5e4c90c3d1 feat: bitvec literal internalization in grind (#11638)
This PR fixes bitvector literal internalization in `grind`. The fix
ensures theorems indexed by `BitVec.ofNat` are properly activated.
2025-12-12 17:28:35 +00:00
Lean stage0 autoupdater
9df8a80c7d chore: update stage0 2025-12-12 16:00:39 +00:00
Paul Reichert
9d7d15b276 feat: lint coercions that are deprecated or banned in core (#11511)
This PR implements a linter that warns when a deprecated coercion is
applied. It also warns when the `Option` coercion or the
`Subarray`-to-`Array` coercion is used in `Init` or `Std`. The linter is
currently limited to `Coe` instances; `CoeFun` instances etc. are not
considered.

The linter works by collecting the `Coe` instance declaration names that
are being expanded in `expandCoe?` and storing them in the info tree.
The linter itself then analyzes the info tree and checks for banned or
deprecated coercions.
2025-12-12 15:09:13 +00:00
Sebastian Ullrich
7f5e28ac89 chore: shake: keep-downstream and import privacy (#11634) 2025-12-12 14:09:50 +00:00
Leonardo de Moura
9be007ad70 fix: enforce Grind.genPattern and Grind.getHEqPattern assumptions (#11635)
This PR ensures the pattern normalizer used in `grind` does violate
assumptions made by the gadgets `Grind.genPattern` and
`Grind.getHEqPattern`.

Closes #11633
2025-12-12 14:05:46 +00:00
Sebastian Ullrich
0c7169efa9 perf: more environment blocking avoidance (#11616) 2025-12-12 13:44:58 +00:00
Wojciech Różowski
73d389f358 feat: add decidable equality to DTreeMap/TreeMap/TreeSet and their extensional variants (#11527)
This PR adds decidable equality to `DTreeMap`/`TreeMap`/`TreeSet` and
their extensional variants.

Stacked on top #11404.
2025-12-12 12:47:57 +00:00
Robert J. Simmons
0158172871 chore: remove NameMapExtension abbreviation (#11632)
This PR avoids a conflict with Mathlib by inlining an abbreviation,
NameMapExtension, that wasn't referred to outside the file.
2025-12-12 12:34:53 +00:00
Sebastian Ullrich
520cdb2038 feat: do not have meta imply partial (#11587)
This PR adjusts the new `meta` keyword of the experimental module system
not to imply `partial` for general consistency.

As the previous behavior can create confusion return types that are not
known to be `Nonempty` and few `def`s should be `meta` in the first
case, this special case does not appear to be worth the minor
convenience.
2025-12-12 11:42:42 +00:00
Sebastian Ullrich
9f74e71f10 perf: avoid kernel env block in realizeConst (#11617) 2025-12-12 11:20:20 +00:00
Kim Morrison
ad02aa159c chore: remove ≥6 month old deprecations (#11627) 2025-12-12 10:40:04 +00:00
Wojciech Różowski
07645775e6 feat: add decidable equality to DHashMap/HashMap/HashSet and their extensional variants (#11421)
This PR adds decidable equality to `DHashMap`/`HashMap`/`HashSet` and
their extensional variants.

Stacked on top of #11266.
2025-12-12 09:55:55 +00:00
Leonardo de Moura
a984e17913 fix: missing condition at normalizePattern in grind (#11629)
This PR adds a missing condition in the pattern normalization code used
in `grind`. It should ignore support ground terms.
2025-12-12 09:32:31 +00:00
Leonardo de Moura
8220baf6db feat: add a few Semiring normalization rules to grind (#11628)
This PR adds a few `*` normalization rules for `Semiring`s to `grind`.
2025-12-12 09:10:49 +00:00
Sebastian Ullrich
fc26c8145c chore: realization of private def should run in private scope (#11618)
In fact the scope should be solely determined by the def name but this
breaks some existing realizer, so postponed.
2025-12-12 08:56:08 +00:00
Wojciech Różowski
9e4f9d317b feat: add BEq to DTreeMap/TreeMap/TreeSet and their extensional variants (#11404)
This PR adds BEq instance for `DTreeMap`/`TreeMap`/`TreeSet` and their
extensional variants and proves lemmas relating it to the equivalence of
hashmaps/equality of extensional variants.

Stacked on top of #11266
2025-12-12 08:30:36 +00:00
Wojciech Różowski
3937af3d75 feat: add a lemma relating minKey? and min? for DTreeMap (#11528)
This PR adds lemmas relating `minKey?` and `min?` on the keys list for
all `DTreeMap` and other containers derived from it.
2025-12-12 08:03:00 +00:00
Eric Wieser
646df6ba16 feat: add lemmas about EStateM.run (#11600)
This PR adds a few lemmas about `EStateM.run` on basic operations.
2025-12-12 03:00:17 +00:00
Kim Morrison
552fa10a60 feat: @[suggest_for ℤ] and @[suggest_for ℚ] annotations (#11596)
This PR adds `@[suggest_for ℤ]` on `Int` and `@[suggest_for ℚ]` on
`Rat`, following the pattern established by `@[suggest_for ℕ]` on `Nat`
in #11554.


🤖 Prepared with Claude Code

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-12 02:59:35 +00:00
Robert Maxton
7d2dbe8787 feat: expose decidable_of_bool (#11625)
This PR adds `@[expose]` to `decidable_of_bool` so that
proofs-by-`decide` elsewhere that reduce to `decidable_of_bool` continue
to reduce.
2025-12-12 02:57:49 +00:00
Robert J. Simmons
3bd1dd633f feat: identifier suggestions on some autobinding failures (#11621)
This PR causes Lean to search through `@[suggest_for]` annotations on
certain errors that look like unknown identifiers that got incorrectly
autobound. This will correctly identify that a declaration of type
`Maybe String` should be `Option String` instead.

## Example

```
example : Except String Unit := return .ok ()
```

```
Function expected at
  Result
but this term has type
  ?m.1

Note: Expected a function because this term is being applied to the argument
  String

Hint: The identifier `Result` is unknown, and Lean's `autoImplicit` option causes an unknown identifier to be treated as an implicitly bound variable with an unknown type. However, the unknown type cannot be a function, and a function is what Lean expects here. This is often the result of a typo or a missing `import` or `open` statement.

Perhaps you meant `Except` in place of `Result`?
```

The last line is added by this PR.
2025-12-11 21:40:16 +00:00
Robert J. Simmons
d824f7e085 feat: identifier suggestions for non-dotted identifiers (#11619)
This PR allows Lean to present suggestions based on `@[suggest_for]`
annotations for unknown identifiers without internal dots. (The
annotations in #11554 only gave suggestion for dotted identifiers like
`Array.every`->`Array.all` and not for bare identifiers like
`Result`->`Except` or `ℕ`->`Nat`.)
2025-12-11 19:47:18 +00:00
Sebastian Graf
381c0f2b61 fix: proper error messages for Std.Do tactic invokations without arguments (#11509) (#11607)
This PR makes argument-less tactic invokations of `Std.Do` tactics such
as `mintro` emit a proper error message "`mintro` expects at least one
pattern" instead of claiming that `Std.Tactic.Do` needs to be imported.

Closes #11509.
2025-12-11 17:44:52 +00:00
Sebastian Ullrich
2d3dec41ca perf: avoid blocking on proof elaboration in Environment.hasUnsafe (#11606) 2025-12-11 17:24:01 +00:00
Garmelon
cec9758c2d chore: measure dynamic symbols in benchmarks (#11568)
Add back the dynamic symbol measurements that were removed in #11264.
2025-12-11 16:10:27 +00:00
Leonardo de Moura
c4477939d0 feat: Int.subNatNat in grind (#11615)
This PR adds a normalization rule for `Int.subNatNat` to `grind`.

Closes #11543
2025-12-11 15:42:42 +00:00
Lean stage0 autoupdater
864acddb4a chore: update stage0 2025-12-11 15:53:31 +00:00
Robert J. Simmons
26ff270e28 fix: better performance for @[suggest_for] (#11598)
This PR fixes a performance issue resulting from misusing persistent
environment extensions that was introduced in #11554.
2025-12-11 15:21:33 +00:00
Leonardo de Moura
6469890178 fix: apply ring normalizer to equalities coming from grind to core to lia (#11613)
This PR ensures we apply the ring normalizer to equalities being
propagated from the `grind` core module to `grind lia`. It also ensures
we use the safe/managed polynomial functions when normalizing.

Closes #11539
2025-12-11 14:32:54 +00:00
Lean stage0 autoupdater
37f9984d71 chore: update stage0 2025-12-11 12:37:21 +00:00
Joachim Breitner
138476d635 fix: noConfusion shape info mistake (#11611)
This PR fixes a `noConfusion` compilation introduced by #11562.

fixes #11610.
2025-12-11 11:50:37 +00:00
Leonardo de Moura
ea10bdf154 feat: improve case-split heuristic in grind (#11609)
This PR improves the case-split heuristics in `grind`. In this PR, we do
not increment the number of case splits in the first case. The idea is
to leverage non-chronological backtracking: if the first case is solved
using a proof that doesn't depend on the case hypothesis, we backtrack
and close the original goal directly. In this scenario, the case-split
was "free", it didn't contribute to the proof. By not counting it, we
allow deeper exploration when case-splits turn out to be irrelevant.
The new heuristic addresses the second example in #11545
2025-12-11 10:42:17 +00:00
Henrik Böving
b8c53b1d29 chore: remove IR elim dead branches (#11576)
This PR removes the old ElimDeadBranches pass and shifts the new one
past lambda lifting.

The reason for dropping the old one is its general unsoundness and the
fact that we want to do refactorings on the IR part. The reason for
shifting the current pass past lambda lifting, is that its analysis is
imprecise in the presence of local function symbols. I experimented with
the exact placement for a while and it seems like it is optimal here.
Overall we observe a slight regression in the amount of C code
generated, likely because we don't propagate information into lambdas
before lifting them anymore. But generally measure a slight performance
improvement in general.
2025-12-11 10:39:02 +00:00
Leonardo de Moura
e7f4fc9baf fix: theorems without parameters in grind E-matching (#11604)
This PR fixes how theorems without parameters are handled in `grind`.

This is a better fix than #11579

---------

Co-authored-by: Kim Morrison <kim@tqft.net>
2025-12-11 10:33:48 +00:00
Mac Malone
d145b9f8ee chore: lake: mv targets test to tests (#11592)
This PR moves Lake's `tests/lake/examples/targets` test from `examples`
to `tests` (and thus disabling it by default).

It is being
[flaky](https://github.com/leanprover/lean4/actions/runs/20111185289/attempts/1)
for some unknown reason, so I am disabling until I have a better
opportunity to debug it.
2025-12-11 09:28:44 +00:00
Leonardo de Moura
05a81248df fix: power internalization in grind linarith (#11605)
This PR fixes a bug in the internalizer of `a^p` terms in `grind
linarith`.

Closes #11597
2025-12-11 08:44:47 +00:00
Leonardo de Moura
9928cf3d64 chore: revert fix: ground theorems as grind parameters" (#11603)
This PR reverts leanprover/lean4#11579
2025-12-11 08:17:40 +00:00
Leonardo de Moura
aa4aff280b fix: ground theorems as grind parameters (#11579)
This PR ensures that ground theorems are properly handled as `grind`
parameters. Additionally, `grind [(thm)]` and `grind [thm]` should be
handled the same way.

---------

Co-authored-by: Kim Morrison <kim@tqft.net>
2025-12-11 07:43:21 +00:00
Kim Morrison
eee58f4506 fix: include term parameters in grind? suggestions (#11594)
This PR fixes `grind?` to include term parameters (like `[show P by
tac]`) in its suggestions. Previously, these were being dropped because
term arguments are stored in `extraFacts` and not tracked via E-matching
like named lemmas.

For example, `grind? [show False by exact h]` now correctly suggests
`grind only [show False by exact h]` instead of just `grind only`.

🤖 Prepared with Claude Code

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-11 05:16:17 +00:00
Kim Morrison
756396ad8f feat: add +all option to exact? and apply? (#11556)
This PR adds a `+all` option to `exact?` and `apply?` that collects all
successful lemmas instead of stopping at the first complete solution.

When `+all` is enabled:
- `exact?` shows all lemmas that completely solve the goal (admits the
goal with `sorry`)
- `apply?` shows all lemmas including both complete and partial
solutions

🤖 Prepared with Claude Code

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> Adds a +all flag to exact? and apply? to collect all successful
lemmas, updates library search to support aggregation and proper
star-lemma fallback, and extends the discriminator tree to
extract/append dropped entries; includes tests.
> 
> - **Tactics / UI**:
> - Add `LibrarySearchConfig.all` and `+all` flag to `exact?`/`apply?`
to collect all successful lemmas.
> - `exact?` now aggregates complete solutions (via
`addExactSuggestions`); `apply?` shows both complete and partial
suggestions.
>   - Updated help texts and error/hint messages.
> - **Library Search Core (`Lean.Meta.Tactic.LibrarySearch`)**:
> - Thread new `collectAll` option through `tryOnEach`,
`librarySearch'`, and `librarySearch`.
> - `tryOnEach` continues collecting complete solutions when `collectAll
= true`.
> - Star-lemma fallback now runs even when primary search yields only
partial results; include complete solutions when aggregating.
> - Cache and retrieve star-indexed lemmas via
`droppedEntriesRef`/`getStarLemmas`.
> - **Lazy Discriminator Tree (`Lean.Meta.LazyDiscrTree`)**:
> - Add `extractKey(s)`/`collectSubtreeAux` to extract and drop entries,
returning them.
> - Modify import/module tree building to optionally append dropped
entries to a shared ref (for star-lemmas), and pass this through
`findMatches`/`createModuleTreeRef`.
> - Minor comment/logic tweaks (append vs set) when handling dropped
entries.
> - **Elaboration (`Lean.Elab.Tactic.LibrarySearch`)**:
> - Integrate `collectAll` into `exact?`/`apply?`; partition and present
complete vs incomplete suggestions; admit goals appropriately when
aggregating.
> - **Tests**:
> - Update existing expectations and add
`tests/lean/run/library_search_all.lean` to verify `+all`, aggregation,
and star-lemma behavior.
> 
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
cbfc9313af. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->

---------

Co-authored-by: Claude <noreply@anthropic.com>
2025-12-11 03:30:52 +00:00
Kim Morrison
cc89a853e5 doc: note that tests/lean/run disables linters (#11595)
This PR documents that tests in `tests/lean/run/` run with
`-Dlinter.all=false`, and explains how to enable specific linters when
testing linter behavior.

🤖 Prepared with Claude Code
2025-12-11 01:33:07 +00:00
Kim Morrison
1c1bd8e064 fix: handle dot notation on local variables in grind parameters (#11573)
This PR fixes `grind` rejecting dot notation terms, mistaking them for
local hypotheses.

When a grind parameter like `n.triv` is given, where `n` is a local
variable and `triv` is a theorem that takes `n` as an argument (so
`n.triv` means `Nat.triv n`), grind was incorrectly rejecting it with
"redundant parameter" because it detected that the identifier resolved
to a local variable via `resolveLocalName`.

The fix checks if `resolveLocalName` returns field projections
(non-empty list), indicating dot notation. In that case, we process the
parameter as a term expression to let elaboration resolve the dot
notation properly, rather than trying to resolve it as a global constant
name.

### Minimal reproducer

```lean
theorem Nat.triv (n : Nat) : n = n := rfl

example (n : Nat) : n = n := by
  grind [n.triv]  -- Previously: "redundant parameter `n.triv`"
```

This also fixes the issue where `grind [x.exp_pos]` was rejected even
though `x.exp_pos` elaborates to `Real.exp_pos x`, a valid theorem
application.

🤖 Prepared with Claude Code
2025-12-11 01:28:22 +00:00
Sebastian Ullrich
a20e029718 chore: make auto param decls of private decls private (#11581)
Fixes #11569
2025-12-11 01:10:45 +00:00
Eric Wieser
95e33d88a8 feat: add MonadControl lemmas for ReaderT, OptionT, StateT, and ExceptT (#11591)
This PR adds missing lemmas about how `ReaderT.run`, `OptionT.run`,
`StateT.run`, and `ExceptT.run` interact with `MonadControl` operations.

This also leaves some comments noting that the lemmas may look less
general than expected; but this is because the instances are also not
very general.
2025-12-11 00:49:08 +00:00
Kim Morrison
351a941756 fix: show deprecation warnings for grind theorem arguments (#11593)
This PR fixes an issue where `grind` did not display deprecation
warnings when deprecated lemmas were used in its argument list.

The fix adds explicit calls to `Linter.checkDeprecated` after resolving
theorem names in both `processParam` (for theorem arguments) and
`elabGrindParams` (for the `-` erase syntax).

Closes #11582

🤖 Prepared with Claude Code

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-11 00:43:15 +00:00
Kim Morrison
124e34ef5a feat: grind_pattern natCast_nonneg (#11574)
This PR adds a lemma that the cast of a natural number into any ordered
ring is non-negative. We can't annotate this directly for `grind`, but
will probably add this to `grind`'s linarith interrnals.
2025-12-10 23:10:26 +00:00
Robert J. Simmons
f88e503f3d feat: @[suggest_for] annotations for prompting easy-to-miss names (#11554)
This PR adds `@[suggest_for]` annotations to Lean, allowing lean to
provide corrections for `.every` or `.some` methods in place of `.all`
or `.any` methods for most default-imported types (arrays, lists,
strings, substrings, and subarrays, and vectors).

Due to the need for stage0 updates for new annotations, the
`suggest_for` annotation itself was introduced in previous PRs: #11367,
#11529, and #11590.

## Example
```
example := "abc".every (! ·.isWhitespace)
```

Error message:
```
Invalid field `every`: The environment does not contain `String.every`, so it is not possible to project the field `every` from an expression
  "abc"
of type `String`

Hint: Perhaps you meant `String.all` in place of `String.every`:
  .e̵v̵e̵r̵y̵a̲l̲l̲
```

(the hint is added by this PR)

## Additional changes

Adds suggestions that are not currently active but that can be used to
generate autocompletion suggestions in the reference manual:
 - `Either` -> `Except` and `Sum`
 - `Exception` -> `Except`
 - `ℕ` -> `Nat`
 - `Nullable` -> `Option` 
 - `Maybe` -> `Option`
 - `Optional` -> `Option`
 - `Result` -> `Except`
2025-12-10 22:50:45 +00:00
Lean stage0 autoupdater
2e4b079e73 chore: update stage0 2025-12-10 22:16:36 +00:00
Robert J. Simmons
9e87560376 chore: switch the association of stored suggestions (#11590)
This PR switches the way olean files store identifier suggestions. The
ordering introduced in #11367 and #11529 made sense if we were only
storing incorrect -> correct mappings, but for the reference manual we
want to store the correct -> incorrect mappings as well, and so it is
more sensible to store just the correct -> incorrect mapping that mimics
the author-generated data better.

Also tweaks error messages further in preparation for public-facing
@[suggest_for] annotations and forbids suggestions on non-public names.

Does not make generally-visible changes as there are no introduced uses
of @[suggest_for] annotations yet.
2025-12-10 21:42:05 +00:00
Eric Wieser
466a24893b chore: add ReaderT.mk and StateT.mk (#11470)
These complement the existing `ExceptT.mk` and `OptionT.mk`, and provide
a symbol to key `simp` lemmas on, to prevent getting stuck on
`StateT.run (fun s => f s) s`.

A future PR could insert these new `mk`s into the implementation of many
definitions, such that unfolding the definitions leaves appropriate
casts behind; but this is invasive, and by itself having `mk` provides
value.
2025-12-10 21:11:03 +00:00
Lean stage0 autoupdater
acb7bc5f22 chore: update stage0 2025-12-10 19:42:56 +00:00
Leonardo de Moura
595d87b5e6 feat: include symbols in ground grind patterns (#11589)
This PR improves indexing for `grind` patterns. We now include symbols
occurring in nested ground patterns. This important to minimize the
number of activated E-match theorems.
2025-12-10 18:51:57 +00:00
Wojciech Różowski
361bfdbc5c refactor: HashMap/TreeMap and their extensional variants to use getElem instance (#11578)
This PR refactors the usage of `get` operation on
`HashMap`/`TreeMap`/`ExtHashMap`/`ExtTreeMap` to `getElem` instace.
2025-12-10 17:52:34 +00:00
Wojciech Różowski
2b257854f9 feat: add lemmas relating insert/insertIfNew and toList on DTreeMap/DHashMap-derived containers (#11565)
This PR adds lemmas that relate `insert`/`insertIfNew` and `toList` on
`DTreeMap`/`DHashMap`-derived containers.
2025-12-10 17:52:18 +00:00
Eric Wieser
18248651a3 fix: call delete [] on array allocations (#11453)
This PR fixes undefined behavior where `delete` (instead of `delete[]`)
is called on an object allocated with `new[]`.
2025-12-10 16:51:54 +00:00
Joachim Breitner
a35ba44197 chore: post-stage0 update fixes 2025-12-10 17:28:06 +01:00
Joachim Breitner
a7ecae5189 chore: update stage0 2025-12-10 17:28:06 +01:00
Joachim Breitner
42e8011320 feat: make noConfusion even more heterogeneous
This PR makes the noConfusion principles even more heterogeneous, by
allowing not just indices but also parameters to be differ.

This is a breaking change for manual use of `noConfusion` for types with
parameters. Pass suitable `rfl` arguments, and use `eq_of_heq` on the
resulting equalities as needed.

This fixes #11560.
2025-12-10 17:28:06 +01:00
Wojciech Różowski
ec008ff55a feat: add BEq to DHashMap/HashMap/HashSet and their extensional variants (#11266)
This PR adds `BEq` instance for `DHashMap`/`HashMap`/`HashSet` and their
extensional variants and proves lemmas relating it to the equivalence of
hashmaps/equality of extensional variants.
2025-12-10 15:40:09 +00:00
Henrik Böving
72196169b6 chore: legitimize projections on tagged values (#11586)
This PR allows projections on `tagged` values in the IR type system.

While executing this branch of code should indeed never happen in
practice, enforcing this through
the type system would require the compiler to always optimize code to
the point where this is not
possible. For example in the code:
```
cases x with
| none => ....
| some =>
    let val : obj := proj[0] x
    ...
```
static analysis might learn that `x` is always none and transform this
to:
```
let x : tagged := none
cases x with
| none => ....
| some =>
    let val : obj := proj[0] x
    ...
```
Which would be type incorrect if projections on `tagged` were
illegitimate. However, we don't want
to force static analysis to always simplify code far enough on its own
to enforce this invariant.
2025-12-10 13:33:01 +00:00
Sofia Rodrigues
9466a052bc fix: segmentation fault that was triggered when initializing a new timer and a reset was called at the same time (#11521)
This PR fixes a segmentation fault that was triggered when initializing
a new timer and a reset was called at the same time.
2025-12-10 12:59:33 +00:00
Eric Wieser
589dde0c3b chore: add the missing ExceptT.run_mk (#11460) 2025-12-10 11:06:41 +00:00
Leonardo de Moura
b9e888df4e fix: add Nat.cast normalizer missing case (#11580)
This PR adds a missing `Nat.cast` missing normalization rule for
`grind`. Example:
```lean
example (n : Nat) : Nat.cast n = n := by
  grind
```
2025-12-10 10:56:47 +00:00
Eric Wieser
088f8b0b9c fix: teach Exception.isRuntime to detect nested errors (#11490)
This PR prevents `try` swallowing heartbeat errors from nested `simp`
calls, and more generally ensures the `isRuntime` flag is propagated by
`throwNestedTacticEx`. This prevents the behavior of proofs (especially
those using `aesop`) being affected by the current recursion depth or
heartbeat limit.

This breaks a single caller in Mathlib where `simp` uses a lemma of the
form `x = f (g x)` and stack overflows, which can be fixed by
generalizing over `g x`.

Closes #7811.

---------

Co-authored-by: Sebastian Ullrich <sebasti@nullri.ch>
2025-12-10 10:19:33 +00:00
Sebastian Ullrich
30ea4170a7 fix: progress bar in tactic combinators (#11577)
This PR fixes the tactic framework reporting file progress bar ranges
that cover up progress inside tactic blocks nested in tactic
combinators. This is a purely visual change, incremental re-elaboration
inside supported combinators was not affected.

Also adds a test though it is not elaborate enough to test proper timing
of progress events per se; see moddoc there.

![Recording 2025-12-10 at 10 21
52](https://github.com/user-attachments/assets/019b8f13-5aad-4b2c-ab0d-a1348033c6be)
2025-12-10 10:04:41 +00:00
Lean stage0 autoupdater
0738e4d61a chore: update stage0 2025-12-10 09:53:29 +00:00
Joachim Breitner
3b40682b22 perf: handle per-constructor noConfusion in toLCNF (#11566)
This PR lets the compiler treat per-constructor `noConfusion` like the
general one, and moves some more logic closer to no confusion
generation.
2025-12-10 09:03:55 +00:00
Zhao Yuyang 赵雨扬
06037ade0f doc: fix typo in Init.Coe module docstring (#11567) 2025-12-10 08:48:55 +00:00
Markus Himmel
9895e25e95 doc: fix typo in docstring of the cases tactic (#11575)
This PR fixes a typo in the docstring of the `cases` tactic.
2025-12-10 08:29:13 +00:00
Joachim Breitner
19e1fe55f3 perf: do not consult isNoConfusion in whnf (#11571)
This PR lets `whnf` not consult `isNoConfusion`, to speed up this hot
path a bit.
2025-12-09 23:36:46 +00:00
Joachim Breitner
5bf5c73f74 chore: prune imports in Try.Collect (#11570)
This PR removed unused imports from Try.Collect
2025-12-09 22:15:34 +00:00
Robert J. Simmons
5326530383 feat: suggestions for ambiguous dotted identifiers (#11555)
This PR scans the environment for viable replacements for a dotted
identifier (like `.zero`) and suggests concrete alternatives as
replacements.

## Example

```
#example .zero
```

Error message:
```
Invalid dotted identifier notation: The expected type of `.cons` could not be determined
```

Additional hint added by this PR:
```
Hint: Using one of these would be unambiguous:
  [apply] `BitVec.cons`
  [apply] `List.cons`
  [apply] `List.Lex.cons`
  [apply] `List.Pairwise.cons`
  [apply] `List.Perm.cons`
  [apply] `List.Sublist.cons`
  [apply] `List.Lex.below.cons`
  [apply] `List.Pairwise.below.cons`
  [apply] `List.Perm.below.cons`
  [apply] `List.Sublist.below.cons`
  [apply] `Lean.Grind.AC.Seq.cons`
```

## Additional changes

This PR also brings several related error message descriptions and code
actions more in line with each other, changing several "Suggested
replacement: " code actions to the more common "Change to " wording, and
sorts suggestions obtained from searching the context by the default
sort for Names (which prefers names with fewer segments).
2025-12-09 17:27:22 +00:00
Markus Himmel
9f99c512e7 doc: grove: more String data (#11557)
This PR adds some information to Grove: a check that all
string/slice-transforming functions are tracked properly (which finds
dozens of missed cases), and some documentation of design designs around
naming in the string library.

The PR also bumps the Grove version to the latest version which contains
many new features and also processes the data a lot faster (40s to 2.5s
for the test project).
2025-12-09 15:49:33 +00:00
Sebastian Ullrich
d247dcffc4 chore: delete obsolete C++ file (#11561) 2025-12-09 15:47:54 +00:00
Lean stage0 autoupdater
2fff4c6522 chore: update stage0 2025-12-09 15:32:10 +00:00
Joachim Breitner
c213882788 chore: remove comment from wrong stdlib_flags.h (#11564)
This PR removes a comment from wrong `stdlib_flags.h`. Only the one in
`stage0/` should be edited.
2025-12-09 14:58:30 +00:00
Eric Wieser
6e711bf067 fix: ensure padding bytes for lean::mpz objects in olean files are zero (#11485)
This PR ensures that `Nat`s in `.olean` files use a deterministic
serialization in the case where `LEAN_USE_GMP` is not set.

This is a simplified version of
https://github.com/leanprover/lean4/pull/2908.
2025-12-09 10:59:15 +00:00
Markus Himmel
cfef643d03 chore: ci: bump grove-action to v0.5 (#11559)
This PR bumps `grove-action` to version 0.5, which fixes a bug in the
handling of upstream invalidated facts.
2025-12-09 10:33:31 +00:00
David Thrane Christiansen
bf51e1dcfa doc: docstring review for Std.Do (#11550)
This PR reviews the docstrings for `Std.Do` that will appear in the Lean
reference manual and adds those that were missing.

---------

Co-authored-by: Sebastian Graf <sgraf1337@gmail.com>
2025-12-09 09:51:52 +00:00
Mac Malone
1d0d3915ca refactor: lake: disambiguate packages by workspace index (#11500)
This PR adds a workspace-index to the name of the package used by build
target. To clarify the distinction between the different uses of a
package's name, this PR also deprecates `Package.name` for more
use-specific variants (e.g., `Package.keyName`, `Package.prettyName`,
`Package.origName`).

More to come. (WIP)
2025-12-09 02:07:24 +00:00
Markus Himmel
3c100ada2a doc: grove: update and add String data (#11551)
This PR bumps Grove to the latest revision and starts adding data about
the `String` library.

Just a small start, more to come.
2025-12-08 16:49:37 +00:00
Paul Reichert
383c0caa91 feat: remove Finite conditions from iterator consumers relying on a new fixpoint combinator (#11038)
This PR introduces a new fixpoint combinator,
`WellFounded.extrinsicFix`. A termination proof, if provided at all, can
be given extrinsically, i.e., looking at the term from the outside, and
is only required if one intends to formally verify the behavior of the
fixpoint. The new combinator is then applied to the iterator API.
Consumers such as `toList` or `ForIn` no longer require a proof that the
underlying iterator is finite. If one wants to ensure the termination of
them intrinsically, there are strictly terminating variants available
as, for example, `it.ensureTermination.toList` instead of `it.toList`.
2025-12-08 16:03:22 +00:00
Garmelon
cbf6fe5d1b chore: add mathlib4-nightly-available label (#11526)
This PR automatically adds
https://github.com/leanprover/lean4/labels/mathlib4-nightly-available to
a PR if a corresponding branch exists in
https://github.com/leanprover-community/mathlib4-nightly-testing. This
way, the `!bench mathlib` command can delay the benchmark job until
everything is ready.
2025-12-08 14:04:21 +00:00
Henrik Böving
e11800d3c8 perf: annotate built-in functions with tagged_return (#11549)
This PR annotates built-in `extern` functions with `tagged_return`.
2025-12-08 13:10:55 +00:00
Lean stage0 autoupdater
c9b8508f6b chore: update stage0 2025-12-08 11:24:45 +00:00
Henrik Böving
ecce5e69bf feat: tagged_return attribute (#11530)
This PR introduces the new `tagged_return` attribute. It allows users to
mark `extern` declarations to be guaranteed to always return `tagged`
return values. Unlike with `object` or `tobject` the compiler does not
emit reference counting operations for them. In the future information
from this attribute will be used for a more powerful analysis to remove
reference counts when possible.
2025-12-08 10:55:46 +00:00
Markus Himmel
459e9f702f feat: ToJson and FromJson for String.Slice (#11548)
This PR adds `Lean.ToJson` and `Lean.FromJson` instances for
`String.Slice`.
2025-12-08 10:19:42 +00:00
Kim Morrison
62f2f92293 fix: make register_try?_tactic auxiliary definitions internal (#11547)
This PR ensures the auxiliary definitions created by
`register_try?_tactic` are internal implementation details that should
not be visible to user-facing linters.

🤖 Generated with Claude Code
2025-12-08 05:49:01 +00:00
Kim Morrison
6cbcbce750 feat: support underscores in String.toNat? and String.toInt? (#11541)
This PR adds support for underscores as digit separators in
String.toNat?, String.toInt?, and related parsing functions. This makes
the string parsing functions consistent with Lean's numeric literal
syntax, which already supports underscores for readability (e.g.,
100_000_000).

The implementation validates that underscores:
- Cannot appear at the start or end of the number
- Cannot appear consecutively
- Are ignored when calculating the numeric value

This resolves a common source of friction when parsing user input from
command-line arguments, environment variables, or configuration files,
where users naturally expect to use the same numeric syntax they use in
source code.

## Examples

Before:
```lean
#eval "100_000_000".toNat?  -- none
```

After:
```lean
#eval "100_000_000".toNat?  -- some 100000000
#eval "1_000".toInt?        -- some 1000
#eval "-1_000_000".toInt?   -- some (-1000000)
```

## Testing

Added comprehensive tests in
`tests/lean/run/string_toNat_underscores.lean` covering:
- Basic underscore support
- Edge cases (leading/trailing/consecutive underscores)
- Both `toNat?` and `toInt?` functions
- String, Slice, and Substring types

All existing tests continue to pass.

Closes #11538

🤖 Prepared with Claude Code

---------

Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-08 03:57:55 +00:00
Kim Morrison
cdb994b776 chore: remove @[grind =] from List.countP_eq_length_filter (#11542)
This PR removes `@[grind =]` from `List.countP_eq_length_filter` and
`Array.countP_eq_size_filter`, as users
[reported](https://leanprover.zulipchat.com/#narrow/channel/270676-lean4/topic/.60countP_eq_length_filter.60.20grind.20attribute/near/561386848)[#lean4
> &#96;countP_eq_length_filter&#96; grind attribute @
💬](https://leanprover.zulipchat.com/#narrow/channel/270676-lean4/topic/.60countP_eq_length_filter.60.20grind.20attribute/near/561386848)
this was problematic.
2025-12-08 03:11:25 +00:00
Gabe
b7ac6243a9 chore: improve bug report template instructions (#11537)
This PR makes it so that in the issue template a line about how to check
boxes is in comment form you can only see it when you are creating the
issue and it does not need to be displayed to everyone.
2025-12-07 19:52:52 +00:00
Tom Levy
2ca3bc2859 chore: fix spelling (#11531)
Hi, these are just some spelling corrections.

There is one I wasn't completely sure about in
src/Init/Data/List/Lemmas.lean:

> See also
> ...
> Also
> \* \`Init.Data.List.Monadic\` for **addiation** _(additional?)_ lemmas
about \`List.mapM\` and \`List.forM\`
2025-12-06 13:54:27 +00:00
Lean stage0 autoupdater
03a6e58cec chore: update stage0 2025-12-06 03:40:18 +00:00
Robert J. Simmons
72ddc479bf fix: modify @[suggest_for] to work with the Prelude (#11529)
This PR fixes a syntax-pattern-matching issue from #11367 that prevented
the addition of suggestions in Init prior to Lean.Parser being
introduced, which was a significant shortcoming. It preserves the
ability to have multiple suggestions for one annotation later in the
process.

Additionally, tweaks a (not-yet-user-visible) error message and modifies
the attribute declaration to store a wrongIdentifier ->
correctIdentifier mapping instead of a correctIdentifier ->
wrongIdentifier mapping.
2025-12-05 22:06:11 +00:00
Henrik Böving
c5e04176b8 perf: eliminate cases with all branches unreachable (#11525)
This PR makes the LCNF simplifier eliminate cases where all alts are
`.unreach` to just an `.unreach`.
  an `.unreach`

We considered dropping a cases in a situation like this but decided
against it because it might hinder reuse.
```
def test x : Bool :=
  cases x : Bool
  | Except.error a.1 =>
    ⊥
  | Except.ok a.2 =>
    let _x.3 := true;
    return _x.3
```
2025-12-05 20:30:20 +00:00
Joachim Breitner
4b77e226ab perf: when matching on values, avoid generating hyps when not needed (#11508)
This PR avoids generating hyps when not needed (i.e. if there is a
catch-all so no completeness checking needed) during matching on values.
    
This tweak was made possible by #11220.
2025-12-05 16:29:20 +00:00
Joachim Breitner
d4c832ecb0 perf: de-fuel some recursive definitions in Core (#11416)
This PR follows up on #7965 and avoids manual fuel constructions in some
recursive definitions.
2025-12-05 16:16:31 +00:00
Wojciech Różowski
9cbff55c56 feat: add difference on ExtDTreeMap/ExtTreeMap/TreeSet (#11408)
This PR adds a difference operation on
`ExtDTreeMap`/`ExtTreeMap`/`TreeSet` and proves several lemmas about it.

Stacked on top of #11407

---------

Co-authored-by: Markus Himmel <markus@himmel-villmar.de>
2025-12-05 10:06:12 +00:00
Sebastian Ullrich
e0650a0336 feat: shake: make Mathlib-ready (#11496)
This PR implements new flags and annotations for `shake` for use in
Mathlib:

> Options:
>   --keep-implied
> Preserves existing imports that are implied by other imports and thus
not technically needed
>     anymore
> 
>   --keep-prefix
> If an import `X` would be replaced in favor of a more specific import
`X.Y...` it implies,
> preserves the original import instead. More generally, prefers
inserting `import X` even if it
> was not part of the original imports as long as it was in the original
transitive import closure
>     of the current module.
> 
>   --keep-public
> Preserves all `public` imports to avoid breaking changes for external
downstream modules
> 
>   --add-public
> Adds new imports as `public` if they have been in the original public
closure of that module.
> In other words, public imports will not be removed from a module
unless they are unused even
> in the private scope, and those that are removed will be re-added as
`public` in downstream
> modules even if only needed in the private scope there. Unlike
`--keep-public`, this may
> introduce breaking changes but will still limit the number of inserted
imports.
> 
> Annotations:
> The following annotations can be added to Lean files in order to
configure the behavior of
> `shake`. Only the substring `shake: ` directly followed by a directive
is checked for, so multiple
> directives can be mixed in one line such as `-- shake:
keep-downstream, shake: keep-all`, and they
> can be surrounded by arbitrary comments such as `-- shake: keep
(metaprogram output dependency)`.
> 
>   * `module -- shake: keep-downstream`:
> Preserves this module in all (current) downstream modules, adding new
imports of it if needed.
> 
>   * `module -- shake: keep-all`:
> Preserves all existing imports in this module as is. New imports now
needed because of upstream
>     changes may still be added.
> 
>   * `import X -- shake: keep`:
> Preserves this specific import in the current module. The most common
use case is to preserve a
> public import that will be needed in downstream modules to make sense
of the output of a
> metaprogram defined in this module. For example, if a tactic is
defined that may synthesize a
> reference to a theorem when run, there is no way for `shake` to detect
this by itself and the
> module of that theorem should be publicly imported and annotated with
`keep` in the tactic's
>     module.
>     ```
>     public import X  -- shake: keep (metaprogram output dependency)
> 
>     ...
> 
>     elab \"my_tactic\" : tactic => do
> ... mkConst ``f -- `f`, defined in `X`, may appear in the output of
this tactic
>     ```
2025-12-05 09:37:58 +00:00
Sebastian Ullrich
76f32e2273 perf: avoid double-open per .olean file (#11507)
This PR optimizes the filesystem accesses during importing for a ~3% win
on Linux, potentially more on other platforms.
2025-12-05 09:37:38 +00:00
Paul Reichert
f2415b7a9a fix: find? -> findRev? (#11514)
This PR fixes a small oversight in a docstring.
2025-12-05 07:36:32 +00:00
Leonardo de Moura
455fd0b448 chore: use not_value at Nat.pow_pos (#11523)
and remove `TODO` from `grind_lint_bitvec.lean`
2025-12-05 06:25:17 +00:00
Leonardo de Moura
b3753ba6db feat: grind propagators for Nat operators (#11522)
This PR implements `grind` propagators for `Nat` operators that have a
simproc associated with them, but do not have any theory solver support.
Examples:

```lean
example (a b : Nat) : a = 3 → b = 6 → a &&& b = 2 := by grind
example (a b : Nat) : a = 3 → b = 6 → a ||| b = 7 := by grind
example (a b : Nat) : a = 3 → b = 6 → a ^^^ b = 5 := by grind
example (a b : Nat) : a = 3 → b = 6 → a <<< b = 192 := by grind
example (a b : Nat) : a = 1135 → b = 6 → a >>> b = 17 := by grind
```

Closes #11498
2025-12-05 05:41:34 +00:00
Lean stage0 autoupdater
8afaa1bc11 chore: update stage0 2025-12-05 05:03:48 +00:00
Leonardo de Moura
71991296e0 feat: add not_value constraint to grind_pattern (#11520)
This PR implements the constraint `not_value x` in the `grind_pattern`
command. It is the negation of the constraint `is_value`.
2025-12-05 04:19:34 +00:00
Leonardo de Moura
b0a12cb49f feat: mark Nat power and divisibility theorems for grind (#11519)
This PR marks `Nat` power and divisibility theorems for `grind`. We use
the new `grind_pattern` constraints to control theorem instantiation.
Examples:

```lean
example {x m n : Nat} (h : x = 4 ^ (m + 1) * n) : x % 4 = 0 := by
  grind

example (a m n o p : Nat) : a ∣ n → a ∣ m * n * o * p := by
  grind

example {a b x m n : Nat}
    : n > 0 → x = b * 4^m * a → a = 9^n → m > 0 → x % 6 = 0 := by
  grind

example {a n : Nat}
    : m > 4 → a = 2^(m^n) → a % 2 = 0 := by
  grind
```

Closes #11515

Remark: We are adding support for installing extra theorems to `lia`
(aka `cutsat`). The example at #11515 can already be solved by `grind`
with this PR, but we still need to add the new theorems to the set for
`lia`.

cc @kim-em
2025-12-05 03:49:01 +00:00
Robert J. Simmons
ab606ba754 feat: hint when an autobound variable's type fails to be a function (#11518)
This PR provides an additional hint when the type of an autobound
implicit is required to have function type or equality type — this
fails, and the existing error message does not address the fact that the
source of the error is an unknown identifier that was automatically
bound.

## Example

```
import Lean
example : MetaM String := pure ""
```

Current error message:
```
Function expected at
  MetaM
but this term has type
  ?m

Note: Expected a function because this term is being applied to the argument
  String
```

Additional error message provided by this PR:
```
Hint: The identifier `MetaM` is unknown, and Lean's `autoImplicit` option 
causes an unknown identifier to be treated as an implicitly bound variable 
with an unknown type. However, the unknown type cannot be a function, and a 
function is what Lean expects here. This is often the result of a typo or a 
missing `import` or `open` statement.
```
2025-12-05 03:07:16 +00:00
Henrik Böving
6ca57a74ed feat: constant folding for Nat.mul (#11517)
This PR implements constant folding for Nat.mul
2025-12-04 23:38:56 +00:00
Kim Morrison
0ba40b798b feat: exact? uses star-indexed lemmas as fallback (#11494)
This PR re-enables star-indexed lemmas as a fallback for `exact?` and
`apply?`.

Star-indexed lemmas (those with overly-general discrimination tree keys
like `[*]`)
were previously dropped entirely for performance reasons. This caused
useful lemmas
like `Empty.elim`, `And.left`, `not_not.mp`, `Sum.elim`, and
`Function.mtr` to be
unfindable by library search.

The implementation adds a two-pass search strategy:
1. First, search using concrete discrimination keys (the current
behavior)
2. If no results are found, fall back to trying star-indexed lemmas

The star-indexed lemmas are extracted during tree initialization and
cached in an
environment extension, avoiding repeated computation.

Users can disable the fallback with `-star`:
```lean
example {α : Sort u} (h : Empty) : α := by apply? -star  -- error: no lemmas found
example {α : Sort u} (h : Empty) : α := by apply?        -- finds Empty.elim
```

🤖 Prepared with Claude Code

---------

Co-authored-by: Claude <noreply@anthropic.com>
2025-12-04 22:50:52 +00:00
Wojciech Różowski
42ded564bd feat: add difference on ExtDHashMap/ExtHashMap/ExtHashSet (#11399)
This PR adds support for the difference operation for
`ExtDHashMap`/`ExtHashMap`/`ExtHashSet` and proves several lemmas about
it.

---------

Co-authored-by: Markus Himmel <markus@himmel-villmar.de>
2025-12-04 16:06:31 +00:00
Joachim Breitner
f0738c2cd1 perf: in CaseValues, subst only once (#11510)
This PR avoids running substCore twice in caseValues.
2025-12-04 15:43:46 +00:00
Lean stage0 autoupdater
5f561bfee2 chore: update stage0 2025-12-04 15:52:42 +00:00
Joachim Breitner
af6d2077a0 refactor: use match compilation to generate splitter (#11220)
This PR changes how match splitters are generated: Rather than rewriting
the match statement, the match compilation pipeline is used again.


The benefits are:

* Re-doing the match compilation means we can do more intelligent book
keeping, e.g. prove overlap assumptions only once and re-use the proof,
or prune the context of the MVar to speed up `contradiction`. This may
have allowed a different solution than #11200.
 
* It would unblock #11105, as the existing splitter implementation would
have trouble dealing with the matchers produced that way.
 
* It provides the necessary machinery also for source-exposed “none of
the above” bindings, a feature that we probably want at some point (and
we mostly need to find good syntax for, see #3136, although maybe I
should open a dedicated RFC).

* It allows us to skip costly things during matcher creation that would
only be useful for the splitter, and thus allows performance
improvements like #11508.
 
 * We can drop the existing implementation.
 
It’s not entirely free:

* We have to run `simpH` twice, once for the match equations and once
for the splitter.
2025-12-04 15:03:13 +00:00
Paul Reichert
31d629cb67 feat: more Nat range lemmas (#11321)
This PR provides specialized lemmas about `Nat` ranges, including `simp`
annotations and induction principles for proving properties for all
ranges.
2025-12-04 14:14:45 +00:00
Joachim Breitner
d60ef53d54 refactor: make CCPO class Prop-valued (#11425)
This PR changes `Lean.Order.CCPO` and `.CompleteLattice` to carry a
Prop. This avoids the `CCPO IO` instance from being `noncomputable`.
2025-12-04 13:33:36 +00:00
Robert J. Simmons
dd57725244 feat: @[suggest_for] attribute to inform replacements (#11367)
This PR introduces a new annotation that allows definitions to describe
plausible-but-wrong name variants for the purpose of improving error
messages.

This PR just adds the notation and extra functionality; a stage0 update
will allow standard Lean functions to have suggestion annotations.
(Hence the changelog-no tag: this should go in the changelog when some
preliminary annotations are actually added.)

## Example

```lean4
inductive MyBool where | tt | ff

attribute [suggest_for MyBool.true] MyBool.tt
attribute [suggest_for MyBool.false] MyBool.ff

@[suggest_for MyBool.not]
def MyBool.swap : MyBool → MyBool
  | tt => ff
  | ff => tt

/--
error: Unknown constant `MyBool.true`

Hint: Perhaps you meant `MyBool.tt` in place of `MyBool.true`:
  M̵y̵B̵o̵o̵l̵.̵t̵r̵u̵e̵M̲y̲B̲o̲o̲l̲.̲t̲t̲
-/
#guard_msgs in
example := MyBool.true

/--
error: Invalid field `not`: The environment does not contain `MyBool.not`, so it is not possible to project the field `not` from an expression
  MyBool.tt
of type `MyBool`

Hint: Perhaps you meant one of these in place of `MyBool.not`:
  [apply] `MyBool.swap`: MyBool.tt.swap
-/
#guard_msgs in
example := MyBool.tt.not
```
2025-12-04 13:20:37 +00:00
Markus Himmel
e548fa414c fix: Char -> Bool as default instance for string search (#11503)
This PR marks `Char -> Bool` patterns as default instances for string
search. This means that things like `" ".find (·.isWhitespace)` can now
be elaborated without error.

Previously, it was necessary to write `" ".find Char.isWhitespace`.

Thank you to David Christiansen for the idea of using a default
instance.
2025-12-04 09:25:16 +00:00
Joachim Breitner
b94cf2c9be test: add big match on nat lit benchmarks (#11502)
This PR adds two benchmarks for elaborating match statements of many
`Nat` literals, one without and one with splitter generation.
2025-12-04 08:21:56 +00:00
Robert J. Simmons
dd28f00588 feat: hint alternatives when field-projecting from an unknown type (#11482)
This PR gives suggestions based on the currently-available constants
when projecting from an unknown type.

## Example: single suggestion in namespace

This was the originally motivating example, as the string refactor led
to a number of anonymous-lambda-expressions with `Char` functions that
were no longer recognized as such.

```lean4
example := (·.isWhitespace)
```
Before:
```
Invalid field notation: Type of
  x✝
is not known; cannot resolve field `isWhitespace`
```
The message is unchanged, but this PR adds a hint:
```
Hint: Consider replacing the field projection `.isWhitespace` with a call to the function `Char.isWhitespace`.
```

## Example: single suggestion in namespace

```lean4
example := fun n => n.succ
```
Before:
```
Invalid field notation: Type of
  n
is not known; cannot resolve field `succ`
```
The message is unchanged, but this PR adds a hint:
```
Hint: Consider replacing the field projection with a call to one of the following:
  • `Fin.succ`
  • `Nat.succ`
  • `Std.PRange.succ`
```
2025-12-03 20:48:34 +00:00
Joachim Breitner
54fbe931ab refactor: make MatchEqs a leaf module (#11493)
This PR makes `Match.MatchEqs` a leaf module, to be less restricted in
which features we can use there.
2025-12-03 09:15:36 +00:00
Joachim Breitner
fb261921b9 refactor: use withImplicitBinderInfos and mkArrowN in more places (#11492)
This PR uses the the helper functions withImplicitBinderInfos and
mkArrowN in more places.
2025-12-03 08:42:16 +00:00
Leonardo de Moura
0173444d24 feat: heterogeneous contructor injectivity in grind (#11491)
This PR implements heterogeneous contructor injectivity in `grind`.

Example:
```lean
opaque double : Nat → Nat

inductive Parity : Nat -> Type
  | even (n) : Parity (double n)
  | odd  (n) : Parity (Nat.succ (double n))

opaque q : Nat → Nat → Prop
axiom qax : q a b → double a = double b
attribute [grind →] qax

example
  (motive : (x : Nat) → Parity x → Sort u_1)
  (h_2 : (j : Nat) → motive (double j) (Parity.even j))
  (j n : Nat)
  (heq_1 : q j n) -- Implies that `double j = double n`
  (heq_2 : Parity.even n ≍ Parity.even j):
  h_2 n ≍ h_2 j := by
grind
```

Closes #11449
2025-12-03 04:01:19 +00:00
Leonardo de Moura
1377da0c76 feat: heterogeneous constructor injectivity theorems (#11487)
This PR adds a heterogeneous version of the constructor injectivity
theorems. These theorems are useful for indexed families, and will be
used in `grind`.
2025-12-03 01:42:46 +00:00
Mac Malone
5db4f96699 feat: lake: resolve module clashes on import (#11270)
This PR adds a module resolution procedure to Lake to disambiguate
modules that are defined in multiple packages.

On an `import`, Lake will now check if multiple packages within the
workspace define the module. If so, it will verify that modules have
sufficiently similar definitions (i.e., artifacts with the same content
hashes). If not, Lake will report an error.

This verification is currently only done for direct imports. Transitive
imports are not checked for consistency. An overhaul of transitive
imports will come later.
2025-12-03 00:46:20 +00:00
Leonardo de Moura
8bc3eb1265 fix: grind pattern validation (#11484)
This PR fixes a bug in the `grind` pattern validation. The bug affected
type classes that were propositions.

Closes #11477
2025-12-02 19:57:58 +00:00
Lean stage0 autoupdater
cac2c47376 chore: update stage0 2025-12-02 20:03:50 +00:00
David Thrane Christiansen
3fe368e8e7 feat: allow Verso docstrings to suppose the existence of instances (#11476)
This PR adds a `` {givenInstance}`C` `` documentation role that adds an
instance of `C` to the document's local assumptions.
2025-12-02 19:16:35 +00:00
Leonardo de Moura
f8866dcc59 fix: grind? dropping options (#11481)
This PR fixes a bug in `grind?`. The suggestion using the `grind`
interactive mode was dropping the configuration options provided by the
user. In the following account, the third suggestion was dropping the
`-reducible` option.

```lean
/--
info: Try these:
  [apply] grind -reducible only [Equiv.congr_fun, #5103]
  [apply] grind -reducible only [Equiv.congr_fun]
  [apply] grind -reducible => cases #5103 <;> instantiate only [Equiv.congr_fun]
-/
example :
    (Equiv.sigmaCongrRight e).trans (Equiv.sigmaEquivProd α₁ β₂)
    = (Equiv.sigmaEquivProd α₁ β₁).trans (prodCongrRight e) := by
  grind? -reducible [Equiv.congr_fun]
```
2025-12-02 19:00:29 +00:00
Leonardo de Moura
9263a6cc9c feat: add Grind.Config.reducible (#11480)
This PR adds the `grind` option `reducible` (default: `true`). When
enabled, definitional equality tests expand only declarations marked as
`@[reducible]`.
Use `grind -reducible` to allow expansion of non-reducible declarations
during definitional equality tests.
This option affects only definitional equality; the canonicalizer and
theorem pattern internalization always unfold reducible declarations
regardless of this setting.
2025-12-02 18:10:55 +00:00
Robert J. Simmons
edcef51434 feat: improve error messages for invalid field access (#11456)
This PR refines several error error messages, mostly involving invalid
use of field notation, generalized field notation, and numeric
projection. Provides a new error explanation for field notation.

## Error message changes

In general:
- Uses a slightly different convention for expression-type pairs, where
the expression is always given `indentExpr` and the type is given
`inlineExpr` treatment. This is something of a workaround for the fact
that the `Format` type is awkward for embedding possibly-linebreaking
expressions in not-linebreaking text, which may be a separate issue
worth addressing.
- Tries to give slightly more "why" reasoning — the environment does not
contain `String.parse`, and _therefore you can't project `.parse` from a
`String`_.

Some specific examples:

### No such projection function
```lean4
#check "".parse
```
before:
```
error: Invalid field `parse`: The environment does not contain `String.parse`
  ""
has type
  String
```
after:
```
error: Invalid field `parse`: The environment does not contain `String.parse`, so it is not possible to project the field `parse` from an expression
  ""
of type `String`
```

### Type does not have the correct form
```lean4
example (x : α) := (foo x).foo
```
before:
```
error: Invalid field notation: Type is not of the form `C ...` where C is a constant
  foo x
has type
  α
```
after:
```
error: Invalid field notation: Field projection operates on types of the form `C ...` where C is a constant. The expression
  foo x
has type `α` which does not have the necessary form.
```

## Refactoring
Includes some refactoring changes as well:
- factors out multiple uses of number (1, 2, 3, 212, 222) to ordinal
("first", "second", "third", "212th", "222nd") conversion into
Lean.Elab.ErrorUtils
- significant refactoring of `resolveLValAux` in `Lean.Elab.App` — in
place of five helper functions, a special-case function case analysis,
and a case analysis on the projection type and structure, there's now a
single case analysis on the projection type and structure. This allows
several error messages to be more explicit (there were a number of cases
where index projection was being described as field projection in an
error messages) and gave the opportunity to slightly improve positining
for several errors: field *notation* errors should appear on `foo.bar`,
but field *projection* errors should appear only on the `bar` part of
`foo.bar`.
2025-12-02 17:46:12 +00:00
Mac Malone
79838834c1 refactor: port shell option processing to Lean (v2) (#11434)
This PR moves the processing of options passed to the CLI from
`shell.cpp` to `Shell.lean`.

As with previous ports, this attempts to mirror as much of the original
behavior as possible, Benefits to be gained from the ported code can
come in later PRs. There should be no significant behavioral changes
from this port. Nonetheless, error reporting has changed some, hopefully
for the better. For instance, errors for improper argument
configurations has been made more consistent (e.g., Lean will now error
if numeric arguments fall outside the expected range for an option).

(Redo of #11345 to fix Windows issue.)
2025-12-02 17:41:51 +00:00
Joachim Breitner
edf804c70f feat: heterogeneous noConfusion (#11474)
This PR generalizes the `noConfusion` constructions to heterogeneous
equalities (assuming propositional equalities between the indices). This
lays ground work for better support for applying injection to
heterogeneous equalities in grind.

The `Meta.mkNoConfusion` app builder shields most of the code from these
changes.

Since the per-constructor noConfusion principles are now more
expressive, `Meta.mkNoConfusion` no longer uses the general one.

In `Init.Prelude` some proofs are more pedestrian because `injection`
now needs a bit more machinery.

This is a breaking change for whoever uses the `noConfusion` principle
manually and explicitly for a type with indices.

Fixes #11450.
2025-12-02 15:19:47 +00:00
Wojciech Różowski
8b7cbe7d2e feat: add mem_of_get_eq and of_getElem_eq (#11452)
This PR adds lemmas stating that if a get operation returns a value,
then the queried key must be contained in the collection. These lemmas
are added for HashMap and TreeMap-based collections, with a similar
lemma also added for `Init.getElem`.
2025-12-02 15:00:00 +00:00
Sebastian Ullrich
a0c503cf2b fix: cadical dynamic dependencies (#11475)
#11423 led to cadical being built with the wrong sysroot flags, which
resulted in it linking against the more recent system glibc
2025-12-02 13:54:26 +00:00
David Thrane Christiansen
0e83422fb6 doc: add missing docstrings for Rxy.Sliceable (#11472)
This PR adds missing docstrings for the `mkSlice` methods.
2025-12-02 08:42:36 +00:00
Henrik Böving
3dd99fc29c perf: eta contract instead of lambda lifting if possible (#11451)
This PR adapts the lambda lifter in LCNF to eta contract instead of
lambda lift if possible. This prevents the creation of a few hundred
unnecessary lambdas across the code base.
2025-12-02 08:39:24 +00:00
Wojciech Różowski
0646bc5979 refactor: move Inhabited instances in constant DTreeMap queries (#11448)
This PR moves the `Inhabited` instances in constant `DTreeMap` (and
related) queries, such as `Const.get!`, where the `Inhabited` instance
can be provided before proving a key.
2025-12-02 08:30:33 +00:00
Kim Morrison
2eca5ca6e4 fix: getEqnsFor? should not panic on matchers (#11463)
This PR fixes a panic in `getEqnsFor?` when called on matchers generated
from match expressions in theorem types.

When a theorem's type contains a match expression (e.g., `theorem bar :
(match ... with ...) = 0`), the compiler generates a matcher like
`bar.match_1`. Calling `getEqnsFor?` on this matcher would panic with:

```
PANIC: duplicate normalized declaration name bar.match_1.eq_1 vs. _private...bar.match_1.eq_1
```

This also affected the `try?` tactic, which internally uses
`getEqnsFor?`.

We make `shouldGenerateEqnThms` return `false` for matchers, since their
equations are already generated separately by
`Lean.Meta.Match.MatchEqs`. This prevents the equation generation
machinery from attempting to create duplicate equation theorems.

Closes #11461
Closes #10390


🤖 Prepared with Claude Code

Co-authored-by: Claude <noreply@anthropic.com>
2025-12-02 07:53:50 +00:00
Leonardo de Moura
1fc4768b68 fix: incorrect reducibility setting in grind interactive mode (#11471)
This PR fixes an incorrect reducibility setting when using `grind`
interactive mode.

Signed-off-by: Leonardo de Moura <leomoura@amazon.com>
2025-12-02 07:04:04 +00:00
Alok Singh
1e1ed16a05 doc: correct typos in documentation and comments (#11465)
This PR fixes various typos across the codebase in documentation and
comments.

- `infered` → `inferred` (ParserCompiler.lean)
- `declartation` → `declaration` (Cleanup.lean)
- `certian` → `certain` (CasesInfo.lean)
- `wil` → `will` (Cache.lean)
- `the the` → `the` (multiple files - PrefixTree.lean, Sum/Basic.lean,
List/Nat/Perm.lean, Time.lean, Bounded.lean, Lake files)
- `to to` → `to` (MutualInductive.lean, simp_bubblesort_256.lean)
- Grammar improvements in Bounded.lean and Time.lean

All changes are to comments and documentation only - no functional
changes.

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

Co-authored-by: Claude <noreply@anthropic.com>
2025-12-02 06:38:05 +00:00
Kim Morrison
226a90f1eb feat: exact? +grind and exact? +try? discharger options (#11469)
This PR adds `+grind` and `+try?` options to `exact?` and `apply?`
tactics.

## `+grind` option

When `+grind` is enabled, `grind` is used as a fallback discharger for
subgoals that `solve_by_elim` cannot close. The proof is wrapped in
`Grind.Marker` so suggestions display `(by grind)` instead of the
complex grind proof term.

Example:
```lean
axiom foo (x : Nat) : x < 37 → 5 < x → x.log2 < 6

/--
info: Try this:
  [apply] exact foo x (by grind) (by grind)
-/
#guard_msgs in
example (x : Nat) (h₁ : x < 30) (h₂ : 8 < x) : x.log2 < 6 := by
  exact? +grind
```

## `+try?` option

When `+try?` is enabled, `try?` is used as a fallback discharger for
subgoals. This is useful when subgoals require induction or other
strategies that `try?` can find but `solve_by_elim` and `grind` cannot.

Example:
```lean
inductive MyList (α : Type _) where
  | nil : MyList α
  | cons : α → MyList α → MyList α

axiom MyListProp : MyList α → Prop
@[grind .] axiom mylist_nil : MyListProp (MyList.nil : MyList α)
@[grind .] axiom mylist_cons : ∀ (x : α) (xs : MyList α), MyListProp xs → MyListProp (MyList.cons x xs)

axiom qux (xs : MyList α) (p : MyListProp xs) : MyListProp2 xs

/--
info: Try this:
  [apply] exact qux xs (by try?)
-/
example (xs : MyList α) : MyListProp2 xs := by
  exact? +try?
```

🤖 Prepared with Claude Code

---------

Co-authored-by: Claude <noreply@anthropic.com>
2025-12-02 06:31:56 +00:00
Kim Morrison
519ccf5d9d feat: add solve_by_elim +suggestions (#11468)
This PR adds `+suggestions` support to `solve_by_elim`, following the
pattern established by `grind +suggestions` and `simp_all +suggestions`.

Gracefully handles invalid/nonexistent suggestions by filtering them out

🤖 Prepared with Claude Code

Co-authored-by: Claude <noreply@anthropic.com>
2025-12-02 02:11:32 +00:00
Kim Morrison
1c1c534a03 feat: add solve_by_elim to try? tactic pipeline (#11462)
This PR adds `solve_by_elim` as a fallback in the `try?` tactic's simple
tactics. When `rfl` and `assumption` both fail but `solve_by_elim`
succeeds (e.g., for goals requiring hypothesis chaining or
backtracking), `try?` will now suggest `solve_by_elim`.

The structure is `first | (attempt_all | rfl | assumption) |
solve_by_elim`, so `solve_by_elim` only runs when the faster tactics
fail.

This is a prerequisite for removing the "first pass" `solve_by_elim`
from `apply?`. Currently `apply?` calls `solve_by_elim` twice: once
before library search, and once after each lemma application. The first
pass can be removed once `try?` includes `solve_by_elim`.

🤖 Prepared with Claude Code

---------

Co-authored-by: Claude <noreply@anthropic.com>
2025-12-02 02:09:59 +00:00
Kim Morrison
8b103f33cf feat: remove solve_by_elim first pass from exact?/apply? (#11466)
This PR removes the "first pass" behavior where `exact?` and `apply?`
would try `solve_by_elim` on the original goal before doing library
search. This simplifies the `librarySearch` API and focuses these
tactics on their primary purpose: finding library lemmas.

Users who want to find proofs using local hypotheses should use `try?`
instead, which now includes `solve_by_elim` in its pipeline (see
https://github.com/leanprover/lean4/pull/11462).

Changes:
- Removed first pass from `librarySearch`
- Simplified `tactic` parameter from `Bool → List MVarId → MetaM (List
MVarId)` to `List MVarId → MetaM (List MVarId)`
- Updated test expectations

🤖 Prepared with Claude Code

---------

Co-authored-by: Claude <noreply@anthropic.com>
2025-12-02 02:05:27 +00:00
Kim Morrison
a0c8404ab8 fix: improve "no library suggestions engine registered" error message (#11464)
This PR improves the error message when no library suggestions engine is
registered to recommend importing `Lean.LibrarySuggestions.Default` for
the built-in engine.

**Before:**
```
No library suggestions engine registered. (Note that Lean does not provide a default library suggestions engine, these must be provided by a downstream library, and configured using `set_library_suggestions`.)
```

**After:**
```
No library suggestions engine registered. (Add `import Lean.LibrarySuggestions.Default` to use Lean's built-in engine, or use `set_library_suggestions` to configure a custom one.)
```

🤖 Prepared with Claude Code

Co-authored-by: Claude <noreply@anthropic.com>
2025-12-02 00:55:46 +00:00
Lean stage0 autoupdater
c0d5b9b52c chore: update stage0 2025-12-01 21:07:38 +00:00
Sebastian Ullrich
96461a4b03 feat: recordIndirectModUse (#11437)
This PR adds recording functionality such that `shake` can more
precisely track whether an import should be preserved solely for its
`attribute` commands.
2025-12-01 20:02:38 +00:00
Henrik Böving
310abce62b fix: boxing may have to correct let binder types (#11426)
This PR closes #11356.
2025-12-01 17:22:32 +00:00
Wojciech Różowski
2da5b528b7 feat: add difference on DTreeMap/TreeMap/TreeSet (#11407)
This PR adds the difference operation on `DTreeMap`/`TreeMap`/`TreeSet`
and proves several lemmas about it.

---------

Co-authored-by: Markus Himmel <markus@himmel-villmar.de>
2025-12-01 16:43:34 +00:00
Garmelon
ca23ed0c17 chore: fix "tests/compiler//sum binary sizes" benchmark (#11444)
The bench script expected no output on stdout from `compile.sh`, which
was not always the case. Now, it separates the compilation and size
measurement steps.
2025-12-01 16:20:34 +00:00
Henrik Böving
5e165e358c fix: better types when creating boxed decls (#11445)
This PR slightly improves the types involved in creating boxed
declarations. Previously the type of
the vdecl used for the return was always `tobj` when returning a boxed
scalar. This is not the most
precise annotation we can give.
2025-12-01 15:11:15 +00:00
Robert J. Simmons
429734c02f feat: suggest deriving an instance when the instance might be derivable (#11346)
This PR modifies the error message for type synthesis failure for the
case where the type class in question is potentially derivable using a
`deriving` command. Also changes the error explanation for type class
instance synthesis failure with an illustration of this pattern.

## Example

```lean4
inductive MyColor where
  | chartreuse | sienna | thistle
def forceColor (oc : Option MyColor) :=
  oc.get!
```

Before this PR, this gives the potentially confusing impression that
Lean may have decided that `MyColor` is _not_ inhabited — people used to
Rust may be especially inclined towards this confusion.
```
failed to synthesize instance of type class
  Inhabited MyColor

Hint: Type class instance resolution failures can be inspected with the `set_option trace.Meta.synthInstance true` command.
```

After this PR, a targeted hint suggests precisely the command that will
fix the issue:
```
error: failed to synthesize instance of type class
  Inhabited MyColor

Hint: Adding the command `deriving instance Inhabited for MyColor` may allow Lean to derive the missing instance.
```
2025-12-01 14:28:15 +00:00
Lean stage0 autoupdater
35a36ae343 chore: update stage0 2025-12-01 13:35:51 +00:00
Joachim Breitner
f9dc77673b feat: dedicated fix operator for well-founded recursion on Nat (#7965)
This PR lets recursive functions defined by well-founded recursion use a
different `fix` function when the termination measure is of type `Nat`.
This fix-point operator use structural recursion on “fuel”, initialized
by the given measure, and is thus reasonable to reduce, e.g. in `by
decide` proofs.

Extra provisions are in place that the fixpoint operator only starts
reducing when the fuel is fully known, to prevent “accidential” defeqs
when the remaining fuel for the recursive calls match the initial fuel
for that recursive argument.

To opt-out, the idiom `termination_by (n,0)` can be used.

We still use `@[irreducible]` as the default for such recursive
definitions, to avoid unexpected `defeq` lemmas. Making these functions
`@[semireducible]` by default showed performance regressions in lean.
When the measure is of type `Nat`, the system will accept an explicit
`@[semireducible]` without the usual warning.

Fixes #5234. Fixes: #11181.
2025-12-01 12:51:55 +00:00
Markus Himmel
1ae680c5e2 chore: minor String API improvements (#11439)
This PR performs minor maintenance on the String API

- Rename `String.Pos.toCopy` to `String.Pos.copy` to adhere to the
naming convention
- Rename `String.Pos.extract` to `String.extract` to get sane dot
notation again
- Add `String.Slice.Pos.extract`
2025-12-01 11:44:14 +00:00
Lean stage0 autoupdater
057b70b443 chore: update stage0 2025-12-01 11:47:18 +00:00
David Thrane Christiansen
677561522d fix: add missing import (#11441)
This PR fixes an issue that prevented the manual from building due to
the missing explanation of an error.
2025-12-01 11:02:03 +00:00
Marc Huisinga
af5b47295f feat: reduce server memory consumption (#11162)
This PR reduces the memory consumption of the language server (the
watchdog process in particular). In Mathlib, it reduces memory
consumption by about 1GB.

It also fixes two bugs in the call hierarchy:
- When an open file had import errors (e.g. from a transitive build
failure), the call hierarchy would not display any usages in that file.
Now we use the reference information from the .ilean instead.
- When a command would not set a parent declaration (e.g. `#check`), the
result was filtered from the call hierarchy. Now we display it as
`[anonymous]` instead.
2025-12-01 10:53:23 +00:00
Sebastian Ullrich
3282ac6f96 chore: CI: exclude additional slow test (#11440) 2025-12-01 10:53:16 +00:00
Joachim Breitner
5bd331e85d perf: kernel-optimize Mon.mul (#11422)
This PR uses a kernel-reduction optimized variant of Mon.mul in grind.
2025-11-30 23:59:59 +00:00
Jon Eugster
856825a4d2 fix: typo in docstring of #guard_msgs (#11432)
This PR fixes a typo in the docstring of `#guard_mgs`.

Closes #11431
2025-11-30 14:44:35 +00:00
Leonardo de Moura
16508196e0 doc: add docstring for grind_pattern command (#11429)
This PR documents the `grind_pattern` command for manually selecting
theorem instantiation patterns, including multi-patterns and the
constraint system (`=/=`, `=?=`, `size`, `depth`, `is_ground`,
`is_value`, `is_strict_value`, `gen`, `max_insts`, `guard`, `check`).
2025-11-30 01:01:48 +00:00
Sebastian Ullrich
4eba5ea96d fix: shake: only record non-builtin simprocs (#11344) 2025-11-29 15:58:29 +00:00
2466 changed files with 38546 additions and 9941 deletions

View File

@@ -39,6 +39,7 @@ These comments explain the scripts' behavior, which repositories get special han
## Important Notes
- **NEVER merge PRs autonomously** - always wait for the user to merge PRs themselves
- The `release_steps.py` script is idempotent - it's safe to rerun
- The `release_checklist.py` script is idempotent - it's safe to rerun
- Some repositories depend on others (e.g., mathlib4 depends on batteries, aesop, etc.)

1
.gitattributes vendored
View File

@@ -4,6 +4,7 @@ RELEASES.md merge=union
stage0/** binary linguist-generated
# The following file is often manually edited, so do show it in diffs
stage0/src/stdlib_flags.h -binary -linguist-generated
doc/std/grove/GroveStdlib/Generated/** linguist-generated
# These files should not have line endings translated on Windows, because
# it throws off parser tests. Later lines override earlier ones, so the
# runner code is still treated as ordinary text.

View File

@@ -9,7 +9,7 @@ assignees: ''
### Prerequisites
Please put an X between the brackets as you perform the following steps:
<!-- Please put an X between the brackets as you perform the following steps: -->
* [ ] Check that your issue is not already filed:
https://github.com/leanprover/lean4/issues

View File

@@ -220,7 +220,7 @@ jobs:
path: pack/*
- name: Lean stats
run: |
build/$TARGET_STAGE/bin/lean --stats src/Lean.lean -Dexperimental.module=true
build/$TARGET_STAGE/bin/lean --stats src/Lean.lean
if: ${{ !matrix.cross }}
- name: Test
id: test

View File

@@ -0,0 +1,57 @@
name: Check stdlib_flags.h modifications
on:
pull_request:
types: [opened, synchronize, reopened, labeled, unlabeled]
jobs:
check-stdlib-flags:
runs-on: ubuntu-latest
steps:
- name: Check if stdlib_flags.h was modified
uses: actions/github-script@v8
with:
script: |
// Get the list of files changed in this PR
const files = await github.paginate(
github.rest.pulls.listFiles,
{
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: context.payload.pull_request.number,
}
);
// Check if stdlib_flags.h was modified
const stdlibFlagsModified = files.some(file =>
file.filename === 'src/stdlib_flags.h'
);
if (stdlibFlagsModified) {
console.log('src/stdlib_flags.h was modified in this PR');
// Check if the unlock label is present
const { data: pr } = await github.rest.pulls.get({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: context.issue.number,
});
const hasUnlockLabel = pr.labels.some(label =>
label.name === 'unlock-upstream-stdlib-flags'
);
if (!hasUnlockLabel) {
core.setFailed(
'src/stdlib_flags.h was modified. This is likely a mistake. If you would like to change ' +
'bootstrapping settings or request a stage0 update, you should modify stage0/src/stdlib_flags.h. ' +
'If you really want to change src/stdlib_flags.h (which should be extremely rare), set the ' +
'unlock-upstream-stdlib-flags label.'
);
} else {
console.log('Found unlock-upstream-stdlib-flags');
}
} else {
console.log('src/stdlib_flags.h was not modified');
}

View File

@@ -52,7 +52,7 @@ jobs:
- name: Checkout
uses: actions/checkout@v5
# don't schedule nightlies on forks
if: github.event_name == 'schedule' && github.repository == 'leanprover/lean4' || inputs.action == 'release nightly'
if: github.event_name == 'schedule' && github.repository == 'leanprover/lean4' || inputs.action == 'release nightly' || (startsWith(github.ref, 'refs/tags/') && github.repository == 'leanprover/lean4')
- name: Set Nightly
if: github.event_name == 'schedule' && github.repository == 'leanprover/lean4' || inputs.action == 'release nightly'
id: set-nightly
@@ -115,7 +115,7 @@ jobs:
CMAKE_MAJOR=$(grep -E "^set\(LEAN_VERSION_MAJOR " src/CMakeLists.txt | grep -oE '[0-9]+')
CMAKE_MINOR=$(grep -E "^set\(LEAN_VERSION_MINOR " src/CMakeLists.txt | grep -oE '[0-9]+')
CMAKE_PATCH=$(grep -E "^set\(LEAN_VERSION_PATCH " src/CMakeLists.txt | grep -oE '[0-9]+')
CMAKE_IS_RELEASE=$(grep -E "^set\(LEAN_VERSION_IS_RELEASE " src/CMakeLists.txt | grep -oE '[0-9]+')
CMAKE_IS_RELEASE=$(grep -m 1 -E "^set\(LEAN_VERSION_IS_RELEASE " src/CMakeLists.txt | grep -oE '[0-9]+')
# Expected values from tag parsing
TAG_MAJOR="${{ steps.set-release.outputs.LEAN_VERSION_MAJOR }}"
@@ -267,11 +267,14 @@ jobs:
"test": true,
// turn off custom allocator & symbolic functions to make LSAN do its magic
"CMAKE_PRESET": "sanitize",
// `StackOverflow*` correctly triggers ubsan
// `reverse-ffi` fails to link in sanitizers
// `StackOverflow*` correctly triggers ubsan.
// `reverse-ffi` fails to link in sanitizers.
// `interactive` and `async_select_channel` fail nondeterministically, would need to
// be investigated.
"CTEST_OPTIONS": "-E 'StackOverflow|reverse-ffi|interactive|async_select_channel'"
// be investigated..
// 9366 is too close to timeout.
// `bv_` sometimes times out calling into cadical even though we should be using the
// standard compile flags for it.
"CTEST_OPTIONS": "-E 'StackOverflow|reverse-ffi|interactive|async_select_channel|9366|run/bv_'"
},
{
"name": "macOS",

View File

@@ -51,7 +51,7 @@ jobs:
- name: Fetch upstream invalidated facts
if: ${{ steps.should-run.outputs.should-run == 'true' && steps.workflow-info.outputs.pullRequestNumber != '' }}
id: fetch-upstream
uses: TwoFx/grove-action/fetch-upstream@v0.4
uses: TwoFx/grove-action/fetch-upstream@v0.5
with:
artifact-name: grove-invalidated-facts
base-ref: master
@@ -65,6 +65,7 @@ jobs:
workflow: ci.yml
path: artifacts
name: "build-Linux release"
allow_forks: true
name_is_regexp: true
- name: Unpack toolchain
@@ -95,7 +96,7 @@ jobs:
- name: Build
if: ${{ steps.should-run.outputs.should-run == 'true' }}
id: build
uses: TwoFx/grove-action/build@v0.4
uses: TwoFx/grove-action/build@v0.5
with:
project-path: doc/std/grove
script-name: grove-stdlib

View File

@@ -127,7 +127,7 @@ jobs:
description: "${{ github.repository_owner }}/lean4-pr-releases:pr-release-${{ steps.workflow-info.outputs.pullRequestNumber }}-${{ env.SHORT_SHA }}",
});
- name: Add label
- name: Add toolchain-available label
if: ${{ steps.workflow-info.outputs.pullRequestNumber != '' }}
uses: actions/github-script@v8
with:
@@ -166,22 +166,14 @@ jobs:
if [ "$NIGHTLY_SHA" = "$MERGE_BASE_SHA" ]; then
echo "The merge base of this PR coincides with the nightly release"
BATTERIES_REMOTE_TAGS="$(git ls-remote https://github.com/leanprover-community/batteries.git nightly-testing-"$MOST_RECENT_NIGHTLY")"
MATHLIB_REMOTE_TAGS="$(git ls-remote https://github.com/leanprover-community/mathlib4-nightly-testing.git nightly-testing-"$MOST_RECENT_NIGHTLY")"
if [[ -n "$BATTERIES_REMOTE_TAGS" ]]; then
echo "... and Batteries has a 'nightly-testing-$MOST_RECENT_NIGHTLY' tag."
if [[ -n "$MATHLIB_REMOTE_TAGS" ]]; then
echo "... and Mathlib has a 'nightly-testing-$MOST_RECENT_NIGHTLY' tag."
MESSAGE=""
if [[ -n "$MATHLIB_REMOTE_TAGS" ]]; then
echo "... and Mathlib has a 'nightly-testing-$MOST_RECENT_NIGHTLY' tag."
else
echo "... but Mathlib does not yet have a 'nightly-testing-$MOST_RECENT_NIGHTLY' tag."
MESSAGE="- ❗ Mathlib CI can not be attempted yet, as the \`nightly-testing-$MOST_RECENT_NIGHTLY\` tag does not exist there yet. We will retry when you push more commits. If you rebase your branch onto \`nightly-with-mathlib\`, Mathlib CI should run now."
fi
else
echo "... but Batteries does not yet have a 'nightly-testing-$MOST_RECENT_NIGHTLY' tag."
MESSAGE="- ❗ Batteries CI can not be attempted yet, as the \`nightly-testing-$MOST_RECENT_NIGHTLY\` tag does not exist there yet. We will retry when you push more commits. If you rebase your branch onto \`nightly-with-mathlib\`, Batteries CI should run now."
echo "... but Mathlib does not yet have a 'nightly-testing-$MOST_RECENT_NIGHTLY' tag."
MESSAGE="- ❗ Mathlib CI can not be attempted yet, as the \`nightly-testing-$MOST_RECENT_NIGHTLY\` tag does not exist there yet. We will retry when you push more commits. If you rebase your branch onto \`nightly-with-mathlib\`, Mathlib CI should run now."
fi
else
echo "The most recently nightly tag on this branch has SHA: $NIGHTLY_SHA"
@@ -515,6 +507,18 @@ jobs:
run: |
git push origin lean-pr-testing-${{ steps.workflow-info.outputs.pullRequestNumber }}
- name: Add mathlib4-nightly-available label
if: steps.workflow-info.outputs.pullRequestNumber != '' && steps.ready.outputs.mathlib_ready == 'true'
uses: actions/github-script@v8
with:
script: |
await github.rest.issues.addLabels({
issue_number: ${{ steps.workflow-info.outputs.pullRequestNumber }},
owner: context.repo.owner,
repo: context.repo.repo,
labels: ['mathlib4-nightly-available']
})
# We next automatically create a reference manual branch using this toolchain.
# Reference manual CI will be responsible for reporting back success or failure
# to the PR comments asynchronously (and thus transitively SubVerso/Verso).

View File

@@ -44,7 +44,9 @@ if (NOT ${CMAKE_SYSTEM_NAME} MATCHES "Emscripten")
set(CADICAL_CXX c++)
if (CADICAL_USE_CUSTOM_CXX)
set(CADICAL_CXX ${CMAKE_CXX_COMPILER})
set(CADICAL_CXXFLAGS "${LEAN_EXTRA_CXX_FLAGS}")
# Use same platform flags as for Lean executables, in particular from `prepare-llvm-linux.sh`,
# but not Lean-specific `LEAN_EXTRA_CXX_FLAGS` such as fsanitize.
set(CADICAL_CXXFLAGS "${CMAKE_CXX_FLAGS}")
set(CADICAL_LDFLAGS "-Wl,-rpath=\\$$ORIGIN/../lib")
endif()
find_program(CCACHE ccache)

View File

@@ -1,189 +1,9 @@
# Foreign Function Interface
NOTE: The current interface was designed for internal use in Lean and should be considered **unstable**.
It will be refined and extended in the future.
The Lean FFI documentation is now part of the [Lean language reference](https://lean-lang.org/doc/reference/latest/).
As Lean is written partially in Lean itself and partially in C++, it offers efficient interoperability between the two languages (or rather, between Lean and any language supporting C interfaces).
This support is however currently limited to transferring Lean data types; in particular, it is not possible yet to pass or return compound data structures such as C `struct`s by value from or to Lean.
* [General FFI](https://lean-lang.org/doc/reference/latest/find/?domain=Verso.Genre.Manual.section&name=ffi)
* [Representation of inductive types](https://lean-lang.org/doc/reference/latest/find/?domain=Verso.Genre.Manual.section&name=inductive-types-ffi)
* [String](https://lean-lang.org/doc/reference/latest/find/?domain=Verso.Genre.Manual.section&name=string-ffi)
* [Array](https://lean-lang.org/doc/reference/latest/find/?domain=Verso.Genre.Manual.section&name=array-ffi)
There are two primary attributes for interoperating with other languages:
* `@[extern "sym"] constant leanSym : ...` binds a Lean declaration to the external symbol `sym`.
It can also be used with `def` to provide an internal definition, but ensuring consistency of both definitions is up to the user.
* `@[export sym] def leanSym : ...` exports `leanSym` under the unmangled symbol name `sym`.
For simple examples of how to call foreign code from Lean and vice versa, see <https://github.com/leanprover/lean4/blob/master/src/lake/examples/ffi> and <https://github.com/leanprover/lean4/blob/master/src/lake/examples/reverse-ffi>, respectively.
## The Lean ABI
The Lean Application Binary Interface (ABI) describes how the signature of a Lean declaration is encoded as a native calling convention.
It is based on the standard C ABI and calling convention of the target platform.
For a Lean declaration marked with either `@[extern "sym"]` or `@[export sym]` for some symbol name `sym`, let `α₁ → ... → αₙ → β` be the normalized declaration's type.
If `n` is 0, the corresponding C declaration is
```c
extern s sym;
```
where `s` is the C translation of `β` as specified in the next section.
In the case of an `@[extern]` definition, the symbol's value is guaranteed to be initialized only after calling the Lean module's initializer or that of an importing module; see [Initialization](#initialization).
If `n` is greater than 0, the corresponding C declaration is
```c
s sym(t, ..., tₘ);
```
where the parameter types `tᵢ` are the C translation of the `αᵢ` as in the next section.
In the case of `@[extern]` all *irrelevant* types are removed first; see next section.
### Translating Types from Lean to C
* The integer types `UInt8`, ..., `UInt64`, `USize` are represented by the C types `uint8_t`, ..., `uint64_t`, `size_t`, respectively
* `Char` is represented by `uint32_t`
* `Float` is represented by `double`
* An *enum* inductive type of at least 2 and at most 2^32 constructors, each of which with no parameters, is represented by the first type of `uint8_t`, `uint16_t`, `uint32_t` that is sufficient to represent all constructor indices.
For example, the type `Bool` is represented as `uint8_t` with values `0` for `false` and `1` for `true`.
* `Decidable α` is represented the same way as `Bool`
* An inductive type with a *trivial structure*, that is,
* it is none of the types described above
* it is not marked `unsafe`
* it has a single constructor with a single parameter of *relevant* type
is represented by the representation of that parameter's type.
For example, `{ x : α // p }`, the `Subtype` structure of a value of type `α` and an irrelevant proof, is represented by the representation of `α`.
Similarly, the signed integer types `Int8`, ..., `Int64`, `ISize` are also represented by the unsigned C types `uint8_t`, ..., `uint64_t`, `size_t`, respectively, because they have a trivial structure.
* `Nat` and `Int` are represented by `lean_object *`.
Their runtime values is either a pointer to an opaque bignum object or, if the lowest bit of the "pointer" is 1 (`lean_is_scalar`), an encoded unboxed natural number or integer (`lean_box`/`lean_unbox`).
* A universe `Sort u`, type constructor `... → Sort u`, `Void α` or proposition `p : Prop` is *irrelevant* and is either statically erased (see above) or represented as a `lean_object *` with the runtime value `lean_box(0)`
* Any other type is represented by `lean_object *`.
Its runtime value is a pointer to an object of a subtype of `lean_object` (see the "Inductive types" section below) or the unboxed value `lean_box(cidx)` for the `cidx`th constructor of an inductive type if this constructor does not have any relevant parameters.
Example: the runtime value of `u : Unit` is always `lean_box(0)`.
#### Inductive types
For inductive types which are in the fallback `lean_object *` case above and not trivial constructors, the type is stored as a `lean_ctor_object`, and `lean_is_ctor` will return true. A `lean_ctor_object` stores the constructor index in the header, and the fields are stored in the `m_objs` portion of the object.
The memory order of the fields is derived from the types and order of the fields in the declaration. They are ordered as follows:
* Non-scalar fields stored as `lean_object *`
* Fields of type `USize`
* Other scalar fields, in decreasing order by size
Within each group the fields are ordered in declaration order. Trivial wrapper types count as their underlying wrapped type for this purpose.
* To access fields of the first kind, use `lean_ctor_get(val, i)` to get the `i`th non-scalar field.
* To access `USize` fields, use `lean_ctor_get_usize(val, n+i)` to get the `i`th usize field and `n` is the total number of fields of the first kind.
* To access other scalar fields, use `lean_ctor_get_uintN(val, off)` or `lean_ctor_get_usize(val, off)` as appropriate. Here `off` is the byte offset of the field in the structure, starting at `n*sizeof(void*)` where `n` is the number of fields of the first two kinds.
For example, a structure such as
```lean
structure S where
ptr_1 : Array Nat
usize_1 : USize
sc64_1 : UInt64
sc64_2 : { x : UInt64 // x > 0 } -- wrappers of scalars count as scalars
sc64_3 : Float -- `Float` is 64 bit
sc8_1 : Bool
sc16_1 : UInt16
sc8_2 : UInt8
sc64_4 : UInt64
usize_2 : USize
sc32_1 : Char -- trivial wrapper around `UInt32`
sc32_2 : UInt32
sc16_2 : UInt16
```
would get re-sorted into the following memory order:
* `S.ptr_1` - `lean_ctor_get(val, 0)`
* `S.usize_1` - `lean_ctor_get_usize(val, 1)`
* `S.usize_2` - `lean_ctor_get_usize(val, 2)`
* `S.sc64_1` - `lean_ctor_get_uint64(val, sizeof(void*)*3)`
* `S.sc64_2` - `lean_ctor_get_uint64(val, sizeof(void*)*3 + 8)`
* `S.sc64_3` - `lean_ctor_get_float(val, sizeof(void*)*3 + 16)`
* `S.sc64_4` - `lean_ctor_get_uint64(val, sizeof(void*)*3 + 24)`
* `S.sc32_1` - `lean_ctor_get_uint32(val, sizeof(void*)*3 + 32)`
* `S.sc32_2` - `lean_ctor_get_uint32(val, sizeof(void*)*3 + 36)`
* `S.sc16_1` - `lean_ctor_get_uint16(val, sizeof(void*)*3 + 40)`
* `S.sc16_2` - `lean_ctor_get_uint16(val, sizeof(void*)*3 + 42)`
* `S.sc8_1` - `lean_ctor_get_uint8(val, sizeof(void*)*3 + 44)`
* `S.sc8_2` - `lean_ctor_get_uint8(val, sizeof(void*)*3 + 45)`
### Borrowing
By default, all `lean_object *` parameters of an `@[extern]` function are considered *owned*, i.e. the external code is passed a "virtual RC token" and is responsible for passing this token along to another consuming function (exactly once) or freeing it via `lean_dec`.
To reduce reference counting overhead, parameters can be marked as *borrowed* by prefixing their type with `@&`.
Borrowed objects must only be passed to other non-consuming functions (arbitrarily often) or converted to owned values using `lean_inc`.
In `lean.h`, the `lean_object *` aliases `lean_obj_arg` and `b_lean_obj_arg` are used to mark this difference on the C side.
Return values and `@[export]` parameters are always owned at the moment.
## Initialization
When including Lean code as part of a larger program, modules must be *initialized* before accessing any of their declarations.
Module initialization entails
* initialization of all "constants" (nullary functions), including closed terms lifted out of other functions
* execution of all `[init]` functions
* execution of all `[builtin_init]` functions, if the `builtin` parameter of the module initializer has been set
The module initializer is automatically run with the `builtin` flag for executables compiled from Lean code and for "plugins" loaded with `lean --plugin`.
For all other modules imported by `lean`, the initializer is run without `builtin`.
Thus `[init]` functions are run iff their module is imported, regardless of whether they have native code available or not, while `[builtin_init]` functions are only run for native executable or plugins, regardless of whether their module is imported or not.
`lean` uses built-in initializers for e.g. registering basic parsers that should be available even without importing their module (which is necessary for bootstrapping).
The initializer for module `A.B` in a package `foo` is called `initialize_foo_A_B`. For modules in the Lean core (e.g., `Init.Prelude`), the initializer is called `initialize_Init_Prelude`. Module initializers will automatically initialize any imported modules. They are also idempotent (when run with the same `builtin` flag), but not thread-safe.
**Important for process-related functionality**: If your application needs to use process-related functions from libuv, such as `Std.Internal.IO.Process.getProcessTitle` and `Std.Internal.IO.Process.setProcessTitle`, you must call `lean_setup_args(argc, argv)` (which returns a potentially modified `argv` that must be used in place of the original) **before** calling `lean_initialize()` or `lean_initialize_runtime_module()`. This sets up process handling capabilities correctly, which is essential for certain system-level operations that Lean's runtime may depend on.
Together with initialization of the Lean runtime, you should execute code like the following exactly once before accessing any Lean declarations:
```c
void lean_initialize_runtime_module();
void lean_initialize();
char ** lean_setup_args(int argc, char ** argv);
lean_object * initialize_A_B(uint8_t builtin);
lean_object * initialize_C(uint8_t builtin);
...
argv = lean_setup_args(argc, argv); // if using process-related functionality
lean_initialize_runtime_module();
//lean_initialize(); // necessary (and replaces `lean_initialize_runtime_module`) if you (indirectly) access the `Lean` package
lean_object * res;
// use same default as for Lean executables
uint8_t builtin = 1;
res = initialize_A_B(builtin);
if (lean_io_result_is_ok(res)) {
lean_dec_ref(res);
} else {
lean_io_result_show_error(res);
lean_dec(res);
return ...; // do not access Lean declarations if initialization failed
}
res = initialize_C(builtin);
if (lean_io_result_is_ok(res)) {
...
//lean_init_task_manager(); // necessary if you (indirectly) use `Task`
lean_io_mark_end_initialization();
```
In addition, any other thread not spawned by the Lean runtime itself must be initialized for Lean use by calling
```c
void lean_initialize_thread();
```
and should be finalized in order to free all thread-local resources by calling
```c
void lean_finalize_thread();
```
## `@[extern]` in the Interpreter
The interpreter can run Lean declarations for which symbols are available in loaded shared libraries, which includes `@[extern]` declarations.
Thus to e.g. run `#eval` on such a declaration, you need to
1. compile (at least) the module containing the declaration and its dependencies into a shared library, and then
1. pass this library to `lean --load-dynlib=` to run code `import`ing this module.
Note that it is not sufficient to load the foreign library containing the external symbol because the interpreter depends on code that is emitted for each `@[extern]` declaration.
Thus it is not possible to interpret an `@[extern]` declaration in the same file.
See [`tests/compiler/foreign`](https://github.com/leanprover/lean4/tree/master/tests/compiler/foreign/) for an example.

View File

@@ -69,6 +69,10 @@ We'll use `v4.6.0` as the intended release version as a running example.
- `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.
- `lean-fro.org`:
After updating the toolchains and running `lake update`, you must run `scripts/update.sh` to regenerate
the site content. This script updates generated files that depend on the Lean version.
The `release_steps.py` script handles this automatically.
- An awkward situation that sometimes occurs (e.g. with Verso) is that the `master`/`main` branch has already been moved
to a nightly toolchain that comes *after* the stable toolchain we are
targeting. In this case it is necessary to create a branch `releases/v4.6.0` from the last commit which was on

View File

@@ -51,6 +51,10 @@ All these tests are included by [src/shell/CMakeLists.txt](https://github.com/le
codes and do not check the expected output even though output is
produced, it is ignored.
**Note:** Tests in this directory run with `-Dlinter.all=false` to reduce noise.
If your test needs to verify linter behavior (e.g., deprecation warnings),
explicitly enable the relevant linter with `set_option linter.<name> true`.
- [`tests/lean/interactive`](https://github.com/leanprover/lean4/tree/master/tests/lean/interactive/): are designed to test server requests at a
given position in the input file. Each .lean file contains comments
that indicate how to simulate a client request at that position.

View File

@@ -4,6 +4,7 @@ import GroveStdlib.Generated.«associative-creation-operations»
import GroveStdlib.Generated.«associative-modification-operations»
import GroveStdlib.Generated.«associative-create-then-query»
import GroveStdlib.Generated.«associative-all-operations-covered»
import GroveStdlib.Generated.«slice-producing»
/-
This file is autogenerated by grove. You can manually edit it, for example to resolve merge
@@ -20,3 +21,4 @@ def restoreState : RestoreStateM Unit := do
«associative-modification-operations».restoreState
«associative-create-then-query».restoreState
«associative-all-operations-covered».restoreState
«slice-producing».restoreState

View File

@@ -0,0 +1,459 @@
import Grove.Framework
/-
This file is autogenerated by grove. You can manually edit it, for example to resolve merge
conflicts, but be careful.
-/
open Grove.Framework Widget
namespace GroveStdlib.Generated.«slice-producing»
def «c8a13d6d-7ed6-4cd1-a386-23e2d55ce6f7» : AssociationTable.Fact .declaration where
widgetId := "slice-producing"
factId := "c8a13d6d-7ed6-4cd1-a386-23e2d55ce6f7"
rowId := "c8a13d6d-7ed6-4cd1-a386-23e2d55ce6f7"
rowState := #["String", "String.slice", Declaration.def {
name := `String.slice
renderedStatement := "String.slice (s : String) (startInclusive endExclusive : s.Pos)\n (h : startInclusive ≤ endExclusive) : String.Slice"
isDeprecated := false
}
,"String.Slice", "String.Slice.slice", Declaration.def {
name := `String.Slice.slice
renderedStatement := "String.Slice.slice (s : String.Slice) (newStart newEnd : s.Pos) (h : newStart ≤ newEnd) :\n String.Slice"
isDeprecated := false
}
,"string-pos-forwards", "String.Pos.slice", Declaration.def {
name := `String.Pos.slice
renderedStatement := "String.Pos.slice {s : String} (pos p₀ p₁ : s.Pos) (h₁ : p₀ ≤ pos) (h₂ : pos ≤ p₁) :\n (s.slice p₀ p₁ ⋯).Pos"
isDeprecated := false
}
,"string-pos-backwards", "String.Pos.ofSlice", Declaration.def {
name := `String.Pos.ofSlice
renderedStatement := "String.Pos.ofSlice {s : String} {p₀ p₁ : s.Pos} {h : p₀ ≤ p₁} (pos : (s.slice p₀ p₁ h).Pos) : s.Pos"
isDeprecated := false
}
,"string-slice-pos-forwards", "String.Slice.Pos.slice", Declaration.def {
name := `String.Slice.Pos.slice
renderedStatement := "String.Slice.Pos.slice {s : String.Slice} (pos p₀ p₁ : s.Pos) (h₁ : p₀ ≤ pos) (h₂ : pos ≤ p₁) :\n (s.slice p₀ p₁ ⋯).Pos"
isDeprecated := false
}
,"string-slice-pos-backwards", "String.Slice.Pos.ofSlice", Declaration.def {
name := `String.Slice.Pos.ofSlice
renderedStatement := "String.Slice.Pos.ofSlice {s : String.Slice} {p₀ p₁ : s.Pos} {h : p₀ ≤ p₁}\n (pos : (s.slice p₀ p₁ h).Pos) : s.Pos"
isDeprecated := false
}
,"string-pos-noproof", "String.Pos.sliceOrPanic", Declaration.def {
name := `String.Pos.sliceOrPanic
renderedStatement := "String.Pos.sliceOrPanic {s : String} (pos p₀ p₁ : s.Pos) {h : p₀ ≤ p₁} : (s.slice p₀ p₁ h).Pos"
isDeprecated := false
}
,"string-slice-pos-noproof", "String.Slice.Pos.sliceOrPanic", Declaration.def {
name := `String.Slice.Pos.sliceOrPanic
renderedStatement := "String.Slice.Pos.sliceOrPanic {s : String.Slice} (pos p₀ p₁ : s.Pos) {h : p₀ ≤ p₁} :\n (s.slice p₀ p₁ h).Pos"
isDeprecated := false
}
,]
metadata := {
status := .done
comment := ""
}
def «21b4fdfd-f8b3-44f5-a59e-57f1dc1d6819» : AssociationTable.Fact .declaration where
widgetId := "slice-producing"
factId := "21b4fdfd-f8b3-44f5-a59e-57f1dc1d6819"
rowId := "21b4fdfd-f8b3-44f5-a59e-57f1dc1d6819"
rowState := #["String", "String.slice?", Declaration.def {
name := `String.slice?
renderedStatement := "String.slice? (s : String) (startInclusive endExclusive : s.Pos) : Option String.Slice"
isDeprecated := false
}
,"String.Slice", "String.Slice.slice?", Declaration.def {
name := `String.Slice.slice?
renderedStatement := "String.Slice.slice? (s : String.Slice) (newStart newEnd : s.Pos) : Option String.Slice"
isDeprecated := false
}
,]
metadata := {
status := .postponed
comment := "Would be good to have better support"
}
def «6f2b6ecb-2f0c-4e45-9da3-eb7f2e15eff0» : AssociationTable.Fact .declaration where
widgetId := "slice-producing"
factId := "6f2b6ecb-2f0c-4e45-9da3-eb7f2e15eff0"
rowId := "6f2b6ecb-2f0c-4e45-9da3-eb7f2e15eff0"
rowState := #["String", "String.slice!", Declaration.def {
name := `String.slice!
renderedStatement := "String.slice! (s : String) (p₁ p₂ : s.Pos) : String.Slice"
isDeprecated := false
}
,"String.Slice", "String.Slice.slice!", Declaration.def {
name := `String.Slice.slice!
renderedStatement := "String.Slice.slice! (s : String.Slice) (newStart newEnd : s.Pos) : String.Slice"
isDeprecated := false
}
,"string-pos-forwards", "String.Pos.slice!", Declaration.def {
name := `String.Pos.slice!
renderedStatement := "String.Pos.slice! {s : String} (pos p₀ p₁ : s.Pos) : (s.slice! p₀ p₁).Pos"
isDeprecated := false
}
,"string-pos-backwards", "String.Pos.ofSlice!", Declaration.def {
name := `String.Pos.ofSlice!
renderedStatement := "String.Pos.ofSlice! {s : String} {p₀ p₁ : s.Pos} (pos : (s.slice! p₀ p₁).Pos) : s.Pos"
isDeprecated := false
}
,"string-slice-pos-forwards", "String.Slice.Pos.slice!", Declaration.def {
name := `String.Slice.Pos.slice!
renderedStatement := "String.Slice.Pos.slice! {s : String.Slice} (pos p₀ p₁ : s.Pos) : (s.slice! p₀ p₁).Pos"
isDeprecated := false
}
,"string-slice-pos-backwards", "String.Slice.Pos.ofSlice!", Declaration.def {
name := `String.Slice.Pos.ofSlice!
renderedStatement := "String.Slice.Pos.ofSlice! {s : String.Slice} {p₀ p₁ : s.Pos} (pos : (s.slice! p₀ p₁).Pos) : s.Pos"
isDeprecated := false
}
,]
metadata := {
status := .done
comment := ""
}
def «a3bdf66d-bc11-4019-aee9-2f1c1701de52» : AssociationTable.Fact .declaration where
widgetId := "slice-producing"
factId := "a3bdf66d-bc11-4019-aee9-2f1c1701de52"
rowId := "a3bdf66d-bc11-4019-aee9-2f1c1701de52"
rowState := #["String", "String.trimAsciiStart", Declaration.def {
name := `String.trimAsciiStart
renderedStatement := "String.trimAsciiStart (s : String) : String.Slice"
isDeprecated := false
}
,"String.Slice", "String.Slice.trimAsciiStart", Declaration.def {
name := `String.Slice.trimAsciiStart
renderedStatement := "String.Slice.trimAsciiStart (s : String.Slice) : String.Slice"
isDeprecated := false
}
,]
metadata := {
status := .bad
comment := "Missing `of` version at least"
}
def «f12b2730-7a4d-465c-8a6d-9d051c300fd5» : AssociationTable.Fact .declaration where
widgetId := "slice-producing"
factId := "f12b2730-7a4d-465c-8a6d-9d051c300fd5"
rowId := "f12b2730-7a4d-465c-8a6d-9d051c300fd5"
rowState := #["String", "String.trimAsciiEnd", Declaration.def {
name := `String.trimAsciiEnd
renderedStatement := "String.trimAsciiEnd (s : String) : String.Slice"
isDeprecated := false
}
,"String.Slice", "String.Slice.trimAsciiEnd", Declaration.def {
name := `String.Slice.trimAsciiEnd
renderedStatement := "String.Slice.trimAsciiEnd (s : String.Slice) : String.Slice"
isDeprecated := false
}
,]
metadata := {
status := .bad
comment := "Missing `of` version at least"
}
def «32307b55-d6d1-4756-a947-dbe4dfde573c» : AssociationTable.Fact .declaration where
widgetId := "slice-producing"
factId := "32307b55-d6d1-4756-a947-dbe4dfde573c"
rowId := "32307b55-d6d1-4756-a947-dbe4dfde573c"
rowState := #["String", "String.trimAscii", Declaration.def {
name := `String.trimAscii
renderedStatement := "String.trimAscii (s : String) : String.Slice"
isDeprecated := false
}
,"String.Slice", "String.Slice.trimAscii", Declaration.def {
name := `String.Slice.trimAscii
renderedStatement := "String.Slice.trimAscii (s : String.Slice) : String.Slice"
isDeprecated := false
}
,]
metadata := {
status := .bad
comment := "Missing `of` version at least\n"
}
def «dce95a38-f55a-4d6a-ae79-078ffe4b5c15» : AssociationTable.Fact .declaration where
widgetId := "slice-producing"
factId := "dce95a38-f55a-4d6a-ae79-078ffe4b5c15"
rowId := "dce95a38-f55a-4d6a-ae79-078ffe4b5c15"
rowState := #["String", "String.toSlice", Declaration.def {
name := `String.toSlice
renderedStatement := "String.toSlice (s : String) : String.Slice"
isDeprecated := false
}
,"string-pos-forwards", "String.Pos.toSlice", Declaration.def {
name := `String.Pos.toSlice
renderedStatement := "String.Pos.toSlice {s : String} (pos : s.Pos) : s.toSlice.Pos"
isDeprecated := false
}
,"string-pos-backwards", "String.Pos.ofToSlice", Declaration.def {
name := `String.Pos.ofToSlice
renderedStatement := "String.Pos.ofToSlice {s : String} (pos : s.toSlice.Pos) : s.Pos"
isDeprecated := false
}
,]
metadata := {
status := .done
comment := ""
}
def «005a3f30-5dab-493f-b168-32c36a2bdf7c» : AssociationTable.Fact .declaration where
widgetId := "slice-producing"
factId := "005a3f30-5dab-493f-b168-32c36a2bdf7c"
rowId := "005a3f30-5dab-493f-b168-32c36a2bdf7c"
rowState := #["String.Slice", "String.Slice.str", Declaration.def {
name := `String.Slice.str
renderedStatement := "String.Slice.str (self : String.Slice) : String"
isDeprecated := false
}
,"string-slice-pos-forwards", "String.Slice.Pos.str", Declaration.def {
name := `String.Slice.Pos.str
renderedStatement := "String.Slice.Pos.str {s : String.Slice} (pos : s.Pos) : s.str.Pos"
isDeprecated := false
}
,"string-slice-pos-backwards", "String.Slice.Pos.ofStr", Declaration.def {
name := `String.Slice.Pos.ofStr
renderedStatement := "String.Slice.Pos.ofStr {s : String.Slice} (pos : s.str.Pos) (h₁ : s.startInclusive ≤ pos)\n (h₂ : pos ≤ s.endExclusive) : s.Pos"
isDeprecated := false
}
,]
metadata := {
status := .bad
comment := "Missing `no proof` version\n"
}
def «5f1a154c-ae2f-43a1-9409-2ce95b163ef3» : AssociationTable.Fact .declaration where
widgetId := "slice-producing"
factId := "5f1a154c-ae2f-43a1-9409-2ce95b163ef3"
rowId := "5f1a154c-ae2f-43a1-9409-2ce95b163ef3"
rowState := #["String", "String.drop", Declaration.def {
name := `String.drop
renderedStatement := "String.drop (s : String) (n : Nat) : String.Slice"
isDeprecated := false
}
,"String.Slice", "String.Slice.drop", Declaration.def {
name := `String.Slice.drop
renderedStatement := "String.Slice.drop (s : String.Slice) (n : Nat) : String.Slice"
isDeprecated := false
}
,]
metadata := {
status := .bad
comment := "Missing position transformations"
}
def «179518d1-ad07-4b2b-8ffe-3b7616e4c4ab» : AssociationTable.Fact .declaration where
widgetId := "slice-producing"
factId := "179518d1-ad07-4b2b-8ffe-3b7616e4c4ab"
rowId := "179518d1-ad07-4b2b-8ffe-3b7616e4c4ab"
rowState := #["String", "String.take", Declaration.def {
name := `String.take
renderedStatement := "String.take (s : String) (n : Nat) : String.Slice"
isDeprecated := false
}
,"String.Slice", "String.Slice.take", Declaration.def {
name := `String.Slice.take
renderedStatement := "String.Slice.take (s : String.Slice) (n : Nat) : String.Slice"
isDeprecated := false
}
,]
metadata := {
status := .bad
comment := "Missing position transformations"
}
def «55c587fd-a7a8-4633-a4ae-e2c4e768ad28» : AssociationTable.Fact .declaration where
widgetId := "slice-producing"
factId := "55c587fd-a7a8-4633-a4ae-e2c4e768ad28"
rowId := "55c587fd-a7a8-4633-a4ae-e2c4e768ad28"
rowState := #["String", "String.dropWhile", Declaration.def {
name := `String.dropWhile
renderedStatement := "String.dropWhile {ρ : Type} (s : String) (pat : ρ) [String.Slice.Pattern.ForwardPattern pat] :\n String.Slice"
isDeprecated := false
}
,"String.Slice", "String.Slice.dropWhile", Declaration.def {
name := `String.Slice.dropWhile
renderedStatement := "String.Slice.dropWhile {ρ : Type} (s : String.Slice) (pat : ρ)\n [String.Slice.Pattern.ForwardPattern pat] : String.Slice"
isDeprecated := false
}
,]
metadata := {
status := .bad
comment := "Missing position transformations"
}
def «d4444684-4279-4400-9be2-561a7cdb32c1» : AssociationTable.Fact .declaration where
widgetId := "slice-producing"
factId := "d4444684-4279-4400-9be2-561a7cdb32c1"
rowId := "d4444684-4279-4400-9be2-561a7cdb32c1"
rowState := #["String", "String.takeWhile", Declaration.def {
name := `String.takeWhile
renderedStatement := "String.takeWhile {ρ : Type} (s : String) (pat : ρ) [String.Slice.Pattern.ForwardPattern pat] :\n String.Slice"
isDeprecated := false
}
,"String.Slice", "String.Slice.takeWhile", Declaration.def {
name := `String.Slice.takeWhile
renderedStatement := "String.Slice.takeWhile {ρ : Type} (s : String.Slice) (pat : ρ)\n [String.Slice.Pattern.ForwardPattern pat] : String.Slice"
isDeprecated := false
}
,]
metadata := {
status := .bad
comment := "Missing position transformations"
}
def «1c9e6689-65a0-4d4b-b001-256e83917d98» : AssociationTable.Fact .declaration where
widgetId := "slice-producing"
factId := "1c9e6689-65a0-4d4b-b001-256e83917d98"
rowId := "1c9e6689-65a0-4d4b-b001-256e83917d98"
rowState := #["String", "String.dropEndWhile", Declaration.def {
name := `String.dropEndWhile
renderedStatement := "String.dropEndWhile {ρ : Type} (s : String) (pat : ρ) [String.Slice.Pattern.BackwardPattern pat] :\n String.Slice"
isDeprecated := false
}
,"String.Slice", "String.Slice.dropEndWhile", Declaration.def {
name := `String.Slice.dropEndWhile
renderedStatement := "String.Slice.dropEndWhile {ρ : Type} (s : String.Slice) (pat : ρ)\n [String.Slice.Pattern.BackwardPattern pat] : String.Slice"
isDeprecated := false
}
,]
metadata := {
status := .bad
comment := "Missing position transformations"
}
def «b836052b-3470-4a8e-8989-6951c898de37» : AssociationTable.Fact .declaration where
widgetId := "slice-producing"
factId := "b836052b-3470-4a8e-8989-6951c898de37"
rowId := "b836052b-3470-4a8e-8989-6951c898de37"
rowState := #["String", "String.takeEndWhile", Declaration.def {
name := `String.takeEndWhile
renderedStatement := "String.takeEndWhile {ρ : Type} (s : String) (pat : ρ) [String.Slice.Pattern.BackwardPattern pat] :\n String.Slice"
isDeprecated := false
}
,"String.Slice", "String.Slice.takeEndWhile", Declaration.def {
name := `String.Slice.takeEndWhile
renderedStatement := "String.Slice.takeEndWhile {ρ : Type} (s : String.Slice) (pat : ρ)\n [String.Slice.Pattern.BackwardPattern pat] : String.Slice"
isDeprecated := false
}
,]
metadata := {
status := .bad
comment := "Missing position transformations"
}
def «5aa777d8-9642-43d8-9e20-30400fb8bb9d» : AssociationTable.Fact .declaration where
widgetId := "slice-producing"
factId := "5aa777d8-9642-43d8-9e20-30400fb8bb9d"
rowId := "5aa777d8-9642-43d8-9e20-30400fb8bb9d"
rowState := #["String", "String.dropPrefix", Declaration.def {
name := `String.dropPrefix
renderedStatement := "String.dropPrefix {ρ : Type} (s : String) (pat : ρ) [String.Slice.Pattern.ForwardPattern pat] :\n String.Slice"
isDeprecated := false
}
,"String.Slice", "String.Slice.dropPrefix", Declaration.def {
name := `String.Slice.dropPrefix
renderedStatement := "String.Slice.dropPrefix {ρ : Type} (s : String.Slice) (pat : ρ)\n [String.Slice.Pattern.ForwardPattern pat] : String.Slice"
isDeprecated := false
}
,]
metadata := {
status := .bad
comment := "Missing position transformations"
}
def «80e3869d-fcfe-459d-8433-fe221f7b3c7a» : AssociationTable.Fact .declaration where
widgetId := "slice-producing"
factId := "80e3869d-fcfe-459d-8433-fe221f7b3c7a"
rowId := "80e3869d-fcfe-459d-8433-fe221f7b3c7a"
rowState := #["String", "String.dropSuffix", Declaration.def {
name := `String.dropSuffix
renderedStatement := "String.dropSuffix {ρ : Type} (s : String) (pat : ρ) [String.Slice.Pattern.BackwardPattern pat] :\n String.Slice"
isDeprecated := false
}
,"String.Slice", "String.Slice.dropSuffix", Declaration.def {
name := `String.Slice.dropSuffix
renderedStatement := "String.Slice.dropSuffix {ρ : Type} (s : String.Slice) (pat : ρ)\n [String.Slice.Pattern.BackwardPattern pat] : String.Slice"
isDeprecated := false
}
,]
metadata := {
status := .bad
comment := "Missing position transformations"
}
def «4feda3e0-903b-4d52-b34e-0af70f7866e0» : AssociationTable.Fact .declaration where
widgetId := "slice-producing"
factId := "4feda3e0-903b-4d52-b34e-0af70f7866e0"
rowId := "4feda3e0-903b-4d52-b34e-0af70f7866e0"
rowState := #["String", "String.dropPrefix?", Declaration.def {
name := `String.dropPrefix?
renderedStatement := "String.dropPrefix? {ρ : Type} (s : String) (pat : ρ) [String.Slice.Pattern.ForwardPattern pat] :\n Option String.Slice"
isDeprecated := false
}
,"String.Slice", "String.Slice.dropPrefix?", Declaration.def {
name := `String.Slice.dropPrefix?
renderedStatement := "String.Slice.dropPrefix? {ρ : Type} (s : String.Slice) (pat : ρ)\n [String.Slice.Pattern.ForwardPattern pat] : Option String.Slice"
isDeprecated := false
}
,]
metadata := {
status := .postponed
comment := "Missing position transformations"
}
def «45ca44c8-fbd5-4400-8297-a60778f302b0» : AssociationTable.Fact .declaration where
widgetId := "slice-producing"
factId := "45ca44c8-fbd5-4400-8297-a60778f302b0"
rowId := "45ca44c8-fbd5-4400-8297-a60778f302b0"
rowState := #["String", "String.dropSuffix?", Declaration.def {
name := `String.dropSuffix?
renderedStatement := "String.dropSuffix? {ρ : Type} (s : String) (pat : ρ) [String.Slice.Pattern.BackwardPattern pat] :\n Option String.Slice"
isDeprecated := false
}
,"String.Slice", "String.Slice.dropSuffix?", Declaration.def {
name := `String.Slice.dropSuffix?
renderedStatement := "String.Slice.dropSuffix? {ρ : Type} (s : String.Slice) (pat : ρ)\n [String.Slice.Pattern.BackwardPattern pat] : Option String.Slice"
isDeprecated := false
}
,]
metadata := {
status := .postponed
comment := "Missing position transformations"
}
def table : AssociationTable.Data .declaration where
widgetId := "slice-producing"
rows := #[
"c8a13d6d-7ed6-4cd1-a386-23e2d55ce6f7", "slice", #["String", "String.slice","String.Slice", "String.Slice.slice","string-pos-forwards", "String.Pos.slice","string-pos-backwards", "String.Pos.ofSlice","string-slice-pos-forwards", "String.Slice.Pos.slice","string-slice-pos-backwards", "String.Slice.Pos.ofSlice","string-pos-noproof", "String.Pos.sliceOrPanic","string-slice-pos-noproof", "String.Slice.Pos.sliceOrPanic",],
"21b4fdfd-f8b3-44f5-a59e-57f1dc1d6819", "slice?", #["String", "String.slice?","String.Slice", "String.Slice.slice?",],
"6f2b6ecb-2f0c-4e45-9da3-eb7f2e15eff0", "slice!", #["String", "String.slice!","String.Slice", "String.Slice.slice!","string-pos-forwards", "String.Pos.slice!","string-pos-backwards", "String.Pos.ofSlice!","string-slice-pos-forwards", "String.Slice.Pos.slice!","string-slice-pos-backwards", "String.Slice.Pos.ofSlice!",],
"a3bdf66d-bc11-4019-aee9-2f1c1701de52", "trimAsciiStart", #["String", "String.trimAsciiStart","String.Slice", "String.Slice.trimAsciiStart",],
"f12b2730-7a4d-465c-8a6d-9d051c300fd5", "trimAsciiEnd", #["String", "String.trimAsciiEnd","String.Slice", "String.Slice.trimAsciiEnd",],
"32307b55-d6d1-4756-a947-dbe4dfde573c", "trimAscii", #["String", "String.trimAscii","String.Slice", "String.Slice.trimAscii",],
"dce95a38-f55a-4d6a-ae79-078ffe4b5c15", "toSlice", #["String", "String.toSlice","string-pos-forwards", "String.Pos.toSlice","string-pos-backwards", "String.Pos.ofToSlice",],
"005a3f30-5dab-493f-b168-32c36a2bdf7c", "str", #["String.Slice", "String.Slice.str","string-slice-pos-forwards", "String.Slice.Pos.str","string-slice-pos-backwards", "String.Slice.Pos.ofStr",],
"5f1a154c-ae2f-43a1-9409-2ce95b163ef3", "drop", #["String", "String.drop","String.Slice", "String.Slice.drop",],
"179518d1-ad07-4b2b-8ffe-3b7616e4c4ab", "take", #["String", "String.take","String.Slice", "String.Slice.take",],
"55c587fd-a7a8-4633-a4ae-e2c4e768ad28", "dropWhile", #["String", "String.dropWhile","String.Slice", "String.Slice.dropWhile",],
"d4444684-4279-4400-9be2-561a7cdb32c1", "takeWhile", #["String", "String.takeWhile","String.Slice", "String.Slice.takeWhile",],
"1c9e6689-65a0-4d4b-b001-256e83917d98", "dropEndWhile", #["String", "String.dropEndWhile","String.Slice", "String.Slice.dropEndWhile",],
"b836052b-3470-4a8e-8989-6951c898de37", "takeEndWhile", #["String", "String.takeEndWhile","String.Slice", "String.Slice.takeEndWhile",],
"5aa777d8-9642-43d8-9e20-30400fb8bb9d", "dropPrefix", #["String", "String.dropPrefix","String.Slice", "String.Slice.dropPrefix",],
"80e3869d-fcfe-459d-8433-fe221f7b3c7a", "dropSuffix", #["String", "String.dropSuffix","String.Slice", "String.Slice.dropSuffix",],
"4feda3e0-903b-4d52-b34e-0af70f7866e0", "dropPrefix?", #["String", "String.dropPrefix?","String.Slice", "String.Slice.dropPrefix?",],
"45ca44c8-fbd5-4400-8297-a60778f302b0", "dropSuffix?", #["String", "String.dropSuffix?","String.Slice", "String.Slice.dropSuffix?",],
]
facts := #[
«c8a13d6d-7ed6-4cd1-a386-23e2d55ce6f7»,
«21b4fdfd-f8b3-44f5-a59e-57f1dc1d6819»,
«6f2b6ecb-2f0c-4e45-9da3-eb7f2e15eff0»,
«a3bdf66d-bc11-4019-aee9-2f1c1701de52»,
«f12b2730-7a4d-465c-8a6d-9d051c300fd5»,
«32307b55-d6d1-4756-a947-dbe4dfde573c»,
«dce95a38-f55a-4d6a-ae79-078ffe4b5c15»,
«005a3f30-5dab-493f-b168-32c36a2bdf7c»,
«5f1a154c-ae2f-43a1-9409-2ce95b163ef3»,
«179518d1-ad07-4b2b-8ffe-3b7616e4c4ab»,
«55c587fd-a7a8-4633-a4ae-e2c4e768ad28»,
«d4444684-4279-4400-9be2-561a7cdb32c1»,
«1c9e6689-65a0-4d4b-b001-256e83917d98»,
«b836052b-3470-4a8e-8989-6951c898de37»,
«5aa777d8-9642-43d8-9e20-30400fb8bb9d»,
«80e3869d-fcfe-459d-8433-fe221f7b3c7a»,
«4feda3e0-903b-4d52-b34e-0af70f7866e0»,
«45ca44c8-fbd5-4400-8297-a60778f302b0»,
]
def restoreState : RestoreStateM Unit := do
addAssociationTable table

View File

@@ -15,7 +15,7 @@ namespace GroveStdlib
namespace Std
def introduction : Node :=
.text "Welcome to the interactive Lean standard library outline!"
.text "introduction", "Welcome to the interactive Lean standard library outline!"
end Std

View File

@@ -11,9 +11,87 @@ namespace GroveStdlib.Std.CoreTypesAndOperations
namespace StringsAndFormatting
open Lean Meta
def introduction : Text where
id := "string-introduction"
content := Grove.Markdown.render [
.h1 "The Lean string library",
.text "The Lean standard library contains a fully-featured string library, centered around the types `String` and `String.Slice`.",
.text "`String` is defined as the subtype of `ByteArray` of valid UTF-8 strings. A `String.Slice` is a `String` together with a start and end position.",
.text "`String` is equivalent to `List Char`, but it has a more efficient runtime representation. While the logical model based on `ByteArray` is overwritten in the runtime, the runtime implementation is very similar to the logical model, with the main difference being that the length of a string in Unicode code points is cached in the runtime implementation.",
.text "We are considering removing this feature in the future (i.e., deprecating `String.length`), as the number of UTF-8 codepoints in a string is not particularly useful, and if needed it can be computed in linear time using `s.positions.count`."
]
def highLevelStringTypes : List Lean.Name :=
[`String, `String.Slice, `String.Pos, `String.Slice.Pos]
def creatingStringsAndSlices : Text where
id := "transforming-strings-and-slices"
content := Grove.Markdown.render [
.h2 "Transforming strings and slices",
.text "The Lean standard library contains a number of functions that take one or more strings and slices and return a string or a slice.",
.text "If possible, these functions should avoid allocating a new string, and return a slice of their input(s) instead.",
.text "Usually, for every operation `f`, there will be functions `String.f` and `String.Slice.f`, where `String.f s` is defined as `String.Slice.f s.toSlice`.",
.text "In particular, functions that transform strings and slices should live in the `String` and `String.Slice` namespaces even if they involve a `String.Pos`/`String.Slice.Pos` (like `String.sliceTo`), for reasons that will become clear shortly.",
.h3 "Transforming positions",
.text "Since positions on strings and slices are dependent on the string or slice, whenever users transform a string/slice, they will be interested in interpreting positions on the original string/slice as positions on the result, or vice versa.",
.text "Consequently, every operation that transforms a string or slice should come with a corresponding set of transformations between positions, usually in both directions, possibly with one of the directions being conditional.",
.text "For example, given a string `s` and a position `p` on `s`, we have the slice `s.sliceFrom p`, which is the slice from `p` to the end of `s`. A position on `s.sliceFrom p` can always be interpreted as a position on `s`. This is the \"backwards\" transformation. Conversely, a position `q` on `s` can be interpreted as a position on `s.sliceFrom p` as long as `p ≤ q`. This is the conditional forwards direction.",
.text "The convention for naming these transformations is that the forwards transformation should have the same name as the transformation on strings/slices, but it should be located in the `String.Pos` or `String.Slice.Pos` namespace, depending on the type of the starting position (so that dot notation is possible for the forward direction). The backwards transformation should have the same name as the operation on strings/slices, but with an `of` prefix, and live in the same namespace as the forwards transformation (so in general dot notation will not be available).",
.text "So, in the `sliceFrom` example, the forward direction would be called `String.Pos.sliceFrom`, while the backwards direction should be called `String.Pos.ofSliceFrom` (not `String.Slice.Pos.ofSliceFrom`).",
.text "If one of the directions is conditional, it should have a corresponding panicking operation that does not require a proof; in our example this would be `String.Pos.sliceFrom!`.",
.text "Sometimes there is a name clash for the panicking operations if the operation on strings is already panicking. For example, there are both `String.slice` and `String.slice!`. If the original operation is already panicking, we only provide panicking transformation operations. But now `String.Pos.slice!` could refer both to the panicking forwards transformation associated with `String.slice`, and also to the (only) forwards transformation associated with `String.slice!`. In this situation, we use an `orPanic` suffix to disambiguate. So the panicking forwards operation associated with `String.slice` is called `String.Pos.sliceOrPanic`, and the forwards operation associated with `String.slice!` is called `String.Pos.slice!`."
]
-- TODO: also include the `HAppend` instance(s)
def sliceProducing : AssociationTable (β := Alias Lean.Name) .declaration
[`String, `String.Slice,
Alias.mk `String.Pos "string-pos-forwards" "String.Pos (forwards)",
Alias.mk `String.Pos "string-pos-backwards" "String.Pos (backwards)",
Alias.mk `String.Pos "string-pos-noproof" "String.Pos (no proof)",
Alias.mk `String.Slice.Pos "string-slice-pos-forwards" "String.Slice.Pos (forwards)",
Alias.mk `String.Slice.Pos "string-slice-pos-backwards" "String.Slice.Pos (backwards)",
Alias.mk `String.Slice.Pos "string-slice-pos-noproof" "String.Slice.Pos (no proof)"] where
id := "slice-producing"
title := "String functions returning strings or slices"
description := "Operations on strings and string slices that themselves return a new string slice."
dataSources n := DataSource.definitionsInNamespace n.inner
def sliceProducingComplete : Assertion where
widgetId := "slice-producing-complete"
title := "Slice-producing table is complete"
description := "All functions in the `String.**` namespace that return a string or a slice are covered in the table"
check := do
let mut ans := #[]
let covered := Std.HashSet.ofArray ( valuesInAssociationTable sliceProducing)
let pred : DataSource.DeclarationPredicate :=
DataSource.DeclarationPredicate.all [.isDefinition, .not .isDeprecated,
.notInNamespace `String.Pos.Raw, .notInNamespace `String.Legacy,
.not .isInstance]
let env getEnv
for name in declarationsMatching `String pred do
let some c := env.find? name | continue
if c.type.getForallBody.getUsedConstants.any (fun n => n == ``String || n == ``String.Slice) then
let success : Bool := name.toString covered
ans := ans.push {
assertionId := name.toString
description := s!"`{name}` should appear in the table."
passed := success
message := s!"`{name}` was{if success then "" else " not"} found in the table."
}
return ans
end StringsAndFormatting
def stringsAndFormatting : Node :=
.section "strings-and-formatting" "Strings and formatting" #[]
open StringsAndFormatting
end GroveStdlib.Std.CoreTypesAndOperations
def stringsAndFormatting : Node :=
.section "strings-and-formatting" "Strings and formatting"
#[.text introduction,
.text creatingStringsAndSlices,
.associationTable sliceProducing,
.assertion sliceProducingComplete]
end GroveStdlib.Std.CoreTypesAndOperations

View File

@@ -5,7 +5,7 @@
"type": "git",
"subDir": "backend",
"scope": "",
"rev": "3e8aabdea58c11813c5d3b7eeb187ded44ee9a34",
"rev": "c580a425c9b7fa2aebaec2a1d8de16b2e2283c40",
"name": "grove",
"manifestFile": "lake-manifest.json",
"inputRev": "master",
@@ -15,10 +15,10 @@
"type": "git",
"subDir": null,
"scope": "leanprover",
"rev": "1604206fcd0462da9a241beeac0e2df471647435",
"rev": "d9fc8ae23024be37424a189982c92356e37935c8",
"name": "Cli",
"manifestFile": "lake-manifest.json",
"inputRev": "main",
"inputRev": "nightly-testing",
"inherited": true,
"configFile": "lakefile.toml"}],
"name": "grovestdlib",

View File

@@ -3,16 +3,21 @@ Copyright (c) 2023 Mario Carneiro. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Mario Carneiro, Sebastian Ullrich
-/
import Lake.CLI.Main
module
import Lean.Environment
import Lean.ExtraModUses
/-! # `lake exe shake` command
import Lake.CLI.Main
import Lean.Parser.Module
import Lake.Load.Workspace
/-! # Shake: A Lean import minimizer
This command will check the current project (or a specified target module) and all dependencies for
unused imports. This works by looking at generated `.olean` files to deduce required imports and
ensuring that every import is used to contribute some constant or other elaboration dependency
recorded by `recordExtraModUse`. Because recompilation is not needed this is quite fast (about 8
seconds to check `Mathlib` and all dependencies).
recorded by `recordExtraModUse` and friends.
-/
/-- help string for the command line interface -/
@@ -28,13 +33,83 @@ Options:
--force
Skips the `lake build --no-build` sanity check
--keep-implied
Preserves existing imports that are implied by other imports and thus not technically needed
anymore
--keep-prefix
If an import `X` would be replaced in favor of a more specific import `X.Y...` it implies,
preserves the original import instead. More generally, prefers inserting `import X` even if it
was not part of the original imports as long as it was in the original transitive import closure
of the current module.
--keep-public
Preserves all `public` imports to avoid breaking changes for external downstream modules
--add-public
Adds new imports as `public` if they have been in the original public closure of that module.
In other words, public imports will not be removed from a module unless they are unused even
in the private scope, and those that are removed will be re-added as `public` in downstream
modules even if only needed in the private scope there. Unlike `--keep-public`, this may
introduce breaking changes but will still limit the number of inserted imports.
--explain
Gives constants explaining why each module is needed
--fix
Apply the suggested fixes directly. Make sure you have a clean checkout
before running this, so you can review the changes.
--gh-style
Outputs messages that can be parsed by `gh-problem-matcher-wrap`
Annotations:
The following annotations can be added to Lean files in order to configure the behavior of
`shake`. Only the substring `shake: ` directly followed by a directive is checked for, so multiple
directives can be mixed in one line such as `-- shake: keep-downstream, shake: keep-all`, and they
can be surrounded by arbitrary comments such as `-- shake: keep (metaprogram output dependency)`.
* `module -- shake: keep-downstream`:
Preserves this module in all (current) downstream modules, adding new imports of it if needed.
* `module -- shake: keep-all`:
Preserves all existing imports in this module as is. New imports now needed because of upstream
changes may still be added.
* `import X -- shake: keep`:
Preserves this specific import in the current module. The most common use case is to preserve a
public import that will be needed in downstream modules to make sense of the output of a
metaprogram defined in this module. For example, if a tactic is defined that may synthesize a
reference to a theorem when run, there is no way for `shake` to detect this by itself and the
module of that theorem should be publicly imported and annotated with `keep` in the tactic's
module.
```
public import X -- shake: keep (metaprogram output dependency)
...
elab \"my_tactic\" : tactic => do
... mkConst ``f -- `f`, defined in `X`, may appear in the output of this tactic
```
"
open Lean
/-- The parsed CLI arguments. See `help` for more information -/
structure Args where
help : Bool := false
keepImplied : Bool := false
keepPrefix : Bool := false
keepPublic : Bool := false
addPublic : Bool := false
force : Bool := false
githubStyle : Bool := false
explain : Bool := false
trace : Bool := false
fix : Bool := false
/-- `<MODULE>..`: the list of root modules to check -/
mods : Array Name := #[]
/-- We use `Nat` as a bitset for doing efficient set operations.
The bit indexes will usually be a module index. -/
structure Bitset where
@@ -88,7 +163,7 @@ def ofImport : Lean.Import → NeedsKind
end NeedsKind
/-- Logically, a map `NeedsKind → Bitset`. -/
/-- Logically, a map `NeedsKind → Set ModuleIdx`, or `Set Import`. -/
structure Needs where
pub : Bitset
priv : Bitset
@@ -124,6 +199,20 @@ def Needs.union (needs : Needs) (k : NeedsKind) (s : Bitset) : Needs :=
def Needs.sub (needs : Needs) (k : NeedsKind) (s : Bitset) : Needs :=
needs.modify k (fun s' => s' ^^^ (s' s))
instance : Union Needs where
union a b := {
pub := a.pub b.pub
priv := a.priv b.priv
metaPub := a.metaPub b.metaPub
metaPriv := a.metaPriv b.metaPriv }
/-- The list of edits that will be applied in `--fix`. `edits[i] = (removed, added)` where:
* If `j ∈ removed` then we want to delete module named `j` from the imports of `i`
* If `j ∈ added` then we want to add module index `j` to the imports of `i`.
-/
abbrev Edits := Std.HashMap Name (Array Import × Array Import)
/-- The main state of the checker, containing information on all loaded modules. -/
structure State where
env : Environment
@@ -143,6 +232,10 @@ structure State where
changes to upstream headers.
-/
transDepsOrig : Array Needs := #[]
/-- Modules that should always be preserved downstream. -/
preserve : Needs := default
/-- Edits to be applied to the module imports. -/
edits : Edits := {}
def State.mods (s : State) := s.env.header.moduleData
def State.modNames (s : State) := s.env.header.moduleNames
@@ -185,13 +278,38 @@ def addTransitiveImps (transImps : Needs) (imp : Import) (j : Nat) (impTransImps
transImps
def isDeclMeta' (env : Environment) (declName : Name) : Bool :=
-- Matchers are not compiled by themselves but inlined by the compiler, so there is no IR decl
-- to be tagged as `meta`.
-- TODO: It would be better to base the entire `meta` inference on the IR only and consider module
-- references from any other context as compatible with both phases.
let inferFor :=
if declName.isStr && (declName.getString!.startsWith "match_" || declName.getString! == "_unsafe_rec") then declName.getPrefix else declName
-- `isMarkedMeta` knows about non-defs such as `meta structure`, isDeclMeta knows about decls
-- implicitly marked meta
isMarkedMeta env inferFor || isDeclMeta env inferFor
/--
Given an `Expr` reference, returns the declaration name that should be considered the reference, if
any.
-/
def getDepConstName? (env : Environment) (ref : Name) : Option Name := do
-- Ignore references to reserved names, they can be re-generated in-place
guard <| !isReservedName env ref
-- `_simp_...` constants are similar, use base decl instead
return if ref.isStr && ref.getString!.startsWith "_simp_" then
ref.getPrefix
else
ref
/-- Calculates the needs for a given module `mod` from constants and recorded extra uses. -/
def calcNeeds (env : Environment) (i : ModuleIdx) : Needs := Id.run do
def calcNeeds (s : State) (i : ModuleIdx) : Needs := Id.run do
let env := s.env
let mut needs := default
for ci in env.header.moduleData[i]!.constants do
-- Added guard for cases like `structure` that are still exported even if private
let pubCI? := guard (!isPrivateName ci.name) *> (env.setExporting true).find? ci.name
let k := { isExported := pubCI?.isSome, isMeta := isMeta env ci.name }
let k := { isExported := pubCI?.isSome, isMeta := isDeclMeta' env ci.name }
needs := visitExpr k ci.type needs
if let some e := ci.value? (allowOpaque := true) then
-- type and value has identical visibility under `meta`
@@ -206,12 +324,19 @@ def calcNeeds (env : Environment) (i : ModuleIdx) : Needs := Id.run do
return needs
where
/-- Accumulate the results from expression `e` into `deps`. -/
visitExpr (k : NeedsKind) e deps :=
Lean.Expr.foldConsts e deps fun c deps => match env.getModuleIdxFor? c with
| some j =>
let k := { k with isMeta := k.isMeta && !isMeta env c }
if j != i then deps.union k {j} else deps
| _ => deps
visitExpr (k : NeedsKind) (e : Expr) (deps : Needs) : Needs :=
let env := s.env
Lean.Expr.foldConsts e deps fun c deps => Id.run do
let mut deps := deps
if let some c := getDepConstName? env c then
if let some j := env.getModuleIdxFor? c then
let k := { k with isMeta := k.isMeta && !isDeclMeta' env c }
if j != i then
deps := deps.union k {j}
for indMod in (indirectModUseExt.getState env)[c]?.getD #[] do
if s.transDeps[i]!.has k indMod then
deps := deps.union k {indMod}
return deps
/--
Calculates the same as `calcNeeds` but tracing each module to a use-def declaration pair or
@@ -223,7 +348,7 @@ def getExplanations (env : Environment) (i : ModuleIdx) :
for ci in env.header.moduleData[i]!.constants do
-- Added guard for cases like `structure` that are still exported even if private
let pubCI? := guard (!isPrivateName ci.name) *> (env.setExporting true).find? ci.name
let k := { isExported := pubCI?.isSome, isMeta := isMeta env ci.name }
let k := { isExported := pubCI?.isSome, isMeta := isDeclMeta' env ci.name }
deps := visitExpr k ci.name ci.type deps
if let some e := ci.value? (allowOpaque := true) then
let k := if k.isMeta then k else
@@ -239,18 +364,18 @@ def getExplanations (env : Environment) (i : ModuleIdx) :
where
/-- Accumulate the results from expression `e` into `deps`. -/
visitExpr (k : NeedsKind) name e deps :=
Lean.Expr.foldConsts e deps fun c deps => match env.getModuleIdxFor? c with
| some i =>
let k := { k with isMeta := k.isMeta && !isMeta env c }
if
if let some (some (name', _)) := deps[(i, k)]? then
decide (name.toString.length < name'.toString.length)
else true
then
deps.insert (i, k) (name, c)
else
deps
| _ => deps
Lean.Expr.foldConsts e deps fun c deps => Id.run do
let mut deps := deps
if let some c := getDepConstName? env c then
if let some j := env.getModuleIdxFor? c then
let k := { k with isMeta := k.isMeta && !isDeclMeta' env c }
if
if let some (some (name', _)) := deps[(j, k)]? then
decide (name.toString.length < name'.toString.length)
else true
then
deps := deps.insert (j, k) (name, c)
return deps
partial def initStateFromEnv (env : Environment) : State := Id.run do
let mut s := { env }
@@ -266,13 +391,6 @@ partial def initStateFromEnv (env : Environment) : State := Id.run do
s := { s with transDepsOrig := s.transDeps }
return s
/-- The list of edits that will be applied in `--fix`. `edits[i] = (removed, added)` where:
* If `j ∈ removed` then we want to delete module named `j` from the imports of `i`
* If `j ∈ added` then we want to add module index `j` to the imports of `i`.
-/
abbrev Edits := Std.HashMap Name (Array Import × Array Import)
/-- Register that we want to remove `tgt` from the imports of `src`. -/
def Edits.remove (ed : Edits) (src : Name) (tgt : Import) : Edits :=
match ed.get? src with
@@ -291,8 +409,8 @@ Returns `(path, inputCtx, imports, endPos)` where `imports` is the `Lean.Parser.
and `endPos` is the position of the end of the header.
-/
def parseHeaderFromString (text path : String) :
IO (System.FilePath × Parser.InputContext ×
TSyntax ``Parser.Module.header × String.Pos.Raw) := do
IO (System.FilePath × (ictx : Parser.InputContext) ×
TSyntax ``Parser.Module.header × String.Pos ictx.fileMap.source) := do
let inputCtx := Parser.mkInputContext text path
let (header, parserState, msgs) Parser.parseHeader inputCtx
if !msgs.toList.isEmpty then -- skip this file if there are parse errors
@@ -300,8 +418,8 @@ def parseHeaderFromString (text path : String) :
throw <| .userError "parse errors in file"
-- the insertion point for `add` is the first newline after the imports
let insertion := header.raw.getTailPos?.getD parserState.pos
let insertion := text.findAux (· == '\n') text.rawEndPos insertion + '\n'
pure (path, inputCtx, header, insertion)
let insertion := inputCtx.fileMap.source.pos! insertion |>.find (· == '\n') |>.next!
pure path, inputCtx, header, insertion
/-- Parse a source file to extract the location of the import lines, for edits and error messages.
@@ -309,8 +427,8 @@ Returns `(path, inputCtx, imports, endPos)` where `imports` is the `Lean.Parser.
and `endPos` is the position of the end of the header.
-/
def parseHeader (srcSearchPath : SearchPath) (mod : Name) :
IO (System.FilePath × Parser.InputContext ×
TSyntax ``Parser.Module.header × String.Pos.Raw) := do
IO (System.FilePath × (ictx : Parser.InputContext) ×
TSyntax ``Parser.Module.header × String.Pos ictx.fileMap.source) := do
-- Parse the input file
let some path srcSearchPath.findModuleWithExt "lean" mod
| throw <| .userError s!"error: failed to find source file for {mod}"
@@ -320,7 +438,7 @@ def parseHeader (srcSearchPath : SearchPath) (mod : Name) :
def decodeHeader : TSyntax ``Parser.Module.header Option (TSyntax `module) × Option (TSyntax `prelude) × TSyntaxArray ``Parser.Module.import
| `(Parser.Module.header| $[module%$moduleTk?]? $[prelude%$preludeTk?]? $imports*) =>
(moduleTk?.map .mk, preludeTk?.map .mk, imports)
| _ => unreachable!
| stx => panic! s!"unexpected header syntax {stx}"
def decodeImport : TSyntax ``Parser.Module.import Import
| `(Parser.Module.import| $[public%$pubTk?]? $[meta%$metaTk?]? import $[all%$allTk?]? $id) =>
@@ -329,73 +447,174 @@ def decodeImport : TSyntax ``Parser.Module.import → Import
/-- Analyze and report issues from module `i`. Arguments:
* `pkg`: the first component of the module name
* `srcSearchPath`: Used to find the path for error reporting purposes
* `i`: the module index
* `needs`: the module's calculated needs
* `pinned`: dependencies that should be preserved even if unused
* `edits`: accumulates the list of edits to apply if `--fix` is true
* `addOnly`: if true, only add missing imports, do not remove unused ones
-/
def visitModule (srcSearchPath : SearchPath)
(i : Nat) (needs : Needs) (preserve : Needs) (edits : Edits) (headerStx : TSyntax ``Parser.Module.header)
(addOnly := false) (githubStyle := false) (explain := false) : StateT State IO Edits := do
def visitModule (pkg : Name) (srcSearchPath : SearchPath)
(i : Nat) (needs : Needs) (headerStx : TSyntax ``Parser.Module.header) (args : Args)
(addOnly := false) : StateT State IO Unit := do
if isExtraRevModUse ( get).env i then
modify fun s => { s with preserve := s.preserve.union (if args.addPublic then .pub else .priv) {i} }
if args.trace then
IO.eprintln s!"Preserving `{(← get).modNames[i]!}` because of recorded extra rev use"
-- only process modules in the selected package
-- TODO: should be after `keep-downstream` but core headers are not found yet?
if !pkg.isPrefixOf ( get).modNames[i]! then
return
let (module?, prelude?, imports) := decodeHeader headerStx
if module?.any (·.raw.getTrailing?.any (·.toString.contains "shake: keep-downstream")) then
modify fun s => { s with preserve := s.preserve.union (if args.addPublic then .pub else .priv) {i} }
let s get
-- Do transitive reduction of `needs` in `deps`.
let addOnly := addOnly || module?.any (·.raw.getTrailing?.any (·.toString.contains "shake: keep-all"))
let mut deps := needs
let (_, prelude?, imports) := decodeHeader headerStx
-- Add additional preserved imports
for impStx in imports do
let imp := decodeImport impStx
let j := s.env.getModuleIdx? imp.module |>.get!
let k := NeedsKind.ofImport imp
if addOnly ||
args.keepPublic && imp.isExported ||
impStx.raw.getTrailing?.any (·.toString.contains "shake: keep") then
deps := deps.union k {j}
if args.trace then
IO.eprintln s!"Adding `{imp}` as additional dependency"
for j in [0:s.mods.size] do
for k in NeedsKind.all do
-- Remove `meta` while preserving, no use-case for preserving `meta` so far.
-- Downgrade to private unless `--add-public` is used.
if s.transDepsOrig[i]!.has k j &&
(s.preserve.has { k with isMeta := false, isExported := false } j ||
s.preserve.has { k with isMeta := false, isExported := true } j) then
deps := deps.union { k with isMeta := false, isExported := k.isExported && args.addPublic } {j}
-- Do transitive reduction of `needs` in `deps`.
if !addOnly then
for j in [0:s.mods.size] do
let transDeps := s.transDeps[j]!
for k in NeedsKind.all do
if deps.has k j then
let transDeps := addTransitiveImps .empty { k with module := .anonymous } j transDeps
for k' in NeedsKind.all do
deps := deps.sub k' (transDeps.sub k' {j} |>.get k')
if prelude?.isNone then
deps := deps.union .pub {s.env.getModuleIdx? `Init |>.get!}
for imp in imports do
if addOnly || imp.raw.getTrailing?.any (·.toString.toSlice.contains "shake: keep") then
let imp := decodeImport imp
let j := s.env.getModuleIdx? imp.module |>.get!
let k := NeedsKind.ofImport imp
deps := deps.union k {j}
for j in [0:s.mods.size] do
let transDeps := s.transDeps[j]!
for k in NeedsKind.all do
if s.transDepsOrig[i]!.has k j && preserve.has k j then
deps := deps.union k {j}
if deps.has k j then
let transDeps := addTransitiveImps .empty { k with module := .anonymous } j transDeps
for k' in NeedsKind.all do
deps := deps.sub k' (transDeps.sub k' {j} |>.get k')
-- Any import which is not in `transDeps` was unused.
-- Also accumulate `newDeps` which is the transitive closure of the remaining imports
let mut toRemove : Array Import := #[]
let mut newDeps := Needs.empty
-- Accumulate `transDeps` which is the non-reflexive transitive closure of the still-live imports
let mut transDeps := Needs.empty
let mut alwaysAdd : Array Import := #[] -- to be added even if implied by other imports
for imp in s.mods[i]!.imports do
let j := s.env.getModuleIdx? imp.module |>.get!
if
-- skip folder-nested imports
s.modNames[i]!.isPrefixOf imp.module ||
imp.importAll then
newDeps := addTransitiveImps newDeps imp j s.transDeps[j]!
else
let k := NeedsKind.ofImport imp
-- A private import should also be removed if the public version is needed
if !deps.has k j || !k.isExported && deps.has { k with isExported := true } j then
toRemove := toRemove.push imp
else
newDeps := addTransitiveImps newDeps imp j s.transDeps[j]!
let k := NeedsKind.ofImport imp
if deps.has k j || imp.importAll then
transDeps := addTransitiveImps transDeps imp j s.transDeps[j]!
deps := deps.union k {j}
-- skip folder-nested `public (meta)? import`s but remove `meta`
else if s.modNames[i]!.isPrefixOf imp.module then
let imp := { imp with isMeta := false }
let k := { k with isMeta := false }
if args.trace then
IO.eprintln s!"`{imp}` is preserved as folder-nested import"
transDeps := addTransitiveImps transDeps imp j s.transDeps[j]!
deps := deps.union k {j}
if !s.mods[i]!.imports.contains imp then
alwaysAdd := alwaysAdd.push imp
-- If `newDeps` does not cover `deps`, then we have to add back some imports until it does.
-- If `transDeps` does not cover `deps`, then we have to add back some imports until it does.
-- To minimize new imports we pick only new imports which are not transitively implied by
-- another new import
-- another new import, so we visit module indices in descending order.
let mut keptPrefix := false
let mut newTransDeps := transDeps
let mut toAdd : Array Import := #[]
for j in [0:s.mods.size] do
for j in (0...s.mods.size).toArray.reverse do
for k in NeedsKind.all do
if deps.has k j && !newDeps.has k j && !newDeps.has { k with isExported := true } j then
let imp := { k with module := s.modNames[j]! }
toAdd := toAdd.push imp
newDeps := addTransitiveImps newDeps imp j s.transDeps[j]!
if deps.has k j && !newTransDeps.has k j && !newTransDeps.has { k with isExported := true } j then
-- `add-public/keep-prefix` may change the import and even module we're considering
let mut k := k
let mut imp : Import := { k with module := s.modNames[j]! }
let mut j := j
if args.trace then
IO.eprintln s!"`{imp}` is needed"
if args.addPublic && !k.isExported &&
-- also add as public if previously `public meta`, which could be from automatic porting
(s.transDepsOrig[i]!.has { k with isExported := true } j || s.transDepsOrig[i]!.has { k with isExported := true, isMeta := true } j) then
k := { k with isExported := true }
imp := { imp with isExported := true }
if args.trace then
IO.eprintln s!"* upgrading to `{imp}` because of `--add-public`"
if args.keepPrefix then
let rec tryPrefix : Name Option ModuleIdx
| .str p _ => tryPrefix p <|> (do
let j' s.env.getModuleIdx? p
-- `j'` must be reachable from `i` (allow downgrading from `meta`)
guard <| s.transDepsOrig[i]!.has k j' || s.transDepsOrig[i]!.has { k with isMeta := true } j'
let j'transDeps := addTransitiveImps .empty p j' s.transDeps[j']!
-- `j` must be reachable from `j'` (now downgrading must be done in the other direction)
guard <| j'transDeps.has k j || j'transDeps.has { k with isMeta := false } j
return j')
| _ => none
if let some j' := tryPrefix imp.module then
imp := { imp with module := s.modNames[j']! }
j := j'
keptPrefix := true
if args.trace then
IO.eprintln s!"* upgrading to `{imp}` because of `--keep-prefix`"
if !s.mods[i]!.imports.contains imp then
toAdd := toAdd.push imp
deps := deps.union k {j}
newTransDeps := addTransitiveImps newTransDeps imp j s.transDeps[j]!
if keptPrefix then
-- if an import was replaced by `--keep-prefix`, we did not necessarily visit the modules in
-- dependency order anymore and so we have to redo the transitive closure checking
newTransDeps := transDeps
for j in (0...s.mods.size).toArray.reverse do
for k in NeedsKind.all do
if deps.has k j then
let mut imp : Import := { k with module := s.modNames[j]! }
if toAdd.contains imp && (newTransDeps.has k j || newTransDeps.has { k with isExported := true } j) then
if args.trace then
IO.eprintln s!"Removing `{imp}` from imports to be added because it is now implied"
toAdd := toAdd.erase imp
deps := deps.sub k {j}
else
newTransDeps := addTransitiveImps newTransDeps imp j s.transDeps[j]!
-- now that `toAdd` filtering is done, add `alwaysAdd`
toAdd := alwaysAdd ++ toAdd
-- Any import which is still not in `deps` was unused
let mut toRemove : Array Import := #[]
for imp in s.mods[i]!.imports do
let j := s.env.getModuleIdx? imp.module |>.get!
let k := NeedsKind.ofImport imp
if args.keepImplied && newTransDeps.has k j then
if args.trace && !deps.has k j then
IO.eprintln s!"`{imp}` is implied by other imports"
else if !deps.has k j then
if args.trace then
IO.eprintln s!"`{imp}` is now unused"
toRemove := toRemove.push imp
-- A private import should also be removed if the public version has been added
else if !k.isExported && !imp.importAll && newTransDeps.has { k with isExported := true } j then
if args.trace then
IO.eprintln s!"`{imp}` is already covered by `{ { imp with isExported := true } }`"
toRemove := toRemove.push imp
-- mark and report the removals
let mut edits := toRemove.foldl (init := edits) fun edits imp =>
edits.remove s.modNames[i]! imp
modify fun s => { s with
edits := toRemove.foldl (init := s.edits) fun edits imp =>
edits.remove s.modNames[i]! imp }
if !toAdd.isEmpty || !toRemove.isEmpty || explain then
if !toAdd.isEmpty || !toRemove.isEmpty || args.explain then
if let some path srcSearchPath.findModuleWithExt "lean" s.modNames[i]! then
println! "{path}:"
else
@@ -404,9 +623,9 @@ def visitModule (srcSearchPath : SearchPath)
if !toRemove.isEmpty then
println! " remove {toRemove}"
if githubStyle then
if args.githubStyle then
try
let (path, inputCtx, stx, endHeader) parseHeader srcSearchPath s.modNames[i]!
let path, inputCtx, stx, endHeader parseHeader srcSearchPath s.modNames[i]!
let (_, _, imports) := decodeHeader stx
for stx in imports do
if toRemove.any fun imp => imp == decodeImport stx then
@@ -415,14 +634,15 @@ def visitModule (srcSearchPath : SearchPath)
(use `lake exe shake --fix` to fix this, or `lake exe shake --update` to ignore)"
if !toAdd.isEmpty then
-- we put the insert message on the beginning of the last import line
let pos := inputCtx.fileMap.toPosition endHeader
let pos := inputCtx.fileMap.toPosition endHeader.offset
println! "{path}:{pos.line-1}:1: warning: \
add {toAdd} instead"
catch _ => pure ()
-- mark and report the additions
edits := toAdd.foldl (init := edits) fun edits imp =>
edits.add s.modNames[i]! imp
modify fun s => { s with
edits := toAdd.foldl (init := s.edits) fun edits imp =>
edits.add s.modNames[i]! imp }
if !toAdd.isEmpty then
println! " add {toAdd}"
@@ -437,14 +657,15 @@ def visitModule (srcSearchPath : SearchPath)
let j := s.env.getModuleIdx? imp.module |>.get!
newTransDepsI := addTransitiveImps newTransDepsI imp j s.transDeps[j]!
set { s with transDeps := s.transDeps.set! i newTransDepsI }
modify fun s => { s with transDeps := s.transDeps.set! i newTransDepsI }
if explain then
if args.explain then
let explanation := getExplanations s.env i
let sanitize n := if n.hasMacroScopes then (sanitizeName n).run' { options := {} } else n
let run (imp : Import) := do
let j := s.env.getModuleIdx? imp.module |>.get!
if let some exp? := explanation[(j, NeedsKind.ofImport imp)]? then
let mut k := NeedsKind.ofImport imp
if let some exp? := explanation[(j, k)]? <|> guard args.addPublic *> explanation[(j, { k with isExported := false})]? then
println! " note: `{imp}` required"
if let some (n, c) := exp? then
println! " because `{sanitize n}` refers to `{sanitize c}`"
@@ -455,8 +676,6 @@ def visitModule (srcSearchPath : SearchPath)
run j
for i in toAdd do run i
return edits
/-- Convert a list of module names to a bitset of module indexes -/
def toBitset (s : State) (ns : List Name) : Bitset :=
ns.foldl (init := ) fun c name =>
@@ -464,40 +683,26 @@ def toBitset (s : State) (ns : List Name) : Bitset :=
| some i => c {i}
| none => c
/-- The parsed CLI arguments. See `help` for more information -/
structure Args where
/-- `--help`: shows the help -/
help : Bool := false
/-- `--force`: skips the `lake build --no-build` sanity check -/
force : Bool := false
/-- `--gh-style`: output messages that can be parsed by `gh-problem-matcher-wrap` -/
githubStyle : Bool := false
/-- `--explain`: give constants explaining why each module is needed -/
explain : Bool := false
/-- `--fix`: apply the fixes directly -/
fix : Bool := false
/-- `<MODULE>..`: the list of root modules to check -/
mods : Array Name := #[]
local instance : Ord Import where
compare a b :=
if a.isExported && !b.isExported then
Ordering.lt
else if !a.isExported && b.isExported then
Ordering.gt
else
a.module.cmp b.module
compare :=
let _ := @lexOrd
compareOn fun imp => (!imp.isExported, imp.module.toString)
/-- The main entry point. See `help` for more information on arguments. -/
def main (args : List String) : IO UInt32 := do
public def main (args : List String) : IO UInt32 := do
initSearchPath ( findSysroot)
-- Parse the arguments
let rec parseArgs (args : Args) : List String Args
| [] => args
| "--help" :: rest => parseArgs { args with help := true } rest
| "--keep-implied" :: rest => parseArgs { args with keepImplied := true } rest
| "--keep-prefix" :: rest => parseArgs { args with keepPrefix := true } rest
| "--keep-public" :: rest => parseArgs { args with keepPublic := true } rest
| "--add-public" :: rest => parseArgs { args with addPublic := true } rest
| "--force" :: rest => parseArgs { args with force := true } rest
| "--fix" :: rest => parseArgs { args with fix := true } rest
| "--explain" :: rest => parseArgs { args with explain := true } rest
| "--trace" :: rest => parseArgs { args with trace := true } rest
| "--gh-style" :: rest => parseArgs { args with githubStyle := true } rest
| "--" :: rest => { args with mods := args.mods ++ rest.map (·.toName) }
| other :: rest => parseArgs { args with mods := args.mods.push other.toName } rest
@@ -540,69 +745,69 @@ def main (args : List String) : IO UInt32 := do
let imps := mods.map ({ module := · })
let (_, s) importModulesCore imps (isExported := true) |>.run
let s := s.markAllExported
let env finalizeImport s (isModule := true) imps {} (leakEnv := false) (loadExts := false)
let mut env finalizeImport s (isModule := true) imps {} (leakEnv := false) (loadExts := false)
-- the one env ext we want to initialize
let is := indirectModUseExt.toEnvExtension.getState env
let newState indirectModUseExt.addImportedFn is.importedEntries { env := env, opts := {} }
env := indirectModUseExt.toEnvExtension.setState (asyncMode := .sync) env { is with state := newState }
StateT.run' (s := initStateFromEnv env) do
let s get
-- Parse the config file
-- Run the calculation of the `needs` array in parallel
let needs := s.mods.mapIdx fun i _ =>
Task.spawn fun _ => calcNeeds s.env i
Task.spawn fun _ => calcNeeds s i
-- Parse headers in parallel
let headers s.mods.mapIdxM fun i _ =>
BaseIO.asTask (parseHeader srcSearchPath s.modNames[i]! |>.toBaseIO)
if !pkg.isPrefixOf s.modNames[i]! then
pure <| Task.pure <| .ok default, default, default, default
else
BaseIO.asTask (parseHeader srcSearchPath s.modNames[i]! |>.toBaseIO)
if args.fix then
println! "The following changes will be made automatically:"
-- Check all selected modules
let mut edits : Edits :=
let mut revNeeds : Needs := default
for i in [0:s.mods.size], t in needs, header in headers do
match header.get with
| .ok (_, _, stx, _) =>
edits visitModule (addOnly := !pkg.isPrefixOf s.modNames[i]!)
srcSearchPath i t.get revNeeds edits stx args.githubStyle args.explain
if isExtraRevModUse s.env i then
revNeeds := revNeeds.union .priv {i}
| .ok _, _, stx, _ =>
visitModule pkg srcSearchPath i t.get stx args
| .error e =>
println! e.toString
if !args.fix then
-- return error if any issues were found
return if edits.isEmpty then 0 else 1
return if ( get).edits.isEmpty then 0 else 1
-- Apply the edits to existing files
let mut count := 0
for mod in s.modNames, header? in headers do
let some (remove, add) := edits[mod]? | continue
let some (remove, add) := ( get).edits[mod]? | continue
let add : Array Import := add.qsortOrd
-- Parse the input file
let .ok (path, inputCtx, stx, insertion) := header?.get | continue
let .ok path, inputCtx, stx, insertion := header?.get | continue
let (_, _, imports) := decodeHeader stx
let text := inputCtx.fileMap.source
-- Calculate the edit result
let mut pos : String.Pos.Raw := 0
let mut pos : String.Pos text := text.startPos
let mut out : String := ""
let mut seen : Std.HashSet Import := {}
for stx in imports do
let mod := decodeImport stx
if remove.contains mod || seen.contains mod then
out := out ++ String.Pos.Raw.extract text pos stx.raw.getPos?.get!
out := out ++ text.extract pos (text.pos! stx.raw.getPos?.get!)
-- We use the end position of the syntax, but include whitespace up to the first newline
pos := text.findAux (· == '\n') text.rawEndPos stx.raw.getTailPos?.get! + '\n'
pos := text.pos! stx.raw.getTailPos?.get! |>.find '\n' |>.next!
seen := seen.insert mod
out := out ++ String.Pos.Raw.extract text pos insertion
out := out ++ text.extract pos insertion
for mod in add do
if !seen.contains mod then
seen := seen.insert mod
out := out ++ s!"{mod}\n"
out := out ++ String.Pos.Raw.extract text insertion text.rawEndPos
out := out ++ text.extract insertion text.endPos
IO.FS.writeFile path out
count := count + 1

441
script/build_artifact.py Executable file
View File

@@ -0,0 +1,441 @@
#!/usr/bin/env python3
"""
build_artifact.py: Download pre-built CI artifacts for a Lean commit.
Usage:
build_artifact.py # Download artifact for current HEAD
build_artifact.py --sha abc1234 # Download artifact for specific commit
build_artifact.py --clear-cache # Clear artifact cache
This script downloads pre-built binaries from GitHub Actions CI runs,
which is much faster than building from source (~30s vs 2-5min).
Artifacts are cached in ~/.cache/lean_build_artifact/ for reuse.
"""
import argparse
import json
import os
import platform
import shutil
import subprocess
import sys
import urllib.request
import urllib.error
from pathlib import Path
from typing import Optional
# Constants
GITHUB_API_BASE = "https://api.github.com"
LEAN4_REPO = "leanprover/lean4"
# CI artifact cache
CACHE_DIR = Path.home() / '.cache' / 'lean_build_artifact'
ARTIFACT_CACHE = CACHE_DIR
# Sentinel value indicating CI failed (don't bother building locally)
CI_FAILED = object()
# ANSI colors for terminal output
class Colors:
RED = '\033[91m'
GREEN = '\033[92m'
YELLOW = '\033[93m'
BLUE = '\033[94m'
BOLD = '\033[1m'
RESET = '\033[0m'
def color(text: str, c: str) -> str:
"""Apply color to text if stdout is a tty."""
if sys.stdout.isatty():
return f"{c}{text}{Colors.RESET}"
return text
def error(msg: str) -> None:
"""Print error message and exit."""
print(color(f"Error: {msg}", Colors.RED), file=sys.stderr)
sys.exit(1)
def warn(msg: str) -> None:
"""Print warning message."""
print(color(f"Warning: {msg}", Colors.YELLOW), file=sys.stderr)
def info(msg: str) -> None:
"""Print info message."""
print(color(msg, Colors.BLUE), file=sys.stderr)
def success(msg: str) -> None:
"""Print success message."""
print(color(msg, Colors.GREEN), file=sys.stderr)
# -----------------------------------------------------------------------------
# Platform detection
# -----------------------------------------------------------------------------
def get_artifact_name() -> Optional[str]:
"""Get CI artifact name for current platform."""
system = platform.system()
machine = platform.machine()
if system == 'Darwin':
if machine == 'arm64':
return 'build-macOS aarch64'
return 'build-macOS' # Intel
elif system == 'Linux':
if machine == 'aarch64':
return 'build-Linux aarch64'
return 'build-Linux release'
# Windows not supported for CI artifact download
return None
# -----------------------------------------------------------------------------
# GitHub API helpers
# -----------------------------------------------------------------------------
_github_token_warning_shown = False
def get_github_token() -> Optional[str]:
"""Get GitHub token from environment or gh CLI."""
global _github_token_warning_shown
# Check environment variable first
token = os.environ.get('GITHUB_TOKEN')
if token:
return token
# Try to get token from gh CLI
try:
result = subprocess.run(
['gh', 'auth', 'token'],
capture_output=True,
text=True,
timeout=5
)
if result.returncode == 0 and result.stdout.strip():
return result.stdout.strip()
except (FileNotFoundError, subprocess.TimeoutExpired):
pass
# Warn once if no token available
if not _github_token_warning_shown:
_github_token_warning_shown = True
warn("No GitHub authentication found. API rate limits may apply.")
warn("Run 'gh auth login' or set GITHUB_TOKEN to avoid rate limiting.")
return None
def github_api_request(url: str) -> dict:
"""Make a GitHub API request and return JSON response."""
headers = {
'Accept': 'application/vnd.github.v3+json',
'User-Agent': 'build-artifact'
}
token = get_github_token()
if token:
headers['Authorization'] = f'token {token}'
req = urllib.request.Request(url, headers=headers)
try:
with urllib.request.urlopen(req, timeout=30) as response:
return json.loads(response.read().decode())
except urllib.error.HTTPError as e:
if e.code == 403:
error(f"GitHub API rate limit exceeded. Set GITHUB_TOKEN environment variable to increase limit.")
elif e.code == 404:
error(f"GitHub resource not found: {url}")
else:
error(f"GitHub API error: {e.code} {e.reason}")
except urllib.error.URLError as e:
error(f"Network error accessing GitHub API: {e.reason}")
# -----------------------------------------------------------------------------
# CI artifact cache functions
# -----------------------------------------------------------------------------
def get_cache_path(sha: str) -> Path:
"""Get cache directory for a commit's artifact."""
return ARTIFACT_CACHE / sha[:12]
def is_cached(sha: str) -> bool:
"""Check if artifact for this commit is already cached and valid."""
cache_path = get_cache_path(sha)
return cache_path.exists() and (cache_path / 'bin' / 'lean').exists()
def check_zstd_support() -> bool:
"""Check if tar supports zstd compression."""
try:
result = subprocess.run(
['tar', '--zstd', '--version'],
capture_output=True,
timeout=5
)
return result.returncode == 0
except (subprocess.TimeoutExpired, FileNotFoundError):
return False
def check_gh_available() -> bool:
"""Check if gh CLI is available and authenticated."""
try:
result = subprocess.run(
['gh', 'auth', 'status'],
capture_output=True,
timeout=10
)
return result.returncode == 0
except (subprocess.TimeoutExpired, FileNotFoundError):
return False
def download_ci_artifact(sha: str, quiet: bool = False):
"""
Try to download CI artifact for a commit.
Returns:
- Path to extracted toolchain directory if available
- CI_FAILED sentinel if CI run failed (don't bother building locally)
- None if no artifact available but local build might work
"""
# Check cache first
if is_cached(sha):
return get_cache_path(sha)
artifact_name = get_artifact_name()
if artifact_name is None:
return None # Unsupported platform
cache_path = get_cache_path(sha)
try:
# Query for CI workflow run for this commit, including status
# Note: Query parameters must be in the URL for GET requests
result = subprocess.run(
['gh', 'api', f'repos/{LEAN4_REPO}/actions/runs?head_sha={sha}&per_page=100',
'--jq', r'.workflow_runs[] | select(.name == "CI") | "\(.id) \(.conclusion // "null")"'],
capture_output=True,
text=True,
timeout=30
)
if result.returncode != 0 or not result.stdout.strip():
return None # No CI run found (old commit?)
# Parse "run_id conclusion" format
line = result.stdout.strip().split('\n')[0]
parts = line.split(' ', 1)
run_id = parts[0]
conclusion = parts[1] if len(parts) > 1 else "null"
# Check if the desired artifact exists for this run
result = subprocess.run(
['gh', 'api', f'repos/{LEAN4_REPO}/actions/runs/{run_id}/artifacts',
'--jq', f'.artifacts[] | select(.name == "{artifact_name}") | .id'],
capture_output=True,
text=True,
timeout=30
)
if result.returncode != 0 or not result.stdout.strip():
# No artifact available
# If CI failed and no artifact, the build itself likely failed - skip
if conclusion == "failure":
return CI_FAILED
# Otherwise (in progress, expired, etc.) - fall back to local build
return None
# Download artifact
cache_path.mkdir(parents=True, exist_ok=True)
if not quiet:
print("downloading CI artifact... ", end='', flush=True)
result = subprocess.run(
['gh', 'run', 'download', run_id,
'-n', artifact_name,
'-R', LEAN4_REPO,
'-D', str(cache_path)],
capture_output=True,
text=True,
timeout=600 # 10 minutes for large downloads
)
if result.returncode != 0:
shutil.rmtree(cache_path, ignore_errors=True)
return None
# Extract tar.zst - find the file (name varies by platform/version)
tar_files = list(cache_path.glob('*.tar.zst'))
if not tar_files:
shutil.rmtree(cache_path, ignore_errors=True)
return None
tar_file = tar_files[0]
if not quiet:
print("extracting... ", end='', flush=True)
result = subprocess.run(
['tar', '--zstd', '-xf', tar_file.name],
cwd=cache_path,
capture_output=True,
timeout=300
)
if result.returncode != 0:
shutil.rmtree(cache_path, ignore_errors=True)
return None
# Move contents up from lean-VERSION-PLATFORM/ to cache_path/
# The extracted directory name varies (e.g., lean-4.15.0-linux, lean-4.15.0-darwin_aarch64)
extracted_dirs = [d for d in cache_path.iterdir() if d.is_dir() and d.name.startswith('lean-')]
if extracted_dirs:
extracted = extracted_dirs[0]
for item in extracted.iterdir():
dest = cache_path / item.name
if dest.exists():
if dest.is_dir():
shutil.rmtree(dest)
else:
dest.unlink()
shutil.move(str(item), str(cache_path / item.name))
extracted.rmdir()
# Clean up tar file
tar_file.unlink()
# Verify the extraction worked
if not (cache_path / 'bin' / 'lean').exists():
shutil.rmtree(cache_path, ignore_errors=True)
return None
return cache_path
except (subprocess.TimeoutExpired, FileNotFoundError):
shutil.rmtree(cache_path, ignore_errors=True)
return None
# -----------------------------------------------------------------------------
# Git helpers
# -----------------------------------------------------------------------------
def get_current_commit() -> str:
"""Get the current git HEAD commit SHA."""
try:
result = subprocess.run(
['git', 'rev-parse', 'HEAD'],
capture_output=True,
text=True,
timeout=5
)
if result.returncode == 0:
return result.stdout.strip()
error(f"Failed to get current commit: {result.stderr.strip()}")
except subprocess.TimeoutExpired:
error("Timeout getting current commit")
except FileNotFoundError:
error("git not found")
def resolve_sha(short_sha: str) -> str:
"""Resolve a (possibly short) SHA to full 40-character SHA using git rev-parse."""
if len(short_sha) == 40:
return short_sha
try:
result = subprocess.run(
['git', 'rev-parse', short_sha],
capture_output=True,
text=True,
timeout=5
)
if result.returncode == 0:
full_sha = result.stdout.strip()
if len(full_sha) == 40:
return full_sha
error(f"Cannot resolve SHA '{short_sha}': {result.stderr.strip() or 'not found in repository'}")
except subprocess.TimeoutExpired:
error(f"Timeout resolving SHA '{short_sha}'")
except FileNotFoundError:
error("git not found - required for SHA resolution")
# -----------------------------------------------------------------------------
# Main
# -----------------------------------------------------------------------------
def main():
parser = argparse.ArgumentParser(
description='Download pre-built CI artifacts for a Lean commit.',
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""
This script downloads pre-built binaries from GitHub Actions CI runs,
which is much faster than building from source (~30s vs 2-5min).
Artifacts are cached in ~/.cache/lean_build_artifact/ for reuse.
Examples:
build_artifact.py # Download for current HEAD
build_artifact.py --sha abc1234 # Download for specific commit
build_artifact.py --clear-cache # Clear cache to free disk space
"""
)
parser.add_argument('--sha', metavar='SHA',
help='Commit SHA to download artifact for (default: current HEAD)')
parser.add_argument('--clear-cache', action='store_true',
help='Clear artifact cache and exit')
parser.add_argument('--quiet', '-q', action='store_true',
help='Suppress progress messages (still prints result path)')
args = parser.parse_args()
# Handle cache clearing
if args.clear_cache:
if ARTIFACT_CACHE.exists():
size = sum(f.stat().st_size for f in ARTIFACT_CACHE.rglob('*') if f.is_file())
shutil.rmtree(ARTIFACT_CACHE)
info(f"Cleared cache at {ARTIFACT_CACHE} ({size / 1024 / 1024:.1f} MB)")
else:
info(f"Cache directory does not exist: {ARTIFACT_CACHE}")
return
# Get commit SHA
if args.sha:
sha = resolve_sha(args.sha)
else:
sha = get_current_commit()
if not args.quiet:
info(f"Commit: {sha[:12]}")
# Check prerequisites
if not check_gh_available():
error("gh CLI not available or not authenticated. Run 'gh auth login' first.")
if not check_zstd_support():
error("tar does not support zstd compression. Install zstd or a newer tar.")
artifact_name = get_artifact_name()
if artifact_name is None:
error(f"No CI artifacts available for this platform ({platform.system()} {platform.machine()})")
if not args.quiet:
info(f"Platform: {artifact_name}")
# Check cache
if is_cached(sha):
path = get_cache_path(sha)
if not args.quiet:
success("Using cached artifact")
print(path)
return
# Download artifact
result = download_ci_artifact(sha, quiet=args.quiet)
if result is CI_FAILED:
if not args.quiet:
print() # End the "downloading..." line
error(f"CI build failed for commit {sha[:12]}")
elif result is None:
if not args.quiet:
print() # End the "downloading..." line
error(f"No CI artifact available for commit {sha[:12]}")
else:
if not args.quiet:
print(color("done", Colors.GREEN))
print(result)
if __name__ == '__main__':
main()

View File

@@ -7,3 +7,5 @@ root = "Modulize"
[[lean_exe]]
name = "shake"
root = "Shake"
# needed by `Lake.loadWorkspace`
supportInterpreter = true

1290
script/lean-bisect Executable file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,307 @@
/-
Copyright Strata Contributors
SPDX-License-Identifier: Apache-2.0 OR MIT
-/
namespace Strata
namespace Python
/-
Parser and translator for some basic regular expression patterns supported by
Python's `re` library
Ref.: https://docs.python.org/3/library/re.html
Also see
https://github.com/python/cpython/blob/759a048d4bea522fda2fe929be0fba1650c62b0e/Lib/re/_parser.py
for a reference implementation.
-/
-------------------------------------------------------------------------------
inductive ParseError where
/--
`patternError` is raised when Python's `re.patternError` exception is
raised.
[Reference: Python's re exceptions](https://docs.python.org/3/library/re.html#exceptions):
"Exception raised when a string passed to one of the functions here is not a
valid regular expression (for example, it might contain unmatched
parentheses) or when some other error occurs during compilation or matching.
It is never an error if a string contains no match for a pattern."
-/
| patternError (message : String) (pattern : String) (pos : String.Pos.Raw)
/--
`unimplemented` is raised whenever we don't support some regex operations
(e.g., lookahead assertions).
-/
| unimplemented (message : String) (pattern : String) (pos : String.Pos.Raw)
deriving Repr
def ParseError.toString : ParseError String
| .patternError msg pat pos => s!"Pattern error at position {pos.byteIdx}: {msg} in pattern '{pat}'"
| .unimplemented msg pat pos => s!"Unimplemented at position {pos.byteIdx}: {msg} in pattern '{pat}'"
instance : ToString ParseError where
toString := ParseError.toString
-------------------------------------------------------------------------------
/--
Regular Expression Nodes
-/
inductive RegexAST where
/-- Single literal character: `a` -/
| char : Char RegexAST
/-- Character range: `[a-z]` -/
| range : Char Char RegexAST
/-- Alternation: `a|b` -/
| union : RegexAST RegexAST RegexAST
/-- Concatenation: `ab` -/
| concat : RegexAST RegexAST RegexAST
/-- Any character: `.` -/
| anychar : RegexAST
/-- Zero or more: `a*` -/
| star : RegexAST RegexAST
/-- One or more: `a+` -/
| plus : RegexAST RegexAST
/-- Zero or one: `a?` -/
| optional : RegexAST RegexAST
/-- Bounded repetition: `a{n,m}` -/
| loop : RegexAST Nat Nat RegexAST
/-- Start of string: `^` -/
| anchor_start : RegexAST
/-- End of string: `$` -/
| anchor_end : RegexAST
/-- Grouping: `(abc)` -/
| group : RegexAST RegexAST
/-- Empty string: `()` or `""` -/
| empty : RegexAST
/-- Complement: `[^a-z]` -/
| complement : RegexAST RegexAST
deriving Inhabited, Repr
-------------------------------------------------------------------------------
/-- Parse character class like [a-z], [0-9], etc. into union of ranges and
chars. Note that this parses `|` as a character. -/
def parseCharClass (s : String) (pos : String.Pos.Raw) : Except ParseError (RegexAST × String.Pos.Raw) := do
if pos.get? s != some '[' then throw (.patternError "Expected '[' at start of character class" s pos)
let mut i := pos.next s
-- Check for complement (negation) with leading ^
let isComplement := !i.atEnd s && i.get? s == some '^'
if isComplement then
i := i.next s
let mut result : Option RegexAST := none
-- Process each element in the character class.
while !i.atEnd s && i.get? s != some ']' do
-- Uncommenting this makes the code stop
--dbg_trace "Working" (pure ())
let some c1 := i.get? s | throw (.patternError "Invalid character in class" s i)
let i1 := i.next s
-- Check for range pattern: c1-c2.
if !i1.atEnd s && i1.get? s == some '-' then
let i2 := i1.next s
if !i2.atEnd s && i2.get? s != some ']' then
let some c2 := i2.get? s | throw (.patternError "Invalid character in range" s i2)
if c1 > c2 then
throw (.patternError s!"Invalid character range [{c1}-{c2}]: \
start character '{c1}' is greater than end character '{c2}'" s i)
let r := RegexAST.range c1 c2
-- Union with previous elements.
result := some (match result with | none => r | some prev => RegexAST.union prev r)
i := i2.next s
continue
-- Single character.
let r := RegexAST.char c1
result := some (match result with | none => r | some prev => RegexAST.union prev r)
i := i.next s
let some ast := result | throw (.patternError "Unterminated character set" s pos)
let finalAst := if isComplement then RegexAST.complement ast else ast
pure (finalAst, i.next s)
-------------------------------------------------------------------------------
/-- Parse numeric repeats like `{10}` or `{1,10}` into min and max bounds. -/
def parseBounds (s : String) (pos : String.Pos.Raw) : Except ParseError (Nat × Nat × String.Pos.Raw) := do
if pos.get? s != some '{' then throw (.patternError "Expected '{' at start of bounds" s pos)
let mut i := pos.next s
let mut numStr := ""
-- Parse first number.
while !i.atEnd s && (i.get? s).any Char.isDigit do
numStr := numStr.push ((i.get? s).get!)
i := i.next s
let some n := numStr.toNat? | throw (.patternError "Invalid minimum bound" s pos)
-- Check for comma (range) or closing brace (exact count).
match i.get? s with
| some '}' => pure (n, n, i.next s) -- {n} means exactly n times.
| some ',' =>
i := i.next s
-- Parse maximum bound
numStr := ""
while !i.atEnd s && (i.get? s).any Char.isDigit do
numStr := numStr.push ((i.get? s).get!)
i := i.next s
let some max := numStr.toNat? | throw (.patternError "Invalid maximum bound" s i)
if i.get? s != some '}' then throw (.patternError "Expected '}' at end of bounds" s i)
-- Validate bounds order
if max < n then
throw (.patternError s!"Invalid repeat bounds \{{n},{max}}: \
maximum {max} is less than minimum {n}" s pos)
pure (n, max, i.next s)
| _ => throw (.patternError "Invalid bounds syntax" s i)
-------------------------------------------------------------------------------
mutual
/--
Parse atom: single element (char, class, anchor, group) with optional
quantifier. Stops at the first `|`.
-/
partial def parseAtom (s : String) (pos : String.Pos.Raw) : Except ParseError (RegexAST × String.Pos.Raw) := do
if pos.atEnd s then throw (.patternError "Unexpected end of regex" s pos)
let some c := pos.get? s | throw (.patternError "Invalid position" s pos)
-- Detect invalid quantifier at start
if c == '*' || c == '+' || c == '{' || c == '?' then
throw (.patternError s!"Quantifier '{c}' at position {pos} has nothing to quantify" s pos)
-- Detect unbalanced closing parenthesis
if c == ')' then
throw (.patternError "Unbalanced parenthesis" s pos)
-- Parse base element (anchor, char class, group, anychar, escape, or single char).
let (base, nextPos) match c with
| '^' => pure (RegexAST.anchor_start, pos.next s)
| '$' => pure (RegexAST.anchor_end, pos.next s)
| '[' => parseCharClass s pos
| '(' => parseExplicitGroup s pos
| '.' => pure (RegexAST.anychar, pos.next s)
| '\\' =>
-- Handle escape sequence.
-- Note: Python uses a single backslash as an escape character, but Lean
-- strings need to escape that. After DDMification, we will see two
-- backslashes in Strata for every Python backslash.
let nextPos := pos.next s
if nextPos.atEnd s then throw (.patternError "Incomplete escape sequence at end of regex" s pos)
let some escapedChar := nextPos.get? s | throw (.patternError "Invalid escape position" s nextPos)
-- Check for special sequences (unsupported right now).
match escapedChar with
| 'A' | 'b' | 'B' | 'd' | 'D' | 's' | 'S' | 'w' | 'W' | 'z' | 'Z' =>
throw (.unimplemented s!"Special sequence \\{escapedChar} is not supported" s pos)
| 'a' | 'f' | 'n' | 'N' | 'r' | 't' | 'u' | 'U' | 'v' | 'x' =>
throw (.unimplemented s!"Escape sequence \\{escapedChar} is not supported" s pos)
| c =>
if c.isDigit then
throw (.unimplemented s!"Backreference \\{c} is not supported" s pos)
else
pure (RegexAST.char escapedChar, nextPos.next s)
| _ => pure (RegexAST.char c, pos.next s)
-- Check for numeric repeat suffix on base element (but not on anchors)
match base with
| .anchor_start | .anchor_end => pure (base, nextPos)
| _ =>
if !nextPos.atEnd s then
match nextPos.get? s with
| some '{' =>
let (min, max, finalPos) parseBounds s nextPos
pure (RegexAST.loop base min max, finalPos)
| some '*' =>
let afterStar := nextPos.next s
if !afterStar.atEnd s then
match afterStar.get? s with
| some '?' => throw (.unimplemented "Non-greedy quantifier *? is not supported" s nextPos)
| some '+' => throw (.unimplemented "Possessive quantifier *+ is not supported" s nextPos)
| _ => pure (RegexAST.star base, afterStar)
else pure (RegexAST.star base, afterStar)
| some '+' =>
let afterPlus := nextPos.next s
if !afterPlus.atEnd s then
match afterPlus.get? s with
| some '?' => throw (.unimplemented "Non-greedy quantifier +? is not supported" s nextPos)
| some '+' => throw (.unimplemented "Possessive quantifier ++ is not supported" s nextPos)
| _ => pure (RegexAST.plus base, afterPlus)
else pure (RegexAST.plus base, afterPlus)
| some '?' =>
let afterQuestion := nextPos.next s
if !afterQuestion.atEnd s then
match afterQuestion.get? s with
| some '?' => throw (.unimplemented "Non-greedy quantifier ?? is not supported" s nextPos)
| some '+' => throw (.unimplemented "Possessive quantifier ?+ is not supported" s nextPos)
| _ => pure (RegexAST.optional base, afterQuestion)
else pure (RegexAST.optional base, afterQuestion)
| _ => pure (base, nextPos)
else
pure (base, nextPos)
/-- Parse explicit group with parentheses. -/
partial def parseExplicitGroup (s : String) (pos : String.Pos.Raw) : Except ParseError (RegexAST × String.Pos.Raw) := do
if pos.get? s != some '(' then throw (.patternError "Expected '(' at start of group" s pos)
let mut i := pos.next s
-- Check for extension notation (?...
if !i.atEnd s && i.get? s == some '?' then
let i1 := i.next s
if !i1.atEnd s then
match i1.get? s with
| some '=' => throw (.unimplemented "Positive lookahead (?=...) is not supported" s pos)
| some '!' => throw (.unimplemented "Negative lookahead (?!...) is not supported" s pos)
| _ => throw (.unimplemented "Extension notation (?...) is not supported" s pos)
let (inner, finalPos) parseGroup s i (some ')')
pure (.group inner, finalPos)
/-- Parse group: handles alternation and concatenation at current scope. -/
partial def parseGroup (s : String) (pos : String.Pos.Raw) (endChar : Option Char) :
Except ParseError (RegexAST × String.Pos.Raw) := do
let mut alternatives : List (List RegexAST) := [[]]
let mut i := pos
-- Parse until end of string or `endChar`.
while !i.atEnd s && (endChar.isNone || i.get? s != endChar) do
if i.get? s == some '|' then
-- Push a new scope to `alternatives`.
alternatives := [] :: alternatives
i := i.next s
else
let (ast, nextPos) parseAtom s i
alternatives := match alternatives with
| [] => [[ast]]
| head :: tail => (ast :: head) :: tail
i := nextPos
-- Check for expected end character.
if let some ec := endChar then
if i.get? s != some ec then
throw (.patternError s!"Expected '{ec}'" s i)
i := i.next s
-- Build result: concatenate each alternative, then union them.
let concatAlts := alternatives.reverse.filterMap fun alt =>
match alt.reverse with
| [] => -- Empty regex.
some (.empty)
| [single] => some single
| head :: tail => some (tail.foldl RegexAST.concat head)
match concatAlts with
| [] => pure (.empty, i)
| [single] => pure (single, i)
| head :: tail => pure (tail.foldl RegexAST.union head, i)
end
/-- info: Except.ok (Strata.Python.RegexAST.range 'A' 'z', { byteIdx := 5 }) -/
#guard_msgs in
#eval parseCharClass "[A-z]" 0
-- Test code: Print done
#print "Done!"

View File

@@ -50,12 +50,26 @@ repositories:
dependencies:
- lean4-cli
- name: lean4-unicode-basic
url: https://github.com/fgdorais/lean4-unicode-basic
toolchain-tag: true
stable-branch: false
branch: main
dependencies: []
- name: BibtexQuery
url: https://github.com/dupuisf/BibtexQuery
toolchain-tag: true
stable-branch: false
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]
dependencies: [lean4-cli, BibtexQuery]
- name: reference-manual
url: https://github.com/leanprover/reference-manual
@@ -113,10 +127,18 @@ repositories:
dependencies:
- mathlib4
- name: verso-web-components
url: https://github.com/leanprover/verso-web-components
toolchain-tag: true
stable-branch: false
branch: main
dependencies:
- verso
- name: lean-fro.org
url: https://github.com/leanprover/lean-fro.org
toolchain-tag: false
stable-branch: false
branch: master
dependencies:
- verso
- verso-web-components

View File

@@ -23,6 +23,7 @@ What this script does:
- Special merging strategies for repositories with nightly-testing branches
- Safety checks for repositories using bump branches
- Custom build and test procedures
- lean-fro.org: runs scripts/update.sh to regenerate site content
6. Commits the changes with message "chore: bump toolchain to {version}"
@@ -412,20 +413,14 @@ def execute_release_steps(repo, version, config):
run_command("lake update", cwd=repo_path, stream_output=True)
print(blue("Running `lake update` in examples/hero..."))
run_command("lake update", cwd=repo_path / "examples" / "hero", stream_output=True)
# Run scripts/update.sh to regenerate content
print(blue("Running `scripts/update.sh` to regenerate content..."))
run_command("scripts/update.sh", cwd=repo_path, stream_output=True)
print(green("Content regenerated successfully"))
elif repo_name == "cslib":
print(blue("Updating lakefile.toml..."))
run_command(f'perl -pi -e \'s/"v4\\.[0-9]+(\\.[0-9]+)?(-rc[0-9]+)?"/"' + version + '"/g\' lakefile.*', cwd=repo_path)
print(blue("Updating docs/lakefile.toml..."))
run_command(f'perl -pi -e \'s/"v4\\.[0-9]+(\\.[0-9]+)?(-rc[0-9]+)?"/"' + version + '"/g\' lakefile.*', cwd=repo_path / "docs")
# Update lean-toolchain in docs
print(blue("Updating docs/lean-toolchain..."))
docs_toolchain = repo_path / "docs" / "lean-toolchain"
with open(docs_toolchain, "w") as f:
f.write(f"leanprover/lean4:{version}\n")
print(green(f"Updated docs/lean-toolchain to leanprover/lean4:{version}"))
run_command("lake update", cwd=repo_path, stream_output=True)
elif dependencies:
run_command(f'perl -pi -e \'s/"v4\\.[0-9]+(\\.[0-9]+)?(-rc[0-9]+)?"/"' + version + '"/g\' lakefile.*', cwd=repo_path)

View File

@@ -10,7 +10,7 @@ endif()
include(ExternalProject)
project(LEAN CXX C)
set(LEAN_VERSION_MAJOR 4)
set(LEAN_VERSION_MINOR 27)
set(LEAN_VERSION_MINOR 28)
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'")
@@ -448,8 +448,8 @@ if(LLVM AND ${STAGE} GREATER 0)
# - In particular, `host/bin/llvm-config` produces flags like `-Lllvm-host/lib/libLLVM`, while
# we need the path to be `-Lllvm/lib/libLLVM`. Thus, we perform this replacement here.
string(REPLACE "llvm-host" "llvm" LEANSHARED_LINKER_FLAGS ${LEANSHARED_LINKER_FLAGS})
string(REPLACE "llvm-host" "llvm" LEAN_EXTRA_CXX_FLAGS ${LEAN_EXTRA_CXX_FLAGS})
message(VERBOSE "leanshared linker flags: '${LEANSHARED_LINKER_FLAGS}' | lean extra cxx flags '${LEAN_EXTR_CXX_FLAGS}'")
string(REPLACE "llvm-host" "llvm" CMAKE_CXX_FLAGS ${CMAKE_CXX_FLAGS})
message(VERBOSE "leanshared linker flags: '${LEANSHARED_LINKER_FLAGS}' | lean extra cxx flags '${CMAKE_CXX_FLAGS}'")
endif()
# get rid of unused parts of C++ stdlib

View File

@@ -15,6 +15,7 @@ public import Init.RCases
public import Init.Core
public import Init.Control
public import Init.WF
public import Init.WFComputable
public import Init.WFTactics
public import Init.Data
public import Init.System

View File

@@ -116,7 +116,7 @@ On top of these instances this file defines several auxiliary type classes:
* `CoeOTC := CoeOut* Coe*`
* `CoeHTC := CoeHead? CoeOut* Coe*`
* `CoeHTCT := CoeHead? CoeOut* Coe* CoeTail?`
* `CoeDep := CoeHead? CoeOut* Coe* CoeTail? | CoeDep`
* `CoeT := CoeHead? CoeOut* Coe* CoeTail? | CoeDep`
-/

View File

@@ -16,3 +16,4 @@ public import Init.Control.Option
public import Init.Control.Lawful
public import Init.Control.StateCps
public import Init.Control.ExceptCps
public import Init.Control.MonadAttach

View File

@@ -25,6 +25,12 @@ instance [Repr ε] [Repr α] : Repr (Result ε σ α) where
| Result.error e _, prec => Repr.addAppParen ("EStateM.Result.error " ++ reprArg e) prec
| Result.ok a _, prec => Repr.addAppParen ("EStateM.Result.ok " ++ reprArg a) prec
instance : MonadAttach (EStateM ε σ) where
CanReturn x a := Exists fun s => Exists fun s' => x.run s = .ok a s'
attach x s := match h : x s with
| .ok a s' => .ok a, s, s', h s'
| .error e s' => .error e s'
end EStateM
namespace EStateM

View File

@@ -329,3 +329,8 @@ instance ExceptT.finally {m : Type u → Type v} {ε : Type u} [MonadFinally m]
| (.ok a, .ok b) => pure (.ok (a, b))
| (_, .error e) => pure (.error e) -- second error has precedence
| (.error e, _) => pure (.error e)
instance [Monad m] [MonadAttach m] : MonadAttach (ExceptT ε m) where
CanReturn x a := MonadAttach.CanReturn (m := m) x (.ok a)
attach x := show m (Except ε _) from
(fun a, h => match a with | .ok a => .ok a, h | .error e => .error e) <$> MonadAttach.attach (m := m) x

View File

@@ -75,6 +75,13 @@ instance [Monad m] : MonadLift m (ExceptCpsT σ m) where
instance [Inhabited ε] : Inhabited (ExceptCpsT ε m α) where
default := fun _ _ k₂ => k₂ default
/--
For continuation monads, it is not possible to provide a computable `MonadAttach` instance that
actually adds information about the return value. Therefore, this instance always attaches a proof
of `True`.
-/
instance : MonadAttach (ExceptCpsT ε m) := .trivial
@[simp] theorem run_pure [Monad m] : run (pure x : ExceptCpsT ε m α) = pure (Except.ok x) := rfl
@[simp] theorem run_lift {α ε : Type u} [Monad m] (x : m α) : run (ExceptCpsT.lift x : ExceptCpsT ε m α) = (x >>= fun a => pure (Except.ok a) : m (Except ε α)) := rfl

View File

@@ -9,6 +9,7 @@ module
prelude
public import Init.Core
public import Init.Control.MonadAttach
public section
@@ -67,4 +68,15 @@ instance [OfNat α n] : OfNat (Id α) n :=
instance {m : Type u Type v} [Pure m] : MonadLiftT Id m where
monadLift x := pure x.run
instance : MonadAttach Id where
CanReturn x a := x.run = a
attach x := pure x.run, rfl
instance : LawfulMonadAttach Id where
map_attach := rfl
canReturn_map_imp := by
intro _ _ x _ h
cases h
exact x.run.2
end Id

View File

@@ -10,3 +10,4 @@ public import Init.Control.Lawful.Basic
public import Init.Control.Lawful.Instances
public import Init.Control.Lawful.Lemmas
public import Init.Control.Lawful.MonadLift
public import Init.Control.Lawful.MonadAttach

View File

@@ -248,10 +248,10 @@ namespace Id
instance : LawfulMonad Id := by
refine LawfulMonad.mk' _ ?_ ?_ ?_ <;> intros <;> rfl
@[simp] theorem run_map (x : Id α) (f : α β) : (f <$> x).run = f x.run := rfl
@[simp] theorem run_bind (x : Id α) (f : α Id β) : (x >>= f).run = (f x.run).run := rfl
@[simp] theorem run_pure (a : α) : (pure a : Id α).run = a := rfl
@[simp] theorem pure_run (a : Id α) : pure a.run = a := rfl
@[simp, grind =] theorem run_map (x : Id α) (f : α β) : (f <$> x).run = f x.run := rfl
@[simp, grind =] theorem run_bind (x : Id α) (f : α Id β) : (x >>= f).run = (f x.run).run := rfl
@[simp, grind =] theorem run_pure (a : α) : (pure a : Id α).run = a := rfl
@[simp, grind =] theorem pure_run (a : Id α) : pure a.run = a := rfl
@[simp] theorem run_seqRight (x y : Id α) : (x *> y).run = y.run := rfl
@[simp] theorem run_seqLeft (x y : Id α) : (x <* y).run = x.run := rfl
@[simp] theorem run_seq (f : Id (α β)) (x : Id α) : (f <*> x).run = f.run x.run := rfl

View File

@@ -17,6 +17,9 @@ public section
open Function
@[simp, grind =] theorem monadMap_refl {m : Type _ Type _} {α} (f : {α}, m α m α) :
monadMap @f = @f α := rfl
/-! # ExceptT -/
namespace ExceptT
@@ -25,6 +28,8 @@ namespace ExceptT
simp [run] at h
assumption
@[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
@[simp, grind =] theorem run_lift [Monad.{u, v} m] (x : m α) : run (ExceptT.lift x : ExceptT ε m α) = (Except.ok <$> x : m (Except ε α)) := rfl
@@ -55,6 +60,9 @@ theorem run_bind [Monad m] (x : ExceptT ε m α) (f : α → ExceptT ε m β)
apply bind_congr
intro a; cases a <;> simp [Except.map]
@[simp, grind =] theorem run_monadMap [MonadFunctorT n m] (f : {β : Type u} n β n β) (x : ExceptT ε m α)
: (monadMap @f x : ExceptT ε m α).run = monadMap @f (x.run) := rfl
protected theorem seq_eq {α β ε : Type u} [Monad m] (mf : ExceptT ε m (α β)) (x : ExceptT ε m α) : mf <*> x = mf >>= fun f => f <$> x :=
rfl
@@ -97,6 +105,22 @@ instance [Monad m] [LawfulMonad m] : LawfulMonad (ExceptT ε m) where
simp only [ExceptT.instMonad, ExceptT.map, ExceptT.mk, throw, throwThe, MonadExceptOf.throw,
pure_bind]
/-! Note that the `MonadControl` instance for `ExceptT` is not monad-generic. -/
@[simp] theorem run_restoreM [Monad m] (x : stM m (ExceptT ε m) α) :
ExceptT.run (restoreM x) = pure x := rfl
@[simp] theorem run_liftWith [Monad m] (f : ({β : Type u} ExceptT ε m β m (stM m (ExceptT ε m) β)) m α) :
ExceptT.run (liftWith f) = Except.ok <$> (f fun x => x.run) :=
rfl
@[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] 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
end ExceptT
/-! # Except -/
@@ -150,6 +174,9 @@ namespace OptionT
apply bind_congr
intro a; cases a <;> simp [OptionT.pure, OptionT.mk]
@[simp, grind =] theorem run_monadMap [MonadFunctorT n m] (f : {β : Type u} n β n β) (x : OptionT m α)
: (monadMap @f x : OptionT m α).run = monadMap @f (x.run) := rfl
protected theorem seq_eq {α β : Type u} [Monad m] (mf : OptionT m (α β)) (x : OptionT m α) : mf <*> x = mf >>= fun f => f <$> x :=
rfl
@@ -211,6 +238,24 @@ instance [Monad m] [LawfulMonad m] : LawfulMonad (OptionT m) where
(x <|> y).run = Option.elimM x.run y.run (fun x => pure (some x)) :=
bind_congr fun | some _ => by rfl | none => by rfl
/-! Note that the `MonadControl` instance for `OptionT` is not monad-generic. -/
@[simp] theorem run_restoreM [Monad m] (x : stM m (OptionT m) α) :
OptionT.run (restoreM x) = pure x := rfl
@[simp] theorem run_liftWith [Monad m] [LawfulMonad m] (f : ({β : Type u} OptionT m β m (stM m (OptionT m) β)) m α) :
OptionT.run (liftWith f) = Option.some <$> (f fun x => x.run) := by
dsimp [liftWith]
rw [ bind_pure_comp]
rfl
@[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]
@[simp] theorem run_control [Monad m] [LawfulMonad m] (f : ({β : Type u} OptionT m β m (stM m (OptionT m) β)) m (stM m (OptionT m) α)) :
OptionT.run (control f) = f fun x => x.run := run_controlAt f
end OptionT
/-! # Option -/
@@ -232,6 +277,9 @@ namespace ReaderT
simp [run] at h
exact funext h
@[simp, grind =] theorem run_mk (x : ρ m α) (ctx : ρ) : run (.mk x : ReaderT ρ m α) ctx = x ctx :=
rfl
@[simp, grind =] theorem run_pure [Monad m] (a : α) (ctx : ρ) : (pure a : ReaderT ρ m α).run ctx = pure a := rfl
@[simp, grind =] theorem run_bind [Monad m] (x : ReaderT ρ m α) (f : α ReaderT ρ m β) (ctx : ρ)
@@ -279,6 +327,22 @@ instance [Monad m] [LawfulMonad m] : LawfulMonad (ReaderT ρ m) where
pure_bind := by intros; apply ext; intros; simp
bind_assoc := by intros; apply ext; intros; simp
/-! Note that the `MonadControl` instance for `ReaderT` is not monad-generic. -/
@[simp] theorem run_restoreM [Monad m] (x : stM m (ReaderT ρ m) α) (ctx : ρ) :
ReaderT.run (restoreM x) ctx = pure x := rfl
@[simp] theorem run_liftWith [Monad m] (f : ({β : Type u} ReaderT ρ m β m (stM m (ReaderT ρ m) β)) m α) (ctx : ρ) :
ReaderT.run (liftWith f) ctx = (f fun x => x.run ctx) :=
rfl
@[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]
@[simp] theorem run_control [Monad m] [LawfulMonad m] (f : ({β : Type u} ReaderT ρ m β m (stM m (ReaderT ρ m) β)) m (stM m (ReaderT ρ m) α)) (ctx : ρ) :
ReaderT.run (control f) ctx = f fun x => x.run ctx := run_controlAt f ctx
end ReaderT
/-! # StateRefT -/
@@ -293,17 +357,20 @@ namespace StateT
@[ext, grind ext] theorem ext {x y : StateT σ m α} (h : s, x.run s = y.run s) : x = y :=
funext h
@[simp, grind =] theorem run_mk [Monad m] (x : σ m (α × σ)) (s : σ) : run (.mk x) s = x s :=
rfl
@[simp, grind =] theorem run'_eq [Monad m] (x : StateT σ m α) (s : σ) : run' x s = (·.1) <$> run x s :=
rfl
@[simp, grind =] theorem run_pure [Monad m] (a : α) (s : σ) : (pure a : StateT σ m α).run s = pure (a, s) := rfl
@[simp, grind =] theorem run_bind [Monad m] (x : StateT σ m α) (f : α StateT σ m β) (s : σ)
: (x >>= f).run s = x.run s >>= λ p => (f p.1).run p.2 := by
simp [bind, StateT.bind, run]
: (x >>= f).run s = x.run s >>= λ p => (f p.1).run p.2 := rfl
@[simp, grind =] theorem run_map {α β σ : Type u} [Monad m] [LawfulMonad m] (f : α β) (x : StateT σ m α) (s : σ) : (f <$> x).run s = (fun (p : α × σ) => (f p.1, p.2)) <$> x.run s := by
simp [Functor.map, StateT.map, run, bind_pure_comp]
rw [ bind_pure_comp (m := m)]
rfl
@[simp, grind =] theorem run_get [Monad m] (s : σ) : (get : StateT σ m σ).run s = pure (s, s) := rfl
@@ -312,13 +379,13 @@ namespace StateT
@[simp, grind =] theorem run_modify [Monad m] (f : σ σ) (s : σ) : (modify f : StateT σ m PUnit).run s = pure (, f s) := rfl
@[simp, grind =] theorem run_modifyGet [Monad m] (f : σ α × σ) (s : σ) : (modifyGet f : StateT σ m α).run s = pure ((f s).1, (f s).2) := by
simp [modifyGet, MonadStateOf.modifyGet, StateT.modifyGet, run]
rfl
@[simp, grind =] theorem run_lift {α σ : Type u} [Monad m] (x : m α) (s : σ) : (StateT.lift x : StateT σ m α).run s = x >>= fun a => pure (a, s) := rfl
@[grind =]
theorem run_bind_lift {α σ : Type u} [Monad m] [LawfulMonad m] (x : m α) (f : α StateT σ m β) (s : σ) : (StateT.lift x >>= f).run s = x >>= fun a => (f a).run s := by
simp [StateT.lift, StateT.run, bind, StateT.bind]
simp
@[simp, grind =] theorem run_monadLift {α σ : Type u} [Monad m] [MonadLiftT n m] (x : n α) (s : σ) : (monadLift x : StateT σ m α).run s = (monadLift x : m α) >>= fun a => pure (a, s) := rfl
@@ -358,10 +425,48 @@ instance [Monad m] [LawfulMonad m] : LawfulMonad (StateT σ m) where
pure_bind := by intros; apply ext; intros; simp
bind_assoc := by intros; apply ext; intros; simp
/-! Note that the `MonadControl` instance for `StateT` is not monad-generic. -/
@[simp] theorem run_restoreM [Monad m] [LawfulMonad m] (x : stM m (StateT σ m) α) (s : σ) :
StateT.run (restoreM x) s = pure x := by
simp [restoreM, MonadControl.restoreM]
rfl
@[simp] theorem run_liftWith [Monad m] [LawfulMonad m] (f : ({β : Type u} StateT σ m β m (stM m (StateT σ m) β)) m α) (s : σ) :
StateT.run (liftWith f) s = ((·, s) <$> f fun x => x.run s) := by
simp [liftWith, MonadControl.liftWith, Function.comp_def]
@[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]
@[simp] theorem run_control [Monad m] [LawfulMonad m] (f : ({β : Type u} StateT σ m β m (stM m (StateT σ m) β)) m (stM m (StateT σ m) α)) (s : σ) :
StateT.run (control f) s = f fun x => x.run s := run_controlAt f s
end StateT
/-! # EStateM -/
namespace EStateM
@[simp, grind =] theorem run_pure (a : α) (s : σ) :
EStateM.run (pure a : EStateM ε σ α) s = .ok a s := rfl
@[simp, grind =] theorem run_get (s : σ) :
EStateM.run (get : EStateM ε σ σ) s = .ok s s := rfl
@[simp, grind =] theorem run_set (s₁ s₂ : σ) :
EStateM.run (set s₁ : EStateM ε σ PUnit) s₂ = .ok .unit s₁ := rfl
@[simp, grind =] theorem run_modify (f : σ σ) (s : σ) :
EStateM.run (modify f : EStateM ε σ PUnit) s = .ok .unit (f s) := rfl
@[simp, grind =] theorem run_modifyGet (f : σ α × σ) (s : σ) :
EStateM.run (modifyGet f : EStateM ε σ α) s = .ok (f s).1 (f s).2 := rfl
@[simp, grind =] theorem run_throw (e : ε) (s : σ):
EStateM.run (throw e : EStateM ε σ PUnit) s = .error e s := rfl
instance : LawfulMonad (EStateM ε σ) := .mk'
(id_map := fun x => funext <| fun s => by
dsimp only [EStateM.instMonad, EStateM.map]
@@ -375,3 +480,5 @@ instance : LawfulMonad (EStateM ε σ) := .mk'
| .ok _ _ => rfl
| .error _ _ => rfl)
(map_const := fun _ _ => rfl)
end EStateM

View File

@@ -0,0 +1,10 @@
/-
Copyright (c) 2025 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.Control.Lawful.MonadAttach.Lemmas
public import Init.Control.Lawful.MonadAttach.Instances

View File

@@ -0,0 +1,86 @@
/-
Copyright (c) 2025 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.Control.Reader
public import Init.Control.Lawful.Instances
import Init.Control.Lawful.MonadAttach.Lemmas
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]
intros; rfl
public instance [Monad m] [LawfulMonad m] [MonadAttach m] [LawfulMonadAttach m] :
LawfulMonadAttach (ReaderT ρ m) where
canReturn_map_imp := by
simp only [Functor.map, MonadAttach.CanReturn, ReaderT.run]
rintro _ _ x a r, h
apply LawfulMonadAttach.canReturn_map_imp h
public instance [Monad m] [LawfulMonad m] [MonadAttach m] [WeaklyLawfulMonadAttach m] :
WeaklyLawfulMonadAttach (StateT σ m) where
map_attach := by
intro α x
simp only [Functor.map, StateT, funext_iff, StateT.map, bind_pure_comp, MonadAttach.attach,
Functor.map_map]
exact fun s => WeaklyLawfulMonadAttach.map_attach
public instance [Monad m] [LawfulMonad m] [MonadAttach m] [LawfulMonadAttach m] :
LawfulMonadAttach (StateT σ m) where
canReturn_map_imp := by
simp only [Functor.map, MonadAttach.CanReturn, StateT.run, StateT.map, bind_pure_comp]
rintro _ _ x a s, s', h
obtain a, h, h' := LawfulMonadAttach.canReturn_map_imp' h
cases h'
exact a.1.2
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
conv => rhs; rw [ WeaklyLawfulMonadAttach.map_attach (m := m) (x := x)]
simp only [map_eq_pure_bind]
apply bind_congr; intro a
match a with
| .ok _, _ => simp
| .error _, _ => simp
public instance [Monad m] [LawfulMonad m] [MonadAttach m] [LawfulMonadAttach m] :
LawfulMonadAttach (ExceptT ε m) where
canReturn_map_imp {α P x a} := by
simp only [Functor.map, MonadAttach.CanReturn, ExceptT.map, ExceptT.mk]
let x' := (fun a => show Subtype (fun a : Except _ _ => match a with | .ok a => P a | .error e => True) from match a with | .ok a => .ok a.1 | .error e => .error e, by cases a <;> simp [Subtype.property]) <$> show m _ from x
have := LawfulMonadAttach.canReturn_map_imp (m := m) (x := x') (a := .ok a)
simp only at this
intro h
apply this
simp only [x', map_eq_pure_bind, bind_assoc]
refine cast ?_ h
congr 1
apply bind_congr; intro a
split <;> simp
public instance [Monad m] [MonadAttach m] [LawfulMonad m] [WeaklyLawfulMonadAttach m] :
WeaklyLawfulMonadAttach (StateRefT' ω σ m) :=
inferInstanceAs (WeaklyLawfulMonadAttach (ReaderT _ _))
public instance [Monad m] [MonadAttach m] [LawfulMonad m] [LawfulMonadAttach m] :
LawfulMonadAttach (StateRefT' ω σ m) :=
inferInstanceAs (LawfulMonadAttach (ReaderT _ _))
section
attribute [local instance] MonadAttach.trivial
public instance [Monad m] [LawfulMonad m] :
WeaklyLawfulMonadAttach m where
map_attach := by simp [MonadAttach.attach]
end

View File

@@ -0,0 +1,90 @@
/-
Copyright (c) 2025 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.Control.MonadAttach
import all Init.Control.MonadAttach
public import Init.Control.Lawful.Lemmas
public import Init.Control.Lawful.MonadLift.Lemmas
public theorem LawfulMonadAttach.canReturn_bind_imp' [Monad m] [LawfulMonad m]
[MonadAttach m] [LawfulMonadAttach m]
{x : m α} {f : α m β} :
MonadAttach.CanReturn (x >>= f) b Exists fun a => MonadAttach.CanReturn x a MonadAttach.CanReturn (f a) b := by
intro h
let P (b : β) := Exists fun a => MonadAttach.CanReturn x a MonadAttach.CanReturn (f a) b
have h' : (x >>= f) = Subtype.val <$> (MonadAttach.attach x >>= (fun a => (do
let b MonadAttach.attach (f a)
return b.1, a.1, a.2, b.2 : m (Subtype P)))) := by
simp only [map_bind, map_pure]
simp only [bind_pure_comp, WeaklyLawfulMonadAttach.map_attach]
rw (occs := [1]) [ WeaklyLawfulMonadAttach.map_attach (x := x)]
simp
rw [h'] at h
have := LawfulMonadAttach.canReturn_map_imp h
exact this
public theorem LawfulMonadAttach.eq_of_canReturn_pure [Monad m] [MonadAttach m]
[LawfulMonad m] [LawfulMonadAttach m] {a b : α}
(h : MonadAttach.CanReturn (m := m) (pure a) b) :
a = b := by
let x : m (Subtype (a = ·)) := pure a, rfl
have : pure a = Subtype.val <$> x := by simp [x]
rw [this] at h
exact LawfulMonadAttach.canReturn_map_imp h
public theorem LawfulMonadAttach.canReturn_map_imp' [Monad m] [LawfulMonad m]
[MonadAttach m] [LawfulMonadAttach m]
{x : m α} {f : α β} :
MonadAttach.CanReturn (f <$> x) b Exists fun a => MonadAttach.CanReturn x a f a = b := by
rw [map_eq_pure_bind]
intro h
obtain a, h, h' := canReturn_bind_imp' h
exact a, h, eq_of_canReturn_pure h'
public theorem LawfulMonadAttach.canReturn_liftM_imp'
[Monad m] [MonadAttach m] [LawfulMonad m] [LawfulMonadAttach m]
[Monad n] [MonadAttach n] [LawfulMonad n] [LawfulMonadAttach n]
[MonadLiftT m n] [LawfulMonadLiftT m n] {x : m α} {a : α} :
MonadAttach.CanReturn (liftM (n := n) x) a MonadAttach.CanReturn x a := by
intro h
simp only [ WeaklyLawfulMonadAttach.map_attach (x := x), liftM_map] at h
exact canReturn_map_imp h
public theorem WeaklyLawfulMonadAttach.attach_bind_val
[Monad m] [MonadAttach m] [LawfulMonad m] [WeaklyLawfulMonadAttach m]
{x : m α} {f : α m β} :
MonadAttach.attach x >>= (fun a => f a.val) = x >>= f := by
conv => rhs; simp only [ map_attach (x := x), bind_map_left]
public theorem WeaklyLawfulMonadAttach.bind_attach_of_nonempty
[Monad m] [MonadAttach m] [LawfulMonad m] [WeaklyLawfulMonadAttach m] [Nonempty (m β)]
{x : m α} {f : Subtype (MonadAttach.CanReturn x) m β} :
open scoped Classical in
MonadAttach.attach x >>= f = x >>= (fun a => if ha : MonadAttach.CanReturn x a then f a, ha else Classical.ofNonempty) := by
conv => rhs; simp +singlePass only [ map_attach (x := x)]
simp [Subtype.property]
public theorem MonadAttach.attach_bind_eq_pbind
[Monad m] [MonadAttach m]
{x : m α} {f : Subtype (MonadAttach.CanReturn x) m β} :
MonadAttach.attach x >>= f = MonadAttach.pbind x (fun a ha => f a, ha) := by
simp [MonadAttach.pbind]
public theorem WeaklyLawfulMonadAttach.pbind_eq_bind
[Monad m] [MonadAttach m] [LawfulMonad m] [WeaklyLawfulMonadAttach m]
{x : m α} {f : α m β} :
MonadAttach.pbind x (fun a _ => f a) = x >>= f := by
conv => rhs; rw [ map_attach (x := x)]
simp [MonadAttach.pbind]
public theorem WeaklyLawfulMonadAttach.pbind_eq_bind'
[Monad m] [MonadAttach m] [LawfulMonad m] [WeaklyLawfulMonadAttach m]
{x : m α} {f : α m β} :
MonadAttach.pbind x (fun a _ => f a) = x >>= f := by
conv => rhs; rw [ map_attach (x := x)]
simp [MonadAttach.pbind]

View File

@@ -6,6 +6,7 @@ Authors: Quang Dao
module
prelude
public import Init.Control.Id
public import Init.Control.Lawful.Basic
public import Init.Control.Lawful.MonadLift.Basic
@@ -13,6 +14,14 @@ public section
universe u v w
theorem instMonadLiftTOfMonadLift_instMonadLiftTOfPure [Monad m] [Monad n] {_ : MonadLift m n}
[LawfulMonadLift m n] : instMonadLiftTOfMonadLift Id m n = Id.instMonadLiftTOfPure := by
have hext {a b : MonadLiftT Id n} (h : @a.monadLift = @b.monadLift) : a = b := by
cases a <;> cases b <;> simp_all
apply hext
ext α x
simp [monadLift, LawfulMonadLift.monadLift_pure]
variable {m : Type u Type v} {n : Type u Type w} [Monad m] [Monad n] [MonadLiftT m n]
[LawfulMonadLiftT m n] {α β : Type u}

View File

@@ -0,0 +1,126 @@
/-
Copyright (c) 2025 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.Control.Basic
set_option linter.all true
set_option doc.verso true
/-!
# {name (scope := "Init.Control.MonadAttach")}`MonadAttach`
This module provides a mechanism for attaching proofs to the return values of monadic computations,
producing a new monadic computation returning a {name}`Subtype`.
This function is primarily used to allow definitions by [well-founded
recursion](lean-manual://section/well-founded-recursion) that sequence computations using
{name}`Bind.bind` (`>>=`) to prove properties about the return values of prior computations when
a recursive call happens.
This allows the well-founded recursion mechanism to prove that the function terminates.
-/
-- verso docstring is added below
set_option linter.missingDocs false in
public class MonadAttach (m : Type u Type v) where
/--
A predicate that can be assumed to be true for all return values {name}`a` of actions {name}`x`
in {name}`m`, in all situations.
-/
CanReturn {α : Type u} : (x : m α) (a : α) Prop
/--
Attaches a proof of {name}`MonadAttach.CanReturn` to the return value of {name}`x`. This proof
can be used to prove the termination of well-founded recursive functions.
-/
attach {α : Type u} (x : m α) : m (Subtype (CanReturn x))
-- verso docstring is added below
set_option linter.missingDocs false in
public class WeaklyLawfulMonadAttach (m : Type u Type v) [Monad m] [MonadAttach m] where
map_attach {α : Type u} {x : m α} : Subtype.val <$> MonadAttach.attach x = x
/--
This type class ensures that {name}`MonadAttach.CanReturn` is the unique strongest possible
postcondition.
-/
public class LawfulMonadAttach (m : Type u Type v) [Monad m] [MonadAttach m] extends
WeaklyLawfulMonadAttach m where
canReturn_map_imp {α : Type u} {P : α Prop} {x : m (Subtype P)} {a : α} :
MonadAttach.CanReturn (Subtype.val <$> x) a P a
/--
Like {name}`Bind.bind`, {name}`pbind` sequences two computations {lean}`x : m α` and {lean}`f`,
allowing the second to depend on the value computed by the first.
But other than with {name}`Bind.bind`, the second computation can also depend on a proof that
the return value {given}`a` of {name}`x` satisfies {lean}`MonadAttach.CanReturn x a`.
-/
public def MonadAttach.pbind [Monad m] [MonadAttach m]
(x : m α) (f : (a : α) MonadAttach.CanReturn x a m β) : m β :=
MonadAttach.attach x >>= (fun a, ha => f a ha)
/--
A {lean}`MonadAttach` instance where all return values are possible and {name}`attach` adds no
information to the return value, except a trivial proof of {name}`True`.
This instance is used whenever no more useful {name}`MonadAttach` instance can be implemented.
It always has a {name}`WeaklyLawfulMonadAttach`, but usually no {name}`LawfulMonadAttach` instance.
-/
@[expose]
public protected def MonadAttach.trivial {m : Type u Type v} [Monad m] : MonadAttach m where
CanReturn _ _ := True
attach x := (·, .intro) <$> x
section
variable (α : Type u) [ m, Monad m] [ m, MonadAttach m]
set_option doc.verso true
/--
For every {given}`x : m α`, this type class provides a predicate {lean}`MonadAttach.CanReturn x`
and a way to attach a proof of this predicate to the return values of {name}`x` by providing
an element {lean}`MonadAttach.attach x` of {lean}`m { a : α // MonadAttach.CanReturn x a }`.
Instances should abide the law {lean}`Subtype.val <$> MonadAttach.attach x = x`, which is encoded by
the {name}`WeaklyLawfulMonadAttach` type class. The stronger type class {name}`LawfulMonadAttach`
ensures that {lean}`MonadAttach.CanReturn x` is the _unique_ strongest possible predicate.
Similarly to {name (scope := "Init.Data.List.Attach")}`List.attach`, the purpose of
{name}`MonadAttach` is to attach proof terms necessary for well-founded termination proofs.
The iterator library relies on {name}`MonadAttach` for combinators such as
{name (scope := "Init.Data.Iterators")}`Std.Iter.filterM` in order to automatically attach
information about the monadic predicate's behavior that could be relevant for the termination
behavior of the iterator.
*Limitations*:
For many monads, there is a strongly lawful {lean}`MonadAttach` instance, but there are exceptions.
For example, there is no way to provide a computable {lean}`MonadAttach` instance for the CPS monad
transformers
{name (scope := "Init.Control.StateCps")}`StateCpsT` and
{name (scope := "Init.Control.StateCps")}`ExceptCpsT` with a predicate that is not always
{name}`True`. Therefore, such CPS monads only provide the trivial {lean}`MonadAttach` instance
{lean}`MonadAttach.trivial` together with {name}`WeaklyLawfulMonadAttach`, but without
{name}`LawfulMonadAttach`.
For most monads with side effects, {lean}`MonadAttach` is too weak to fully capture the behavior of
computations because the postcondition represented by {name}`MonadAttach.CanReturn` neither depends
on the prior internal state of the monad, nor does it contain information about how the state of the
monad changes with the computation.
-/
add_decl_doc MonadAttach
/--
This type class ensures that every monadic action {given}`x : m α` can be recovered by stripping the
proof component from the subtypes returned by
{lean}`(MonadAttach.attach x) : m { a : α // MonadAttach.CanReturn x a }` . In other words,
the type class ensures that {lean}`Subtype.val <$> MonadAttach.attach x = x`.
-/
add_decl_doc WeaklyLawfulMonadAttach
end

View File

@@ -112,6 +112,12 @@ instance (ε : Type u) [MonadExceptOf ε m] : MonadExceptOf ε (OptionT m) where
throw e := OptionT.mk <| throwThe ε e
tryCatch x handle := OptionT.mk <| tryCatchThe ε x handle
instance [MonadAttach m] : MonadAttach (OptionT m) where
CanReturn x a := MonadAttach.CanReturn x.run (some a)
attach x := .mk ((fun
| some a, h => some a, h
| none, _ => none) <$> MonadAttach.attach x.run)
end OptionT
instance [Monad m] : MonadControl m (OptionT m) where

View File

@@ -51,3 +51,7 @@ A monad with access to a read-only value of type `ρ`. The value can be locally
`withReader`, but it cannot be mutated.
-/
abbrev ReaderM (ρ : Type u) := ReaderT ρ Id
instance [Monad m] [MonadAttach m] : MonadAttach (ReaderT ρ m) where
CanReturn x a := Exists (fun r => MonadAttach.CanReturn (x.run r) a)
attach x := fun r => (fun a, h => a, r, h) <$> MonadAttach.attach (x.run r)

View File

@@ -25,6 +25,12 @@ of a value and a state.
@[expose] def StateT (σ : Type u) (m : Type u Type v) (α : Type u) : Type (max u v) :=
σ m (α × σ)
/--
Interpret `σ → m (α × σ)` as an element of `StateT σ m α`.
-/
@[always_inline, inline, expose]
def StateT.mk {σ : Type u} {m : Type u Type v} {α : Type u} (x : σ m (α × σ)) : StateT σ m α := x
/--
Executes an action from a monad with added state in the underlying monad `m`. Given an initial
state, it returns a value paired with the final state.
@@ -198,3 +204,7 @@ instance StateT.tryFinally {m : Type u → Type v} {σ : Type u} [MonadFinally m
| some (a, s') => h (some a) s'
| none => h none s
pure ((a, b), s'')
instance [Monad m] [MonadAttach m] : MonadAttach (StateT σ m) where
CanReturn x a := Exists fun s => Exists fun s' => MonadAttach.CanReturn (x.run s) (a, s')
attach x := fun s => (fun a, s', h => a, s, s', h, s') <$> MonadAttach.attach (x.run s)

View File

@@ -68,6 +68,13 @@ instance : MonadStateOf σ (StateCpsT σ m) where
set s := fun _ _ k => k s
modifyGet f := fun _ s k => let (a, s) := f s; k a s
/--
For continuation monads, it is not possible to provide a computable `MonadAttach` instance that
actually adds information about the return value. Therefore, this instance always attaches a proof
of `True`.
-/
instance : MonadAttach (StateCpsT ε m) := .trivial
/--
Runs an action from the underlying monad in the monad with state. The state is not modified.

View File

@@ -64,6 +64,7 @@ instance [Monad m] : Monad (StateRefT' ω σ m) := inferInstanceAs (Monad (Reade
instance : MonadLift m (StateRefT' ω σ m) := StateRefT'.lift
instance (σ m) : MonadFunctor m (StateRefT' ω σ m) := inferInstanceAs (MonadFunctor m (ReaderT _ _))
instance [Alternative m] [Monad m] : Alternative (StateRefT' ω σ m) := inferInstanceAs (Alternative (ReaderT _ _))
instance [Monad m] [MonadAttach m] : MonadAttach (StateRefT' ω σ m) := inferInstanceAs (MonadAttach (ReaderT _ _))
/--
Retrieves the current value of the monad's mutable state.

View File

@@ -201,6 +201,7 @@ An element of `α ⊕ β` is either an `a : α` wrapped in `Sum.inl` or a `b :
indication of which of the two types was chosen. The union of a singleton set with itself contains
one element, while `Unit ⊕ Unit` contains distinct values `inl ()` and `inr ()`.
-/
@[suggest_for Either]
inductive Sum (α : Type u) (β : Type v) where
/-- Left injection into the sum type `α ⊕ β`. -/
| inl (val : α) : Sum α β
@@ -939,9 +940,7 @@ theorem HEq.subst {p : (T : Sort u) → T → Prop} (h₁ : a ≍ b) (h₂ : p
@[symm] theorem HEq.symm (h : a b) : b a :=
h.rec (HEq.refl a)
/-- Propositionally equal terms are also heterogeneously equal. -/
theorem heq_of_eq (h : a = a') : a a' :=
Eq.subst h (HEq.refl a)
/-- Heterogeneous equality is transitive. -/
theorem HEq.trans (h₁ : a b) (h₂ : b c) : a c :=
@@ -1370,7 +1369,7 @@ instance {α : Type u} {p : α → Prop} [BEq α] [LawfulBEq α] : LawfulBEq {x
instance {α : Sort u} {p : α Prop} [DecidableEq α] : DecidableEq {x : α // p x} :=
fun a, h₁ b, h₂ =>
if h : a = b then isTrue (by subst h; exact rfl)
else isFalse (fun h' => Subtype.noConfusion h' (fun h' => absurd h' h))
else isFalse (fun h' => Subtype.noConfusion rfl .rfl (heq_of_eq h') (fun h' => absurd (eq_of_heq h') h))
end Subtype
@@ -1429,8 +1428,8 @@ instance [DecidableEq α] [DecidableEq β] : DecidableEq (α × β) :=
| isTrue e₁ =>
match decEq b b' with
| isTrue e₂ => isTrue (e₁ e₂ rfl)
| isFalse n₂ => isFalse fun h => Prod.noConfusion h fun _ e₂' => absurd e₂' n₂
| isFalse n₁ => isFalse fun h => Prod.noConfusion h fun e₁' _ => absurd e₁' n₁
| isFalse n₂ => isFalse fun h => Prod.noConfusion rfl rfl (heq_of_eq h) fun _ e₂' => absurd (eq_of_heq e₂') n₂
| isFalse n₁ => isFalse fun h => Prod.noConfusion rfl rfl (heq_of_eq h) fun e₁' _ => absurd (eq_of_heq e₁') n₁
instance [BEq α] [BEq β] : BEq (α × β) where
beq := fun (a₁, b₁) (a₂, b₂) => a₁ == a₂ && b₁ == b₂

View File

@@ -572,9 +572,6 @@ def unattach {α : Type _} {p : α → Prop} (xs : Array { x // p x }) : Array
@[simp] theorem unattach_empty {p : α Prop} : (#[] : Array { x // p x }).unattach = #[] := by
simp [unattach]
@[deprecated unattach_empty (since := "2025-05-26")]
abbrev unattach_nil := @unattach_empty
@[simp] theorem unattach_push {p : α Prop} {a : { x // p x }} {xs : Array { x // p x }} :
(xs.push a).unattach = xs.unattach.push a.1 := by
simp only [unattach, Array.map_push]

View File

@@ -589,6 +589,8 @@ unsafe def foldlMUnsafe {α : Type u} {β : Type v} {m : Type v → Type w} [Mon
if start < stop then
if stop as.size then
fold (USize.ofNat start) (USize.ofNat stop) init
else if start < as.size then
fold (USize.ofNat start) (USize.ofNat as.size) init
else
pure init
else
@@ -1348,7 +1350,7 @@ Examples:
* `#[2, 4, 5, 6].any (· % 2 = 0) = true`
* `#[2, 4, 5, 6].any (· % 2 = 1) = true`
-/
@[inline, expose]
@[inline, expose, suggest_for Array.some]
def any (as : Array α) (p : α Bool) (start := 0) (stop := as.size) : Bool :=
Id.run <| as.anyM (pure <| p ·) start stop
@@ -1366,7 +1368,7 @@ Examples:
* `#[2, 4, 6].all (· % 2 = 0) = true`
* `#[2, 4, 5, 6].all (· % 2 = 0) = false`
-/
@[inline]
@[inline, suggest_for Array.every]
def all (as : Array α) (p : α Bool) (start := 0) (stop := as.size) : Bool :=
Id.run <| as.allM (pure <| p ·) start stop

View File

@@ -73,9 +73,6 @@ theorem foldrM_eq_reverse_foldlM_toList [Monad m] {f : α → β → m β} {init
rcases xs with xs
simp [push, List.concat_eq_append]
@[deprecated toList_push (since := "2025-05-26")]
abbrev push_toList := @toList_push
@[simp, grind =] theorem toListAppend_eq {xs : Array α} {l : List α} : xs.toListAppend l = xs.toList ++ l := by
simp [toListAppend, foldr_toList]

View File

@@ -62,12 +62,12 @@ theorem size_eq_countP_add_countP {xs : Array α} : xs.size = countP p xs + coun
rcases xs with xs
simp [List.length_eq_countP_add_countP (p := p)]
@[grind =]
theorem countP_eq_size_filter {xs : Array α} : countP p xs = (filter p xs).size := by
rcases xs with xs
simp [List.countP_eq_length_filter]
@[grind =]
grind_pattern countP_eq_size_filter => xs.countP p, xs.filter p
theorem countP_eq_size_filter' : countP p = size filter p := by
funext xs
apply countP_eq_size_filter

View File

@@ -99,23 +99,23 @@ instance instDecidableEq [DecidableEq α] : DecidableEq (Array α) := fun xs ys
| [] =>
match ys with
| [] => isTrue rfl
| _ :: _ => isFalse (Array.noConfusion · (List.noConfusion ·))
| _ :: _ => isFalse (fun h => Array.noConfusion rfl (heq_of_eq h) (fun h => List.noConfusion rfl h))
| a :: as =>
match ys with
| [] => isFalse (Array.noConfusion · (List.noConfusion ·))
| [] => isFalse (fun h => Array.noConfusion rfl (heq_of_eq h) (fun h => List.noConfusion rfl h))
| b :: bs => instDecidableEqImpl a :: as b :: bs
@[csimp]
theorem instDecidableEq_csimp : @instDecidableEq = @instDecidableEqImpl :=
Subsingleton.allEq _ _
/--
Equality with `#[]` is decidable even if the underlying type does not have decidable equality.
-/
instance instDecidableEqEmp (xs : Array α) : Decidable (xs = #[]) :=
match xs with
| [] => isTrue rfl
| _ :: _ => isFalse (Array.noConfusion · (List.noConfusion ·))
| _ :: _ => isFalse (fun h => Array.noConfusion rfl (heq_of_eq h) (fun h => List.noConfusion rfl h))
/--
Equality with `#[]` is decidable even if the underlying type does not have decidable equality.
@@ -123,7 +123,7 @@ Equality with `#[]` is decidable even if the underlying type does not have decid
instance instDecidableEmpEq (ys : Array α) : Decidable (#[] = ys) :=
match ys with
| [] => isTrue rfl
| _ :: _ => isFalse (Array.noConfusion · (List.noConfusion ·))
| _ :: _ => isFalse (fun h => Array.noConfusion rfl (heq_of_eq h) (fun h => List.noConfusion rfl h))
theorem beq_eq_decide [BEq α] (xs ys : Array α) :
(xs == ys) = if h : xs.size = ys.size then

View File

@@ -389,9 +389,6 @@ theorem eraseIdx_append_of_size_le {xs : Array α} {k : Nat} (hk : xs.size ≤ k
simp at hk
simp [List.eraseIdx_append_of_length_le, *]
@[deprecated eraseIdx_append_of_size_le (since := "2025-06-11")]
abbrev eraseIdx_append_of_length_le := @eraseIdx_append_of_size_le
@[grind =]
theorem eraseIdx_append {xs ys : Array α} (h : k < (xs ++ ys).size) :
eraseIdx (xs ++ ys) k =

View File

@@ -159,9 +159,6 @@ theorem find?_singleton {a : α} {p : α → Bool} :
findRev? p (xs.push a) = findRev? p xs := by
cases xs; simp [h]
@[deprecated findRev?_push_of_neg (since := "2025-06-12")]
abbrev findRev?_cons_of_neg := @findRev?_push_of_neg
@[grind =]
theorem finRev?_push {xs : Array α} :
findRev? p (xs.push a) = (Option.guard p a).or (xs.findRev? p) := by
@@ -171,9 +168,6 @@ theorem finRev?_push {xs : Array α} :
· rw [findRev?_push_of_pos, Option.guard_eq_some_iff.mpr rfl, h]
all_goals simp [h]
@[deprecated finRev?_push (since := "2025-06-12")]
abbrev findRev?_cons := @finRev?_push
@[simp, grind =] theorem find?_eq_none : find? p xs = none x xs, ¬ p x := by
cases xs; simp

View File

@@ -53,11 +53,6 @@ theorem eraseIdx_insertIdx_self {i : Nat} {xs : Array α} (h : i ≤ xs.size) :
rcases xs with xs
simp_all
@[deprecated eraseIdx_insertIdx_self (since := "2025-06-15")]
theorem eraseIdx_insertIdx {i : Nat} {xs : Array α} (h : i xs.size) :
(xs.insertIdx i a).eraseIdx i (by simp; omega) = xs := by
simp [eraseIdx_insertIdx_self]
theorem insertIdx_eraseIdx_of_ge {as : Array α}
(w₁ : i < as.size) (w₂ : j (as.eraseIdx i).size) (h : i j) :
(as.eraseIdx i).insertIdx j a =

View File

@@ -62,6 +62,9 @@ theorem eq_empty_of_size_eq_zero (h : xs.size = 0) : xs = #[] := by
cases xs
simp_all
grind_pattern eq_empty_of_size_eq_zero => xs.size where
guard xs.size = 0
theorem ne_empty_of_size_eq_add_one (h : xs.size = n + 1) : xs #[] := by
cases xs
simpa using List.ne_nil_of_length_eq_add_one h
@@ -1758,11 +1761,6 @@ theorem toArray_append {xs : List α} {ys : Array α} :
theorem singleton_eq_toArray_singleton {a : α} : #[a] = [a].toArray := rfl
@[deprecated empty_append (since := "2025-05-26")]
theorem empty_append_fun : ((#[] : Array α) ++ ·) = id := by
funext l
simp
@[simp, grind =] theorem mem_append {a : α} {xs ys : Array α} : a xs ++ ys a xs a ys := by
simp only [mem_def, toList_append, List.mem_append]
@@ -3248,14 +3246,6 @@ rather than `(arr.push a).size` as the argument.
l.foldl (fun xs x => xs.push x) xs = xs ++ l.toArray := by
simpa using List.foldl_push_eq_append (f := id)
@[deprecated _root_.List.foldl_push_eq_append' (since := "2025-05-18")]
theorem _root_.List.foldl_push {l : List α} {as : Array α} : l.foldl Array.push as = as ++ l.toArray := by
induction l generalizing as <;> simp [*]
@[deprecated _root_.List.foldr_push_eq_append' (since := "2025-05-18")]
theorem _root_.List.foldr_push {l : List α} {as : Array α} : l.foldr (fun a bs => push bs a) as = as ++ l.reverse.toArray := by
rw [List.foldr_eq_foldl_reverse, List.foldl_push_eq_append']
-- TODO: a multi-pattern is being selected there because E-matching does not go inside lambdas.
@[simp, grind! ] theorem foldr_append_eq_append {xs : Array α} {f : α Array β} {ys : Array β} :
xs.foldr (f · ++ ·) ys = (xs.map f).flatten ++ ys := by
@@ -4334,11 +4324,6 @@ theorem getElem_eq_getD {xs : Array α} {i} {h : i < xs.size} (fallback : α) :
xs[i]'h = xs.getD i fallback := by
rw [getD_eq_getD_getElem?, getElem_eq_getElem?_get, Option.get_eq_getD]
/-! # mem -/
@[deprecated mem_toList_iff (since := "2025-05-26")]
theorem mem_toList {a : α} {xs : Array α} : a xs.toList a xs := mem_def.symm
/-! # get lemmas -/
theorem lt_of_getElem {x : α} {xs : Array α} {i : Nat} {hidx : i < xs.size} (_ : xs[i] = x) :

View File

@@ -73,19 +73,11 @@ private theorem cons_lex_cons [BEq α] {lt : αα → Bool} {a b : α} {xs
(lt a b || a == b && xs.lex ys lt) := by
simp only [lex, size_append, List.size_toArray, List.length_cons, List.length_nil, Nat.zero_add,
Nat.add_min_add_left, Nat.add_lt_add_iff_left, Std.Rco.forIn'_eq_forIn'_toList]
conv =>
lhs; congr; congr
rw [cons_lex_cons.forIn'_congr_aux Std.Rco.toList_eq_if_roo rfl (fun _ _ _ => rfl)]
simp only [bind_pure_comp, map_pure]
rw [cons_lex_cons.forIn'_congr_aux (if_pos (by omega)) rfl (fun _ _ _ => rfl)]
simp only [Std.toList_roo_eq_toList_rco_of_isSome_succ? (lo := 0) (h := rfl),
Std.PRange.UpwardEnumerable.succ?, Nat.add_comm 1, Std.PRange.Nat.toList_rco_succ_succ,
Option.get_some, List.forIn'_cons, List.size_toArray, List.length_cons, List.length_nil,
Nat.lt_add_one, getElem_append_left, List.getElem_toArray, List.getElem_cons_zero]
cases lt a b
· rw [bne]
cases a == b <;> simp
· simp
rw [cons_lex_cons.forIn'_congr_aux (Nat.toList_rco_eq_cons (by omega)) rfl (fun _ _ _ => rfl)]
simp only [bind_pure_comp, map_pure, Nat.toList_rco_succ_succ, Nat.add_comm 1]
cases h : lt a b
· cases h' : a == b <;> simp [bne, *]
· simp [*]
@[simp, grind =] theorem _root_.List.lex_toArray [BEq α] {lt : α α Bool} {l₁ l₂ : List α} :
l₁.toArray.lex l₂.toArray lt = l₁.lex l₂ lt := by

View File

@@ -39,10 +39,6 @@ theorem map_toList_inj [Monad m] [LawfulMonad m]
@[simp, grind =] theorem idRun_mapM {xs : Array α} {f : α Id β} : (xs.mapM f).run = xs.map (f · |>.run) :=
mapM_pure
@[deprecated idRun_mapM (since := "2025-05-21")]
theorem mapM_id {xs : Array α} {f : α Id β} : xs.mapM f = xs.map f :=
mapM_pure
@[simp, grind =] theorem mapM_map [Monad m] [LawfulMonad m] {f : α β} {g : β m γ} {xs : Array α} :
(xs.map f).mapM g = xs.mapM (g f) := by
rcases xs with xs
@@ -201,13 +197,6 @@ theorem idRun_forIn'_yield_eq_foldl
xs.attach.foldl (fun b a, h => f a h b |>.run) init := by
simp
@[deprecated idRun_forIn'_yield_eq_foldl (since := "2025-05-21")]
theorem forIn'_yield_eq_foldl
{xs : Array α} (f : (a : α) a xs β β) (init : β) :
forIn' (m := Id) xs init (fun a m b => .yield (f a m b)) =
xs.attach.foldl (fun b a, h => f a h b) init :=
forIn'_pure_yield_eq_foldl _ _
@[simp, grind =] theorem forIn'_map [Monad m] [LawfulMonad m]
{xs : Array α} (g : α β) (f : (b : β) b xs.map g γ m (ForInStep γ)) :
forIn' (xs.map g) init f = forIn' xs init fun a h y => f (g a) (mem_map_of_mem h) y := by
@@ -249,13 +238,6 @@ theorem idRun_forIn_yield_eq_foldl
xs.foldl (fun b a => f a b |>.run) init := by
simp
@[deprecated idRun_forIn_yield_eq_foldl (since := "2025-05-21")]
theorem forIn_yield_eq_foldl
{xs : Array α} (f : α β β) (init : β) :
forIn (m := Id) xs init (fun a b => .yield (f a b)) =
xs.foldl (fun b a => f a b) init :=
forIn_pure_yield_eq_foldl _ _
@[simp, grind =] theorem forIn_map [Monad m] [LawfulMonad m]
{xs : Array α} {g : α β} {f : β γ m (ForInStep γ)} :
forIn (xs.map g) init f = forIn xs init fun a y => f (g a) y := by

View File

@@ -280,7 +280,7 @@ Checks whether any of the elements in a subarray satisfy a Boolean predicate.
The elements are tested starting at the lowest index and moving up. The search terminates as soon as
an element that satisfies the predicate is found.
-/
@[inline]
@[inline, suggest_for Subarray.some]
def any {α : Type u} (p : α Bool) (as : Subarray α) : Bool :=
Id.run <| as.anyM (pure <| p ·)
@@ -290,7 +290,7 @@ Checks whether all of the elements in a subarray satisfy a Boolean predicate.
The elements are tested starting at the lowest index and moving up. The search terminates as soon as
an element that does not satisfy the predicate is found.
-/
@[inline]
@[inline, suggest_for Subarray.every]
def all {α : Type u} (p : α Bool) (as : Subarray α) : Bool :=
Id.run <| as.allM (pure <| p ·)

View File

@@ -353,14 +353,6 @@ theorem zipWithM_eq_mapM_id_zipWith {m : Type v → Type w} [Monad m] [LawfulMon
/-! ### unzip -/
@[deprecated fst_unzip (since := "2025-05-26")]
theorem unzip_fst : (unzip l).fst = l.map Prod.fst := by
simp
@[deprecated snd_unzip (since := "2025-05-26")]
theorem unzip_snd : (unzip l).snd = l.map Prod.snd := by
simp
@[grind =]
theorem unzip_eq_map {xs : Array (α × β)} : unzip xs = (xs.map Prod.fst, xs.map Prod.snd) := by
cases xs

View File

@@ -77,9 +77,6 @@ Returns the `i`th least significant bit.
-/
@[inline, expose] def getLsb (x : BitVec w) (i : Fin w) : Bool := x.toNat.testBit i
@[deprecated getLsb (since := "2025-06-17"), inherit_doc getLsb]
abbrev getLsb' := @getLsb
/-- Returns the `i`th least significant bit, or `none` if `i ≥ w`. -/
@[inline, expose] def getLsb? (x : BitVec w) (i : Nat) : Option Bool :=
if h : i < w then some (getLsb x i, h) else none
@@ -89,9 +86,6 @@ Returns the `i`th most significant bit.
-/
@[inline] def getMsb (x : BitVec w) (i : Fin w) : Bool := x.getLsb w-1-i, by omega
@[deprecated getMsb (since := "2025-06-17"), inherit_doc getMsb]
abbrev getMsb' := @getMsb
/-- Returns the `i`th most significant bit or `none` if `i ≥ w`. -/
@[inline] def getMsb? (x : BitVec w) (i : Nat) : Option Bool :=
if h : i < w then some (getMsb x i, h) else none
@@ -296,7 +290,7 @@ Lean convention that division by zero returns zero.
Examples:
* `(7#4).sdiv 2 = 3#4`
* `(-9#4).sdiv 2 = -4#4`
* `(-8#4).sdiv 2 = -4#4`
* `(5#4).sdiv -2 = -2#4`
* `(-7#4).sdiv (-2) = 3#4`
-/
@@ -870,4 +864,17 @@ def clz (x : BitVec w) : BitVec w := clzAuxRec x (w - 1)
/-- Count the number of trailing zeros. -/
def ctz (x : BitVec w) : BitVec w := (x.reverse).clz
/-- Count the number of bits with value `1` downward from the `pos`-th bit to the
`0`-th bit of `x`, storing the result in `acc`. -/
def cpopNatRec (x : BitVec w) (pos acc : Nat) : Nat :=
match pos with
| 0 => acc
| n + 1 => x.cpopNatRec n (acc + (x.getLsbD n).toNat)
/-- Population count operation, to count the number of bits with value `1` in `x`.
Also known as `popcount`, `popcnt`.
-/
@[suggest_for BitVec.popcount BitVec.popcnt]
def cpop (x : BitVec w) : BitVec w := BitVec.ofNat w (cpopNatRec x w 0)
end BitVec

View File

@@ -835,7 +835,7 @@ execution. -/
structure DivModArgs (w : Nat) where
/-- the numerator (aka, dividend) -/
n : BitVec w
/-- the denumerator (aka, divisor)-/
/-- the denominator (aka, divisor)-/
d : BitVec w
/-- A `DivModState` is lawful if the remainder width `wr` plus the numerator width `wn` equals `w`,

View File

@@ -145,10 +145,6 @@ theorem two_pow_le_toNat_of_getElem_eq_true {i : Nat} {x : BitVec w}
@[grind =] theorem getMsbD_eq_getLsbD (x : BitVec w) (i : Nat) : x.getMsbD i = (decide (i < w) && x.getLsbD (w - 1 - i)) := by
rw [getMsbD, getLsbD]
@[deprecated getMsb_eq_getLsb (since := "2025-06-17")]
theorem getMsb'_eq_getLsb' (x : BitVec w) (i : Nat) : x.getMsbD i = (decide (i < w) && x.getLsbD (w - 1 - i)) := by
rw [getMsbD, getLsbD]
theorem getLsbD_eq_getMsbD (x : BitVec w) (i : Nat) : x.getLsbD i = (decide (i < w) && x.getMsbD (w - 1 - i)) := by
rw [getMsbD]
by_cases h₁ : i < w <;> by_cases h₂ : w - 1 - i < w <;>
@@ -1023,6 +1019,14 @@ theorem setWidth_ofNat_one_eq_ofNat_one_of_lt {v w : Nat} (hv : 0 < v) :
rw [Nat.mod_mod_of_dvd]
exact Nat.pow_dvd_pow_iff_le_right'.mpr h
@[simp]
theorem setWidth_ofNat_of_le_of_lt {x : Nat} (h : w v) (h' : x < 2 ^ w) :
setWidth v (BitVec.ofNat w x) = BitVec.ofNat v x := by
apply BitVec.eq_of_toNat_eq
have := Nat.pow_le_pow_of_le (a := 2) (m := v) (n := w) (by omega) h
simp only [toNat_setWidth, toNat_ofNat]
rw [Nat.mod_eq_of_lt (by omega), Nat.mod_eq_of_lt (by omega), Nat.mod_eq_of_lt (by omega)]
/--
Iterated `setWidth` agrees with the second `setWidth`
except in the case the first `setWidth` is a non-trivial truncation,
@@ -1256,11 +1260,31 @@ theorem extractLsb'_setWidth_of_le {b : BitVec w} {start len w' : Nat} (h : star
simp
omega
@[simp]
theorem extractLsb_setWidth_of_lt {x : BitVec w} {hi lo v : Nat} (h : lo + hi < v) :
(x.setWidth v).extractLsb hi lo = x.extractLsb hi lo := by
simp only [BitVec.extractLsb]
ext k hk
simp
omega
theorem setWidth_extractLsb'_of_le {c : BitVec w} (h : len₁ len₂) :
(c.extractLsb' start len₂).setWidth len₁ = c.extractLsb' start len₁ := by
ext i hi
simp [show i < len₂ by omega]
theorem extractLsb'_cast {x : BitVec w} :
(x.cast hcast).extractLsb' start len = x.extractLsb' start len := by
ext k hk
simp
@[simp]
theorem extractLsb'_extractLsb'_of_le {x : BitVec w} (hlt : start + len len') :
(x.extractLsb' 0 len').extractLsb' start len = x.extractLsb' start len := by
ext k hk
simp
omega
/-! ### allOnes -/
@[simp, grind =] theorem toNat_allOnes : (allOnes v).toNat = 2^v - 1 := by
@@ -2917,6 +2941,15 @@ theorem setWidth_eq_append {v : Nat} {x : BitVec v} {w : Nat} (h : v ≤ w) :
omega
· simp [hiv, getLsbD_of_ge x i (by omega)]
@[simp]
theorem extractLsb'_append_extractLsb' {x : BitVec (w + len)} :
(x.extractLsb' len w ++ x.extractLsb' 0 len) = x := by
ext i hi
simp only [getElem_append, getElem_extractLsb', Nat.zero_add, dite_eq_ite]
split
· rw [ getLsbD_eq_getElem]
· simp [show len + (i - len) = i by omega, getLsbD_eq_getElem]
theorem setWidth_eq_extractLsb' {v : Nat} {x : BitVec v} {w : Nat} (h : w v) :
x.setWidth w = x.extractLsb' 0 w := by
rw [setWidth_eq_append_extractLsb']
@@ -3214,6 +3247,11 @@ theorem cons_append_append (x : BitVec w₁) (y : BitVec w₂) (z : BitVec w₃)
· simp [h₂]; omega
· simp [h₂]; omega
@[simp]
theorem extractLsb'_cons {x : BitVec w} :
(x.cons y).extractLsb' 0 w = x := by
simp [BitVec.toNat_eq, Nat.or_mod_two_pow, Nat.shiftLeft_eq]
/-! ### concat -/
@[simp, grind =] theorem toNat_concat (x : BitVec w) (b : Bool) :
@@ -3312,6 +3350,15 @@ theorem msb_concat {w : Nat} {b : Bool} {x : BitVec w} :
ext
simp [getElem_concat]
theorem extractLsb'_concat {x : BitVec (w + 1)} {y : Bool} :
(x.concat y).extractLsb' 0 (t + 1) = (x.extractLsb' 0 t).concat y := by
ext i hi
simp only [ getLsbD_eq_getElem, getLsbD_extractLsb', hi, decide_true, Nat.zero_add,
getLsbD_concat, Bool.true_and]
split
· simp
· simp [show i - 1 < t by omega]
/-! ### shiftConcat -/
@[grind =]
@@ -5601,7 +5648,7 @@ theorem msb_eq_toNat {x : BitVec w}:
simp only [msb_eq_decide, ge_iff_le]
/-- Negating a bitvector created from a natural number equals
creating a bitvector from the the negative of that number.
creating a bitvector from the negative of that number.
-/
theorem neg_ofNat_eq_ofInt_neg {w : Nat} {x : Nat} :
- BitVec.ofNat w x = BitVec.ofInt w (- x) := by
@@ -5816,6 +5863,16 @@ theorem reverse_reverse_eq {x : BitVec w} :
ext k hk
rw [getElem_reverse, getMsbD_reverse, getLsbD_eq_getElem]
@[simp]
theorem concat_reverse_setWidth_msb_eq_reverse {x : BitVec (w + 1)} :
concat ((x.setWidth w).reverse) x.msb = x.reverse := by
ext i hi
simp only [getElem_reverse, BitVec.msb, getElem_concat, getMsbD_setWidth, Nat.le_add_right,
Nat.sub_eq_zero_of_le, Nat.zero_le, decide_true, Bool.true_and, dite_eq_ite]
by_cases hzero : i = 0
· simp [hzero]
· simp [hzero, show i - 1 + (w + 1) - w = i by omega]
/-! ### Inequalities (le / lt) -/
theorem ule_eq_not_ult (x y : BitVec w) : x.ule y = !y.ult x := by
@@ -6291,4 +6348,241 @@ theorem two_pow_ctz_le_toNat_of_ne_zero {x : BitVec w} (hx : x ≠ 0#w) :
have hclz := getLsbD_true_ctz_of_ne_zero (x := x) hx
exact Nat.ge_two_pow_of_testBit hclz
/-! ### Population Count -/
@[simp]
theorem cpopNatRec_zero_self {x : BitVec w} :
x.cpopNatRec 0 acc = acc := rfl
@[simp]
theorem cpopNatRec_succ {n : Nat} {x : BitVec w} :
x.cpopNatRec (n + 1) acc = x.cpopNatRec n (acc + (x.getLsbD n).toNat) := rfl
@[simp]
theorem cpopNatRec_zero :
(0#w).cpopNatRec n acc = acc := by
induction n
· case zero =>
simp
· case succ n ihn =>
simp [ihn]
theorem cpopNatRec_eq {x : BitVec w} {n : Nat} (acc : Nat):
x.cpopNatRec n acc = x.cpopNatRec n 0 + acc := by
induction n generalizing acc
· case zero =>
simp
· case succ n ihn =>
simp [ihn (acc := acc + (x.getLsbD n).toNat), ihn (acc := (x.getLsbD n).toNat)]
omega
theorem cpopNatRec_add {x : BitVec w} {acc n : Nat} :
x.cpopNatRec n (acc + acc') = x.cpopNatRec n acc + acc' := by
rw [cpopNatRec_eq (acc := acc + acc'), cpopNatRec_eq (acc := acc), Nat.add_assoc]
theorem cpopNatRec_le {x : BitVec w} (n : Nat) :
x.cpopNatRec n acc acc + n := by
induction n generalizing acc
· case zero =>
simp
· case succ n ihn =>
have : (x.getLsbD n).toNat 1 := by cases x.getLsbD n <;> simp
specialize ihn (acc := acc + (x.getLsbD n).toNat)
simp
omega
@[simp]
theorem cpopNatRec_of_le {x : BitVec w} (k n : Nat) (hn : w n) :
x.cpopNatRec (n + k) acc = x.cpopNatRec n acc := by
induction k
· case zero =>
simp
· case succ k ihk =>
simp [show n + (k + 1) = (n + k) + 1 by omega, ihk, show w n + k by omega]
theorem cpopNatRec_zero_le (x : BitVec w) (n : Nat) :
x.cpopNatRec n 0 w := by
induction n
· case zero =>
simp
· case succ n ihn =>
by_cases hle : n w
· by_cases hx : x.getLsbD n
· have := cpopNatRec_le (x := x) (acc := 1) (by omega)
have := lt_of_getLsbD hx
simp [hx]
omega
· have := cpopNatRec_le (x := x) (acc := 0) (by omega)
simp [hx]
omega
· simp [show w n by omega]
omega
@[simp]
theorem cpopNatRec_allOnes (h : n w) :
(allOnes w).cpopNatRec n acc = acc + n := by
induction n
· case zero =>
simp
· case succ n ihn =>
specialize ihn (by omega)
simp [show n < w by omega, ihn,
cpopNatRec_add (acc := acc) (acc' := 1)]
omega
@[simp]
theorem cpop_allOnes :
(allOnes w).cpop = BitVec.ofNat w w := by
simp [cpop, cpopNatRec_allOnes]
@[simp]
theorem cpop_zero :
(0#w).cpop = 0#w := by
simp [cpop]
theorem toNat_cpop_le (x : BitVec w) :
x.cpop.toNat w := by
have hlt := Nat.lt_two_pow_self (n := w)
have hle := cpopNatRec_zero_le (x := x) (n := w)
simp only [cpop, toNat_ofNat, ge_iff_le]
rw [Nat.mod_eq_of_lt (by omega)]
exact hle
@[simp]
theorem cpopNatRec_cons_of_le {x : BitVec w} {b : Bool} (hn : n w) :
(cons b x).cpopNatRec n acc = x.cpopNatRec n acc := by
induction n generalizing acc
· case zero =>
simp
· case succ n ihn =>
specialize ihn (acc := acc + ((cons b x).getLsbD n).toNat) (by omega)
rw [cpopNatRec_succ, ihn, getLsbD_cons]
simp [show ¬ n = w by omega]
@[simp]
theorem cpopNatRec_cons_of_lt {x : BitVec w} {b : Bool} (hn : w < n) :
(cons b x).cpopNatRec n acc = b.toNat + x.cpopNatRec n acc := by
induction n generalizing acc
· case zero =>
omega
· case succ n ihn =>
by_cases hlt : w < n
· rw [cpopNatRec_succ, ihn (acc := acc + ((cons b x).getLsbD n).toNat) (by omega)]
simp [getLsbD_cons, show ¬ n = w by omega]
· simp [show w = n by omega, getElem_cons,
cpopNatRec_add (acc := acc) (acc' := b.toNat), Nat.add_comm]
theorem cpopNatRec_concat_of_lt {x : BitVec w} {b : Bool} (hn : 0 < n) :
(concat x b).cpopNatRec n acc = b.toNat + x.cpopNatRec (n - 1) acc := by
induction n generalizing acc
· case zero =>
omega
· case succ n ihn =>
by_cases hn0 : 0 < n
· specialize ihn (acc := (acc + ((x.concat b).getLsbD n).toNat)) (by omega)
rw [cpopNatRec_succ, ihn, cpopNatRec_add (acc := acc)]
simp [getLsbD_concat, show ¬ n = 0 by omega, show n + 1 - 1 = n - 1 + 1 by omega, cpopNatRec_add]
· simp [show n = 0 by omega]
omega
theorem toNat_cpop (x : BitVec w) :
x.cpop.toNat = x.cpopNatRec w 0 := by
have := cpopNatRec_zero_le x w
have := toNat_cpop_le x
have := Nat.lt_two_pow_self (n := w)
rw [cpop, toNat_ofNat, Nat.mod_eq_of_lt]
omega
@[simp]
theorem toNat_cpop_cons {x : BitVec w} {b : Bool} :
(x.cons b).cpop.toNat = b.toNat + x.cpop.toNat := by
simp [toNat_cpop, getElem_cons, cpopNatRec_eq (acc := b.toNat), Nat.add_comm]
@[simp]
theorem cpopNatRec_setWidth_of_le (x : BitVec w) (h : pos v) :
(setWidth v x).cpopNatRec pos acc = x.cpopNatRec pos acc := by
induction pos generalizing acc
· case zero =>
simp
· case succ pos ih =>
simp only [cpopNatRec_succ, getLsbD_setWidth]
rw [ih]
· congr
by_cases h : pos < v
<;> simp [h]
omega
· omega
theorem cpop_cons {x : BitVec w} {b : Bool} :
(x.cons b).cpop = b.toNat + x.cpop.setWidth (w + 1) := by
have := toNat_cpop_le x
have := Bool.toNat_lt b
simp only [natCast_eq_ofNat, toNat_eq, toNat_add, toNat_ofNat, toNat_setWidth, Nat.lt_add_one,
toNat_mod_cancel_of_lt, Nat.mod_add_mod]
rw [toNat_cpop_cons, Nat.mod_eq_of_lt]
omega
theorem cpop_concat {x : BitVec w} {b : Bool} :
(x.concat b).cpop = b.toNat + x.cpop.setWidth (w + 1) := by
have := cpopNatRec_zero_le (x := x) (n := w)
have := Nat.lt_two_pow_self (n := w)
rw [cpop, cpop, cpopNatRec_concat_of_lt,
Nat.add_one_sub_one, natCast_eq_ofNat, ofNat_add]
congr
rw [setWidth_ofNat_of_le_of_lt (x := x.cpopNatRec w 0) (by omega) (by omega)]
omega
@[simp]
theorem toNat_cpop_concat {x : BitVec w} {b : Bool} :
(x.concat b).cpop.toNat = b.toNat + x.cpop.toNat := by
have := toNat_cpop_le (x := x)
have := Nat.lt_two_pow_self (n := w + 1)
simp only [cpop_concat, natCast_eq_ofNat, toNat_add, toNat_ofNat, toNat_setWidth, Nat.lt_add_one,
toNat_mod_cancel_of_lt, Nat.mod_add_mod]
rw [Nat.mod_eq_of_lt]
cases b <;> (simp; omega)
theorem cpop_cons_eq_cpop_concat (x : BitVec w) :
(x.cons y).cpop = (x.concat y).cpop := by
rw [cpop_cons, cpop_concat]
@[simp]
theorem cpop_reverse (x : BitVec w) :
x.reverse.cpop = x.cpop := by
induction w
· case zero =>
simp [cpop, reverse]
· case succ w ihw =>
rw [ concat_reverse_setWidth_msb_eq_reverse, cpop_concat, ihw, cpop_cons]
simp
@[simp]
theorem cpopNatRec_cast_eq_of_eq {x : BitVec w} (p : w = v) :
(x.cast p).cpopNatRec n = x.cpopNatRec n := by
subst p; simp
@[simp]
theorem cpop_cast (x : BitVec w) (h : w = v) :
(x.cast h).cpop = x.cpop.cast h := by
simp [cpop, cpopNatRec_cast_eq_of_eq, h]
@[simp]
theorem toNat_cpop_append {x : BitVec w} {y : BitVec u} :
(x ++ y).cpop.toNat = x.cpop.toNat + y.cpop.toNat := by
induction w generalizing u
· case zero =>
simp [cpop]
· case succ w ihw =>
rw [ cons_msb_setWidth x, toNat_cpop_cons, cons_append, cpop_cast, toNat_cast,
toNat_cpop_cons, ihw, Nat.add_assoc]
theorem cpop_append {x : BitVec w} {y : BitVec u} :
(x ++ y).cpop = x.cpop.setWidth (w + u) + y.cpop.setWidth (w + u) := by
apply eq_of_toNat_eq
have := toNat_cpop_le x
have := toNat_cpop_le y
have := Nat.lt_two_pow_self (n := w + u)
simp only [toNat_cpop_append, toNat_add, toNat_setWidth, Nat.add_mod_mod, Nat.mod_add_mod]
rw [Nat.mod_eq_of_lt (by omega)]
end BitVec

View File

@@ -260,7 +260,7 @@ instance : Std.Associative (· != ·) := ⟨bne_assoc⟩
theorem eq_not_of_ne : {x y : Bool}, x y x = !y := by decide
/-! ### coercision related normal forms -/
/-! ### coercion related normal forms -/
theorem beq_eq_decide_eq [BEq α] [LawfulBEq α] [DecidableEq α] (a b : α) :
(a == b) = decide (a = b) := by

View File

@@ -269,6 +269,8 @@ unsafe def foldlMUnsafe {β : Type v} {m : Type v → Type w} [Monad m] (f : β
if start < stop then
if stop as.size then
fold (USize.ofNat start) (USize.ofNat stop) init
else if start < as.size then
fold (USize.ofNat start) (USize.ofNat as.size) init
else
pure init
else

View File

@@ -102,7 +102,7 @@ Returns `true` if the character is a uppercase ASCII letter.
The uppercase ASCII letters are the following: `ABCDEFGHIJKLMNOPQRSTUVWXYZ`.
-/
@[inline] def isUpper (c : Char) : Bool :=
c.val 65 && c.val 90
c.val 'A'.val c.val 'Z'.val
/--
Returns `true` if the character is a lowercase ASCII letter.
@@ -110,7 +110,7 @@ Returns `true` if the character is a lowercase ASCII letter.
The lowercase ASCII letters are the following: `abcdefghijklmnopqrstuvwxyz`.
-/
@[inline] def isLower (c : Char) : Bool :=
c.val 97 && c.val 122
c.val 'a'.val && c.val 'z'.val
/--
Returns `true` if the character is an ASCII letter.
@@ -126,7 +126,7 @@ Returns `true` if the character is an ASCII digit.
The ASCII digits are the following: `0123456789`.
-/
@[inline] def isDigit (c : Char) : Bool :=
c.val 48 && c.val 57
c.val '0'.val && c.val '9'.val
/--
Returns `true` if the character is an ASCII letter or digit.
@@ -143,9 +143,16 @@ alphabet are returned unchanged.
The uppercase ASCII letters are the following: `ABCDEFGHIJKLMNOPQRSTUVWXYZ`.
-/
@[inline]
def toLower (c : Char) : Char :=
let n := toNat c;
if n >= 65 n <= 90 then ofNat (n + 32) else c
if h : c.val 'A'.val c.val 'Z'.val then
c.val + ('a'.val - 'A'.val), ?_
else
c
where finally
have h : c.val.toBitVec.toNat + ('a'.val - 'A'.val).toBitVec.toNat < 0xd800 :=
Nat.add_lt_add_right (Nat.lt_of_le_of_lt h.2 (by decide)) _
exact .inl (lt_of_eq_of_lt (Nat.mod_eq_of_lt (Nat.lt_trans h (by decide))) h)
/--
Converts a lowercase ASCII letter to the corresponding uppercase letter. Letters outside the ASCII
@@ -153,8 +160,20 @@ alphabet are returned unchanged.
The lowercase ASCII letters are the following: `abcdefghijklmnopqrstuvwxyz`.
-/
@[inline]
def toUpper (c : Char) : Char :=
let n := toNat c;
if n >= 97 n <= 122 then ofNat (n - 32) else c
if h : c.val 'a'.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
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₂
end Char

View File

@@ -56,10 +56,6 @@ theorem Internal.ofNat_eq_ofNat {n : Nat} {hn} {a : Nat} :
letI : NeZero n := Nat.pos_iff_ne_zero.1 hn
Fin.Internal.ofNat n hn a = Fin.ofNat n a := rfl
@[deprecated Fin.ofNat (since := "2025-05-28")]
protected def ofNat' (n : Nat) [NeZero n] (a : Nat) : Fin n :=
Fin.ofNat n a
-- We provide this because other similar types have a `toNat` function, but `simp` rewrites
-- `i.toNat` to `i.val`.
/--

View File

@@ -18,8 +18,6 @@ namespace Fin
@[simp, grind =] theorem ofNat_zero (n : Nat) [NeZero n] : Fin.ofNat n 0 = 0 := rfl
@[deprecated ofNat_zero (since := "2025-05-28")] abbrev ofNat'_zero := @ofNat_zero
theorem mod_def (a m : Fin n) : a % m = Fin.mk (a.val % m.val) (Nat.lt_of_le_of_lt (Nat.mod_le _ _) a.2) :=
rfl
@@ -76,22 +74,16 @@ theorem mk_val (i : Fin n) : (⟨i, i.isLt⟩ : Fin n) = i := Fin.eta ..
@[simp, grind =] theorem val_ofNat (n : Nat) [NeZero n] (a : Nat) :
(Fin.ofNat n a).val = a % n := rfl
@[deprecated val_ofNat (since := "2025-05-28")] abbrev val_ofNat' := @val_ofNat
@[simp, grind =] theorem ofNat_self {n : Nat} [NeZero n] : Fin.ofNat n n = 0 := by
ext
simp
congr
@[deprecated ofNat_self (since := "2025-05-28")] abbrev ofNat'_self := @ofNat_self
@[simp] theorem ofNat_val_eq_self [NeZero n] (x : Fin n) : (Fin.ofNat n x.val) = x := by
ext
rw [val_ofNat, Nat.mod_eq_of_lt]
exact x.2
@[deprecated ofNat_val_eq_self (since := "2025-05-28")] abbrev ofNat'_val_eq_self := @ofNat_val_eq_self
@[simp] theorem mod_val (a b : Fin n) : (a % b).val = a.val % b.val :=
rfl
@@ -1084,15 +1076,11 @@ theorem ofNat_add [NeZero n] (x : Nat) (y : Fin n) :
apply Fin.eq_of_val_eq
simp [Fin.ofNat, Fin.add_def]
@[deprecated ofNat_add (since := "2025-05-28")] abbrev ofNat_add' := @ofNat_add
theorem add_ofNat [NeZero n] (x : Fin n) (y : Nat) :
x + Fin.ofNat n y = Fin.ofNat n (x.val + y) := by
apply Fin.eq_of_val_eq
simp [Fin.ofNat, Fin.add_def]
@[deprecated add_ofNat (since := "2025-05-28")] abbrev add_ofNat' := @add_ofNat
/-! ### sub -/
@[deprecated val_sub (since := "2025-11-21")]
@@ -1104,15 +1092,11 @@ theorem ofNat_sub [NeZero n] (x : Nat) (y : Fin n) :
apply Fin.eq_of_val_eq
simp [Fin.ofNat, Fin.sub_def]
@[deprecated ofNat_sub (since := "2025-05-28")] abbrev ofNat_sub' := @ofNat_sub
theorem sub_ofNat [NeZero n] (x : Fin n) (y : Nat) :
x - Fin.ofNat n y = Fin.ofNat n ((n - y % n) + x.val) := by
apply Fin.eq_of_val_eq
simp [Fin.ofNat, Fin.sub_def]
@[deprecated sub_ofNat (since := "2025-05-28")] abbrev sub_ofNat' := @sub_ofNat
@[simp] protected theorem sub_self [NeZero n] {x : Fin n} : x - x = 0 := by
ext
rw [Fin.sub_def]
@@ -1175,15 +1159,11 @@ theorem ofNat_mul [NeZero n] (x : Nat) (y : Fin n) :
apply Fin.eq_of_val_eq
simp [Fin.ofNat, Fin.mul_def]
@[deprecated ofNat_mul (since := "2025-05-28")] abbrev ofNat_mul' := @ofNat_mul
theorem mul_ofNat [NeZero n] (x : Fin n) (y : Nat) :
x * Fin.ofNat n y = Fin.ofNat n (x.val * y) := by
apply Fin.eq_of_val_eq
simp [Fin.ofNat, Fin.mul_def]
@[deprecated mul_ofNat (since := "2025-05-28")] abbrev mul_ofNat' := @mul_ofNat
@[deprecated val_mul (since := "2025-10-26")]
theorem coe_mul {n : Nat} : a b : Fin n, ((a * b : Fin n) : Nat) = a * b % n
| _, _, _, _ => rfl

View File

@@ -42,7 +42,7 @@ instance : EmptyCollection FloatArray where
def push : FloatArray Float FloatArray
| ds, b => ds.push b
@[extern "lean_float_array_size"]
@[extern "lean_float_array_size", tagged_return]
def size : (@& FloatArray) Nat
| ds => ds.size
@@ -144,6 +144,8 @@ unsafe def foldlMUnsafe {β : Type v} {m : Type v → Type w} [Monad m] (f : β
if start < stop then
if stop as.size then
fold (USize.ofNat start) (USize.ofNat stop) init
else if start < as.size then
fold (USize.ofNat start) (USize.ofNat as.size) init
else
pure init
else

View File

@@ -42,6 +42,7 @@ larger numbers use a fast arbitrary-precision arithmetic library (usually
than the platform's pointer size (i.e. 63 bits on 64-bit architectures and 31 bits on 32-bit
architectures).
-/
@[suggest_for ]
inductive Int : Type where
/--
A natural number is an integer.
@@ -278,7 +279,11 @@ set_option bootstrap.genMatcherCode false in
def decNonneg (m : @& Int) : Decidable (NonNeg m) :=
match m with
| ofNat m => isTrue <| NonNeg.mk m
| -[_ +1] => isFalse <| fun h => nomatch h
| -[i +1] => isFalse <| fun h =>
have : j, (j = -[i +1]) NonNeg j False := fun _ hj hnn =>
Int.NonNeg.casesOn (motive := fun j _ => j = -[i +1] False) hnn
(fun _ h => Int.noConfusion h) hj
this -[i +1] rfl h
/-- Decides whether `a ≤ b`.

View File

@@ -29,13 +29,6 @@ theorem subNatNat_of_sub_eq_succ {m n k : Nat} (h : n - m = succ k) : subNatNat
@[norm_cast] theorem natCast_succ (n : Nat) : (succ n : Int) = n + 1 := rfl
@[norm_cast] theorem natCast_add_one (n : Nat) : ((n + 1 : Nat) : Int) = n + 1 := rfl
@[deprecated natCast_add (since := "2025-04-17")]
theorem ofNat_add (n m : Nat) : ((n + m) : Int) = n + m := rfl
@[deprecated natCast_mul (since := "2025-04-17")]
theorem ofNat_mul (n m : Nat) : ((n * m) : Int) = n * m := rfl
@[deprecated natCast_succ (since := "2025-04-17")]
theorem ofNat_succ (n : Nat) : (succ n : Int) = n + 1 := rfl
theorem neg_ofNat_zero : -((0 : Nat) : Int) = 0 := rfl
theorem neg_ofNat_succ (n : Nat) : -(succ n : Int) = -[n+1] := rfl
@[simp] theorem neg_negSucc (n : Nat) : -(-[n+1]) = ((n + 1 : Nat) : Int) := rfl
@@ -340,6 +333,12 @@ protected theorem sub_sub_self (a b : Int) : a - (a - b) = b := by
@[simp] protected theorem add_sub_cancel (a b : Int) : a + b - b = a :=
Int.add_neg_cancel_right a b
protected theorem add_sub_add_right (n k m : Int) : (n + k) - (m + k) = n - m := by
rw [Int.add_comm m, Int.sub_sub, Int.add_sub_cancel]
protected theorem add_sub_add_left (k n m : Int) : (k + n) - (k + m) = n - m := by
rw [Int.add_comm k, Int.add_comm k, Int.add_sub_add_right]
protected theorem add_sub_assoc (a b c : Int) : a + b - c = a + (b - c) := by
rw [Int.sub_eq_add_neg, Int.add_assoc, Int.add_neg_eq_sub]
@@ -553,6 +552,7 @@ protected theorem mul_eq_zero {a b : Int} : a * b = 0 ↔ a = 0 b = 0 := by
| .ofNat 0, _, _ => by simp
| _, .ofNat 0, _ => by simp
| .ofNat (_+1), .negSucc _, h => by cases h
| .negSucc _, .negSucc _, h => by cases h
protected theorem mul_ne_zero {a b : Int} (a0 : a 0) (b0 : b 0) : a * b 0 :=
Or.rec a0 b0 Int.mul_eq_zero.mp

View File

@@ -81,10 +81,7 @@ theorem lt.dest {a b : Int} (h : a < b) : ∃ n : Nat, a + Nat.succ n = b :=
@[simp, norm_cast] theorem ofNat_lt {n m : Nat} : (n : Int) < m n < m := by
rw [lt_iff_add_one_le, natCast_succ, ofNat_le]; rfl
@[simp, norm_cast] theorem natCast_pos {n : Nat} : (0 : Int) < n 0 < n := ofNat_lt
@[deprecated natCast_pos (since := "2025-05-13"), simp high]
theorem ofNat_pos {n : Nat} : 0 < (n : Int) 0 < n := ofNat_lt
@[simp high, norm_cast] theorem natCast_pos {n : Nat} : (0 : Int) < n 0 < n := ofNat_lt
@[simp]
theorem natCast_nonneg (n : Nat) : 0 (n : Int) := _
@@ -92,6 +89,8 @@ theorem natCast_nonneg (n : Nat) : 0 ≤ (n : Int) := ⟨_⟩
@[deprecated natCast_nonneg (since := "2025-10-26")]
theorem ofNat_zero_le (n : Nat) : 0 (n : Int) := ofNat_le.2 n.zero_le
-- This was still being used in `omega` as of 2025-12-12,
-- so we're keeping this for another month.
@[deprecated natCast_nonneg (since := "2025-05-13")]
theorem ofNat_nonneg (n : Nat) : 0 (n : Int) := _
@@ -475,6 +474,20 @@ protected theorem max_lt {a b c : Int} : max a b < c ↔ a < c ∧ b < c := by
simp only [Int.lt_iff_add_one_le]
simpa using Int.max_le (a := a + 1) (b := b + 1) (c := c)
protected theorem max_eq_right_iff {a b : Int} : max a b = b a b := by
apply Iff.intro
· intro h
rw [ h]
apply Int.le_max_left
· apply Int.max_eq_right
protected theorem max_eq_left_iff {a b : Int} : max a b = a b a := by
apply Iff.intro
· intro h
rw [ h]
apply Int.le_max_right
· apply Int.max_eq_left
@[simp] theorem ofNat_max_zero (n : Nat) : (max (n : Int) 0) = n := by
rw [Int.max_eq_left (natCast_nonneg n)]
@@ -913,6 +926,16 @@ protected theorem sub_right_le_of_le_add {a b c : Int} (h : a ≤ b + c) : a - c
have h := Int.add_le_add_right h (-c)
rwa [Int.add_neg_cancel_right] at h
protected theorem sub_right_le_iff_le_add {a b c : Int} : a - c b a b + c :=
Int.le_add_of_sub_right_le, Int.sub_right_le_of_le_add
theorem toNat_sub_eq_zero_iff (m n : Int) : toNat (m - n) = 0 m n := by
rw [ ofNat_inj, ofNat_toNat, cast_ofNat_Int, Int.max_eq_right_iff, Int.sub_right_le_iff_le_add,
Int.zero_add]
theorem zero_eq_toNat_sub_iff (m n : Int) : 0 = toNat (m - n) m n := by
rw [eq_comm (a := 0), toNat_sub_eq_zero_iff]
protected theorem le_add_of_neg_add_le_left {a b c : Int} (h : -b + a c) : a b + c := by
rw [Int.add_comm] at h
exact Int.le_add_of_sub_left_le h
@@ -990,6 +1013,10 @@ protected theorem lt_sub_right_of_add_lt {a b c : Int} (h : a + b < c) : a < c -
have h := Int.add_lt_add_right h (-b)
rwa [Int.add_neg_cancel_right] at h
protected theorem lt_sub_right_iff_add_lt {a b c : Int} :
a < c - b a + b < c :=
Int.add_lt_of_lt_sub_right, Int.lt_sub_right_of_add_lt
protected theorem lt_add_of_neg_add_lt {a b c : Int} (h : -b + a < c) : a < b + c := by
have h := Int.add_lt_add_left h b
rwa [Int.add_neg_cancel_left] at h

View File

@@ -77,8 +77,6 @@ public theorem Shrink.deflate_inj {α} {x y : α} :
· rintro rfl
rfl
namespace Iterators
-- It is not fruitful to move the following docstrings to verso right now because there are lots of
-- forward references that cannot be realized nicely.
set_option doc.verso false
@@ -124,6 +122,7 @@ def x := ([1, 2, 3].iterM IO : IterM IO Nat)
-/
@[ext]
structure IterM {α : Type w} (m : Type w Type w') (β : Type w) where
mk' ::
/-- Internal implementation detail of the iterator. -/
internalState : α
@@ -293,6 +292,11 @@ theorem IterStep.mapIterator_id {step : IterStep α β} :
step.mapIterator id = step := by
cases step <;> rfl
@[simp]
theorem IterStep.mapIterator_id' {step : IterStep α β} :
step.mapIterator (fun x => x) = step := by
cases step <;> rfl
/--
A variant of `IterStep` that bundles the step together with a proof that it is "plausible".
The plausibility predicate will later be chosen to assert that a state is a plausible successor
@@ -306,7 +310,7 @@ def PlausibleIterStep (IsPlausibleStep : IterStep α β → Prop) := Subtype IsP
/--
Match pattern for the `yield` case. See also `IterStep.yield`.
-/
@[match_pattern, simp, expose]
@[match_pattern, simp, spec, expose]
def PlausibleIterStep.yield {IsPlausibleStep : IterStep α β Prop}
(it' : α) (out : β) (h : IsPlausibleStep (.yield it' out)) :
PlausibleIterStep IsPlausibleStep :=
@@ -315,7 +319,7 @@ def PlausibleIterStep.yield {IsPlausibleStep : IterStep α β → Prop}
/--
Match pattern for the `skip` case. See also `IterStep.skip`.
-/
@[match_pattern, simp, expose]
@[match_pattern, simp, grind =, expose]
def PlausibleIterStep.skip {IsPlausibleStep : IterStep α β Prop}
(it' : α) (h : IsPlausibleStep (.skip it')) : PlausibleIterStep IsPlausibleStep :=
.skip it', h
@@ -323,7 +327,7 @@ def PlausibleIterStep.skip {IsPlausibleStep : IterStep α β → Prop}
/--
Match pattern for the `done` case. See also `IterStep.done`.
-/
@[match_pattern, simp, expose]
@[match_pattern, simp, grind =, expose]
def PlausibleIterStep.done {IsPlausibleStep : IterStep α β Prop}
(h : IsPlausibleStep .done) : PlausibleIterStep IsPlausibleStep :=
.done, h
@@ -358,21 +362,27 @@ class Iterator (α : Type w) (m : Type w → Type w') (β : outParam (Type w)) w
section Monadic
/--
Converts wraps the state of an iterator into an `IterM` object.
Wraps the state of an iterator into an `IterM` object.
-/
@[always_inline, inline, expose]
def toIterM {α : Type w} (it : α) (m : Type w Type w') (β : Type w) :
def IterM.mk {α : Type w} (it : α) (m : Type w Type w') (β : Type w) :
IterM (α := α) m β :=
it
@[deprecated IterM.mk (since := "2025-12-01"), inline, expose]
def Iterators.toIterM := @IterM.mk
@[simp]
theorem toIterM_internalState {α m β} (it : IterM (α := α) m β) :
toIterM it.internalState m β = it :=
theorem IterM.mk_internalState {α m β} (it : IterM (α := α) m β) :
.mk it.internalState m β = it :=
rfl
@[deprecated IterM.mk_internalState (since := "2025-12-01")]
def Iterators.toIterM_internalState := @IterM.mk_internalState
@[simp]
theorem internalState_toIterM {α m β} (it : α) :
(toIterM it m β).internalState = it :=
(IterM.mk it m β).internalState = it :=
rfl
/--
@@ -393,6 +403,16 @@ abbrev IterM.Step {α : Type w} {m : Type w → Type w'} {β : Type w} [Iterator
(it : IterM (α := α) m β) :=
PlausibleIterStep it.IsPlausibleStep
/--
Makes a single step with the given iterator `it`, potentially emitting a value and providing a
succeeding iterator. If this function is used recursively, termination can sometimes be proved with
the termination measures `it.finitelyManySteps` and `it.finitelyManySkips`.
-/
@[always_inline, inline, expose]
def IterM.step {α : Type w} {m : Type w Type w'} {β : Type w} [Iterator α m β]
(it : IterM (α := α) m β) : m (Shrink it.Step) :=
Iterator.step it
/--
Asserts that a certain output value could plausibly be emitted by the given iterator in its next
step.
@@ -420,16 +440,6 @@ def IterM.IsPlausibleSkipSuccessorOf {α : Type w} {m : Type w → Type w'} {β
[Iterator α m β] (it' it : IterM (α := α) m β) : Prop :=
it.IsPlausibleStep (.skip it')
/--
Makes a single step with the given iterator `it`, potentially emitting a value and providing a
succeeding iterator. If this function is used recursively, termination can sometimes be proved with
the termination measures `it.finitelyManySteps` and `it.finitelyManySkips`.
-/
@[always_inline, inline, expose]
def IterM.step {α : Type w} {m : Type w Type w'} {β : Type w} [Iterator α m β]
(it : IterM (α := α) m β) : m (Shrink it.Step) :=
Iterator.step it
end Monadic
section Pure
@@ -677,11 +687,11 @@ this means that the relation of plausible successors is well-founded.
Given this typeclass, termination proofs for well-founded recursion over an iterator `it` can use
`it.finitelyManySteps` as a termination measure.
-/
class Finite (α : Type w) (m : Type w Type w') {β : Type w} [Iterator α m β] : Prop where
class Iterators.Finite (α : Type w) (m : Type w Type w') {β : Type w} [Iterator α m β] : Prop where
/-- The relation of plausible successors is well-founded. -/
wf : WellFounded (IterM.IsPlausibleSuccessorOf (α := α) (m := m))
theorem Finite.wf_of_id {α : Type w} {β : Type w} [Iterator α Id β] [Finite α Id] :
theorem Iterators.Finite.wf_of_id {α : Type w} {β : Type w} [Iterator α Id β] [Finite α Id] :
WellFounded (Iter.IsPlausibleSuccessorOf (α := α)) := by
simpa [Iter.isPlausibleSuccessorOf_eq_invImage] using InvImage.wf _ Finite.wf
@@ -703,10 +713,11 @@ def IterM.TerminationMeasures.Finite.Rel
TerminationMeasures.Finite α m TerminationMeasures.Finite α m Prop :=
Relation.TransGen <| InvImage IterM.IsPlausibleSuccessorOf IterM.TerminationMeasures.Finite.it
instance {α : Type w} {m : Type w Type w'} {β : Type w} [Iterator α m β]
[Finite α m] : WellFoundedRelation (IterM.TerminationMeasures.Finite α m) where
instance IterM.TerminationMeasures.instWellFoundedRelationFinite {α : Type w} {m : Type w Type w'}
{β : Type w} [Iterator α m β] [Iterators.Finite α m] :
WellFoundedRelation (IterM.TerminationMeasures.Finite α m) where
rel := IterM.TerminationMeasures.Finite.Rel
wf := by exact (InvImage.wf _ Finite.wf).transGen
wf := by exact (InvImage.wf _ Iterators.Finite.wf).transGen
/--
Termination measure to be used in well-founded recursive functions recursing over a finite iterator
@@ -714,7 +725,16 @@ Termination measure to be used in well-founded recursive functions recursing ove
-/
@[expose]
def IterM.finitelyManySteps {α : Type w} {m : Type w Type w'} {β : Type w} [Iterator α m β]
[Finite α m] (it : IterM (α := α) m β) : IterM.TerminationMeasures.Finite α m :=
[Iterators.Finite α m] (it : IterM (α := α) m β) : IterM.TerminationMeasures.Finite α m :=
it
/--
Termination measure to be used in well-founded recursive functions recursing over a finite iterator
(see also `Finite`).
-/
@[expose]
def IterM.finitelyManySteps! {α : Type w} {m : Type w Type w'} {β : Type w} [Iterator α m β]
(it : IterM (α := α) m β) : IterM.TerminationMeasures.Finite α m :=
it
/--
@@ -747,7 +767,7 @@ macro_rules | `(tactic| decreasing_trivial) => `(tactic|
| fail)
@[inherit_doc IterM.finitelyManySteps, expose]
def Iter.finitelyManySteps {α : Type w} {β : Type w} [Iterator α Id β] [Finite α Id]
def Iter.finitelyManySteps {α : Type w} {β : Type w} [Iterator α Id β] [Iterators.Finite α Id]
(it : Iter (α := α) β) : IterM.TerminationMeasures.Finite α Id :=
it.toIterM.finitelyManySteps
@@ -797,7 +817,7 @@ well-founded.
Given this typeclass, termination proofs for well-founded recursion over an iterator `it` can use
`it.finitelyManySkips` as a termination measure.
-/
class Productive (α m) {β} [Iterator α m β] : Prop where
class Iterators.Productive (α m) {β} [Iterator α m β] : Prop where
/-- The relation of plausible successors during skips is well-founded. -/
wf : WellFounded (IterM.IsPlausibleSkipSuccessorOf (α := α) (m := m))
@@ -837,10 +857,11 @@ theorem IterM.TerminationMeasures.Finite.Rel.of_productive
refine .trans ih ?_
exact .single _, rfl, hab
instance {α : Type w} {m : Type w Type w'} {β : Type w} [Iterator α m β]
[Productive α m] : WellFoundedRelation (IterM.TerminationMeasures.Productive α m) where
instance IterM.TerminationMeasures.instWellFoundedRelationProductive {α : Type w}
{m : Type w Type w'} {β : Type w} [Iterator α m β] [Iterators.Productive α m] :
WellFoundedRelation (IterM.TerminationMeasures.Productive α m) where
rel := IterM.TerminationMeasures.Productive.Rel
wf := by exact (InvImage.wf _ Productive.wf).transGen
wf := by exact (InvImage.wf _ Iterators.Productive.wf).transGen
/--
Termination measure to be used in well-founded recursive functions recursing over a productive
@@ -848,7 +869,7 @@ iterator (see also `Productive`).
-/
@[expose]
def IterM.finitelyManySkips {α : Type w} {m : Type w Type w'} {β : Type w} [Iterator α m β]
[Productive α m] (it : IterM (α := α) m β) : IterM.TerminationMeasures.Productive α m :=
[Iterators.Productive α m] (it : IterM (α := α) m β) : IterM.TerminationMeasures.Productive α m :=
it
/--
@@ -867,7 +888,7 @@ macro_rules | `(tactic| decreasing_trivial) => `(tactic|
| fail)
@[inherit_doc IterM.finitelyManySkips, expose]
def Iter.finitelyManySkips {α : Type w} {β : Type w} [Iterator α Id β] [Productive α Id]
def Iter.finitelyManySkips {α : Type w} {β : Type w} [Iterator α Id β] [Iterators.Productive α Id]
(it : Iter (α := α) β) : IterM.TerminationMeasures.Productive α Id :=
it.toIterM.finitelyManySkips
@@ -886,12 +907,13 @@ macro_rules | `(tactic| decreasing_trivial) => `(tactic|
| exact Iter.TerminationMeasures.Productive.rel_of_skip _
| fail)
instance [Iterator α m β] [Finite α m] : Productive α m where
instance Iterators.instProductiveOfFinte [Iterator α m β] [Iterators.Finite α m] :
Iterators.Productive α m where
wf := by
apply Subrelation.wf (r := IterM.IsPlausibleSuccessorOf)
· intro it' it h
exact IterM.isPlausibleSuccessorOf_of_skip h
· exact Finite.wf
· exact Iterators.Finite.wf
end Productive
@@ -910,8 +932,4 @@ class LawfulDeterministicIterator (α : Type w) (m : Type w → Type w') [Iterat
where
isPlausibleStep_eq_eq : it : IterM (α := α) m β, step, it.IsPlausibleStep = (· = step)
end Iterators
export Iterators (Iter IterM)
end Std

View File

@@ -11,7 +11,8 @@ public import Init.Data.Iterators.Combinators.FilterMap
public section
namespace Std.Iterators
namespace Std
open Std.Iterators
@[always_inline, inline, expose, inherit_doc IterM.attachWith]
def Iter.attachWith {α β : Type w}
@@ -24,4 +25,4 @@ where finally
simp only [ isPlausibleIndirectOutput_iff_isPlausibleIndirectOutput_toIterM]
exact h
end Std.Iterators
end Std

View File

@@ -30,7 +30,8 @@ Several variants of these combinators are provided:
iterator, and particularly for specialized termination proofs. If possible, avoid this.
-/
namespace Std.Iterators
namespace Std
open Std.Iterators
-- We cannot use `inherit_doc` because the docstring for `IterM` states that a `MonadLiftT` instance
-- is needed.
@@ -197,12 +198,8 @@ it.filterMapM ---a'-----c'-------⊥
For certain mapping functions `f`, the resulting iterator will be finite (or productive) even though
no `Finite` (or `Productive`) instance is provided. For example, if `f` never returns `none`, then
this combinator will preserve productiveness. If `f` is an `ExceptT` monad and will always fail,
then `it.filterMapM` will be finite even if `it` isn't. In the first case, consider
using the `map`/`mapM`/`mapWithPostcondition` combinators instead, which provide more instances out
of the box.
If that does not help, the more general combinator `it.filterMapWithPostcondition f` makes it
possible to manually prove `Finite` and `Productive` instances depending on the concrete choice of `f`.
then `it.filterMapM` will be finite even if `it` isn't. In such cases, the termination proof needs
to be done manually.
**Performance:**
@@ -211,7 +208,7 @@ returned `Option` value.
-/
@[always_inline, inline, expose]
def Iter.filterMapM {α β γ : Type w} [Iterator α Id β] {m : Type w Type w'}
[Monad m] (f : β m (Option γ)) (it : Iter (α := α) β) :=
[Monad m] [MonadAttach m] (f : β m (Option γ)) (it : Iter (α := α) β) :=
(letI : MonadLift Id m := pure; it.toIterM.filterMapM f : IterM m γ)
/--
@@ -237,10 +234,7 @@ it.filterM ---a-----c-------⊥
For certain mapping functions `f`, the resulting iterator will be finite (or productive) even though
no `Finite` (or `Productive`) instance is provided. For example, if `f` is an `ExceptT` monad and
will always fail, then `it.filterWithPostcondition` will be finite -- and productive -- even if `it`
isn't.
In such situations, the more general combinator `it.filterWithPostcondition f` makes it possible to
manually prove `Finite` and `Productive` instances depending on the concrete choice of `f`.
isn't. In such cases, the termination proof needs to be done manually.
**Performance:**
@@ -248,7 +242,7 @@ For each value emitted by the base iterator `it`, this combinator calls `f`.
-/
@[always_inline, inline, expose]
def Iter.filterM {α β : Type w} [Iterator α Id β] {m : Type w Type w'}
[Monad m] (f : β m (ULift Bool)) (it : Iter (α := α) β) :=
[Monad m] [MonadAttach m] (f : β m (ULift Bool)) (it : Iter (α := α) β) :=
(letI : MonadLift Id m := pure; it.toIterM.filterM f : IterM m β)
/--
@@ -276,10 +270,8 @@ it.mapM ---a'--b'--c'--d'-e'----⊥
For certain mapping functions `f`, the resulting iterator will be finite (or productive) even though
no `Finite` (or `Productive`) instance is provided. For example, if `f` is an `ExceptT` monad and
will always fail, then `it.mapM` will be finite even if `it` isn't.
If that does not help, the more general combinator `it.mapWithPostcondition f` makes it possible to
manually prove `Finite` and `Productive` instances depending on the concrete choice of `f`.
will always fail, then `it.mapM` will be finite even if `it` isn't. In such cases, the termination
proof needs to be done manually.
**Performance:**
@@ -287,7 +279,7 @@ For each value emitted by the base iterator `it`, this combinator calls `f`.
-/
@[always_inline, inline, expose]
def Iter.mapM {α β γ : Type w} [Iterator α Id β] {m : Type w Type w'}
[Monad m] (f : β m γ) (it : Iter (α := α) β) :=
[Monad m] [MonadAttach m] (f : β m γ) (it : Iter (α := α) β) :=
(letI : MonadLift Id m := pure; it.toIterM.mapM f : IterM m γ)
@[always_inline, inline, inherit_doc IterM.filterMap, expose]
@@ -305,4 +297,4 @@ def Iter.map {α : Type w} {β : Type w} {γ : Type w} [Iterator α Id β]
(f : β γ) (it : Iter (α := α) β) :=
((it.toIterM.map f).toIter : Iter γ)
end Std.Iterators
end Std

View File

@@ -24,17 +24,17 @@ and so on. In other words, {lit}`it` flattens the iterator of iterators obtained
{lit}`f`.
-/
namespace Std.Iterators
namespace Std
@[always_inline, inherit_doc IterM.flatMapAfterM]
public def Iter.flatMapAfterM {α : Type w} {β : Type w} {α₂ : Type w}
{γ : Type w} {m : Type w Type w'} [Monad m] [Iterator α Id β] [Iterator α₂ m γ]
{γ : Type w} {m : Type w Type w'} [Monad m] [MonadAttach m] [Iterator α Id β] [Iterator α₂ m γ]
(f : β m (IterM (α := α₂) m γ)) (it₁ : Iter (α := α) β) (it₂ : Option (IterM (α := α₂) m γ)) :=
((it₁.mapM pure).flatMapAfterM f it₂ : IterM m γ)
((it₁.mapWithPostcondition pure).flatMapAfterM f it₂ : IterM m γ)
@[always_inline, expose, inherit_doc IterM.flatMapM]
public def Iter.flatMapM {α : Type w} {β : Type w} {α₂ : Type w}
{γ : Type w} {m : Type w Type w'} [Monad m] [Iterator α Id β] [Iterator α₂ m γ]
{γ : Type w} {m : Type w Type w'} [Monad m] [MonadAttach m] [Iterator α Id β] [Iterator α₂ m γ]
(f : β m (IterM (α := α₂) m γ)) (it : Iter (α := α) β) :=
(it.flatMapAfterM f none : IterM m γ)
@@ -49,5 +49,3 @@ public def Iter.flatMap {α : Type w} {β : Type w} {α₂ : Type w}
{γ : Type w} [Iterator α Id β] [Iterator α₂ Id γ]
(f : β Iter (α := α₂) γ) (it : Iter (α := α) β) :=
(it.flatMapAfter f none : Iter γ)
end Std.Iterators

View File

@@ -86,27 +86,12 @@ instance Attach.instProductive {α β : Type w} {m : Type w → Type w'} [Monad
Productive (Attach α m P) m :=
.of_productivenessRelation instProductivenessRelation
instance Attach.instIteratorCollect {α β : Type w} {m : Type w Type w'} [Monad m] [Monad n]
{P : β Prop} [Iterator α m β] :
IteratorCollect (Attach α m P) m n :=
.defaultImplementation
instance Attach.instIteratorCollectPartial {α β : Type w} {m : Type w Type w'} [Monad m]
[Monad n] {P : β Prop} [Iterator α m β] :
IteratorCollectPartial (Attach α m P) m n :=
.defaultImplementation
instance Attach.instIteratorLoop {α β : Type w} {m : Type w Type w'} [Monad m]
{n : Type x Type x'} [Monad n] {P : β Prop} [Iterator α m β] :
IteratorLoop (Attach α m P) m n :=
.defaultImplementation
instance Attach.instIteratorLoopPartial {α β : Type w} {m : Type w Type w'} [Monad m]
{n : Type x Type x'} [Monad n] {P : β Prop} [Iterator α m β] :
IteratorLoopPartial (Attach α m P) m n :=
.defaultImplementation
end Types
end Iterators.Types
/--
“Attaches” individual proofs to an iterator of values that satisfy a predicate `P`, returning an
@@ -121,7 +106,7 @@ iterator with values in the corresponding subtype `{ x // P x }`.
def IterM.attachWith {α β : Type w} {m : Type w Type w'} [Monad m]
[Iterator α m β] (it : IterM (α := α) m β) (P : β Prop)
(h : out, it.IsPlausibleIndirectOutput out P out) :
IterM (α := Types.Attach α m P) m { out : β // P out } :=
IterM (α := Iterators.Types.Attach α m P) m { out : β // P out } :=
it, h
end Std.Iterators
end Std

View File

@@ -32,7 +32,9 @@ Several variants of these combinators are provided:
iterator, and particularly for specialized termination proofs. If possible, avoid this.
-/
namespace Std.Iterators
namespace Std
namespace Iterators.Types
/--
Internal state of the `filterMap` combinator. Do not depend on its internals.
@@ -53,19 +55,23 @@ def Map (α : Type w) {β γ : Type w} (m : Type w → Type w') (n : Type w →
(f : β PostconditionT n γ) :=
FilterMap α m n lift (fun b => PostconditionT.map some (f b))
end Iterators.Types
open Std.Iterators Std.Iterators.Types
@[always_inline, inline, expose]
def IterM.InternalCombinators.filterMap {α β γ : Type w} {m : Type w Type w'}
{n : Type w Type w''} (lift : α : Type w m α n α)
[Iterator α m β] (f : β PostconditionT n (Option γ))
(it : IterM (α := α) m β) : IterM (α := FilterMap α m n lift f) n γ :=
toIterM it n γ
.mk it n γ
@[always_inline, inline, expose]
def IterM.InternalCombinators.map {α β γ : Type w} {m : Type w Type w'}
{n : Type w Type w''} [Monad n] (lift : α : Type w m α n α)
[Iterator α m β] (f : β PostconditionT n γ)
(it : IterM (α := α) m β) : IterM (α := Map α m n lift f) n γ :=
toIterM it n γ
.mk it n γ
/--
*Note: This is a very general combinator that requires an advanced understanding of monads,
@@ -117,16 +123,18 @@ returned `Option` value.
def IterM.filterMapWithPostcondition {α β γ : Type w} {m : Type w Type w'} {n : Type w Type w''}
[MonadLiftT m n] [Iterator α m β] (f : β PostconditionT n (Option γ))
(it : IterM (α := α) m β) : IterM (α := FilterMap α m n (fun _ => monadLift) f) n γ :=
IterM.InternalCombinators.filterMap (fun _ => monadLift) f it
IterM.InternalCombinators.filterMap (n := n) (fun _ => monadLift) f it
namespace Iterators.Types
/--
`it.PlausibleStep step` is the proposition that `step` is a possible next step from the
`filterMap` iterator `it`. This is mostly internally relevant, except if one needs to manually
prove termination (`Finite` or `Productive` instances, for example) of a `filterMap` iterator.
-/
inductive FilterMap.PlausibleStep {α β γ : Type w} {m : Type w Type w'} {n : Type w Type w''}
{lift : α : Type w m α n α} {f : β PostconditionT n (Option γ)} [Iterator α m β]
(it : IterM (α := FilterMap α m n lift f) n γ) :
inductive FilterMap.PlausibleStep {α β γ : Type w} {m : Type w Type w'}
{n : Type w Type w''} {lift : α : Type w m α n α} {f : β PostconditionT n (Option γ)}
[Iterator α m β] (it : IterM (α := FilterMap α m n lift f) n γ) :
IterStep (IterM (α := FilterMap α m n lift f) n γ) γ Prop where
| yieldNone : {it' out},
it.internalState.inner.IsPlausibleStep (.yield it' out)
@@ -139,8 +147,8 @@ inductive FilterMap.PlausibleStep {α β γ : Type w} {m : Type w → Type w'} {
PlausibleStep it (.skip (IterM.InternalCombinators.filterMap lift f it'))
| done : it.internalState.inner.IsPlausibleStep .done PlausibleStep it .done
instance FilterMap.instIterator {α β γ : Type w} {m : Type w Type w'} {n : Type w Type w''}
{lift : α : Type w m α n α} {f : β PostconditionT n (Option γ)}
instance FilterMap.instIterator {α β γ : Type w} {m : Type w Type w'}
{n : Type w Type w''} {lift : α : Type w m α n α} {f : β PostconditionT n (Option γ)}
[Iterator α m β] [Monad n] :
Iterator (FilterMap α m n lift f) n γ where
IsPlausibleStep := FilterMap.PlausibleStep (m := m) (n := n)
@@ -155,9 +163,8 @@ instance FilterMap.instIterator {α β γ : Type w} {m : Type w → Type w'} {n
| .skip it' h => pure <| .deflate <| .skip (it'.filterMapWithPostcondition f) (by exact .skip h)
| .done h => pure <| .deflate <| .done (.done h)
instance {α β γ : Type w} {m : Type w Type w'} {n : Type w Type w''} [Monad n] [Iterator α m β]
{lift : α : Type w m α n α}
{f : β PostconditionT n γ} :
instance Map.instIterator {α β γ : Type w} {m : Type w Type w'} {n : Type w Type w''} [Monad n]
[Iterator α m β] {lift : α : Type w m α n α} {f : β PostconditionT n γ} :
Iterator (Map α m n lift f) n γ :=
inferInstanceAs <| Iterator (FilterMap α m n lift _) n γ
@@ -189,8 +196,8 @@ instance FilterMap.instFinite {α β γ : Type w} {m : Type w → Type w'}
Finite.of_finitenessRelation FilterMap.instFinitenessRelation
@[no_expose]
instance {α β γ : Type w} {m : Type w Type w'} {n : Type w Type w''} [Monad n] [Iterator α m β]
{lift : α : Type w m α n α} {f : β PostconditionT n γ} [Finite α m] :
instance Map.instFinite {α β γ : Type w} {m : Type w Type w'} {n : Type w Type w''} [Monad n]
[Iterator α m β] {lift : α : Type w m α n α} {f : β PostconditionT n γ} [Finite α m] :
Finite (Map α m n lift f) n :=
Finite.of_finitenessRelation FilterMap.instFinitenessRelation
@@ -214,63 +221,13 @@ instance Map.instProductive {α β γ : Type w} {m : Type w → Type w'}
Productive (Map α m n lift f) n :=
Productive.of_productivenessRelation Map.instProductivenessRelation
instance {α β γ : Type w} {m : Type w Type w'}
{n : Type w Type w''} {o : Type w Type x} [Monad n] [Monad o] [Iterator α m β]
{lift : α : Type w m α n α}
{f : β PostconditionT n (Option γ)} :
IteratorCollect (FilterMap α m n lift f) n o :=
.defaultImplementation
instance {α β γ : Type w} {m : Type w Type w'}
{n : Type w Type w''} {o : Type w Type x} [Monad n] [Monad o] [Iterator α m β]
{lift : α : Type w m α n α}
{f : β PostconditionT n (Option γ)} [Finite α m] :
IteratorCollectPartial (FilterMap α m n lift f) n o :=
.defaultImplementation
instance FilterMap.instIteratorLoop {α β γ : Type w} {m : Type w Type w'}
{n : Type w Type w''} {o : Type x Type x'}
[Monad n] [Monad o] [Iterator α m β] {lift : α : Type w m α n α}
{f : β PostconditionT n (Option γ)} [Finite α m] :
{f : β PostconditionT n (Option γ)} :
IteratorLoop (FilterMap α m n lift f) n o :=
.defaultImplementation
instance FilterMap.instIteratorLoopPartial {α β γ : Type w} {m : Type w Type w'}
{n : Type w Type w''} {o : Type x Type x'}
[Monad n] [Monad o] [Iterator α m β] {lift : α : Type w m α n α}
{f : β PostconditionT n (Option γ)} :
IteratorLoopPartial (FilterMap α m n lift f) n o :=
.defaultImplementation
/--
`map` operations allow for a more efficient implementation of `toArray`. For example,
`array.iter.map f |>.toArray happens in-place if possible.
-/
instance Map.instIteratorCollect {α β γ : Type w} {m : Type w Type w'}
{n : Type w Type w''} {o : Type w Type x} [Monad n] [Monad o] [Iterator α m β]
{lift₁ : α : Type w m α n α}
{f : β PostconditionT n γ} [IteratorCollect α m o] [Finite α m] :
IteratorCollect (Map α m n lift₁ f) n o where
toArrayMapped lift₂ _ g it :=
letI : MonadLift m n := lift₁ (α := _)
letI : MonadLift n o := lift₂ (δ := _)
IteratorCollect.toArrayMapped
(lift := fun _ => monadLift)
(fun x => do g ( (f x).operation))
it.internalState.inner (m := m)
@[no_expose]
instance Map.instIteratorCollectPartial {α β γ : Type w} {m : Type w Type w'}
{n : Type w Type w''} {o : Type w Type x} [Monad n] [Monad o] [Iterator α m β]
{lift₁ : α : Type w m α n α}
{f : β PostconditionT n γ} [IteratorCollectPartial α m o] :
IteratorCollectPartial (Map α m n lift₁ f) n o where
toArrayMappedPartial lift₂ _ g it :=
IteratorCollectPartial.toArrayMappedPartial
(lift := fun _ a => lift₂ (lift₁ a))
(fun x => do g ( lift₂ (f x).operation))
it.internalState.inner (m := m)
instance Map.instIteratorLoop {α β γ : Type w} {m : Type w Type w'}
{n : Type w Type w''} {o : Type x Type x'} [Monad n] [Monad o] [Iterator α m β]
{lift : α : Type w m α n α}
@@ -278,12 +235,7 @@ instance Map.instIteratorLoop {α β γ : Type w} {m : Type w → Type w'}
IteratorLoop (Map α m n lift f) n o :=
.defaultImplementation
instance Map.instIteratorLoopPartial {α β γ : Type w} {m : Type w Type w'}
{n : Type w Type w''} {o : Type x Type x'} [Monad n] [Monad o] [Iterator α m β]
{lift : α : Type w m α n α}
{f : β PostconditionT n γ} :
IteratorLoopPartial (Map α m n lift f) n o :=
.defaultImplementation
end Iterators.Types
/--
*Note: This is a very general combinator that requires an advanced understanding of monads, dependent
@@ -406,12 +358,8 @@ it.filterMapM ---a'-----c'-------⊥
For certain mapping functions `f`, the resulting iterator will be finite (or productive) even though
no `Finite` (or `Productive`) instance is provided. For example, if `f` never returns `none`, then
this combinator will preserve productiveness. If `f` is an `ExceptT` monad and will always fail,
then `it.filterMapM` will be finite even if `it` isn't. In the first case, consider
using the `map`/`mapM`/`mapWithPostcondition` combinators instead, which provide more instances out of
the box.
If that does not help, the more general combinator `it.filterMapWithPostcondition f` makes it
possible to manually prove `Finite` and `Productive` instances depending on the concrete choice of `f`.
then `it.filterMapM` will be finite even if `it` isn't. In such cases, the termination proof needs
to be done manually.
**Performance:**
@@ -420,9 +368,9 @@ returned `Option` value.
-/
@[inline, expose]
def IterM.filterMapM {α β γ : Type w} {m : Type w Type w'} {n : Type w Type w''}
[Iterator α m β] [Monad n] [MonadLiftT m n]
[Iterator α m β] [Monad n] [MonadAttach n] [MonadLiftT m n]
(f : β n (Option γ)) (it : IterM (α := α) m β) :=
(it.filterMapWithPostcondition (fun b => PostconditionT.lift (f b)) : IterM n γ)
(it.filterMapWithPostcondition (fun b => PostconditionT.attachLift (f b)) : IterM n γ)
/--
If `it` is an iterator, then `it.mapM f` is another iterator that applies a monadic
@@ -449,10 +397,8 @@ it.mapM ---a'--b'--c'--d'-e'----⊥
For certain mapping functions `f`, the resulting iterator will be finite (or productive) even though
no `Finite` (or `Productive`) instance is provided. For example, if `f` is an `ExceptT` monad and
will always fail, then `it.mapM` will be finite even if `it` isn't.
If that does not help, the more general combinator `it.mapWithPostcondition f` makes it possible to
manually prove `Finite` and `Productive` instances depending on the concrete choice of `f`.
will always fail, then `it.mapM` will be finite even if `it` isn't. In such cases, the termination
proof needs to be done manually.
**Performance:**
@@ -460,8 +406,8 @@ For each value emitted by the base iterator `it`, this combinator calls `f`.
-/
@[inline, expose]
def IterM.mapM {α β γ : Type w} {m : Type w Type w'} {n : Type w Type w''} [Iterator α m β]
[Monad n] [MonadLiftT m n] (f : β n γ) (it : IterM (α := α) m β) :=
(it.mapWithPostcondition (fun b => PostconditionT.lift (f b)) : IterM n γ)
[Monad n] [MonadAttach n] [MonadLiftT m n] (f : β n γ) (it : IterM (α := α) m β) :=
(it.mapWithPostcondition (fun b => PostconditionT.attachLift (f b)) : IterM n γ)
/--
If `it` is an iterator, then `it.filterM f` is another iterator that applies a monadic
@@ -489,10 +435,7 @@ it.filterM ---a-----c-------⊥
For certain mapping functions `f`, the resulting iterator will be finite (or productive) even though
no `Finite` (or `Productive`) instance is provided. For example, if `f` is an `ExceptT` monad and
will always fail, then `it.filterWithPostcondition` will be finite -- and productive -- even if `it`
isn't.
In such situations, the more general combinator `it.filterWithPostcondition f` makes it possible to
manually prove `Finite` and `Productive` instances depending on the concrete choice of `f`.
isn't. In such cases, the termination proof needs to be done manually.
**Performance:**
@@ -500,9 +443,9 @@ For each value emitted by the base iterator `it`, this combinator calls `f`.
-/
@[inline, expose]
def IterM.filterM {α β : Type w} {m : Type w Type w'} {n : Type w Type w''} [Iterator α m β]
[Monad n] [MonadLiftT m n] (f : β n (ULift Bool)) (it : IterM (α := α) m β) :=
[Monad n] [MonadAttach n] [MonadLiftT m n] (f : β n (ULift Bool)) (it : IterM (α := α) m β) :=
(it.filterMapWithPostcondition
(fun b => (PostconditionT.lift (f b)).map (if ·.down = true then some b else none)) : IterM n β)
(fun b => (PostconditionT.attachLift (f b)).map (if ·.down = true then some b else none)) : IterM n β)
/--
If `it` is an iterator, then `it.filterMap f` is another iterator that applies a function `f` to all
@@ -604,4 +547,4 @@ def IterM.filter {α β : Type w} {m : Type w → Type w'} [Iterator α m β] [M
(f : β Bool) (it : IterM (α := α) m β) :=
(it.filterMap (fun b => if f b then some b else none) : IterM m β)
end Std.Iterators
end Std

View File

@@ -22,22 +22,23 @@ and so on. In other words, `it` flattens the iterator of iterators obtained by m
`f`.
-/
namespace Std.Iterators
namespace Std
open Iterators.Types
/-- Internal implementation detail of the `flatMap` combinator -/
@[ext, unbox]
public structure Flatten (α α₂ β : Type w) (m) where
public structure Iterators.Types.Flatten (α α₂ β : Type w) (m) where
it₁ : IterM (α := α) m (IterM (α := α₂) m β)
it₂ : Option (IterM (α := α₂) m β)
/--
Internal iterator combinator that is used to implement all `flatMap` variants
-/
@[always_inline]
@[always_inline, inline]
def IterM.flattenAfter {α α₂ β : Type w} {m : Type w Type w'} [Monad m]
[Iterator α m (IterM (α := α₂) m β)] [Iterator α₂ m β]
(it₁ : IterM (α := α) m (IterM (α := α₂) m β)) (it₂ : Option (IterM (α := α₂) m β)) :=
(toIterM (α := Flatten α α₂ β m) it₁, it₂ m β : IterM m β)
(.mk (α := Flatten α α₂ β m) it₁, it₂ m β : IterM m β)
/--
Let `it₁` and `it₂` be iterators and `f` a monadic function mapping `it₁`'s outputs to iterators
@@ -75,9 +76,9 @@ iterator.
For each value emitted by the outer iterator `it₁`, this combinator calls `f`.
-/
@[always_inline]
@[always_inline, inline]
public def IterM.flatMapAfterM {α : Type w} {β : Type w} {α₂ : Type w}
{γ : Type w} {m : Type w Type w'} [Monad m] [Iterator α m β] [Iterator α₂ m γ]
{γ : Type w} {m : Type w Type w'} [Monad m] [MonadAttach m] [Iterator α m β] [Iterator α₂ m γ]
(f : β m (IterM (α := α₂) m γ)) (it₁ : IterM (α := α) m β) (it₂ : Option (IterM (α := α₂) m γ)) :=
((it₁.mapM f).flattenAfter it₂ : IterM m γ)
@@ -114,9 +115,9 @@ This combinator incurs an additional O(1) cost with each output of `it` or an in
For each value emitted by the outer iterator `it`, this combinator calls `f`.
-/
@[always_inline, expose]
@[always_inline, inline, expose]
public def IterM.flatMapM {α : Type w} {β : Type w} {α₂ : Type w}
{γ : Type w} {m : Type w Type w'} [Monad m] [Iterator α m β] [Iterator α₂ m γ]
{γ : Type w} {m : Type w Type w'} [Monad m] [MonadAttach m] [Iterator α m β] [Iterator α₂ m γ]
(f : β m (IterM (α := α₂) m γ)) (it : IterM (α := α) m β) :=
(it.flatMapAfterM f none : IterM m γ)
@@ -156,7 +157,7 @@ iterator.
For each value emitted by the outer iterator `it₁`, this combinator calls `f`.
-/
@[always_inline]
@[always_inline, inline]
public def IterM.flatMapAfter {α : Type w} {β : Type w} {α₂ : Type w}
{γ : Type w} {m : Type w Type w'} [Monad m] [Iterator α m β] [Iterator α₂ m γ]
(f : β IterM (α := α₂) m γ) (it₁ : IterM (α := α) m β) (it₂ : Option (IterM (α := α₂) m γ)) :=
@@ -195,29 +196,31 @@ This combinator incurs an additional O(1) cost with each output of `it` or an in
For each value emitted by the outer iterator `it`, this combinator calls `f`.
-/
@[always_inline, expose]
@[always_inline, inline, expose]
public def IterM.flatMap {α : Type w} {β : Type w} {α₂ : Type w}
{γ : Type w} {m : Type w Type w'} [Monad m] [Iterator α m β] [Iterator α₂ m γ]
(f : β IterM (α := α₂) m γ) (it : IterM (α := α) m β) :=
(it.flatMapAfter f none : IterM m γ)
namespace Iterators.Types
variable {α α₂ β : Type w} {m : Type w Type w'}
/-- The plausible-step predicate for `Flatten` iterators -/
public inductive Flatten.IsPlausibleStep [Iterator α m (IterM (α := α₂) m β)] [Iterator α₂ m β] :
(it : IterM (α := Flatten α α₂ β m) m β) (step : IterStep (IterM (α := Flatten α α₂ β m) m β) β) Prop where
| outerYield : {it₁ it₁' it₂'}, it₁.IsPlausibleStep (.yield it₁' it₂')
IsPlausibleStep (toIterM it₁, none m β) (.skip (toIterM it₁', some it₂' m β))
IsPlausibleStep (.mk it₁, none m β) (.skip (.mk it₁', some it₂' m β))
| outerSkip : {it₁ it₁'}, it₁.IsPlausibleStep (.skip it₁')
IsPlausibleStep (toIterM it₁, none m β) (.skip (toIterM it₁', none m β))
IsPlausibleStep (.mk it₁, none m β) (.skip (.mk it₁', none m β))
| outerDone : {it₁}, it₁.IsPlausibleStep .done
IsPlausibleStep (toIterM it₁, none m β) .done
IsPlausibleStep (.mk it₁, none m β) .done
| innerYield : {it₁ it₂ it₂' b}, it₂.IsPlausibleStep (.yield it₂' b)
IsPlausibleStep (toIterM it₁, some it₂ m β) (.yield (toIterM it₁, some it₂' m β) b)
IsPlausibleStep (.mk it₁, some it₂ m β) (.yield (.mk it₁, some it₂' m β) b)
| innerSkip : {it₁ it₂ it₂'}, it₂.IsPlausibleStep (.skip it₂')
IsPlausibleStep (toIterM it₁, some it₂ m β) (.skip (toIterM it₁, some it₂' m β))
IsPlausibleStep (.mk it₁, some it₂ m β) (.skip (.mk it₁, some it₂' m β))
| innerDone : {it₁ it₂}, it₂.IsPlausibleStep .done
IsPlausibleStep (toIterM it₁, some it₂ m β) (.skip (toIterM it₁, none m β))
IsPlausibleStep (.mk it₁, some it₂ m β) (.skip (.mk it₁, none m β))
public instance Flatten.instIterator [Monad m] [Iterator α m (IterM (α := α₂) m β)] [Iterator α₂ m β] :
Iterator (Flatten α α₂ β m) m β where
@@ -246,7 +249,7 @@ section Finite
variable {α : Type w} {α₂ : Type w} {β : Type w} {m : Type w Type w'}
variable (α m β) in
def Rel [Monad m] [Iterator α m (IterM (α := α₂) m β)] [Iterator α₂ m β] [Finite α m] [Finite α₂ m] :
def Flatten.Rel [Monad m] [Iterator α m (IterM (α := α₂) m β)] [Iterator α₂ m β] [Finite α m] [Finite α₂ m] :
IterM (α := Flatten α α₂ β m) m β IterM (α := Flatten α α₂ β m) m β Prop :=
InvImage
(Prod.Lex
@@ -271,7 +274,7 @@ theorem Flatten.rel_of_right₂ [Monad m] [Iterator α m (IterM (α := α₂) m
Rel α β m it₁, none it₁, some it₂ :=
Prod.Lex.right _ True.intro
instance [Monad m] [Iterator α m (IterM (α := α₂) m β)] [Iterator α₂ m β]
def Flatten.instFinitenessRelation [Monad m] [Iterator α m (IterM (α := α₂) m β)] [Iterator α₂ m β]
[Finite α m] [Finite α₂ m] :
FinitenessRelation (Flatten α α₂ β m) m where
rel := Rel α β m
@@ -299,9 +302,9 @@ instance [Monad m] [Iterator α m (IterM (α := α₂) m β)] [Iterator α₂ m
apply Flatten.rel_of_right₂
@[no_expose]
public instance [Monad m] [Iterator α m (IterM (α := α₂) m β)] [Iterator α₂ m β]
public instance Flatten.instFinite [Monad m] [Iterator α m (IterM (α := α₂) m β)] [Iterator α₂ m β]
[Finite α m] [Finite α₂ m] : Finite (Flatten α α₂ β m) m :=
.of_finitenessRelation instFinitenessRelationFlattenOfIterMOfFinite
.of_finitenessRelation instFinitenessRelation
end Finite
@@ -310,7 +313,7 @@ section Productive
variable {α : Type w} {α₂ : Type w} {β : Type w} {m : Type w Type w'}
variable (α m β) in
def ProductiveRel [Monad m] [Iterator α m (IterM (α := α₂) m β)] [Iterator α₂ m β] [Finite α m]
def Flatten.ProductiveRel [Monad m] [Iterator α m (IterM (α := α₂) m β)] [Iterator α₂ m β] [Finite α m]
[Productive α₂ m] :
IterM (α := Flatten α α₂ β m) m β IterM (α := Flatten α α₂ β m) m β Prop :=
InvImage
@@ -336,8 +339,8 @@ theorem Flatten.productiveRel_of_right₂ [Monad m] [Iterator α m (IterM (α :=
ProductiveRel α β m it₁, none it₁, some it₂ :=
Prod.Lex.right _ True.intro
instance [Monad m] [Iterator α m (IterM (α := α₂) m β)] [Iterator α₂ m β]
[Finite α m] [Productive α₂ m] :
def Flatten.instProductivenessRelation [Monad m] [Iterator α m (IterM (α := α₂) m β)]
[Iterator α₂ m β] [Finite α m] [Productive α₂ m] :
ProductivenessRelation (Flatten α α₂ β m) m where
rel := ProductiveRel α β m
wf := by
@@ -360,26 +363,14 @@ instance [Monad m] [Iterator α m (IterM (α := α₂) m β)] [Iterator α₂ m
apply Flatten.productiveRel_of_right₂
@[no_expose]
public instance [Monad m] [Iterator α m (IterM (α := α₂) m β)] [Iterator α₂ m β]
public def Flatten.instProductive [Monad m] [Iterator α m (IterM (α := α₂) m β)] [Iterator α₂ m β]
[Finite α m] [Productive α₂ m] : Productive (Flatten α α₂ β m) m :=
.of_productivenessRelation instProductivenessRelationFlattenOfFiniteIterMOfProductive
.of_productivenessRelation instProductivenessRelation
end Productive
public instance Flatten.instIteratorCollect [Monad m] [Monad n] [Iterator α m (IterM (α := α₂) m β)]
[Iterator α₂ m β] : IteratorCollect (Flatten α α₂ β m) m n :=
.defaultImplementation
public instance Flatten.instIteratorCollectPartial [Monad m] [Monad n] [Iterator α m (IterM (α := α₂) m β)]
[Iterator α₂ m β] : IteratorCollectPartial (Flatten α α₂ β m) m n :=
.defaultImplementation
public instance Flatten.instIteratorLoop [Monad m] [Monad n] [Iterator α m (IterM (α := α₂) m β)]
[Iterator α₂ m β] : IteratorLoop (Flatten α α₂ β m) m n :=
.defaultImplementation
public instance Flatten.instIteratorLoopPartial [Monad m] [Monad n] [Iterator α m (IterM (α := α₂) m β)]
[Iterator α₂ m β] : IteratorLoopPartial (Flatten α α₂ β m) m n :=
.defaultImplementation
end Std.Iterators
end Std.Iterators.Types

View File

@@ -17,7 +17,7 @@ public import Init.Data.Iterators.Internal.Termination
This module provides the iterator combinator `IterM.take`.
-/
namespace Std.Iterators
namespace Std
variable {α : Type w} {m : Type w Type w'} {β : Type w}
@@ -25,7 +25,7 @@ variable {α : Type w} {m : Type w → Type w'} {β : Type w}
The internal state of the `IterM.take` iterator combinator.
-/
@[unbox]
structure Take (α : Type w) (m : Type w Type w') {β : Type w} [Iterator α m β] where
structure Iterators.Types.Take (α : Type w) (m : Type w Type w') {β : Type w} [Iterator α m β] where
/--
Internal implementation detail of the iterator library.
Caution: For `take n`, `countdown` is `n + 1`.
@@ -40,6 +40,8 @@ structure Take (α : Type w) (m : Type w → Type w') {β : Type w} [Iterator α
-/
finite : countdown > 0 Finite α m
open Std.Iterators Std.Iterators.Types
/--
Given an iterator `it` and a natural number `n`, `it.take n` is an iterator that outputs
up to the first `n` of `it`'s values in order and then terminates.
@@ -65,7 +67,7 @@ This combinator incurs an additional O(1) cost with each output of `it`.
-/
@[always_inline, inline]
def IterM.take [Iterator α m β] (n : Nat) (it : IterM (α := α) m β) :=
toIterM (Take.mk (n + 1) it (Or.inl <| Nat.zero_lt_succ _)) m β
IterM.mk (Take.mk (n + 1) it (Or.inl <| Nat.zero_lt_succ _)) m β
/--
This combinator is only useful for advanced use cases.
@@ -91,7 +93,7 @@ This combinator incurs an additional O(1) cost with each output of `it`.
-/
@[always_inline, inline]
def IterM.toTake [Iterator α m β] [Finite α m] (it : IterM (α := α) m β) :=
toIterM (Take.mk 0 it (Or.inr inferInstance)) m β
IterM.mk (Take.mk 0 it (Or.inr inferInstance)) m β
theorem IterM.take.surjective_of_zero_lt {α : Type w} {m : Type w Type w'} {β : Type w}
[Iterator α m β] (it : IterM (α := Take α m) m β) (h : 0 < it.internalState.countdown) :
@@ -100,6 +102,8 @@ theorem IterM.take.surjective_of_zero_lt {α : Type w} {m : Type w → Type w'}
simp only [take, Nat.sub_add_cancel (m := 1) (n := it.internalState.countdown) (by omega)]
rfl
namespace Iterators.Types
inductive Take.PlausibleStep [Iterator α m β] (it : IterM (α := Take α m) m β) :
(step : IterStep (IterM (α := Take α m) m β) β) Prop where
| yield : {it' out}, it.internalState.inner.IsPlausibleStep (.yield it' out)
@@ -204,20 +208,8 @@ instance Take.instFinite [Monad m] [Iterator α m β] [Productive α m] :
Finite (Take α m) m :=
by exact Finite.of_finitenessRelation instFinitenessRelation
instance Take.instIteratorCollect {n : Type w Type w'} [Monad m] [Monad n] [Iterator α m β] :
IteratorCollect (Take α m) m n :=
.defaultImplementation
instance Take.instIteratorCollectPartial {n : Type w Type w'} [Monad m] [Monad n] [Iterator α m β] :
IteratorCollectPartial (Take α m) m n :=
.defaultImplementation
instance Take.instIteratorLoop {n : Type x Type x'} [Monad m] [Monad n] [Iterator α m β] :
IteratorLoop (Take α m) m n :=
.defaultImplementation
instance Take.instIteratorLoopPartial [Monad m] [Monad n] [Iterator α m β] :
IteratorLoopPartial (Take α m) m n :=
.defaultImplementation
end Std.Iterators
end Std.Iterators.Types

View File

@@ -11,11 +11,12 @@ public import Init.Data.Iterators.Consumers.Monadic
public section
namespace Std.Iterators
namespace Std
universe v u v' u'
section ULiftT
namespace Iterators
/-- `ULiftT.{v, u}` shrinks a monad on `Type max u v` to a monad on `Type u`. -/
@[expose] -- for codegen
@@ -60,11 +61,14 @@ theorem ULiftT.run_map {n : Type max u v → Type v'} [Monad n] {α β : Type u}
(f <$> x).run = x.run >>= (fun a => pure <| .up (f a.down)) :=
(rfl)
end Iterators
end ULiftT
namespace Iterators.Types
/-- Internal state of the `uLift` iterator combinator. Do not depend on its internals. -/
@[unbox]
structure Types.ULiftIterator (α : Type u) (m : Type u Type u') (n : Type max u v Type v')
structure ULiftIterator (α : Type u) (m : Type u Type u') (n : Type max u v Type v')
(β : Type u) (lift : γ : Type u, m γ ULiftT n γ) : Type max u v where
inner : IterM (α := α) m β
@@ -75,14 +79,14 @@ variable {α : Type u} {m : Type u → Type u'} {n : Type max u v → Type v'}
Transforms a step of the base iterator into a step of the `uLift` iterator.
-/
@[always_inline, inline, expose]
def Types.ULiftIterator.Monadic.modifyStep (step : IterStep (IterM (α := α) m β) β) :
def ULiftIterator.Monadic.modifyStep (step : IterStep (IterM (α := α) m β) β) :
IterStep (IterM (α := ULiftIterator.{v} α m n β lift) n (ULift.{v} β)) (ULift.{v} β) :=
match step with
| .yield it' out => .yield it' (.up out)
| .skip it' => .skip it'
| .done => .done
instance Types.ULiftIterator.instIterator [Iterator α m β] [Monad n] :
instance ULiftIterator.instIterator [Iterator α m β] [Monad n] :
Iterator (ULiftIterator α m n β lift) n (ULift β) where
IsPlausibleStep it step :=
step', it.internalState.inner.IsPlausibleStep step'
@@ -93,7 +97,7 @@ instance Types.ULiftIterator.instIterator [Iterator α m β] [Monad n] :
where finally
case hp => exact step.inflate.val, step.inflate.property, rfl
def Types.ULiftIterator.instFinitenessRelation [Iterator α m β] [Finite α m] [Monad n] :
private def ULiftIterator.instFinitenessRelation [Iterator α m β] [Finite α m] [Monad n] :
FinitenessRelation (ULiftIterator α m n β lift) n where
rel := InvImage WellFoundedRelation.rel (fun it => it.internalState.inner.finitelyManySteps)
wf := InvImage.wf _ WellFoundedRelation.wf
@@ -105,11 +109,11 @@ def Types.ULiftIterator.instFinitenessRelation [Iterator α m β] [Finite α m]
· apply IterM.TerminationMeasures.Finite.rel_of_skip
exact hp
instance Types.ULiftIterator.instFinite [Iterator α m β] [Finite α m] [Monad n] :
instance ULiftIterator.instFinite [Iterator α m β] [Finite α m] [Monad n] :
Finite (ULiftIterator α m n β lift) n :=
.of_finitenessRelation instFinitenessRelation
def Types.ULiftIterator.instProductivenessRelation [Iterator α m β] [Productive α m] [Monad n] :
private def ULiftIterator.instProductivenessRelation [Iterator α m β] [Productive α m] [Monad n] :
ProductivenessRelation (ULiftIterator α m n β lift) n where
rel := InvImage WellFoundedRelation.rel (fun it => it.internalState.inner.finitelyManySkips)
wf := InvImage.wf _ WellFoundedRelation.wf
@@ -119,26 +123,18 @@ def Types.ULiftIterator.instProductivenessRelation [Iterator α m β] [Productiv
apply IterM.TerminationMeasures.Productive.rel_of_skip
exact hp
instance Types.ULiftIterator.instProductive [Iterator α m β] [Productive α m] [Monad n] :
instance ULiftIterator.instProductive [Iterator α m β] [Productive α m] [Monad n] :
Productive (ULiftIterator α m n β lift) n :=
.of_productivenessRelation instProductivenessRelation
instance Types.ULiftIterator.instIteratorLoop {o : Type x Type x'} [Monad n] [Monad o]
instance ULiftIterator.instIteratorLoop {o : Type x Type x'} [Monad n] [Monad o]
[Iterator α m β] :
IteratorLoop (ULiftIterator α m n β lift) n o :=
.defaultImplementation
instance Types.ULiftIterator.instIteratorLoopPartial {o : Type x Type x'} [Monad n] [Monad o] [Iterator α m β] :
IteratorLoopPartial (ULiftIterator α m n β lift) n o :=
.defaultImplementation
end Iterators.Types
instance Types.ULiftIterator.instIteratorCollect [Monad n] [Monad o] [Iterator α m β] :
IteratorCollect (ULiftIterator α m n β lift) n o :=
.defaultImplementation
instance Types.ULiftIterator.instIteratorCollectPartial {o} [Monad n] [Monad o] [Iterator α m β] :
IteratorCollectPartial (ULiftIterator α m n β lift) n o :=
.defaultImplementation
open Std.Iterators Std.Iterators.Types
/--
Transforms an `m`-monadic iterator with values in `β` into an `n`-monadic iterator with
@@ -157,9 +153,9 @@ it.uLift n ---.up a----.up b---.up c--.up d---⊥
* `Productive`: only if the original iterator is productive
-/
@[always_inline, inline, expose]
def IterM.uLift (it : IterM (α := α) m β) (n : Type max u v Type v')
[lift : MonadLiftT m (ULiftT n)] :
IterM (α := Types.ULiftIterator α m n β (fun _ => lift.monadLift)) n (ULift β) :=
def IterM.uLift {α β : Type u} {m : Type u Type u'} (it : IterM (α := α) m β)
(n : Type max u v Type v') [lift : MonadLiftT m (ULiftT n)] :
IterM (α := ULiftIterator α m n β (fun _ => lift.monadLift)) n (ULift β) :=
it
end Std.Iterators
end Std

View File

@@ -10,7 +10,8 @@ public import Init.Data.Iterators.Combinators.Monadic.Take
@[expose] public section
namespace Std.Iterators
namespace Std
open Std.Iterators Std.Iterators.Types
/--
Given an iterator `it` and a natural number `n`, `it.take n` is an iterator that outputs
@@ -67,4 +68,4 @@ def Iter.toTake {α : Type w} {β : Type w} [Iterator α Id β] [Finite α Id] (
Iter (α := Take α Id) β :=
it.toIterM.toTake.toIter
end Std.Iterators
end Std

View File

@@ -10,7 +10,8 @@ public import Init.Data.Iterators.Combinators.Monadic.ULift
public section
namespace Std.Iterators
namespace Std
open Std.Iterators Std.Iterators.Types
universe v u v' u'
@@ -20,10 +21,10 @@ variable {α : Type u} {β : Type u}
Transforms a step of the base iterator into a step of the `uLift` iterator.
-/
@[always_inline, inline]
def Types.ULiftIterator.modifyStep (step : IterStep (Iter (α := α) β) β) :
def Iterators.Types.ULiftIterator.modifyStep (step : IterStep (Iter (α := α) β) β) :
IterStep (Iter (α := ULiftIterator.{v} α Id Id β (fun _ => monadLift)) (ULift.{v} β))
(ULift.{v} β) :=
(Monadic.modifyStep (step.mapIterator Iter.toIterM)).mapIterator IterM.toIter
(ULiftIterator.Monadic.modifyStep (step.mapIterator Iter.toIterM)).mapIterator IterM.toIter
/--
Transforms an iterator with values in `β` into one with values in `ULift β`.
@@ -48,4 +49,4 @@ def Iter.uLift (it : Iter (α := α) β) :
Iter (α := Types.ULiftIterator.{v} α Id Id β (fun _ => monadLift)) (ULift β) :=
(it.toIterM.uLift Id).toIter
end Std.Iterators
end Std

View File

@@ -11,5 +11,6 @@ public import Init.Data.Iterators.Consumers.Access
public import Init.Data.Iterators.Consumers.Collect
public import Init.Data.Iterators.Consumers.Loop
public import Init.Data.Iterators.Consumers.Partial
public import Init.Data.Iterators.Consumers.Total
public import Init.Data.Iterators.Consumers.Stream

View File

@@ -11,7 +11,8 @@ public import Init.Data.Iterators.Consumers.Monadic.Access
@[expose] public section
namespace Std.Iterators
namespace Std
open Std.Iterators
/--
If possible, takes `n` steps with the iterator `it` and
@@ -62,4 +63,4 @@ def Iter.atIdx? {α β} [Iterator α Id β] [Productive α Id] [IteratorAccess
| .skip _ => none
| .done => none
end Std.Iterators
end Std

View File

@@ -7,6 +7,7 @@ module
prelude
public import Init.Data.Iterators.Consumers.Partial
public import Init.Data.Iterators.Consumers.Total
public import Init.Data.Iterators.Consumers.Monadic.Collect
@[expose] public section
@@ -20,41 +21,113 @@ Concretely, the following operations are provided:
* `Iter.toList`, collecting the values in a list
* `Iter.toListRev`, collecting the values in a list in reverse order but more efficiently
* `Iter.toArray`, collecting the values in an array
Some operations are implemented using the `IteratorCollect` and `IteratorCollectPartial`
typeclasses.
-/
namespace Std.Iterators
namespace Std
open Std.Iterators
@[always_inline, inline, inherit_doc IterM.toArray]
/--
Traverses the given iterator and stores the emitted values in an array.
If the iterator is not finite, this function might run forever. The variant
`it.ensureTermination.toArray` always terminates after finitely many steps.
-/
@[always_inline, inline]
def Iter.toArray {α : Type w} {β : Type w}
[Iterator α Id β] [Finite α Id] [IteratorCollect α Id Id] (it : Iter (α := α) β) : Array β :=
[Iterator α Id β] (it : Iter (α := α) β) : Array β :=
it.toIterM.toArray.run
@[always_inline, inline, inherit_doc IterM.Partial.toArray]
def Iter.Partial.toArray {α : Type w} {β : Type w}
[Iterator α Id β] [IteratorCollectPartial α Id Id] (it : Iter.Partial (α := α) β) : Array β :=
it.it.toIterM.allowNontermination.toArray.run
/--
Traverses the given iterator and stores the emitted values in an array.
@[always_inline, inline, inherit_doc IterM.toListRev]
This function is deprecated. Instead of `it.allowNontermination.toArray`, use `it.toArray`.
-/
@[always_inline, inline, deprecated Iter.toArray (since := "2025-12-04")]
def Iter.Partial.toArray {α : Type w} {β : Type w}
[Iterator α Id β] (it : Iter.Partial (α := α) β) : Array β :=
it.it.toArray
/--
Traverses the given iterator and stores the emitted values in an array.
This variant terminates after finitely many steps and requires a proof that the iterator is
finite. If such a proof is not available, consider using `Iter.toArray`.
-/
@[always_inline, inline]
def Iter.Total.toArray {α : Type w} {β : Type w}
[Iterator α Id β] [Finite α Id] (it : Iter.Total (α := α) β) :
Array β :=
it.it.toArray
/--
Traverses the given iterator and stores the emitted values in reverse order in a list. Because
lists are prepend-only, this `toListRev` is usually more efficient that `toList`.
If the iterator is not finite, this function might run forever. The variant
`it.ensureTermination.toListRev` always terminates after finitely many steps.
-/
@[always_inline, inline]
def Iter.toListRev {α : Type w} {β : Type w}
[Iterator α Id β] [Finite α Id] (it : Iter (α := α) β) : List β :=
[Iterator α Id β] (it : Iter (α := α) β) : List β :=
it.toIterM.toListRev.run
@[always_inline, inline, inherit_doc IterM.Partial.toListRev]
/--
Traverses the given iterator and stores the emitted values in reverse order in a list. Because
lists are prepend-only, this `toListRev` is usually more efficient that `toList`.
This function is deprecated. Instead of `it.allowNontermination.toListRev`, use `it.toListRev`.
-/
@[always_inline, inline, deprecated Iter.toListRev (since := "2025-12-04")]
def Iter.Partial.toListRev {α : Type w} {β : Type w}
[Iterator α Id β] (it : Iter.Partial (α := α) β) : List β :=
it.it.toIterM.allowNontermination.toListRev.run
it.it.toListRev
@[always_inline, inline, inherit_doc IterM.toList]
/--
Traverses the given iterator and stores the emitted values in reverse order in a list. Because
lists are prepend-only, this `toListRev` is usually more efficient that `toList`.
This variant terminates after finitely many steps and requires a proof that the iterator is
finite. If such a proof is not available, consider using `Iter.toListRev`.
-/
@[always_inline, inline]
def Iter.Total.toListRev {α : Type w} {β : Type w}
[Iterator α Id β] [Finite α Id] (it : Iter.Total (α := α) β) : List β :=
it.it.toListRev
/--
Traverses the given iterator and stores the emitted values in a list. Because
lists are prepend-only, `toListRev` is usually more efficient that `toList`.
If the iterator is not finite, this function might run forever. The variant
`it.ensureTermination.toList` always terminates after finitely many steps.
-/
@[always_inline, inline]
def Iter.toList {α : Type w} {β : Type w}
[Iterator α Id β] [Finite α Id] [IteratorCollect α Id Id] (it : Iter (α := α) β) : List β :=
[Iterator α Id β] (it : Iter (α := α) β) : List β :=
it.toIterM.toList.run
@[always_inline, inline, inherit_doc IterM.Partial.toList]
def Iter.Partial.toList {α : Type w} {β : Type w}
[Iterator α Id β] [IteratorCollectPartial α Id Id] (it : Iter.Partial (α := α) β) : List β :=
it.it.toIterM.allowNontermination.toList.run
/--
Traverses the given iterator and stores the emitted values in a list. Because
lists are prepend-only, `toListRev` is usually more efficient that `toList`.
end Std.Iterators
This function is deprecated. Instead of `it.allowNontermination.toList`, use `it.toList`.
-/
@[always_inline, deprecated Iter.toList (since := "2025-12-04")]
def Iter.Partial.toList {α : Type w} {β : Type w}
[Iterator α Id β] (it : Iter.Partial (α := α) β) : List β :=
it.it.toList
/--
Traverses the given iterator and stores the emitted values in a list. Because
lists are prepend-only, `toListRev` is usually more efficient that `toList`.
This variant terminates after finitely many steps and requires a proof that the iterator is
finite. If such a proof is not available, consider using `Iter.toList`.
-/
@[always_inline, inline]
def Iter.Total.toList {α : Type w} {β : Type w}
[Iterator α Id β] [Finite α Id] (it : Iter.Total (α := α) β) :
List β :=
it.it.toList
end Std

View File

@@ -23,10 +23,11 @@ function in every iteration. Concretely, the following operations are provided:
* `Iter.fold`, the analogue of `List.foldl`
* `Iter.foldM`, the analogue of `List.foldlM`
These operations are implemented using the `IteratorLoop` and `IteratorLoopPartial` typeclasses.
These operations are implemented using the `IteratorLoop` type class.
-/
namespace Std.Iterators
namespace Std
open Std.Iterators
/--
A `ForIn'` instance for iterators. Its generic membership relation is not easy to use,
@@ -35,7 +36,7 @@ or future library improvements will make it more comfortable.
-/
@[always_inline, inline]
def Iter.instForIn' {α : Type w} {β : Type w} {n : Type x Type x'} [Monad n]
[Iterator α Id β] [Finite α Id] [IteratorLoop α Id n] :
[Iterator α Id β] [IteratorLoop α Id n] :
ForIn' n (Iter (α := α) β) β fun it out => it.IsPlausibleIndirectOutput out where
forIn' it init f :=
IteratorLoop.finiteForIn' (fun _ _ f c => f c.run) |>.forIn' it.toIterM init
@@ -43,7 +44,7 @@ def Iter.instForIn' {α : Type w} {β : Type w} {n : Type x → Type x'} [Monad
f out (Iter.isPlausibleIndirectOutput_iff_isPlausibleIndirectOutput_toIterM.mpr h) acc
instance (α : Type w) (β : Type w) (n : Type x Type x') [Monad n]
[Iterator α Id β] [Finite α Id] [IteratorLoop α Id n] :
[Iterator α Id β] [IteratorLoop α Id n] :
ForIn n (Iter (α := α) β) β :=
haveI : ForIn' n (Iter (α := α) β) β _ := Iter.instForIn'
instForInOfForIn'
@@ -53,44 +54,58 @@ An implementation of `for h : ... in ... do ...` notation for partial iterators.
-/
@[always_inline, inline]
def Iter.Partial.instForIn' {α : Type w} {β : Type w} {n : Type x Type x'} [Monad n]
[Iterator α Id β] [IteratorLoopPartial α Id n] :
[Iterator α Id β] [IteratorLoop α Id n] :
ForIn' n (Iter.Partial (α := α) β) β fun it out => it.it.IsPlausibleIndirectOutput out where
forIn' it init f :=
IteratorLoopPartial.forInPartial (α := α) (m := Id) (n := n) (fun _ _ f c => f c.run)
it.it.toIterM init
fun out h acc =>
f out (Iter.isPlausibleIndirectOutput_iff_isPlausibleIndirectOutput_toIterM.mpr h) acc
haveI := @Iter.instForIn'
forIn' it.it init f
instance (α : Type w) (β : Type w) (n : Type x Type x') [Monad n]
[Iterator α Id β] [IteratorLoopPartial α Id n] :
[Iterator α Id β] [IteratorLoop α Id n] :
ForIn n (Iter.Partial (α := α) β) β :=
haveI : ForIn' n (Iter.Partial (α := α) β) β _ := Iter.Partial.instForIn'
instForInOfForIn'
/--
A `ForIn'` instance for iterators that is guaranteed to terminate after finitely many steps.
It is not marked as an instance because the membership predicate is difficult to work with.
-/
@[always_inline, inline]
def Iter.Total.instForIn' {α : Type w} {β : Type w} {n : Type x Type x'} [Monad n]
[Iterator α Id β] [IteratorLoop α Id n] [Finite α Id] :
ForIn' n (Iter.Total (α := α) β) β fun it out => it.it.IsPlausibleIndirectOutput out where
forIn' it init f := Iter.instForIn'.forIn' it.it init f
instance (α : Type w) (β : Type w) (n : Type x Type x') [Monad n]
[Iterator α Id β] [IteratorLoop α Id n] [Finite α Id] :
ForIn n (Iter.Total (α := α) β) β :=
haveI : ForIn' n (Iter.Total (α := α) β) β _ := Iter.Total.instForIn'
instForInOfForIn'
instance {m : Type x Type x'}
{α : Type w} {β : Type w} [Iterator α Id β] [Finite α Id] [IteratorLoop α Id m] [Monad m] :
{α : Type w} {β : Type w} [Iterator α Id β] [IteratorLoop α Id m] [Monad m] :
ForM m (Iter (α := α) β) β where
forM it f := forIn it PUnit.unit (fun out _ => do f out; return .yield .unit)
instance {m : Type x Type x'}
{α : Type w} {β : Type w} [Iterator α Id β] [Finite α Id] [IteratorLoopPartial α Id m] [Monad m] :
{α : Type w} {β : Type w} [Iterator α Id β] [IteratorLoop α Id m] [Monad m] :
ForM m (Iter.Partial (α := α) β) β where
forM it f := forIn it PUnit.unit (fun out _ => do f out; return .yield .unit)
instance {m : Type x Type x'}
{α : Type w} {β : Type w} [Monad m] [Iterator α Id β] [IteratorLoop α Id m] [Finite α Id] :
ForM m (Iter.Total (α := α) β) β where
forM it f := forIn it PUnit.unit (fun out _ => do f out; return .yield .unit)
/--
Folds a monadic function over an iterator from the left, accumulating a value starting with `init`.
The accumulated value is combined with the each element of the list in order, using `f`.
It is equivalent to `it.toList.foldlM`.
This function requires a `Finite` instance proving that the iterator will finish after a finite
number of steps. If the iterator is not finite or such an instance is not available, consider using
`it.allowNontermination.foldM` instead of `it.foldM`. However, it is not possible to formally
verify the behavior of the partial variant.
-/
@[always_inline, inline]
def Iter.foldM {m : Type x Type x'} [Monad m]
{α : Type w} {β : Type w} {γ : Type x} [Iterator α Id β] [Finite α Id]
{α : Type w} {β : Type w} {γ : Type x} [Iterator α Id β]
[IteratorLoop α Id m] (f : γ β m γ)
(init : γ) (it : Iter (α := α) β) : m γ :=
ForIn.forIn it init (fun x acc => ForInStep.yield <$> f acc x)
@@ -101,29 +116,39 @@ The accumulated value is combined with the each element of the list in order, us
It is equivalent to `it.toList.foldlM`.
This is a partial, potentially nonterminating, function. It is not possible to formally verify
its behavior. If the iterator has a `Finite` instance, consider using `IterM.foldM` instead.
This function is deprecated. Instead of `it.allowNontermination.foldM`, use `it.foldM`.
-/
@[always_inline, inline]
@[always_inline, inline, deprecated Iter.foldM (since := "2025-12-04")]
def Iter.Partial.foldM {m : Type x Type x'} [Monad m]
{α : Type w} {β : Type w} {γ : Type x} [Iterator α Id β]
[IteratorLoopPartial α Id m] (f : γ β m γ)
[IteratorLoop α Id m] (f : γ β m γ)
(init : γ) (it : Iter.Partial (α := α) β) : m γ :=
ForIn.forIn it init (fun x acc => ForInStep.yield <$> f acc x)
it.it.foldM (init := init) f
/--
Folds a monadic function over an iterator from the left, accumulating a value starting with `init`.
The accumulated value is combined with the each element of the list in order, using `f`.
It is equivalent to `it.toList.foldlM`.
This variant terminates after finitely many steps and requires a proof that the iterator is
finite. If such a proof is not available, consider using `Iter.foldM`.
-/
@[always_inline, inline]
def Iter.Total.foldM {m : Type x Type x'} [Monad m]
{α : Type w} {β : Type w} {γ : Type x} [Iterator α Id β]
[IteratorLoop α Id m] [Finite α Id] (f : γ β m γ)
(init : γ) (it : Iter.Total (α := α) β) : m γ :=
it.it.foldM (init := init) f
/--
Folds a function over an iterator from the left, accumulating a value starting with `init`.
The accumulated value is combined with the each element of the list in order, using `f`.
It is equivalent to `it.toList.foldl`.
This function requires a `Finite` instance proving that the iterator will finish after a finite
number of steps. If the iterator is not finite or such an instance is not available, consider using
`it.allowNontermination.fold` instead of `it.fold`. However, it is not possible to formally
verify the behavior of the partial variant.
-/
@[always_inline, inline]
def Iter.fold {α : Type w} {β : Type w} {γ : Type x} [Iterator α Id β] [Finite α Id]
def Iter.fold {α : Type w} {β : Type w} {γ : Type x} [Iterator α Id β]
[IteratorLoop α Id Id] (f : γ β γ)
(init : γ) (it : Iter (α := α) β) : γ :=
ForIn.forIn (m := Id) it init (fun x acc => ForInStep.yield (f acc x))
@@ -134,14 +159,28 @@ The accumulated value is combined with the each element of the list in order, us
It is equivalent to `it.toList.foldl`.
This is a partial, potentially nonterminating, function. It is not possible to formally verify
its behavior. If the iterator has a `Finite` instance, consider using `IterM.fold` instead.
This function is deprecated. Instead of `it.allowNontermination.fold`, use `it.fold`.
-/
@[always_inline, inline, deprecated Iter.fold (since := "2025-12-04")]
def Iter.Partial.fold {α : Type w} {β : Type w} {γ : Type x} [Iterator α Id β]
[IteratorLoop α Id Id] (f : γ β γ)
(init : γ) (it : Iter.Partial (α := α) β) : γ :=
it.it.fold (init := init) f
/--
Folds a function over an iterator from the left, accumulating a value starting with `init`.
The accumulated value is combined with the each element of the list in order, using `f`.
It is equivalent to `it.toList.foldl`.
This variant terminates after finitely many steps and requires a proof that the iterator is
finite. If such a proof is not available, consider using `Iter.fold`.
-/
@[always_inline, inline]
def Iter.Partial.fold {α : Type w} {β : Type w} {γ : Type x} [Iterator α Id β]
[IteratorLoopPartial α Id Id] (f : γ β γ)
(init : γ) (it : Iter.Partial (α := α) β) : γ :=
ForIn.forIn (m := Id) it init (fun x acc => ForInStep.yield (f acc x))
def Iter.Total.fold {α : Type w} {β : Type w} {γ : Type x} [Iterator α Id β]
[IteratorLoop α Id Id] [Finite α Id] (f : γ β γ)
(init : γ) (it : Iter.Total (α := α) β) : γ :=
it.it.fold (init := init) f
set_option doc.verso true in
/--
@@ -151,9 +190,9 @@ any element emitted by the iterator {name}`it`.
{lit}`O(|xs|)`. Short-circuits upon encountering the first match. The elements in {name}`it` are
examined in order of iteration.
-/
@[specialize]
@[always_inline]
def Iter.anyM {α β : Type w} {m : Type Type w'} [Monad m]
[Iterator α Id β] [IteratorLoop α Id m] [Finite α Id]
[Iterator α Id β] [IteratorLoop α Id m]
(p : β m Bool) (it : Iter (α := α) β) : m Bool :=
ForIn.forIn it false (fun x _ => do
if p x then
@@ -161,6 +200,23 @@ def Iter.anyM {α β : Type w} {m : Type → Type w'} [Monad m]
else
return .yield false)
set_option doc.verso true in
/--
Returns {lean}`true` if the monadic predicate {name}`p` returns {lean}`true` for
any element emitted by the iterator {name}`it`.
{lit}`O(|xs|)`. Short-circuits upon encountering the first match. The elements in {name}`it` are
examined in order of iteration.
This variant terminates after finitely many steps and requires a proof that the iterator is
finite. If such a proof is not available, consider using {name}`Iter.anyM`.
-/
@[always_inline, inline]
def Iter.Total.anyM {α β : Type w} {m : Type Type w'} [Monad m]
[Iterator α Id β] [IteratorLoop α Id m] [Finite α Id]
(p : β m Bool) (it : Iter.Total (α := α) β) : m Bool :=
it.it.anyM p
set_option doc.verso true in
/--
Returns {lean}`true` if the pure predicate {name}`p` returns {lean}`true` for
@@ -171,21 +227,38 @@ examined in order of iteration.
-/
@[inline]
def Iter.any {α β : Type w}
[Iterator α Id β] [IteratorLoop α Id Id] [Finite α Id]
[Iterator α Id β] [IteratorLoop α Id Id]
(p : β Bool) (it : Iter (α := α) β) : Bool :=
(it.anyM (fun x => pure (f := Id) (p x))).run
set_option doc.verso true in
/--
Returns {lean}`true` if the monadic predicate {name}`p` returns {lean}`true` for
all elements emitted by the iterator {name}`it`.
Returns {lean}`true` if the pure predicate {name}`p` returns {lean}`true` for
any element emitted by the iterator {name}`it`.
{lit}`O(|xs|)`. Short-circuits upon encountering the first mismatch. The elements in {name}`it` are
{lit}`O(|xs|)`. Short-circuits upon encountering the first match. The elements in {name}`it` are
examined in order of iteration.
This variant terminates after finitely many steps and requires a proof that the iterator is
finite. If such a proof is not available, consider using {name}`Iter.any`.
-/
@[inline]
def Iter.Total.any {α β : Type w}
[Iterator α Id β] [IteratorLoop α Id Id] [Finite α Id]
(p : β Bool) (it : Iter.Total (α := α) β) : Bool :=
it.it.any p
set_option doc.verso true in
/--
Returns {lean}`true` if the monadic predicate {name}`p` returns {lean}`true` for
all element emitted by the iterator {name}`it`.
{lit}`O(|xs|)`. Short-circuits upon encountering the first match. The elements in {name}`it` are
examined in order of iteration.
-/
@[specialize]
@[always_inline, inline]
def Iter.allM {α β : Type w} {m : Type Type w'} [Monad m]
[Iterator α Id β] [IteratorLoop α Id m] [Finite α Id]
[Iterator α Id β] [IteratorLoop α Id m]
(p : β m Bool) (it : Iter (α := α) β) : m Bool :=
ForIn.forIn it true (fun x _ => do
if p x then
@@ -195,36 +268,82 @@ def Iter.allM {α β : Type w} {m : Type → Type w'} [Monad m]
set_option doc.verso true in
/--
Returns {lean}`true` if the pure predicate {name}`p` returns {lean}`true` for
all elements emitted by the iterator {name}`it`.
Returns {lean}`true` if the monadic predicate {name}`p` returns {lean}`true` for
all element emitted by the iterator {name}`it`.
{lit}`O(|xs|)`. Short-circuits upon encountering the first mismatch. The elements in {name}`it` are
{lit}`O(|xs|)`. Short-circuits upon encountering the first match. The elements in {name}`it` are
examined in order of iteration.
This variant terminates after finitely mall steps and requires a proof that the iterator is
finite. If such a proof is not available, consider using {name}`Iter.allM`.
-/
@[always_inline, inline]
def Iter.Total.allM {α β : Type w} {m : Type Type w'} [Monad m]
[Iterator α Id β] [IteratorLoop α Id m] [Finite α Id]
(p : β m Bool) (it : Iter.Total (α := α) β) : m Bool :=
it.it.allM p
set_option doc.verso true in
/--
Returns {lean}`true` if the pure predicate {name}`p` returns {lean}`true` for
all element emitted by the iterator {name}`it`.
{lit}`O(|xs|)`. Short-circuits upon encountering the first match. The elements in {name}`it` are
examined in order of iteration.
-/
@[inline]
def Iter.all {α β : Type w}
[Iterator α Id β] [IteratorLoop α Id Id] [Finite α Id]
[Iterator α Id β] [IteratorLoop α Id Id]
(p : β Bool) (it : Iter (α := α) β) : Bool :=
(it.allM (fun x => pure (f := Id) (p x))).run
set_option doc.verso true in
/--
Steps through the iterator until the monadic function `f` returns `some` for an element, at which
point iteration stops and the result of `f` is returned. If the iterator is completely consumed
without `f` returning `some`, then the result is `none`.
Returns {lean}`true` if the pure predicate {name}`p` returns {lean}`true` for
all element emitted by the iterator {name}`it`.
{lit}`O(|xs|)`. Short-circuits upon encountering the first match. The elements in {name}`it` are
examined in order of iteration.
This variant terminates after finitely mall steps and requires a proof that the iterator is
finite. If such a proof is not available, consider using {name}`Iter.all`.
-/
@[inline]
def Iter.Total.all {α β : Type w}
[Iterator α Id β] [IteratorLoop α Id Id] [Finite α Id]
(p : β Bool) (it : Iter.Total (α := α) β) : Bool :=
it.it.all p
/--
Returns the first non-`none` result of applying the monadic function `f` to each output
of the iterator, in order. Returns `none` if `f` returns `none` for all outputs.
`O(|it|)`. Short-circuits when `f` returns `some _`. The outputs of `it` are
examined in order of iteration.
If the iterator is not finite, this function might run forever. The variant
`it.ensureTermination.findSomeM?` always terminates after finitely many steps.
Example:
```lean example
#eval [7, 6, 5, 8, 1, 2, 6].iter.findSomeM? fun i => do
if i < 5 then
return some (i * 10)
if i ≤ 6 then
IO.println s!"Almost! {i}"
return none
```
```output
Almost! 6
Almost! 5
```
```output
some 10
```
-/
@[inline]
def Iter.findSomeM? {α β : Type w} {γ : Type x} {m : Type x Type w'} [Monad m] [Iterator α Id β]
[IteratorLoop α Id m] [Finite α Id] (it : Iter (α := α) β) (f : β m (Option γ)) :
m (Option γ) :=
ForIn.forIn it none (fun x _ => do
match f x with
| none => return .yield none
| some fx => return .done (some fx))
@[inline, inherit_doc Iter.findSomeM?]
def Iter.Partial.findSomeM? {α β : Type w} {γ : Type x} {m : Type x Type w'} [Monad m]
[Iterator α Id β] [IteratorLoopPartial α Id m] (it : Iter.Partial (α := α) β)
(f : β m (Option γ)) :
[IteratorLoop α Id m] (it : Iter (α := α) β) (f : β m (Option γ)) :
m (Option γ) :=
ForIn.forIn it none (fun x _ => do
match f x with
@@ -232,52 +351,284 @@ def Iter.Partial.findSomeM? {α β : Type w} {γ : Type x} {m : Type x → Type
| some fx => return .done (some fx))
/--
Steps through the iterator until `f` returns `some` for an element, at which point iteration stops
and the result of `f` is returned. If the iterator is completely consumed without `f` returning
`some`, then the result is `none`.
Returns the first non-`none` result of applying the monadic function `f` to each output
of the iterator, in order. Returns `none` if `f` returns `none` for all outputs.
`O(|it|)`. Short-circuits when `f` returns `some _`. The outputs of `it` are
examined in order of iteration.
This function is deprecated. Instead of `it.allowNontermination.findSomeM?`, use `it.findSomeM?`.
Example:
```lean example
#eval [7, 6, 5, 8, 1, 2, 6].iter.findSomeM? fun i => do
if i < 5 then
return some (i * 10)
if i ≤ 6 then
IO.println s!"Almost! {i}"
return none
```
```output
Almost! 6
Almost! 5
```
```output
some 10
```
-/
@[inline, deprecated Iter.findSomeM? (since := "2025-12-04")]
def Iter.Partial.findSomeM? {α β : Type w} {γ : Type x} {m : Type x Type w'} [Monad m]
[Iterator α Id β] [IteratorLoop α Id m] (it : Iter.Partial (α := α) β)
(f : β m (Option γ)) :
m (Option γ) :=
it.it.findSomeM? f
/--
Returns the first non-`none` result of applying the monadic function `f` to each output
of the iterator, in order. Returns `none` if `f` returns `none` for all outputs.
`O(|it|)`. Short-circuits when `f` returns `some _`. The outputs of `it` are
examined in order of iteration.
This variant terminates after finitely many steps and requires a proof that the iterator is
finite. If such a proof is not available, consider using `Iter.findSomeM?`.
Example:
```lean example
#eval [7, 6, 5, 8, 1, 2, 6].iter.findSomeM? fun i => do
if i < 5 then
return some (i * 10)
if i ≤ 6 then
IO.println s!"Almost! {i}"
return none
```
```output
Almost! 6
Almost! 5
```
```output
some 10
```
-/
@[inline]
def Iter.Total.findSomeM? {α β : Type w} {γ : Type x} {m : Type x Type w'} [Monad m]
[Iterator α Id β] [IteratorLoop α Id m] [Finite α Id] (it : Iter.Total (α := α) β)
(f : β m (Option γ)) :
m (Option γ) :=
it.it.findSomeM? f
/--
Returns the first non-`none` result of applying `f` to each output of the iterator, in order.
Returns `none` if `f` returns `none` for all outputs.
`O(|it|)`. Short-circuits when `f` returns `some _`.The outputs of `it` are examined in order of
iteration.
If the iterator is not finite, this function might run forever. The variant
`it.ensureTermination.findSome?` always terminates after finitely many steps.
Examples:
* `[7, 6, 5, 8, 1, 2, 6].iter.findSome? (fun x => if x < 5 then some (10 * x) else none) = some 10`
* `[7, 6, 5, 8, 1, 2, 6].iter.findSome? (fun x => if x < 1 then some (10 * x) else none) = none`
-/
@[inline]
def Iter.findSome? {α β : Type w} {γ : Type x} [Iterator α Id β]
[IteratorLoop α Id Id] [Finite α Id] (it : Iter (α := α) β) (f : β Option γ) :
Option γ :=
Id.run (it.findSomeM? (pure <| f ·))
@[inline, inherit_doc Iter.findSome?]
def Iter.Partial.findSome? {α β : Type w} {γ : Type x} [Iterator α Id β]
[IteratorLoopPartial α Id Id] (it : Iter.Partial (α := α) β) (f : β Option γ) :
[IteratorLoop α Id Id] (it : Iter (α := α) β) (f : β Option γ) :
Option γ :=
Id.run (it.findSomeM? (pure <| f ·))
/--
Steps through the iterator until an element satisfies the monadic predicate `f`, at which point
iteration stops and the element is returned. If no element satisfies `f`, then the result is
`none`.
Returns the first non-`none` result of applying `f` to each output of the iterator, in order.
Returns `none` if `f` returns `none` for all outputs.
`O(|it|)`. Short-circuits when `f` returns `some _`.The outputs of `it` are examined in order of
iteration.
This function is deprecated. Instead of `it.allowNontermination.findSome?`, use `it.findSome?`.
Examples:
* `[7, 6, 5, 8, 1, 2, 6].iter.allowNontermination.findSome? (fun x => if x < 5 then some (10 * x) else none) = some 10`
* `[7, 6, 5, 8, 1, 2, 6].iter.allowNontermination.findSome? (fun x => if x < 1 then some (10 * x) else none) = none`
-/
@[inline, deprecated Iter.findSome? (since := "2025-12-04")]
def Iter.Partial.findSome? {α β : Type w} {γ : Type x} [Iterator α Id β]
[IteratorLoop α Id Id] (it : Iter.Partial (α := α) β) (f : β Option γ) :
Option γ :=
it.it.findSome? f
/--
Returns the first non-`none` result of applying `f` to each output of the iterator, in order.
Returns `none` if `f` returns `none` for all outputs.
`O(|it|)`. Short-circuits when `f` returns `some _`.The outputs of `it` are examined in order of
iteration.
This variant terminates after finitely many steps and requires a proof that the iterator is
finite. If such a proof is not available, consider using `Iter.findSome?`.
Examples:
* `[7, 6, 5, 8, 1, 2, 6].iter.ensureTermination.findSome? (fun x => if x < 5 then some (10 * x) else none) = some 10`
* `[7, 6, 5, 8, 1, 2, 6].iter.ensureTermination.findSome? (fun x => if x < 1 then some (10 * x) else none) = none`
-/
@[inline]
def Iter.Total.findSome? {α β : Type w} {γ : Type x} [Iterator α Id β]
[IteratorLoop α Id Id] [Finite α Id] (it : Iter.Total (α := α) β) (f : β Option γ) :
Option γ :=
it.it.findSome? f
/--
Returns the first output of the iterator for which the monadic predicate `p` returns `true`, or
`none` if no such element is found.
`O(|it|)`. Short-circuits when `f` returns `true`. The outputs of `it` are examined in order of
iteration.
If the iterator is not finite, this function might run forever. The variant
`it.ensureTermination.findM?` always terminates after finitely many steps.
Example:
```lean example
#eval [7, 6, 5, 8, 1, 2, 6].iter.findM? fun i => do
if i < 5 then
return true
if i ≤ 6 then
IO.println s!"Almost! {i}"
return false
```
```output
Almost! 6
Almost! 5
```
```output
some 1
```
-/
@[inline]
def Iter.findM? {α β : Type w} {m : Type w Type w'} [Monad m] [Iterator α Id β]
[IteratorLoop α Id m] [Finite α Id] (it : Iter (α := α) β) (f : β m (ULift Bool)) :
m (Option β) :=
it.findSomeM? (fun x => return if ( f x).down then some x else none)
@[inline, inherit_doc Iter.findM?]
def Iter.Partial.findM? {α β : Type w} {m : Type w Type w'} [Monad m] [Iterator α Id β]
[IteratorLoopPartial α Id m] (it : Iter.Partial (α := α) β) (f : β m (ULift Bool)) :
[IteratorLoop α Id m] (it : Iter (α := α) β) (f : β m (ULift Bool)) :
m (Option β) :=
it.findSomeM? (fun x => return if ( f x).down then some x else none)
/--
Steps through the iterator until an element satisfies `f`, at which point iteration stops and the
element is returned. If no element satisfies `f`, then the result is `none`.
Returns the first output of the iterator for which the monadic predicate `p` returns `true`, or
`none` if no such element is found.
`O(|it|)`. Short-circuits when `f` returns `true`. The outputs of `it` are examined in order of
iteration.
This function is deprecated. Instead of `it.ensureTermination.findM?`, use `it.findM?`.
Example:
```lean example
#eval [7, 6, 5, 8, 1, 2, 6].iter.findM? fun i => do
if i < 5 then
return true
if i ≤ 6 then
IO.println s!"Almost! {i}"
return false
```
```output
Almost! 6
Almost! 5
```
```output
some 1
```
-/
@[inline, deprecated Iter.findM? (since := "2025-12-04")]
def Iter.Partial.findM? {α β : Type w} {m : Type w Type w'} [Monad m] [Iterator α Id β]
[IteratorLoop α Id m] (it : Iter.Partial (α := α) β) (f : β m (ULift Bool)) :
m (Option β) :=
it.it.findM? f
/--
Returns the first output of the iterator for which the monadic predicate `p` returns `true`, or
`none` if no such element is found.
`O(|it|)`. Short-circuits when `f` returns `true`. The outputs of `it` are examined in order of
iteration.
This variant requires terminates after finitely many steps and requires a proof that the iterator is
finite. If such a proof is not available, consider using `Iter.findM?`.
Example:
```lean example
#eval [7, 6, 5, 8, 1, 2, 6].iter.findM? fun i => do
if i < 5 then
return true
if i ≤ 6 then
IO.println s!"Almost! {i}"
return false
```
```output
Almost! 6
Almost! 5
```
```output
some 1
```
-/
@[inline]
def Iter.Total.findM? {α β : Type w} {m : Type w Type w'} [Monad m] [Iterator α Id β]
[IteratorLoop α Id m] [Finite α Id] (it : Iter.Total (α := α) β) (f : β m (ULift Bool)) :
m (Option β) :=
it.it.findM? f
/--
Returns the first output of the iterator for which the predicate `p` returns `true`, or `none` if
no such output is found.
`O(|it|)`. Short-circuits upon encountering the first match. The elements in `it` are examined in
order of iteration.
If the iterator is not finite, this function might run forever. The variant
`it.ensureTermination.find?` always terminates after finitely many steps.
Examples:
* `[7, 6, 5, 8, 1, 2, 6].iter.find? (· < 5) = some 1`
* `[7, 6, 5, 8, 1, 2, 6].iter.find? (· < 1) = none`
-/
@[inline]
def Iter.find? {α β : Type w} [Iterator α Id β] [IteratorLoop α Id Id]
[Finite α Id] (it : Iter (α := α) β) (f : β Bool) : Option β :=
(it : Iter (α := α) β) (f : β Bool) : Option β :=
Id.run (it.findM? (pure <| .up <| f ·))
@[inline, inherit_doc Iter.find?]
def Iter.Partial.find? {α β : Type w} [Iterator α Id β] [IteratorLoopPartial α Id Id]
/--
Returns the first output of the iterator for which the predicate `p` returns `true`, or `none` if
no such output is found.
`O(|it|)`. Short-circuits upon encountering the first match. The elements in `it` are examined in
order of iteration.
This function is deprecated. Instead of `it.allowNontermination.find?`, use `it.find?`.
Examples:
* `[7, 6, 5, 8, 1, 2, 6].iter.allowNontermination.find? (· < 5) = some 1`
* `[7, 6, 5, 8, 1, 2, 6].iter.allowNontermination.find? (· < 1) = none`
-/
@[inline, deprecated Iter.find? (since := "2025-12-04")]
def Iter.Partial.find? {α β : Type w} [Iterator α Id β] [IteratorLoop α Id Id]
(it : Iter.Partial (α := α) β) (f : β Bool) : Option β :=
Id.run (it.findM? (pure <| .up <| f ·))
it.it.find? f
/--
Returns the first output of the iterator for which the predicate `p` returns `true`, or `none` if
no such output is found.
`O(|it|)`. Short-circuits upon encountering the first match. The elements in `it` are examined in
order of iteration.
This variant terminates after finitely many steps and requires a proof that the iterator is
finite. If such a proof is not available, consider using `Iter.find?`.
Examples:
* `[7, 6, 5, 8, 1, 2, 6].iter.find? (· < 5) = some 1`
* `[7, 6, 5, 8, 1, 2, 6].iter.find? (· < 1) = none`
-/
@[inline]
def Iter.Total.find? {α β : Type w} [Iterator α Id β] [IteratorLoop α Id Id] [Finite α Id]
(it : Iter.Total (α := α) β) (f : β Bool) : Option β :=
it.it.find? f
/--
Steps through the whole iterator, counting the number of outputs emitted.
@@ -287,7 +638,7 @@ Steps through the whole iterator, counting the number of outputs emitted.
This function's runtime is linear in the number of steps taken by the iterator.
-/
@[always_inline, inline, expose]
def Iter.count {α : Type w} {β : Type w} [Iterator α Id β] [Finite α Id] [IteratorLoop α Id Id]
def Iter.count {α : Type w} {β : Type w} [Iterator α Id β] [IteratorLoop α Id Id]
(it : Iter (α := α) β) : Nat :=
it.toIterM.count.run.down
@@ -299,7 +650,7 @@ Steps through the whole iterator, counting the number of outputs emitted.
This function's runtime is linear in the number of steps taken by the iterator.
-/
@[always_inline, inline, expose, deprecated Iter.count (since := "2025-10-29")]
def Iter.size {α : Type w} {β : Type w} [Iterator α Id β] [Finite α Id] [IteratorLoop α Id Id]
def Iter.size {α : Type w} {β : Type w} [Iterator α Id β] [IteratorLoop α Id Id]
(it : Iter (α := α) β) : Nat :=
it.count
@@ -310,10 +661,10 @@ Steps through the whole iterator, counting the number of outputs emitted.
This function's runtime is linear in the number of steps taken by the iterator.
-/
@[always_inline, inline, expose]
def Iter.Partial.count {α : Type w} {β : Type w} [Iterator α Id β] [IteratorLoopPartial α Id Id]
@[always_inline, inline, expose, deprecated Iter.count (since := "2025-12-04")]
def Iter.Partial.count {α : Type w} {β : Type w} [Iterator α Id β] [IteratorLoop α Id Id]
(it : Iter.Partial (α := α) β) : Nat :=
it.it.toIterM.allowNontermination.count.run.down
it.it.toIterM.count.run.down
/--
Steps through the whole iterator, counting the number of outputs emitted.
@@ -322,9 +673,9 @@ Steps through the whole iterator, counting the number of outputs emitted.
This function's runtime is linear in the number of steps taken by the iterator.
-/
@[always_inline, inline, expose, deprecated Iter.Partial.count (since := "2025-10-29")]
def Iter.Partial.size {α : Type w} {β : Type w} [Iterator α Id β] [IteratorLoopPartial α Id Id]
@[always_inline, inline, expose, deprecated Iter.count (since := "2025-10-29")]
def Iter.Partial.size {α : Type w} {β : Type w} [Iterator α Id β] [IteratorLoop α Id Id]
(it : Iter.Partial (α := α) β) : Nat :=
it.count
it.it.count
end Std.Iterators
end Std

View File

@@ -10,7 +10,8 @@ public import Init.Data.Iterators.Basic
public section
namespace Std.Iterators
namespace Std
open Std.Iterators
/--
`it.IsPlausibleNthOutputStep n step` is the proposition that according to the
@@ -105,4 +106,4 @@ def IterM.atIdx? [Iterator α m β] [IteratorAccess α m] [Monad m] (it : IterM
| .skip _ => return none
| .done => return none
end Std.Iterators
end Std

View File

@@ -7,7 +7,9 @@ module
prelude
public import Init.Data.Iterators.Consumers.Monadic.Partial
public import Init.Data.Iterators.Consumers.Monadic.Total
public import Init.Data.Iterators.Internal.LawfulMonadLiftFunction
public import Init.WFExtrinsicFix
@[expose] public section
@@ -20,177 +22,65 @@ Concretely, the following operations are provided:
* `IterM.toList`, collecting the values in a list
* `IterM.toListRev`, collecting the values in a list in reverse order but more efficiently
* `IterM.toArray`, collecting the values in an array
Some producers and combinators provide specialized implementations. These are captured by the
`IteratorCollect` and `IteratorCollectPartial` typeclasses. They should be implemented by all
types of iterators. A default implementation is provided. The typeclass `LawfulIteratorCollect`
asserts that an `IteratorCollect` instance equals the default implementation.
-/
namespace Std.Iterators
open Std.Internal
section Typeclasses
/--
`IteratorCollect α m` provides efficient implementations of collectors for `α`-based
iterators. Right now, it is limited to a potentially optimized `toArray` implementation.
This class is experimental and users of the iterator API should not explicitly depend on it.
They can, however, assume that consumers that require an instance will work for all iterators
provided by the standard library.
Note: For this to be compositional enough to be useful, `toArrayMapped` would need to accept a
termination proof for the specific mapping function used instead of the blanket `Finite α m`
instance. Otherwise, most combinators like `map` cannot implement their own instance relying on
the instance of their base iterators. However, fixing this is currently low priority.
-/
class IteratorCollect (α : Type w) (m : Type w Type w') (n : Type w Type w'')
{β : Type w} [Iterator α m β] where
/--
Maps the emitted values of an iterator using the given function and collects the results in an
`Array`. This is an internal implementation detail. Consider using `it.map f |>.toArray` instead.
-/
toArrayMapped [Finite α m] :
(lift : δ : Type w m δ n δ) {γ : Type w} (β n γ) IterM (α := α) m β n (Array γ)
/--
`IteratorCollectPartial α m` provides efficient implementations of collectors for `α`-based
iterators. Right now, it is limited to a potentially optimized partial `toArray` implementation.
This class is experimental and users of the iterator API should not explicitly depend on it.
They can, however, assume that consumers that require an instance will work for all iterators
provided by the standard library.
-/
class IteratorCollectPartial (α : Type w) (m : Type w Type w') (n : Type w Type w'')
{β : Type w} [Iterator α m β] where
/--
Maps the emitted values of an iterator using the given function and collects the results in an
`Array`. This is an internal implementation detail.
Consider using `it.map f |>.allowNontermination.toArray` instead.
-/
toArrayMappedPartial :
(lift : δ : Type w m δ n δ) {γ : Type w} (β n γ) IterM (α := α) m β n (Array γ)
end Typeclasses
namespace Std
open Std.Internal Std.Iterators
section ToArray
/--
This is an internal function used in `IteratorCollect.defaultImplementation`.
It iterates over an iterator and applies `f` whenever a value is emitted before inserting the result
of `f` into an array.
If this relation is well-founded, then `IterM.toArray`, `IterM.toList` and `IterM.toListRev` are
guaranteed to finish after finitely many steps. If all of the iterator's steps terminate
individually, `IterM.toArray` is guaranteed to terminate.
-/
@[always_inline, inline]
def IterM.DefaultConsumers.toArrayMapped {α β : Type w} {m : Type w Type w'}
{n : Type w Type w''} [Monad n] [Iterator α m β] [Finite α m]
(lift : α : Type w m α n α) {γ : Type w} (f : β n γ)
(it : IterM (α := α) m β) : n (Array γ) :=
go it #[]
where
@[specialize]
go [Monad n] [Finite α m] (it : IterM (α := α) m β) a := letI : MonadLift m n := lift (α := _); do
match ( it.step).inflate with
| .yield it' b _ => go it' (a.push ( f b))
| .skip it' _ => go it' a
| .done _ => return a
termination_by it.finitelyManySteps
/--
This is the default implementation of the `IteratorLoop` class.
It simply iterates through the iterator using `IterM.step`, incrementally building up the desired
data structure. For certain iterators, more efficient implementations are possible and should be
used instead.
-/
@[always_inline, inline]
def IteratorCollect.defaultImplementation {α β : Type w} {m : Type w Type w'}
{n : Type w Type w''} [Monad n] [Iterator α m β] :
IteratorCollect α m n where
toArrayMapped := IterM.DefaultConsumers.toArrayMapped
/--
Asserts that a given `IteratorCollect` instance is equal to `IteratorCollect.defaultImplementation`.
(Even though equal, the given instance might be vastly more efficient.)
-/
class LawfulIteratorCollect (α : Type w) (m : Type w Type w') (n : Type w Type w'')
{β : Type w} [Monad m] [Monad n] [Iterator α m β] [i : IteratorCollect α m n] where
lawful_toArrayMapped : lift [LawfulMonadLiftFunction lift] [Finite α m],
i.toArrayMapped lift (α := α) (γ := γ)
= IteratorCollect.defaultImplementation.toArrayMapped lift
theorem LawfulIteratorCollect.toArrayMapped_eq {α β γ : Type w} {m : Type w Type w'}
{n : Type w Type w''} [Monad m] [Monad n] [Iterator α m β] [Finite α m] [IteratorCollect α m n]
[hl : LawfulIteratorCollect α m n] {lift : δ : Type w m δ n δ}
[LawfulMonadLiftFunction lift]
{f : β n γ} {it : IterM (α := α) m β} :
IteratorCollect.toArrayMapped lift f it (m := m) =
IterM.DefaultConsumers.toArrayMapped lift f it (m := m) := by
rw [lawful_toArrayMapped]; rfl
instance (α β : Type w) (m : Type w Type w') (n : Type w Type w'') [Monad n]
[Iterator α m β] [Monad m] [Iterator α m β] [Finite α m] :
haveI : IteratorCollect α m n := .defaultImplementation
LawfulIteratorCollect α m n :=
letI : IteratorCollect α m n := .defaultImplementation
fun _ => rfl
/--
This is an internal function used in `IteratorCollectPartial.defaultImplementation`.
It iterates over an iterator and applies `f` whenever a value is emitted before inserting the result
of `f` into an array.
-/
@[always_inline, inline]
partial def IterM.DefaultConsumers.toArrayMappedPartial {α β : Type w} {m : Type w Type w'}
{n : Type w Type w''} [Monad n] [Iterator α m β]
(lift : {α : Type w} m α n α) {γ : Type w} (f : β n γ)
(it : IterM (α := α) m β) : n (Array γ) :=
go it #[]
where
@[specialize]
go [Monad n] (it : IterM (α := α) m β) a := letI : MonadLift m n := lift; do
match ( it.step).inflate with
| .yield it' b _ => go it' (a.push ( f b))
| .skip it' _ => go it' a
| .done _ => return a
/--
This is the default implementation of the `IteratorLoopPartial` class.
It simply iterates through the iterator using `IterM.step`, incrementally building up the desired
data structure. For certain iterators, more efficient implementations are possible and should be
used instead.
-/
@[always_inline, inline]
def IteratorCollectPartial.defaultImplementation {α β : Type w} {m : Type w Type w'}
{n : Type w Type w''} [Monad n] [Iterator α m β] :
IteratorCollectPartial α m n where
toArrayMappedPartial := IterM.DefaultConsumers.toArrayMappedPartial
def IterM.toArray.RecursionRel {α β : Type w} {m : Type w Type w'}
[Iterator α m β] {γ : Type w} (x' x : (_ : IterM (α := α) m β) ×' Array γ) : Prop :=
( out, x.1.IsPlausibleStep (.yield x'.1 out) a, x'.2 = x.2.push a)
(x.1.IsPlausibleStep (.skip x'.1) x'.2 = x.2)
/--
Traverses the given iterator and stores the emitted values in an array.
This function requires a `Finite` instance proving that the iterator will finish after a finite
number of steps. If the iterator is not finite or such an instance is not available, consider using
`it.allowNontermination.toArray` instead of `it.toArray`. However, it is not possible to formally
verify the behavior of the partial variant.
If the iterator is not finite, this function might run forever. The variant
`it.ensureTermination.toArray` always terminates after finitely many steps.
-/
@[always_inline, inline]
def IterM.toArray {α β : Type w} {m : Type w Type w'} [Monad m]
[Iterator α m β] [Finite α m] [IteratorCollect α m m]
def IterM.toArray {α β : Type w} {m : Type w Type w'} [Monad m] [Iterator α m β]
(it : IterM (α := α) m β) : m (Array β) :=
IteratorCollect.toArrayMapped (fun _ => id) pure it
go it #[]
where
@[always_inline]
go it (acc : Array β) : m (Array β) :=
WellFounded.extrinsicFix₂ (C₂ := fun _ _ => m (Array β)) (InvImage TerminationMeasures.Finite.Rel (·.1.finitelyManySteps!))
(fun (it : IterM (α := α) m β) acc recur => do
match ( it.step).inflate with
| .yield it' out h =>
recur it' (acc.push out) (by exact TerminationMeasures.Finite.rel_of_yield _)
| .skip it' h => recur it' acc (by exact TerminationMeasures.Finite.rel_of_skip _)
| .done _ => return acc) it acc
/--
Traverses the given iterator and stores the emitted values in an array.
This is a partial, potentially nonterminating, function. It is not possible to formally verify
its behavior. If the iterator has a `Finite` instance, consider using `IterM.toArray` instead.
This function is deprecated. Instead of `it.allowNontermination.toArray`, use `it.toArray`.
-/
@[always_inline, inline, deprecated IterM.toArray (since := "2025-10-23")]
def IterM.Partial.toArray {α : Type w} {m : Type w Type w'} {β : Type w} [Monad m]
[Iterator α m β] (it : IterM.Partial (α := α) m β) : m (Array β) :=
it.it.toArray
/--
Traverses the given iterator and stores the emitted values in an array.
This variant terminates after finitely many steps and requires a proof that the iterator is
finite. If such a proof is not available, consider using `IterM.toArray`.
-/
@[always_inline, inline]
def IterM.Partial.toArray {α : Type w} {m : Type w Type w'} {β : Type w} [Monad m]
[Iterator α m β] (it : IterM.Partial (α := α) m β) [IteratorCollectPartial α m m] : m (Array β) :=
IteratorCollectPartial.toArrayMappedPartial (fun _ => id) pure it.it
def IterM.Total.toArray {α : Type w} {m : Type w Type w'} {β : Type w} [Monad m]
[Iterator α m β] [Finite α m] (it : IterM.Total (α := α) m β) :
m (Array β) :=
it.it.toArray
end ToArray
@@ -198,67 +88,82 @@ end ToArray
Traverses the given iterator and stores the emitted values in reverse order in a list. Because
lists are prepend-only, this `toListRev` is usually more efficient that `toList`.
This function requires a `Finite` instance proving that the iterator will finish after a finite
number of steps. If the iterator is not finite or such an instance is not available, consider using
`it.allowNontermination.toListRev` instead of `it.toListRev`. However, it is not possible to
formally verify the behavior of the partial variant.
If the iterator is not finite, this function might run forever. The variant
`it.ensureTermination.toListRev` always terminates after finitely many steps.
-/
@[inline]
@[always_inline, inline]
def IterM.toListRev {α : Type w} {m : Type w Type w'} [Monad m] {β : Type w}
[Iterator α m β] [Finite α m] (it : IterM (α := α) m β) : m (List β) :=
[Iterator α m β] (it : IterM (α := α) m β) : m (List β) :=
go it []
where
go [Finite α m] it bs := do
match ( it.step).inflate with
| .yield it' b _ => go it' (b :: bs)
| .skip it' _ => go it' bs
| .done _ => return bs
termination_by it.finitelyManySteps
@[always_inline, inline]
go (it : IterM m β) acc :=
WellFounded.extrinsicFix₂ (InvImage TerminationMeasures.Finite.Rel (·.1.finitelyManySteps!))
(fun it acc recur => do
match ( it.step).inflate with
| .yield it' out h => recur it' (out :: acc) (TerminationMeasures.Finite.rel_of_yield h)
| .skip it' h => recur it' acc (TerminationMeasures.Finite.rel_of_skip h)
| .done _ => return acc) it acc
/--
Traverses the given iterator and stores the emitted values in reverse order in a list. Because
lists are prepend-only, this `toListRev` is usually more efficient that `toList`.
This is a partial, potentially nonterminating, function. It is not possible to formally verify
its behavior. If the iterator has a `Finite` instance, consider using `IterM.toListRev` instead.
This function is deprecated. Instead of `it.allowNontermination.toListRev`, use `it.toListRev`.
-/
@[always_inline, inline]
@[always_inline, inline, deprecated IterM.toListRev (since := "2025-10-23")]
partial def IterM.Partial.toListRev {α : Type w} {m : Type w Type w'} [Monad m] {β : Type w}
[Iterator α m β] (it : IterM.Partial (α := α) m β) : m (List β) :=
go it.it []
where
@[specialize]
go it bs := do
match ( it.step).inflate with
| .yield it' b _ => go it' (b :: bs)
| .skip it' _ => go it' bs
| .done _ => return bs
it.it.toListRev
/--
Traverses the given iterator and stores the emitted values in reverse order in a list. Because
lists are prepend-only, this `toListRev` is usually more efficient that `toList`.
This variant terminates after finitely many steps and requires a proof that the iterator is
finite. If such a proof is not available, consider using `IterM.toListRev`.
-/
@[always_inline, inline]
def IterM.Total.toListRev {α : Type w} {m : Type w Type w'} {β : Type w} [Monad m]
[Iterator α m β] [Finite α m] (it : IterM.Total (α := α) m β) :
m (List β) :=
it.it.toListRev
/--
Traverses the given iterator and stores the emitted values in a list. Because
lists are prepend-only, `toListRev` is usually more efficient that `toList`.
This function requires a `Finite` instance proving that the iterator will finish after a finite
number of steps. If the iterator is not finite or such an instance is not available, consider using
`it.allowNontermination.toList` instead of `it.toList`. However, it is not possible to
formally verify the behavior of the partial variant.
If the iterator is not finite, this function might run forever. The variant
`it.ensureTermination.toList` always terminates after finitely many steps.
-/
@[always_inline, inline]
def IterM.toList {α : Type w} {m : Type w Type w'} [Monad m] {β : Type w}
[Iterator α m β] [Finite α m] [IteratorCollect α m m] (it : IterM (α := α) m β) : m (List β) :=
[Iterator α m β] (it : IterM (α := α) m β) : m (List β) :=
Array.toList <$> IterM.toArray it
/--
Traverses the given iterator and stores the emitted values in a list. Because
lists are prepend-only, `toListRev` is usually more efficient that `toList`.
This is a partial, potentially nonterminating, function. It is not possible to formally verify
its behavior. If the iterator has a `Finite` instance, consider using `IterM.toList` instead.
This function is deprecated. Instead of `it.allowNontermination.toList`, use `it.toList`.
-/
@[always_inline, inline, deprecated IterM.toList (since := "2025-10-23")]
def IterM.Partial.toList {α : Type w} {m : Type w Type w'} [Monad m] {β : Type w}
[Iterator α m β] (it : IterM.Partial (α := α) m β) :
m (List β) :=
Array.toList <$> it.it.toArray
/--
Traverses the given iterator and stores the emitted values in a list. Because
lists are prepend-only, `toListRev` is usually more efficient that `toList`.
This variant terminates after finitely many steps and requires a proof that the iterator is
finite. If such a proof is not available, consider using `IterM.toList`.
-/
@[always_inline, inline]
def IterM.Partial.toList {α : Type w} {m : Type w Type w'} [Monad m] {β : Type w}
[Iterator α m β] (it : IterM.Partial (α := α) m β) [IteratorCollectPartial α m m] :
def IterM.Total.toList {α : Type w} {m : Type w Type w'} {β : Type w} [Monad m]
[Iterator α m β] [Finite α m] (it : IterM.Total (α := α) m β) :
m (List β) :=
Array.toList <$> it.toArray
it.it.toList
end Std.Iterators
end Std

File diff suppressed because it is too large Load Diff

View File

@@ -10,7 +10,7 @@ public import Init.Data.Iterators.Basic
public section
namespace Std.Iterators
namespace Std
/--
A wrapper around an iterator that provides partial consumers. See `IterM.allowNontermination`.
@@ -29,4 +29,4 @@ def IterM.allowNontermination {α : Type w} {m : Type w → Type w'} {β : Type
(it : IterM (α := α) m β) : IterM.Partial (α := α) m β :=
it
end Std.Iterators
end Std

View File

@@ -0,0 +1,36 @@
/-
Copyright (c) 2025 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.Iterators.Basic
set_option doc.verso true
public section
namespace Std
structure IterM.Total {α : Type w} (m : Type w Type w') (β : Type w) where
it : IterM (α := α) m β
/--
For an iterator {name}`it`, {lean}`it.ensureTermination` provides variants of consumers that always
terminate.
-/
@[always_inline, inline]
def IterM.ensureTermination {α : Type w} {β : Type w} {m : Type w Type w'}
(it : IterM (α := α) m β) :
IterM.Total (α := α) m β :=
it
/--
A wrapper around an iterator that provides strictly terminating consumers. See
{name}`IterM.ensureTermination`.
-/
add_decl_doc IterM.Total
end Std

View File

@@ -10,7 +10,7 @@ public import Init.Data.Iterators.Basic
public section
namespace Std.Iterators
namespace Std
/--
A wrapper around an iterator that provides partial consumers. See `Iter.allowNontermination`.
@@ -29,4 +29,4 @@ def Iter.allowNontermination {α : Type w} {β : Type w}
(it : Iter (α := α) β) : Iter.Partial (α := α) β :=
it
end Std.Iterators
end Std

View File

@@ -11,7 +11,8 @@ public import Init.Data.Iterators.Consumers.Access
public section
namespace Std.Iterators
namespace Std
open Std.Iterators
instance {α β} [Iterator α Id β] [Productive α Id] [IteratorAccess α Id] :
Stream (Iter (α := α) β) β where
@@ -24,4 +25,4 @@ instance {α β} [Iterator α Id β] [Productive α Id] [IteratorAccess α Id] :
revert h
exact IterM.not_isPlausibleNthOutputStep_yield
end Std.Iterators
end Std

View File

@@ -0,0 +1,36 @@
/-
Copyright (c) 2025 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.Iterators.Basic
set_option doc.verso true
public section
namespace Std
structure Iter.Total {α : Type w} (β : Type w) where
it : Iter (α := α) β
/--
For an iterator {name}`it`, {lean}`it.ensureTermination` provides variants of consumers that always
terminate.
-/
@[always_inline, inline]
def Iter.ensureTermination {α : Type w} {β : Type w}
(it : Iter (α := α) β) :
Iter.Total (α := α) β :=
it
/--
A wrapper around an iterator that provides strictly terminating consumers. See
{name}`Iter.ensureTermination`.
-/
add_decl_doc Iter.Total
end Std

View File

@@ -10,7 +10,8 @@ public import Init.Data.Iterators.Basic
public section
namespace Std.Iterators
namespace Std
open Std.Iterators
/--
Induction principle for finite iterators: One can define a function `f` that maps every
@@ -46,4 +47,4 @@ def Iter.inductSkips {α β} [Iterator α Id β] [Productive α Id]
step it (fun {it'} _ => inductSkips motive step it')
termination_by it.finitelyManySkips
end Std.Iterators
end Std

View File

@@ -16,7 +16,8 @@ public import Init.Data.Array.Attach
public section
namespace Std.Iterators
namespace Std
open Std.Iterators
theorem Iter.unattach_eq_toIter_unattach_toIterM [Iterator α Id β] {it : Iter (α := α) β} {hP} :
it.attachWith P hP =
@@ -26,8 +27,7 @@ theorem Iter.unattach_eq_toIter_unattach_toIterM [Iterator α Id β] {it : Iter
theorem Iter.unattach_toList_attachWith [Iterator α Id β]
{it : Iter (α := α) β} {hP}
[Finite α Id] [IteratorCollect α Id Id]
[LawfulIteratorCollect α Id Id] :
[Finite α Id] :
(it.attachWith P hP).toList.unattach = it.toList := by
simp [Iter.unattach_eq_toIter_unattach_toIterM,
Id.run_map (f := List.unattach), IterM.map_unattach_toList_attachWith,
@@ -36,8 +36,7 @@ theorem Iter.unattach_toList_attachWith [Iterator α Id β]
@[simp]
theorem Iter.toList_attachWith [Iterator α Id β]
{it : Iter (α := α) β} {hP}
[Finite α Id] [IteratorCollect α Id Id]
[LawfulIteratorCollect α Id Id] :
[Finite α Id] :
(it.attachWith P hP).toList = it.toList.attachWith P
(fun out h => hP out (isPlausibleIndirectOutput_of_mem_toList h)) := by
apply List.ext_getElem
@@ -49,16 +48,14 @@ theorem Iter.toList_attachWith [Iterator α Id β]
theorem Iter.unattach_toListRev_attachWith [Iterator α Id β]
{it : Iter (α := α) β} {hP}
[Finite α Id] [IteratorCollect α Id Id]
[LawfulIteratorCollect α Id Id] :
[Finite α Id] :
(it.attachWith P hP).toListRev.unattach = it.toListRev := by
simp [toListRev_eq]
@[simp]
theorem Iter.toListRev_attachWith [Iterator α Id β]
{it : Iter (α := α) β} {hP}
[Finite α Id] [IteratorCollect α Id Id]
[LawfulIteratorCollect α Id Id] :
[Finite α Id] :
(it.attachWith P hP).toListRev = it.toListRev.attachWith P
(fun out h => hP out (isPlausibleIndirectOutput_of_mem_toListRev h)) := by
simp [toListRev_eq]
@@ -66,16 +63,14 @@ theorem Iter.toListRev_attachWith [Iterator α Id β]
@[simp]
theorem Iter.unattach_toArray_attachWith [Iterator α Id β]
{it : Iter (α := α) β} {hP}
[Finite α Id] [IteratorCollect α Id Id]
[LawfulIteratorCollect α Id Id] :
[Finite α Id] :
(it.attachWith P hP).toListRev.unattach = it.toListRev := by
simp [toListRev_eq]
@[simp]
theorem Iter.toArray_attachWith [Iterator α Id β]
{it : Iter (α := α) β} {hP}
[Finite α Id] [IteratorCollect α Id Id]
[LawfulIteratorCollect α Id Id] :
[Finite α Id] :
(it.attachWith P hP).toArray = it.toArray.attachWith P
(fun out h => hP out (isPlausibleIndirectOutput_of_mem_toArray h)) := by
suffices (it.attachWith P hP).toArray.toList = (it.toArray.attachWith P
@@ -89,8 +84,7 @@ theorem Iter.count_attachWith [Iterator α Id β]
[Finite α Id] [IteratorLoop α Id Id]
[LawfulIteratorLoop α Id Id] :
(it.attachWith P hP).count = it.count := by
letI : IteratorCollect α Id Id := .defaultImplementation
rw [ Iter.length_toList_eq_count, toList_attachWith]
simp
end Std.Iterators
end Std

View File

@@ -9,10 +9,12 @@ prelude
public import Init.Data.Iterators.Lemmas.Consumers
public import Init.Data.Iterators.Lemmas.Combinators.Monadic.FilterMap
public import Init.Data.Iterators.Combinators.FilterMap
import Init.Control.Lawful.MonadAttach.Lemmas
public section
namespace Std.Iterators
namespace Std
open Std.Iterators
variable {α β γ : Type w} [Iterator α Id β] {it : Iter (α := α) β}
{m : Type w Type w'} {n : Type w Type w''}
@@ -31,15 +33,15 @@ theorem Iter.mapWithPostcondition_eq_toIter_mapWithPostcondition_toIterM [Monad
it.mapWithPostcondition f = (letI : MonadLift Id m := pure; it.toIterM.mapWithPostcondition f) :=
rfl
theorem Iter.filterMapM_eq_toIter_filterMapM_toIterM [Monad m] {f : β m (Option γ)} :
theorem Iter.filterMapM_eq_toIter_filterMapM_toIterM [Monad m] [MonadAttach m] {f : β m (Option γ)} :
it.filterMapM f = (letI : MonadLift Id m := pure; it.toIterM.filterMapM f) :=
rfl
theorem Iter.filterM_eq_toIter_filterM_toIterM [Monad m] {f : β m (ULift Bool)} :
theorem Iter.filterM_eq_toIter_filterM_toIterM [Monad m] [MonadAttach m] {f : β m (ULift Bool)} :
it.filterM f = (letI : MonadLift Id m := pure; it.toIterM.filterM f) :=
rfl
theorem Iter.mapM_eq_toIter_mapM_toIterM [Monad m] {f : β m γ} :
theorem Iter.mapM_eq_toIter_mapM_toIterM [Monad m] [MonadAttach m] {f : β m γ} :
it.mapM f = (letI : MonadLift Id m := pure; it.toIterM.mapM f) :=
rfl
@@ -107,7 +109,7 @@ theorem Iter.step_filterWithPostcondition {f : β → PostconditionT n (ULift Bo
| .done h => rfl
theorem Iter.step_mapWithPostcondition {f : β PostconditionT n γ}
[Monad n] [LawfulMonad n] [MonadLiftT m n] :
[Monad n] [LawfulMonad n] :
(it.mapWithPostcondition f).step = (do
match it.step with
| .yield it' out h => do
@@ -128,15 +130,15 @@ theorem Iter.step_mapWithPostcondition {f : β → PostconditionT n γ}
| .done h => rfl
theorem Iter.step_filterMapM {β' : Type w} {f : β n (Option β')}
[Monad n] [LawfulMonad n] [MonadLiftT m n] :
[Monad n] [MonadAttach n] [LawfulMonad n] [MonadLiftT m n] :
(it.filterMapM f).step = (do
match it.step with
| .yield it' out h => do
match f out with
| none =>
pure <| .deflate <| .skip (it'.filterMapM f) (.yieldNone (out := out) h .intro)
| some out' =>
pure <| .deflate <| .yield (it'.filterMapM f) out' (.yieldSome (out := out) h .intro)
match MonadAttach.attach (f out) with
| none, hf =>
pure <| .deflate <| .skip (it'.filterMapM f) (.yieldNone (out := out) h hf)
| some out', hf =>
pure <| .deflate <| .yield (it'.filterMapM f) out' (.yieldSome (out := out) h hf)
| .skip it' h =>
pure <| .deflate <| .skip (it'.filterMapM f) (.skip h)
| .done h =>
@@ -153,15 +155,15 @@ theorem Iter.step_filterMapM {β' : Type w} {f : β → n (Option β')}
| .done h => rfl
theorem Iter.step_filterM {f : β n (ULift Bool)}
[Monad n] [LawfulMonad n] [MonadLiftT m n] :
[Monad n] [MonadAttach n] [LawfulMonad n] [MonadLiftT m n] :
(it.filterM f).step = (do
match it.step with
| .yield it' out h => do
match f out with
| .up false =>
pure <| .deflate <| .skip (it'.filterM f) (.yieldNone (out := out) h .up false, .intro, rfl)
| .up true =>
pure <| .deflate <| .yield (it'.filterM f) out (.yieldSome (out := out) h .up true, .intro, rfl)
match MonadAttach.attach (f out) with
| .up false, hf =>
pure <| .deflate <| .skip (it'.filterM f) (.yieldNone (out := out) h .up false, hf, rfl)
| .up true, hf =>
pure <| .deflate <| .yield (it'.filterM f) out (.yieldSome (out := out) h .up true, hf, rfl)
| .skip it' h =>
pure <| .deflate <| .skip (it'.filterM f) (.skip h)
| .done h =>
@@ -171,20 +173,19 @@ theorem Iter.step_filterM {f : β → n (ULift Bool)}
generalize it.toIterM.step = step
match step.inflate with
| .yield it' out h =>
simp [PostconditionT.lift]
apply bind_congr
intro step
simp only
apply bind_congr; intro step
rcases step with _ | _ <;> rfl
| .skip it' h => rfl
| .done h => rfl
theorem Iter.step_mapM {f : β n γ}
[Monad n] [LawfulMonad n] :
[Monad n] [MonadAttach n] [LawfulMonad n] :
(it.mapM f).step = (do
match it.step with
| .yield it' out h => do
let out' f out
pure <| .deflate <| .yield (it'.mapM f) out' (.yieldSome h out', True.intro, rfl)
let out' MonadAttach.attach (f out)
pure <| .deflate <| .yield (it'.mapM f) out'.val (.yieldSome h out', out'.property, rfl)
| .skip it' h =>
pure <| .deflate <| .skip (it'.mapM f) (.skip h)
| .done h =>
@@ -290,174 +291,417 @@ def Iter.val_step_filter {f : β → Bool} :
· simp
@[simp]
theorem Iter.toList_filterMap
[IteratorCollect α Id Id] [LawfulIteratorCollect α Id Id] [Finite α Id]
theorem Iter.toList_filterMap [Finite α Id]
{f : β Option γ} :
(it.filterMap f).toList = it.toList.filterMap f := by
simp [filterMap_eq_toIter_filterMap_toIterM, toList_eq_toList_toIterM, IterM.toList_filterMap]
@[simp]
theorem Iter.toList_map
[IteratorCollect α Id Id] [LawfulIteratorCollect α Id Id] [Finite α Id]
{f : β γ} :
theorem Iter.toList_mapWithPostcondition [Monad m] [LawfulMonad m] [Finite α Id]
{f : β PostconditionT m γ} :
(it.mapWithPostcondition f).toList = it.toList.mapM (fun x => (f x).run) := by
simp [Iter.mapWithPostcondition, IterM.toList_mapWithPostcondition, Iter.toList_eq_toList_toIterM]
@[simp]
theorem Iter.toList_mapM [Monad m] [MonadAttach m] [LawfulMonad m] [WeaklyLawfulMonadAttach m]
[Finite α Id] {f : β m γ} :
(it.mapM f).toList = it.toList.mapM f := by
simp [Iter.mapM_eq_toIter_mapM_toIterM, IterM.toList_mapM, Iter.toList_eq_toList_toIterM]
@[simp]
theorem Iter.toList_map [Finite α Id] {f : β γ} :
(it.map f).toList = it.toList.map f := by
simp [map_eq_toIter_map_toIterM, IterM.toList_map, Iter.toList_eq_toList_toIterM]
@[simp]
theorem Iter.toList_filter
[IteratorCollect α Id Id] [LawfulIteratorCollect α Id Id] [Finite α Id]
{f : β Bool} :
theorem Iter.toList_filter [Finite α Id] {f : β Bool} :
(it.filter f).toList = it.toList.filter f := by
simp [filter_eq_toIter_filter_toIterM, IterM.toList_filter, Iter.toList_eq_toList_toIterM]
@[simp]
theorem Iter.toListRev_filterMap
[IteratorCollect α Id Id] [LawfulIteratorCollect α Id Id] [Finite α Id]
theorem Iter.toList_filterMapWithPostcondition_filterMapWithPostcondition
[Monad m] [LawfulMonad m] [Monad n] [LawfulMonad n] [MonadLiftT m n] [LawfulMonadLiftT m n]
[Finite α Id]
{f : β PostconditionT m (Option γ)} {g : γ PostconditionT n (Option δ)} :
((it.filterMapWithPostcondition f).filterMapWithPostcondition g).toList =
(it.filterMapWithPostcondition (m := n) (fun b => do
match (haveI : MonadLift m n := monadLift; f b) with
| none => return none
| some fb => g fb)).toList := by
simp only [Iter.filterMapWithPostcondition]
rw [IterM.toList_filterMapWithPostcondition_filterMapWithPostcondition,
instMonadLiftTOfMonadLift_instMonadLiftTOfPure]
rfl
@[simp]
theorem Iter.toList_mapWithPostcondition_mapWithPostcondition
[Monad m] [LawfulMonad m] [Monad n] [LawfulMonad n] [MonadLiftT m n] [LawfulMonadLiftT m n]
[Finite α Id]
{f : β PostconditionT m γ} {g : γ PostconditionT n δ} :
((it.mapWithPostcondition f).mapWithPostcondition g).toList =
(it.mapWithPostcondition (m := n) (haveI : MonadLift m n := monadLift; fun b => f b >>= g)).toList := by
simp only [Iter.mapWithPostcondition]
rw [IterM.toList_mapWithPostcondition_mapWithPostcondition,
instMonadLiftTOfMonadLift_instMonadLiftTOfPure]
@[simp]
theorem Iter.toListRev_filterMap [Finite α Id]
{f : β Option γ} :
(it.filterMap f).toListRev = it.toListRev.filterMap f := by
simp [filterMap_eq_toIter_filterMap_toIterM, toListRev_eq_toListRev_toIterM, IterM.toListRev_filterMap]
@[simp]
theorem Iter.toListRev_map
[IteratorCollect α Id Id] [LawfulIteratorCollect α Id Id] [Finite α Id]
theorem Iter.toListRev_map [Finite α Id]
{f : β γ} :
(it.map f).toListRev = it.toListRev.map f := by
simp [map_eq_toIter_map_toIterM, IterM.toListRev_map, Iter.toListRev_eq_toListRev_toIterM]
@[simp]
theorem Iter.toListRev_filter
[IteratorCollect α Id Id] [LawfulIteratorCollect α Id Id] [Finite α Id]
theorem Iter.toListRev_filter [Finite α Id]
{f : β Bool} :
(it.filter f).toListRev = it.toListRev.filter f := by
simp [filter_eq_toIter_filter_toIterM, IterM.toListRev_filter, Iter.toListRev_eq_toListRev_toIterM]
@[simp]
theorem Iter.toArray_filterMap
[IteratorCollect α Id Id] [LawfulIteratorCollect α Id Id] [Finite α Id]
theorem Iter.toArray_filterMap [Finite α Id]
{f : β Option γ} :
(it.filterMap f).toArray = it.toArray.filterMap f := by
simp [filterMap_eq_toIter_filterMap_toIterM, toArray_eq_toArray_toIterM, IterM.toArray_filterMap]
@[simp]
theorem Iter.toArray_map
[IteratorCollect α Id Id] [LawfulIteratorCollect α Id Id] [Finite α Id]
{f : β γ} :
theorem Iter.toArray_mapWithPostcondition [Monad m] [LawfulMonad m] [Finite α Id]
{f : β PostconditionT m γ} :
(it.mapWithPostcondition f).toArray = it.toArray.mapM (fun x => (f x).run) := by
simp [Iter.mapWithPostcondition, IterM.toArray_mapWithPostcondition, Iter.toArray_eq_toArray_toIterM]
@[simp]
theorem Iter.toArray_mapM [Monad m] [MonadAttach m] [LawfulMonad m] [WeaklyLawfulMonadAttach m]
[Finite α Id] {f : β m γ} :
(it.mapM f).toArray = it.toArray.mapM f := by
simp [Iter.mapM_eq_toIter_mapM_toIterM, IterM.toArray_mapM, Iter.toArray_eq_toArray_toIterM]
@[simp]
theorem Iter.toArray_map [Finite α Id] {f : β γ} :
(it.map f).toArray = it.toArray.map f := by
simp [map_eq_toIter_map_toIterM, IterM.toArray_map, Iter.toArray_eq_toArray_toIterM]
@[simp]
theorem Iter.toArray_filter
[IteratorCollect α Id Id] [LawfulIteratorCollect α Id Id] [Finite α Id]
{f : β Bool} :
theorem Iter.toArray_filter[Finite α Id] {f : β Bool} :
(it.filter f).toArray = it.toArray.filter f := by
simp [filter_eq_toIter_filter_toIterM, IterM.toArray_filter, Iter.toArray_eq_toArray_toIterM]
@[simp]
theorem Iter.toArray_filterMapWithPostcondition_filterMapWithPostcondition
[Monad m] [LawfulMonad m] [Monad n] [LawfulMonad n] [MonadLiftT m n] [LawfulMonadLiftT m n]
[Finite α Id]
{f : β PostconditionT m (Option γ)} {g : γ PostconditionT n (Option δ)} :
((it.filterMapWithPostcondition f).filterMapWithPostcondition g).toArray =
(it.filterMapWithPostcondition (m := n) (fun b => do
match (haveI : MonadLift m n := monadLift; f b) with
| none => return none
| some fb => g fb)).toArray := by
simp only [Iter.filterMapWithPostcondition]
rw [IterM.toArray_filterMapWithPostcondition_filterMapWithPostcondition,
instMonadLiftTOfMonadLift_instMonadLiftTOfPure]
rfl
@[simp]
theorem Iter.toArray_mapWithPostcondition_mapWithPostcondition
[Monad m] [LawfulMonad m] [Monad n] [LawfulMonad n] [MonadLiftT m n] [LawfulMonadLiftT m n]
[Finite α Id]
{f : β PostconditionT m γ} {g : γ PostconditionT n δ} :
((it.mapWithPostcondition f).mapWithPostcondition g).toArray =
(it.mapWithPostcondition (m := n) (haveI : MonadLift m n := monadLift; fun b => f b >>= g)).toArray := by
simp only [Iter.mapWithPostcondition]
rw [IterM.toArray_mapWithPostcondition_mapWithPostcondition,
instMonadLiftTOfMonadLift_instMonadLiftTOfPure]
section ForIn
theorem Iter.forIn_filterMapWithPostcondition
[Monad n] [LawfulMonad n] [Monad o] [LawfulMonad o]
[MonadLiftT n o] [LawfulMonadLiftT n o] [Finite α Id]
[IteratorLoop α Id o] [LawfulIteratorLoop α Id o]
{it : Iter (α := α) β} {f : β PostconditionT n (Option β₂)} {init : γ}
{g : β₂ γ o (ForInStep γ)} :
forIn (it.filterMapWithPostcondition f) init g = forIn it init (fun out acc => do
match (f out).run with
| some c => g c acc
| none => return .yield acc) := by
simp [Iter.forIn_eq_forIn_toIterM, filterMapWithPostcondition, IterM.forIn_filterMapWithPostcondition,
instMonadLiftTOfMonadLift_instMonadLiftTOfPure]; rfl
theorem Iter.forIn_filterMapM
[Monad n] [LawfulMonad n] [Monad o] [LawfulMonad o]
[MonadAttach n] [WeaklyLawfulMonadAttach n]
[MonadLiftT n o] [LawfulMonadLiftT n o]
[Finite α Id] [IteratorLoop α Id o] [LawfulIteratorLoop α Id o]
{it : Iter (α := α) β} {f : β n (Option β₂)} {init : γ} {g : β₂ γ o (ForInStep γ)} :
forIn (it.filterMapM f) init g = forIn it init (fun out acc => do
match f out with
| some c => g c acc
| none => return .yield acc) := by
simp [filterMapM, forIn_eq_forIn_toIterM, IterM.forIn_filterMapM,
instMonadLiftTOfMonadLift_instMonadLiftTOfPure]; rfl
theorem Iter.forIn_filterMap
[Monad n] [LawfulMonad n] [Finite α Id]
[IteratorLoop α Id n] [LawfulIteratorLoop α Id n]
{it : Iter (α := α) β} {f : β Option β₂} {init : γ} {g : β₂ γ n (ForInStep γ)} :
forIn (it.filterMap f) init g = forIn it init (fun out acc => do
match f out with
| some c => g c acc
| none => return .yield acc) := by
simp [filterMap, forIn_eq_forIn_toIterM, IterM.forIn_filterMap]; rfl
theorem Iter.forIn_mapWithPostcondition
[Monad n] [LawfulMonad n] [Monad o] [LawfulMonad o]
[MonadLiftT n o] [LawfulMonadLiftT n o] [Finite α Id]
[IteratorLoop α Id o] [LawfulIteratorLoop α Id o]
{it : Iter (α := α) β} {f : β PostconditionT n β₂} {init : γ}
{g : β₂ γ o (ForInStep γ)} :
forIn (it.mapWithPostcondition f) init g =
forIn it init (fun out acc => do g ( (f out).run) acc) := by
simp [mapWithPostcondition, forIn_eq_forIn_toIterM, IterM.forIn_mapWithPostcondition,
instMonadLiftTOfMonadLift_instMonadLiftTOfPure]
theorem Iter.forIn_mapM
[Monad n] [LawfulMonad n] [Monad o] [LawfulMonad o]
[MonadAttach n] [WeaklyLawfulMonadAttach n]
[MonadLiftT n o] [LawfulMonadLiftT n o]
[Finite α Id]
[IteratorLoop α Id o] [LawfulIteratorLoop α Id o]
{it : Iter (α := α) β} {f : β n β₂} {init : γ} {g : β₂ γ o (ForInStep γ)} :
forIn (it.mapM f) init g = forIn it init (fun out acc => do g ( f out) acc) := by
rw [mapM, forIn_eq_forIn_toIterM, IterM.forIn_mapM, instMonadLiftTOfMonadLift_instMonadLiftTOfPure]
theorem Iter.forIn_map
[Monad n] [LawfulMonad n]
[Finite α Id] [IteratorLoop α Id n] [LawfulIteratorLoop α Id n]
{it : Iter (α := α) β} {f : β β₂} {init : γ} {g : β₂ γ n (ForInStep γ)} :
forIn (it.map f) init g = forIn it init (fun out acc => do g (f out) acc) := by
simp [map, forIn_eq_forIn_toIterM, IterM.forIn_map]
theorem Iter.forIn_filterWithPostcondition
[Monad n] [LawfulMonad n] [Monad o] [LawfulMonad o]
[MonadLiftT n o] [LawfulMonadLiftT n o]
[Finite α Id] [IteratorLoop α Id o] [LawfulIteratorLoop α Id o]
{it : Iter (α := α) β} {f : β PostconditionT n (ULift Bool)} {init : γ}
{g : β γ o (ForInStep γ)} :
haveI : MonadLift n o := monadLift
forIn (it.filterWithPostcondition f) init g =
forIn it init (fun out acc => do if ( (f out).run).down then g out acc else return .yield acc) := by
simp [filterWithPostcondition, forIn_eq_forIn_toIterM, IterM.forIn_filterWithPostcondition,
instMonadLiftTOfMonadLift_instMonadLiftTOfPure]
theorem Iter.forIn_filterM
[Monad n] [LawfulMonad n] [Monad o] [LawfulMonad o]
[MonadAttach n] [WeaklyLawfulMonadAttach n]
[MonadLiftT n o] [LawfulMonadLiftT n o] [Finite α Id]
[IteratorLoop α Id o] [LawfulIteratorLoop α Id o]
{it : Iter (α := α) β} {f : β n (ULift Bool)} {init : γ} {g : β γ o (ForInStep γ)} :
forIn (it.filterM f) init g = forIn it init (fun out acc => do if ( f out).down then g out acc else return .yield acc) := by
simp [filterM, forIn_eq_forIn_toIterM, IterM.forIn_filterM,
instMonadLiftTOfMonadLift_instMonadLiftTOfPure]
theorem Iter.forIn_filter
[Monad n] [LawfulMonad n]
[Finite α Id] [IteratorLoop α Id n] [LawfulIteratorLoop α Id n]
{it : Iter (α := α) β} {f : β Bool} {init : γ} {g : β γ n (ForInStep γ)} :
forIn (it.filter f) init g = forIn it init (fun out acc => do if f out then g out acc else return .yield acc) := by
simp [filter, forIn_eq_forIn_toIterM, IterM.forIn_filter]
end ForIn
section Fold
theorem Iter.foldM_filterMapM {α β γ δ : Type w} {m : Type w Type w'} {n : Type w Type w''}
[Iterator α Id β] [Finite α Id] [Monad m] [Monad n] [LawfulMonad m] [LawfulMonad n]
[IteratorLoop α Id Id] [IteratorLoop α Id m] [IteratorLoop α Id n]
[MonadLiftT m n] [LawfulMonadLiftT m n]
[LawfulIteratorLoop α Id Id] [LawfulIteratorLoop α Id m] [LawfulIteratorLoop α Id n]
{f : β m (Option γ)} {g : δ γ n δ} {init : δ} {it : Iter (α := α) β} :
theorem Iter.foldM_filterMapWithPostcondition {α β γ δ : Type w}
{n : Type w Type w''} {o : Type w Type w'''}
[Iterator α Id β] [Finite α Id]
[Monad n] [Monad o] [LawfulMonad n] [LawfulMonad o]
[IteratorLoop α Id n] [IteratorLoop α Id o]
[LawfulIteratorLoop α Id n] [LawfulIteratorLoop α Id o]
[MonadLiftT n o] [LawfulMonadLiftT n o]
{f : β PostconditionT n (Option γ)} {g : δ γ o δ} {init : δ} {it : Iter (α := α) β} :
(it.filterMapWithPostcondition f).foldM (init := init) g =
it.foldM (init := init) (fun d b => do
let some c (f b).run | pure d
g d c) := by
rw [filterMapWithPostcondition, IterM.foldM_filterMapWithPostcondition, foldM_eq_foldM_toIterM,
instMonadLiftTOfMonadLift_instMonadLiftTOfPure]; rfl
theorem Iter.foldM_filterMapM {α β γ δ : Type w}
{n : Type w Type w''} {o : Type w Type w'''}
[Iterator α Id β] [Finite α Id]
[Monad n] [MonadAttach n] [LawfulMonad n] [WeaklyLawfulMonadAttach n]
[Monad o] [LawfulMonad o]
[IteratorLoop α Id n] [IteratorLoop α Id o]
[LawfulIteratorLoop α Id n] [LawfulIteratorLoop α Id o]
[MonadLiftT n o] [LawfulMonadLiftT n o]
{f : β n (Option γ)} {g : δ γ o δ} {init : δ} {it : Iter (α := α) β} :
(it.filterMapM f).foldM (init := init) g =
it.foldM (init := init) (fun d b => do
let some c f b | pure d
g d c) := by
rw [foldM_eq_foldM_toIterM, filterMapM_eq_toIter_filterMapM_toIterM, IterM.foldM_filterMapM]
congr
simp [instMonadLiftTOfMonadLift, Id.instMonadLiftTOfPure]
simp [filterMapM, IterM.foldM_filterMapM, foldM_eq_foldM_toIterM,
instMonadLiftTOfMonadLift_instMonadLiftTOfPure]; rfl
theorem Iter.foldM_mapM {α β γ δ : Type w} {m : Type w Type w'} {n : Type w Type w''}
[Iterator α Id β] [Finite α Id] [Monad m] [Monad n] [LawfulMonad m] [LawfulMonad n]
[IteratorLoop α Id m] [IteratorLoop α Id n]
[LawfulIteratorLoop α Id m] [LawfulIteratorLoop α Id n]
[MonadLiftT m n] [LawfulMonadLiftT m n]
{f : β m γ} {g : δ γ n δ} {init : δ} {it : Iter (α := α) β} :
theorem Iter.foldM_mapWithPostcondition {α β γ δ : Type w}
{n : Type w Type w''} {o : Type w Type w'''}
[Iterator α Id β] [Finite α Id]
[Monad m] [Monad n] [Monad o] [LawfulMonad m][LawfulMonad n] [LawfulMonad o]
[IteratorLoop α Id n] [IteratorLoop α Id o]
[LawfulIteratorLoop α Id n] [LawfulIteratorLoop α Id o]
[MonadLiftT n o] [LawfulMonadLiftT n o]
{f : β PostconditionT n γ} {g : δ γ o δ} {init : δ} {it : Iter (α := α) β} :
(it.mapWithPostcondition f).foldM (init := init) g =
it.foldM (init := init) (fun d b => do let c (f b).run; g d c) := by
simp [mapWithPostcondition, IterM.foldM_mapWithPostcondition, foldM_eq_foldM_toIterM,
instMonadLiftTOfMonadLift_instMonadLiftTOfPure]
theorem Iter.foldM_mapM {α β γ δ : Type w}
{n : Type w Type w''} {o : Type w Type w'''}
[Iterator α Id β] [Finite α Id]
[Monad n] [MonadAttach n] [LawfulMonad n] [WeaklyLawfulMonadAttach n]
[Monad o] [LawfulMonad o]
[IteratorLoop α Id n] [IteratorLoop α Id o]
[LawfulIteratorLoop α Id n] [LawfulIteratorLoop α Id o]
[MonadLiftT n o] [LawfulMonadLiftT n o]
{f : β n γ} {g : δ γ o δ} {init : δ} {it : Iter (α := α) β} :
haveI : MonadLift n o := MonadLiftT.monadLift
(it.mapM f).foldM (init := init) g =
it.foldM (init := init) (fun d b => do let c f b; g d c) := by
rw [foldM_eq_foldM_toIterM, mapM_eq_toIter_mapM_toIterM, IterM.foldM_mapM]
congr
simp [instMonadLiftTOfMonadLift, Id.instMonadLiftTOfPure]
simp [mapM, IterM.foldM_mapM, foldM_eq_foldM_toIterM,
instMonadLiftTOfMonadLift_instMonadLiftTOfPure]
theorem Iter.foldM_filterMap {α β γ : Type w} {δ : Type x} {m : Type x Type w'}
[Iterator α Id β] [Finite α Id] [Monad m] [LawfulMonad m]
[IteratorLoop α Id m] [LawfulIteratorLoop α Id m]
{f : β Option γ} {g : δ γ m δ} {init : δ} {it : Iter (α := α) β} :
theorem Iter.foldM_filterWithPostcondition {α β δ : Type w}
{n : Type w Type w''} {o : Type w Type w'''}
[Iterator α Id β] [Finite α Id]
[Monad n] [Monad o] [LawfulMonad n] [LawfulMonad o]
[IteratorLoop α Id n] [IteratorLoop α Id o]
[LawfulIteratorLoop α Id n] [LawfulIteratorLoop α Id o]
[MonadLiftT n o] [LawfulMonadLiftT n o]
{f : β PostconditionT n (ULift Bool)} {g : δ β o δ} {init : δ} {it : Iter (α := α) β} :
(it.filterWithPostcondition f).foldM (init := init) g =
it.foldM (init := init) (fun d b => do if ( (f b).run).down then g d b else pure d) := by
simp [filterWithPostcondition, IterM.foldM_filterWithPostcondition, foldM_eq_foldM_toIterM,
instMonadLiftTOfMonadLift_instMonadLiftTOfPure]
theorem Iter.foldM_filterM {α β δ : Type w}
{n : Type w Type w''} {o : Type w Type w'''}
[Iterator α Id β] [Finite α Id]
[Monad n] [MonadAttach n] [LawfulMonad n] [WeaklyLawfulMonadAttach n]
[Monad o] [LawfulMonad o]
[IteratorLoop α Id n] [IteratorLoop α Id o]
[LawfulIteratorLoop α Id n] [LawfulIteratorLoop α Id o]
[MonadLiftT n o] [LawfulMonadLiftT n o]
{f : β n (ULift Bool)} {g : δ β o δ} {init : δ} {it : Iter (α := α) β} :
(it.filterM f).foldM (init := init) g =
it.foldM (init := init) (fun d b => do if ( f b).down then g d b else pure d) := by
simp [filterM, IterM.foldM_filterM, foldM_eq_foldM_toIterM,
instMonadLiftTOfMonadLift_instMonadLiftTOfPure]
theorem Iter.foldM_filterMap {α β γ δ : Type w} {n : Type w Type w''}
[Iterator α Id β] [Finite α Id] [Monad n] [LawfulMonad n]
[IteratorLoop α Id n]
[LawfulIteratorLoop α Id n]
{f : β Option γ} {g : δ γ n δ} {init : δ} {it : Iter (α := α) β} :
(it.filterMap f).foldM (init := init) g =
it.foldM (init := init) (fun d b => do
let some c := f b | pure d
g d c) := by
induction it using Iter.inductSteps generalizing init with | step it ihy ihs
rw [foldM_eq_match_step, foldM_eq_match_step, step_filterMap]
-- There seem to be some type dependencies that, combined with nested match expressions,
-- force us to split a lot.
split <;> rename_i h
· split at h
· split at h
· cases h
· cases h; simp [*, ihy _]
· cases h
· cases h
· split at h
· split at h
· cases h; simp [*, ihy _]
· cases h
· cases h; simp [*, ihs _]
· cases h
· split at h
· split at h
· cases h
· cases h
· cases h
· simp [*]
simp [filterMap, IterM.foldM_filterMap, foldM_eq_foldM_toIterM]; rfl
theorem Iter.foldM_map {α β γ : Type w} {δ : Type x} {m : Type x Type w'}
[Iterator α Id β] [Finite α Id] [Monad m] [LawfulMonad m]
[IteratorLoop α Id m] [LawfulIteratorLoop α Id m]
{f : β γ} {g : δ γ m δ} {init : δ} {it : Iter (α := α) β} :
theorem Iter.foldM_map {α β γ δ : Type w} {n : Type w Type w''}
[Iterator α Id β] [Finite α Id] [Monad n] [LawfulMonad n]
[IteratorLoop α Id n] [LawfulIteratorLoop α Id n]
{f : β γ} {g : δ γ n δ} {init : δ} {it : Iter (α := α) β} :
(it.map f).foldM (init := init) g =
it.foldM (init := init) (fun d b => g d (f b)) := by
induction it using Iter.inductSteps generalizing init with | step it ihy ihs
rw [foldM_eq_match_step, foldM_eq_match_step, step_map]
cases it.step using PlausibleIterStep.casesOn
· simp [*, ihy _]
· simp [*, ihs _]
· simp
it.foldM (init := init) (fun d b => do g d (f b)) := by
simp [foldM_eq_forIn, forIn_map]
theorem Iter.fold_filterMapM {α β γ δ : Type w} {m : Type w Type w'}
[Iterator α Id β] [Finite α Id] [Monad m] [LawfulMonad m]
[IteratorLoop α Id Id.{w}] [IteratorLoop α Id m]
[LawfulIteratorLoop α Id Id] [LawfulIteratorLoop α Id m]
{f : β m (Option γ)} {g : δ γ δ} {init : δ} {it : Iter (α := α) β} :
theorem Iter.foldM_filter {α β δ : Type w} {n : Type w Type w''}
[Iterator α Id β] [Finite α Id] [Monad n] [LawfulMonad n]
[IteratorLoop α Id n] [LawfulIteratorLoop α Id n]
{f : β Bool} {g : δ β n δ} {init : δ} {it : Iter (α := α) β} :
(it.filter f).foldM (init := init) g =
it.foldM (init := init) (fun d b => if f b then g d b else pure d) := by
simp only [foldM_eq_forIn, forIn_filter]
congr 1; ext out acc
cases f out <;> simp
theorem Iter.fold_filterMapWithPostcondition {α β γ δ : Type w} {n : Type w Type w''}
[Iterator α Id β] [Finite α Id]
[Monad n] [LawfulMonad n]
[IteratorLoop α Id n] [LawfulIteratorLoop α Id n]
{f : β PostconditionT n (Option γ)} {g : δ γ δ} {init : δ} {it : Iter (α := α) β} :
(it.filterMapWithPostcondition f).fold (init := init) g =
it.foldM (init := init) (fun d b => do
let some c (f b).run | pure d
return g d c) := by
simp [filterMapWithPostcondition, IterM.fold_filterMapWithPostcondition, foldM_eq_foldM_toIterM]
rfl
theorem Iter.fold_filterMapM {α β γ δ : Type w} {n : Type w Type w''}
[Iterator α Id β] [Finite α Id]
[Monad n] [MonadAttach n] [LawfulMonad n] [WeaklyLawfulMonadAttach n]
[IteratorLoop α Id n] [LawfulIteratorLoop α Id n]
{f : β n (Option γ)} {g : δ γ δ} {init : δ} {it : Iter (α := α) β} :
(it.filterMapM f).fold (init := init) g =
it.foldM (init := init) (fun d b => do
let some c f b | pure d
return g d c) := by
rw [foldM_eq_foldM_toIterM, filterMapM_eq_toIter_filterMapM_toIterM, IterM.fold_filterMapM]
rfl
simp [filterMapM, IterM.fold_filterMapM, foldM_eq_foldM_toIterM]; rfl
theorem Iter.fold_mapM {α β γ δ : Type w} {m : Type w Type w'}
[Iterator α Id β] [Finite α Id] [Monad m] [LawfulMonad m]
[IteratorLoop α Id Id.{w}] [IteratorLoop α Id m]
[LawfulIteratorLoop α Id Id] [LawfulIteratorLoop α Id m]
{f : β m γ} {g : δ γ δ} {init : δ} {it : Iter (α := α) β} :
theorem Iter.fold_mapWithPostcondition {α β γ δ : Type w} {n : Type w Type w''}
[Iterator α Id β] [Finite α Id]
[Monad n] [LawfulMonad n]
[IteratorLoop α Id n] [LawfulIteratorLoop α Id n]
{f : β PostconditionT n γ} {g : δ γ δ} {init : δ} {it : Iter (α := α) β} :
(it.mapWithPostcondition f).fold (init := init) g =
it.foldM (init := init) (fun d b => do let c (f b).run; return g d c) := by
simp [mapWithPostcondition, IterM.fold_mapWithPostcondition, foldM_eq_foldM_toIterM]
theorem Iter.fold_mapM {α β γ δ : Type w} {n : Type w Type w''}
[Iterator α Id β] [Finite α Id]
[Monad n] [MonadAttach n] [LawfulMonad n] [WeaklyLawfulMonadAttach n]
[IteratorLoop α Id n] [LawfulIteratorLoop α Id n]
{f : β n γ} {g : δ γ δ} {init : δ} {it : Iter (α := α) β} :
(it.mapM f).fold (init := init) g =
it.foldM (init := init) (fun d b => do return g d ( f b)) := by
rw [foldM_eq_foldM_toIterM, mapM_eq_toIter_mapM_toIterM, IterM.fold_mapM]
it.foldM (init := init) (fun d b => do let c f b; return g d c) := by
simp [mapM, IterM.fold_mapM, foldM_eq_foldM_toIterM]
theorem Iter.fold_filterMap {α β γ : Type w} {δ : Type x}
[Iterator α Id β] [Finite α Id] [IteratorLoop α Id Id] [LawfulIteratorLoop α Id Id]
theorem Iter.fold_filterWithPostcondition {α β δ : Type w}
{n : Type w Type w''}
[Iterator α Id β] [Finite α Id]
[Monad n] [LawfulMonad n]
[IteratorLoop α Id n] [LawfulIteratorLoop α Id n]
{f : β PostconditionT n (ULift Bool)} {g : δ β δ} {init : δ} {it : Iter (α := α) β} :
(it.filterWithPostcondition f).fold (init := init) g =
it.foldM (init := init) (fun d b => return if ( (f b).run).down then g d b else d) := by
simp [filterWithPostcondition, IterM.fold_filterWithPostcondition, foldM_eq_foldM_toIterM]
theorem Iter.fold_filterM {α β δ : Type w} {n : Type w Type w''}
[Iterator α Id β] [Finite α Id]
[Monad n] [MonadAttach n] [LawfulMonad n] [WeaklyLawfulMonadAttach n]
[IteratorLoop α Id n] [LawfulIteratorLoop α Id n]
{f : β n (ULift Bool)} {g : δ β δ} {init : δ} {it : Iter (α := α) β} :
(it.filterM f).fold (init := init) g =
it.foldM (init := init) (fun d b => return if ( f b).down then g d b else d) := by
simp [filterM, IterM.fold_filterM, foldM_eq_foldM_toIterM]
theorem Iter.fold_filterMap {α β γ δ : Type w}
[Iterator α Id β] [Finite α Id]
[IteratorLoop α Id Id] [LawfulIteratorLoop α Id Id]
{f : β Option γ} {g : δ γ δ} {init : δ} {it : Iter (α := α) β} :
(it.filterMap f).fold (init := init) g =
it.fold (init := init) (fun d b =>
match f b with
| some c => g d c
| _ => d) := by
simp only [fold_eq_foldM, foldM_filterMap]
rfl
simp [filterMap, IterM.fold_filterMap, fold_eq_fold_toIterM]; rfl
theorem Iter.fold_map {α β γ : Type w} {δ : Type x}
theorem Iter.fold_map {α β γ δ : Type w}
[Iterator α Id β] [Finite α Id]
[IteratorLoop α Id Id] [LawfulIteratorLoop α Id Id]
{f : β γ} {g : δ γ δ} {init : δ} {it : Iter (α := α) β} :
@@ -465,6 +709,14 @@ theorem Iter.fold_map {α β γ : Type w} {δ : Type x}
it.fold (init := init) (fun d b => g d (f b)) := by
simp [fold_eq_foldM, foldM_map]
theorem Iter.fold_filter {α β δ : Type w}
[Iterator α Id β] [Finite α Id]
[IteratorLoop α Id Id] [LawfulIteratorLoop α Id Id]
{f : β Bool} {g : δ β δ} {init : δ} {it : Iter (α := α) β} :
(it.filter f).fold (init := init) g =
it.fold (init := init) (fun d b => if f b then g d b else d) := by
simp [filter, IterM.fold_filter, fold_eq_fold_toIterM]
end Fold
section Count
@@ -479,7 +731,7 @@ theorem Iter.count_map {α β β' : Type w} [Iterator α Id β]
end Count
theorem Iter.anyM_filterMapM {α β β' : Type w} {m : Type w Type w'}
[Iterator α Id β] [Finite α Id] [Monad m] [LawfulMonad m]
[Iterator α Id β] [Finite α Id] [Monad m] [MonadAttach m] [LawfulMonad m] [WeaklyLawfulMonadAttach m]
{it : Iter (α := α) β} {f : β m (Option β')} {p : β' m (ULift Bool)} :
(it.filterMapM f).anyM p = (it.mapM (pure (f := m))).anyM (fun x => do
match f x with
@@ -494,14 +746,24 @@ This lemma expresses `Iter.anyM` in terms of `IterM.anyM`.
It requires all involved types to live in `Type 0`.
-/
theorem Iter.anyM_eq_anyM_mapM_pure {α β : Type} {m : Type Type w'} [Iterator α Id β]
[Finite α Id] [Monad m] [LawfulMonad m] [IteratorLoop α Id m] [LawfulIteratorLoop α Id m]
[Finite α Id] [Monad m] [MonadAttach m] [LawfulMonad m] [WeaklyLawfulMonadAttach m]
[IteratorLoop α Id m] [LawfulIteratorLoop α Id m]
{it : Iter (α := α) β} {p : β m Bool} :
it.anyM p = ULift.down <$> (it.mapM (α := α) (pure (f := m))).anyM (fun x => ULift.up <$> p x) := by
rw [anyM_eq_forIn, IterM.anyM_eq_forIn, map_eq_pure_bind]
induction it using Iter.inductSteps with | step it ihy ihs =>
rw [forIn_eq_match_step, IterM.forIn_eq_match_step, bind_assoc, step_mapM]
cases it.step using PlausibleIterStep.casesOn
· simp only [bind_assoc, liftM_pure, pure_bind, map_eq_pure_bind, Shrink.inflate_deflate]
· rename_i out _
simp only [bind_assoc, pure_bind, map_eq_pure_bind, Shrink.inflate_deflate,
liftM, monadLift]
have {x : m Bool} : x = MonadAttach.attach (pure out) >>= (fun _ => x) := by
rw (occs := [1]) [show x = pure out >>= (fun _ => x) by simp]
conv => lhs; rw [ WeaklyLawfulMonadAttach.map_attach (x := pure out)]
simp
refine Eq.trans this ?_
simp only [WeaklyLawfulMonadAttach.bind_attach_of_nonempty (x := pure out), pure_bind]
split; rotate_left; rfl
apply bind_congr; intro px
split
· simp
@@ -510,13 +772,13 @@ theorem Iter.anyM_eq_anyM_mapM_pure {α β : Type} {m : Type → Type w'} [Itera
· simp
theorem Iter.anyM_mapM {α β β' : Type w} {m : Type w Type w'}
[Iterator α Id β] [Finite α Id] [Monad m] [LawfulMonad m]
[Iterator α Id β] [Finite α Id] [Monad m] [MonadAttach m] [LawfulMonad m] [WeaklyLawfulMonadAttach m]
{it : Iter (α := α) β} {f : β m β'} {p : β' m (ULift Bool)} :
(it.mapM f).anyM p = (it.mapM (pure (f := m))).anyM (fun x => do p ( f x)) := by
rw [mapM_eq_toIter_mapM_toIterM, IterM.anyM_mapM, mapM_eq_toIter_mapM_toIterM]
theorem Iter.anyM_filterM {α β : Type w} {m : Type w Type w'}
[Iterator α Id β] [Finite α Id] [Monad m] [LawfulMonad m]
[Iterator α Id β] [Finite α Id] [Monad m] [MonadAttach m] [LawfulMonad m] [WeaklyLawfulMonadAttach m]
{it : Iter (α := α) β} {f : β m (ULift Bool)} {p : β m (ULift Bool)} :
(it.filterM f).anyM p = (it.mapM (pure (f := m))).anyM (fun x => do
if ( f x).down then
@@ -576,8 +838,8 @@ theorem Iter.anyM_filter {α β : Type w} {m : Type → Type w'}
· simp
theorem Iter.any_filterMapM {α β β' : Type w} {m : Type w Type w'}
[Iterator α Id β] [Finite α Id] [Monad m] [IteratorLoop α Id m]
[LawfulMonad m] [LawfulIteratorLoop α Id m]
[Iterator α Id β] [Finite α Id] [IteratorLoop α Id m]
[Monad m] [MonadAttach m] [LawfulMonad m] [WeaklyLawfulMonadAttach m] [LawfulIteratorLoop α Id m]
{it : Iter (α := α) β} {f : β m (Option β')} {p : β' Bool} :
(it.filterMapM f).any p = (it.mapM (pure (f := m))).anyM (fun x => do
match f x with
@@ -586,15 +848,15 @@ theorem Iter.any_filterMapM {α β β' : Type w} {m : Type w → Type w'}
simp [IterM.any_eq_anyM, anyM_filterMapM]
theorem Iter.any_mapM {α β β' : Type w} {m : Type w Type w'}
[Iterator α Id β] [Finite α Id] [Monad m] [IteratorLoop α Id m]
[LawfulMonad m] [LawfulIteratorLoop α Id m]
[Iterator α Id β] [Finite α Id] [IteratorLoop α Id m]
[Monad m] [MonadAttach m] [LawfulMonad m] [WeaklyLawfulMonadAttach m] [LawfulIteratorLoop α Id m]
{it : Iter (α := α) β} {f : β m β'} {p : β' Bool} :
(it.mapM f).any p = (it.mapM pure).anyM (fun x => (.up <| p ·) <$> (f x)) := by
simp [IterM.any_eq_anyM, anyM_mapM]
theorem Iter.any_filterM {α β : Type w} {m : Type w Type w'}
[Iterator α Id β] [Finite α Id] [Monad m] [IteratorLoop α Id m]
[LawfulMonad m] [LawfulIteratorLoop α Id m]
[Iterator α Id β] [Finite α Id] [IteratorLoop α Id m]
[Monad m] [MonadAttach m] [LawfulMonad m] [WeaklyLawfulMonadAttach m] [LawfulIteratorLoop α Id m]
{it : Iter (α := α) β} {f : β m (ULift Bool)} {p : β Bool} :
(it.filterM f).any p = (it.mapM (pure (f := m))).anyM (fun x => do
if ( f x).down then
@@ -636,7 +898,7 @@ theorem Iter.any_map {α β β' : Type w}
· simp
theorem Iter.allM_filterMapM {α β β' : Type w} {m : Type w Type w'}
[Iterator α Id β] [Finite α Id] [Monad m] [LawfulMonad m]
[Iterator α Id β] [Finite α Id] [Monad m] [MonadAttach m] [LawfulMonad m] [WeaklyLawfulMonadAttach m]
{it : Iter (α := α) β} {f : β m (Option β')} {p : β' m (ULift Bool)} :
(it.filterMapM f).allM p = (it.mapM (pure (f := m))).allM (fun x => do
match f x with
@@ -650,29 +912,19 @@ This lemma expresses `Iter.allM` in terms of `IterM.allM`.
It requires all involved types to live in `Type 0`.
-/
theorem Iter.allM_eq_allM_mapM_pure {α β : Type} {m : Type Type w'} [Iterator α Id β]
[Finite α Id] [Monad m] [LawfulMonad m] [IteratorLoop α Id m] [LawfulIteratorLoop α Id m]
{it : Iter (α := α) β} {p : β m Bool} :
[Finite α Id] [Monad m] [MonadAttach m] [LawfulMonad m] [WeaklyLawfulMonadAttach m]
[IteratorLoop α Id m] [LawfulIteratorLoop α Id m] {it : Iter (α := α) β} {p : β m Bool} :
it.allM p = ULift.down <$> (it.mapM (α := α) (pure (f := m))).allM (fun x => ULift.up <$> p x) := by
rw [allM_eq_forIn, IterM.allM_eq_forIn, map_eq_pure_bind]
induction it using Iter.inductSteps with | step it ihy ihs =>
rw [forIn_eq_match_step, IterM.forIn_eq_match_step, bind_assoc, step_mapM]
cases it.step using PlausibleIterStep.casesOn
· simp only [bind_assoc, liftM_pure, pure_bind, map_eq_pure_bind, Shrink.inflate_deflate]
apply bind_congr; intro px
split
· simp [ihy _]
· simp
· simp [ihs _]
· simp
simp [allM_eq_not_anyM_not, anyM_eq_anyM_mapM_pure, IterM.allM_eq_not_anyM_not]
theorem Iter.allM_mapM {α β β' : Type w} {m : Type w Type w'}
[Iterator α Id β] [Finite α Id] [Monad m] [LawfulMonad m]
[Iterator α Id β] [Finite α Id] [Monad m] [MonadAttach m] [LawfulMonad m] [WeaklyLawfulMonadAttach m]
{it : Iter (α := α) β} {f : β m β'} {p : β' m (ULift Bool)} :
(it.mapM f).allM p = (it.mapM (pure (f := m))).allM (fun x => do p ( f x)) := by
rw [mapM_eq_toIter_mapM_toIterM, IterM.allM_mapM, mapM_eq_toIter_mapM_toIterM]
theorem Iter.allM_filterM {α β : Type w} {m : Type w Type w'}
[Iterator α Id β] [Finite α Id] [Monad m] [LawfulMonad m]
[Iterator α Id β] [Finite α Id] [Monad m] [MonadAttach m] [LawfulMonad m] [WeaklyLawfulMonadAttach m]
{it : Iter (α := α) β} {f : β m (ULift Bool)} {p : β m (ULift Bool)} :
(it.filterM f).allM p = (it.mapM (pure (f := m))).allM (fun x => do
if ( f x).down then
@@ -732,8 +984,9 @@ theorem Iter.allM_filter {α β : Type w} {m : Type → Type w'}
· simp
theorem Iter.all_filterMapM {α β β' : Type w} {m : Type w Type w'}
[Iterator α Id β] [Finite α Id] [Monad m] [IteratorLoop α Id m]
[LawfulMonad m] [LawfulIteratorLoop α Id m]
[Iterator α Id β] [Finite α Id] [IteratorLoop α Id m]
[Monad m] [MonadAttach m] [LawfulMonad m] [WeaklyLawfulMonadAttach m]
[LawfulIteratorLoop α Id m]
{it : Iter (α := α) β} {f : β m (Option β')} {p : β' Bool} :
(it.filterMapM f).all p = (it.mapM (pure (f := m))).allM (fun x => do
match f x with
@@ -742,15 +995,15 @@ theorem Iter.all_filterMapM {α β β' : Type w} {m : Type w → Type w'}
simp [IterM.all_eq_allM, allM_filterMapM]
theorem Iter.all_mapM {α β β' : Type w} {m : Type w Type w'}
[Iterator α Id β] [Finite α Id] [Monad m] [IteratorLoop α Id m]
[LawfulMonad m] [LawfulIteratorLoop α Id m]
{it : Iter (α := α) β} {f : β m β'} {p : β' Bool} :
[Iterator α Id β] [Finite α Id] [IteratorLoop α Id m]
[Monad m] [MonadAttach m] [LawfulMonad m] [WeaklyLawfulMonadAttach m]
[LawfulIteratorLoop α Id m] {it : Iter (α := α) β} {f : β m β'} {p : β' Bool} :
(it.mapM f).all p = (it.mapM pure).allM (fun x => (.up <| p ·) <$> (f x)) := by
simp [IterM.all_eq_allM, allM_mapM]
theorem Iter.all_filterM {α β : Type w} {m : Type w Type w'}
[Iterator α Id β] [Finite α Id] [Monad m] [IteratorLoop α Id m]
[LawfulMonad m] [LawfulIteratorLoop α Id m]
[Iterator α Id β] [Finite α Id] [IteratorLoop α Id m]
[Monad m] [MonadAttach m] [LawfulMonad m] [WeaklyLawfulMonadAttach m] [LawfulIteratorLoop α Id m]
{it : Iter (α := α) β} {f : β m (ULift Bool)} {p : β Bool} :
(it.filterM f).all p = (it.mapM (pure (f := m))).allM (fun x => do
if ( f x).down then
@@ -791,4 +1044,4 @@ theorem Iter.all_map {α β β' : Type w}
· simp [ihs _]
· simp
end Std.Iterators
end Std

View File

@@ -10,48 +10,59 @@ import Init.Data.Iterators.Lemmas.Combinators.FilterMap
public import Init.Data.Iterators.Combinators.FlatMap
import all Init.Data.Iterators.Combinators.FlatMap
public import Init.Data.Iterators.Lemmas.Combinators.Monadic.FlatMap
import Init.Control.Lawful.MonadAttach.Lemmas
namespace Std.Iterators
open Std.Internal
namespace Std
open Std.Internal Std.Iterators
namespace Iterators.Types
public theorem Flatten.IsPlausibleStep.outerYield_flatMapM_pure {α : Type w} {β : Type w} {α₂ : Type w}
{γ : Type w} {m : Type w Type w'} [Monad m] [LawfulMonad m] [Iterator α Id β] [Iterator α₂ m γ]
{γ : Type w} {m : Type w Type w'}
[Monad m] [MonadAttach m] [LawfulMonad m] [WeaklyLawfulMonadAttach m] [Iterator α Id β]
[Iterator α₂ m γ]
{f : β m (IterM (α := α₂) m γ)} {it₁ it₁' : Iter (α := α) β} {it₂' b}
(h : it₁.IsPlausibleStep (.yield it₁' b)) :
(h : it₁.IsPlausibleStep (.yield it₁' b)) (h' : MonadAttach.CanReturn (f b) it₂') :
(it₁.flatMapAfterM f none).IsPlausibleStep (.skip (it₁'.flatMapAfterM f (some it₂'))) := by
apply outerYield_flatMapM
exact .yieldSome h (out' := b) (by simp [PostconditionT.lift, PostconditionT.bind])
apply outerYield_flatMapM (b := b)
· exact FilterMap.PlausibleStep.yieldSome h (by simp)
· exact h'
public theorem Flatten.IsPlausibleStep.outerSkip_flatMapM_pure {α : Type w} {β : Type w} {α₂ : Type w}
{γ : Type w} {m : Type w Type w'} [Monad m] [LawfulMonad m] [Iterator α Id β] [Iterator α₂ m γ]
{γ : Type w} {m : Type w Type w'}
[Monad m] [MonadAttach m] [LawfulMonad m] [Iterator α Id β] [Iterator α₂ m γ]
{f : β m (IterM (α := α₂) m γ)} {it₁ it₁' : Iter (α := α) β}
(h : it₁.IsPlausibleStep (.skip it₁')) :
(it₁.flatMapAfterM f none).IsPlausibleStep (.skip (it₁'.flatMapAfterM f none)) :=
outerSkip_flatMapM (.skip h)
public theorem Flatten.IsPlausibleStep.outerDone_flatMapM_pure {α : Type w} {β : Type w} {α₂ : Type w}
{γ : Type w} {m : Type w Type w'} [Monad m] [LawfulMonad m] [Iterator α Id β] [Iterator α₂ m γ]
{γ : Type w} {m : Type w Type w'}
[Monad m] [MonadAttach m] [LawfulMonad m] [Iterator α Id β] [Iterator α₂ m γ]
{f : β m (IterM (α := α₂) m γ)} {it₁ : Iter (α := α) β}
(h : it₁.IsPlausibleStep .done) :
(it₁.flatMapAfterM f none).IsPlausibleStep .done :=
outerDone_flatMapM (.done h)
public theorem Flatten.IsPlausibleStep.innerYield_flatMapM_pure {α : Type w} {β : Type w} {α₂ : Type w}
{γ : Type w} {m : Type w Type w'} [Monad m] [LawfulMonad m] [Iterator α Id β] [Iterator α₂ m γ]
{γ : Type w} {m : Type w Type w'}
[Monad m] [MonadAttach m] [LawfulMonad m] [Iterator α Id β] [Iterator α₂ m γ]
{f : β m (IterM (α := α₂) m γ)} {it₁ : Iter (α := α) β} {it₂ it₂' b}
(h : it₂.IsPlausibleStep (.yield it₂' b)) :
(it₁.flatMapAfterM f (some it₂)).IsPlausibleStep (.yield (it₁.flatMapAfterM f (some it₂')) b) :=
innerYield_flatMapM h
public theorem Flatten.IsPlausibleStep.innerSkip_flatMapM_pure {α : Type w} {β : Type w} {α₂ : Type w}
{γ : Type w} {m : Type w Type w'} [Monad m] [LawfulMonad m] [Iterator α Id β] [Iterator α₂ m γ]
{γ : Type w} {m : Type w Type w'}
[Monad m] [MonadAttach m] [LawfulMonad m] [Iterator α Id β] [Iterator α₂ m γ]
{f : β m (IterM (α := α₂) m γ)} {it₁ : Iter (α := α) β} {it₂ it₂'}
(h : it₂.IsPlausibleStep (.skip it₂')) :
(it₁.flatMapAfterM f (some it₂)).IsPlausibleStep (.skip (it₁.flatMapAfterM f (some it₂'))) :=
innerSkip_flatMapM h
public theorem Flatten.IsPlausibleStep.innerDone_flatMapM_pure {α : Type w} {β : Type w} {α₂ : Type w}
{γ : Type w} {m : Type w Type w'} [Monad m] [LawfulMonad m] [Iterator α Id β] [Iterator α₂ m γ]
{γ : Type w} {m : Type w Type w'}
[Monad m] [MonadAttach m] [LawfulMonad m] [Iterator α Id β] [Iterator α₂ m γ]
{f : β m (IterM (α := α₂) m γ)} {it₁ : Iter (α := α) β} {it₂}
(h : it₂.IsPlausibleStep .done) :
(it₁.flatMapAfterM f (some it₂)).IsPlausibleStep (.skip (it₁.flatMapAfterM f none)) :=
@@ -99,15 +110,19 @@ public theorem Flatten.IsPlausibleStep.innerDone_flatMap_pure {α : Type w} {β
(it₁.flatMapAfter f (some it₂)).IsPlausibleStep (.skip (it₁.flatMapAfter f none)) :=
innerDone_flatMap h
end Iterators.Types
public theorem Iter.step_flatMapAfterM {α : Type w} {β : Type w} {α₂ : Type w}
{γ : Type w} {m : Type w Type w'} [Monad m] [LawfulMonad m] [Iterator α Id β] [Iterator α₂ m γ]
{γ : Type w} {m : Type w Type w'}
[Monad m] [MonadAttach m] [LawfulMonad m] [WeaklyLawfulMonadAttach m] [Iterator α Id β] [Iterator α₂ m γ]
{f : β m (IterM (α := α₂) m γ)} {it₁ : Iter (α := α) β} {it₂ : Option (IterM (α := α₂) m γ)} :
(it₁.flatMapAfterM f it₂).step = (do
match it₂ with
| none =>
match it₁.step with
| .yield it₁' b h =>
return .deflate (.skip (it₁'.flatMapAfterM f (some ( f b))) (.outerYield_flatMapM_pure h))
let fx MonadAttach.attach (f b)
return .deflate (.skip (it₁'.flatMapAfterM f (some fx.val)) (.outerYield_flatMapM_pure h fx.property))
| .skip it₁' h => return .deflate (.skip (it₁'.flatMapAfterM f none) (.outerSkip_flatMapM_pure h))
| .done h => return .deflate (.done (.outerDone_flatMapM_pure h))
| some it₂ =>
@@ -118,18 +133,22 @@ public theorem Iter.step_flatMapAfterM {α : Type w} {β : Type w} {α₂ : Type
return .deflate (.skip (it₁.flatMapAfterM f (some it₂')) (.innerSkip_flatMapM_pure h))
| .done h =>
return .deflate (.skip (it₁.flatMapAfterM f none) (.innerDone_flatMapM_pure h))) := by
simp only [flatMapAfterM, IterM.step_flatMapAfterM, Iter.step_mapM]
simp only [flatMapAfterM, IterM.step_flatMapAfterM, Iter.step_mapWithPostcondition,
PostconditionT.operation_pure]
split
· split <;> simp [*]
· rfl
public theorem Iter.step_flatMapM {α : Type w} {β : Type w} {α₂ : Type w}
{γ : Type w} {m : Type w Type w'} [Monad m] [LawfulMonad m] [Iterator α Id β] [Iterator α₂ m γ]
{γ : Type w} {m : Type w Type w'}
[Monad m] [MonadAttach m] [LawfulMonad m] [WeaklyLawfulMonadAttach m]
[Iterator α Id β] [Iterator α₂ m γ]
{f : β m (IterM (α := α₂) m γ)} {it₁ : Iter (α := α) β} :
(it₁.flatMapM f).step = (do
match it₁.step with
| .yield it₁' b h =>
return .deflate (.skip (it₁'.flatMapAfterM f (some ( f b))) (.outerYield_flatMapM_pure h))
let fx MonadAttach.attach (f b)
return .deflate (.skip (it₁'.flatMapAfterM f (some fx.val)) (.outerYield_flatMapM_pure h fx.property))
| .skip it₁' h => return .deflate (.skip (it₁'.flatMapAfterM f none) (.outerSkip_flatMapM_pure h))
| .done h => return .deflate (.done (.outerDone_flatMapM_pure h))) := by
simp [flatMapM, step_flatMapAfterM]
@@ -167,10 +186,9 @@ public theorem Iter.step_flatMap {α : Type w} {β : Type w} {α₂ : Type w}
| .done h => .done (.outerDone_flatMap_pure h)) := by
simp [flatMap, step_flatMapAfter]
public theorem Iter.toList_flatMapAfterM {α α₂ β γ : Type w} {m : Type w Type w'} [Monad m]
[LawfulMonad m] [Iterator α Id β] [Iterator α₂ m γ] [Finite α Id] [Finite α₂ m]
[IteratorCollect α Id m] [IteratorCollect α₂ m m]
[LawfulIteratorCollect α Id m] [LawfulIteratorCollect α₂ m m]
public theorem Iter.toList_flatMapAfterM {α α₂ β γ : Type w} {m : Type w Type w'}
[Monad m] [MonadAttach m] [LawfulMonad m] [WeaklyLawfulMonadAttach m]
[Iterator α Id β] [Iterator α₂ m γ] [Finite α Id] [Finite α₂ m]
{f : β m (IterM (α := α₂) m γ)}
{it₁ : Iter (α := α) β} {it₂ : Option (IterM (α := α₂) m γ)} :
(it₁.flatMapAfterM f it₂).toList = do
@@ -179,17 +197,11 @@ public theorem Iter.toList_flatMapAfterM {α α₂ β γ : Type w} {m : Type w
| some it₂ => return ( it₂.toList) ++
( List.flatten <$> (it₁.mapM fun b => do ( f b).toList).toList) := by
simp only [flatMapAfterM, IterM.toList_flatMapAfterM]
split
· simp only [mapM, IterM.toList_mapM_mapM, monadLift_self]
congr <;> simp
· apply bind_congr; intro step
simp only [mapM, IterM.toList_mapM_mapM, monadLift_self, bind_pure_comp, Functor.map_map]
congr <;> simp
split <;> simp [IterM.toList_mapM_eq_toList_mapWithPostcondition]
public theorem Iter.toArray_flatMapAfterM {α α₂ β γ : Type w} {m : Type w Type w'} [Monad m]
[LawfulMonad m] [Iterator α Id β] [Iterator α₂ m γ] [Finite α Id] [Finite α₂ m]
[IteratorCollect α Id m] [IteratorCollect α₂ m m]
[LawfulIteratorCollect α Id m] [LawfulIteratorCollect α₂ m m]
public theorem Iter.toArray_flatMapAfterM {α α₂ β γ : Type w} {m : Type w Type w'}
[Monad m] [MonadAttach m] [LawfulMonad m] [WeaklyLawfulMonadAttach m]
[Iterator α Id β] [Iterator α₂ m γ] [Finite α Id] [Finite α₂ m]
{f : β m (IterM (α := α₂) m γ)}
{it₁ : Iter (α := α) β} {it₂ : Option (IterM (α := α₂) m γ)} :
(it₁.flatMapAfterM f it₂).toArray = do
@@ -198,58 +210,47 @@ public theorem Iter.toArray_flatMapAfterM {α α₂ β γ : Type w} {m : Type w
| some it₂ => return ( it₂.toArray) ++
( Array.flatten <$> (it₁.mapM fun b => do ( f b).toArray).toArray) := by
simp only [flatMapAfterM, IterM.toArray_flatMapAfterM]
split
· simp only [mapM, IterM.toArray_mapM_mapM, monadLift_self]
congr <;> simp
· apply bind_congr; intro step
simp only [mapM, IterM.toArray_mapM_mapM, monadLift_self, bind_pure_comp, Functor.map_map]
congr <;> simp
split <;> simp [IterM.toArray_mapM_eq_toArray_mapWithPostcondition]
public theorem Iter.toList_flatMapM {α α₂ β γ : Type w} {m : Type w Type w'} [Monad m]
[LawfulMonad m] [Iterator α Id β] [Iterator α₂ m γ] [Finite α Id] [Finite α₂ m]
[IteratorCollect α Id m] [IteratorCollect α₂ m m]
[LawfulIteratorCollect α Id m] [LawfulIteratorCollect α₂ m m]
public theorem Iter.toList_flatMapM {α α₂ β γ : Type w} {m : Type w Type w'}
[Monad m] [MonadAttach m] [LawfulMonad m] [WeaklyLawfulMonadAttach m]
[Iterator α Id β] [Iterator α₂ m γ] [Finite α Id] [Finite α₂ m]
{f : β m (IterM (α := α₂) m γ)}
{it₁ : Iter (α := α) β} :
(it₁.flatMapM f).toList = List.flatten <$> (it₁.mapM fun b => do ( f b).toList).toList := by
simp [flatMapM, toList_flatMapAfterM]
public theorem Iter.toArray_flatMapM {α α₂ β γ : Type w} {m : Type w Type w'} [Monad m]
[LawfulMonad m] [Iterator α Id β] [Iterator α₂ m γ] [Finite α Id] [Finite α₂ m]
[IteratorCollect α Id m] [IteratorCollect α₂ m m]
[LawfulIteratorCollect α Id m] [LawfulIteratorCollect α₂ m m]
public theorem Iter.toArray_flatMapM {α α₂ β γ : Type w} {m : Type w Type w'}
[Monad m] [MonadAttach m] [LawfulMonad m] [WeaklyLawfulMonadAttach m]
[Iterator α Id β] [Iterator α₂ m γ] [Finite α Id] [Finite α₂ m]
{f : β m (IterM (α := α₂) m γ)}
{it₁ : Iter (α := α) β} :
(it₁.flatMapM f).toArray = Array.flatten <$> (it₁.mapM fun b => do ( f b).toArray).toArray := by
simp [flatMapM, toArray_flatMapAfterM]
public theorem Iter.toList_flatMapAfter {α α₂ β γ : Type w} [Iterator α Id β] [Iterator α₂ Id γ]
[Finite α Id] [Finite α₂ Id] [IteratorCollect α Id Id] [IteratorCollect α₂ Id Id]
[LawfulIteratorCollect α Id Id] [LawfulIteratorCollect α₂ Id Id]
[Finite α Id] [Finite α₂ Id]
{f : β Iter (α := α₂) γ} {it₁ : Iter (α := α) β} {it₂ : Option (Iter (α := α₂) γ)} :
(it₁.flatMapAfter f it₂).toList = match it₂ with
| none => (it₁.map fun b => (f b).toList).toList.flatten
| some it₂ => it₂.toList ++
(it₁.map fun b => (f b).toList).toList.flatten := by
simp only [flatMapAfter, Iter.toList, toIterM_toIter, IterM.toList_flatMapAfter]
cases it₂ <;> simp [map, IterM.toList_map_eq_toList_mapM]
cases it₂ <;> simp [map, IterM.toList_map_eq_toList_mapM, - IterM.toList_map]
public theorem Iter.toArray_flatMapAfter {α α₂ β γ : Type w} [Iterator α Id β] [Iterator α₂ Id γ]
[Finite α Id] [Finite α₂ Id] [IteratorCollect α Id Id] [IteratorCollect α₂ Id Id]
[LawfulIteratorCollect α Id Id] [LawfulIteratorCollect α₂ Id Id]
[Finite α Id] [Finite α₂ Id]
{f : β Iter (α := α₂) γ} {it₁ : Iter (α := α) β} {it₂ : Option (Iter (α := α₂) γ)} :
(it₁.flatMapAfter f it₂).toArray = match it₂ with
| none => (it₁.map fun b => (f b).toArray).toArray.flatten
| some it₂ => it₂.toArray ++
(it₁.map fun b => (f b).toArray).toArray.flatten := by
simp only [flatMapAfter, Iter.toArray, toIterM_toIter, IterM.toArray_flatMapAfter]
cases it₂ <;> simp [map, IterM.toArray_map_eq_toArray_mapM]
cases it₂ <;> simp [map, IterM.toArray_map_eq_toArray_mapM, - IterM.toArray_map]
public theorem Iter.toList_flatMap {α α₂ β γ : Type w} [Iterator α Id β] [Iterator α₂ Id γ]
[Finite α Id] [Finite α₂ Id]
[Iterator α Id β] [Iterator α₂ Id γ] [Finite α Id] [Finite α₂ Id]
[IteratorCollect α Id Id] [IteratorCollect α₂ Id Id]
[LawfulIteratorCollect α Id Id] [LawfulIteratorCollect α₂ Id Id]
{f : β Iter (α := α₂) γ} {it₁ : Iter (α := α) β} :
(it₁.flatMap f).toList = (it₁.map fun b => (f b).toList).toList.flatten := by
simp [flatMap, toList_flatMapAfter]
@@ -257,10 +258,8 @@ public theorem Iter.toList_flatMap {α α₂ β γ : Type w} [Iterator α Id β]
public theorem Iter.toArray_flatMap {α α₂ β γ : Type w} [Iterator α Id β] [Iterator α₂ Id γ]
[Finite α Id] [Finite α₂ Id]
[Iterator α Id β] [Iterator α₂ Id γ] [Finite α Id] [Finite α₂ Id]
[IteratorCollect α Id Id] [IteratorCollect α₂ Id Id]
[LawfulIteratorCollect α Id Id] [LawfulIteratorCollect α₂ Id Id]
{f : β Iter (α := α₂) γ} {it₁ : Iter (α := α) β} :
(it₁.flatMap f).toArray = (it₁.map fun b => (f b).toArray).toArray.flatten := by
simp [flatMap, toArray_flatMapAfter]
end Std.Iterators
end Std

View File

@@ -13,20 +13,20 @@ public import Init.Data.Iterators.Lemmas.Consumers.Monadic.Loop
public section
namespace Std.Iterators
namespace Std
open Std.Iterators Std.Iterators.Types
variable {α : Type w} {m : Type w Type w'} {β : Type w} {P : β Prop}
theorem IterM.step_attachWith [Iterator α m β] [Monad m] {it : IterM (α := α) m β} {hP} :
(it.attachWith P hP).step =
(fun s => .deflate Types.Attach.Monadic.modifyStep (it.attachWith P hP) s.inflate, s.inflate, rfl) <$> it.step :=
(fun s => .deflate Attach.Monadic.modifyStep (it.attachWith P hP) s.inflate, s.inflate, rfl) <$> it.step :=
rfl
@[simp]
theorem IterM.map_unattach_toList_attachWith [Iterator α m β] [Monad m]
{it : IterM (α := α) m β} {hP}
[Finite α m] [IteratorCollect α m m]
[LawfulMonad m] [LawfulIteratorCollect α m m] :
[Finite α m] [LawfulMonad m] :
List.unattach <$> (it.attachWith P hP).toList = it.toList := by
induction it using IterM.inductSteps with | step it ihy ihs
rw [IterM.toList_eq_match_step, IterM.toList_eq_match_step, step_attachWith]
@@ -45,8 +45,7 @@ theorem IterM.map_unattach_toList_attachWith [Iterator α m β] [Monad m]
@[simp]
theorem IterM.map_unattach_toListRev_attachWith [Iterator α m β] [Monad m] [Monad n]
{it : IterM (α := α) m β} {hP}
[Finite α m] [IteratorCollect α m m]
[LawfulMonad m] [LawfulIteratorCollect α m m] :
[Finite α m] [LawfulMonad m] :
List.unattach <$> (it.attachWith P hP).toListRev = it.toListRev := by
rw [toListRev_eq, toListRev_eq, map_unattach_toList_attachWith (it := it) (hP := hP)]
simp [-map_unattach_toList_attachWith]
@@ -54,8 +53,8 @@ theorem IterM.map_unattach_toListRev_attachWith [Iterator α m β] [Monad m] [Mo
@[simp]
theorem IterM.map_unattach_toArray_attachWith [Iterator α m β] [Monad m] [Monad n]
{it : IterM (α := α) m β} {hP}
[Finite α m] [IteratorCollect α m m]
[LawfulMonad m] [LawfulIteratorCollect α m m] :
[Finite α m]
[LawfulMonad m] :
(·.map Subtype.val) <$> (it.attachWith P hP).toArray = it.toArray := by
rw [ toArray_toList, toArray_toList, map_unattach_toList_attachWith (it := it) (hP := hP)]
simp [-map_unattach_toList_attachWith, -IterM.toArray_toList]
@@ -65,9 +64,8 @@ theorem IterM.count_attachWith [Iterator α m β] [Monad m] [Monad n]
{it : IterM (α := α) m β} {hP}
[Finite α m] [IteratorLoop α m m] [LawfulMonad m] [LawfulIteratorLoop α m m] :
(it.attachWith P hP).count = it.count := by
letI : IteratorCollect α m m := .defaultImplementation
rw [ up_length_toList_eq_count, up_length_toList_eq_count,
map_unattach_toList_attachWith (it := it) (P := P) (hP := hP)]
simp only [Functor.map_map, List.length_unattach]
end Std.Iterators
end Std

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