Compare commits

..

121 Commits

Author SHA1 Message Date
Kim Morrison
f5b6070594 update scripts 2024-06-01 03:12:29 +10:00
Kim Morrison
bd00c5bca3 Merge remote-tracking branch 'origin/master' into github_metrics 2024-05-24 23:11:36 +10:00
Sebastian Ullrich
d3ee0be908 feat: show signature elaboration errors on body parse error (#4267)
Fixes #3556

---------

Co-authored-by: Joachim Breitner <mail@joachim-breitner.de>
2024-05-24 10:07:07 +00:00
Sebastian Ullrich
d1a96f6d8f chore: CI: native-compile aarch64 macOS (#4265) 2024-05-24 08:18:49 +00:00
Kim Morrison
b0c1112471 chore: better omega error message if no facts found (#4264) 2024-05-24 05:15:15 +00:00
JovanGerb
e5e5a4d2e0 chore: fix the MonadStore type classes, with semiOutParam (#4205)
The type class `MonadStore1` and friends have an outParam, which should
not be an outParam, because there are multiple possible values for this
parameter. At this function
[fetchOrCreate](1382e9fbc4/src/lake/Lake/Load/Main.lean (L196C49-L196C63)),
there are multiple stacked `StateT` monad transformers that each give a
different instance to `MonadStore1`. It is an implementation detail of
type class synthesis which instance is found. This particular type class
synthesis fails when the unused instance
`Lake.instMonadStore1OfMonadDStoreOfFamilyOut` is set to a lower
priority, because then the synthesis order happens to go differently, so
the wrong instance is found.

Replacing the outParam with a semiOutParam solves this issue. Thus, we
make a new type class `MonadStore1Of`, which is the same, but with a
semiOutParam. This follows the design of `MonadState` and
`MonadStateOf`.

However, then it turns out that the instance cannot anymore be
synthesised.

There are two instances for `MonadStore1`:
```
instance [MonadDStore κ β m] : MonadStore1 k (β k) m
instance [MonadDStore κ β m] [FamilyOut β k α] : MonadStore1 k α m 
```
The first one is problematic during unification, especially when `β`
should be instantiated as a constant function. We make the second one
sufficient by adding an instance for the general type family:
```
/-- The general type family -/
instance (priority := low) : FamilyDef Fam a (Fam a) where
  family_key_eq_type := rfl
```
So then we can get rid of the first instance.
2024-05-24 02:09:07 +00:00
Kim Morrison
e5d6872065 average ages of *all* issues 2024-05-24 11:11:05 +10:00
Kim Morrison
45e05788ed report average age 2024-05-24 11:04:02 +10:00
Kim Morrison
e22e2d5051 chore: script to collect github metrics 2024-05-24 10:51:01 +10:00
Sebastian Ullrich
e020f3d159 chore: CI: move some expensive checks from merge queue to releases (#4255) 2024-05-23 20:45:44 +00:00
Sebastian Ullrich
811bad16e1 fix: ensure incremental commands and tactics are reached only on supported paths (#4259)
Without this, it would not easy but perhaps be feasible to break
incrementality when editing command prefixes such as `set_option ... in
theorem` or also `theorem namesp.name ...` (which is a macro),
especially if at some later point we support incrementality in input
shifted by an edit. Explicit, sound support for these common cases will
be brought back soon.
2024-05-23 17:57:42 +00:00
Sebastian Ullrich
67338bac23 chore: replace registerBuiltinIncrementalTactic with @[builtin_incremental] 2024-05-23 17:53:58 +02:00
Sebastian Ullrich
ba629545cc chore: update stage0 2024-05-23 17:26:21 +02:00
Sebastian Ullrich
dfb496a271 fix: allow multiple declareBuiltin per declaration 2024-05-23 17:23:39 +02:00
Sebastian Ullrich
250994166c feat: [(builtin_)incremental] elab attribute 2024-05-23 17:23:39 +02:00
Sebastian Ullrich
73a0c73c7c chore: modernize build instructions (#4032)
Use `cmake --preset`, adjust and document parallelism settings
2024-05-23 10:55:07 +00:00
meow-sister
258cc28dfc fix: docstring in Attributes.lean (#4238)
Changing document string in `Attributes.lean`, in order to consistent
with code in `Lean.Parser.Attr`.

---------

Co-authored-by: Kim Morrison <kim@tqft.net>
2024-05-23 10:13:43 +00:00
Sebastian Ullrich
f61a64d2ff chore: reset LEAN_VERSION_IS_RELEASE 2024-05-23 12:28:24 +02:00
Kyle Miller
d984030c6a chore: set up procedure for preparing release notes (#4247)
We are switching to a new system for preparing release notes.
* Release notes will be compiled when creating a release candidate from
all the commits that are part of that release.
* PRs can include suggestions for release notes in PR messages. Please
use language such as "release notes" and "breaking changes" to call
attention to the suggestions. Release notes are user-centric rather than
developer-centric.
* For more complicated release notes, these can be put into the
`releases_drafts` folder.

This solves an issue where PRs that include release notes can, when
merged, have those notes appear under the wrong Lean version, since they
might have been created before a release but not merged until after. It
also solves merge conflicts due to multiple PRs updating the release
notes.
2024-05-23 02:46:11 +00:00
FR
93758cc222 perf: faster Nat.testBit (#4188)
`1 &&& n` is faster than `n &&& 1` for big `n`.

---
2024-05-23 01:34:40 +00:00
Alex Keizer
4fa3b3c4a0 feat: bitblasting theorems for signed comparisons (#4201)
Prove theorems that relate `BitVec.slt` and `sle` to `carry`, so that
these signed comparisons may be bitblasted in LeanSAT.

This PR is stacked on top of #4200. For the diff without changes from
that PR, see:
https://github.com/opencompl/lean4/compare/opencompl:lean4:bitvec-toInt-iff-msb...bitvec-slt-blast

---------

Co-authored-by: Kim Morrison <scott@tqft.net>
2024-05-23 01:24:04 +00:00
Leonardo de Moura
2bc41d8f3a fix: case tactic in macros (#4252)
We must erase macro scopes for tags in `case` as we do in `cases .. with
..` and `induction .. with ..`.
2024-05-23 00:01:24 +00:00
Sebastian Ullrich
f97a7d4234 feat: incremental elaboration of definition headers, bodies, and tactics (#3940)
Extends Lean's incremental reporting and reuse between commands into
various steps inside declarations:
* headers and bodies of each (mutual) definition/theorem
* `theorem ... := by` for each contained tactic step, including
recursively inside supported combinators currently consisting of
  * `·` (cdot), `case`, `next`
  * `induction`, `cases`
  * macros such as `next` unfolding to the above

![Recording 2024-05-10 at 11 07
32](https://github.com/leanprover/lean4/assets/109126/c9d67b6f-c131-4bc3-a0de-7d63eaf1bfc9)

*Incremental reuse* means not recomputing any such steps if they are not
affected by a document change. *Incremental reporting* includes the
parts seen in the recording above: the progress bar and messages. Other
language server features such as hover etc. are *not yet* supported
incrementally, i.e. they are shown only when the declaration has been
fully processed as before.

---------

Co-authored-by: Scott Morrison <scott.morrison@gmail.com>
2024-05-22 13:23:30 +00:00
Alex Keizer
23a202b6be feat: characterize BitVec.toInt in terms of BitVec.msb (#4200)
This PR extracts `msb_eq_false_iff_two_mul_lt` and
`msb_eq_true_iff_two_mul_ge` from #4179, and uses them to prove a
theorem that characterizes `BitVec.toInt` in terms of `BitVec.msb`. This
lemma will be useful to prove a bit-blasting theorem for `BitVec.slt`
and `BitVec.sle`.

Also cleans up an existing proof (`toInt_eq_toNat_cond `), which turns
out to be provable by `rfl`.

---------

Co-authored-by: Kim Morrison <scott@tqft.net>
2024-05-22 11:14:37 +00:00
Leonardo de Moura
ff37e5d512 feat: add grind core module (#4249) 2024-05-22 03:50:36 +00:00
Kim Morrison
c2b8a1e618 chore: fix implicitness in HashMap.ofList (#4248)
Reported at
https://leanprover.zulipchat.com/#narrow/stream/270676-lean4/topic/Typeclass.20Inference.20Failure.20on.20HashMap/near/439842209
2024-05-22 03:04:37 +00:00
Mac Malone
8ca00918fb chore: lake: fix noRelease test when lean repo is tagged (#4244)
Fixes a bug with the Lake `noRelease` test where the test would fail if
the current revision of the Lean repository itself had a tag.
2024-05-21 22:51:33 +00:00
Leonardo de Moura
6155513c60 chore: update stage0
Motivation: `[grind_cases]` at `Init`
2024-05-21 21:46:23 +02:00
Leonardo de Moura
d6709eb157 feat: add [grind_cases] attribute 2024-05-21 21:46:23 +02:00
Leonardo de Moura
e6be8b90f5 feat: add grind.injection (#4243) 2024-05-21 17:57:02 +00:00
Joachim Breitner
82401938cf fix: hovers on binders with metavariables (#4192)
this fixes #4078. It is an alternative fix to the one in #4137,
suggested
by @kmill.

Incidentially, it makes the unused variable linter better. My theory is
that
if we don’t reset the info when backtracking, the binder shows up more
than
once in the info tree, and then it is considered “used”, although there
are
just multiple binders.
2024-05-21 10:34:58 +00:00
Kim Morrison
3de60bb1f6 chore: Lake details & improve formatting in changelog (#4239)
This is #4207, but targeting `master`.

Co-authored-by: Mac Malone <tydeu@hatpress.net>
2024-05-21 06:11:32 +00:00
Leonardo de Moura
8c03650359 feat: some Char, UInt, and Fin theorems (#4231)
for SSFT24 summer school: https://github.com/david-christiansen/ssft24

---------

Co-authored-by: Kim Morrison <kim@tqft.net>
Co-authored-by: Kim Morrison <scott.morrison@gmail.com>
Co-authored-by: David Thrane Christiansen <david@davidchristiansen.dk>
2024-05-21 06:11:23 +00:00
Austin Letson
2faa81d41f doc: add docstrings and examples for String functions (#4166)
Add docstrings, usage examples, and doc tests for `String.prev`,
`.front`, `.back`, `.atEnd`.

Improve docstring examples for `String.next` based on discussion
examples for `String.prev`.

---------

Co-authored-by: Kim Morrison <kim@tqft.net>
2024-05-21 04:27:40 +00:00
Siddharth
097a4d5b6b feat: define rotateLeft/Right with modulo (#4229)
This ensures that rotateLeft/Right behave correctly even when the
rotation amount is larger than the bitwidth.

This shall be followed up with `getLsb` theorems for rotations for
LeanSAT.

We choose to write `aux` definitions since it is cleaner to reason about
the `aux` theorems with the assumption that `rotation-amount <
bit-width`, followed by auxiliary lemmas that link the behavior of
rotation to the canonical case when `rotation-amount < bit-width`.

Proof strategy we will execute based on these definitions: [Link to
proof of
`getLsb_rotateLeft`](a0b18ec0f4/src/Init/Data/BitVec/Lemmas.lean (L1129-L1204))

---------

Co-authored-by: Tobias Grosser <github@grosser.es>
2024-05-21 03:49:09 +00:00
Mac Malone
f512826b9a chore: lake: spinner icon for computing step (#4236) 2024-05-21 03:48:39 +00:00
Leonardo de Moura
7c053259d3 feat: add grind.cases tactic (#4235) 2024-05-21 02:03:33 +00:00
Leonardo de Moura
f3ccd6b023 feat: some string simprocs (#4233)
For the SSFT24 summer school.
2024-05-20 22:53:10 +00:00
Mac Malone
7ece5d56e3 feat: lake: ANSI monitor: only display progress + running job info (#4232)
The ANSI mode build monitor now now longer displays built jobs (instead
only those that print info or failed). Also upgrades the progress ticker
with a spinner icon and information on the number of running jobs.
2024-05-20 20:29:21 +00:00
Kyle Miller
a7338c5ad8 feat: make frontend normalize line endings to LF (#3903)
To eliminate parsing differences between Windows and other platforms,
the frontend now normalizes all CRLF line endings to LF, like [in
Rust](https://github.com/rust-lang/rust/issues/62865).

Effects:
- This makes Lake hashes be faithful to what Lean sees (Lake already
normalizes line endings before computing hashes).
- Docstrings now have normalized line endings. In particular, this fixes
`#guard_msgs` failing multiline tests for Windows users using CRLF.
- Now strings don't have different lengths depending on the platform.
Before this PR, the following theorem is true for LF and false for CRLF
files.
```lean
example : "
".length = 1 := rfl
```

Note: the normalization will take `\r\r\n` and turn it into `\r\n`. In
the elaborator, we reject loose `\r`'s that appear in whitespace. Rust
instead takes the approach of making the normalization routine fail.
They do this so that there's no downstream confusion about any `\r\n`
that appears.

Implementation note: the LSP maintains its own copy of a source file
that it updates when edit operations are applied. We are assuming that
edit operations never split or join CRLFs. If this assumption is not
correct, then the LSP copy of a source file can become slightly out of
sync. If this is an issue, there is some discussion
[here](https://github.com/leanprover/lean4/pull/3903#discussion_r1592930085).
2024-05-20 17:13:08 +00:00
Leonardo de Moura
b278f9dd30 fix: missing withIncRecDepth and unifyEqs? and add support for offsets at unifyEq? (#4224)
Given `h` with type `x + k = y + k'` (or `h : k = k')`, `cases h`
produced a proof of size linear in `min k k'`. `isDefEq` has support for
offset, but `unifyEq?` did not have it, and a stack overflow occurred
while processing the resulting proof. This PR fixes this issue.

closes #4219
2024-05-20 13:42:36 +00:00
Eric Wieser
f0471a519b doc: upstream the Lean4 pygments lexer (#3125)
An improved `lean4` lexer is now part of pygments.

This depends on https://github.com/pygments/pygments/pull/2618 (now
merged), and [a subsequent
release](https://github.com/pygments/pygments/milestone/23)
2024-05-20 11:40:24 +00:00
Alex Keizer
42215cc072 feat: Nat.shiftLeft_shiftRight (#4199)
Show that shifting a natural number left and then shifting right by the
same amount is a no-op.

I originally proved this in a different PR, ended up not needing the
fact after all, but it still seemed like a generally useful simp lemma
to have.
2024-05-20 06:50:28 +00:00
Leonardo de Moura
f53b778c0d feat: improve grind preprocessor (#4221) 2024-05-20 04:29:49 +00:00
Leonardo de Moura
72b345c621 chore: remove #guard_msgs from tests that rely on pointer equality 2024-05-20 06:12:43 +02:00
Mac Malone
6171070deb chore: lake: cloud release build output fixes & related touchups (#4220)
Fixes two output bugs with cloud releases: (1) the fetch as part of an
`extraDep` was not properly isolated in a job, and (2) the release job
would be shown even if the release had already been successfully
fetched.

Also includes some related touchups, including the addition of show all
jobs on `-v` which helps with debugging job counts.
2024-05-20 03:28:50 +00:00
Kim Morrison
7c5249278e chore: move release notes about MessageData to v4.9.0 (#4222) 2024-05-20 01:42:30 +00:00
Leonardo de Moura
239ade80dc chore: update stage0 2024-05-19 07:20:10 +02:00
Leonardo de Moura
47c8e340d6 fix: move cdot and calc parsers to Lean namespace
closes #3168
2024-05-19 07:20:10 +02:00
Leonardo de Moura
c8b72beb4d chore: update stage0 2024-05-19 07:20:10 +02:00
Leonardo de Moura
9803c5dd63 chore: prepare to move cdot and calc parsers to Lean namespace
see issue #3618
2024-05-19 07:20:10 +02:00
Leonardo de Moura
d66d00dece fix: missing occurs-check at delayed assignment (#4217)
closes #4144
2024-05-19 02:53:00 +00:00
JovanGerb
9fde33a09f fix: oversight in isReadOnlyOrSyntheticOpaque (#4206)
### Explanation
In the case that `assignSyntheticOpaque := true` and the given
metavariable is `syntheticOpaque` and the depth of the metavariable is
not the current depth, `isReadOnlyOrSyntheticOpaque` returns false, even
though the metavariable is read-only because of being declared at a
smaller depth. This causes the metavariable to (wrongly) be able to be
instantiated by `isDefEq`.

This bug was found at the proof of
[RingHom.PropertyIsLocal.sourceAffineLocally_of_source_openCover](https://leanprover-community.github.io/mathlib4_docs/Mathlib/AlgebraicGeometry/Morphisms/RingHomProperties.html#RingHom.PropertyIsLocal.sourceAffineLocally_of_source_openCover),
which involves a type class synthesis for `CommRing ?m.2404`, and the
synthesis manages to instantiate this metavariable into different
values, even though `synthInstance?` increases the metavariable depth.
This synthesis fails after 1 second.

I found the bug while modifying the instance synthesis code: the
modified code spent several minutes on this failed synthesis.

### Test
The problem can be verified with the test:
```
run_meta do
  let m ← mkFreshExprMVar (Expr.sort levelOne) MetavarKind.syntheticOpaque
  withAssignableSyntheticOpaque do
  withNewMCtxDepth do
  let eq ← isDefEq m (.const ``Nat [])
  Lean.logInfo m! "{eq}"
```
this unification used to succeed, giving `true`, and this fix makes it
return `false`.

### Impact on Mathlib

This fix causes a change in the behaviour of `congr`, `convert` and
friends, which breaks a couple of proofs in mathlib. Most of these are
fixed by supplying more arguments.

I fixed these proofs, and
[benched](http://speed.lean-fro.org/mathlib4/compare/b821bfd9-3769-4930-b77f-0adc6f9d218f/to/e7b27246-a3e6-496a-b552-ff4b45c7236e?hash2=4f3c460cc1668820c9af8418a87a23db44c7acab)
mathlib. The result is that most files are unaffected, but some files
are significantly improved. This is most prominent in
Mathlib.RingTheory.Jacobson, where the number of instructions has
decreased by 28%. The overall improvement is a 0.3% reduction in
instructions.

[Zulip
message](https://leanprover.zulipchat.com/#narrow/stream/287929-mathlib4/topic/Ways.20to.20speed.20up.20Mathlib/near/439218960)
2024-05-18 21:01:31 +00:00
Kyle Miller
b639d102d1 fix: use maxType when building expression in expression tree elaborator (#4215)
The expression tree elaborator computes a "maxType" that every leaf term
can be coerced to, but the elaborator was not ensuring that the entire
expression tree would have maxType as its type. This led to unexpected
errors in examples such as
```lean
example (a : Nat) (b : Int) :
  a = id (a * b^2) := sorry
```
where it would say it could not synthesize an `HMul Int Int Nat`
instance (the `Nat` would propagate from the `a` on the LHS of the
equality). The issue in this case is that `HPow` uses default instances,
so while the expression tree elaborator decides that `a * b^2` should be
referring to an `Int`, the actual elaborated type is temporarily a
metavariable. Then, when the binrel elaborator is looking at both sides
of the equality, it decides that `Nat` will work and coercions don't
need to be inserted.

The fix is to unify the type of the resulting elaborated expression with
the computed maxType. One wrinkle is that `hasUncomparable` being false
is a valid test only if there are no leaf terms with unknown types (if
they become known, it could change `hasUncomparable` to true), so this
unification is only performed if the leaf terms all have known types.

Fixes issue described by Floris van Doorn on
[Zulip](https://leanprover.zulipchat.com/#narrow/stream/287929-mathlib4/topic/elaboration.20issue.20involving.20powers.20and.20sums/near/439243587).
2024-05-18 20:59:54 +00:00
Leonardo de Moura
02b6fb3f41 fix: canonInstances := true issue (#4216)
closes #4213
2024-05-18 18:13:41 +00:00
Joachim Breitner
9f6bbfa106 feat: apply’s error message should show implicit arguments as needed (#3929)
luckily the necessary functionality already exists in the form of
`addPPExplicitToExposeDiff`. But it is not cheap, and we should not run
this code
when the error message isn’t shown, so we should do this lazily.

We already had `MessageData.ofPPFormat` to assemble the error message
lazily, but it
was restricted to returning `FormatWithInfo`, a data type that doesn’t
admit a nice
API to compose more complex messages (like `Format` or `MessageData`
has; an attempt to
fix that is in #3926).

Therefore we split the functionality of `.ofPPFormat` into
`.ofFormatWithInfo` and `.ofLazy`,
and use `.ofLazy` to compute the more complex error message of `apply`.

Fixes #3232.

---------

Co-authored-by: David Thrane Christiansen <david@davidchristiansen.dk>
Co-authored-by: Wojciech Nawrocki <wjnawrocki@protonmail.com>
2024-05-18 06:25:43 +00:00
Leonardo de Moura
1ff0e7a2f2 fix: split at h when h has forward dependencies (#4211)
We use an approach similar to the one used in `simp`. 

closes #3731
2024-05-18 02:48:15 +00:00
Leonardo de Moura
3cb6eb0ae6 fix: ensure a local instance is not registered multiple times (#4210)
closes #4203
2024-05-18 02:30:12 +00:00
Alok Singh
489d2d11ec feat: lake: add readme to package templates (#4147)
Messaged @tydeu about adding a README.md to new lake projects. I decided
to add it with the help of GPT.

---------

Co-authored-by: Mac Malone <tydeu@hatpress.net>
2024-05-18 02:02:36 +00:00
Mac Malone
7648bf255c feat: lake: build monitor improvements (#4127)
The new Lake build monitor is now more selective, accurate, and prettier
in what it prints.

**Key Changes:**
 
* Poll jobs at a fixed frequency (100ms), updating the caption and
finished job count.
* Add `action` field to jobs to record information about what jobs do.
It enables distinguishing between jobs which build something, fetch from
a store, or reload logs from the cache.
* At standard verbosity, print build captions only when a job is know to
build or fetch something (i.e., `action >= .fetch`).
* Add an icon and color to job captions based on their log-level / build
status. Also add color to levels in logs.
* Add `--ansi`/`--no-ansi` to toggle Lake's use of ANSI escape codes.
* Fix some `v4.8.0-rc1` bugs and `--old`.

Closes #2822.
2024-05-17 23:20:45 +00:00
Leonardo de Moura
4d2ff6fb04 feat: pretty print Array DiscrTree.Key (#4208) 2024-05-17 22:35:24 +00:00
Leonardo de Moura
ee0bcc8321 feat: add Simp.Config.index (#4202)
The `simp` tactic uses a discrimination tree to select candidate
theorems that will be used to rewrite an expression. This indexing data
structure minimizes the number of theorems that need to be tried and
improves performance. However, indexing modulo reducibility is
challenging, and a theorem that could be applied, when taking reduction
into account, may be missed. For example, suppose we have a `simp`
theorem `foo : forall x y, f x (x, y).2 = y`, and we are trying to
simplify the expression `f a b <= b`. `foo` will not be tried by `simp`
because the second argument of `f a b` is not a projection of a pair.
However, `f a b` is definitionally equal to `f a (a, b).2` since we can
reduce `(a, b).2`.

In Lean 3, we had a much simpler indexing data structure where only the
head symbol was taken into account. For the theorem `foo`, the head
symbol is `f`. Thus, the theorem would be considered by `simp`.

This commit adds the option `Simp.Config.index`. When `simp (config := {
index := false })`, only the head symbol is considered when retrieving
theorems, as in Lean 3. Moreover, if `set_option diagnostics true`,
`simp` will check whether every applied theorem would also have been
applied if `index := true`, and report them. This feature can help users
diagnose tricky issues in code that has been ported from libraries
developed using Lean 3 and then ported to Lean 4. In the following
example, it will report that `foo` is a problematic theorem.

```lean
opaque f : Nat → Nat → Nat

@[simp] theorem foo : f x (x, y).2 = y := by sorry

example : f a b ≤ b := by
  set_option diagnostics true in
  simp (config := { index := false })
```

In the example above, the following diagnostic message is produced.
```lean
[simp] theorems with bad keys
    foo, key: [f, *, Prod.1, Prod.mk, Nat, Nat, *, *]
```

With the information above, users can annotate theorems such as `foo`
using `no_index` for problematic subterms.
Example:
```lean
opaque f : Nat → Nat → Nat

@[simp] theorem foo : f x (no_index (x, y).2) = y := by sorry

example : f a b ≤ b := by
  simp -- `foo` is still applied
```

cc @semorrison 
cc @PatrickMassot
2024-05-17 21:14:58 +00:00
Leonardo de Moura
1382e9fbc4 feat: simprocs for applying shiftLeft_shiftLeft and shiftRight_shiftRight (#4194) 2024-05-16 19:34:46 +00:00
Leonardo de Moura
e8c4540f87 feat: simprocs for reducing x >>> i and x <<< i where i is a bittvector literal (#4193) 2024-05-16 18:16:52 +00:00
FR
f2a304e555 style: fix whitespace and remove duplicate docstring (#4189) 2024-05-16 06:46:39 +00:00
Kim Morrison
3a457e6ad6 chore: use #guard_msgs in run tests (#4175)
Many of our tests in `tests/lean/run/` produce output from `#eval` (or
`#check`) statements, that is then ignored.

This PR tries to capture all the useful output using `#guard_msgs`. I've
only done a cursory check that the output is still sane --- there is a
chance that some "unchecked" tests have already accumulated regressions
and this just cements them!

In the other direction, I did identify two rotten tests:
* a minor one in `setStructInstNotation.lean`, where a comment says `Set
Nat`, but `#check` actually prints `?_`. Weird?
* `CompilerProbe.lean` is generating empty output, apparently indicating
that something is broken, but I don't know the signficance of this file.

In any case, I'll ask about these elsewhere.

(This started by noticing that a recent `grind` test file had an
untested `trace_state`, and then got carried away.)
2024-05-16 00:38:31 +00:00
Alex Keizer
2a966b46f2 feat: bitblasting theorems about unsigned bitvector inequalities (#4178)
This PR adds theorems that relate unsigned bitvector comparisons
`BitVec.ult` and `BitVec.ule` to `BitVec.carry`. These lemmas are a
prerequisite to bit-blasting these comparisons in LeanSAT.
2024-05-16 00:01:31 +00:00
Leonardo de Moura
8204b79b3c fix: cleanup type annotations in congruence theorems (#4185) 2024-05-15 23:50:35 +00:00
Kim Morrison
f63616891f chore: fix bug in omega (#4184)
Fixes #4183
2024-05-15 22:21:17 +00:00
Leonardo de Moura
9a8e7a6411 feat: add cleanupAnnotations parameter to forallTelescope methods (#4180) 2024-05-15 22:19:07 +00:00
JovanGerb
c7741607fb fix: add missing registerTraceClass `Meta.synthInstance.instances (#4151)
The trace class Meta.synthInstance.answer isn't registered, so it can't
be used.

I set `inherited := true`, because I think it is a useful trace to have.
In particular it tells you when an instance has been found that has a
too large size. This is very useful information.
2024-05-15 18:59:01 +00:00
Joachim Breitner
82666e5e7c fix: make erased names in simp clickable (#4176)
as usually, just a matter of using the `WithInfo` variant.

Also simplifying the code a bit, it seems we can use
`realizeGlobalConstNoOverloadWithInfo` here.

(It's somehwatdubious API design that of all the functions in
the `{resolve/realise}GlobalConst{NoOverload,}{WithInfo,}` family
the one with the longest name is the one that should be used
unless one has a reason to use another one.)

Fixes: #4174
2024-05-15 14:12:15 +00:00
Joachim Breitner
aeea7fdf5d fix: List.length_pos no @[simp] (#4172)
in #4158 I was experimenting with a change to the simplifier that
affectes the order in which lemmas were tried, and of course it breaks
proofs all over the place whenever we have a non-confluent simp set.

Among the first breakages encountered, a large fraction was due to
`simp` rewriting with `List.length_pos  : 0 < length l ↔ l ≠ []`.

This does not strike me a as a good simp lemma: If `l` is a manifest
constructor, the simplifier will reduce `length` and solve it anyways,
and if it isn't then an inequality usually isn’t very simp friendly. It
is also highly non-confluent with any kind of `length`-lemma we might
have.

This therefore removes it from the standard simp set.
2024-05-15 13:28:54 +00:00
Leonardo de Moura
3035d2f8f6 feat: grind preprocessor skeleton (#4170) 2024-05-15 04:25:22 +00:00
Leonardo de Moura
3493d066e4 feat: add MVarId.ensureNoMVar (#4169) 2024-05-15 00:37:28 +00:00
Leonardo de Moura
d0e34aaed5 feat: add revertAll tactic for grind (#4167) 2024-05-14 23:22:54 +00:00
Siddharth
367b97885a chore: delete double namespace BitVec.BitVec (#4165)
Fixes double namespace introduced in #4148
2024-05-14 19:34:14 +00:00
Leonardo de Moura
f3538dbdfa feat: grind normalization theorems (#4164) 2024-05-14 19:19:38 +00:00
Leonardo de Moura
770235855f chore: update stage0 2024-05-14 19:52:25 +02:00
Leonardo de Moura
0a515e2ec9 feat: add grind_norm simp attribute for grind tactic 2024-05-14 19:52:25 +02:00
Kim Morrison
91244b2dd9 chore: add dates to @[deprecated] attributes (#3967) 2024-05-14 03:24:57 +00:00
Leonardo de Moura
de5e039c83 fix: type class issues with maxSynthPendingDepth := 1 (#4119)
Summary:

- Take `synthPendingDepth` into account when caching TC results
- Add `maxSynthPendingDepth` option with default := 2.
- Add support for tracking `synthPending` failures when using
`set_option diagnostics true`

closes #2522
closes #3313
closes #3927

Identical to #4114  but with `maxSynthPendingDepth := 1`

closes #4114 

cc @semorrison
2024-05-14 03:03:32 +00:00
Mac Malone
61a84c96db refactor: lake: import errors, job captions, log grouping (#4115)
This improves job captions, the grouping of logs underneath them, and
the handling of import errors. It also adds a number of log-related
utilities to help achieve this.

**Key Changes:**

* Job captions for facets now include the name of the object (e.g.,
module, library, facet). A caption has also been added to the top-level
build of imports (e.g., for the server and `lake lean`).
 
* Stray I/O and errors outside the build job in a build function
captioned with `withRegisterJob` (e.g., user-defined targets) will now
be properly grouped under that caption instead of ending up under
"Computing build jobs". Stray I/O will be converted to a single
informational log entry.

* Builds no longer fail immediately on erroneous imports. Lake will now
attempt to recover as best as possible from any import errors.
Information on the import error will appear both in the build of the
erroneous import and in the files which transitive import it. For
example, uf `Lib.B` imports a missing module `Lib.A`, then the build of
`Lib.A` will mention that the file does not exist, and the build of
`Lib.B` will mention the bad import of `Lib.A`.

Closes #3351. Closes #3809.
2024-05-13 22:11:18 +00:00
Leonardo de Moura
98b2681d0e perf: simplify canonicalizer (#4155) 2024-05-13 19:45:20 +00:00
Joachim Breitner
7c4284aa91 refactor: no need for simpMatchWF? (#4153)
Despite what it said in its docstring, `simpMatchWF?` seems to behave
like `simpMatch?`, so let’s just use that.
2024-05-13 19:33:23 +00:00
Joachim Breitner
842280321b refactor: let Nat.mod reduce more (#4145)
this refined upon #4098 and makes `Nat.mod` reduce on even more
literals. The key observation that I missed earlier is that `if m ≤ n`
reduces better than `if n < m`.

Also see discussion at

https://github.com/leanprover-community/mathlib4/pull/12853#discussion_r1597798308
2024-05-13 16:41:09 +00:00
Joachim Breitner
d833f82fe8 chore: rerun CI only when full-ci label is added or removed (#4136)
Previously, the CI would run upon every label addition, including things
like `builds-mathlib`
or `will-merge-soon`, possibly triggering a new PR release, new mathlib
builds etc. Very wasteful!

Unfortunately (but not surprisingly) Github does not offer a nice way of
saying
“this workflow depends on that label, please re-run if changed”. Not
enough
functional programmer or nix enthusiasts there, I guess…

So here is the next iteration trying to work with what we have from
Github:

A new workflow watches for (only) `full-ci` label addition or deletion,
and then re-runs
the CI job for the current PR.

Sounds simple? But remember, this is github!

* `github.event.pull_request.labels.*.name` is *not* updated when a job
is re-run.

(This is actually a reasonable step towards determinism, but doesn't
help us
   constructing this work-around.)
   
   Ok, so let’s use the API to fetch the current state of the label.

* There is no good way to say “find the latest run of workflow `"CI"` on
PR `$n`”.

The best approximation seems to search by branch and triggering event.
This can
probably go wrong if there are multiple PRs from different repos with
the same
head ref name (`patch-1` anyone?). Let’s hope that it doesn’t happen too
often.

* You cannot just rerun a workflow. You can only rerun a finished
workflow. So cancel
  it first. And `sleep` a bit…

So let’s see how well this will work. It’s plausibly an improvement.
2024-05-13 16:40:36 +00:00
Siddharth
a17c3f424c feat: BitVec.shiftLeft_shiftLeft, BitVec.shiftRight_shiftRight (#4148)
Closes two `sorry`s at
https://github.com/leanprover/leansat/pull/64/files.

---------

Co-authored-by: Kim Morrison <scott@tqft.net>
2024-05-13 12:35:59 +00:00
Kim Morrison
799923d145 chore: move have to decreasing_by in substrEq.loop (#4143)
Currently this causes linter warnings downstream in proofs that unfold
substrEq.loop.
2024-05-13 06:18:44 +00:00
Kim Morrison
f74980ccee chore: incorrect lemma resolution in omega (#4141)
Fixes #4138.
2024-05-12 23:06:48 +00:00
Joachim Breitner
b8f2f28e0d fix: check that funind-reserved names are available (#4135)
I did not introduce `inductTheoremSuffix` etc, it seems more direct to
just spell out the suffix here. If we ever change it there are many
occurrences where they need to be changed anyways, so the definition
doesn't seem to save much work or add that much robustness.
2024-05-12 20:39:14 +00:00
Marcus Rossel
0d9af1b777 fix: typo in Meta.unfoldProjInstWhenInstances? (#4139) 2024-05-12 16:45:56 +00:00
Leonardo de Moura
7db8e6482e fix: auto/option params should not break sorry (#4132)
closes #2649
2024-05-11 02:10:40 +00:00
Leonardo de Moura
147aeaea45 test: for issue 2558 (#4133)
Issue has been fixed by another PR.

closes #2558
2024-05-11 00:47:33 +00:00
Leonardo de Moura
a875ae3acf feat: recover from runtime errors in tactics (#4130)
closes #3554
2024-05-11 00:07:13 +00:00
Mac Malone
25e94f916f feat: IO.TaskState (#4097)
Adds `IO.getTaskState` which returns the state of a `Task` in the Lean
runtime's task manager. The `TaskState` inductive has 3 constructors:
`waiting`, `running`, and `finished`. The `waiting` constructor
encompasses the waiting and queued states within the C task object
documentation, because the task object does not provide a low cost way
to distinguish these different forms of waiting. Furthermore, it seems
unlikely for consumers to wish to distinguish between these internal
states. The `running` constructor encompasses both the running and
promised states in C docs. While not ideal, the C implementation does
not provide a way to distinguish between a running `Task` and a waiting
`Promise.result` (they both have null closures).
2024-05-10 23:04:54 +00:00
Kyle Miller
a1be9ec850 chore: tidying up Lean.unresolveNameGlobal (#4091)
The main loop logic could be simplified, and `if let` could be used to
make control flow more obvious.

Also adds a check for macro scopes to prevent `unresolveNameGlobal` from
returning names with macro scopes in the event there's an alias with
one.

This is a follow up to #3946.
2024-05-10 22:37:34 +00:00
Leonardo de Moura
e237e12478 refactor: add tryCatchRuntimeEx combinator (#4129)
see #4079
2024-05-10 22:34:09 +00:00
Leonardo de Moura
a6d186a81d fix: add checkSystem and withIncRecDepth to withAutoBoundImplicit (#4128)
Fix stack overflow crash.

Closes #4117

The fix can be improved: we could try to avoid creating hundreds of auto
implicits before failing.
2024-05-10 21:55:26 +00:00
Arthur Adjedj
6c6b56e7fc fix: revert "monadic generalization of FindExpr" (#4125)
This reverts commit 706a4cfd73 introduced
in #3970

As explained in #4124, `findM?` can become a footgun if used in monads
which induce side-effects such as caching. This PR removes that
function, and fixes the code introduced by #3398 for which the function
was first added.

cc @semorrison.
2024-05-10 20:36:08 +00:00
JovanGerb
228ff58f3a chore: remove duplicate check (#4126) 2024-05-10 20:35:21 +00:00
Lean stage0 autoupdater
dcdc3db3d4 chore: update stage0 2024-05-10 07:39:47 +00:00
Joachim Breitner
39286862e3 feat: well-founded definitions irreducible by default (#4061)
we keep running into examples where working with well-founded recursion
is slow because defeq checks (which are all over the place, including
failing ones that are back-tracked) unfold well-founded definitions.

The definition of a function defined by well-founded recursion should be
an implementation detail that should only be peeked inside by the
equation generator and the functional induction generator.

We now mark the mutual recursive function as irreducible (if the user
did not
set a flag explicitly), and use `withAtLeastTransparency .all` when
producing
the equations.

Proofs can be fixed by using rewriting, or – a bit blunt, but nice for
adjusting
existing proofs – using `unseal` (a.k.a. `attribute [local
semireducible]`).

Mathlib performance does not change a whole lot:

http://speed.lean-fro.org/mathlib4/compare/08b82265-75db-4a28-b12b-08751b9ad04a/to/16f46d5e-28b1-41c4-a107-a6f6594841f8
Build instructions -0.126 %, four modules with significant instructions
decrease.

To reduce impact, these definitions were changed:

* `Nat.mod`, to make `1 % n` reduce definitionally, so that `1` as a
`Fin 2` literal
works nicely. Theorems with larger `Fin` literals tend to need a `unseal
Nat.modCore`
   https://github.com/leanprover/lean4/pull/4098
* `List.ofFn` rewritten to be structurally recursive and not go via
`Array.ofFn`:
   https://github.com/leanprover-community/batteries/pull/784

Alternative designs explored were

 * Making `WellFounded.fix` irreducible. 
 
One benefit is that recursive functions with equal definitions (possibly
after
instantiating fixed parameters) are defeq; this is used in mathlib to
relate

[`OrdinalApprox.gfpApprox`](https://leanprover-community.github.io/mathlib4_docs/Mathlib/SetTheory/Ordinal/FixedPointApproximants.html#OrdinalApprox.gfpApprox)
with `.lfpApprox`.
   
   But the downside is that one cannot use `unseal` in a
targeted way, being explicit in which recursive function needs to be
reducible here.

And in cases where Lean does unwanted unfolding, we’d still unfold the
recursive
definition once to expose `WellFounded.fix`, leading to large terms for
often no good
   reason.

* Defining `WellFounded.fix` to unroll defintionally once before hitting
a irreducible
`WellFounded.fixF`. This was explored in #4002. It shares most of the
ups and downs
with the previous variant, with the additional neat benefit that
function calls that
do not lead to recursive cases (e.g. a `[]` base case) reduce nicely.
This means that
   the majority of existing `rfl` proofs continue to work.

Issue #4051, which demonstrates how badly things can go if wf recursive
functions can be
unrolled, showed that making the recursive function irreducible there
leads to noticeably
faster elaboration than making `WellFounded.fix` irreducible; this is
good evidence that
the present PR is the way to go. 

This fixes https://github.com/leanprover/lean4/issues/3988

---------

Co-authored-by: Leonardo de Moura <leomoura@amazon.com>
2024-05-10 06:45:21 +00:00
Mac Malone
ca6437df71 fix: lake: TOML key order bug in ppTable (#4104)
Fixes a bug in `Lake.Toml.ppTable` where root table keys could be
printed after a subtable header.

Closes #4099.
2024-05-10 05:12:19 +00:00
Mario Carneiro
3491c56c49 fix: segfault in old compiler due to noConfusion assumptions (#2903)
This fixes #2901, a bug in the old compiler which causes a segfault. The
issue is that when visiting `noConfusion` applications, it assumes that
each constructor case has `nfields` arguments, e.g. `head1 = head2 ->
tail1 = tail2 -> P` has two arguments because `List.cons` has 2 fields,
but in fact propositional fields are skipped by the noConfusion type
generator, so for example `Subtype.noConfusionType` is:
```lean
@[reducible] protected def Subtype.noConfusionType.{u_1, u} : {α : Sort u} →
  {p : α → Prop} → Sort u_1 → Subtype p → Subtype p → Sort u_1 :=
fun {α} {p} P v1 v2 ↦
  Subtype.casesOn v1 fun val property ↦ Subtype.casesOn v2 fun val_1 property ↦ 
    (val = val_1 → P) → P
```
where `val = val_1 → P` only has the one argument even though
`Subtype.mk` has two fields, presumably because it is useless to have an
equality of propositions. Unfortunately there isn't any easy cache or
getter to use here to get the number of non-propositional fields, so we
just calculate it on the spot.
2024-05-10 01:38:38 +00:00
Tobias Grosser
368adaf847 feat: add BitVec.[toInt_inj|toInt_ne] (#4075)
Co-authored-by: Kim Morrison <scott@tqft.net>
2024-05-10 00:57:00 +00:00
Leonardo de Moura
6a040ab068 feat: propagate maxHeartbeats to kernel (#4113)
Co-authored-by: Sebastian Ullrich <sebasti@nullri.ch>
2024-05-09 17:44:19 +00:00
Kim Morrison
fe7b96d8a0 fix: generate deprecation warnings for dot notation (#3969)
Fixes #3270 by moving the deprecation check from
`Lean.Elab.Term.mkConsts` to `Lean.Elab.Term.mkConst`, so
`Lean.Elab.Term.mkBaseProjections`, `.elabAppLValsAux`, `.elabAppFn`,
and `.elabForIn` also hit the check. Not all of these really need to hit
the check, so I'll run `!bench` to see if it's a problem.
2024-05-09 04:52:09 +00:00
Leonardo de Moura
ec87283465 perf: use withSynthesize when elaborating let/have type (#4096)
closes #4051

cc @semorrison
2024-05-09 00:58:43 +00:00
Leonardo de Moura
d7c6920550 chore: remove dead functionpreprocessLevels (#4112) 2024-05-08 15:38:04 +00:00
Joachim Breitner
227e861719 refactor: make 1 % n reduce without well-founded recursion (#4098)
this is in preparation for #4061. Once that lands, `1 % 42 = 1` will no
longer hold definitionally (at least not without an ungly `unseal
Nat.modCore in` around). This affects mathlib in a few places,
essentially every time a `1 : Fin (n+1)` literal is written.

So this extends the existing special case for `0 % n = 0` to `1 % n`.
2024-05-08 15:12:47 +00:00
Joachim Breitner
e9c302c17e chore: bug template: point to live.lean-lang.org (#4109)
https://live.lean-lang.org/#project=lean-nightly now allows users to
play around with the latest lean nightly, and it seems prudent to ask
them to test bug reports, if possible, there, and not just with whatever
release they use.

Also reformatted the descriptions to look well in a text area. Users
will not see this as rendered markdown, but as plain text.

---------

Co-authored-by: Sebastian Ullrich <sebasti@nullri.ch>
2024-05-08 13:21:17 +00:00
Mario Carneiro
5814a45d44 fix: mainModuleName should use srcSearchPath (#4066)
As [reported on
Zulip](https://leanprover.zulipchat.com/#narrow/stream/341532-lean4-dev/topic/Find.20references.20broken.20in.20lean.20core/near/437051935).
The `mainModuleName` was being set incorrectly when browsing lean core
sources, resulting in failure of cross-file server requests like "Find
References". Because the `srcSearchPath` is generated asynchronously, we
store it as a `Task Name` which is resolved some time before the header
is finished parsing. (I don't think the `.get` here will ever block,
because the srcSearchPath will be ready by the time the initial command
snap is requested.)

---------

Co-authored-by: Sebastian Ullrich <sebasti@nullri.ch>
2024-05-08 12:34:27 +00:00
Kim Morrison
dcf74b0d89 chore: Std -> Batteries renaming (#4108) 2024-05-08 05:04:25 +00:00
Kyle Miller
a257767417 fix: make deriving handler for Repr be consistent about erasing types and proofs (#3944)
The deriving handler would use `_` for types and proofs for structures
but not for inductives.

Reported by Graham Leach-Krouse on
[Zulip](https://leanprover.zulipchat.com/#narrow/stream/113489-new-members/topic/Deriving.20Repr.20for.20an.20inductive.20with.20proof.20parameters/near/434181985).
2024-05-07 23:55:52 +00:00
Austin Letson
b8e67d87a8 doc: add docstrings and usage examples in Init.Data.String.Basic (#4001)
Add docstrings and usage examples for `String.length`, `.push`,
`.append`, `.get?`, `.set`, `.modyify`, and `.next`. Update docstrings
and add usage examples for `String.toList`, `.get`, and `.get!`.

---------

Co-authored-by: Joachim Breitner <mail@joachim-breitner.de>
Co-authored-by: David Thrane Christiansen <david@davidchristiansen.dk>
2024-05-07 23:49:43 +00:00
Leonardo de Moura
2a5ca00ad6 perf: issue at binop% and binrel% elaborators (#4092)
This issue was affecting several Mathlib files.

@mattrobball @semorrison This is a different solution for the issue. The
comment at `Extra.lean` describes the new solution and documents the new
issues found with the previous one.

closes #4085
2024-05-07 23:31:05 +00:00
François G. Dorais
ec27b3760d fix: swap Nat.zero_or and Nat.or_zero (#4094)
Closes #4093
2024-05-07 23:29:38 +00:00
Siddharth
e5b7dc819b feat: bitvec lemma to turn negation into bitwise not+add (#4095)
Identity 2-2 (a) (Section: Addition Combined with Logical Operations)
from Hacker's Delight, 2nd edition.
2024-05-07 22:31:19 +00:00
Leonardo de Moura
93c06c0552 feat: relaxed reset/reuse in the code generator (#4100)
closes #4089
2024-05-07 22:08:32 +00:00
Mario Carneiro
bb7e6e4769 chore: lake: tweak hovers for family_def et al (#4088)
This makes the `leanArts` in `library_data leanArts : BuildJob Unit` get
a hover for the generated axiom. It also simplifies the `quoteFrom`
function so that it delaborates properly by using a name literal (which
elaborates to `mkStr1`, `mkStr2` etc) instead of a `mkStr` application.
2024-05-07 15:14:09 +00:00
1135 changed files with 13521 additions and 3227 deletions

View File

@@ -9,9 +9,15 @@ assignees: ''
### Prerequisites
* [ ] Put an X between the brackets on this line if you have done all of the following:
* Check that your issue is not already [filed](https://github.com/leanprover/lean4/issues).
* Reduce the issue to a minimal, self-contained, reproducible test case. Avoid dependencies to mathlib4 or std4.
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
* [ ] Reduce the issue to a minimal, self-contained, reproducible test case.
Avoid dependencies to Mathlib or Batteries.
* [ ] Test your test case against the latest nightly release, for example on
https://live.lean-lang.org/#project=lean-nightly
(You can also use the settings there to switch to “Lean nightly”)
### Description
@@ -33,8 +39,8 @@ assignees: ''
### Versions
[Output of `#eval Lean.versionString` or of `lean --version` in the folder that the issue occured in]
[OS version]
[Output of `#eval Lean.versionString`]
[OS version, if not using live.lean-lang.org.]
### Additional Information

View File

@@ -6,7 +6,6 @@ on:
tags:
- '*'
pull_request:
types: [opened, synchronize, reopened, labeled]
merge_group:
schedule:
- cron: '0 7 * * *' # 8AM CET/11PM PT
@@ -21,8 +20,10 @@ jobs:
configure:
runs-on: ubuntu-latest
outputs:
# Should we run only a quick CI? Yes on a pull request without the full-ci label
quick: ${{ steps.set-quick.outputs.quick }}
# 0: PRs without special label
# 1: PRs with `merge-ci` label, merge queue checks, master commits
# 2: PRs with `release-ci` label, releases (incl. nightlies)
check-level: ${{ steps.set-level.outputs.check-level }}
# The build matrix, dynamically generated here
matrix: ${{ steps.set-matrix.outputs.result }}
# Should we make a nightly release? If so, this output contains the lean version string, else it is empty
@@ -39,161 +40,6 @@ jobs:
RELEASE_TAG: ${{ steps.set-release.outputs.RELEASE_TAG }}
steps:
- name: Run quick CI?
id: set-quick
env:
quick: ${{
github.event_name == 'pull_request' && !contains( github.event.pull_request.labels.*.name, 'full-ci')
}}
run: |
echo "quick=${{env.quick}}" >> "$GITHUB_OUTPUT"
- name: Configure build matrix
id: set-matrix
uses: actions/github-script@v7
with:
script: |
const quick = ${{ steps.set-quick.outputs.quick }};
console.log(`quick: ${quick}`);
// use large runners outside PRs where available (original repo)
// disabled for now as this mostly just speeds up the test suite which is not a bottleneck
// let large = ${{ github.event_name != 'pull_request' && github.repository == 'leanprover/lean4' }} ? "-large" : "";
let matrix = [
{
// portable release build: use channel with older glibc (2.27)
"name": "Linux LLVM",
"os": "ubuntu-latest",
"release": false,
"quick": false,
"shell": "nix develop .#oldGlibc -c bash -euxo pipefail {0}",
"llvm-url": "https://github.com/leanprover/lean-llvm/releases/download/15.0.1/lean-llvm-x86_64-linux-gnu.tar.zst",
"prepare-llvm": "../script/prepare-llvm-linux.sh lean-llvm*",
"binary-check": "ldd -v",
// foreign code may be linked against more recent glibc
// reverse-ffi needs to be updated to link to LLVM libraries
"CTEST_OPTIONS": "-E 'foreign|leanlaketest_reverse-ffi'",
"CMAKE_OPTIONS": "-DLLVM=ON -DLLVM_CONFIG=${GITHUB_WORKSPACE}/build/llvm-host/bin/llvm-config"
},
{
"name": "Linux release",
"os": "ubuntu-latest",
"release": true,
"quick": true,
"shell": "nix develop .#oldGlibc -c bash -euxo pipefail {0}",
"llvm-url": "https://github.com/leanprover/lean-llvm/releases/download/15.0.1/lean-llvm-x86_64-linux-gnu.tar.zst",
"prepare-llvm": "../script/prepare-llvm-linux.sh lean-llvm*",
"binary-check": "ldd -v",
// foreign code may be linked against more recent glibc
"CTEST_OPTIONS": "-E 'foreign'"
},
{
"name": "Linux",
"os": "ubuntu-latest",
"check-stage3": true,
"test-speedcenter": true,
"quick": false,
},
{
"name": "Linux Debug",
"os": "ubuntu-latest",
"quick": false,
"CMAKE_OPTIONS": "-DCMAKE_BUILD_TYPE=Debug",
// exclude seriously slow tests
"CTEST_OPTIONS": "-E 'interactivetest|leanpkgtest|laketest|benchtest'"
},
// TODO: suddenly started failing in CI
/*{
"name": "Linux fsanitize",
"os": "ubuntu-latest",
"quick": false,
// turn off custom allocator & symbolic functions to make LSAN do its magic
"CMAKE_OPTIONS": "-DLEAN_EXTRA_CXX_FLAGS=-fsanitize=address,undefined -DLEANC_EXTRA_FLAGS='-fsanitize=address,undefined -fsanitize-link-c++-runtime' -DSMALL_ALLOCATOR=OFF -DBSYMBOLIC=OFF",
// exclude seriously slow/problematic tests (laketests crash)
"CTEST_OPTIONS": "-E 'interactivetest|leanpkgtest|laketest|benchtest'"
},*/
{
"name": "macOS",
"os": "macos-13",
"release": true,
"quick": false,
"shell": "bash -euxo pipefail {0}",
"llvm-url": "https://github.com/leanprover/lean-llvm/releases/download/15.0.1/lean-llvm-x86_64-apple-darwin.tar.zst",
"prepare-llvm": "../script/prepare-llvm-macos.sh lean-llvm*",
"binary-check": "otool -L",
"tar": "gtar" // https://github.com/actions/runner-images/issues/2619
},
{
"name": "macOS aarch64",
"os": "macos-13",
"release": true,
"quick": false,
"cross": true,
"cross_target": "aarch64-apple-darwin",
"shell": "bash -euxo pipefail {0}",
"CMAKE_OPTIONS": "-DUSE_GMP=OFF -DLEAN_INSTALL_SUFFIX=-darwin_aarch64",
"llvm-url": "https://github.com/leanprover/lean-llvm/releases/download/15.0.1/lean-llvm-aarch64-apple-darwin.tar.zst https://github.com/leanprover/lean-llvm/releases/download/15.0.1/lean-llvm-x86_64-apple-darwin.tar.zst",
"prepare-llvm": "../script/prepare-llvm-macos.sh lean-llvm-aarch64-* lean-llvm-x86_64-*",
"binary-check": "otool -L",
"tar": "gtar" // https://github.com/actions/runner-images/issues/2619
},
{
"name": "Windows",
"os": "windows-2022",
"release": true,
"quick": false,
"shell": "msys2 {0}",
"CMAKE_OPTIONS": "-G \"Unix Makefiles\" -DUSE_GMP=OFF",
// for reasons unknown, interactivetests are flaky on Windows
"CTEST_OPTIONS": "--repeat until-pass:2",
"llvm-url": "https://github.com/leanprover/lean-llvm/releases/download/15.0.1/lean-llvm-x86_64-w64-windows-gnu.tar.zst",
"prepare-llvm": "../script/prepare-llvm-mingw.sh lean-llvm*",
"binary-check": "ldd"
},
{
"name": "Linux aarch64",
"os": "ubuntu-latest",
"CMAKE_OPTIONS": "-DUSE_GMP=OFF -DLEAN_INSTALL_SUFFIX=-linux_aarch64",
"release": true,
"quick": false,
"cross": true,
"cross_target": "aarch64-unknown-linux-gnu",
"shell": "nix develop .#oldGlibcAArch -c bash -euxo pipefail {0}",
"llvm-url": "https://github.com/leanprover/lean-llvm/releases/download/15.0.1/lean-llvm-x86_64-linux-gnu.tar.zst https://github.com/leanprover/lean-llvm/releases/download/15.0.1/lean-llvm-aarch64-linux-gnu.tar.zst",
"prepare-llvm": "../script/prepare-llvm-linux.sh lean-llvm-aarch64-* lean-llvm-x86_64-*"
},
{
"name": "Linux 32bit",
"os": "ubuntu-latest",
// Use 32bit on stage0 and stage1 to keep oleans compatible
"CMAKE_OPTIONS": "-DSTAGE0_USE_GMP=OFF -DSTAGE0_LEAN_EXTRA_CXX_FLAGS='-m32' -DSTAGE0_LEANC_OPTS='-m32' -DSTAGE0_MMAP=OFF -DUSE_GMP=OFF -DLEAN_EXTRA_CXX_FLAGS='-m32' -DLEANC_OPTS='-m32' -DMMAP=OFF -DLEAN_INSTALL_SUFFIX=-linux_x86",
"cmultilib": true,
"release": true,
"quick": false,
"cross": true,
"shell": "bash -euxo pipefail {0}"
},
{
"name": "Web Assembly",
"os": "ubuntu-latest",
// Build a native 32bit binary in stage0 and use it to compile the oleans and the wasm build
"CMAKE_OPTIONS": "-DCMAKE_C_COMPILER_WORKS=1 -DSTAGE0_USE_GMP=OFF -DSTAGE0_LEAN_EXTRA_CXX_FLAGS='-m32' -DSTAGE0_LEANC_OPTS='-m32' -DSTAGE0_CMAKE_CXX_COMPILER=clang++ -DSTAGE0_CMAKE_C_COMPILER=clang -DSTAGE0_CMAKE_EXECUTABLE_SUFFIX=\"\" -DUSE_GMP=OFF -DMMAP=OFF -DSTAGE0_MMAP=OFF -DCMAKE_AR=../emsdk/emsdk-main/upstream/emscripten/emar -DCMAKE_TOOLCHAIN_FILE=../emsdk/emsdk-main/upstream/emscripten/cmake/Modules/Platform/Emscripten.cmake -DLEAN_INSTALL_SUFFIX=-linux_wasm32",
"wasm": true,
"cmultilib": true,
"release": true,
"quick": false,
"cross": true,
"shell": "bash -euxo pipefail {0}",
// Just a few selected tests because wasm is slow
"CTEST_OPTIONS": "-R \"leantest_1007\\.lean|leantest_Format\\.lean|leanruntest\\_1037.lean|leanruntest_ac_rfl\\.lean\""
}
];
console.log(`matrix:\n${JSON.stringify(matrix, null, 2)}`)
if (quick) {
return matrix.filter((job) => job.quick)
} else {
return matrix
}
- name: Checkout
uses: actions/checkout@v3
# don't schedule nightlies on forks
@@ -244,6 +90,170 @@ jobs:
echo "Tag ${TAG_NAME} did not match SemVer regex."
fi
- name: Set check level
id: set-level
# We do not use github.event.pull_request.labels.*.name here because
# re-running a run does not update that list, and we do want to be able to
# rerun the workflow run after setting the `release-ci`/`merge-ci` labels.
run: |
check_level=0
if [[ -n "${{ steps.set-nightly.outputs.nightly }}" || -n "${{ steps.set-release.outputs.RELEASE_TAG }}" ]]; then
check_level=2
elif [[ "${{ github.event_name }}" != "pull_request" ]]; then
check_level=1
else
labels="$(gh api repos/${{ github.repository_owner }}/${{ github.event.repository.name }}/pulls/${{ github.event.pull_request.number }}) --jq '.labels'"
if echo "$labels" | grep -q "release-ci"; then
check_level=2
elif echo "$labels" | grep -q "merge-ci"; then
check_level=1
fi
fi
echo "check-level=$check_level" >> "$GITHUB_OUTPUT"
env:
GH_TOKEN: ${{ github.token }}
- name: Configure build matrix
id: set-matrix
uses: actions/github-script@v7
with:
script: |
const level = ${{ steps.set-level.outputs.check-level }};
console.log(`level: ${level}`);
// use large runners outside PRs where available (original repo)
// disabled for now as this mostly just speeds up the test suite which is not a bottleneck
// let large = ${{ github.event_name != 'pull_request' && github.repository == 'leanprover/lean4' }} ? "-large" : "";
let matrix = [
{
// portable release build: use channel with older glibc (2.27)
"name": "Linux LLVM",
"os": "ubuntu-latest",
"release": false,
"check-level": 2,
"shell": "nix develop .#oldGlibc -c bash -euxo pipefail {0}",
"llvm-url": "https://github.com/leanprover/lean-llvm/releases/download/15.0.1/lean-llvm-x86_64-linux-gnu.tar.zst",
"prepare-llvm": "../script/prepare-llvm-linux.sh lean-llvm*",
"binary-check": "ldd -v",
// foreign code may be linked against more recent glibc
// reverse-ffi needs to be updated to link to LLVM libraries
"CTEST_OPTIONS": "-E 'foreign|leanlaketest_reverse-ffi'",
"CMAKE_OPTIONS": "-DLLVM=ON -DLLVM_CONFIG=${GITHUB_WORKSPACE}/build/llvm-host/bin/llvm-config"
},
{
"name": "Linux release",
"os": "ubuntu-latest",
"release": true,
"check-level": 0,
"shell": "nix develop .#oldGlibc -c bash -euxo pipefail {0}",
"llvm-url": "https://github.com/leanprover/lean-llvm/releases/download/15.0.1/lean-llvm-x86_64-linux-gnu.tar.zst",
"prepare-llvm": "../script/prepare-llvm-linux.sh lean-llvm*",
"binary-check": "ldd -v",
// foreign code may be linked against more recent glibc
"CTEST_OPTIONS": "-E 'foreign'"
},
{
"name": "Linux",
"os": "ubuntu-latest",
"check-stage3": level >= 2,
"test-speedcenter": level >= 2,
"check-level": 1,
},
{
"name": "Linux Debug",
"os": "ubuntu-latest",
"check-level": 2,
"CMAKE_PRESET": "debug",
// exclude seriously slow tests
"CTEST_OPTIONS": "-E 'interactivetest|leanpkgtest|laketest|benchtest'"
},
// TODO: suddenly started failing in CI
/*{
"name": "Linux fsanitize",
"os": "ubuntu-latest",
"check-level": 2,
// turn off custom allocator & symbolic functions to make LSAN do its magic
"CMAKE_PRESET": "sanitize",
// exclude seriously slow/problematic tests (laketests crash)
"CTEST_OPTIONS": "-E 'interactivetest|leanpkgtest|laketest|benchtest'"
},*/
{
"name": "macOS",
"os": "macos-13",
"release": true,
"check-level": 2,
"shell": "bash -euxo pipefail {0}",
"llvm-url": "https://github.com/leanprover/lean-llvm/releases/download/15.0.1/lean-llvm-x86_64-apple-darwin.tar.zst",
"prepare-llvm": "../script/prepare-llvm-macos.sh lean-llvm*",
"binary-check": "otool -L",
"tar": "gtar" // https://github.com/actions/runner-images/issues/2619
},
{
"name": "macOS aarch64",
"os": "macos-14",
"release": true,
"check-level": 1,
"shell": "bash -euxo pipefail {0}",
"llvm-url": "https://github.com/leanprover/lean-llvm/releases/download/15.0.1/lean-llvm-aarch64-apple-darwin.tar.zst",
"prepare-llvm": "../script/prepare-llvm-macos.sh lean-llvm*",
"binary-check": "otool -L",
"tar": "gtar" // https://github.com/actions/runner-images/issues/2619
},
{
"name": "Windows",
"os": "windows-2022",
"release": true,
"check-level": 2,
"shell": "msys2 {0}",
"CMAKE_OPTIONS": "-G \"Unix Makefiles\" -DUSE_GMP=OFF",
// for reasons unknown, interactivetests are flaky on Windows
"CTEST_OPTIONS": "--repeat until-pass:2",
"llvm-url": "https://github.com/leanprover/lean-llvm/releases/download/15.0.1/lean-llvm-x86_64-w64-windows-gnu.tar.zst",
"prepare-llvm": "../script/prepare-llvm-mingw.sh lean-llvm*",
"binary-check": "ldd"
},
{
"name": "Linux aarch64",
"os": "ubuntu-latest",
"CMAKE_OPTIONS": "-DUSE_GMP=OFF -DLEAN_INSTALL_SUFFIX=-linux_aarch64",
"release": true,
"check-level": 2,
"cross": true,
"cross_target": "aarch64-unknown-linux-gnu",
"shell": "nix develop .#oldGlibcAArch -c bash -euxo pipefail {0}",
"llvm-url": "https://github.com/leanprover/lean-llvm/releases/download/15.0.1/lean-llvm-x86_64-linux-gnu.tar.zst https://github.com/leanprover/lean-llvm/releases/download/15.0.1/lean-llvm-aarch64-linux-gnu.tar.zst",
"prepare-llvm": "../script/prepare-llvm-linux.sh lean-llvm-aarch64-* lean-llvm-x86_64-*"
},
{
"name": "Linux 32bit",
"os": "ubuntu-latest",
// Use 32bit on stage0 and stage1 to keep oleans compatible
"CMAKE_OPTIONS": "-DSTAGE0_USE_GMP=OFF -DSTAGE0_LEAN_EXTRA_CXX_FLAGS='-m32' -DSTAGE0_LEANC_OPTS='-m32' -DSTAGE0_MMAP=OFF -DUSE_GMP=OFF -DLEAN_EXTRA_CXX_FLAGS='-m32' -DLEANC_OPTS='-m32' -DMMAP=OFF -DLEAN_INSTALL_SUFFIX=-linux_x86",
"cmultilib": true,
"release": true,
"check-level": 2,
"cross": true,
"shell": "bash -euxo pipefail {0}"
},
{
"name": "Web Assembly",
"os": "ubuntu-latest",
// Build a native 32bit binary in stage0 and use it to compile the oleans and the wasm build
"CMAKE_OPTIONS": "-DCMAKE_C_COMPILER_WORKS=1 -DSTAGE0_USE_GMP=OFF -DSTAGE0_LEAN_EXTRA_CXX_FLAGS='-m32' -DSTAGE0_LEANC_OPTS='-m32' -DSTAGE0_CMAKE_CXX_COMPILER=clang++ -DSTAGE0_CMAKE_C_COMPILER=clang -DSTAGE0_CMAKE_EXECUTABLE_SUFFIX=\"\" -DUSE_GMP=OFF -DMMAP=OFF -DSTAGE0_MMAP=OFF -DCMAKE_AR=../emsdk/emsdk-main/upstream/emscripten/emar -DCMAKE_TOOLCHAIN_FILE=../emsdk/emsdk-main/upstream/emscripten/cmake/Modules/Platform/Emscripten.cmake -DLEAN_INSTALL_SUFFIX=-linux_wasm32",
"wasm": true,
"cmultilib": true,
"release": true,
"check-level": 2,
"cross": true,
"shell": "bash -euxo pipefail {0}",
// Just a few selected tests because wasm is slow
"CTEST_OPTIONS": "-R \"leantest_1007\\.lean|leantest_Format\\.lean|leanruntest\\_1037.lean|leanruntest_ac_rfl\\.lean\""
}
];
console.log(`matrix:\n${JSON.stringify(matrix, null, 2)}`)
return matrix.filter((job) => level >= job["check-level"])
build:
needs: [configure]
if: github.event_name != 'schedule' || github.repository == 'leanprover/lean4'
@@ -322,6 +332,9 @@ jobs:
# store in current directory, for easy uploading together with binary
echo $PWD/coredumps/%e.%p.%t | sudo tee /proc/sys/kernel/core_pattern
if: runner.os == 'Linux'
- name: Set up NPROC
run: |
echo "NPROC=$(nproc 2>/dev/null || sysctl -n hw.logicalcpu 2>/dev/null || echo 4)" >> $GITHUB_ENV
- name: Build
run: |
mkdir build
@@ -352,8 +365,8 @@ jobs:
OPTIONS+=(-DLEAN_SPECIAL_VERSION_DESC=${{ needs.configure.outputs.LEAN_SPECIAL_VERSION_DESC }})
fi
# contortion to support empty OPTIONS with old macOS bash
cmake .. ${{ matrix.CMAKE_OPTIONS }} ${OPTIONS[@]+"${OPTIONS[@]}"} -DLEAN_INSTALL_PREFIX=$PWD/..
make -j4
cmake .. --preset ${{ matrix.CMAKE_PRESET || 'release' }} -B . ${{ matrix.CMAKE_OPTIONS }} ${OPTIONS[@]+"${OPTIONS[@]}"} -DLEAN_INSTALL_PREFIX=$PWD/..
make -j$NPROC
make install
- name: Check Binaries
run: ${{ matrix.binary-check }} lean-*/bin/* || true
@@ -382,32 +395,29 @@ jobs:
build/stage1/bin/lean --stats src/Lean.lean
if: ${{ !matrix.cross }}
- name: Test
id: test
run: |
cd build/stage1
ulimit -c unlimited # coredumps
# exclude nonreproducible test
ctest -j4 --progress --output-junit test-results.xml --output-on-failure ${{ matrix.CTEST_OPTIONS }} < /dev/null
if: (matrix.wasm || !matrix.cross) && needs.configure.outputs.quick == 'false'
ctest --preset ${{ matrix.CMAKE_PRESET || 'release' }} --test-dir build/stage1 -j$NPROC --output-junit test-results.xml ${{ matrix.CTEST_OPTIONS }}
if: (matrix.wasm || !matrix.cross) && needs.configure.outputs.check-level >= 1
- name: Test Summary
uses: test-summary/action@v2
with:
paths: build/stage1/test-results.xml
# prefix `if` above with `always` so it's run even if tests failed
if: always() && (matrix.wasm || !matrix.cross) && needs.configure.outputs.quick == 'false'
if: always() && steps.test.conclusion != 'skipped'
- name: Check Test Binary
run: ${{ matrix.binary-check }} tests/compiler/534.lean.out
if: ${{ !matrix.cross && needs.configure.outputs.quick == 'false' }}
if: (!matrix.cross) && steps.test.conclusion != 'skipped'
- name: Build Stage 2
run: |
cd build
ulimit -c unlimited # coredumps
make -j4 stage2
make -C build -j$NPROC stage2
if: matrix.test-speedcenter
- name: Check Stage 3
run: |
cd build
ulimit -c unlimited # coredumps
make -j4 check-stage3
make -C build -j$NPROC stage3
if: matrix.test-speedcenter
- name: Test Speedcenter Benchmarks
run: |
@@ -418,11 +428,10 @@ jobs:
if: matrix.test-speedcenter
- name: Check rebootstrap
run: |
cd build
ulimit -c unlimited # coredumps
# clean rebuild in case of Makefile changes
make update-stage0 && rm -rf ./stage* && make -j4
if: matrix.name == 'Linux' && needs.configure.outputs.quick == 'false'
make -C build update-stage0 && rm -rf build/stage* && make -C build -j$NPROC
if: matrix.name == 'Linux' && needs.configure.outputs.check-level >= 1
- name: CCache stats
run: ccache -s
- name: Show stacktrace for coredumps

View File

@@ -6,7 +6,6 @@ on:
tags:
- '*'
pull_request:
types: [opened, synchronize, reopened, labeled]
merge_group:
concurrency:

View File

@@ -126,11 +126,11 @@ jobs:
if [ "$NIGHTLY_SHA" = "$MERGE_BASE_SHA" ]; then
echo "The merge base of this PR coincides with the nightly release"
STD_REMOTE_TAGS="$(git ls-remote https://github.com/leanprover/std4.git nightly-testing-"$MOST_RECENT_NIGHTLY")"
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.git nightly-testing-"$MOST_RECENT_NIGHTLY")"
if [[ -n "$STD_REMOTE_TAGS" ]]; then
echo "... and Std has a 'nightly-testing-$MOST_RECENT_NIGHTLY' tag."
if [[ -n "$BATTERIES_REMOTE_TAGS" ]]; then
echo "... and Batteries has a 'nightly-testing-$MOST_RECENT_NIGHTLY' tag."
MESSAGE=""
if [[ -n "$MATHLIB_REMOTE_TAGS" ]]; then
@@ -140,8 +140,8 @@ jobs:
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 Std does not yet have a 'nightly-testing-$MOST_RECENT_NIGHTLY' tag."
MESSAGE="- ❗ Std 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\`, Std CI should run now."
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."
fi
else
@@ -151,7 +151,7 @@ jobs:
git -C lean4.git fetch origin nightly-with-mathlib
NIGHTLY_WITH_MATHLIB_SHA="$(git -C lean4.git rev-parse "origin/nightly-with-mathlib")"
MESSAGE="- ❗ Std/Mathlib CI will not be attempted unless your PR branches off the \`nightly-with-mathlib\` branch. Try \`git rebase $MERGE_BASE_SHA --onto $NIGHTLY_WITH_MATHLIB_SHA\`."
MESSAGE="- ❗ Batteries/Mathlib CI will not be attempted unless your PR branches off the \`nightly-with-mathlib\` branch. Try \`git rebase $MERGE_BASE_SHA --onto $NIGHTLY_WITH_MATHLIB_SHA\`."
fi
if [[ -n "$MESSAGE" ]]; then
@@ -223,27 +223,27 @@ jobs:
description: description,
});
# We next automatically create a Std branch using this toolchain.
# Std doesn't itself have a mechanism to report results of CI from this branch back to Lean
# Instead this is taken care of by Mathlib CI, which will fail if Std fails.
# We next automatically create a Batteries branch using this toolchain.
# Batteries doesn't itself have a mechanism to report results of CI from this branch back to Lean
# Instead this is taken care of by Mathlib CI, which will fail if Batteries fails.
- name: Cleanup workspace
if: steps.workflow-info.outputs.pullRequestNumber != '' && steps.ready.outputs.mathlib_ready == 'true'
run: |
sudo rm -rf ./*
# Checkout the Std repository with all branches
- name: Checkout Std repository
# Checkout the Batteries repository with all branches
- name: Checkout Batteries repository
if: steps.workflow-info.outputs.pullRequestNumber != '' && steps.ready.outputs.mathlib_ready == 'true'
uses: actions/checkout@v3
with:
repository: leanprover/std4
repository: leanprover-community/batteries
token: ${{ secrets.MATHLIB4_BOT }}
ref: nightly-testing
fetch-depth: 0 # This ensures we check out all tags and branches.
- name: Check if tag exists
if: steps.workflow-info.outputs.pullRequestNumber != '' && steps.ready.outputs.mathlib_ready == 'true'
id: check_std_tag
id: check_batteries_tag
run: |
git config user.name "leanprover-community-mathlib4-bot"
git config user.email "leanprover-community-mathlib4-bot@users.noreply.github.com"
@@ -251,7 +251,7 @@ jobs:
if git ls-remote --heads --tags --exit-code origin "nightly-testing-${MOST_RECENT_NIGHTLY}" >/dev/null; then
BASE="nightly-testing-${MOST_RECENT_NIGHTLY}"
else
echo "This shouldn't be possible: couldn't find a 'nightly-testing-${MOST_RECENT_NIGHTLY}' tag at Std. Falling back to 'nightly-testing'."
echo "This shouldn't be possible: couldn't find a 'nightly-testing-${MOST_RECENT_NIGHTLY}' tag at Batteries. Falling back to 'nightly-testing'."
BASE=nightly-testing
fi
@@ -268,7 +268,7 @@ jobs:
else
echo "Branch already exists, pushing an empty commit."
git switch lean-pr-testing-${{ steps.workflow-info.outputs.pullRequestNumber }}
# The Std `nightly-testing` or `nightly-testing-YYYY-MM-DD` branch may have moved since this branch was created, so merge their changes.
# The Batteries `nightly-testing` or `nightly-testing-YYYY-MM-DD` branch may have moved since this branch was created, so merge their changes.
# (This should no longer be possible once `nightly-testing-YYYY-MM-DD` is a tag, but it is still safe to merge.)
git merge "$BASE" --strategy-option ours --no-commit --allow-unrelated-histories
git commit --allow-empty -m "Trigger CI for https://github.com/leanprover/lean4/pull/${{ steps.workflow-info.outputs.pullRequestNumber }}"
@@ -321,7 +321,7 @@ jobs:
git switch -c lean-pr-testing-${{ steps.workflow-info.outputs.pullRequestNumber }} "$BASE"
echo "leanprover/lean4-pr-releases:pr-release-${{ steps.workflow-info.outputs.pullRequestNumber }}" > lean-toolchain
git add lean-toolchain
sed -i "s/require std from git \"https:\/\/github.com\/leanprover\/std4\" @ \".\+\"/require std from git \"https:\/\/github.com\/leanprover\/std4\" @ \"nightly-testing-${MOST_RECENT_NIGHTLY}\"/" lakefile.lean
sed -i "s/require batteries from git \"https:\/\/github.com\/leanprover-community\/batteries\" @ \".\+\"/require batteries from git \"https:\/\/github.com\/leanprover-community\/batteries\" @ \"nightly-testing-${MOST_RECENT_NIGHTLY}\"/" lakefile.lean
git add lakefile.lean
git commit -m "Update lean-toolchain for testing https://github.com/leanprover/lean4/pull/${{ steps.workflow-info.outputs.pullRequestNumber }}"
else

31
.github/workflows/restart-on-label.yml vendored Normal file
View File

@@ -0,0 +1,31 @@
name: Restart by label
on:
pull_request_target:
types:
- unlabeled
- labeled
jobs:
restart-on-label:
runs-on: ubuntu-latest
if: contains(github.event.label.name, 'merge-ci') || contains(github.event.label.name, 'release-ci')
steps:
- run: |
# Finding latest CI workflow run on current pull request
# (unfortunately cannot search by PR number, only base branch,
# and that is't even unique given PRs from forks, but the risk
# of confusion is low and the danger is mild)
run_id=$(gh run list -e pull_request -b "$head_ref" --workflow 'CI' --limit 1 \
--limit 1 --json databaseId --jq '.[0].databaseId')
echo "Run id: ${run_id}"
gh run view "$run_id"
echo "Cancelling (just in case)"
gh run cancel "$run_id" || echo "(failed)"
echo "Waiting for 10s"
sleep 10
echo "Rerunning"
gh run rerun "$run_id"
shell: bash
env:
head_ref: ${{ github.head_ref }}
GH_TOKEN: ${{ github.token }}
GH_REPO: ${{ github.repository }}

83
CMakePresets.json Normal file
View File

@@ -0,0 +1,83 @@
{
"version": 2,
"cmakeMinimumRequired": {
"major": 3,
"minor": 10,
"patch": 0
},
"configurePresets": [
{
"name": "release",
"displayName": "Default development optimized build config",
"generator": "Unix Makefiles",
"binaryDir": "${sourceDir}/build/release"
},
{
"name": "debug",
"displayName": "Debug build config",
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Debug"
},
"generator": "Unix Makefiles",
"binaryDir": "${sourceDir}/build/debug"
},
{
"name": "sanitize",
"displayName": "Sanitize build config",
"cacheVariables": {
"LEAN_EXTRA_CXX_FLAGS": "-fsanitize=address,undefined",
"LEANC_EXTRA_FLAGS": "-fsanitize=address,undefined -fsanitize-link-c++-runtime",
"SMALL_ALLOCATOR": "OFF",
"BSYMBOLIC": "OFF"
},
"generator": "Unix Makefiles",
"binaryDir": "${sourceDir}/build/sanitize"
},
{
"name": "sandebug",
"inherits": ["debug", "sanitize"],
"displayName": "Sanitize+debug build config",
"binaryDir": "${sourceDir}/build/sandebug"
}
],
"buildPresets": [
{
"name": "release",
"configurePreset": "release"
},
{
"name": "debug",
"configurePreset": "debug"
},
{
"name": "sanitize",
"configurePreset": "sanitize"
},
{
"name": "sandebug",
"configurePreset": "sandebug"
}
],
"testPresets": [
{
"name": "release",
"configurePreset": "release",
"output": {"outputOnFailure": true, "shortProgress": true}
},
{
"name": "debug",
"configurePreset": "debug",
"inherits": "release"
},
{
"name": "sanitize",
"configurePreset": "sanitize",
"inherits": "release"
},
{
"name": "sandebug",
"configurePreset": "sandebug",
"inherits": "release"
}
]
}

View File

@@ -1,148 +1,23 @@
# Lean 4 releases
This file contains release notes for each stable release.
Please check the [releases](https://github.com/leanprover/lean4/releases) page for the current status
of each version.
During development, drafts of future release notes appear in [`releases_drafts`](https://github.com/leanprover/lean4/tree/master/script).
We intend to provide regular "minor version" releases of the Lean language at approximately monthly intervals.
There is not yet a strong guarantee of backwards compatibility between versions,
only an expectation that breaking changes will be documented in this file.
This file contains work-in-progress notes for the upcoming release, as well as previous stable releases.
Please check the [releases](https://github.com/leanprover/lean4/releases) page for the current status
of each version.
v4.9.0 (development in progress)
v4.9.0
---------
v4.8.0
Development in progress.
v4.8.0
---------
* **Executables configured with `supportInterpreter := true` on Windows should now be run via `lake exe` to function properly.**
The way Lean is built on Windows has changed (see PR [#3601](https://github.com/leanprover/lean4/pull/3601)). As a result, Lake now dynamically links executables with `supportInterpreter := true` on Windows to `libleanshared.dll` and `libInit_shared.dll`. Therefore, such executables will not run unless those shared libraries are co-located with the executables or part of `PATH`. Running the executable via `lake exe` will ensure these libraries are part of `PATH`.
In a related change, the signature of the `nativeFacets` Lake configuration options has changed from a static `Array` to a function `(shouldExport : Bool) → Array`. See its docstring or Lake's [README](src/lake/README.md) for further details on the changed option.
* Lean now generates an error if the type of a theorem is **not** a proposition.
* Importing two different files containing proofs of the same theorem is no longer considered an error. This feature is particularly useful for theorems that are automatically generated on demand (e.g., equational theorems).
* Functional induction principles.
Derived from the definition of a (possibly mutually) recursive function, a **functional induction principle** is created that is tailored to proofs about that function.
For example from:
```
def ackermann : Nat → Nat → Nat
| 0, m => m + 1
| n+1, 0 => ackermann n 1
| n+1, m+1 => ackermann n (ackermann (n + 1) m)
```
we get
```
ackermann.induct (motive : Nat → Nat → Prop) (case1 : ∀ (m : Nat), motive 0 m)
(case2 : ∀ (n : Nat), motive n 1 → motive (Nat.succ n) 0)
(case3 : ∀ (n m : Nat), motive (n + 1) m → motive n (ackermann (n + 1) m) → motive (Nat.succ n) (Nat.succ m))
(x x : Nat) : motive x x
```
It can be used in the `induction` tactic using the `using` syntax:
```
induction n, m using ackermann.induct
```
* The termination checker now recognizes more recursion patterns without an
explicit `termination_by`. In particular the idiom of counting up to an upper
bound, as in
```
def Array.sum (arr : Array Nat) (i acc : Nat) : Nat :=
if _ : i < arr.size then
Array.sum arr (i+1) (acc + arr[i])
else
acc
```
is recognized without having to say `termination_by arr.size - i`.
* Shorter instances names. There is a new algorithm for generating names for anonymous instances.
Across Std and Mathlib, the median ratio between lengths of new names and of old names is about 72%.
With the old algorithm, the longest name was 1660 characters, and now the longest name is 202 characters.
The new algorithm's 95th percentile name length is 67 characters, versus 278 for the old algorithm.
While the new algorithm produces names that are 1.2% less unique,
it avoids cross-project collisions by adding a module-based suffix
when it does not refer to declarations from the same "project" (modules that share the same root).
PR [#3089](https://github.com/leanprover/lean4/pull/3089).
* Attribute `@[pp_using_anonymous_constructor]` to make structures pretty print like `⟨x, y, z⟩`
rather than `{a := x, b := y, c := z}`.
This attribute is applied to `Sigma`, `PSigma`, `PProd`, `Subtype`, `And`, and `Fin`.
* Now structure instances pretty print with parent structures' fields inlined.
That is, if `B` extends `A`, then `{ toA := { x := 1 }, y := 2 }` now pretty prints as `{ x := 1, y := 2 }`.
Setting option `pp.structureInstances.flatten` to false turns this off.
* Option `pp.structureProjections` is renamed to `pp.fieldNotation`, and there is now a suboption `pp.fieldNotation.generalized`
to enable pretty printing function applications using generalized field notation (defaults to true).
Field notation can be disabled on a function-by-function basis using the `@[pp_nodot]` attribute.
* Added options `pp.mvars` (default: true) and `pp.mvars.withType` (default: false).
When `pp.mvars` is false, expression metavariables pretty print as `?_` and universe metavariables pretty print as `_`.
When `pp.mvars.withType` is true, expression metavariables pretty print with a type ascription.
These can be set when using `#guard_msgs` to make tests not depend on the particular names of metavariables.
[#3798](https://github.com/leanprover/lean4/pull/3798) and
[#3978](https://github.com/leanprover/lean4/pull/3978).
* Hovers for terms in `match` expressions in the Infoview now reliably show the correct term.
* Added `@[induction_eliminator]` and `@[cases_eliminator]` attributes to be able to define custom eliminators
for the `induction` and `cases` tactics, replacing the `@[eliminator]` attribute.
Gives custom eliminators for `Nat` so that `induction` and `cases` put goal states into terms of `0` and `n + 1`
rather than `Nat.zero` and `Nat.succ n`.
Added option `tactic.customEliminators` to control whether to use custom eliminators.
Added a hack for `rcases`/`rintro`/`obtain` to use the custom eliminator for `Nat`.
[#3629](https://github.com/leanprover/lean4/pull/3629),
[#3655](https://github.com/leanprover/lean4/pull/3655), and
[#3747](https://github.com/leanprover/lean4/pull/3747).
* The `#guard_msgs` command now has options to change whitespace normalization and sensitivity to message ordering.
For example, `#guard_msgs (whitespace := lax) in cmd` collapses whitespace before checking messages,
and `#guard_msgs (ordering := sorted) in cmd` sorts the messages in lexicographic order before checking.
PR [#3883](https://github.com/leanprover/lean4/pull/3883).
* The `#guard_msgs` command now supports showing a diff between the expected and actual outputs. This feature is currently
disabled by default, but can be enabled with `set_option guard_msgs.diff true`. Depending on user feedback, this option
may default to `true` in a future version of Lean.
Breaking changes:
* Automatically generated equational theorems are now named using suffix `.eq_<idx>` instead of `._eq_<idx>`, and `.def` instead of `._unfold`. Example:
```
def fact : Nat → Nat
| 0 => 1
| n+1 => (n+1) * fact n
theorem ex : fact 0 = 1 := by unfold fact; decide
#check fact.eq_1
-- fact.eq_1 : fact 0 = 1
#check fact.eq_2
-- fact.eq_2 (n : Nat) : fact (Nat.succ n) = (n + 1) * fact n
#check fact.def
/-
fact.def :
∀ (x : Nat),
fact x =
match x with
| 0 => 1
| Nat.succ n => (n + 1) * fact n
-/
```
* The coercion from `String` to `Name` was removed. Previously, it was `Name.mkSimple`, which does not separate strings at dots, but experience showed that this is not always the desired coercion. For the previous behavior, manually insert a call to `Name.mkSimple`.
* The `Subarray` fields `as`, `h₁` and `h₂` have been renamed to `array`, `start_le_stop`, and `stop_le_array_size`, respectively. This more closely follows standard Lean conventions. Deprecated aliases for the field projections were added; these will be removed in a future release.
* The change to the instance name algorithm (described above) can break projects that made use of the auto-generated names.
* `Option.toMonad` has been renamed to `Option.getM` and the unneeded `[Monad m]` instance argument has been removed.
Release candidate, release notes will be copied from branch `releases/v4.8.0` once completed.
v4.7.0
---------

View File

@@ -1,4 +1,4 @@
open Std
open Batteries
open Lean
inductive BoolExpr where

View File

@@ -50,13 +50,13 @@ We'll use `v4.6.0` as the intended release version as a running example.
- Toolchain bump PR
- Create and push the tag
- Merge the tag into `stable`
- [Std](https://github.com/leanprover-community/std4)
- [Batteries](https://github.com/leanprover-community/batteries)
- No dependencies
- Toolchain bump PR
- Create and push the tag
- Merge the tag into `stable`
- [ProofWidgets4](https://github.com/leanprover-community/ProofWidgets4)
- Dependencies: `Std`
- Dependencies: `Batteries`
- Note on versions and branches:
- `ProofWidgets` uses a sequential version tagging scheme, e.g. `v0.0.29`,
which does not refer to the toolchain being used.
@@ -65,7 +65,7 @@ We'll use `v4.6.0` as the intended release version as a running example.
- Toolchain bump PR
- Create and push the tag, following the version convention of the repository
- [Aesop](https://github.com/leanprover-community/aesop)
- Dependencies: `Std`
- Dependencies: `Batteries`
- Toolchain bump PR including updated Lake manifest
- Create and push the tag
- Merge the tag into `stable`
@@ -79,7 +79,7 @@ We'll use `v4.6.0` as the intended release version as a running example.
- Create and push the tag
- There is no `stable` branch; skip this step
- [Mathlib](https://github.com/leanprover-community/mathlib4)
- Dependencies: `Aesop`, `ProofWidgets4`, `lean4checker`, `Std`, `doc-gen4`, `import-graph`
- Dependencies: `Aesop`, `ProofWidgets4`, `lean4checker`, `Batteries`, `doc-gen4`, `import-graph`
- Toolchain bump PR notes:
- In addition to updating the `lean-toolchain` and `lakefile.lean`,
in `.github/workflows/build.yml.in` in the `lean4checker` section update the line
@@ -123,8 +123,8 @@ We'll use `v4.7.0-rc1` as the intended release version in this example.
- Decide which nightly release you want to turn into a release candidate.
We will use `nightly-2024-02-29` in this example.
- It is essential that Std and Mathlib already have reviewed branches compatible with this nightly.
- Check that both Std and Mathlib's `bump/v4.7.0` branch contain `nightly-2024-02-29`
- It is essential that Batteries and Mathlib already have reviewed branches compatible with this nightly.
- Check that both Batteries and Mathlib's `bump/v4.7.0` branch contain `nightly-2024-02-29`
in their `lean-toolchain`.
- The steps required to reach that state are beyond the scope of this checklist, but see below!
- Create the release branch from this nightly tag:
@@ -182,7 +182,7 @@ We'll use `v4.7.0-rc1` as the intended release version in this example.
- We do this for the same list of repositories as for stable releases, see above.
As above, there are dependencies between these, and so the process above is iterative.
It greatly helps if you can merge the `bump/v4.7.0` PRs yourself!
- For Std/Aesop/Mathlib, which maintain a `nightly-testing` branch, make sure there is a tag
- For Batteries/Aesop/Mathlib, which maintain a `nightly-testing` branch, make sure there is a tag
`nightly-testing-2024-02-29` with date corresponding to the nightly used for the release
(create it if not), and then on the `nightly-testing` branch `git reset --hard master`, and force push.
- Make an announcement!
@@ -204,7 +204,7 @@ In particular, updating the downstream repositories is significantly more work
# Preparing `bump/v4.7.0` branches
While not part of the release process per se,
this is a brief summary of the work that goes into updating Std/Aesop/Mathlib to new versions.
this is a brief summary of the work that goes into updating Batteries/Aesop/Mathlib to new versions.
Please read https://leanprover-community.github.io/contribute/tags_and_branches.html

View File

@@ -1,4 +1,4 @@
#!/usr/bin/env bash
source ../../tests/common.sh
exec_check lean -j 0 -Dlinter.all=false "$f"
exec_check lean -Dlinter.all=false "$f"

View File

@@ -1,100 +0,0 @@
# -*- coding: utf-8 -*-
"""
pygments.lexers.theorem
~~~~~~~~~~~~~~~~~~~~~~~
Lexers for theorem-proving languages.
:copyright: Copyright 2006-2017 by the Pygments team, see AUTHORS.
:license: BSD, see LICENSE for details.
"""
import re
from pygments.lexer import RegexLexer, default, words
from pygments.token import Text, Comment, Operator, Keyword, Name, String, \
Number, Punctuation, Generic
__all__ = ['Lean4Lexer']
class Lean4Lexer(RegexLexer):
"""
For the `Lean 4 <https://github.com/leanprover/lean4>`_
theorem prover.
.. versionadded:: 2.0
"""
name = 'Lean4'
aliases = ['lean4']
filenames = ['*.lean']
mimetypes = ['text/x-lean']
flags = re.MULTILINE | re.UNICODE
keywords1 = (
'import', 'abbreviation', 'opaque_hint', 'tactic_hint', 'definition',
'renaming', 'inline', 'hiding', 'parameter', 'lemma', 'variable',
'theorem', 'axiom', 'inductive', 'structure', 'universe', 'alias',
'help', 'options', 'precedence', 'postfix', 'prefix',
'infix', 'infixl', 'infixr', 'notation', '#eval',
'#check', '#reduce', '#exit', 'coercion', 'end', 'private', 'using', 'namespace',
'including', 'instance', 'section', 'context', 'protected', 'expose',
'export', 'set_option', 'extends', 'open', 'example',
'constant', 'constants', 'print', 'opaque', 'reducible', 'irreducible',
'def', 'macro', 'elab', 'syntax', 'macro_rules', 'reduce', 'where',
'abbrev', 'noncomputable', 'class', 'attribute', 'synth', 'mutual',
)
keywords2 = (
'forall', 'fun', 'Pi', 'obtain', 'from', 'have', 'show', 'assume',
'take', 'let', 'if', 'else', 'then', 'by', 'in', 'with', 'begin',
'proof', 'qed', 'calc', 'match', 'nomatch', 'do', 'at',
)
keywords3 = (
# Sorts
'Type', 'Prop', 'Sort',
)
operators = (
u'!=', u'#', u'&', u'&&', u'*', u'+', u'-', u'/', u'@', u'!', u'`',
u'-.', u'->', u'.', u'..', u'...', u'::', u':>', u';', u';;', u'<',
u'<-', u'=', u'==', u'>', u'_', u'|', u'||', u'~', u'=>', u'<=', u'>=',
u'/\\', u'\\/', u'', u'Π', u'λ', u'', u'', u'', u'', u'', u'',
u'¬', u'⁻¹', u'', u'', u'', u'', u'', u'', u'', u'×', u'',
u'', u'', u'', u'',
)
punctuation = (u'(', u')', u':', u'{', u'}', u'[', u']', u'', u'',
u':=', u',')
tokens = {
'root': [
(r'\s+', Text),
(r'/-', Comment, 'comment'),
(r'--.*?$', Comment.Single),
(words(keywords1, prefix=r'\b', suffix=r'\b'), Keyword.Namespace),
(words(keywords2, prefix=r'\b', suffix=r'\b'), Keyword),
(words(keywords3, prefix=r'\b', suffix=r'\b'), Keyword.Type),
(words(operators), Name.Builtin.Pseudo),
(words(punctuation), Operator),
(u"[A-Za-z_\u03b1-\u03ba\u03bc-\u03fb\u1f00-\u1ffe\u2100-\u214f]"
u"[A-Za-z_'\u03b1-\u03ba\u03bc-\u03fb\u1f00-\u1ffe\u2070-\u2079"
u"\u207f-\u2089\u2090-\u209c\u2100-\u214f0-9]*", Name),
(r'\d+', Number.Integer),
(r'"', String.Double, 'string'),
(r'[~?][a-z][\w\']*:', Name.Variable)
],
'comment': [
# Multiline Comments
(r'[^/-]', Comment.Multiline),
(r'/-', Comment.Multiline, '#push'),
(r'-/', Comment.Multiline, '#pop'),
(r'[/-]', Comment.Multiline)
],
'string': [
(r'[^\\"]+', String.Double),
(r'\\[n"\\]', String.Escape),
('"', String.Double, '#pop'),
],
}

View File

@@ -1,3 +1,7 @@
These are instructions to set up a working development environment for those who wish to make changes to Lean itself. It is part of the [Development Guide](doc/dev/index.md).
We strongly suggest that new users instead follow the [Quickstart](doc/quickstart.md) to get started using Lean, since this sets up an environment that can automatically manage multiple Lean toolchain versions, which is necessary when working within the Lean ecosystem.
Requirements
------------
@@ -17,39 +21,27 @@ Platform-Specific Setup
Generic Build Instructions
--------------------------
Setting up a basic release build:
Setting up a basic parallelized release build:
```bash
git clone https://github.com/leanprover/lean4 --recurse-submodules
git clone https://github.com/leanprover/lean4
cd lean4
mkdir -p build/release
cd build/release
cmake ../..
make
cmake --preset release
make -C build/release -j$(nproc) # see below for macOS
```
For regular development, we recommend running
```bash
git config submodule.recurse true
```
in the checkout so that `--recurse-submodules` doesn't have to be
specified with `git pull/checkout/...`.
You can replace `$(nproc)`, which is not available on macOS and some alternative shells, with the desired parallelism amount.
The above commands will compile the Lean library and binaries into the
`stage1` subfolder; see below for details. Add `-j N` for an
appropriate `N` to `make` for a parallel build.
`stage1` subfolder; see below for details.
For example, on an AMD Ryzen 9 `make` takes 00:04:55, whereas `make -j 10`
takes 00:01:38. Your results may vary depending on the speed of your hard
drive.
You should not usually run `make install` after a successful build.
You should not usually run `cmake --install` after a successful build.
See [Dev setup using elan](../dev/index.md#dev-setup-using-elan) on how to properly set up your editor to use the correct stage depending on the source directory.
Useful CMake Configuration Settings
-----------------------------------
Pass these along with the `cmake ../..` command.
Pass these along with the `cmake --preset release` command.
There are also two alternative presets that combine some of these options you can use instead of `release`: `debug` and `sandebug` (sanitize + debug).
* `-D CMAKE_BUILD_TYPE=`\
Select the build type. Valid values are `RELEASE` (default), `DEBUG`,

View File

@@ -1,39 +0,0 @@
# Compiling Lean with Visual Studio
WARNING: Compiling Lean with Visual Studio doesn't currently work.
There's an ongoing effort to port Lean to Visual Studio.
The instructions below are for VS 2017.
In the meantime you can use [MSYS2](msys2.md) or [WSL](wsl.md).
## Installing dependencies
First, install `vcpkg` from https://github.com/Microsoft/vcpkg if you haven't
done so already.
Then, open a console in the directory you cloned `vcpkg` to, and type:
`vcpkg install mpir` for the 32-bit library or
`vcpkg install mpir:x64-windows` for the x64 one.
In Visual Studio, use the "open folder" feature and open the Lean directory.
Go to the `CMake->Change CMake Settings` menu. File `CMakeSettings.json` opens.
In each of the targets, add the following snippet (i.e., after every
`ctestCommandArgs`):
```json
"variables": [
{
"name": "CMAKE_TOOLCHAIN_FILE",
"value": "C:\\path\\to\\vcpkg\\scripts\\buildsystems\\vcpkg.cmake"
}
]
```
## Enable Intellisense
In Visual Studio, press Ctrl+Q and type `CppProperties.json` and press Enter.
Ensure `includePath` variables include `"${workspaceRoot}\\src"`.
## Build Lean
Press F7.

View File

@@ -38,10 +38,9 @@ cmake --version
Then follow the [generic build instructions](index.md) in the MSYS2
MinGW shell, using:
```
cmake ../.. -G "Unix Makefiles" -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++
cmake --preset release -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++
```
instead of `cmake ../..`. This ensures that cmake will call `sh` instead of `cmd.exe`
for script tasks and it will use the clang compiler instead of gcc, which is required.
instead of `cmake --preset release`. This will use the clang compiler instead of gcc, which is required with msys2.
## Install lean

View File

@@ -15,7 +15,7 @@ data type containing several important pieces of information. First and foremost
current player, and it has a random generator.
-/
open Std (HashMap)
open Batteries (HashMap)
abbrev TileIndex := Nat × Nat -- a 2D index
inductive TileState where

View File

@@ -6,6 +6,7 @@ Platforms built & tested by our CI, available as binary releases via elan (see b
* x86-64 Linux with glibc 2.27+
* x86-64 macOS 10.15+
* aarch64 (Apple Silicon) macOS 10.15+
* x86-64 Windows 10+
### Tier 2
@@ -16,7 +17,6 @@ Releases may be silently broken due to the lack of automated testing.
Issue reports and fixes are welcome.
* aarch64 Linux with glibc 2.27+
* aarch64 (Apple Silicon) macOS
* x86 (32-bit) Linux
* Emscripten Web Assembly

View File

@@ -43,7 +43,8 @@ $ pdflatex test.tex
## Example with `minted`
First [install Pygments](https://pygments.org/download/). Then save [`lean4.py`](https://raw.githubusercontent.com/leanprover/lean4/master/doc/latex/lean4.py), which contains an version of the Lean highlighter updated for Lean 4, and the following sample LaTeX file `test.tex` into the same directory:
First [install Pygments](https://pygments.org/download/) (version 2.18 or newer).
Then save the following sample LaTeX file `test.tex` into the same directory:
```latex
\documentclass{article}
@@ -51,9 +52,8 @@ First [install Pygments](https://pygments.org/download/). Then save [`lean4.py`]
% switch to a monospace font supporting more Unicode characters
\setmonofont{FreeMono}
\usepackage{minted}
% instruct minted to use our local theorem.py
\newmintinline[lean]{lean4.py:Lean4Lexer -x}{bgcolor=white}
\newminted[leancode]{lean4.py:Lean4Lexer -x}{fontsize=\footnotesize}
\newmintinline[lean]{lean4}{bgcolor=white}
\newminted[leancode]{lean4}{fontsize=\footnotesize}
\usemintedstyle{tango} % a nice, colorful theme
\begin{document}
@@ -67,9 +67,6 @@ theorem funext {f₁ f₂ : ∀ (x : α), β x} (h : ∀ x, f₁ x = f₂ x) : f
\end{document}
```
If your version of `minted` is v2.7 or newer, but before v3.0,
you will additionally need to follow the workaround described in https://github.com/gpoore/minted/issues/360.
You can then compile `test.tex` by executing the following command:
```bash
@@ -81,11 +78,14 @@ Some remarks:
- either `xelatex` or `lualatex` is required to handle Unicode characters in the code.
- `--shell-escape` is needed to allow `xelatex` to execute `pygmentize` in a shell.
- If the chosen monospace font is missing some Unicode symbols, you can direct them to be displayed using a fallback font or other replacement LaTeX code.
``` latex
\usepackage{newunicodechar}
\newfontfamily{\freeserif}{DejaVu Sans}
\newunicodechar{✝}{\freeserif{✝}}
\newunicodechar{𝓞}{\ensuremath{\mathcal{O}}}
```
- minted has a "helpful" feature that draws red boxes around characters the chosen lexer doesn't recognize.
Since the Lean lexer cannot encompass all user-defined syntax, it is advisable to [work around](https://tex.stackexchange.com/a/343506/14563) this feature.
``` latex
\usepackage{newunicodechar}
\newfontfamily{\freeserif}{DejaVu Sans}
\newunicodechar{✝}{\freeserif{✝}}
\newunicodechar{𝓞}{\ensuremath{\mathcal{O}}}
```
- If you are using an old version of Pygments, you can copy
[`lean.py`](https://raw.githubusercontent.com/pygments/pygments/master/pygments/lexers/lean.py) into your working directory,
and use `lean4.py:Lean4Lexer -x` instead of `lean4` above.
If your version of `minted` is v2.7 or newer, but before v3.0,
you will additionally need to follow the workaround described in https://github.com/gpoore/minted/issues/360.

View File

@@ -170,7 +170,7 @@ rec {
ln -sf ${lean-all}/* .
'';
buildPhase = ''
ctest --output-junit test-results.xml --output-on-failure -E 'leancomptest_(doc_example|foreign)' -j$NIX_BUILD_CORES
ctest --output-junit test-results.xml --output-on-failure -E 'leancomptest_(doc_example|foreign)|leanlaketest_init' -j$NIX_BUILD_CORES
'';
installPhase = ''
mkdir $out

22
releases_drafts/README.md Normal file
View File

@@ -0,0 +1,22 @@
Draft release notes
-------------------
This folder contains drafts of release notes for inclusion in `RELEASES.md`.
During the process to create a release candidate, we look through all the commits that make up the release
to prepare the release notes, and in that process we take these drafts into account.
Guidelines:
- You should prefer adding release notes to commit messages over adding anything to this folder.
A release note should briefly explain the impact of a change from a user's point of view.
Please mark these parts out with words such as **release notes** and/or **breaking changes**.
- It is not necessary to add anything to this folder. It is meant for larger features that span multiple PRs,
or for anything that would be helpful when preparing the release notes that might be missed
by someone reading through the change log.
- If the PR that adds a feature simultaneously adds a draft release note, including the PR number is not required
since it can be obtained from the git history for the file.
When release notes are prepared, all the draft release notes are deleted from this folder.
For release candidates beyond the first one, you can either update `RELEASE.md` directly
or continue to add drafts.
When a release is finalized, we will copy the completed release notes from `RELEASE.md` to the `master` branch.

View File

@@ -0,0 +1,13 @@
* The `MessageData.ofPPFormat` constructor has been removed.
Its functionality has been split into two:
- for lazy structured messages, please use `MessageData.lazy`;
- for embedding `Format` or `FormatWithInfos`, use `MessageData.ofFormatWithInfos`.
An example migration can be found in [#3929](https://github.com/leanprover/lean4/pull/3929/files#diff-5910592ab7452a0e1b2616c62d22202d2291a9ebb463145f198685aed6299867L109).
* The `MessageData.ofFormat` constructor has been turned into a function.
If you need to inspect `MessageData`,
you can pattern-match on `MessageData.ofFormatWithInfos`.
part of #3929

12
releases_drafts/wf.md Normal file
View File

@@ -0,0 +1,12 @@
Functions defined by well-founded recursion are now marked as
`@[irreducible]`, which should prevent expensive and often unfruitful
unfolding of such definitions.
Existing proofs that hold by definitional equality (e.g. `rfl`) can be
rewritten to explictly unfold the function definition (using `simp`,
`unfold`, `rw`), or the recursive function can be temporariliy made
semireducible (using `unseal f in` before the command) or the function
definition itself can be marked as `@[semireducible]` to get the previous
behavor.
#4061

180
script/github-issues-fro.py Executable file
View File

@@ -0,0 +1,180 @@
#!/usr/bin/env python3
import subprocess
import sys
import json
from datetime import datetime, timedelta
from urllib.parse import urlencode
import argparse
import calendar
import time
import statistics
# Reminder: Ensure you have `gh` CLI installed and authorized before running this script.
# Follow instructions from https://cli.github.com/ to set up `gh` and ensure it is authorized.
LABELS = ["bug", "feature", "RFC", "new-user-papercuts", "Lake"]
def get_items(query):
items = []
page = 1
base_url = 'https://api.github.com/search/issues'
retries = 0
max_retries = 5
while True:
params = {'q': query, 'per_page': 100, 'page': page}
url = f"{base_url}?{urlencode(params)}"
# print(f"Fetching page {page} from URL: {url}")
try:
result = subprocess.run(['gh', 'api', url], capture_output=True, text=True)
data = json.loads(result.stdout)
if 'items' in data:
items.extend(data['items'])
elif 'message' in data and 'rate limit' in data['message'].lower():
if retries < max_retries:
wait_time = (2 ** retries) * 60 # Exponential backoff
time.sleep(wait_time)
retries += 1
continue
else:
print("Max retries exceeded. Exiting.")
break
else:
print(f"Error fetching data: {data}")
break
if len(data['items']) < 100:
break
page += 1
except Exception as e:
print(f"Error fetching data: {e}")
print(result.stdout) # Print the JSON output for debugging
break
return items
def get_fro_team_members():
try:
result = subprocess.run(['gh', 'api', '-H', 'Accept: application/vnd.github.v3+json', '/orgs/leanprover/teams/fro/members'], capture_output=True, text=True)
members = json.loads(result.stdout)
return [member['login'] for member in members]
except Exception as e:
print(f"Error fetching team members: {e}")
return []
def calculate_average_time_to_close(closed_items):
times_to_close = [(datetime.strptime(item['closed_at'], '%Y-%m-%dT%H:%M:%SZ') - datetime.strptime(item['created_at'], '%Y-%m-%dT%H:%M:%SZ')).days for item in closed_items]
average_time_to_close = sum(times_to_close) / len(times_to_close) if times_to_close else 0
return average_time_to_close
def parse_dates(date_args):
if len(date_args) == 2:
start_date = date_args[0]
end_date = date_args[1]
elif len(date_args) == 1:
if len(date_args[0]) == 7: # YYYY-MM format
year, month = map(int, date_args[0].split('-'))
start_date = f"{year}-{month:02d}-01"
end_date = f"{year}-{month:02d}-{calendar.monthrange(year, month)[1]}"
elif len(date_args[0]) == 4: # YYYY format
year = int(date_args[0])
start_date = f"{year}-07-01"
end_date = f"{year+1}-06-30"
elif len(date_args[0]) == 6 and date_args[0][4] == 'Q': # YYYYQn format
year = int(date_args[0][:4])
quarter = int(date_args[0][5])
if quarter == 1:
start_date = f"{year}-01-01"
end_date = f"{year}-03-31"
elif quarter == 2:
start_date = f"{year}-04-01"
end_date = f"{year}-06-30"
elif quarter == 3:
start_date = f"{year}-07-01"
end_date = f"{year}-09-30"
elif quarter == 4:
start_date = f"{year}-10-01"
end_date = f"{year}-12-31"
else:
raise ValueError("Invalid quarter format")
else:
raise ValueError("Invalid date format")
else:
raise ValueError("Invalid number of date arguments")
return start_date, end_date
def split_date_range(start_date, end_date):
start = datetime.strptime(start_date, '%Y-%m-%d')
end = datetime.strptime(end_date, '%Y-%m-%d')
date_ranges = []
# Splitting into month-long windows to work around the GitHub search 1000 result limit.
while start <= end:
month_end = start + timedelta(days=calendar.monthrange(start.year, start.month)[1] - start.day)
month_end = min(month_end, end)
date_ranges.append((start.strftime('%Y-%m-%d'), month_end.strftime('%Y-%m-%d')))
start = month_end + timedelta(days=1)
return date_ranges
def main():
parser = argparse.ArgumentParser(description="Fetch and count GitHub issues assigned to fro team members between two dates.")
parser.add_argument("dates", type=str, nargs='+', help="Start and end dates in YYYY-MM-DD, YYYY-MM, YYYY-Qn, or YYYY format")
args = parser.parse_args()
start_date, end_date = parse_dates(args.dates)
repo = "leanprover/lean4"
date_ranges = split_date_range(start_date, end_date)
fro_members = get_fro_team_members()
fro_members.append("unassigned") # Add "unassigned" for issues with no assignee
label_headers = ", ".join([f"MTTR ({label})" for label in LABELS])
print(f"# username, open issues, new issues, closed issues, MTTR (all), {label_headers}")
for member in fro_members:
open_issues_count = 0
new_issues_count = 0
closed_issues_count = 0
total_time_to_close_issues = 0
closed_issues = []
label_times = {label: [] for label in LABELS}
for start, end in date_ranges:
if member == "unassigned":
open_issues_query1 = f'repo:{repo} is:issue no:assignee state:open created:<={end}'
open_issues_query2 = f'repo:{repo} is:issue no:assignee state:closed created:<={end} closed:>{end}'
new_issues_query = f'repo:{repo} is:issue no:assignee created:{start}..{end}'
closed_issues_query = f'repo:{repo} is:issue no:assignee closed:{start}..{end}'
else:
open_issues_query1 = f'repo:{repo} is:issue assignee:{member} state:open created:<={end}'
open_issues_query2 = f'repo:{repo} is:issue assignee:{member} state:closed created:<={end} closed:>{end}'
new_issues_query = f'repo:{repo} is:issue assignee:{member} created:{start}..{end}'
closed_issues_query = f'repo:{repo} is:issue assignee:{member} closed:{start}..{end}'
open_issues1 = get_items(open_issues_query1)
open_issues2 = get_items(open_issues_query2)
new_issues = get_items(new_issues_query)
closed_issues_period = get_items(closed_issues_query)
open_issues_count = len(open_issues1) + len(open_issues2)
new_issues_count += len(new_issues)
closed_issues_count += len(closed_issues_period)
closed_issues.extend(closed_issues_period)
for issue in closed_issues_period:
time_to_close = (datetime.strptime(issue['closed_at'], '%Y-%m-%dT%H:%M:%SZ') - datetime.strptime(issue['created_at'], '%Y-%m-%dT%H:%M:%SZ')).days
total_time_to_close_issues += time_to_close
for label in LABELS:
if label in [l['name'] for l in issue['labels']]:
label_times[label].append(time_to_close)
average_time_to_close_issues = total_time_to_close_issues / closed_issues_count if closed_issues_count else 0
label_averages = {label: (sum(times) / len(times)) if times else 0 for label, times in label_times.items()}
label_averages_str = ", ".join([f"{label_averages[label]:.2f}" for label in LABELS])
print(f"{member},{open_issues_count},{new_issues_count},{closed_issues_count},{average_time_to_close_issues:.2f},{label_averages_str}")
if __name__ == "__main__":
main()

163
script/github-metrics.py Executable file
View File

@@ -0,0 +1,163 @@
#!/usr/bin/env python3
import subprocess
import sys
import json
from datetime import datetime, timedelta
from urllib.parse import urlencode
import argparse
import calendar
import time
# Reminder: Ensure you have `gh` CLI installed and authorized before running this script.
# Follow instructions from https://cli.github.com/ to set up `gh` and ensure it is authorized.
def get_items(query):
items = []
page = 1
base_url = 'https://api.github.com/search/issues'
retries = 0
max_retries = 5
while True:
params = {'q': query, 'per_page': 100, 'page': page}
url = f"{base_url}?{urlencode(params)}"
# print(f"Fetching page {page} from URL: {url}")
try:
result = subprocess.run(['gh', 'api', url], capture_output=True, text=True)
data = json.loads(result.stdout)
if 'items' in data:
items.extend(data['items'])
elif 'message' in data and 'rate limit' in data['message'].lower():
if retries < max_retries:
wait_time = (2 ** retries) * 60 # Exponential backoff
print(f"Rate limit exceeded. Retrying in {wait_time} seconds...")
time.sleep(wait_time)
retries += 1
continue
else:
print("Max retries exceeded. Exiting.")
break
else:
print(f"Error fetching data: {data}")
break
if len(data['items']) < 100:
break
page += 1
except Exception as e:
print(f"Error fetching data: {e}")
print(result.stdout) # Print the JSON output for debugging
break
return items
def calculate_average_time_to_close(closed_items):
times_to_close = [(datetime.strptime(item['closed_at'], '%Y-%m-%dT%H:%M:%SZ') - datetime.strptime(item['created_at'], '%Y-%m-%dT%H:%M:%SZ')).days for item in closed_items]
average_time_to_close = sum(times_to_close) / len(times_to_close) if times_to_close else 0
return average_time_to_close
def parse_dates(date_args):
if len(date_args) == 2:
start_date = date_args[0]
end_date = date_args[1]
elif len(date_args) == 1:
if len(date_args[0]) == 7: # YYYY-MM format
year, month = map(int, date_args[0].split('-'))
start_date = f"{year}-{month:02d}-01"
end_date = f"{year}-{month:02d}-{calendar.monthrange(year, month)[1]}"
elif len(date_args[0]) == 4: # YYYY format
year = int(date_args[0])
start_date = f"{year}-07-01"
end_date = f"{year+1}-06-30"
elif len(date_args[0]) == 6 and date_args[0][4] == 'Q': # YYYYQn format
year = int(date_args[0][:4])
quarter = int(date_args[0][5])
if quarter == 1:
start_date = f"{year}-01-01"
end_date = f"{year}-03-31"
elif quarter == 2:
start_date = f"{year}-04-01"
end_date = f"{year}-06-30"
elif quarter == 3:
start_date = f"{year}-07-01"
end_date = f"{year}-09-30"
elif quarter == 4:
start_date = f"{year}-10-01"
end_date = f"{year}-12-31"
else:
raise ValueError("Invalid quarter format")
else:
raise ValueError("Invalid date format")
else:
raise ValueError("Invalid number of date arguments")
return start_date, end_date
def split_date_range(start_date, end_date):
start = datetime.strptime(start_date, '%Y-%m-%d')
end = datetime.strptime(end_date, '%Y-%m-%d')
date_ranges = []
# Splitting into month-long windows to work around the GitHub search 1000 result limit.
while start <= end:
month_end = start + timedelta(days=calendar.monthrange(start.year, start.month)[1] - start.day)
month_end = min(month_end, end)
date_ranges.append((start.strftime('%Y-%m-%d'), month_end.strftime('%Y-%m-%d')))
start = month_end + timedelta(days=1)
return date_ranges
def main():
parser = argparse.ArgumentParser(description="Fetch and count GitHub issues and pull requests between two dates.")
parser.add_argument("dates", type=str, nargs='+', help="Start and end dates in YYYY-MM-DD, YYYY-MM, YYYY-Qn, or YYYY format")
args = parser.parse_args()
start_date, end_date = parse_dates(args.dates)
repo = "leanprover/lean4"
date_ranges = split_date_range(start_date, end_date)
open_issues_count = 0
opened_issues_count = 0
closed_issues_count = 0
total_time_to_close_issues = 0
open_prs_count = 0
closed_but_not_merged_prs_count = 0
merged_prs_count = 0
for start, end in date_ranges:
open_issues_query1 = f'repo:{repo} is:issue state:open created:<={end}'
open_issues_query2 = f'repo:{repo} is:issue state:closed created:<={end} closed:>{end}'
opened_issues_query = f'repo:{repo} is:issue created:{start}..{end}'
closed_issues_query = f'repo:{repo} is:issue closed:{start}..{end}'
open_prs_query1 = f'repo:{repo} is:pr state:open created:<={end}'
open_prs_query2 = f'repo:{repo} is:pr state:closed created:<={end} closed:>{end}'
closed_but_not_merged_prs_query = f'repo:{repo} is:pr state:closed is:unmerged closed:{start}..{end}'
merged_prs_query = f'repo:{repo} is:pr is:merged closed:{start}..{end}'
open_issues1 = get_items(open_issues_query1)
open_issues2 = get_items(open_issues_query2)
opened_issues = get_items(opened_issues_query)
closed_issues = get_items(closed_issues_query)
open_prs1 = get_items(open_prs_query1)
open_prs2 = get_items(open_prs_query2)
closed_but_not_merged_prs = get_items(closed_but_not_merged_prs_query)
merged_prs = get_items(merged_prs_query)
open_issues_count = len(open_issues1) + len(open_issues2)
opened_issues_count += len(opened_issues)
closed_issues_count += len(closed_issues)
total_time_to_close_issues += sum((datetime.strptime(item['closed_at'], '%Y-%m-%dT%H:%M:%SZ') - datetime.strptime(item['created_at'], '%Y-%m-%dT%H:%M:%SZ')).days for item in closed_issues)
open_prs_count = len(open_prs1) + len(open_prs2)
closed_but_not_merged_prs_count += len(closed_but_not_merged_prs)
merged_prs_count += len(merged_prs)
average_time_to_close_issues = total_time_to_close_issues / closed_issues_count if closed_issues_count else 0
print("# open issues, opened issues, closed issues, average age of closed issues, open PRs, closed PRs, merged PRs")
print(f"{open_issues_count},{opened_issues_count},{closed_issues_count},{average_time_to_close_issues:.2f},{open_prs_count},{closed_but_not_merged_prs_count},{merged_prs_count}")
if __name__ == "__main__":
main()

View File

@@ -34,3 +34,4 @@ import Init.BinderPredicates
import Init.Ext
import Init.Omega
import Init.MacroTrace
import Init.Grind

View File

@@ -943,7 +943,7 @@ theorem anyM_loop_iff_exists (p : α → Bool) (as : Array α) (start stop) (h :
omega
termination_by stop - start
-- This could also be proved from `SatisfiesM_anyM_iff_exists` in `Std.Data.Array.Init.Monadic`
-- This could also be proved from `SatisfiesM_anyM_iff_exists` in `Batteries.Data.Array.Init.Monadic`
theorem any_iff_exists (p : α Bool) (as : Array α) (start stop) :
any as p start stop i : Fin as.size, start i.1 i.1 < stop p as[i] := by
dsimp [any, anyM, Id.run]

View File

@@ -15,13 +15,13 @@ structure Subarray (α : Type u) where
start_le_stop : start stop
stop_le_array_size : stop array.size
@[deprecated Subarray.array]
@[deprecated Subarray.array (since := "2024-04-13")]
abbrev Subarray.as (s : Subarray α) : Array α := s.array
@[deprecated Subarray.start_le_stop]
@[deprecated Subarray.start_le_stop (since := "2024-04-13")]
theorem Subarray.h₁ (s : Subarray α) : s.start s.stop := s.start_le_stop
@[deprecated Subarray.stop_le_array_size]
@[deprecated Subarray.stop_le_array_size (since := "2024-04-13")]
theorem Subarray.h₂ (s : Subarray α) : s.stop s.array.size := s.stop_le_array_size
namespace Subarray

View File

@@ -34,7 +34,8 @@ structure BitVec (w : Nat) where
O(1), because we use `Fin` as the internal representation of a bitvector. -/
toFin : Fin (2^w)
@[deprecated] protected abbrev Std.BitVec := _root_.BitVec
@[deprecated (since := "2024-04-12")]
protected abbrev Std.BitVec := _root_.BitVec
-- We manually derive the `DecidableEq` instances for `BitVec` because
-- we want to have builtin support for bit-vector literals, and we
@@ -73,7 +74,7 @@ protected def toNat (a : BitVec n) : Nat := a.toFin.val
/-- Return the bound in terms of toNat. -/
theorem isLt (x : BitVec w) : x.toNat < 2^w := x.toFin.isLt
@[deprecated isLt]
@[deprecated isLt (since := "2024-03-12")]
theorem toNat_lt (x : BitVec n) : x.toNat < 2^n := x.isLt
/-- Theorem for normalizing the bit vector literal representation. -/
@@ -533,6 +534,11 @@ def sshiftRight (a : BitVec n) (s : Nat) : BitVec n := .ofInt n (a.toInt >>> s)
instance {n} : HShiftLeft (BitVec m) (BitVec n) (BitVec m) := fun x y => x <<< y.toNat
instance {n} : HShiftRight (BitVec m) (BitVec n) (BitVec m) := fun x y => x >>> y.toNat
/-- Auxiliary function for `rotateLeft`, which does not take into account the case where
the rotation amount is greater than the bitvector width. -/
def rotateLeftAux (x : BitVec w) (n : Nat) : BitVec w :=
x <<< n ||| x >>> (w - n)
/--
Rotate left for bit vectors. All the bits of `x` are shifted to higher positions, with the top `n`
bits wrapping around to fill the low bits.
@@ -542,7 +548,15 @@ rotateLeft 0b0011#4 3 = 0b1001
```
SMT-Lib name: `rotate_left` except this operator uses a `Nat` shift amount.
-/
def rotateLeft (x : BitVec w) (n : Nat) : BitVec w := x <<< n ||| x >>> (w - n)
def rotateLeft (x : BitVec w) (n : Nat) : BitVec w := rotateLeftAux x (n % w)
/--
Auxiliary function for `rotateRight`, which does not take into account the case where
the rotation amount is greater than the bitvector width.
-/
def rotateRightAux (x : BitVec w) (n : Nat) : BitVec w :=
x >>> n ||| x <<< (w - n)
/--
Rotate right for bit vectors. All the bits of `x` are shifted to lower positions, with the
@@ -553,7 +567,7 @@ rotateRight 0b01001#5 1 = 0b10100
```
SMT-Lib name: `rotate_right` except this operator uses a `Nat` shift amount.
-/
def rotateRight (x : BitVec w) (n : Nat) : BitVec w := x >>> n ||| x <<< (w - n)
def rotateRight (x : BitVec w) (n : Nat) : BitVec w := rotateRightAux x (n % w)
/--
Concatenation of bitvectors. This uses the "big endian" convention that the more significant

View File

@@ -184,4 +184,55 @@ theorem bit_neg_eq_neg (x : BitVec w) : -x = (adc (((iunfoldr (fun (i : Fin w) c
simp [ sub_toAdd, BitVec.sub_add_cancel]
· simp [bit_not_testBit x _]
/-! ### Inequalities (le / lt) -/
theorem ult_eq_not_carry (x y : BitVec w) : x.ult y = !carry w x (~~~y) true := by
simp only [BitVec.ult, carry, toNat_mod_cancel, toNat_not, toNat_true, ge_iff_le, decide_not,
Nat.not_le, decide_eq_decide]
rw [Nat.mod_eq_of_lt (by omega)]
omega
theorem ule_eq_not_ult (x y : BitVec w) : x.ule y = !y.ult x := by
simp [BitVec.ule, BitVec.ult, decide_not]
theorem ule_eq_carry (x y : BitVec w) : x.ule y = carry w y (~~~x) true := by
simp [ule_eq_not_ult, ult_eq_not_carry]
/-- If two bitvectors have the same `msb`, then signed and unsigned comparisons coincide -/
theorem slt_eq_ult_of_msb_eq {x y : BitVec w} (h : x.msb = y.msb) :
x.slt y = x.ult y := by
simp only [BitVec.slt, toInt_eq_msb_cond, BitVec.ult, decide_eq_decide, h]
cases y.msb <;> simp
/-- If two bitvectors have different `msb`s, then unsigned comparison is determined by this bit -/
theorem ult_eq_msb_of_msb_neq {x y : BitVec w} (h : x.msb y.msb) :
x.ult y = y.msb := by
simp only [BitVec.ult, msb_eq_decide, ne_eq, decide_eq_decide] at *
omega
/-- If two bitvectors have different `msb`s, then signed and unsigned comparisons are opposites -/
theorem slt_eq_not_ult_of_msb_neq {x y : BitVec w} (h : x.msb y.msb) :
x.slt y = !x.ult y := by
simp only [BitVec.slt, toInt_eq_msb_cond, Bool.eq_not_of_ne h, ult_eq_msb_of_msb_neq h]
cases y.msb <;> (simp; omega)
theorem slt_eq_ult (x y : BitVec w) :
x.slt y = (x.msb != y.msb).xor (x.ult y) := by
by_cases h : x.msb = y.msb
· simp [h, slt_eq_ult_of_msb_eq]
· have h' : x.msb != y.msb := by simp_all
simp [slt_eq_not_ult_of_msb_neq h, h']
theorem slt_eq_not_carry (x y : BitVec w) :
x.slt y = (x.msb == y.msb).xor (carry w x (~~~y) true) := by
simp only [slt_eq_ult, bne, ult_eq_not_carry]
cases x.msb == y.msb <;> simp
theorem sle_eq_not_slt (x y : BitVec w) : x.sle y = !y.slt x := by
simp only [BitVec.sle, BitVec.slt, decide_not, decide_eq_decide]; omega
theorem sle_eq_carry (x y : BitVec w) :
x.sle y = !((x.msb == y.msb).xor (carry w y (~~~x) true)) := by
rw [sle_eq_not_slt, slt_eq_not_carry, beq_comm]
end BitVec

View File

@@ -9,6 +9,7 @@ import Init.Data.Bool
import Init.Data.BitVec.Basic
import Init.Data.Fin.Lemmas
import Init.Data.Nat.Lemmas
import Init.Data.Nat.Mod
namespace BitVec
@@ -146,7 +147,8 @@ theorem getLsb_ofNat (n : Nat) (x : Nat) (i : Nat) :
getLsb (x#n) i = (i < n && x.testBit i) := by
simp [getLsb, BitVec.ofNat, Fin.val_ofNat']
@[simp, deprecated toNat_ofNat] theorem toNat_zero (n : Nat) : (0#n).toNat = 0 := by trivial
@[simp, deprecated toNat_ofNat (since := "2024-02-22")]
theorem toNat_zero (n : Nat) : (0#n).toNat = 0 := by trivial
@[simp] theorem getLsb_zero : (0#w).getLsb i = false := by simp [getLsb]
@@ -173,8 +175,7 @@ theorem msb_eq_getLsb_last (x : BitVec w) :
x.getLsb (w-1) = decide (2 ^ (w-1) x.toNat) := by
rcases w with rfl | w
· simp
· simp only [Nat.zero_lt_succ, decide_True, getLsb, Nat.testBit, Nat.succ_sub_succ_eq_sub,
Nat.sub_zero, Nat.and_one_is_mod, Bool.true_and, Nat.shiftRight_eq_div_pow]
· simp only [getLsb, Nat.testBit_to_div_mod, Nat.succ_sub_succ_eq_sub, Nat.sub_zero]
rcases (Nat.lt_or_ge (BitVec.toNat x) (2 ^ w)) with h | h
· simp [Nat.div_eq_of_lt h, h]
· simp only [h]
@@ -221,9 +222,21 @@ theorem toInt_eq_toNat_cond (i : BitVec n) :
if 2*i.toNat < 2^n then
(i.toNat : Int)
else
(i.toNat : Int) - (2^n : Nat) := by
unfold BitVec.toInt
split <;> omega
(i.toNat : Int) - (2^n : Nat) :=
rfl
theorem msb_eq_false_iff_two_mul_lt (x : BitVec w) : x.msb = false 2 * x.toNat < 2^w := by
cases w <;> simp [Nat.pow_succ, Nat.mul_comm _ 2, msb_eq_decide]
theorem msb_eq_true_iff_two_mul_ge (x : BitVec w) : x.msb = true 2 * x.toNat 2^w := by
simp [ Bool.ne_false_iff, msb_eq_false_iff_two_mul_lt]
/-- Characterize `x.toInt` in terms of `x.msb`. -/
theorem toInt_eq_msb_cond (x : BitVec w) :
x.toInt = if x.msb then (x.toNat : Int) - (2^w : Nat) else (x.toNat : Int) := by
simp only [BitVec.toInt, msb_eq_false_iff_two_mul_lt]
cases x.msb <;> rfl
theorem toInt_eq_toNat_bmod (x : BitVec n) : x.toInt = Int.bmod x.toNat (2^n) := by
simp only [toInt_eq_toNat_cond]
@@ -245,6 +258,12 @@ theorem eq_of_toInt_eq {i j : BitVec n} : i.toInt = j.toInt → i = j := by
have _jlt := j.isLt
split <;> split <;> omega
theorem toInt_inj (x y : BitVec n) : x.toInt = y.toInt x = y :=
Iff.intro eq_of_toInt_eq (congrArg BitVec.toInt)
theorem toInt_ne (x y : BitVec n) : x.toInt y.toInt x y := by
rw [Ne, toInt_inj]
@[simp] theorem toNat_ofInt {n : Nat} (i : Int) :
(BitVec.ofInt n i).toNat = (i % (2^n : Nat)).toNat := by
unfold BitVec.ofInt
@@ -602,6 +621,17 @@ theorem shiftLeftZeroExtend_eq {x : BitVec w} :
(shiftLeftZeroExtend x i).msb = x.msb := by
simp [shiftLeftZeroExtend_eq, BitVec.msb]
theorem shiftLeft_shiftLeft {w : Nat} (x : BitVec w) (n m : Nat) :
(x <<< n) <<< m = x <<< (n + m) := by
ext i
simp only [getLsb_shiftLeft, Fin.is_lt, decide_True, Bool.true_and]
rw [show i - (n + m) = (i - m - n) by omega]
cases h₂ : decide (i < m) <;>
cases h₃ : decide (i - m < w) <;>
cases h₄ : decide (i - m < n) <;>
cases h₅ : decide (i < n + m) <;>
simp at * <;> omega
/-! ### ushiftRight -/
@[simp, bv_toNat] theorem toNat_ushiftRight (x : BitVec n) (i : Nat) :
@@ -687,6 +717,11 @@ theorem msb_append {x : BitVec w} {y : BitVec v} :
simp only [getLsb_append, cond_eq_if]
split <;> simp [*]
theorem shiftRight_shiftRight {w : Nat} (x : BitVec w) (n m : Nat) :
(x >>> n) >>> m = x >>> (n + m) := by
ext i
simp [Nat.add_assoc n m i]
/-! ### rev -/
theorem getLsb_rev (x : BitVec w) (i : Fin w) :
@@ -919,6 +954,13 @@ theorem negOne_eq_allOnes : -1#w = allOnes w := by
have r : (2^w - 1) < 2^w := by omega
simp [Nat.mod_eq_of_lt q, Nat.mod_eq_of_lt r]
theorem neg_eq_not_add (x : BitVec w) : -x = ~~~x + 1 := by
apply eq_of_toNat_eq
simp only [toNat_neg, ofNat_eq_ofNat, toNat_add, toNat_not, toNat_ofNat, Nat.add_mod_mod]
congr
have hx : x.toNat < 2^w := x.isLt
rw [Nat.sub_sub, Nat.add_comm 1 x.toNat, Nat.sub_sub, Nat.sub_add_cancel (by omega)]
/-! ### mul -/
theorem mul_def {n} {x y : BitVec n} : x * y = (ofFin <| x.toFin * y.toFin) := by rfl
@@ -1013,4 +1055,32 @@ theorem toNat_intMax_eq : (intMax w).toNat = 2^w - 1 := by
(ofBoolListLE bs).getMsb i = (decide (i < bs.length) && bs.getD (bs.length - 1 - i) false) := by
simp [getMsb_eq_getLsb]
/-! # Rotate Left -/
/-- rotateLeft is invariant under `mod` by the bitwidth. -/
@[simp]
theorem rotateLeft_mod_eq_rotateLeft {x : BitVec w} {r : Nat} :
x.rotateLeft (r % w) = x.rotateLeft r := by
simp only [rotateLeft, Nat.mod_mod]
/-- `rotateLeft` equals the bit fiddling definition of `rotateLeftAux` when the rotation amount is
smaller than the bitwidth. -/
theorem rotateLeft_eq_rotateLeftAux_of_lt {x : BitVec w} {r : Nat} (hr : r < w) :
x.rotateLeft r = x.rotateLeftAux r := by
simp only [rotateLeft, Nat.mod_eq_of_lt hr]
/-! ## Rotate Right -/
/-- `rotateRight` equals the bit fiddling definition of `rotateRightAux` when the rotation amount is
smaller than the bitwidth. -/
theorem rotateRight_eq_rotateRightAux_of_lt {x : BitVec w} {r : Nat} (hr : r < w) :
x.rotateRight r = x.rotateRightAux r := by
simp only [rotateRight, Nat.mod_eq_of_lt hr]
/-- rotateRight is invariant under `mod` by the bitwidth. -/
@[simp]
theorem rotateRight_mod_eq_rotateRight {x : BitVec w} {r : Nat} :
x.rotateRight (r % w) = x.rotateRight r := by
simp only [rotateRight, Nat.mod_mod]
end BitVec

View File

@@ -227,6 +227,8 @@ instance : Std.Associative (· != ·) := ⟨bne_assoc⟩
@[simp] theorem bne_left_inj : (x y z : Bool), (x != y) = (x != z) y = z := by decide
@[simp] theorem bne_right_inj : (x y z : Bool), (x != z) = (y != z) x = y := by decide
theorem eq_not_of_ne : {x y : Bool}, x y x = !y := by decide
/-! ### coercision related normal forms -/
theorem beq_eq_decide_eq [BEq α] [LawfulBEq α] [DecidableEq α] (a b : α) :
@@ -360,7 +362,8 @@ def toNat (b:Bool) : Nat := cond b 1 0
theorem toNat_le (c : Bool) : c.toNat 1 := by
cases c <;> trivial
@[deprecated toNat_le] abbrev toNat_le_one := toNat_le
@[deprecated toNat_le (since := "2024-02-23")]
abbrev toNat_le_one := toNat_le
theorem toNat_lt (b : Bool) : b.toNat < 2 :=
Nat.lt_succ_of_le (toNat_le _)

View File

@@ -5,3 +5,4 @@ Authors: Leonardo de Moura
-/
prelude
import Init.Data.Char.Basic
import Init.Data.Char.Lemmas

View File

@@ -0,0 +1,25 @@
/-
Copyright (c) 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Leonardo de Moura
-/
prelude
import Init.Data.Char.Basic
import Init.Data.UInt.Lemmas
namespace Char
theorem le_def {a b : Char} : a b a.1 b.1 := .rfl
theorem lt_def {a b : Char} : a < b a.1 < b.1 := .rfl
theorem lt_iff_val_lt_val {a b : Char} : a < b a.val < b.val := Iff.rfl
@[simp] protected theorem not_le {a b : Char} : ¬ a b b < a := UInt32.not_le
@[simp] protected theorem not_lt {a b : Char} : ¬ a < b b a := UInt32.not_lt
@[simp] protected theorem le_refl (a : Char) : a a := by simp [le_def]
@[simp] protected theorem lt_irrefl (a : Char) : ¬ a < a := by simp
protected theorem le_trans {a b c : Char} : a b b c a c := UInt32.le_trans
protected theorem lt_trans {a b c : Char} : a < b b < c a < c := UInt32.lt_trans
protected theorem le_total (a b : Char) : a b b a := UInt32.le_total a.1 b.1
protected theorem lt_asymm {a b : Char} (h : a < b) : ¬ b < a := UInt32.lt_asymm h
protected theorem ne_of_lt {a b : Char} (h : a < b) : a b := Char.ne_of_val_ne (UInt32.ne_of_lt h)
end Char

View File

@@ -1,7 +1,7 @@
/-
Copyright (c) 2022 Mario Carneiro. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Mario Carneiro
Authors: Mario Carneiro, Leonardo de Moura
-/
prelude
import Init.Data.Fin.Basic
@@ -11,6 +11,9 @@ import Init.ByCases
import Init.Conv
import Init.Omega
-- Remove after the next stage0 update
set_option allowUnsafeReducibility true
namespace Fin
/-- If you actually have an element of `Fin n`, then the `n` is always positive -/
@@ -59,7 +62,8 @@ theorem mk_val (i : Fin n) : (⟨i, i.isLt⟩ : Fin n) = i := Fin.eta ..
@[simp] theorem val_ofNat' (a : Nat) (is_pos : n > 0) :
(Fin.ofNat' a is_pos).val = a % n := rfl
@[deprecated ofNat'_zero_val] theorem ofNat'_zero_val : (Fin.ofNat' 0 h).val = 0 := Nat.zero_mod _
@[deprecated ofNat'_zero_val (since := "2024-02-22")]
theorem ofNat'_zero_val : (Fin.ofNat' 0 h).val = 0 := Nat.zero_mod _
@[simp] theorem mod_val (a b : Fin n) : (a % b).val = a.val % b.val :=
rfl
@@ -90,6 +94,18 @@ theorem lt_iff_val_lt_val {a b : Fin n} : a < b ↔ a.val < b.val := Iff.rfl
@[simp] protected theorem not_lt {a b : Fin n} : ¬ a < b b a := Nat.not_lt
@[simp] protected theorem le_refl (a : Fin n) : a a := by simp [le_def]
@[simp] protected theorem lt_irrefl (a : Fin n) : ¬ a < a := by simp
protected theorem le_trans {a b c : Fin n} : a b b c a c := Nat.le_trans
protected theorem lt_trans {a b c : Fin n} : a < b b < c a < c := Nat.lt_trans
protected theorem le_total (a b : Fin n) : a b b a := Nat.le_total a b
protected theorem lt_asymm {a b : Fin n} (h : a < b) : ¬ b < a := Nat.lt_asymm h
protected theorem ne_of_lt {a b : Fin n} (h : a < b) : a b := Fin.ne_of_val_ne (Nat.ne_of_lt h)
protected theorem ne_of_gt {a b : Fin n} (h : a < b) : b a := Fin.ne_of_val_ne (Nat.ne_of_gt h)
@@ -819,27 +835,3 @@ protected theorem zero_mul (k : Fin (n + 1)) : (0 : Fin (n + 1)) * k = 0 := by
simp [ext_iff, mul_def]
end Fin
namespace USize
@[simp] theorem lt_def {a b : USize} : a < b a.toNat < b.toNat := .rfl
@[simp] theorem le_def {a b : USize} : a b a.toNat b.toNat := .rfl
@[simp] theorem zero_toNat : (0 : USize).toNat = 0 := Nat.zero_mod _
@[simp] theorem mod_toNat (a b : USize) : (a % b).toNat = a.toNat % b.toNat :=
Fin.mod_val ..
@[simp] theorem div_toNat (a b : USize) : (a / b).toNat = a.toNat / b.toNat :=
Fin.div_val ..
@[simp] theorem modn_toNat (a : USize) (b : Nat) : (a.modn b).toNat = a.toNat % b :=
Fin.modn_val ..
theorem mod_lt (a b : USize) (h : 0 < b) : a % b < b := USize.modn_lt _ (by simp at h; exact h)
theorem toNat.inj : {a b : USize}, a.toNat = b.toNat a = b
| _, _, _, _, rfl => rfl
end USize

View File

@@ -14,6 +14,8 @@ import Init.RCases
# Lemmas about integer division needed to bootstrap `omega`.
-/
-- Remove after the next stage0 update
set_option allowUnsafeReducibility true
open Nat (succ)
@@ -142,12 +144,14 @@ theorem eq_one_of_mul_eq_one_left {a b : Int} (H : 0 ≤ b) (H' : a * b = 1) : b
| ofNat _ => show ofNat _ = _ by simp
| -[_+1] => show -ofNat _ = _ by simp
unseal Nat.div in
@[simp] protected theorem div_zero : a : Int, div a 0 = 0
| ofNat _ => show ofNat _ = _ by simp
| -[_+1] => rfl
@[simp] theorem zero_fdiv (b : Int) : fdiv 0 b = 0 := by cases b <;> rfl
unseal Nat.div in
@[simp] protected theorem fdiv_zero : a : Int, fdiv a 0 = 0
| 0 => rfl
| succ _ => rfl
@@ -178,7 +182,7 @@ theorem fdiv_eq_div {a b : Int} (Ha : 0 ≤ a) (Hb : 0 ≤ b) : fdiv a b = div a
@[simp] theorem mod_zero : a : Int, mod a 0 = a
| ofNat _ => congrArg ofNat <| Nat.mod_zero _
| -[_+1] => rfl
| -[_+1] => congrArg (fun n => -ofNat n) <| Nat.mod_zero _
@[simp] theorem zero_fmod (b : Int) : fmod 0 b = 0 := by cases b <;> rfl
@@ -225,7 +229,9 @@ theorem mod_add_div : ∀ a b : Int, mod a b + b * (a.div b) = a
| ofNat m, -[n+1] => by
show (m % succ n + -(succ n) * -(m / succ n) : Int) = m
rw [Int.neg_mul_neg]; exact congrArg ofNat (Nat.mod_add_div ..)
| -[_+1], 0 => rfl
| -[m+1], 0 => by
show -(((succ m) % 0) : Int) + 0 * -(succ m / 0) = -(succ m)
rw [Nat.mod_zero, Int.zero_mul, Int.add_zero]
| -[m+1], ofNat n => by
show -(((succ m) % n) : Int) + n * -(succ m / n) = -(succ m)
rw [Int.mul_neg, Int.neg_add]
@@ -763,11 +769,13 @@ theorem ediv_eq_ediv_of_mul_eq_mul {a b c d : Int}
| (n:Nat) => congrArg ofNat (Nat.div_one _)
| -[n+1] => by simp [Int.div, neg_ofNat_succ]; rfl
unseal Nat.div in
@[simp] protected theorem div_neg : a b : Int, a.div (-b) = -(a.div b)
| ofNat m, 0 => show ofNat (m / 0) = -(m / 0) by rw [Nat.div_zero]; rfl
| ofNat m, -[n+1] | -[m+1], succ n => (Int.neg_neg _).symm
| ofNat m, succ n | -[m+1], 0 | -[m+1], -[n+1] => rfl
unseal Nat.div in
@[simp] protected theorem neg_div : a b : Int, (-a).div b = -(a.div b)
| 0, n => by simp [Int.neg_zero]
| succ m, (n:Nat) | -[m+1], 0 | -[m+1], -[n+1] => rfl
@@ -936,6 +944,7 @@ theorem fdiv_nonneg {a b : Int} (Ha : 0 ≤ a) (Hb : 0 ≤ b) : 0 ≤ a.fdiv b :
match a, b, eq_ofNat_of_zero_le Ha, eq_ofNat_of_zero_le Hb with
| _, _, _, rfl, _, rfl => ofNat_fdiv .. ofNat_zero_le _
unseal Nat.div in
theorem fdiv_nonpos : {a b : Int}, 0 a b 0 a.fdiv b 0
| 0, 0, _, _ | 0, -[_+1], _, _ | succ _, 0, _, _ | succ _, -[_+1], _, _ => _

View File

@@ -96,7 +96,7 @@ protected theorem le_antisymm {a b : Int} (h₁ : a ≤ b) (h₂ : b ≤ a) : a
have := Int.ofNat.inj <| Int.add_left_cancel <| this.trans (Int.add_zero _).symm
rw [ hn, Nat.eq_zero_of_add_eq_zero_left this, ofNat_zero, Int.add_zero a]
protected theorem lt_irrefl (a : Int) : ¬a < a := fun H =>
@[simp] protected theorem lt_irrefl (a : Int) : ¬a < a := fun H =>
let n, hn := lt.dest H
have : (a+Nat.succ n) = a+0 := by
rw [hn, Int.add_zero]
@@ -813,6 +813,20 @@ protected theorem sub_lt_sub_right {a b : Int} (h : a < b) (c : Int) : a - c < b
protected theorem sub_lt_sub {a b c d : Int} (hab : a < b) (hcd : c < d) : a - d < b - c :=
Int.add_lt_add hab (Int.neg_lt_neg hcd)
protected theorem lt_of_sub_lt_sub_left {a b c : Int} (h : c - a < c - b) : b < a :=
Int.lt_of_neg_lt_neg <| Int.lt_of_add_lt_add_left h
protected theorem lt_of_sub_lt_sub_right {a b c : Int} (h : a - c < b - c) : a < b :=
Int.lt_of_add_lt_add_right h
@[simp] protected theorem sub_lt_sub_left_iff (a b c : Int) :
c - a < c - b b < a :=
Int.lt_of_sub_lt_sub_left, (Int.sub_lt_sub_left · c)
@[simp] protected theorem sub_lt_sub_right_iff (a b c : Int) :
a - c < b - c a < b :=
Int.lt_of_sub_lt_sub_right, (Int.sub_lt_sub_right · c)
protected theorem sub_lt_sub_of_le_of_lt {a b c d : Int}
(hab : a b) (hcd : c < d) : a - d < b - c :=
Int.add_lt_add_of_le_of_lt hab (Int.neg_lt_neg hcd)

View File

@@ -16,13 +16,6 @@ namespace List
open Nat
/-!
# Bootstrapping theorems for lists
These are theorems used in the definitions of `Std.Data.List.Basic` and tactics.
New theorems should be added to `Std.Data.List.Lemmas` if they are not needed by the bootstrap.
-/
attribute [simp] concat_eq_append append_assoc
@[simp] theorem get?_nil : @get? α [] n = none := rfl
@@ -784,7 +777,6 @@ theorem exists_cons_of_length_succ :
{l : List α}, l.length = n + 1 h t, l = h :: t
| _::_, _ => _, _, rfl
@[simp]
theorem length_pos {l : List α} : 0 < length l l [] :=
Nat.pos_iff_ne_zero.trans (not_congr length_eq_zero)

View File

@@ -78,6 +78,8 @@ of a number.
-/
/-- `testBit m n` returns whether the `(n+1)` least significant bit is `1` or `0`-/
def testBit (m n : Nat) : Bool := (m >>> n) &&& 1 != 0
def testBit (m n : Nat) : Bool :=
-- `1 &&& n` is faster than `n &&& 1` for big `n`.
1 &&& (m >>> n) != 0
end Nat

View File

@@ -50,13 +50,23 @@ noncomputable def div2Induction {motive : Nat → Sort u}
apply hyp
exact Nat.div_lt_self n_pos (Nat.le_refl _)
@[simp] theorem zero_and (x : Nat) : 0 &&& x = 0 := by rfl
@[simp] theorem zero_and (x : Nat) : 0 &&& x = 0 := by
simp only [HAnd.hAnd, AndOp.and, land]
unfold bitwise
simp
@[simp] theorem and_zero (x : Nat) : x &&& 0 = 0 := by
simp only [HAnd.hAnd, AndOp.and, land]
unfold bitwise
simp
@[simp] theorem one_and_eq_mod_two (n : Nat) : 1 &&& n = n % 2 := by
if n0 : n = 0 then
subst n0; decide
else
simp only [HAnd.hAnd, AndOp.and, land]
cases mod_two_eq_zero_or_one n with | _ h => simp [bitwise, n0, h]
@[simp] theorem and_one_is_mod (x : Nat) : x &&& 1 = x % 2 := by
if xz : x = 0 then
simp [xz, zero_and]
@@ -71,7 +81,7 @@ noncomputable def div2Induction {motive : Nat → Sort u}
/-! ### testBit -/
@[simp] theorem zero_testBit (i : Nat) : testBit 0 i = false := by
simp only [testBit, zero_shiftRight, zero_and, bne_self_eq_false]
simp only [testBit, zero_shiftRight, and_zero, bne_self_eq_false]
@[simp] theorem testBit_zero (x : Nat) : testBit x 0 = decide (x % 2 = 1) := by
cases mod_two_eq_zero_or_one x with | _ p => simp [testBit, p]
@@ -188,8 +198,6 @@ theorem lt_pow_two_of_testBit (x : Nat) (p : ∀i, i ≥ n → testBit x i = fal
have test_false := p _ i_ge_n
simp only [test_true] at test_false
/-! ### testBit -/
private theorem succ_mod_two : succ x % 2 = 1 - x % 2 := by
induction x with
| zero =>
@@ -233,7 +241,7 @@ theorem testBit_two_pow_add_gt {i j : Nat} (j_lt_i : j < i) (x : Nat) :
rw [Nat.sub_eq_zero_iff_le] at i_sub_j_eq
exact Nat.not_le_of_gt j_lt_i i_sub_j_eq
| d+1 =>
simp [Nat.pow_succ, Nat.mul_comm _ 2, Nat.mul_add_mod]
simp [Nat.pow_succ, Nat.mul_comm _ 2, Nat.mul_add_mod]
@[simp] theorem testBit_mod_two_pow (x j i : Nat) :
testBit (x % 2^j) i = (decide (i < j) && testBit x i) := by
@@ -257,7 +265,7 @@ theorem testBit_two_pow_add_gt {i j : Nat} (j_lt_i : j < i) (x : Nat) :
exact Nat.lt_add_of_pos_right (Nat.two_pow_pos j)
simp only [hyp y y_lt_x]
if i_lt_j : i < j then
rw [ Nat.add_comm _ (2^_), testBit_two_pow_add_gt i_lt_j]
rw [Nat.add_comm _ (2^_), testBit_two_pow_add_gt i_lt_j]
else
simp [i_lt_j]
@@ -402,12 +410,12 @@ theorem and_pow_two_identity {x : Nat} (lt : x < 2^n) : x &&& 2^n-1 = x := by
/-! ### lor -/
@[simp] theorem or_zero (x : Nat) : 0 ||| x = x := by
@[simp] theorem zero_or (x : Nat) : 0 ||| x = x := by
simp only [HOr.hOr, OrOp.or, lor]
unfold bitwise
simp [@eq_comm _ 0]
@[simp] theorem zero_or (x : Nat) : x ||| 0 = x := by
@[simp] theorem or_zero (x : Nat) : x ||| 0 = x := by
simp only [HOr.hOr, OrOp.or, lor]
unfold bitwise
simp [@eq_comm _ 0]

View File

@@ -82,22 +82,34 @@ decreasing_by apply div_rec_lemma; assumption
@[extern "lean_nat_mod"]
protected def mod : @& Nat @& Nat Nat
/- This case is not needed mathematically as the case below is equal to it; however, it makes
`0 % n = 0` true definitionally rather than just propositionally.
This property is desirable for `Fin n`, as it means `(ofNat 0 : Fin n).val = 0` by definition.
Primarily, this is valuable because mathlib in Lean3 assumed this was true definitionally, and so
keeping this definitional equality makes mathlib easier to port to mathlib4. -/
/-
Nat.modCore is defined by well-founded recursion and thus irreducible. Nevertheless it is
desireable if trivial `Nat.mod` calculations, namely
* `Nat.mod 0 m` for all `m`
* `Nat.mod n (m+n)` for concrete literals `n`
reduce definitionally.
This property is desirable for `Fin n` literals, as it means `(ofNat 0 : Fin n).val = 0` by
definition.
-/
| 0, _ => 0
| x@(_ + 1), y => Nat.modCore x y
| n@(_ + 1), m =>
if m n -- NB: if n < m does not reduce as well as `m ≤ n`!
then Nat.modCore n m
else n
instance instMod : Mod Nat := Nat.mod
protected theorem modCore_eq_mod (x y : Nat) : Nat.modCore x y = x % y := by
cases x with
| zero =>
protected theorem modCore_eq_mod (n m : Nat) : Nat.modCore n m = n % m := by
show Nat.modCore n m = Nat.mod n m
match n, m with
| 0, _ =>
rw [Nat.modCore]
exact if_neg fun hlt, hle => Nat.lt_irrefl _ (Nat.lt_of_lt_of_le hlt hle)
| succ x => rfl
| (_ + 1), _ =>
rw [Nat.mod]; dsimp
refine iteInduction (fun _ => rfl) (fun h => ?false) -- cannot use `split` this early yet
rw [Nat.modCore]
exact if_neg fun _hlt, hle => h hle
theorem mod_eq (x y : Nat) : x % y = if 0 < y y x then (x - y) % y else x := by
rw [Nat.modCore_eq_mod, Nat.modCore_eq_mod, Nat.modCore]

View File

@@ -37,11 +37,11 @@ def gcd (m n : @& Nat) : Nat :=
termination_by m
decreasing_by simp_wf; apply mod_lt _ (zero_lt_of_ne_zero _); assumption
@[simp] theorem gcd_zero_left (y : Nat) : gcd 0 y = y :=
rfl
@[simp] theorem gcd_zero_left (y : Nat) : gcd 0 y = y := by
rw [gcd]; rfl
theorem gcd_succ (x y : Nat) : gcd (succ x) y = gcd (y % succ x) (succ x) :=
rfl
theorem gcd_succ (x y : Nat) : gcd (succ x) y = gcd (y % succ x) (succ x) := by
rw [gcd]; rfl
@[simp] theorem gcd_one_left (n : Nat) : gcd 1 n = 1 := by
rw [gcd_succ, mod_one]
@@ -64,7 +64,7 @@ instance : Std.IdempotentOp gcd := ⟨gcd_self⟩
theorem gcd_rec (m n : Nat) : gcd m n = gcd (n % m) m :=
match m with
| 0 => by have := (mod_zero n).symm; rwa [gcd_zero_right]
| 0 => by have := (mod_zero n).symm; rwa [gcd, gcd_zero_right]
| _ + 1 => by simp [gcd_succ]
@[elab_as_elim] theorem gcd.induction {P : Nat Nat Prop} (m n : Nat)

View File

@@ -137,14 +137,14 @@ protected theorem sub_le_iff_le_add' {a b c : Nat} : a - b ≤ c ↔ a ≤ b + c
protected theorem le_sub_iff_add_le {n : Nat} (h : k m) : n m - k n + k m :=
Nat.add_le_of_le_sub h, Nat.le_sub_of_add_le
@[deprecated Nat.le_sub_iff_add_le]
@[deprecated Nat.le_sub_iff_add_le (since := "2024-02-19")]
protected theorem add_le_to_le_sub (n : Nat) (h : m k) : n + m k n k - m :=
(Nat.le_sub_iff_add_le h).symm
protected theorem add_le_of_le_sub' {n k m : Nat} (h : m k) : n k - m m + n k :=
Nat.add_comm .. Nat.add_le_of_le_sub h
@[deprecated Nat.add_le_of_le_sub']
@[deprecated Nat.add_le_of_le_sub' (since := "2024-02-19")]
protected theorem add_le_of_le_sub_left {n k m : Nat} (h : m k) : n k - m m + n k :=
Nat.add_le_of_le_sub' h
@@ -401,11 +401,11 @@ protected theorem mul_min_mul_left (a b c : Nat) : min (a * b) (a * c) = a * min
/-! ### mul -/
@[deprecated Nat.mul_le_mul_left]
@[deprecated Nat.mul_le_mul_left (since := "2024-02-19")]
protected theorem mul_le_mul_of_nonneg_left {a b c : Nat} : a b c * a c * b :=
Nat.mul_le_mul_left c
@[deprecated Nat.mul_le_mul_right]
@[deprecated Nat.mul_le_mul_right (since := "2024-02-19")]
protected theorem mul_le_mul_of_nonneg_right {a b c : Nat} : a b a * c b * c :=
Nat.mul_le_mul_right c
@@ -478,6 +478,7 @@ protected theorem mul_lt_mul_of_lt_of_lt {a b c d : Nat} (hac : a < c) (hbd : b
theorem succ_mul_succ (a b) : succ a * succ b = a * b + a + b + 1 := by
rw [succ_mul, mul_succ]; rfl
theorem mul_le_add_right (m k n : Nat) : k * m m + n (k-1) * m n := by
match k with
| 0 =>
@@ -677,6 +678,10 @@ protected theorem pow_lt_pow_iff_right {a n m : Nat} (h : 1 < a) :
/-! ### log2 -/
@[simp]
theorem log2_zero : Nat.log2 0 = 0 := by
simp [Nat.log2]
theorem le_log2 (h : n 0) : k n.log2 2 ^ k n := by
match k with
| 0 => simp [show 1 n from Nat.pos_of_ne_zero h]
@@ -697,7 +702,7 @@ theorem log2_self_le (h : n ≠ 0) : 2 ^ n.log2 ≤ n := (le_log2 h).1 (Nat.le_r
theorem lt_log2_self : n < 2 ^ (n.log2 + 1) :=
match n with
| 0 => Nat.zero_lt_two
| 0 => by simp
| n+1 => (log2_lt n.succ_ne_zero).1 (Nat.le_refl _)
/-! ### dvd -/
@@ -789,6 +794,9 @@ theorem shiftLeft_shiftLeft (m n : Nat) : ∀ k, (m <<< n) <<< k = m <<< (n + k)
| 0 => rfl
| k + 1 => by simp [ Nat.add_assoc, shiftLeft_shiftLeft _ _ k, shiftLeft_succ]
@[simp] theorem shiftLeft_shiftRight (x n : Nat) : x <<< n >>> n = x := by
rw [Nat.shiftLeft_eq, Nat.shiftRight_eq_div_pow, Nat.mul_div_cancel _ (Nat.two_pow_pos _)]
theorem mul_add_div {m : Nat} (m_pos : m > 0) (x y : Nat) : (m * x + y) / m = x + y / m := by
match x with
| 0 => simp

View File

@@ -714,4 +714,10 @@ theorem Expr.eq_of_toNormPoly_eq (ctx : Context) (e e' : Expr) (h : e.toNormPoly
simp [Expr.toNormPoly, Poly.norm] at h
assumption
end Nat.Linear
end Linear
def elimOffset {α : Sort u} (a b k : Nat) (h₁ : a + k = b + k) (h₂ : a = b α) : α := by
simp_arith at h₁
exact h₂ h₁
end Nat

View File

@@ -18,8 +18,8 @@ def getM [Alternative m] : Option α → m α
| none => failure
| some a => pure a
@[deprecated getM] def toMonad [Monad m] [Alternative m] : Option α m α :=
getM
@[deprecated getM (since := "2024-04-17")]
def toMonad [Monad m] [Alternative m] : Option α m α := getM
/-- Returns `true` on `some x` and `false` on `none`. -/
@[inline] def isSome : Option α Bool

View File

@@ -6,3 +6,4 @@ Authors: Leonardo de Moura
prelude
import Init.Data.String.Basic
import Init.Data.String.Extra
import Init.Data.String.Lemmas

View File

@@ -24,23 +24,59 @@ instance : LT String :=
instance decLt (s₁ s₂ : @& String) : Decidable (s₁ < s₂) :=
List.hasDecidableLt s₁.data s₂.data
@[reducible] protected def le (a b : String) : Prop := ¬ b < a
instance : LE String :=
String.le
instance decLE (s₁ s₂ : String) : Decidable (s₁ s₂) :=
inferInstanceAs (Decidable (Not _))
/--
Returns the length of a string in Unicode code points.
Examples:
* `"".length = 0`
* `"abc".length = 3`
* `"L∃∀N".length = 4`
-/
@[extern "lean_string_length"]
def length : (@& String) Nat
| s => s.length
/-- The internal implementation uses dynamic arrays and will perform destructive updates
if the String is not shared. -/
/--
Pushes a character onto the end of a string.
The internal implementation uses dynamic arrays and will perform destructive updates
if the string is not shared.
Example: `"abc".push 'd' = "abcd"`
-/
@[extern "lean_string_push"]
def push : String Char String
| s, c => s ++ [c]
/-- The internal implementation uses dynamic arrays and will perform destructive updates
if the String is not shared. -/
/--
Appends two strings.
The internal implementation uses dynamic arrays and will perform destructive updates
if the string is not shared.
Example: `"abc".append "def" = "abcdef"`
-/
@[extern "lean_string_append"]
def append : String (@& String) String
| a, b => a ++ b
/-- O(n) in the runtime, where n is the length of the String -/
/--
Converts a string to a list of characters.
Even though the logical model of strings is as a structure that wraps a list of characters,
this operation takes time and space linear in the length of the string, because the compiler
uses an optimized representation as dynamic arrays.
Example: `"abc".toList = ['a', 'b', 'c']`
-/
def toList (s : String) : List Char :=
s.data
@@ -59,9 +95,17 @@ def utf8GetAux : List Char → Pos → Pos → Char
| c::cs, i, p => if i = p then c else utf8GetAux cs (i + c) p
/--
Return character at position `p`. If `p` is not a valid position
returns `(default : Char)`.
See `utf8GetAux` for the reference implementation.
Returns the character at position `p` of a string. If `p` is not a valid position,
returns `(default : Char)`.
See `utf8GetAux` for the reference implementation.
Examples:
* `"abc".get ⟨1⟩ = 'b'`
* `"abc".get ⟨3⟩ = (default : Char) = 'A'`
Positions can also be invalid if a byte index points into the middle of a multi-byte UTF-8
character. For example,`"L∃∀N".get ⟨2⟩ = (default : Char) = 'A'`.
-/
@[extern "lean_string_utf8_get"]
def get (s : @& String) (p : @& Pos) : Char :=
@@ -72,12 +116,30 @@ def utf8GetAux? : List Char → Pos → Pos → Option Char
| [], _, _ => none
| c::cs, i, p => if i = p then c else utf8GetAux? cs (i + c) p
/--
Returns the character at position `p`. If `p` is not a valid position, returns `none`.
Examples:
* `"abc".get? ⟨1⟩ = some 'b'`
* `"abc".get? ⟨3⟩ = none`
Positions can also be invalid if a byte index points into the middle of a multi-byte UTF-8
character. For example, `"L∃∀N".get? ⟨2⟩ = none`
-/
@[extern "lean_string_utf8_get_opt"]
def get? : (@& String) (@& Pos) Option Char
| s, p => utf8GetAux? s 0 p
/--
Similar to `get`, but produces a panic error message if `p` is not a valid `String.Pos`.
Returns the character at position `p` of a string. If `p` is not a valid position,
returns `(default : Char)` and produces a panic error message.
Examples:
* `"abc".get! ⟨1⟩ = 'b'`
* `"abc".get! ⟨3⟩` panics
Positions can also be invalid if a byte index points into the middle of a multi-byte UTF-8 character. For example,
`"L∃∀N".get! ⟨2⟩` panics.
-/
@[extern "lean_string_utf8_get_bang"]
def get! (s : @& String) (p : @& Pos) : Char :=
@@ -89,13 +151,49 @@ def utf8SetAux (c' : Char) : List Char → Pos → Pos → List Char
| c::cs, i, p =>
if i = p then (c'::cs) else c::(utf8SetAux c' cs (i + c) p)
/--
Replaces the character at a specified position in a string with a new character. If the position
is invalid, the string is returned unchanged.
If both the replacement character and the replaced character are ASCII characters and the string
is not shared, destructive updates are used.
Examples:
* `"abc".set ⟨1⟩ 'B' = "aBc"`
* `"abc".set ⟨3⟩ 'D' = "abc"`
* `"L∃∀N".set ⟨4⟩ 'X' = "L∃XN"`
Because `'∃'` is a multi-byte character, the byte index `2` in `L∃∀N` is an invalid position,
so `"L∃∀N".set ⟨2⟩ 'X' = "L∃∀N"`.
-/
@[extern "lean_string_utf8_set"]
def set : String (@& Pos) Char String
| s, i, c => utf8SetAux c s 0 i
/--
Replaces the character at position `p` in the string `s` with the result of applying `f` to that character.
If `p` is an invalid position, the string is returned unchanged.
Examples:
* `abc.modify ⟨1⟩ Char.toUpper = "aBc"`
* `abc.modify ⟨3⟩ Char.toUpper = "abc"`
-/
def modify (s : String) (i : Pos) (f : Char Char) : String :=
s.set i <| f <| s.get i
/--
Returns the next position in a string after position `p`. If `p` is not a valid position or `p = s.endPos`,
the result is unspecified.
Examples:
Given `def abc := "abc"` and `def lean := "L∃∀N"`,
* `abc.get (0 |> abc.next) = 'b'`
* `lean.get (0 |> lean.next |> lean.next) = '∀'`
Cases where the result is unspecified:
* `"abc".next ⟨3⟩`, since `3 = s.endPos`
* `"L∃∀N".next ⟨2⟩`, since `2` points into the middle of a multi-byte UTF-8 character
-/
@[extern "lean_string_utf8_next"]
def next (s : @& String) (p : @& Pos) : Pos :=
let c := get s p
@@ -107,16 +205,52 @@ def utf8PrevAux : List Char → Pos → Pos → Pos
let i' := i + c
if i' = p then i else utf8PrevAux cs i' p
/--
Returns the position in a string before a specified position, `p`. If `p = ⟨0⟩`, returns `0`.
If `p` is not a valid position, the result is unspecified.
Examples:
Given `def abc := "abc"` and `def lean := "L∃∀N"`,
* `abc.get (abc.endPos |> abc.prev) = 'c'`
* `lean.get (lean.endPos |> lean.prev |> lean.prev |> lean.prev) = '∃'`
* `"L∃∀N".prev ⟨3⟩` is unspecified, since byte 3 occurs in the middle of the multi-byte character `'∃'`.
-/
@[extern "lean_string_utf8_prev"]
def prev : (@& String) (@& Pos) Pos
| s, p => if p = 0 then 0 else utf8PrevAux s 0 p
/--
Returns the first character in `s`. If `s = ""`, returns `(default : Char)`.
Examples:
* `"abc".front = 'a'`
* `"".front = (default : Char)`
-/
def front (s : String) : Char :=
get s 0
/--
Returns the last character in `s`. If `s = ""`, returns `(default : Char)`.
Examples:
* `"abc".back = 'c'`
* `"".back = (default : Char)`
-/
def back (s : String) : Char :=
get s (prev s s.endPos)
/--
Returns `true` if a specified position is greater than or equal to the position which
points to the end of a string. Otherwise, returns `false`.
Examples:
Given `def abc := "abc"` and `def lean := "L∃∀N"`,
* `(0 |> abc.next |> abc.next |> abc.atEnd) = false`
* `(0 |> abc.next |> abc.next |> abc.next |> abc.next |> abc.atEnd) = true`
* `(0 |> lean.next |> lean.next |> lean.next |> lean.next |> lean.atEnd) = true`
Because `"L∃∀N"` contains multi-byte characters, `lean.next (lean.next 0)` is not equal to `abc.next (abc.next 0)`.
-/
@[extern "lean_string_utf8_at_end"]
def atEnd : (@& String) (@& Pos) Bool
| s, p => p.byteIdx utf8ByteSize s
@@ -594,13 +728,15 @@ def substrEq (s1 : String) (off1 : String.Pos) (s2 : String) (off2 : String.Pos)
off1.byteIdx + sz s1.endPos.byteIdx && off2.byteIdx + sz s2.endPos.byteIdx && loop off1 off2 { byteIdx := off1.byteIdx + sz }
where
loop (off1 off2 stop1 : Pos) :=
if h : off1.byteIdx < stop1.byteIdx then
if _h : off1.byteIdx < stop1.byteIdx then
let c₁ := s1.get off1
let c₂ := s2.get off2
have := Nat.sub_lt_sub_left h (Nat.add_lt_add_left (one_le_csize c₁) off1.1)
c₁ == c₂ && loop (off1 + c₁) (off2 + c₂) stop1
else true
termination_by stop1.1 - off1.1
decreasing_by
have := Nat.sub_lt_sub_left _h (Nat.add_lt_add_left (one_le_csize c₁) off1.1)
decreasing_tactic
/-- Return true iff `p` is a prefix of `s` -/
def isPrefixOf (p : String) (s : String) : Bool :=
@@ -815,6 +951,10 @@ def beq (ss1 ss2 : Substring) : Bool :=
instance hasBeq : BEq Substring := beq
/-- Checks whether two substrings have the same position and content. -/
def sameAs (ss1 ss2 : Substring) : Bool :=
ss1.startPos == ss2.startPos && ss1 == ss2
end Substring
namespace String

View File

@@ -198,4 +198,35 @@ def removeLeadingSpaces (s : String) : String :=
let n := findLeadingSpacesSize s
if n == 0 then s else removeNumLeadingSpaces n s
/--
Replaces each `\r\n` with `\n` to normalize line endings,
but does not validate that there are no isolated `\r` characters.
It is an optimized version of `String.replace text "\r\n" "\n"`.
-/
def crlfToLf (text : String) : String :=
go "" 0 0
where
go (acc : String) (accStop pos : String.Pos) : String :=
if h : text.atEnd pos then
-- note: if accStop = 0 then acc is empty
if accStop = 0 then text else acc ++ text.extract accStop pos
else
let c := text.get' pos h
let pos' := text.next' pos h
if h' : ¬ text.atEnd pos' c == '\r' text.get pos' == '\n' then
let acc := acc ++ text.extract accStop pos
go acc pos' (text.next' pos' h'.1)
else
go acc accStop pos'
termination_by text.utf8ByteSize - pos.byteIdx
decreasing_by
decreasing_with
show text.utf8ByteSize - (text.next' (text.next' pos _) _).byteIdx < text.utf8ByteSize - pos.byteIdx
have k := Nat.gt_of_not_le <| mt decide_eq_true h
exact Nat.sub_lt_sub_left k (Nat.lt_trans (String.lt_next text pos) (String.lt_next _ _))
decreasing_with
show text.utf8ByteSize - (text.next' pos _).byteIdx < text.utf8ByteSize - pos.byteIdx
have k := Nat.gt_of_not_le <| mt decide_eq_true h
exact Nat.sub_lt_sub_left k (String.lt_next _ _)
end String

View File

@@ -0,0 +1,21 @@
/-
Copyright (c) 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Leonardo de Moura
-/
prelude
import Init.Data.Char.Lemmas
namespace String
protected theorem data_eq_of_eq {a b : String} (h : a = b) : a.data = b.data :=
h rfl
protected theorem ne_of_data_ne {a b : String} (h : a.data b.data) : a b :=
fun h' => absurd (String.data_eq_of_eq h') h
@[simp] protected theorem lt_irrefl (s : String) : ¬ s < s :=
List.lt_irrefl' Char.lt_irrefl s.data
protected theorem ne_of_lt {a b : String} (h : a < b) : a b := by
have := String.lt_irrefl a
intro h; subst h; contradiction
end String

View File

@@ -6,3 +6,4 @@ Authors: Henrik Böving
prelude
import Init.Data.UInt.Basic
import Init.Data.UInt.Log2
import Init.Data.UInt.Lemmas

View File

@@ -364,6 +364,3 @@ instance (a b : USize) : Decidable (a < b) := USize.decLt a b
instance (a b : USize) : Decidable (a b) := USize.decLe a b
instance : Max USize := maxOfLe
instance : Min USize := minOfLe
theorem USize.modn_lt {m : Nat} : (u : USize), m > 0 USize.toNat (u % m) < m
| u, h => Fin.modn_lt u h

View File

@@ -0,0 +1,66 @@
/-
Copyright (c) 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Leonardo de Moura
-/
prelude
import Init.Data.UInt.Basic
import Init.Data.Fin.Lemmas
set_option hygiene false in
macro "declare_uint_theorems" typeName:ident : command =>
`(
namespace $typeName
instance : Inhabited $typeName where
default := 0
theorem zero_def : (0 : $typeName) = 0 := rfl
theorem one_def : (1 : $typeName) = 1 := rfl
theorem sub_def (a b : $typeName) : a - b = a.val - b.val := rfl
theorem mul_def (a b : $typeName) : a * b = a.val * b.val := rfl
theorem mod_def (a b : $typeName) : a % b = a.val % b.val := rfl
theorem add_def (a b : $typeName) : a + b = a.val + b.val := rfl
@[simp] theorem mk_val_eq : (a : $typeName), mk a.val = a
| _, _ => rfl
theorem val_eq_of_lt {a : Nat} : a < size ((ofNat a).val : Nat) = a :=
Nat.mod_eq_of_lt
theorem le_def {a b : $typeName} : a b a.1 b.1 := .rfl
theorem lt_def {a b : $typeName} : a < b a.1 < b.1 := .rfl
theorem lt_iff_val_lt_val {a b : $typeName} : a < b a.val < b.val := .rfl
@[simp] protected theorem not_le {a b : $typeName} : ¬ a b b < a := Fin.not_le
@[simp] protected theorem not_lt {a b : $typeName} : ¬ a < b b a := Fin.not_lt
@[simp] protected theorem le_refl (a : $typeName) : a a := by simp [le_def]
@[simp] protected theorem lt_irrefl (a : $typeName) : ¬ a < a := by simp
protected theorem le_trans {a b c : $typeName} : a b b c a c := Fin.le_trans
protected theorem lt_trans {a b c : $typeName} : a < b b < c a < c := Fin.lt_trans
protected theorem le_total (a b : $typeName) : a b b a := Fin.le_total a.1 b.1
protected theorem lt_asymm {a b : $typeName} (h : a < b) : ¬ b < a := Fin.lt_asymm h
protected theorem val_eq_of_eq {a b : $typeName} (h : a = b) : a.val = b.val := h rfl
protected theorem eq_of_val_eq {a b : $typeName} (h : a.val = b.val) : a = b := by cases a; cases b; simp at h; simp [h]
open $typeName (val_eq_of_eq) in
protected theorem ne_of_val_ne {a b : $typeName} (h : a.val b.val) : a b := fun h' => absurd (val_eq_of_eq h') h
open $typeName (ne_of_val_ne) in
protected theorem ne_of_lt {a b : $typeName} (h : a < b) : a b := ne_of_val_ne (Fin.ne_of_lt h)
@[simp] protected theorem zero_toNat : (0 : $typeName).toNat = 0 := Nat.zero_mod _
@[simp] protected theorem mod_toNat (a b : $typeName) : (a % b).toNat = a.toNat % b.toNat := Fin.mod_val ..
@[simp] protected theorem div_toNat (a b : $typeName) : (a / b).toNat = a.toNat / b.toNat := Fin.div_val ..
@[simp] protected theorem modn_toNat (a : $typeName) (b : Nat) : (a.modn b).toNat = a.toNat % b := Fin.modn_val ..
protected theorem modn_lt {m : Nat} : (u : $typeName), m > 0 toNat (u % m) < m
| u, h => Fin.modn_lt u h
open $typeName (modn_lt) in
protected theorem mod_lt (a b : $typeName) (h : 0 < b) : a % b < b := modn_lt _ (by simp [lt_def] at h; exact h)
protected theorem toNat.inj : {a b : $typeName}, a.toNat = b.toNat a = b
| _, _, _, _, rfl => rfl
end $typeName
)
declare_uint_theorems UInt8
declare_uint_theorems UInt16
declare_uint_theorems UInt32
declare_uint_theorems UInt64
declare_uint_theorems USize

10
src/Init/Grind.lean Normal file
View File

@@ -0,0 +1,10 @@
/-
Copyright (c) 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Leonardo de Moura
-/
prelude
import Init.Grind.Norm
import Init.Grind.Tactics
import Init.Grind.Lemmas
import Init.Grind.Cases

15
src/Init/Grind/Cases.lean Normal file
View File

@@ -0,0 +1,15 @@
/-
Copyright (c) 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Leonardo de Moura
-/
prelude
import Init.Core
attribute [grind_cases] And Prod False Empty True Unit Exists
namespace Lean.Grind.Eager
attribute [scoped grind_cases] Or
end Lean.Grind.Eager

View File

@@ -0,0 +1,14 @@
/-
Copyright (c) 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Leonardo de Moura
-/
prelude
import Init.Core
namespace Lean.Grind
theorem intro_with_eq (p p' q : Prop) (he : p = p') (h : p' q) : p q :=
fun hp => h (he.mp hp)
end Lean.Grind

110
src/Init/Grind/Norm.lean Normal file
View File

@@ -0,0 +1,110 @@
/-
Copyright (c) 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Leonardo de Moura
-/
prelude
import Init.SimpLemmas
import Init.Classical
import Init.ByCases
namespace Lean.Grind
/-!
Normalization theorems for the `grind` tactic.
We are also going to use simproc's in the future.
-/
-- Not
attribute [grind_norm] Classical.not_not
-- Ne
attribute [grind_norm] ne_eq
-- Iff
@[grind_norm] theorem iff_eq (p q : Prop) : (p q) = (p = q) := by
by_cases p <;> by_cases q <;> simp [*]
-- Eq
attribute [grind_norm] eq_self heq_eq_eq
-- Prop equality
@[grind_norm] theorem eq_true_eq (p : Prop) : (p = True) = p := by simp
@[grind_norm] theorem eq_false_eq (p : Prop) : (p = False) = ¬p := by simp
@[grind_norm] theorem not_eq_prop (p q : Prop) : (¬(p = q)) = (p = ¬q) := by
by_cases p <;> by_cases q <;> simp [*]
-- True
attribute [grind_norm] not_true
-- False
attribute [grind_norm] not_false_eq_true
-- Implication as a clause
@[grind_norm] theorem imp_eq (p q : Prop) : (p q) = (¬ p q) := by
by_cases p <;> by_cases q <;> simp [*]
-- And
@[grind_norm] theorem not_and (p q : Prop) : (¬(p q)) = (¬p ¬q) := by
by_cases p <;> by_cases q <;> simp [*]
attribute [grind_norm] and_true true_and and_false false_and and_assoc
-- Or
attribute [grind_norm] not_or
attribute [grind_norm] or_true true_or or_false false_or or_assoc
-- ite
attribute [grind_norm] ite_true ite_false
@[grind_norm] theorem not_ite {_ : Decidable p} (q r : Prop) : (¬ite p q r) = ite p (¬q) (¬r) := by
by_cases p <;> simp [*]
-- Forall
@[grind_norm] theorem not_forall (p : α Prop) : (¬ x, p x) = x, ¬p x := by simp
attribute [grind_norm] forall_and
-- Exists
@[grind_norm] theorem not_exists (p : α Prop) : (¬ x, p x) = x, ¬p x := by simp
attribute [grind_norm] exists_const exists_or
-- Bool cond
@[grind_norm] theorem cond_eq_ite (c : Bool) (a b : α) : cond c a b = ite c a b := by
cases c <;> simp [*]
-- Bool or
attribute [grind_norm]
Bool.or_false Bool.or_true Bool.false_or Bool.true_or Bool.or_eq_true Bool.or_assoc
-- Bool and
attribute [grind_norm]
Bool.and_false Bool.and_true Bool.false_and Bool.true_and Bool.and_eq_true Bool.and_assoc
-- Bool not
attribute [grind_norm]
Bool.not_not
-- beq
attribute [grind_norm] beq_iff_eq
-- bne
attribute [grind_norm] bne_iff_ne
-- Bool not eq true/false
attribute [grind_norm] Bool.not_eq_true Bool.not_eq_false
-- decide
attribute [grind_norm] decide_eq_true_eq decide_not not_decide_eq_true
-- Nat LE
attribute [grind_norm] Nat.le_zero_eq
-- Nat/Int LT
@[grind_norm] theorem Nat.lt_eq (a b : Nat) : (a < b) = (a + 1 b) := by
simp [Nat.lt, LT.lt]
@[grind_norm] theorem Int.lt_eq (a b : Int) : (a < b) = (a + 1 b) := by
simp [Int.lt, LT.lt]
-- GT GE
attribute [grind_norm] GT.gt GE.ge
end Lean.Grind

View File

@@ -0,0 +1,25 @@
/-
Copyright (c) 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Leonardo de Moura
-/
prelude
import Init.Tactics
namespace Lean.Grind
/--
The configuration for `grind`.
Passed to `grind` using, for example, the `grind (config := { eager := true })` syntax.
-/
structure Config where
/--
When `eager` is true (default: `false`), `grind` eagerly splits `if-then-else` and `match`
expressions.
-/
eager : Bool := false
deriving Inhabited, BEq
/-!
`grind` tactic and related tactics.
-/
end Lean.Grind

View File

@@ -169,6 +169,11 @@ structure Config where
That is, given a local context containing entry `x : t := e`, the free variable `x` reduces to `e`.
-/
zetaDelta : Bool := false
/--
When `index` (default : `true`) is `false`, `simp` will only use the root symbol
to find candidate `simp` theorems. It approximates Lean 3 `simp` behavior.
-/
index : Bool := true
deriving Inhabited, BEq
-- Configuration object for `simp_all`

View File

@@ -87,6 +87,7 @@ macro:35 xs:bracketedExplicitBinders " × " b:term:35 : term => expandBrackedBi
macro:35 xs:bracketedExplicitBinders " ×' " b:term:35 : term => expandBrackedBinders ``PSigma xs b
end
namespace Lean
-- first step of a `calc` block
syntax calcFirstStep := ppIndent(colGe term (" := " term)?)
-- enforce indentation of calc steps so we know when to stop parsing them
@@ -136,6 +137,7 @@ syntax (name := calcTactic) "calc" calcSteps : tactic
@[inherit_doc «calc»]
macro tk:"calc" steps:calcSteps : conv =>
`(conv| tactic => calc%$tk $steps)
end Lean
@[app_unexpander Unit.unit] def unexpandUnit : Lean.PrettyPrinter.Unexpander
| `($(_)) => `(())
@@ -361,6 +363,7 @@ macro_rules
| `(letI $_:ident $_* : $_ := $_; $_) => Lean.Macro.throwUnsupported -- handled by elab
namespace Lean
syntax cdotTk := patternIgnore("· " <|> ". ")
/-- `· tac` focuses on the main goal and tries to solve it using `tac`, or else fails. -/
syntax (name := cdot) cdotTk tacticSeqIndentGt : tactic
@@ -368,12 +371,11 @@ syntax (name := cdot) cdotTk tacticSeqIndentGt : tactic
/--
Similar to `first`, but succeeds only if one the given tactics solves the current goal.
-/
syntax (name := solve) "solve" withPosition((ppDedent(ppLine) colGe "| " tacticSeq)+) : tactic
syntax (name := solveTactic) "solve" withPosition((ppDedent(ppLine) colGe "| " tacticSeq)+) : tactic
macro_rules
| `(tactic| solve $[| $ts]* ) => `(tactic| focus first $[| ($ts); done]*)
namespace Lean
/-! # `repeat` and `while` notation -/
inductive Loop where

View File

@@ -68,7 +68,7 @@ abbrev map (f : Int → Int) (xs : Coeffs) : Coeffs := List.map f xs
/-- Shim for `.enum.find?`. -/
abbrev findIdx? (f : Int Bool) (xs : Coeffs) : Option Nat :=
-- List.findIdx? f xs
-- We could avoid `Std.Data.List.Basic` by using the less efficient:
-- We could avoid `Batteries.Data.List.Basic` by using the less efficient:
xs.enum.find? (f ·.2) |>.map (·.1)
/-- Shim for `IntList.bmod`. -/
abbrev bmod (x : Coeffs) (m : Nat) : Coeffs := IntList.bmod x m

View File

@@ -3644,6 +3644,17 @@ def getPos? (info : SourceInfo) (canonicalOnly := false) : Option String.Pos :=
| synthetic (pos := pos) .., false => some pos
| _, _ => none
/--
Gets the end position information from a `SourceInfo`, if available.
If `originalOnly` is true, then `.synthetic` syntax will also return `none`.
-/
def getTailPos? (info : SourceInfo) (canonicalOnly := false) : Option String.Pos :=
match info, canonicalOnly with
| original (endPos := endPos) .., _
| synthetic (endPos := endPos) (canonical := true) .., _
| synthetic (endPos := endPos) .., false => some endPos
| _, _ => none
end SourceInfo
/--

View File

@@ -210,8 +210,44 @@ def sleep (ms : UInt32) : BaseIO Unit :=
/-- Request cooperative cancellation of the task. The task must explicitly call `IO.checkCanceled` to react to the cancellation. -/
@[extern "lean_io_cancel"] opaque cancel : @& Task α BaseIO Unit
/-- The current state of a `Task` in the Lean runtime's task manager. -/
inductive TaskState
/--
The `Task` is waiting to be run.
It can be waiting for dependencies to complete or
sitting in the task manager queue waiting for a thread to run on.
-/
| waiting
/--
The `Task` is actively running on a thread or,
in the case of a `Promise`, waiting for a call to `IO.Promise.resolve`.
-/
| running
/--
The `Task` has finished running and its result is available.
Calling `Task.get` or `IO.wait` on the task will not block.
-/
| finished
deriving Inhabited, Repr, DecidableEq, Ord
instance : LT TaskState := ltOfOrd
instance : LE TaskState := leOfOrd
instance : Min TaskState := minOfLe
instance : Max TaskState := maxOfLe
protected def TaskState.toString : TaskState String
| .waiting => "waiting"
| .running => "running"
| .finished => "finished"
instance : ToString TaskState := TaskState.toString
/-- Returns current state of the `Task` in the Lean runtime's task manager. -/
@[extern "lean_io_get_task_state"] opaque getTaskState : @& Task α BaseIO TaskState
/-- Check if the task has finished execution, at which point calling `Task.get` will return immediately. -/
@[extern "lean_io_has_finished"] opaque hasFinished : @& Task α BaseIO Bool
@[inline] def hasFinished (task : Task α) : BaseIO Bool := do
return ( getTaskState task) matches .finished
/-- Wait for the task to finish, then return its result. -/
@[extern "lean_io_wait"] opaque wait (t : Task α) : BaseIO α :=
@@ -228,6 +264,13 @@ local macro "nonempty_list" : tactic =>
/-- Helper method for implementing "deterministic" timeouts. It is the number of "small" memory allocations performed by the current execution thread. -/
@[extern "lean_io_get_num_heartbeats"] opaque getNumHeartbeats : BaseIO Nat
/--
Adjusts the heartbeat counter of the current thread by the given amount. This can be useful to give
allocation-avoiding code additional "weight" and is also used to adjust the counter after resuming
from a snapshot.
-/
@[extern "lean_io_add_heartbeats"] opaque addHeartbeats (count : UInt64) : BaseIO Unit
/--
The mode of a file handle (i.e., a set of `open` flags and an `fdopen` mode).
@@ -750,6 +793,32 @@ instance : MonadLift (ST IO.RealWorld) BaseIO := ⟨id⟩
def mkRef (a : α) : BaseIO (IO.Ref α) :=
ST.mkRef a
/--
Mutable cell that can be passed around for purposes of cooperative task cancellation: request
cancellation with `CancelToken.set` and check for it with `CancelToken.isSet`.
This is a more flexible alternative to `Task.cancel` as the token can be shared between multiple
tasks.
-/
structure CancelToken where
private ref : IO.Ref Bool
namespace CancelToken
/-- Creates a new cancellation token. -/
def new : BaseIO CancelToken :=
CancelToken.mk <$> IO.mkRef false
/-- Activates a cancellation token. Idempotent. -/
def set (tk : CancelToken) : BaseIO Unit :=
tk.ref.set true
/-- Checks whether the cancellation token has been activated. -/
def isSet (tk : CancelToken) : BaseIO Bool :=
tk.ref.get
end CancelToken
namespace FS
namespace Stream

View File

@@ -835,7 +835,7 @@ syntax (name := renameI) "rename_i" (ppSpace colGt binderIdent)+ : tactic
/--
`repeat tac` repeatedly applies `tac` to the main goal until it fails.
That is, if `tac` produces multiple subgoals, only subgoals up to the first failure will be visited.
The `Std` library provides `repeat'` which repeats separately in each subgoal.
The `Batteries` library provides `repeat'` which repeats separately in each subgoal.
-/
syntax "repeat " tacticSeq : tactic
macro_rules
@@ -1266,7 +1266,7 @@ Optional arguments passed via a configuration argument as `solve_by_elim (config
but it is often useful to change to `.reducible`,
so semireducible definitions will not be unfolded when trying to apply a lemma.
See also the doc-comment for `Std.Tactic.BacktrackConfig` for the options
See also the doc-comment for `Lean.Meta.Tactic.Backtrack.BacktrackConfig` for the options
`proc`, `suspend`, and `discharge` which allow further customization of `solve_by_elim`.
Both `apply_assumption` and `apply_rules` are implemented via these hooks.
-/
@@ -1425,6 +1425,16 @@ If there are several with the same priority, it is uses the "most recent one". E
-/
syntax (name := simp) "simp" (Tactic.simpPre <|> Tactic.simpPost)? (ppSpace prio)? : attr
/--
Theorems tagged with the `grind_norm` attribute are used by the `grind` tactic normalizer/pre-processor.
-/
syntax (name := grind_norm) "grind_norm" (Tactic.simpPre <|> Tactic.simpPost)? (ppSpace prio)? : attr
/--
Simplification procedures tagged with the `grind_norm_proc` attribute are used by the `grind` tactic normalizer/pre-processor.
-/
syntax (name := grind_norm_proc) "grind_norm_proc" (Tactic.simpPre <|> Tactic.simpPost)? : attr
/-- The possible `norm_cast` kinds: `elim`, `move`, or `squash`. -/
syntax normCastLabel := &"elim" <|> &"move" <|> &"squash"

View File

@@ -37,3 +37,4 @@ import Lean.Log
import Lean.Linter
import Lean.SubExpr
import Lean.LabelAttribute
import Lean.AddDecl

31
src/Lean/AddDecl.lean Normal file
View File

@@ -0,0 +1,31 @@
/-
Copyright (c) 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Leonardo de Moura
-/
prelude
import Lean.CoreM
namespace Lean
def Environment.addDecl (env : Environment) (opts : Options) (decl : Declaration) : Except KernelException Environment :=
addDeclCore env (Core.getMaxHeartbeats opts).toUSize decl
def Environment.addAndCompile (env : Environment) (opts : Options) (decl : Declaration) : Except KernelException Environment := do
let env addDecl env opts decl
compileDecl env opts decl
def addDecl (decl : Declaration) : CoreM Unit := do
profileitM Exception "type checking" ( getOptions) do
withTraceNode `Kernel (fun _ => return m!"typechecking declaration") do
if !( MonadLog.hasErrors) && decl.hasSorry then
logWarning "declaration uses 'sorry'"
match ( getEnv).addDecl ( getOptions) decl with
| .ok env => setEnv env
| .error ex => throwKernelException ex
def addAndCompile (decl : Declaration) : CoreM Unit := do
addDecl decl
compileDecl decl
end Lean

View File

@@ -67,13 +67,11 @@ def registerBuiltinAttribute (attr : AttributeImpl) : IO Unit := do
Helper methods for decoding the parameters of builtin attributes that are defined before `Lean.Parser`.
We have the following ones:
```
@[builtin_attr_parser] def simple := leading_parser ident >> optional ident >> optional priorityParser
/- We can't use `simple` for `class`, `instance`, `export` and `macro` because they are keywords. -/
@[builtin_attr_parser] def «class» := leading_parser "class"
@[builtin_attr_parser] def «instance» := leading_parser "instance" >> optional priorityParser
@[builtin_attr_parser] def simple := leading_parser ident >> optional (ppSpace >> (priorityParser <|> ident))
@[builtin_attr_parser] def «macro» := leading_parser "macro " >> ident
@[builtin_attr_parser] def «export» := leading_parser "export " >> ident
```
Note that we need the parsers for `class`, `instance`, and `macros` because they are keywords.
Note that we need the parsers for `class`, `instance`, `export` and `macros` because they are keywords.
-/
def Attribute.Builtin.ensureNoArgs (stx : Syntax) : AttrM Unit := do

View File

@@ -29,43 +29,50 @@ Here are the main differences:
does not occur in a function body. See example at `livevars.lean`.
-/
private def mayReuse (c₁ c₂ : CtorInfo) : Bool :=
private def mayReuse (c₁ c₂ : CtorInfo) (relaxedReuse : Bool) : Bool :=
c₁.size == c₂.size && c₁.usize == c₂.usize && c₁.ssize == c₂.ssize &&
/- The following condition is a heuristic.
We don't want to reuse cells from different types even when they are compatible
If `relaxedReuse := false`, then we don't want to reuse cells from
different constructors even when they are compatible
because it produces counterintuitive behavior. -/
c₁.name.getPrefix == c₂.name.getPrefix
(relaxedReuse || c₁.name.getPrefix == c₂.name.getPrefix)
/--
Replace `ctor` applications with `reuse` applications if compatible.
`w` contains the "memory cell" being reused.
-/
private partial def S (w : VarId) (c : CtorInfo) : FnBody FnBody
private partial def S (w : VarId) (c : CtorInfo) (relaxedReuse : Bool) (b : FnBody) : FnBody :=
go b
where
go : FnBody FnBody
| .vdecl x t v@(.ctor c' ys) b =>
if mayReuse c c' then
if mayReuse c c' relaxedReuse then
let updtCidx := c.cidx != c'.cidx
.vdecl x t (.reuse w c' updtCidx ys) b
else
.vdecl x t v (S w c b)
.vdecl x t v (go b)
| .jdecl j ys v b =>
let v' := S w c v
let v' := go v
if v == v' then
.jdecl j ys v (S w c b)
.jdecl j ys v (go b)
else
.jdecl j ys v' b
| .case tid x xType alts =>
.case tid x xType <| alts.map fun alt => alt.modifyBody (S w c)
.case tid x xType <| alts.map fun alt => alt.modifyBody go
| b =>
if b.isTerminal then
b
else let
(instr, b) := b.split
instr.setBody (S w c b)
else
let (instr, b) := b.split
instr.setBody (go b)
structure Context where
lctx : LocalContext := {}
/--
Contains all variables in `cases` statements in the current path.
Contains all variables in `cases` statements in the current path
and variables that are already in `reset` statements when we
invoke `R`.
We use this information to prevent double-reset in code such as
```
case x_i : obj of
@@ -74,8 +81,18 @@ structure Context where
Prod.mk →
...
```
A variable can already be in a `reset` statement when we
invoke `R` because we execute it with and without `relaxedReuse`.
-/
casesVars : PHashSet VarId := {}
alreadyFound : PHashSet VarId := {}
/--
If `relaxedReuse := true`, then allow memory cells from different
constructors to be reused. For example, we can reuse a `PSigma.mk`
to allocate a `Prod.mk`. To avoid counterintuitive behavior,
we first try `relaxedReuse := false`, and then `relaxedReuse := true`.
-/
relaxedReuse : Bool := false
/-- We use `Context` to track join points in scope. -/
abbrev M := ReaderT Context (StateT Index Id)
@@ -90,7 +107,7 @@ to replace a `ctor` withe `reuse` in `b`.
-/
private def tryS (x : VarId) (c : CtorInfo) (b : FnBody) : M FnBody := do
let w mkFresh
let b' := S w c b
let b' := S w c ( read).relaxedReuse b
if b == b' then
return b
else
@@ -102,8 +119,8 @@ private def Dfinalize (x : VarId) (c : CtorInfo) : FnBody × Bool → M FnBody
private def argsContainsVar (ys : Array Arg) (x : VarId) : Bool :=
ys.any fun arg => match arg with
| Arg.var y => x == y
| _ => false
| .var y => x == y
| _ => false
private def isCtorUsing (b : FnBody) (x : VarId) : Bool :=
match b with
@@ -161,8 +178,8 @@ private def D (x : VarId) (c : CtorInfo) (b : FnBody) : M FnBody :=
partial def R (e : FnBody) : M FnBody := do
match e with
| .case tid x xType alts =>
let alreadyFound := ( read).casesVars.contains x
withReader (fun ctx => { ctx with casesVars := ctx.casesVars.insert x }) do
let alreadyFound := ( read).alreadyFound.contains x
withReader (fun ctx => { ctx with alreadyFound := ctx.alreadyFound.insert x }) do
let alts alts.mapM fun alt => do
let alt alt.mmodifyBody R
match alt with
@@ -187,16 +204,43 @@ partial def R (e : FnBody) : M FnBody := do
let b R b
return instr.setBody b
end ResetReuse
abbrev N := StateT (PHashSet VarId) Id
partial def collectResets (e : FnBody) : N Unit := do
match e with
| .case _ _ _ alts => alts.forM fun alt => collectResets alt.body
| .jdecl _ _ v b => collectResets v; collectResets b
| .vdecl _ _ (.reset _ x) b => modify fun s => s.insert x; collectResets b
| e => unless e.isTerminal do
let (_, b) := e.split
collectResets b
end ResetReuse
open ResetReuse
def Decl.insertResetReuse (d : Decl) : Decl :=
def Decl.insertResetReuseCore (d : Decl) (relaxedReuse : Bool) : Decl :=
match d with
| .fdecl (body := b) .. =>
let nextIndex := d.maxIndex + 1
let bNew := (R b {}).run' nextIndex
-- First time we execute `insertResetReuseCore`, `relaxedReuse := false`.
let alreadyFound : PHashSet VarId := if relaxedReuse then (collectResets b *> get).run' {} else {}
let bNew := R b { relaxedReuse, alreadyFound } |>.run' nextIndex
d.updateBody! bNew
| other => other
def Decl.insertResetReuse (d : Decl) : Decl :=
/-
We execute the reset/reuse algorithm twice. The first time, we only reuse memory cells
between identical constructor memory cells. That is, we do not reuse a `PSigma.mk` memory cell
when allocating a `Prod.mk` memory cell, even though they have the same layout. Recall
that the reset/reuse placement algorithm is a heuristic, and the first pass prevents reuses
that are unlikely to be useful at runtime. Then, we run the procedure again,
relaxing this restriction. If there are still opportunities for reuse, we will take advantage of them.
The second pass addresses issue #4089.
-/
d.insertResetReuseCore (relaxedReuse := false)
|>.insertResetReuseCore (relaxedReuse := true)
end Lean.IR

View File

@@ -4,6 +4,8 @@ Released under Apache 2.0 license as described in the file LICENSE.
Authors: Leonardo de Moura
-/
prelude
import Lean.AddDecl
import Lean.MonadEnv
import Lean.Elab.InfoTree.Main
namespace Lean
@@ -138,7 +140,7 @@ def setBuiltinInitAttr (env : Environment) (declName : Name) (initFnName : Name
builtinInitAttr.setParam env declName initFnName
def declareBuiltin (forDecl : Name) (value : Expr) : CoreM Unit := do
let name := `_regBuiltin ++ forDecl
let name mkAuxName (`_regBuiltin ++ forDecl) 1
let type := mkApp (mkConst `IO) (mkConst `Unit)
let decl := Declaration.defnDecl { name, levelParams := [], type, value, hints := ReducibilityHints.opaque,
safety := DefinitionSafety.safe }

View File

@@ -67,9 +67,4 @@ opaque compileDecls (env : Environment) (opt : @& Options) (decls : @& List Name
def compileDecl (env : Environment) (opt : @& Options) (decl : @& Declaration) : Except KernelException Environment :=
compileDecls env opt (Compiler.getDeclNamesForCodeGen decl)
def addAndCompile (env : Environment) (opt : Options) (decl : Declaration) : Except KernelException Environment := do
let env addDecl env decl
compileDecl env opt decl
end Environment

View File

@@ -11,6 +11,7 @@ import Lean.Eval
import Lean.ResolveName
import Lean.Elab.InfoTree.Types
import Lean.MonadEnv
import Lean.Elab.Exception
namespace Lean
register_builtin_option diagnostics : Bool := {
@@ -30,6 +31,8 @@ register_builtin_option maxHeartbeats : Nat := {
descr := "maximum amount of heartbeats per command. A heartbeat is number of (small) memory allocations (in thousands), 0 means no limit"
}
def useDiagnosticMsg := s!"use `set_option {diagnostics.name} true` to get diagnostic information"
namespace Core
builtin_initialize registerTraceClass `Kernel
@@ -79,16 +82,17 @@ structure Context where
maxHeartbeats : Nat := getMaxHeartbeats options
currMacroScope : MacroScope := firstFrontendMacroScope
/--
If `catchRuntimeEx = false`, then given `try x catch ex => h ex`,
an runtime exception occurring in `x` is not handled by `h`.
Recall that runtime exceptions are `maxRecDepth` or `maxHeartbeats`.
-/
catchRuntimeEx : Bool := false
/--
If `diag := true`, different parts of the system collect diagnostics.
Use the `set_option diag true` to set it to true.
-/
diag : Bool := false
/-- If set, used to cancel elaboration from outside when results are not needed anymore. -/
cancelTk? : Option IO.CancelToken := none
/--
If set (when `showPartialSyntaxErrors` is not set and parsing failed), suppresses most elaboration
errors; see also `logMessage` below.
-/
suppressElabErrors : Bool := false
deriving Nonempty
/-- CoreM is a monad for manipulating the Lean environment.
@@ -205,16 +209,45 @@ instance : MonadTrace CoreM where
getTraceState := return ( get).traceState
modifyTraceState f := modify fun s => { s with traceState := f s.traceState }
/-- Restore backtrackable parts of the state. -/
def restore (b : State) : CoreM Unit :=
modify fun s => { s with env := b.env, messages := b.messages, infoState := b.infoState }
structure SavedState extends State where
/-- Number of heartbeats passed inside `withRestoreOrSaveFull`, not used otherwise. -/
passedHearbeats : Nat
deriving Nonempty
def saveState : CoreM SavedState := do
let s get
return { toState := s, passedHearbeats := 0 }
/--
Restores full state including sources for unique identifiers. Only intended for incremental reuse
between elaboration runs, not for backtracking within a single run.
Incremental reuse primitive: if `reusableResult?` is `none`, runs `cont` with an action `save` that
on execution returns the saved monadic state at this point including the heartbeats used by `cont`
so far. If `reusableResult?` on the other hand is `some (a, state)`, restores full `state` including
heartbeats used and returns `a`.
The intention is for steps that support incremental reuse to initially pass `none` as
`reusableResult?` and call `save` as late as possible in `cont`. In a further run, if reuse is
possible, `reusableResult?` should be set to the previous state and result, ensuring that the state
after running `withRestoreOrSaveFull` is identical in both runs. Note however that necessarily this
is only an approximation in the case of heartbeats as heartbeats used by `withRestoreOrSaveFull`, by
the remainder of `cont` after calling `save`, as well as by reuse-handling code such as the one
supplying `reusableResult?` are not accounted for.
-/
def restoreFull (b : State) : CoreM Unit :=
set b
@[specialize] def withRestoreOrSaveFull (reusableResult? : Option (α × SavedState))
(cont : (save : CoreM SavedState) CoreM α) : CoreM α := do
if let some (val, state) := reusableResult? then
set state.toState
IO.addHeartbeats state.passedHearbeats.toUInt64
return val
let startHeartbeats IO.getNumHeartbeats
cont (do
let s get
let stopHeartbeats IO.getNumHeartbeats
return { toState := s, passedHearbeats := stopHeartbeats - startHeartbeats })
/-- Restore backtrackable parts of the state. -/
def SavedState.restore (b : SavedState) : CoreM Unit :=
modify fun s => { s with env := b.env, messages := b.messages, infoState := b.infoState }
private def mkFreshNameImp (n : Name) : CoreM Name := do
let fresh modifyGet fun s => (s.nextMacroScope, { s with nextMacroScope := s.nextMacroScope + 1 })
@@ -245,13 +278,29 @@ instance [MetaEval α] : MetaEval (CoreM α) where
protected def withIncRecDepth [Monad m] [MonadControlT CoreM m] (x : m α) : m α :=
controlAt CoreM fun runInBase => withIncRecDepth (runInBase x)
builtin_initialize interruptExceptionId : InternalExceptionId registerInternalExceptionId `interrupt
/--
Throws an internal interrupt exception if cancellation has been requested. The exception is not
caught by `try catch` but is intended to be caught by `Command.withLoggingExceptions` at the top
level of elaboration. In particular, we want to skip producing further incremental snapshots after
the exception has been thrown.
-/
@[inline] def checkInterrupted : CoreM Unit := do
if ( IO.checkCanceled) then
-- should never be visible to users!
throw <| Exception.error .missing "elaboration interrupted"
if let some tk := ( read).cancelTk? then
if ( tk.isSet) then
throw <| .internal interruptExceptionId
register_builtin_option debug.moduleNameAtTimeout : Bool := {
defValue := true
group := "debug"
descr := "include module name in deterministic timeout error messages.\nRemark: we set this option to false to increase the stability of our test suite"
}
def throwMaxHeartbeat (moduleName : Name) (optionName : Name) (max : Nat) : CoreM Unit := do
let msg := s!"(deterministic) timeout at `{moduleName}`, maximum number of heartbeats ({max/1000}) has been reached\nuse `set_option {optionName} <num>` to set the limit\nuse `set_option {diagnostics.name} true` to get diagnostic information"
let includeModuleName := debug.moduleNameAtTimeout.get ( getOptions)
let atModuleName := if includeModuleName then s!" at `{moduleName}`" else ""
let msg := s!"(deterministic) timeout{atModuleName}, maximum number of heartbeats ({max/1000}) has been reached\nuse `set_option {optionName} <num>` to set the limit\n{useDiagnosticMsg}"
throw <| Exception.error ( getRef) (MessageData.ofFormat (Std.Format.text msg))
def checkMaxHeartbeatsCore (moduleName : String) (optionName : Name) (max : Nat) : CoreM Unit := do
@@ -285,11 +334,13 @@ def getMessageLog : CoreM MessageLog :=
return ( get).messages
/--
Returns the current log and then resets its messages but does NOT reset `MessageLog.hadErrors`. Used
Returns the current log and then resets its messages while adjusting `MessageLog.hadErrors`. Used
for incremental reporting during elaboration of a single command.
-/
def getAndEmptyMessageLog : CoreM MessageLog :=
modifyGet fun log => ({ log with msgs := {} }, log)
modifyGet fun s => (s.messages, { s with
messages.unreported := {}
messages.hadErrors := s.messages.hasErrors })
instance : MonadLog CoreM where
getRef := getRef
@@ -297,6 +348,12 @@ instance : MonadLog CoreM where
getFileName := return ( read).fileName
hasErrors := return ( get).messages.hasErrors
logMessage msg := do
if ( read).suppressElabErrors then
-- discard elaboration errors, except for a few important and unlikely misleading ones, on
-- parse error
unless msg.data.hasTag (· matches `Elab.synthPlaceholder | `Tactic.unsolvedGoals) do
return
let ctx read
let msg := { msg with data := MessageData.withNamingContext { currNamespace := ctx.currNamespace, openDecls := ctx.openDecls } msg.data };
modify fun s => { s with messages := s.messages.add msg }
@@ -328,7 +385,8 @@ export Core (CoreM mkFreshUserName checkSystem withCurrHeartbeats)
We used a similar hack at `Exception.isMaxRecDepth` -/
def Exception.isMaxHeartbeat (ex : Exception) : Bool :=
match ex with
| Exception.error _ (MessageData.ofFormat (Std.Format.text msg)) => "(deterministic) timeout".isPrefixOf msg
| Exception.error _ (MessageData.ofFormatWithInfos Std.Format.text msg, _) =>
"(deterministic) timeout".isPrefixOf msg
| _ => false
/-- Creates the expression `d → b` -/
@@ -338,15 +396,6 @@ def mkArrow (d b : Expr) : CoreM Expr :=
/-- Iterated `mkArrow`, creates the expression `a₁ → a₂ → … → aₙ → b`. Also see `arrowDomainsN`. -/
def mkArrowN (ds : Array Expr) (e : Expr) : CoreM Expr := ds.foldrM mkArrow e
def addDecl (decl : Declaration) : CoreM Unit := do
profileitM Exception "type checking" ( getOptions) do
withTraceNode `Kernel (fun _ => return m!"typechecking declaration") do
if !( MonadLog.hasErrors) && decl.hasSorry then
logWarning "declaration uses 'sorry'"
match ( getEnv).addDecl decl with
| Except.ok env => setEnv env
| Except.error ex => throwKernelException ex
private def supportedRecursors :=
#[``Empty.rec, ``False.rec, ``Eq.ndrec, ``Eq.rec, ``Eq.recOn, ``Eq.casesOn, ``False.casesOn, ``Empty.casesOn, ``And.rec, ``And.casesOn]
@@ -400,10 +449,6 @@ def compileDecls (decls : List Name) : CoreM Unit := do
| Except.error ex =>
throwKernelException ex
def addAndCompile (decl : Declaration) : CoreM Unit := do
addDecl decl;
compileDecl decl
def getDiag (opts : Options) : Bool :=
diagnostics.get opts
@@ -416,42 +461,55 @@ def ImportM.runCoreM (x : CoreM α) : ImportM α := do
let (a, _) (withOptions (fun _ => ctx.opts) x).toIO { fileName := "<ImportM>", fileMap := default } { env := ctx.env }
return a
/-- Return `true` if the exception was generated by one our resource limits. -/
/-- Return `true` if the exception was generated by one of our resource limits. -/
def Exception.isRuntime (ex : Exception) : Bool :=
ex.isMaxHeartbeat || ex.isMaxRecDepth
/-- Returns `true` if the exception is an interrupt generated by `checkInterrupted`. -/
def Exception.isInterrupt : Exception Bool
| Exception.internal id _ => id == Core.interruptExceptionId
| _ => false
/--
Custom `try-catch` for all monads based on `CoreM`. We don't want to catch "runtime exceptions"
in these monads, but on `CommandElabM`. See issues #2775 and #2744 as well as `MonadAlwayExcept`.
Custom `try-catch` for all monads based on `CoreM`. We usually don't want to catch "runtime
exceptions" these monads, but on `CommandElabM`. See issues #2775 and #2744 as well as
`MonadAlwaysExcept`. Also, we never want to catch interrupt exceptions inside the elaborator.
-/
@[inline] protected def Core.tryCatch (x : CoreM α) (h : Exception CoreM α) : CoreM α := do
try
x
catch ex =>
if ex.isRuntime && !( read).catchRuntimeEx then
throw ex
if ex.isInterrupt || ex.isRuntime then
throw ex -- We should use `tryCatchRuntimeEx` for catching runtime exceptions
else
h ex
@[inline] protected def Core.tryCatchRuntimeEx (x : CoreM α) (h : Exception CoreM α) : CoreM α := do
try
x
catch ex =>
h ex
instance : MonadExceptOf Exception CoreM where
throw := throw
tryCatch := Core.tryCatch
@[inline] def Core.withCatchingRuntimeEx (flag : Bool) (x : CoreM α) : CoreM α :=
withReader (fun ctx => { ctx with catchRuntimeEx := flag }) x
class MonadRuntimeException (m : Type Type) where
tryCatchRuntimeEx (body : m α) (handler : Exception m α) : m α
export MonadRuntimeException (tryCatchRuntimeEx)
instance : MonadRuntimeException CoreM where
tryCatchRuntimeEx := Core.tryCatchRuntimeEx
@[inline] instance [MonadRuntimeException m] : MonadRuntimeException (ReaderT ρ m) where
tryCatchRuntimeEx := fun x c r => tryCatchRuntimeEx (x r) (fun e => (c e) r)
@[inline] instance [MonadRuntimeException m] : MonadRuntimeException (StateRefT' ω σ m) where
tryCatchRuntimeEx := fun x c s => tryCatchRuntimeEx (x s) (fun e => c e s)
@[inline] def mapCoreM [MonadControlT CoreM m] [Monad m] (f : forall {α}, CoreM α CoreM α) {α} (x : m α) : m α :=
controlAt CoreM fun runInBase => f <| runInBase x
/--
Execute `x` with `catchRuntimeEx = flag`. That is, given `try x catch ex => h ex`,
if `x` throws a runtime exception, the handler `h` will be invoked if `flag = true`
Recall that
-/
@[inline] def withCatchingRuntimeEx [MonadControlT CoreM m] [Monad m] (x : m α) : m α :=
mapCoreM (Core.withCatchingRuntimeEx true) x
@[inline] def withoutCatchingRuntimeEx [MonadControlT CoreM m] [Monad m] (x : m α) : m α :=
mapCoreM (Core.withCatchingRuntimeEx false) x
end Lean

View File

@@ -249,6 +249,8 @@ def toArray (m : HashMap α β) : Array (α × β) :=
def numBuckets (m : HashMap α β) : Nat :=
m.val.buckets.val.size
variable [BEq α] [Hashable α]
/-- Builds a `HashMap` from a list of key-value pairs. Values of duplicated keys are replaced by their respective last occurrences. -/
def ofList (l : List (α × β)) : HashMap α β :=
l.foldl (init := HashMap.empty) (fun m p => m.insert p.fst p.snd)
@@ -260,6 +262,7 @@ def ofListWith (l : List (α × β)) (f : β → β → β) : HashMap α β :=
match m.find? p.fst with
| none => m.insert p.fst p.snd
| some v => m.insert p.fst $ f v p.snd)
end Lean.HashMap
/--

View File

@@ -106,7 +106,7 @@ def ofPosition (text : FileMap) (pos : Position) : String.Pos :=
/--
Returns the position of the start of (1-based) line `line`.
This gives the stame result as `map.ofPosition ⟨line, 0⟩`, but is more efficient.
This gives the same result as `map.ofPosition ⟨line, 0⟩`, but is more efficient.
-/
def lineStart (map : FileMap) (line : Nat) : String.Pos :=
if h : line - 1 < map.positions.size then

View File

@@ -7,6 +7,7 @@ prelude
import Lean.Elab.Quotation.Precheck
import Lean.Elab.Term
import Lean.Elab.BindersUtil
import Lean.Elab.SyntheticMVars
import Lean.Elab.PreDefinition.WF.TerminationHint
namespace Lean.Elab.Term
@@ -646,7 +647,29 @@ def elabLetDeclAux (id : Syntax) (binders : Array Syntax) (typeStx : Syntax) (va
(expectedType? : Option Expr) (useLetExpr : Bool) (elabBodyFirst : Bool) (usedLetOnly : Bool) : TermElabM Expr := do
let (type, val, binders) elabBindersEx binders fun xs => do
let (binders, fvars) := xs.unzip
let type elabType typeStx
/-
We use `withSynthesize` to ensure that any postponed elaboration problem
and nested tactics in `type` are resolved before elaborating `val`.
Resolved: we want to avoid synthethic opaque metavariables in `type`.
Recall that this kind of metavariable is non-assignable, and `isDefEq`
may waste a lot of time unfolding declarations before failing.
See issue #4051 for an example.
Here is the analysis for issue #4051.
- Given `have x : type := value; body`, we were previously elaborating `value` even
if `type` contained postponed elaboration problems.
- Moreover, the metavariables in `type` corresponding to postponed elaboration
problems cannot be assigned by `isDefEq` since the elaborator is supposed to assign them.
- Then, when checking whether type of `value` is definitionally equal to `type`,
a very long-time was spent unfolding a bunch of declarations before it failed.
In #4051, it was unfolding `Array.swaps` which is defined by well-founded recursion.
After the failure, the elaborator inserted a postponed coercion
that would be resolved later as soon as the types don't have unassigned metavariables.
We use `postpone := .partial` to allow type class (TC) resolution problems to be postponed
Recall that TC resolution does **not** produce synthetic opaque metavariables.
-/
let type withSynthesize (postpone := .partial) <| elabType typeStx
registerCustomErrorIfMVar type typeStx "failed to infer 'let' declaration type"
if elabBodyFirst then
let type mkForallFVars fvars type

View File

@@ -123,7 +123,7 @@ private partial def elabChoiceAux (cmds : Array Syntax) (i : Nat) : CommandElabM
n[1].forArgsM addUnivLevel
@[builtin_command_elab «init_quot»] def elabInitQuot : CommandElab := fun _ => do
match ( getEnv).addDecl Declaration.quotDecl with
match ( getEnv).addDecl ( getOptions) Declaration.quotDecl with
| Except.ok env => setEnv env
| Except.error ex => throwError (ex.toMessageData ( getOptions))

View File

@@ -98,7 +98,7 @@ open Meta
show Nat from 0
```
-/
let type withSynthesize (mayPostpone := true) do
let type withSynthesize (postpone := .yes) do
let type elabType type
if let some expectedType := expectedType? then
-- Recall that a similar approach is used when elaborating applications
@@ -205,7 +205,7 @@ private def elabTParserMacroAux (prec lhsPrec e : Term) : TermElabM Syntax := do
| _ => Macro.throwUnsupported
@[builtin_term_elab «sorry»] def elabSorry : TermElab := fun stx expectedType? => do
let stxNew `(sorryAx _ false)
let stxNew `(@sorryAx _ false) -- Remark: we use `@` to ensure `sorryAx` will not consume auot params
withMacroExpansion stx stxNew <| elabTerm stxNew expectedType?
/-- Return syntax `Prod.mk elems[0] (Prod.mk elems[1] ... (Prod.mk elems[elems.size - 2] elems[elems.size - 1])))` -/
@@ -314,11 +314,11 @@ where
@[builtin_term_elab typeAscription] def elabTypeAscription : TermElab
| `(($e : $type)), _ => do
let type withSynthesize (mayPostpone := true) <| elabType type
let type withSynthesize (postpone := .yes) <| elabType type
let e elabTerm e type
ensureHasType type e
| `(($e :)), expectedType? => do
let e withSynthesize (mayPostpone := false) <| elabTerm e none
let e withSynthesize (postpone := .no) <| elabTerm e none
ensureHasType expectedType? e
| _, _ => throwUnsupportedSyntax

View File

@@ -42,7 +42,7 @@ def mkCalcTrans (result resultType step stepType : Expr) : MetaM (Expr × Expr)
unless ( getCalcRelation? resultType).isSome do
throwError "invalid 'calc' step, step result is not a relation{indentExpr resultType}"
return (result, resultType)
| _ => throwError "invalid 'calc' step, failed to synthesize `Trans` instance{indentExpr selfType}"
| _ => throwError "invalid 'calc' step, failed to synthesize `Trans` instance{indentExpr selfType}\n{useDiagnosticMsg}"
/--
Adds a type annotation to a hole that occurs immediately at the beginning of the term.
@@ -112,10 +112,12 @@ def elabCalcSteps (steps : TSyntax ``calcSteps) : TermElabM Expr := do
return result?.get!.1
/-- Elaborator for the `calc` term mode variant. -/
@[builtin_term_elab «calc»]
@[builtin_term_elab Lean.calc]
def elabCalc : TermElab := fun stx expectedType? => do
let steps : TSyntax ``calcSteps := stx[1]
let result elabCalcSteps steps
synthesizeSyntheticMVarsUsingDefault
let result ensureHasType expectedType? result
return result
end Lean.Elab.Term

View File

@@ -47,8 +47,9 @@ structure Context where
ref : Syntax := Syntax.missing
tacticCache? : Option (IO.Ref Tactic.Cache)
/--
Snapshot for incremental reuse and reporting of command elaboration. Currently unused in Lean
itself.
Snapshot for incremental reuse and reporting of command elaboration. Currently only used for
(mutual) defs and contained tactics, in which case the `DynamicSnapshot` is a
`HeadersParsedSnapshot`.
Definitely resolved in `Language.Lean.process.doElab`.
@@ -56,6 +57,13 @@ structure Context where
old elaboration are identical.
-/
snap? : Option (Language.SnapshotBundle Language.DynamicSnapshot)
/-- Cancellation token forwarded to `Core.cancelTk?`. -/
cancelTk? : Option IO.CancelToken
/--
If set (when `showPartialSyntaxErrors` is not set and parsing failed), suppresses most elaboration
errors; see also `logMessage` below.
-/
suppressElabErrors : Bool := false
abbrev CommandElabCoreM (ε) := ReaderT Context $ StateRefT State $ EIO ε
abbrev CommandElabM := CommandElabCoreM Exception
@@ -73,6 +81,21 @@ Remark: see comment at TermElabM
@[always_inline]
instance : Monad CommandElabM := let i := inferInstanceAs (Monad CommandElabM); { pure := i.pure, bind := i.bind }
/-- Like `Core.tryCatch` but do catch runtime exceptions. -/
@[inline] protected def tryCatch (x : CommandElabM α) (h : Exception CommandElabM α) :
CommandElabM α := do
try
x
catch ex =>
if ex.isInterrupt then
throw ex
else
h ex
instance : MonadExceptOf Exception CommandElabM where
throw := throw
tryCatch := Command.tryCatch
def mkState (env : Environment) (messages : MessageLog := {}) (opts : Options := {}) : State := {
env := env
messages := messages
@@ -160,17 +183,18 @@ private def runCore (x : CoreM α) : CommandElabM α := do
let env := Kernel.resetDiag s.env
let scope := s.scopes.head!
let coreCtx : Core.Context := {
fileName := ctx.fileName
fileMap := ctx.fileMap
currRecDepth := ctx.currRecDepth
maxRecDepth := s.maxRecDepth
ref := ctx.ref
currNamespace := scope.currNamespace
openDecls := scope.openDecls
initHeartbeats := heartbeats
currMacroScope := ctx.currMacroScope
options := scope.opts
}
fileName := ctx.fileName
fileMap := ctx.fileMap
currRecDepth := ctx.currRecDepth
maxRecDepth := s.maxRecDepth
ref := ctx.ref
currNamespace := scope.currNamespace
openDecls := scope.openDecls
initHeartbeats := heartbeats
currMacroScope := ctx.currMacroScope
options := scope.opts
cancelTk? := ctx.cancelTk?
suppressElabErrors := ctx.suppressElabErrors }
let x : EIO _ _ := x.run coreCtx {
env
ngen := s.ngen
@@ -215,6 +239,11 @@ instance : MonadLog CommandElabM where
getFileName := return ( read).fileName
hasErrors := return ( get).messages.hasErrors
logMessage msg := do
if ( read).suppressElabErrors then
-- discard elaboration errors on parse error
-- NOTE: unlike `CoreM`'s `logMessage`, we do not currently have any command-level errors that
-- we want to allowlist
return
let currNamespace getCurrNamespace
let openDecls getOpenDecls
let msg := { msg with data := MessageData.withNamingContext { currNamespace := currNamespace, openDecls := openDecls } msg.data }
@@ -267,11 +296,29 @@ private def mkInfoTree (elaborator : Name) (stx : Syntax) (trees : PersistentArr
}
return InfoTree.context ctx tree
/--
Disables incremental command reuse *and* reporting for `act` if `cond` is true by setting
`Context.snap?` to `none`.
-/
def withoutCommandIncrementality (cond : Bool) (act : CommandElabM α) : CommandElabM α := do
let opts getOptions
withReader (fun ctx => { ctx with snap? := ctx.snap?.filter fun snap => Id.run do
if let some old := snap.old? then
if cond && opts.getBool `trace.Elab.reuse then
dbg_trace "reuse stopped: guard failed at {old.stx}"
return !cond
}) act
private def elabCommandUsing (s : State) (stx : Syntax) : List (KeyedDeclsAttribute.AttributeEntry CommandElab) CommandElabM Unit
| [] => withInfoTreeContext (mkInfoTree := mkInfoTree `no_elab stx) <| throwError "unexpected syntax{indentD stx}"
| (elabFn::elabFns) =>
catchInternalId unsupportedSyntaxExceptionId
(withInfoTreeContext (mkInfoTree := mkInfoTree elabFn.declName stx) <| elabFn.value stx)
(do
-- prevent unsupported commands from accidentally accessing `Context.snap?` (e.g. by nested
-- supported commands)
withoutCommandIncrementality (!( isIncrementalElab elabFn.declName)) do
withInfoTreeContext (mkInfoTree := mkInfoTree elabFn.declName stx) do
elabFn.value stx)
(fun _ => do set s; elabCommandUsing s stx elabFns)
/-- Elaborate `x` with `stx` on the macro stack -/
@@ -298,7 +345,10 @@ partial def elabCommand (stx : Syntax) : CommandElabM Unit := do
if k == nullKind then
-- list of commands => elaborate in order
-- The parser will only ever return a single command at a time, but syntax quotations can return multiple ones
args.forM elabCommand
-- TODO: support incrementality at least for some cases such as expansions of
-- `set_option in` or `def a.b`
withoutCommandIncrementality true do
args.forM elabCommand
else withTraceNode `Elab.command (fun _ => return stx) (tag :=
-- special case: show actual declaration kind for `declaration` commands
(if stx.isOfKind ``Parser.Command.declaration then stx[1] else stx).getKind.toString) do
@@ -321,11 +371,19 @@ partial def elabCommand (stx : Syntax) : CommandElabM Unit := do
builtin_initialize registerTraceClass `Elab.input
/-- Option for showing elaboration errors from partial syntax errors. -/
register_builtin_option showPartialSyntaxErrors : Bool := {
defValue := false
descr := "show elaboration errors from partial syntax trees (i.e. after parser recovery)"
}
/--
`elabCommand` wrapper that should be used for the initial invocation, not for recursive calls after
macro expansion etc.
-/
def elabCommandTopLevel (stx : Syntax) : CommandElabM Unit := withRef stx do profileitM Exception "elaboration" ( getOptions) do
withReader ({ · with suppressElabErrors :=
stx.hasMissing && !showPartialSyntaxErrors.get ( getOptions) }) do
let initMsgs modifyGet fun st => (st.messages, { st with messages := {} })
let initInfoTrees getResetInfoTrees
try
@@ -462,7 +520,12 @@ def runTermElabM (elabFn : Array Expr → TermElabM α) : CommandElabM α := do
Term.addAutoBoundImplicits' xs someType fun xs _ =>
Term.withoutAutoBoundImplicit <| elabFn xs
@[inline] def catchExceptions (x : CommandElabM Unit) : CommandElabCoreM Empty Unit := fun ctx ref =>
/--
Catches and logs exceptions occurring in `x`. Unlike `try catch` in `CommandElabM`, this function
catches interrupt exceptions as well and thus is intended for use at the top level of elaboration.
Interrupt and abort exceptions are caught but not logged.
-/
@[inline] def withLoggingExceptions (x : CommandElabM Unit) : CommandElabCoreM Empty Unit := fun ctx ref =>
EIO.catchExceptions (withLogging x ctx ref) (fun _ => pure ())
private def liftAttrM {α} (x : AttrM α) : CommandElabM α := do
@@ -528,6 +591,7 @@ def liftCommandElabM (cmd : CommandElabM α) : CoreM α := do
ref := getRef
tacticCache? := none
snap? := none
cancelTk? := ( read).cancelTk?
} |>.run {
env := getEnv
maxRecDepth := getMaxRecDepth
@@ -537,7 +601,7 @@ def liftCommandElabM (cmd : CommandElabM α) : CoreM α := do
traceState.traces := coreState.traceState.traces ++ commandState.traceState.traces
env := commandState.env
}
if let some err := commandState.messages.msgs.toArray.find? (·.severity matches .error) then
if let some err := commandState.messages.toArray.find? (·.severity matches .error) then
throwError err.data
pure a

View File

@@ -188,7 +188,7 @@ def elabClassInductive (modifiers : Modifiers) (stx : Syntax) : CommandElabM Uni
let v classInductiveSyntaxToView modifiers stx
elabInductiveViews #[v]
@[builtin_command_elab declaration]
@[builtin_command_elab declaration, builtin_incremental]
def elabDeclaration : CommandElab := fun stx => do
match ( liftMacroM <| expandDeclNamespace? stx) with
| some (ns, newStx) => do
@@ -198,22 +198,24 @@ def elabDeclaration : CommandElab := fun stx => do
| none => do
let decl := stx[1]
let declKind := decl.getKind
if declKind == ``Lean.Parser.Command.«axiom» then
let modifiers elabModifiers stx[0]
elabAxiom modifiers decl
else if declKind == ``Lean.Parser.Command.«inductive» then
let modifiers elabModifiers stx[0]
elabInductive modifiers decl
else if declKind == ``Lean.Parser.Command.classInductive then
let modifiers elabModifiers stx[0]
elabClassInductive modifiers decl
else if declKind == ``Lean.Parser.Command.«structure» then
let modifiers elabModifiers stx[0]
elabStructure modifiers decl
else if isDefLike decl then
if isDefLike decl then
-- only case implementing incrementality currently
elabMutualDef #[stx]
else
throwError "unexpected declaration"
else withoutCommandIncrementality true do
if declKind == ``Lean.Parser.Command.«axiom» then
let modifiers elabModifiers stx[0]
elabAxiom modifiers decl
else if declKind == ``Lean.Parser.Command.«inductive» then
let modifiers elabModifiers stx[0]
elabInductive modifiers decl
else if declKind == ``Lean.Parser.Command.classInductive then
let modifiers elabModifiers stx[0]
elabClassInductive modifiers decl
else if declKind == ``Lean.Parser.Command.«structure» then
let modifiers elabModifiers stx[0]
elabStructure modifiers decl
else
throwError "unexpected declaration"
/-- Return true if all elements of the mutual-block are inductive declarations. -/
private def isMutualInductive (stx : Syntax) : Bool :=
@@ -322,14 +324,16 @@ def expandMutualPreamble : Macro := fun stx =>
let endCmd `(end)
return mkNullNode (#[secCmd] ++ preamble ++ #[newMutual] ++ #[endCmd])
@[builtin_command_elab «mutual»]
@[builtin_command_elab «mutual», builtin_incremental]
def elabMutual : CommandElab := fun stx => do
if isMutualInductive stx then
elabMutualInductive stx[1].getArgs
else if isMutualDef stx then
if isMutualDef stx then
-- only case implementing incrementality currently
elabMutualDef stx[1].getArgs
else
throwError "invalid mutual block: either all elements of the block must be inductive declarations, or they must all be definitions/theorems/abbrevs"
else withoutCommandIncrementality true do
if isMutualInductive stx then
elabMutualInductive stx[1].getArgs
else
throwError "invalid mutual block: either all elements of the block must be inductive declarations, or they must all be definitions/theorems/abbrevs"
/- leading_parser "attribute " >> "[" >> sepBy1 (eraseAttr <|> Term.attrInstance) ", " >> "]" >> many1 ident -/
@[builtin_command_elab «attribute»] def elabAttr : CommandElab := fun stx => do

View File

@@ -28,14 +28,101 @@ def DefKind.isExample : DefKind → Bool
| .example => true
| _ => false
/-- Header elaboration data of a `DefView`. -/
structure DefViewElabHeaderData where
/--
Short name. Recall that all declarations in Lean 4 are potentially recursive. We use `shortDeclName` to refer
to them at `valueStx`, and other declarations in the same mutual block. -/
shortDeclName : Name
/-- Full name for this declaration. This is the name that will be added to the `Environment`. -/
declName : Name
/-- Universe level parameter names explicitly provided by the user. -/
levelNames : List Name
/-- Syntax objects for the binders occurring before `:`, we use them to populate the `InfoTree` when elaborating `valueStx`. -/
binderIds : Array Syntax
/-- Number of parameters before `:`, it also includes auto-implicit parameters automatically added by Lean. -/
numParams : Nat
/-- Type including parameters. -/
type : Expr
deriving Inhabited
section Snapshots
open Language
/-- Snapshot after processing of a definition body. -/
structure BodyProcessedSnapshot extends Language.Snapshot where
/-- State after elaboration. -/
state : Term.SavedState
/-- Elaboration result. -/
value : Expr
deriving Nonempty
instance : Language.ToSnapshotTree BodyProcessedSnapshot where
toSnapshotTree s := s.toSnapshot, #[]
/-- Snapshot after elaboration of a definition header. -/
structure HeaderProcessedSnapshot extends Language.Snapshot where
/-- Elaboration results. -/
view : DefViewElabHeaderData
/-- Resulting elaboration state, including any environment additions. -/
state : Term.SavedState
/-- Syntax of top-level tactic block if any, for checking reuse of `tacSnap?`. -/
tacStx? : Option Syntax
/-- Incremental execution of main tactic block, if any. -/
tacSnap? : Option (SnapshotTask Tactic.TacticParsedSnapshot)
/-- Syntax of definition body, for checking reuse of `bodySnap`. -/
bodyStx : Syntax
/-- Result of body elaboration. -/
bodySnap : SnapshotTask (Option BodyProcessedSnapshot)
deriving Nonempty
instance : Language.ToSnapshotTree HeaderProcessedSnapshot where
toSnapshotTree s := s.toSnapshot,
(match s.tacSnap? with
| some tac => #[tac.map (sync := true) toSnapshotTree]
| none => #[]) ++
#[s.bodySnap.map (sync := true) toSnapshotTree]
/-- State before elaboration of a mutual definition. -/
structure DefParsed where
/--
Unstructured syntax object comprising the full "header" of the definition from the modifiers
(incl. docstring) up to the value, used for determining header elaboration reuse.
-/
fullHeaderRef : Syntax
/-- Elaboration result, unless fatal exception occurred. -/
headerProcessedSnap : SnapshotTask (Option HeaderProcessedSnapshot)
deriving Nonempty
/-- Snapshot after syntax tree has been split into separate mutual def headers. -/
structure DefsParsedSnapshot extends Language.Snapshot where
/-- Definitions of this mutual block. -/
defs : Array DefParsed
deriving Nonempty, TypeName
instance : Language.ToSnapshotTree DefsParsedSnapshot where
toSnapshotTree s := s.toSnapshot,
s.defs.map (·.headerProcessedSnap.map (sync := true) toSnapshotTree)
end Snapshots
structure DefView where
kind : DefKind
ref : Syntax
/--
An unstructured syntax object that comprises the "header" of the definition, i.e. everything up
to the value. Used as a more specific ref for header elaboration.
-/
headerRef : Syntax
modifiers : Modifiers
declId : Syntax
binders : Syntax
type? : Option Syntax
value : Syntax
/--
Snapshot for incremental processing of this definition.
Invariant: If the bundle's `old?` is set, then elaboration of the header is guaranteed to result
in the same elaboration result and state, i.e. reuse is possible.
-/
headerSnap? : Option (Language.SnapshotBundle (Option HeaderProcessedSnapshot)) := none
deriving? : Option (Array Syntax) := none
deriving Inhabited
@@ -50,20 +137,20 @@ def mkDefViewOfAbbrev (modifiers : Modifiers) (stx : Syntax) : DefView :=
let (binders, type) := expandOptDeclSig stx[2]
let modifiers := modifiers.addAttribute { name := `inline }
let modifiers := modifiers.addAttribute { name := `reducible }
{ ref := stx, kind := DefKind.abbrev, modifiers,
{ ref := stx, headerRef := mkNullNode stx.getArgs[:3], kind := DefKind.abbrev, modifiers,
declId := stx[1], binders, type? := type, value := stx[3] }
def mkDefViewOfDef (modifiers : Modifiers) (stx : Syntax) : DefView :=
-- leading_parser "def " >> declId >> optDeclSig >> declVal >> optDefDeriving
let (binders, type) := expandOptDeclSig stx[2]
let deriving? := if stx[4].isNone then none else some stx[4][1].getSepArgs
{ ref := stx, kind := DefKind.def, modifiers,
{ ref := stx, headerRef := mkNullNode stx.getArgs[:3], kind := DefKind.def, modifiers,
declId := stx[1], binders, type? := type, value := stx[3], deriving? }
def mkDefViewOfTheorem (modifiers : Modifiers) (stx : Syntax) : DefView :=
-- leading_parser "theorem " >> declId >> declSig >> declVal
let (binders, type) := expandDeclSig stx[2]
{ ref := stx, kind := DefKind.theorem, modifiers,
{ ref := stx, headerRef := mkNullNode stx.getArgs[:3], kind := DefKind.theorem, modifiers,
declId := stx[1], binders, type? := some type, value := stx[3] }
def mkDefViewOfInstance (modifiers : Modifiers) (stx : Syntax) : CommandElabM DefView := do
@@ -84,7 +171,7 @@ def mkDefViewOfInstance (modifiers : Modifiers) (stx : Syntax) : CommandElabM De
trace[Elab.instance.mkInstanceName] "generated {(← getCurrNamespace) ++ id}"
pure <| mkNode ``Parser.Command.declId #[mkIdentFrom stx id, mkNullNode]
return {
ref := stx, kind := DefKind.def, modifiers := modifiers,
ref := stx, headerRef := mkNullNode stx.getArgs[:5], kind := DefKind.def, modifiers := modifiers,
declId := declId, binders := binders, type? := type, value := stx[5]
}
@@ -97,7 +184,7 @@ def mkDefViewOfOpaque (modifiers : Modifiers) (stx : Syntax) : CommandElabM DefV
let val if modifiers.isUnsafe then `(default_or_ofNonempty% unsafe) else `(default_or_ofNonempty%)
`(Parser.Command.declValSimple| := $val)
return {
ref := stx, kind := DefKind.opaque, modifiers := modifiers,
ref := stx, headerRef := mkNullNode stx.getArgs[:3], kind := DefKind.opaque, modifiers := modifiers,
declId := stx[1], binders := binders, type? := some type, value := val
}
@@ -106,7 +193,7 @@ def mkDefViewOfExample (modifiers : Modifiers) (stx : Syntax) : DefView :=
let (binders, type) := expandOptDeclSig stx[1]
let id := mkIdentFrom stx `_example
let declId := mkNode ``Parser.Command.declId #[id, mkNullNode]
{ ref := stx, kind := DefKind.example, modifiers := modifiers,
{ ref := stx, headerRef := mkNullNode stx.getArgs[:2], kind := DefKind.example, modifiers := modifiers,
declId := declId, binders := binders, type? := type, value := stx[2] }
def isDefLike (stx : Syntax) : Bool :=

View File

@@ -70,6 +70,8 @@ where
if localDecl.binderInfo.isExplicit then
if ( inferType x).isAppOf indVal.name then
rhs `($rhs ++ Format.line ++ $(mkIdent auxFunName):ident $a:ident max_prec)
else if ( isType x <||> isProof x) then
rhs `($rhs ++ Format.line ++ "_")
else
rhs `($rhs ++ Format.line ++ reprArg $a)
patterns := patterns.push ( `(@$(mkIdent ctorName):ident $ctorArgs:term*))

View File

@@ -5,7 +5,7 @@ Authors: Leonardo de Moura
-/
prelude
import Lean.InternalExceptionId
import Lean.Meta.Basic
import Lean.Exception
namespace Lean.Elab

View File

@@ -188,7 +188,7 @@ private partial def toTree (s : Syntax) : TermElabM Tree := do
the macro declaration names in the `op` nodes.
-/
let result go s
synthesizeSyntheticMVars (mayPostpone := true)
synthesizeSyntheticMVars (postpone := .yes)
return result
where
go (s : Syntax) := do
@@ -241,7 +241,10 @@ private def hasCoe (fromType toType : Expr) : TermElabM Bool := do
private structure AnalyzeResult where
max? : Option Expr := none
hasUncomparable : Bool := false -- `true` if there are two types `α` and `β` where we don't have coercions in any direction.
/-- `true` if there are two types `α` and `β` where we don't have coercions in any direction. -/
hasUncomparable : Bool := false
/-- `true` if there are any leaf terms with an unknown type (according to `isUnknown`). -/
hasUnknown : Bool := false
private def isUnknown : Expr Bool
| .mvar .. => true
@@ -255,7 +258,7 @@ private def analyze (t : Tree) (expectedType? : Option Expr) : TermElabM Analyze
match expectedType? with
| none => pure none
| some expectedType =>
let expectedType instantiateMVars expectedType
let expectedType := ( instantiateMVars expectedType).cleanupAnnotations
if isUnknown expectedType then pure none else pure (some expectedType)
(go t *> get).run' { max? }
where
@@ -268,12 +271,40 @@ where
| .binop _ _ _ lhs rhs => go lhs; go rhs
| .unop _ _ arg => go arg
| .term _ _ val =>
let type instantiateMVars ( inferType val)
unless isUnknown type do
let type := ( instantiateMVars ( inferType val)).cleanupAnnotations
if isUnknown type then
modify fun s => { s with hasUnknown := true }
else
match ( get).max? with
| none => modify fun s => { s with max? := type }
| some max =>
unless ( withNewMCtxDepth <| isDefEqGuarded max type) do
/-
Remark: Previously, we used `withNewMCtxDepth` to prevent metavariables in `max` and `type` from being assigned.
Reason: This is a heuristic procedure for introducing coercions in scenarios such as:
- Given `(n : Nat) (i : Int)`, elaborate `n = i`. The coercion must be inserted at `n`.
Consider the elaboration problem `(n + 0) + i`, where the type of term `0` is a metavariable.
We do not want it to be elaborated as `(Int.ofNat n + Int.ofNat (0 : Nat)) + i`; instead, we prefer the result to be `(Int.ofNat n + (0 : Int)) + i`.
Here is another example where we avoid assigning metavariables: `max := BitVec n` and `type := BitVec ?m`.
However, the combination `withNewMCtxDepth <| isDefEqGuarded max type` introduced performance issues in several
Mathlib files because `isDefEq` was spending a lot of time unfolding definitions in `max` and `type` before failing.
To address this issue, we allowed only reducible definitions to be unfolded during this check, using
`withNewMCtxDepth <| withReducible <| isDefEqGuarded max type`. This change fixed some performance issues but created new ones.
Lean was now spending time trying to use `hasCoe`, likely occurring in places where `withNewMCtxDepth <| isDefEqGuarded max type`
used to succeed but was now failing after we introduced `withReducible`.
We then considered using just `isDefEqGuarded max type` and changing the definition of `isUnknown`. In the new definition,
the else-case would be `| e => e.hasExprMVar` instead of `| _ => false`. However, we could not even compile this repo using
this configuration. The problem arises because some files require coercions even when `max` contains metavariables,
for example: `max := Option ?m` and `type := Name`.
As a result, rather than restricting reducibility, we decided to set `Meta.Config.isDefEqStuckEx := true`.
This means that if `isDefEq` encounters a subproblem `?m =?= a` where `?m` is non-assignable, it aborts the test
instead of unfolding definitions.
-/
unless ( withNewMCtxDepth <| withConfig (fun config => { config with isDefEqStuckEx := true }) <| isDefEqGuarded max type) do
if ( hasCoe type max) then
return ()
else if ( hasCoe max type) then
@@ -404,7 +435,7 @@ mutual
| .unop ref f arg =>
return .unop ref f ( go arg none false false)
| .term ref trees e =>
let type instantiateMVars ( inferType e)
let type := ( instantiateMVars ( inferType e)).cleanupAnnotations
trace[Elab.binop] "visiting {e} : {type} =?= {maxType}"
if isUnknown type then
if let some f := f? then
@@ -422,12 +453,17 @@ mutual
private partial def toExpr (tree : Tree) (expectedType? : Option Expr) : TermElabM Expr := do
let r analyze tree expectedType?
trace[Elab.binop] "hasUncomparable: {r.hasUncomparable}, maxType: {r.max?}"
trace[Elab.binop] "hasUncomparable: {r.hasUncomparable}, hasUnknown: {r.hasUnknown}, maxType: {r.max?}"
if r.hasUncomparable || r.max?.isNone then
let result toExprCore tree
ensureHasType expectedType? result
else
let result toExprCore ( applyCoe tree r.max?.get! (isPred := false))
unless r.hasUnknown do
-- Record the resulting maxType calculation.
-- We can do this when all the types are known, since in this case `hasUncomparable` is valid.
-- If they're not known, recording maxType like this can lead to heterogeneous operations failing to elaborate.
discard <| isDefEqGuarded ( inferType result) r.max?.get!
trace[Elab.binop] "result: {result}"
ensureHasType expectedType? result
@@ -460,7 +496,6 @@ def elabBinRelCore (noProp : Bool) (stx : Syntax) (expectedType? : Option Expr)
| some f => withSynthesizeLight do
/-
We used to use `withSynthesize (mayPostpone := true)` here instead of `withSynthesizeLight` here.
Recall that `withSynthesizeLight` is equivalent to `withSynthesize (mayPostpone := true) (synthesizeDefault := false)`.
It seems too much to apply default instances at binary relations. For example, we cannot elaborate
```
def as : List Int := [-1, 2, 0, -3, 4]
@@ -494,7 +529,7 @@ def elabBinRelCore (noProp : Bool) (stx : Syntax) (expectedType? : Option Expr)
let rhs withRef rhsStx <| toTree rhsStx
let tree := .binop stx .regular f lhs rhs
let r analyze tree none
trace[Elab.binrel] "hasUncomparable: {r.hasUncomparable}, maxType: {r.max?}"
trace[Elab.binrel] "hasUncomparable: {r.hasUncomparable}, hasUnknown: {r.hasUnknown}, maxType: {r.max?}"
if r.hasUncomparable || r.max?.isNone then
-- Use default elaboration strategy + `toBoolIfNecessary`
let lhs toExprCore lhs

View File

@@ -16,6 +16,7 @@ structure State where
parserState : Parser.ModuleParserState
cmdPos : String.Pos
commands : Array Syntax := #[]
deriving Nonempty
structure Context where
inputCtx : Parser.InputContext
@@ -34,6 +35,7 @@ def setCommandState (commandState : Command.State) : FrontendM Unit :=
fileMap := ctx.inputCtx.fileMap
tacticCache? := none
snap? := none
cancelTk? := none
}
match ( liftM <| EIO.toIO' <| (x cmdCtx).run s.commandState) with
| Except.error e => throw <| IO.Error.userError s!"unexpected internal error: {← e.toMessageData.toString}"
@@ -44,15 +46,6 @@ def elabCommandAtFrontend (stx : Syntax) : FrontendM Unit := do
let initMsgs modifyGet fun st => (st.messages, { st with messages := {} })
Command.elabCommandTopLevel stx
let mut msgs := ( get).messages
-- `stx.hasMissing` should imply `initMsgs.hasErrors`, but the latter should be cheaper to check
-- in general
if !Language.Lean.showPartialSyntaxErrors.get ( getOptions) && initMsgs.hasErrors &&
stx.hasMissing then
-- discard elaboration errors, except for a few important and unlikely misleading ones, on
-- parse error
msgs := msgs.msgs.filter fun msg =>
msg.data.hasTag (fun tag => tag == `Elab.synthPlaceholder ||
tag == `Tactic.unsolvedGoals || (`_traceMsg).isSuffixOf tag)
modify ({ · with messages := initMsgs ++ msgs })
def updateCmdPos : FrontendM Unit := do
@@ -92,6 +85,47 @@ def IO.processCommands (inputCtx : Parser.InputContext) (parserState : Parser.Mo
let (_, s) (Frontend.processCommands.run { inputCtx := inputCtx }).run { commandState := commandState, parserState := parserState, cmdPos := parserState.pos }
pure s
structure IncrementalState extends State where
inputCtx : Parser.InputContext
initialSnap : Language.Lean.CommandParsedSnapshot
deriving Nonempty
open Language in
/--
Variant of `IO.processCommands` that uses the new Lean language processor implementation for
potential incremental reuse. Pass in result of a previous invocation done with the same state
(but usually different input context) to allow for reuse.
-/
-- `IO.processCommands` can be reimplemented on top of this as soon as the additional tasks speed up
-- things instead of slowing them down
partial def IO.processCommandsIncrementally (inputCtx : Parser.InputContext)
(parserState : Parser.ModuleParserState) (commandState : Command.State)
(old? : Option IncrementalState) :
BaseIO IncrementalState := do
let task Language.Lean.processCommands inputCtx parserState commandState
(old?.map fun old => (old.inputCtx, old.initialSnap))
go task.get task #[]
where
go initialSnap t commands :=
let snap := t.get
let commands := commands.push snap.data.stx
if let some next := snap.nextCmdSnap? then
go initialSnap next commands
else
-- Opting into reuse also enables incremental reporting, so make sure to collect messages from
-- all snapshots
let messages := toSnapshotTree initialSnap
|>.getAll.map (·.diagnostics.msgLog)
|>.foldl (· ++ ·) {}
let trees := toSnapshotTree initialSnap
|>.getAll.map (·.infoTree?) |>.filterMap id |>.toPArray'
return {
commandState := { snap.data.finishedSnap.get.cmdState with messages, infoState.trees := trees }
parserState := snap.data.parserState
cmdPos := snap.data.parserState.pos
inputCtx, initialSnap, commands
}
def process (input : String) (env : Environment) (opts : Options) (fileName : Option String := none) : IO (Environment × MessageLog) := do
let fileName := fileName.getD "<input>"
let inputCtx := Parser.mkInputContext input fileName
@@ -113,8 +147,7 @@ def runFrontend
: IO (Environment × Bool) := do
let startTime := ( IO.monoNanosNow).toFloat / 1000000000
let inputCtx := Parser.mkInputContext input fileName
-- TODO: replace with `#lang` processing
if /- Lean #lang? -/ true then
if true then
-- Temporarily keep alive old cmdline driver for the Lean language so that we don't pay the
-- overhead of passing the environment between snapshots until we actually make good use of it
-- outside the server
@@ -154,9 +187,9 @@ def runFrontend
return (s.commandState.env, !s.commandState.messages.hasErrors)
let ctx := { inputCtx with mainModuleName, opts, trustLevel }
let ctx := { inputCtx with }
let processor := Language.Lean.process
let snap processor none ctx
let snap processor (fun _ => pure <| .ok { mainModuleName, opts, trustLevel }) none ctx
let snaps := Language.toSnapshotTree snap
snaps.runAndReport opts jsonOutput
if let some ileanFileName := ileanFileName? then

View File

@@ -324,7 +324,7 @@ private def elabCtors (indFVars : Array Expr) (indFVar : Expr) (params : Array E
| some ctorType =>
let type Term.elabType ctorType
trace[Elab.inductive] "elabType {ctorView.declName} : {type} "
Term.synthesizeSyntheticMVars (mayPostpone := true)
Term.synthesizeSyntheticMVars (postpone := .yes)
let type instantiateMVars type
let type checkParamOccs type
forallTelescopeReducing type fun _ resultingType => do

View File

@@ -20,28 +20,24 @@ import Lean.Elab.DeclarationRange
namespace Lean.Elab
open Lean.Parser.Term
/-- `DefView` after elaborating the header. -/
structure DefViewElabHeader where
ref : Syntax
modifiers : Modifiers
/-- Stores whether this is the header of a definition, theorem, ... -/
kind : DefKind
open Language
/-- `DefView` plus header elaboration data and snapshot. -/
structure DefViewElabHeader extends DefView, DefViewElabHeaderData where
/--
Short name. Recall that all declarations in Lean 4 are potentially recursive. We use `shortDeclName` to refer
to them at `valueStx`, and other declarations in the same mutual block. -/
shortDeclName : Name
/-- Full name for this declaration. This is the name that will be added to the `Environment`. -/
declName : Name
/-- Universe level parameter names explicitly provided by the user. -/
levelNames : List Name
/-- Syntax objects for the binders occurring before `:`, we use them to populate the `InfoTree` when elaborating `valueStx`. -/
binderIds : Array Syntax
/-- Number of parameters before `:`, it also includes auto-implicit parameters automatically added by Lean. -/
numParams : Nat
/-- Type including parameters. -/
type : Expr
/-- `Syntax` object the body/value of the definition. -/
valueStx : Syntax
Snapshot for incremental processing of top-level tactic block, if any.
Invariant: if the bundle's `old?` is set, then the state *up to the start* of the tactic block is
unchanged, i.e. reuse is possible.
-/
tacSnap? : Option (Language.SnapshotBundle Tactic.TacticParsedSnapshot)
/--
Snapshot for incremental processing of definition body.
Invariant: if the bundle's `old?` is set, then elaboration of the body is guaranteed to result in
the same elaboration result and state, i.e. reuse is possible.
-/
bodySnap? : Option (Language.SnapshotBundle (Option BodyProcessedSnapshot))
deriving Inhabited
namespace Term
@@ -127,16 +123,71 @@ private def cleanupOfNat (type : Expr) : MetaM Expr := do
let eNew := mkApp e.appFn! argArgs[1]!
return .done eNew
/-- Elaborate only the declaration headers. We have to elaborate the headers first because we support mutually recursive declarations in Lean 4. -/
private def elabHeaders (views : Array DefView) : TermElabM (Array DefViewElabHeader) := do
let expandedDeclIds views.mapM fun view => withRef view.ref do
/--
Elaborates only the declaration view headers. We have to elaborate the headers first because we
support mutually recursive declarations in Lean 4.
-/
private def elabHeaders (views : Array DefView)
(bodyPromises : Array (IO.Promise (Option BodyProcessedSnapshot)))
(tacPromises : Array (IO.Promise Tactic.TacticParsedSnapshot)) :
TermElabM (Array DefViewElabHeader) := do
let expandedDeclIds views.mapM fun view => withRef view.headerRef do
Term.expandDeclId ( getCurrNamespace) ( getLevelNames) view.declId view.modifiers
withAutoBoundImplicitForbiddenPred (fun n => expandedDeclIds.any (·.shortName == n)) do
let mut headers := #[]
for view in views, shortDeclName, declName, levelNames in expandedDeclIds do
let newHeader withRef view.ref do
addDeclarationRanges declName view.ref
-- Can we reuse the result for a body? For starters, all headers (even those below the body)
-- must be reusable
let mut reuseBody := views.all (·.headerSnap?.any (·.old?.isSome))
for view in views, shortDeclName, declName, levelNames in expandedDeclIds,
tacPromise in tacPromises, bodyPromise in bodyPromises do
let mut reusableResult? := none
if let some snap := view.headerSnap? then
-- by the `DefView.headerSnap?` invariant, safe to reuse results at this point, so let's
-- wait for them!
if let some old := snap.old?.bind (·.val.get) then
let (tacStx?, newTacTask?) mkTacTask view.value tacPromise
snap.new.resolve <| some { old with
tacStx?
tacSnap? := newTacTask?
bodyStx := view.value
bodySnap := mkBodyTask view.value bodyPromise
}
-- Transition from `DefView.snap?` to `DefViewElabHeader.tacSnap?` invariant: if all
-- headers and all previous bodies could be reused, then the state at the *start* of the
-- top-level tactic block (if any) is unchanged
let reuseTac := reuseBody
-- Transition from `DefView.snap?` to `DefViewElabHeader.bodySnap?` invariant: if all
-- headers and all previous bodies could be reused and this body syntax is unchanged, then
-- we can reuse the result
reuseBody := reuseBody &&
view.value.structRangeEqWithTraceReuse ( getOptions) old.bodyStx
let header := { old.view, view with
-- We should only forward the promise if we are actually waiting on the corresponding
-- task; otherwise, diagnostics assigned to it will be lost
tacSnap? := guard newTacTask?.isSome *> some {
old? := do
guard reuseTac
some ( old.tacStx?), ( old.tacSnap?)
new := tacPromise
}
bodySnap? := some {
-- no syntax guard to store, we already did the necessary checks
old? := guard reuseBody *> pure .missing, old.bodySnap
new := bodyPromise
}
}
reusableResult? := some (header, old.state)
else
reuseBody := false
let header withRestoreOrSaveFull reusableResult? fun save => do
withRef view.headerRef do
addDeclarationRanges declName view.ref -- NOTE: this should be the full `ref`
applyAttributesAt declName view.modifiers.attrs .beforeElaboration
-- do not hide header errors on partial body syntax as these two elaboration parts are
-- sufficiently independent
withTheReader Core.Context ({ · with suppressElabErrors :=
view.headerRef.hasMissing && !Command.showPartialSyntaxErrors.get ( getOptions) }) do
withDeclName declName <| withAutoBoundImplicit <| withLevelNames levelNames <|
elabBindersEx view.binders.getArgs fun xs => do
let refForElabFunType := view.value
@@ -164,21 +215,62 @@ private def elabHeaders (views : Array DefView) : TermElabM (Array DefViewElabHe
let pendingMVarIds getMVars type
discard <| logUnassignedUsingErrorInfos pendingMVarIds <|
getPendindMVarErrorMessage views
let newHeader := {
ref := view.ref
modifiers := view.modifiers
kind := view.kind
shortDeclName := shortDeclName
declName, type, levelNames, binderIds
numParams := xs.size
valueStx := view.value : DefViewElabHeader }
let newHeader : DefViewElabHeaderData := {
declName, shortDeclName, type, levelNames, binderIds
numParams := xs.size
}
let mut newHeader : DefViewElabHeader := { view, newHeader with
bodySnap? := none, tacSnap? := none }
if let some snap := view.headerSnap? then
let (tacStx?, newTacTask?) mkTacTask view.value tacPromise
snap.new.resolve <| some {
diagnostics :=
( Language.Snapshot.Diagnostics.ofMessageLog ( Core.getAndEmptyMessageLog))
view := newHeader.toDefViewElabHeaderData
state := ( save)
tacStx?
tacSnap? := newTacTask?
bodyStx := view.value
bodySnap := mkBodyTask view.value bodyPromise
}
newHeader := { newHeader with
-- We should only forward the promise if we are actually waiting on the
-- corresponding task; otherwise, diagnostics assigned to it will be lost
tacSnap? := guard newTacTask?.isSome *> some { old? := none, new := tacPromise }
bodySnap? := some { old? := none, new := bodyPromise }
}
check headers newHeader
return newHeader
headers := headers.push newHeader
headers := headers.push header
return headers
where
getBodyTerm? (stx : Syntax) : Option Syntax :=
-- TODO: does not work with partial syntax
--| `(Parser.Command.declVal| := $body $_suffix:suffix $[$_where]?) => body
guard (stx.isOfKind ``Parser.Command.declValSimple) *> some stx[1]
/-- Creates snapshot task with appropriate range from body syntax and promise. -/
mkBodyTask (body : Syntax) (new : IO.Promise (Option BodyProcessedSnapshot)) :
Language.SnapshotTask (Option BodyProcessedSnapshot) :=
let rangeStx := getBodyTerm? body |>.getD body
{ range? := rangeStx.getRange?, task := new.result }
/--
If `body` allows for incremental tactic reporting and reuse, creates a snapshot task out of the
passed promise with appropriate range, otherwise immediately resolves the promise to a dummy
value.
-/
mkTacTask (body : Syntax) (tacPromise : IO.Promise Tactic.TacticParsedSnapshot) :
TermElabM (Option Syntax × Option (Language.SnapshotTask Tactic.TacticParsedSnapshot))
:= do
if let some e := getBodyTerm? body then
if let `(by $tacs*) := e then
return (e, some { range? := mkNullNode tacs |>.getRange?, task := tacPromise.result })
tacPromise.resolve default
return (none, none)
/--
Create auxiliary local declarations `fs` for the given hearders using their `shortDeclName` and `type`, given hearders, and execute `k fs`.
Create auxiliary local declarations `fs` for the given headers using their `shortDeclName` and `type`, given headers, and execute `k fs`.
The new free variables are tagged as `auxDecl`.
Remark: `fs.size = headers.size`.
-/
@@ -250,15 +342,44 @@ private def declValToTerminationHint (declVal : Syntax) : TermElabM WF.Terminati
return .none
private def elabFunValues (headers : Array DefViewElabHeader) : TermElabM (Array Expr) :=
headers.mapM fun header => withDeclName header.declName <| withLevelNames header.levelNames do
let valStx liftMacroM <| declValToTerm header.valueStx
forallBoundedTelescope header.type header.numParams fun xs type => do
-- Add new info nodes for new fvars. The server will detect all fvars of a binder by the binder's source location.
for i in [0:header.binderIds.size] do
-- skip auto-bound prefix in `xs`
addLocalVarInfo header.binderIds[i]! xs[header.numParams - header.binderIds.size + i]!
let val elabTermEnsuringType valStx type
mkLambdaFVars xs val
headers.mapM fun header => do
let mut reusableResult? := none
if let some snap := header.bodySnap? then
if let some old := snap.old? then
-- guaranteed reusable as by the `bodySnap?` invariant, so let's wait on the previous
-- elaboration
if let some old := old.val.get then
snap.new.resolve <| some old
-- also make sure to reuse tactic snapshots if present so that body reuse does not lead to
-- missed tactic reuse on further changes
if let some tacSnap := header.tacSnap? then
if let some oldTacSnap := tacSnap.old? then
tacSnap.new.resolve oldTacSnap.val.get
reusableResult? := some (old.value, old.state)
withRestoreOrSaveFull reusableResult? fun save => do
withDeclName header.declName <| withLevelNames header.levelNames do
let valStx liftMacroM <| declValToTerm header.value
forallBoundedTelescope header.type header.numParams fun xs type => do
-- Add new info nodes for new fvars. The server will detect all fvars of a binder by the binder's source location.
for i in [0:header.binderIds.size] do
-- skip auto-bound prefix in `xs`
addLocalVarInfo header.binderIds[i]! xs[header.numParams - header.binderIds.size + i]!
let val withReader ({ · with tacSnap? := header.tacSnap? }) do
-- synthesize mvars here to force the top-level tactic block (if any) to run
elabTermEnsuringType valStx type <* synthesizeSyntheticMVarsNoPostponing
-- NOTE: without this `instantiatedMVars`, `mkLambdaFVars` may leave around a redex that
-- leads to more section variables being included than necessary
let val instantiateMVars val
let val mkLambdaFVars xs val
if let some snap := header.bodySnap? then
snap.new.resolve <| some {
diagnostics :=
( Language.Snapshot.Diagnostics.ofMessageLog ( Core.getAndEmptyMessageLog))
state := ( save)
value := val
}
return val
private def collectUsed (headers : Array DefViewElabHeader) (values : Array Expr) (toLift : List LetRecToLift)
: StateRefT CollectFVars.State MetaM Unit := do
@@ -640,7 +761,7 @@ def pushMain (preDefs : Array PreDefinition) (sectionVars : Array Expr) (mainHea
: TermElabM (Array PreDefinition) :=
mainHeaders.size.foldM (init := preDefs) fun i preDefs => do
let header := mainHeaders[i]!
let termination declValToTerminationHint header.valueStx
let termination declValToTerminationHint header.value
let termination := termination.rememberExtraParams header.numParams mainVals[i]!
let value mkLambdaFVars sectionVars mainVals[i]!
let type mkForallFVars sectionVars header.type
@@ -796,38 +917,40 @@ def elabMutualDef (vars : Array Expr) (views : Array DefView) : TermElabM Unit :
else
go
where
go := do
let scopeLevelNames getLevelNames
let headers elabHeaders views
let headers levelMVarToParamHeaders views headers
let allUserLevelNames := getAllUserLevelNames headers
withFunLocalDecls headers fun funFVars => do
for view in views, funFVar in funFVars do
addLocalVarInfo view.declId funFVar
let values
try
let values elabFunValues headers
Term.synthesizeSyntheticMVarsNoPostponing
values.mapM (instantiateMVars ·)
catch ex =>
logException ex
headers.mapM fun header => mkSorry header.type (synthetic := true)
let headers headers.mapM instantiateMVarsAtHeader
let letRecsToLift getLetRecsToLift
let letRecsToLift letRecsToLift.mapM instantiateMVarsAtLetRecToLift
checkLetRecsToLiftTypes funFVars letRecsToLift
withUsed vars headers values letRecsToLift fun vars => do
let preDefs MutualClosure.main vars headers funFVars values letRecsToLift
for preDef in preDefs do
trace[Elab.definition] "{preDef.declName} : {preDef.type} :=\n{preDef.value}"
let preDefs withLevelNames allUserLevelNames <| levelMVarToParamPreDecls preDefs
let preDefs instantiateMVarsAtPreDecls preDefs
let preDefs fixLevelParams preDefs scopeLevelNames allUserLevelNames
for preDef in preDefs do
trace[Elab.definition] "after eraseAuxDiscr, {preDef.declName} : {preDef.type} :=\n{preDef.value}"
checkForHiddenUnivLevels allUserLevelNames preDefs
addPreDefinitions preDefs
processDeriving headers
go :=
withAlwaysResolvedPromises views.size fun bodyPromises =>
withAlwaysResolvedPromises views.size fun tacPromises => do
let scopeLevelNames getLevelNames
let headers elabHeaders views bodyPromises tacPromises
let headers levelMVarToParamHeaders views headers
let allUserLevelNames := getAllUserLevelNames headers
withFunLocalDecls headers fun funFVars => do
for view in views, funFVar in funFVars do
addLocalVarInfo view.declId funFVar
let values
try
let values elabFunValues headers
Term.synthesizeSyntheticMVarsNoPostponing
values.mapM (instantiateMVars ·)
catch ex =>
logException ex
headers.mapM fun header => mkSorry header.type (synthetic := true)
let headers headers.mapM instantiateMVarsAtHeader
let letRecsToLift getLetRecsToLift
let letRecsToLift letRecsToLift.mapM instantiateMVarsAtLetRecToLift
checkLetRecsToLiftTypes funFVars letRecsToLift
withUsed vars headers values letRecsToLift fun vars => do
let preDefs MutualClosure.main vars headers funFVars values letRecsToLift
for preDef in preDefs do
trace[Elab.definition] "{preDef.declName} : {preDef.type} :=\n{preDef.value}"
let preDefs withLevelNames allUserLevelNames <| levelMVarToParamPreDecls preDefs
let preDefs instantiateMVarsAtPreDecls preDefs
let preDefs fixLevelParams preDefs scopeLevelNames allUserLevelNames
for preDef in preDefs do
trace[Elab.definition] "after eraseAuxDiscr, {preDef.declName} : {preDef.type} :=\n{preDef.value}"
checkForHiddenUnivLevels allUserLevelNames preDefs
addPreDefinitions preDefs
processDeriving headers
processDeriving (headers : Array DefViewElabHeader) := do
for header in headers, view in views do
@@ -842,12 +965,46 @@ end Term
namespace Command
def elabMutualDef (ds : Array Syntax) : CommandElabM Unit := do
let views ds.mapM fun d => do
let modifiers elabModifiers d[0]
if ds.size > 1 && modifiers.isNonrec then
throwErrorAt d "invalid use of 'nonrec' modifier in 'mutual' block"
mkDefView modifiers d[1]
runTermElabM fun vars => Term.elabMutualDef vars views
let opts getOptions
withAlwaysResolvedPromises ds.size fun headerPromises => do
let snap? := ( read).snap?
let mut views := #[]
let mut defs := #[]
let mut reusedAllHeaders := true
for h : i in [0:ds.size], headerPromise in headerPromises do
let d := ds[i]
let modifiers elabModifiers d[0]
if ds.size > 1 && modifiers.isNonrec then
throwErrorAt d "invalid use of 'nonrec' modifier in 'mutual' block"
let mut view mkDefView modifiers d[1]
let fullHeaderRef := mkNullNode #[d[0], view.headerRef]
if let some snap := snap? then
view := { view with headerSnap? := some {
old? := do
-- transitioning from `Context.snap?` to `DefView.headerSnap?` invariant: if the
-- elaboration context and state are unchanged, and the syntax of this as well as all
-- previous headers is unchanged, then the elaboration result for this header (which
-- includes state from elaboration of previous headers!) should be unchanged.
guard reusedAllHeaders
let old snap.old?
-- blocking wait, `HeadersParsedSnapshot` (and hopefully others) should be quick
let old old.val.get.toTyped? DefsParsedSnapshot
let oldParsed old.defs[i]?
guard <| fullHeaderRef.structRangeEqWithTraceReuse opts oldParsed.fullHeaderRef
-- no syntax guard to store, we already did the necessary checks
return .missing, oldParsed.headerProcessedSnap
new := headerPromise
} }
defs := defs.push {
fullHeaderRef
headerProcessedSnap := { range? := d.getRange?, task := headerPromise.result }
}
reusedAllHeaders := reusedAllHeaders && view.headerSnap?.any (·.old?.isSome)
views := views.push view
if let some snap := snap? then
-- no non-fatal diagnostics at this point
snap.new.resolve <| .ofTyped { defs, diagnostics := .empty : DefsParsedSnapshot }
runTermElabM fun vars => Term.elabMutualDef vars views
end Command
end Lean.Elab

View File

@@ -90,6 +90,11 @@ private def addAsAxioms (preDefs : Array PreDefinition) : TermElabM Unit := do
applyAttributesOf #[preDef] AttributeApplicationTime.afterTypeChecking
applyAttributesOf #[preDef] AttributeApplicationTime.afterCompilation
def ensureFunIndReservedNamesAvailable (preDefs : Array PreDefinition) : MetaM Unit := do
preDefs.forM fun preDef =>
withRef preDef.ref <| ensureReservedNameAvailable preDef.declName "induct"
withRef preDefs[0]!.ref <| ensureReservedNameAvailable preDefs[0]!.declName "mutual_induct"
def addPreDefinitions (preDefs : Array PreDefinition) : TermElabM Unit := withLCtx {} {} do
for preDef in preDefs do
trace[Elab.definition.body] "{preDef.declName} : {preDef.type} :=\n{preDef.value}"
@@ -121,6 +126,7 @@ def addPreDefinitions (preDefs : Array PreDefinition) : TermElabM Unit := withLC
addAndCompilePartial preDefs
preDefs.forM (·.termination.ensureNone "partial")
else
ensureFunIndReservedNamesAvailable preDefs
try
let hasHints := preDefs.any fun preDef => preDef.termination.isNotNone
if hasHints then

View File

@@ -9,6 +9,7 @@ import Lean.Meta.Tactic.Split
import Lean.Elab.PreDefinition.Basic
import Lean.Elab.PreDefinition.Eqns
import Lean.Meta.ArgsPacker.Basic
import Init.Data.Array.Basic
namespace Lean.Elab.WF
open Meta
@@ -39,41 +40,6 @@ private def rwFixEq (mvarId : MVarId) : MetaM MVarId := mvarId.withContext do
mvarId.assign ( mkEqTrans h mvarNew)
return mvarNew.mvarId!
/--
Simplify `match`-expressions when trying to prove equation theorems for a recursive declaration defined using well-founded recursion.
It is similar to `simpMatch?`, but is also tries to fold `WellFounded.fix` applications occurring in discriminants.
See comment at `tryToFoldWellFoundedFix`.
-/
def simpMatchWF? (mvarId : MVarId) : MetaM (Option MVarId) :=
mvarId.withContext do
let target instantiateMVars ( mvarId.getType)
let discharge? mvarId.withContext do SplitIf.mkDischarge?
let (targetNew, _) Simp.main target ( Split.getSimpMatchContext) (methods := { pre, discharge? })
let mvarIdNew applySimpResultToTarget mvarId target targetNew
if mvarId != mvarIdNew then return some mvarIdNew else return none
where
pre (e : Expr) : SimpM Simp.Step := do
let some app matchMatcherApp? e
| return Simp.Step.continue
-- First try to reduce matcher
match ( reduceRecMatcher? e) with
| some e' => return Simp.Step.done { expr := e' }
| none => Simp.simpMatchCore app.matcherName e
/--
Given a goal of the form `|- f.{us} a_1 ... a_n b_1 ... b_m = ...`, return `(us, #[a_1, ..., a_n])`
where `f` is a constant named `declName`, and `n = info.fixedPrefixSize`.
-/
private def getFixedPrefix (declName : Name) (info : EqnInfo) (mvarId : MVarId) : MetaM (List Level × Array Expr) := mvarId.withContext do
let target mvarId.getType'
let some (_, lhs, _) := target.eq? | unreachable!
let lhsArgs := lhs.getAppArgs
if lhsArgs.size < info.fixedPrefixSize || !lhs.getAppFn matches .const .. then
throwError "failed to generate equational theorem for '{declName}', unexpected number of arguments in the equation left-hand-side\n{mvarId}"
let result := lhsArgs[:info.fixedPrefixSize]
trace[Elab.definition.wf.eqns] "fixedPrefix: {result}"
return (lhs.getAppFn.constLevels!, result)
private partial def mkProof (declName : Name) (type : Expr) : MetaM Expr := do
trace[Elab.definition.wf.eqns] "proving: {type}"
withNewMCtxDepth do
@@ -81,11 +47,11 @@ private partial def mkProof (declName : Name) (type : Expr) : MetaM Expr := do
let (_, mvarId) main.mvarId!.intros
let rec go (mvarId : MVarId) : MetaM Unit := do
trace[Elab.definition.wf.eqns] "step\n{MessageData.ofGoal mvarId}"
if ( tryURefl mvarId) then
if withAtLeastTransparency .all (tryURefl mvarId) then
return ()
else if ( tryContradiction mvarId) then
return ()
else if let some mvarId simpMatchWF? mvarId then
else if let some mvarId simpMatch? mvarId then
go mvarId
else if let some mvarId simpIf? mvarId then
go mvarId

View File

@@ -132,12 +132,15 @@ def wfRecursion (preDefs : Array PreDefinition) : TermElabM Unit := do
return { unaryPreDef with value }
trace[Elab.definition.wf] ">> {preDefNonRec.declName} :=\n{preDefNonRec.value}"
let preDefs preDefs.mapM fun d => eraseRecAppSyntax d
if ( isOnlyOneUnaryDef preDefs fixedPrefixSize) then
addNonRec preDefNonRec (applyAttrAfterCompilation := false)
else
withEnableInfoTree false do
-- Do not complain if the user sets @[semireducible], which usually is a noop,
-- we recognize that below and then do not set @[irreducible]
withOptions (allowUnsafeReducibility.set · true) do
if ( isOnlyOneUnaryDef preDefs fixedPrefixSize) then
addNonRec preDefNonRec (applyAttrAfterCompilation := false)
addNonRecPreDefs fixedPrefixSize argsPacker preDefs preDefNonRec
else
withEnableInfoTree false do
addNonRec preDefNonRec (applyAttrAfterCompilation := false)
addNonRecPreDefs fixedPrefixSize argsPacker preDefs preDefNonRec
-- We create the `_unsafe_rec` before we abstract nested proofs.
-- Reason: the nested proofs may be referring to the _unsafe_rec.
addAndCompilePartialRec preDefs
@@ -146,6 +149,10 @@ def wfRecursion (preDefs : Array PreDefinition) : TermElabM Unit := do
for preDef in preDefs do
markAsRecursive preDef.declName
applyAttributesOf #[preDef] AttributeApplicationTime.afterCompilation
-- Unless the user asks for something else, mark the definition as irreducible
unless preDef.modifiers.attrs.any fun a =>
a.name = `reducible || a.name = `semireducible do
setIrreducibleAttribute preDef.declName
builtin_initialize registerTraceClass `Elab.definition.wf

View File

@@ -939,7 +939,7 @@ private def elabStructInstAux (stx : Syntax) (expectedType? : Option Expr) (sour
TODO: investigate whether this design decision may have unintended side effects or produce confusing behavior.
-/
let { val := r, struct, instMVars } withSynthesize (mayPostpone := true) <| elabStruct struct expectedType?
let { val := r, struct, instMVars } withSynthesize (postpone := .yes) <| elabStruct struct expectedType?
trace[Elab.struct] "before propagate {r}"
DefaultFields.propagate struct
synthesizeAppInstMVars instMVars r

View File

@@ -288,6 +288,32 @@ private def processPostponedUniverseContraints : TermElabM Unit := do
private def markAsResolved (mvarId : MVarId) : TermElabM Unit :=
modify fun s => { s with syntheticMVars := s.syntheticMVars.erase mvarId }
/--
Auxiliary type for `synthesizeSyntheticMVars`. It specifies
whether pending synthetic metavariables can be postponed or not.
-/
inductive PostponeBehavior where
/--
Any kind of pending synthetic metavariable can be postponed.
Universe constrains may also be postponed.
-/
| yes
/--
Pending synthetic metavariables cannot be postponed.
-/
| no
/--
Synthectic metavariables associated with type class resolution can be postponed.
Motivation: this kind of metavariable are not synthethic opaque, and can be assigned by `isDefEq`.
Unviverse constraints can also be postponed.
-/
| «partial»
deriving Inhabited, Repr, BEq
def PostponeBehavior.ofBool : Bool PostponeBehavior
| true => .yes
| false => .no
mutual
/--
@@ -298,7 +324,6 @@ mutual
If `report := false`, then `runTactic` will not capture exceptions nor will report unsolved goals. Unsolved goals become exceptions.
-/
partial def runTactic (mvarId : MVarId) (tacticCode : Syntax) (report := true) : TermElabM Unit := withoutAutoBoundImplicit do
let code := tacticCode[1]
instantiateMVarDeclMVars mvarId
/-
TODO: consider using `runPendingTacticsAt` at `mvarId` local context and target type.
@@ -314,26 +339,26 @@ mutual
Regarding issue #1380, we addressed the issue by avoiding the elaboration postponement step. However, the same issue can happen
in more complicated scenarios.
-/
try
let remainingGoals withInfoHole mvarId <| Tactic.run mvarId do
withTacticInfoContext tacticCode do
-- also put an info node on the `by` keyword specifically -- the token may be `canonical` and thus shown in the info
-- view even though it is synthetic while a node like `tacticCode` never is (#1990)
withTacticInfoContext tacticCode[0] do
evalTactic code
synthesizeSyntheticMVars (mayPostpone := false)
unless remainingGoals.isEmpty do
if report then
reportUnsolvedGoals remainingGoals
tryCatchRuntimeEx
(do let remainingGoals withInfoHole mvarId <| Tactic.run mvarId do
withTacticInfoContext tacticCode do
-- also put an info node on the `by` keyword specifically -- the token may be `canonical` and thus shown in the info
-- view even though it is synthetic while a node like `tacticCode` never is (#1990)
withTacticInfoContext tacticCode[0] do
withNarrowedArgTacticReuse (argIdx := 1) (evalTactic ·) tacticCode
synthesizeSyntheticMVars (postpone := .no)
unless remainingGoals.isEmpty do
if report then
reportUnsolvedGoals remainingGoals
else
throwError "unsolved goals\n{goalsToMessageData remainingGoals}")
fun ex => do
if report && ( read).errToSorry then
for mvarId in ( getMVars (mkMVar mvarId)) do
mvarId.admit
logException ex
else
throwError "unsolved goals\n{goalsToMessageData remainingGoals}"
catch ex =>
if report && ( read).errToSorry then
for mvarId in ( getMVars (mkMVar mvarId)) do
mvarId.admit
logException ex
else
throw ex
throw ex
/-- Try to synthesize the given pending synthetic metavariable. -/
private partial def synthesizeSyntheticMVar (mvarId : MVarId) (postponeOnError : Bool) (runTactics : Bool) : TermElabM Bool := do
@@ -388,25 +413,27 @@ mutual
return numSyntheticMVars != remainingPendingMVars.length
/--
Try to process pending synthetic metavariables. If `mayPostpone == false`,
then `pendingMVars` is `[]` after executing this method.
Try to process pending synthetic metavariables.
If `postpone == .no`,then `pendingMVars` is `[]` after executing this method.
If `postpone == .partial`, then `pendingMVars` contains only `.tc` and `.coe` kinds.
It keeps executing `synthesizeSyntheticMVarsStep` while progress is being made.
If `mayPostpone == false`, then it applies default instances to `SyntheticMVarKind.typeClass` (if available)
If `postpone != .yes`, then it applies default instances to `SyntheticMVarKind.typeClass` (if available)
metavariables that are still unresolved, and then tries to resolve metavariables
with `mayPostpone == false`. That is, we force them to produce error messages and/or commit to
a "best option". If, after that, we still haven't made progress, we report "stuck" errors.
with `postponeOnError == false`. That is, we force them to produce error messages and/or commit to
a "best option". If, after that, we still haven't made progress, we report "stuck" errors If `postpone == .no`.
Remark: we set `ignoreStuckTC := true` when elaborating `simp` arguments. Then,
pending TC problems become implicit parameters for the simp theorem.
-/
partial def synthesizeSyntheticMVars (mayPostpone := true) (ignoreStuckTC := false) : TermElabM Unit := do
partial def synthesizeSyntheticMVars (postpone := PostponeBehavior.yes) (ignoreStuckTC := false) : TermElabM Unit := do
let rec loop (_ : Unit) : TermElabM Unit := do
withRef ( getSomeSyntheticMVarsRef) <| withIncRecDepth do
unless ( get).pendingMVars.isEmpty do
if synthesizeSyntheticMVarsStep (postponeOnError := false) (runTactics := false) then
loop ()
else if !mayPostpone then
else if postpone != .yes then
/- Resume pending metavariables with "elaboration postponement" disabled.
We postpone elaboration errors in this step by setting `postponeOnError := true`.
Example:
@@ -431,48 +458,58 @@ mutual
loop ()
else if synthesizeSyntheticMVarsStep (postponeOnError := false) (runTactics := true) then
loop ()
else
else if postpone == .no then
reportStuckSyntheticMVars ignoreStuckTC
loop ()
unless mayPostpone do
if postpone == .no then
processPostponedUniverseContraints
end
def synthesizeSyntheticMVarsNoPostponing (ignoreStuckTC := false) : TermElabM Unit :=
synthesizeSyntheticMVars (mayPostpone := false) (ignoreStuckTC := ignoreStuckTC)
synthesizeSyntheticMVars (postpone := .no) (ignoreStuckTC := ignoreStuckTC)
/-- Keep invoking `synthesizeUsingDefault` until it returns false. -/
private partial def synthesizeUsingDefaultLoop : TermElabM Unit := do
if ( synthesizeUsingDefault) then
synthesizeSyntheticMVars (mayPostpone := true)
synthesizeSyntheticMVars (postpone := .yes)
synthesizeUsingDefaultLoop
def synthesizeSyntheticMVarsUsingDefault : TermElabM Unit := do
synthesizeSyntheticMVars (mayPostpone := true)
synthesizeSyntheticMVars (postpone := .yes)
synthesizeUsingDefaultLoop
private partial def withSynthesizeImp {α} (k : TermElabM α) (mayPostpone : Bool) (synthesizeDefault : Bool) : TermElabM α := do
let pendingMVarsSaved := ( get).pendingMVars
modify fun s => { s with pendingMVars := [] }
try
let a k
synthesizeSyntheticMVars mayPostpone
if mayPostpone && synthesizeDefault then
synthesizeUsingDefaultLoop
return a
finally
modify fun s => { s with pendingMVars := s.pendingMVars ++ pendingMVarsSaved }
private partial def withSynthesizeImp (k : TermElabM α) (postpone : PostponeBehavior) : TermElabM α := do
let pendingMVarsSaved := ( get).pendingMVars
modify fun s => { s with pendingMVars := [] }
try
let a k
synthesizeSyntheticMVars (postpone := postpone)
if postpone == .yes then
synthesizeUsingDefaultLoop
return a
finally
modify fun s => { s with pendingMVars := s.pendingMVars ++ pendingMVarsSaved }
/--
Execute `k`, and synthesize pending synthetic metavariables created while executing `k` are solved.
If `mayPostpone == false`, then all of them must be synthesized.
Remark: even if `mayPostpone == true`, the method still uses `synthesizeUsingDefault` -/
@[inline] def withSynthesize [MonadFunctorT TermElabM m] [Monad m] (k : m α) (mayPostpone := false) : m α :=
monadMap (m := TermElabM) (withSynthesizeImp · mayPostpone (synthesizeDefault := true)) k
@[inline] def withSynthesize [MonadFunctorT TermElabM m] [Monad m] (k : m α) (postpone := PostponeBehavior.no) : m α :=
monadMap (m := TermElabM) (withSynthesizeImp · postpone) k
/-- Similar to `withSynthesize`, but sets `mayPostpone` to `true`, and do not use `synthesizeUsingDefault` -/
private partial def withSynthesizeLightImp (k : TermElabM α) : TermElabM α := do
let pendingMVarsSaved := ( get).pendingMVars
modify fun s => { s with pendingMVars := [] }
try
let a k
synthesizeSyntheticMVars (postpone := .yes)
return a
finally
modify fun s => { s with pendingMVars := s.pendingMVars ++ pendingMVarsSaved }
/-- Similar to `withSynthesize`, but uses `postpone := .true`, does not use use `synthesizeUsingDefault` -/
@[inline] def withSynthesizeLight [MonadFunctorT TermElabM m] [Monad m] (k : m α) : m α :=
monadMap (m := TermElabM) (withSynthesizeImp · (mayPostpone := true) (synthesizeDefault := false)) k
monadMap (m := TermElabM) (withSynthesizeLightImp ·) k
/-- Elaborate `stx`, and make sure all pending synthetic metavariables created while elaborating `stx` are solved. -/
def elabTermAndSynthesize (stx : Syntax) (expectedType? : Option Expr) : TermElabM Expr :=

View File

@@ -34,10 +34,6 @@ structure Context where
-/
recover : Bool := true
structure SavedState where
term : Term.SavedState
tactic : State
abbrev TacticM := ReaderT Context $ StateRefT State TermElabM
abbrev Tactic := Syntax TacticM Unit
@@ -100,6 +96,16 @@ def SavedState.restore (b : SavedState) (restoreInfo := false) : TacticM Unit :=
b.term.restore restoreInfo
set b.tactic
@[specialize, inherit_doc Core.withRestoreOrSaveFull]
def withRestoreOrSaveFull (reusableResult? : Option (α × SavedState))
(cont : TacticM SavedState TacticM α) : TacticM α := do
if let some (_, state) := reusableResult? then
set state.tactic
let reusableResult? := reusableResult?.map (fun (val, state) => (val, state.term))
controlAt TermElabM fun runInBase =>
Term.withRestoreOrSaveFull reusableResult? fun restore =>
runInBase <| cont (return { term := ( restore), tactic := ( get) })
protected def getCurrMacroScope : TacticM MacroScope := do pure ( readThe Core.Context).currMacroScope
protected def getMainModule : TacticM Name := do pure ( getEnv).mainModule
@@ -146,7 +152,10 @@ partial def evalTactic (stx : Syntax) : TacticM Unit := do
| .node _ k _ =>
if k == nullKind then
-- Macro writers create a sequence of tactics `t₁ ... tₙ` using `mkNullNode #[t₁, ..., tₙ]`
stx.getArgs.forM evalTactic
-- We could support incrementality here by allocating `n` new snapshot bundles but the
-- practical value is not clear
Term.withoutTacticIncrementality true do
stx.getArgs.forM evalTactic
else withTraceNode `Elab.step (fun _ => return stx) (tag := stx.getKind.toString) do
let evalFns := tacticElabAttribute.getEntries ( getEnv) stx.getKind
let macros := macroAttribute.getEntries ( getEnv) stx.getKind
@@ -200,7 +209,11 @@ where
| [] => throwExs failures
| evalFn::evalFns => do
try
withReader ({ · with elaborator := evalFn.declName }) <| withTacticInfoContext stx <| evalFn.value stx
-- prevent unsupported tactics from accidentally accessing `Term.Context.tacSnap?`
Term.withoutTacticIncrementality (!( isIncrementalElab evalFn.declName)) do
withReader ({ · with elaborator := evalFn.declName }) do
withTacticInfoContext stx do
evalFn.value stx
catch ex => handleEx s failures ex (eval s evalFns)
def throwNoGoalsToBeSolved : TacticM α :=
@@ -231,15 +244,15 @@ def closeUsingOrAdmit (tac : TacticM Unit) : TacticM Unit := do
/- Important: we must define `closeUsingOrAdmit` before we define
the instance `MonadExcept` for `TacticM` since it backtracks the state including error messages. -/
let mvarId :: mvarIds getUnsolvedGoals | throwNoGoalsToBeSolved
try
focusAndDone tac
catch ex =>
if ( read).recover then
logException ex
admitGoal mvarId
setGoals mvarIds
else
throw ex
tryCatchRuntimeEx
(focusAndDone tac)
fun ex => do
if ( read).recover then
logException ex
admitGoal mvarId
setGoals mvarIds
else
throw ex
instance : MonadBacktrack SavedState TacticM where
saveState := Tactic.saveState

View File

@@ -29,13 +29,90 @@ open Parser.Tactic
@[builtin_tactic Lean.Parser.Tactic.«done»] def evalDone : Tactic := fun _ =>
done
@[builtin_tactic seq1] def evalSeq1 : Tactic := fun stx => do
let args := stx[0].getArgs
for i in [:args.size] do
if i % 2 == 0 then
evalTactic args[i]!
else
saveTacticInfoForToken args[i]! -- add `TacticInfo` node for `;`
open Language in
/--
Evaluates a tactic script in form of a syntax node with alternating tactics and separators as
children.
-/
partial def evalSepTactics : Tactic := goEven
where
-- `stx[0]` is the next tactic step, if any
goEven stx := do
if stx.getNumArgs == 0 then
return
let tac := stx[0]
/-
Each `goEven` step creates three promises under incrementality and reuses their older versions
where possible:
* `finished` is resolved when `tac` finishes execution; if `tac` is wholly unchanged from the
previous version, its state is reused and `tac` execution is skipped. Note that this promise
is never turned into a `SnapshotTask` and added to the snapshot tree as incremental reporting
is already covered by the next two promises.
* `inner` is passed to `tac` if it is marked as supporting incrementality and can be used for
reporting and partial reuse inside of it; if the tactic is unsupported or `finished` is wholly
reused, it is ignored.
* `next` is used as the context when invoking `goOdd` and thus eventually used for the next
`goEven` step. Thus, the incremental state of a tactic script is ultimately represented as a
chain of `next` snapshots. Its reuse is disabled if `tac` or its following separator are
changed in any way.
-/
let mut oldInner? := none
if let some snap := ( readThe Term.Context).tacSnap? then
if let some old := snap.old? then
let oldParsed := old.val.get
oldInner? := oldParsed.next.get? 0 |>.map (oldParsed.data.stx, ·)
-- compare `stx[0]` for `finished`/`next` reuse, focus on remainder of script
Term.withNarrowedTacticReuse (stx := stx) (fun stx => (stx[0], mkNullNode stx.getArgs[1:])) fun stxs => do
let some snap := ( readThe Term.Context).tacSnap?
| do evalTactic tac; goOdd stxs
let mut reusableResult? := none
let mut oldNext? := none
if let some old := snap.old? then
-- `tac` must be unchanged given the narrow above; let's reuse `finished`'s state!
let oldParsed := old.val.get
if let some state := oldParsed.data.finished.get.state? then
reusableResult? := some (state, state)
-- only allow `next` reuse in this case
oldNext? := oldParsed.next.get? 1 |>.map (old.stx, ·)
withAlwaysResolvedPromise fun next => do
withAlwaysResolvedPromise fun finished => do
withAlwaysResolvedPromise fun inner => do
snap.new.resolve <| .mk {
stx := tac
diagnostics := ( Language.Snapshot.Diagnostics.ofMessageLog
( Core.getAndEmptyMessageLog))
finished := finished.result
} #[
{
range? := tac.getRange?
task := inner.result },
{
range? := stxs |>.getRange?
task := next.result }]
let state withRestoreOrSaveFull reusableResult? fun save => do
-- set up nested reuse; `evalTactic` will check for `isIncrementalElab`
withTheReader Term.Context ({ · with
tacSnap? := some { old? := oldInner?, new := inner } }) do
evalTactic tac
save
finished.resolve { state? := state }
withTheReader Term.Context ({ · with tacSnap? := some {
new := next
old? := oldNext?
} }) do
goOdd stxs
-- `stx[0]` is the next separator, if any
goOdd stx := do
if stx.getNumArgs == 0 then
return
saveTacticInfoForToken stx[0] -- add `TacticInfo` node for `;`
-- disable further reuse on separator change as to not reuse wrong `TacticInfo`
Term.withNarrowedTacticReuse (fun stx => (stx[0], mkNullNode stx.getArgs[1:])) goEven stx
@[builtin_tactic seq1] def evalSeq1 : Tactic := fun stx =>
evalSepTactics stx[0]
@[builtin_tactic paren] def evalParen : Tactic := fun stx =>
evalTactic stx[1]
@@ -104,26 +181,20 @@ def addCheckpoints (stx : Syntax) : TacticM Syntax := do
output := output ++ currentCheckpointBlock
return stx.setArgs output
/-- Evaluate `sepByIndent tactic "; " -/
def evalSepByIndentTactic (stx : Syntax) : TacticM Unit := do
let stx addCheckpoints stx
for arg in stx.getArgs, i in [:stx.getArgs.size] do
if i % 2 == 0 then
evalTactic arg
else
saveTacticInfoForToken arg
@[builtin_tactic tacticSeq1Indented, builtin_incremental]
def evalTacticSeq1Indented : Tactic :=
Term.withNarrowedArgTacticReuse (argIdx := 0) evalSepTactics
@[builtin_tactic tacticSeq1Indented] def evalTacticSeq1Indented : Tactic := fun stx =>
evalSepByIndentTactic stx[0]
@[builtin_tactic tacticSeqBracketed] def evalTacticSeqBracketed : Tactic := fun stx => do
@[builtin_tactic tacticSeqBracketed, builtin_incremental]
def evalTacticSeqBracketed : Tactic := fun stx => do
let initInfo mkInitialTacticInfo stx[0]
withRef stx[2] <| closeUsingOrAdmit do
-- save state before/after entering focus on `{`
withInfoContext (pure ()) initInfo
evalSepByIndentTactic stx[1]
Term.withNarrowedArgTacticReuse (argIdx := 1) evalSepTactics stx
@[builtin_tactic cdot] def evalTacticCDot : Tactic := fun stx => do
@[builtin_tactic Lean.cdot, builtin_incremental]
def evalTacticCDot : Tactic := fun stx => do
-- adjusted copy of `evalTacticSeqBracketed`; we used to use the macro
-- ``| `(tactic| $cdot:cdotTk $tacs) => `(tactic| {%$cdot ($tacs) }%$cdot)``
-- but the token antiquotation does not copy trailing whitespace, leading to
@@ -132,7 +203,7 @@ def evalSepByIndentTactic (stx : Syntax) : TacticM Unit := do
withRef stx[0] <| closeUsingOrAdmit do
-- save state before/after entering focus on `·`
withInfoContext (pure ()) initInfo
evalSepByIndentTactic stx[1]
Term.withNarrowedArgTacticReuse (argIdx := 1) evalTactic stx
@[builtin_tactic Parser.Tactic.focus] def evalFocus : Tactic := fun stx => do
let mkInfo mkInitialTacticInfo stx[0]
@@ -205,8 +276,9 @@ private def getOptRotation (stx : Syntax) : Nat :=
throwError "failed on all goals"
setGoals mvarIdsNew.toList
@[builtin_tactic tacticSeq] def evalTacticSeq : Tactic := fun stx =>
evalTactic stx[0]
@[builtin_tactic tacticSeq, builtin_incremental]
def evalTacticSeq : Tactic :=
Term.withNarrowedArgTacticReuse (argIdx := 0) evalTactic
partial def evalChoiceAux (tactics : Array Syntax) (i : Nat) : TacticM Unit :=
if h : i < tactics.size then
@@ -270,7 +342,7 @@ where
pure (fvarId, [mvarId])
if let some typeStx := typeStx? then
withMainContext do
let type Term.withSynthesize (mayPostpone := true) <| Term.elabType typeStx
let type Term.withSynthesize (postpone := .yes) <| Term.elabType typeStx
let fvar := mkFVar fvarId
let fvarType inferType fvar
unless ( isDefEqGuarded type fvarType) do
@@ -392,7 +464,7 @@ def renameInaccessibles (mvarId : MVarId) (hs : TSyntaxArray ``binderIdent) : Ta
private def getCaseGoals (tag : TSyntax ``binderIdent) : TacticM (MVarId × List MVarId) := do
let gs getUnsolvedGoals
let g if let `(binderIdent| $tag:ident) := tag then
let tag := tag.getId
let tag := tag.getId.eraseMacroScopes
let some g findTag? gs tag | notFound gs tag
pure g
else
@@ -426,16 +498,16 @@ where
.group <| .nest 2 <|
.ofFormat .line ++ .joinSep items sep
@[builtin_tactic «case»] def evalCase : Tactic
| stx@`(tactic| case $[$tag $hs*]|* =>%$arr $tac:tacticSeq) =>
@[builtin_tactic «case», builtin_incremental]
def evalCase : Tactic
| stx@`(tactic| case $[$tag $hs*]|* =>%$arr $tac:tacticSeq1Indented) =>
for tag in tag, hs in hs do
let (g, gs) getCaseGoals tag
let g renameInaccessibles g hs
setGoals [g]
g.setTag Name.anonymous
withCaseRef arr tac do
closeUsingOrAdmit (withTacticInfoContext stx (evalTactic tac))
withCaseRef arr tac <| closeUsingOrAdmit <| withTacticInfoContext stx <|
Term.withNarrowedArgTacticReuse (argIdx := 3) (evalTactic ·) stx
setGoals gs
| _ => throwUnsupportedSyntax

View File

@@ -11,7 +11,7 @@ namespace Lean.Elab.Tactic
open Meta
/-- Elaborator for the `calc` tactic mode variant. -/
@[builtin_tactic calcTactic]
@[builtin_tactic Lean.calcTactic]
def evalCalc : Tactic := fun stx => withMainContext do
let steps : TSyntax ``calcSteps := stx[1]
let (val, mvarIds) withCollectingNewGoalsFrom (tagSuffix := `calc) do
@@ -32,3 +32,5 @@ def evalCalc : Tactic := fun stx => withMainContext do
return val
( getMainGoal).assign val
replaceMainGoal mvarIds
end Lean.Elab.Tactic

View File

@@ -29,7 +29,7 @@ def runTermElab (k : TermElabM α) (mayPostpone := false) : TacticM α := do
else
Term.withoutErrToSorry go
where
go := k <* Term.synthesizeSyntheticMVars (mayPostpone := mayPostpone)
go := k <* Term.synthesizeSyntheticMVars (postpone := .ofBool mayPostpone)
/-- Elaborate `stx` in the current `MVarContext`. If given, the `expectedType` will be used to help
elaboration but not enforced (use `elabTermEnsuringType` to enforce an expected type). -/

View File

@@ -54,23 +54,25 @@ private def getAltDArrow (alt : Syntax) : Syntax :=
def isHoleRHS (rhs : Syntax) : Bool :=
rhs.isOfKind ``Parser.Term.syntheticHole || rhs.isOfKind ``Parser.Term.hole
def evalAlt (mvarId : MVarId) (alt : Syntax) (addInfo : TermElabM Unit) (remainingGoals : Array MVarId) : TacticM (Array MVarId) :=
def evalAlt (mvarId : MVarId) (alt : Syntax) (addInfo : TermElabM Unit) : TacticM Unit :=
let rhs := getAltRHS alt
withCaseRef (getAltDArrow alt) rhs do
if isHoleRHS rhs then
addInfo
let gs' mvarId.withContext <| withTacticInfoContext rhs do
mvarId.withContext <| withTacticInfoContext rhs do
let mvarDecl mvarId.getDecl
let val elabTermEnsuringType rhs mvarDecl.type
mvarId.assign val
let gs' getMVarsNoDelayed val
tagUntaggedGoals mvarDecl.userName `induction gs'.toList
pure gs'
return remainingGoals ++ gs'
setGoals <| ( getGoals) ++ gs'.toList
else
setGoals [mvarId]
closeUsingOrAdmit (withTacticInfoContext alt (addInfo *> evalTactic rhs))
return remainingGoals
let goals getGoals
try
setGoals [mvarId]
closeUsingOrAdmit (withTacticInfoContext alt (addInfo *> evalTactic rhs))
finally
setGoals goals
/-!
Helper method for creating an user-defined eliminator/recursor application.
@@ -199,6 +201,9 @@ private def getAltNumFields (elimInfo : ElimInfo) (altName : Name) : TermElabM N
return altInfo.numFields
throwError "unknown alternative name '{altName}'"
private def isWildcard (altStx : Syntax) : Bool :=
getAltName altStx == `_
private def checkAltNames (alts : Array Alt) (altsSyntax : Array Syntax) : TacticM Unit :=
for i in [:altsSyntax.size] do
let altStx := altsSyntax[i]!
@@ -229,151 +234,184 @@ private def saveAltVarsInfo (altMVarId : MVarId) (altStx : Syntax) (fvarIds : Ar
Term.addLocalVarInfo altVars[i]! (mkFVar fvarId)
i := i + 1
/--
If `altsSyntax` is not empty we reorder `alts` using the order the alternatives have been provided
in `altsSyntax`. Motivations:
1- It improves the effectiveness of the `checkpoint` and `save` tactics. Consider the following example:
```lean
example (h₁ : p q) (h₂ : p → x = 0) (h₃ : q → y = 0) : x * y = 0 := by
cases h₁ with
| inr h =>
sleep 5000 -- sleeps for 5 seconds
save
have : y = 0 := h₃ h
-- We can confortably work here
| inl h => stop ...
```
If we do reorder, the `inl` alternative will be executed first. Moreover, as we type in the `inr` alternative,
type errors will "swallow" the `inl` alternative and affect the tactic state at `save` making it ineffective.
2- The errors are produced in the same order the appear in the code above. This is not super important when using IDEs.
-/
def reorderAlts (alts : Array Alt) (altsSyntax : Array Syntax) : Array Alt := Id.run do
if altsSyntax.isEmpty then
return alts
else
let mut alts := alts
let mut result := #[]
for altStx in altsSyntax do
let altName := getAltName altStx
let some i := alts.findIdx? (·.1 == altName) | return result ++ alts
result := result.push alts[i]!
alts := alts.eraseIdx i
return result ++ alts
def evalAlts (elimInfo : ElimInfo) (alts : Array Alt) (optPreTac : Syntax) (altsSyntax : Array Syntax)
open Language in
def evalAlts (elimInfo : ElimInfo) (alts : Array Alt) (optPreTac : Syntax) (altStxs : Array Syntax)
(initialInfo : Info)
(numEqs : Nat := 0) (numGeneralized : Nat := 0) (toClear : Array FVarId := #[])
(toTag : Array (Ident × FVarId) := #[]) : TacticM Unit := do
let hasAlts := altsSyntax.size > 0
let hasAlts := altStxs.size > 0
if hasAlts then
-- default to initial state outside of alts
-- HACK: because this node has the same span as the original tactic,
-- we need to take all the info trees we have produced so far and re-nest them
-- inside this node as well
let treesSaved getResetInfoTrees
withInfoContext ((modifyInfoState fun s => { s with trees := treesSaved }) *> go) (pure initialInfo)
else go
withInfoContext ((modifyInfoState fun s => { s with trees := treesSaved }) *> goWithInfo) (pure initialInfo)
else goWithInfo
where
go := do
checkAltNames alts altsSyntax
let alts := reorderAlts alts altsSyntax
let hasAlts := altsSyntax.size > 0
let mut usedWildcard := false
let mut subgoals := #[] -- when alternatives are not provided, we accumulate subgoals here
let mut altsSyntax := altsSyntax
-- continuation in the correct info context
goWithInfo := do
let hasAlts := altStxs.size > 0
if hasAlts then
if let some tacSnap := ( readThe Term.Context).tacSnap? then
-- incrementality: create a new promise for each alternative, resolve current snapshot to
-- them, eventually put each of them back in `Context.tacSnap?` in `applyAltStx`
withAlwaysResolvedPromise fun finished => do
withAlwaysResolvedPromises altStxs.size fun altPromises => do
tacSnap.new.resolve <| .mk {
-- save all relevant syntax here for comparison with next document version
stx := mkNullNode altStxs
diagnostics := .empty
finished := finished.result
} (altStxs.zipWith altPromises fun stx prom =>
{ range? := stx.getRange?, task := prom.result })
goWithIncremental <| altPromises.mapIdx fun i prom => {
old? := do
let old tacSnap.old?
-- waiting is fine here: this is the old version of the snapshot resolved above
-- immediately at the beginning of the tactic
let old := old.val.get
-- use old version of `mkNullNode altsSyntax` as guard, will be compared with new
-- version and picked apart in `applyAltStx`
return old.data.stx, ( old.next[i]?)
new := prom
}
finished.resolve { state? := ( saveState) }
return
goWithIncremental #[]
-- continuation in the correct incrementality context
goWithIncremental (tacSnaps : Array (SnapshotBundle TacticParsedSnapshot)) := do
let hasAlts := altStxs.size > 0
let mut alts := alts
-- initial sanity checks: named cases should be known, wildcards should be last
checkAltNames alts altStxs
/-
First process `altsSyntax` in order, removing covered alternatives from `alts`. Previously we
did one loop through `alts`, looking up suitable alternatives from `altsSyntax`.
Motivations for the change:
1- It improves the effectiveness of incremental reuse. Consider the following example:
```lean
example (h₁ : p q) (h₂ : p → x = 0) (h₃ : q → y = 0) : x * y = 0 := by
cases h₁ with
| inr h =>
sleep 5000 -- sleeps for 5 seconds
save
have : y = 0 := h₃ h
-- We can comfortably work here
| inl h => stop ...
```
If we iterated through `alts` instead of `altsSyntax`, the `inl` alternative would be executed
first, making partial reuse in `inr` impossible (without support for reuse with position
adjustments).
2- The errors are produced in the same order the appear in the code above. This is not super
important when using IDEs.
-/
for altStxIdx in [0:altStxs.size] do
let altStx := altStxs[altStxIdx]!
let altName := getAltName altStx
if let some i := alts.findIdx? (·.1 == altName) then
-- cover named alternative
applyAltStx tacSnaps altStxIdx altStx alts[i]!
alts := alts.eraseIdx i
else if !alts.isEmpty && isWildcard altStx then
-- cover all alternatives
for alt in alts do
applyAltStx tacSnaps altStxIdx altStx alt
alts := #[]
else
throwErrorAt altStx "unused alternative '{altName}'"
-- now process remaining alternatives; these might either be unreachable or we're in `induction`
-- without `with`. In all other cases, remaining alternatives are flagged as errors.
for { name := altName, info, mvarId := altMVarId } in alts do
let numFields getAltNumFields elimInfo altName
let mut isWildcard := false
let altStx?
match altsSyntax.findIdx? (fun alt => getAltName alt == altName) with
| some idx =>
let altStx := altsSyntax[idx]!
altsSyntax := altsSyntax.eraseIdx idx
pure (some altStx)
| none => match altsSyntax.findIdx? (fun alt => getAltName alt == `_) with
| some idx =>
isWildcard := true
pure (some altsSyntax[idx]!)
| none =>
pure none
match altStx? with
| none =>
let mut (_, altMVarId) altMVarId.introN numFields
match ( Cases.unifyEqs? numEqs altMVarId {}) with
| none => pure () -- alternative is not reachable
| some (altMVarId', subst) =>
altMVarId if info.provesMotive then
(_, altMVarId) altMVarId'.introNP numGeneralized
pure altMVarId
else
pure altMVarId'
for fvarId in toClear do
altMVarId altMVarId.tryClear fvarId
altMVarId.withContext do
for (stx, fvar) in toTag do
Term.addLocalVarInfo stx (subst.get fvar)
let altMVarIds applyPreTac altMVarId
if !hasAlts then
-- User did not provide alternatives using `|`
subgoals := subgoals ++ altMVarIds.toArray
else if altMVarIds.isEmpty then
pure ()
else
logError m!"alternative '{altName}' has not been provided"
altMVarIds.forM fun mvarId => admitGoal mvarId
| some altStx =>
(subgoals, usedWildcard) withRef altStx do
let altVars := getAltVars altStx
let numFieldsToName if altHasExplicitModifier altStx then pure numFields else getNumExplicitFields altMVarId numFields
if altVars.size > numFieldsToName then
logError m!"too many variable names provided at alternative '{altName}', #{altVars.size} provided, but #{numFieldsToName} expected"
let mut (fvarIds, altMVarId) altMVarId.introN numFields (altVars.toList.map getNameOfIdent') (useNamesForExplicitOnly := !altHasExplicitModifier altStx)
-- Delay adding the infos for the pattern LHS because we want them to nest
-- inside tacticInfo for the current alternative (in `evalAlt`)
let addInfo : TermElabM Unit := do
if ( getInfoState).enabled then
if let some declName := info.declName? then
addConstInfo (getAltNameStx altStx) declName
saveAltVarsInfo altMVarId altStx fvarIds
let unusedAlt := do
addInfo
if isWildcard then
pure (#[], usedWildcard)
else
throwError "alternative '{altName}' is not needed"
match ( Cases.unifyEqs? numEqs altMVarId {}) with
| none => unusedAlt
| some (altMVarId', subst) =>
altMVarId if info.provesMotive then
(_, altMVarId) altMVarId'.introNP numGeneralized
pure altMVarId
else
pure altMVarId'
for fvarId in toClear do
altMVarId altMVarId.tryClear fvarId
altMVarId.withContext do
for (stx, fvar) in toTag do
Term.addLocalVarInfo stx (subst.get fvar)
let altMVarIds applyPreTac altMVarId
if altMVarIds.isEmpty then
unusedAlt
else
let mut subgoals := subgoals
for altMVarId' in altMVarIds do
subgoals evalAlt altMVarId' altStx addInfo subgoals
pure (subgoals, usedWildcard || isWildcard)
if usedWildcard then
altsSyntax := altsSyntax.filter fun alt => getAltName alt != `_
unless altsSyntax.isEmpty do
logErrorAt altsSyntax[0]! "unused alternative"
setGoals subgoals.toList
let mut (_, altMVarId) altMVarId.introN numFields
let some (altMVarId', subst) Cases.unifyEqs? numEqs altMVarId {}
| continue -- alternative is not reachable
altMVarId if info.provesMotive then
(_, altMVarId) altMVarId'.introNP numGeneralized
pure altMVarId
else
pure altMVarId'
for fvarId in toClear do
altMVarId altMVarId.tryClear fvarId
altMVarId.withContext do
for (stx, fvar) in toTag do
Term.addLocalVarInfo stx (subst.get fvar)
let altMVarIds applyPreTac altMVarId
if !hasAlts then
-- User did not provide alternatives using `|`
setGoals <| ( getGoals) ++ altMVarIds
else if !altMVarIds.isEmpty then
logError m!"alternative '{altName}' has not been provided"
altMVarIds.forM fun mvarId => admitGoal mvarId
/-- Applies syntactic alternative to alternative goal. -/
applyAltStx tacSnaps altStxIdx altStx alt := withRef altStx do
let { name := altName, info, mvarId := altMVarId } := alt
-- also checks for unknown alternatives
let numFields getAltNumFields elimInfo altName
let altVars := getAltVars altStx
let numFieldsToName if altHasExplicitModifier altStx then pure numFields else getNumExplicitFields altMVarId numFields
if altVars.size > numFieldsToName then
logError m!"too many variable names provided at alternative '{altName}', #{altVars.size} provided, but #{numFieldsToName} expected"
let mut (fvarIds, altMVarId) altMVarId.introN numFields (altVars.toList.map getNameOfIdent') (useNamesForExplicitOnly := !altHasExplicitModifier altStx)
-- Delay adding the infos for the pattern LHS because we want them to nest
-- inside tacticInfo for the current alternative (in `evalAlt`)
let addInfo : TermElabM Unit := do
if ( getInfoState).enabled then
if let some declName := info.declName? then
addConstInfo (getAltNameStx altStx) declName
saveAltVarsInfo altMVarId altStx fvarIds
let unusedAlt := do
addInfo
if !isWildcard altStx then
throwError "alternative '{altName}' is not needed"
let some (altMVarId', subst) Cases.unifyEqs? numEqs altMVarId {}
| unusedAlt
altMVarId if info.provesMotive then
(_, altMVarId) altMVarId'.introNP numGeneralized
pure altMVarId
else
pure altMVarId'
for fvarId in toClear do
altMVarId altMVarId.tryClear fvarId
altMVarId.withContext do
for (stx, fvar) in toTag do
Term.addLocalVarInfo stx (subst.get fvar)
let altMVarIds applyPreTac altMVarId
if altMVarIds.isEmpty then
return ( unusedAlt)
-- select corresponding snapshot bundle for incrementality of this alternative
-- note that `tacSnaps[altStxIdx]?` is `none` if `tacSnap?` was `none` to begin with
withTheReader Term.Context ({ · with tacSnap? := tacSnaps[altStxIdx]? }) do
-- all previous alternatives have to be unchanged for reuse
Term.withNarrowedArgTacticReuse (stx := mkNullNode altStxs) (argIdx := altStxIdx) fun altStx => do
-- everything up to rhs has to be unchanged for reuse
Term.withNarrowedArgTacticReuse (stx := altStx) (argIdx := 2) fun _rhs => do
-- disable reuse if rhs is run multiple times
Term.withoutTacticIncrementality (altMVarIds.length != 1 || isWildcard altStx) do
for altMVarId' in altMVarIds do
evalAlt altMVarId' altStx addInfo
/-- Applies `induction .. with $preTac | ..`, if any, to an alternative goal. -/
applyPreTac (mvarId : MVarId) : TacticM (List MVarId) :=
if optPreTac.isNone then
return [mvarId]
else
evalTacticAt optPreTac[0] mvarId
-- disable incrementality for the pre-tactic to avoid non-monotonic progress reporting; it
-- would be possible to include a custom task around the pre-tac with an appropriate range in
-- the snapshot such that it is cached as well if it turns out that this is valuable
Term.withoutTacticIncrementality true do
evalTacticAt optPreTac[0] mvarId
end ElimApp
@@ -420,8 +458,24 @@ Return an array containing its alternatives.
private def getAltsOfInductionAlts (inductionAlts : Syntax) : Array Syntax :=
inductionAlts[2].getArgs
private def getAltsOfOptInductionAlts (optInductionAlts : Syntax) : Array Syntax :=
if optInductionAlts.isNone then #[] else getAltsOfInductionAlts optInductionAlts[0]
/--
Given `inductionAlts` of the form
```
syntax inductionAlts := "with " (tactic)? withPosition( (colGe inductionAlt)+)
```
runs `cont alts` where `alts` is an array containing all `inductionAlt`s while disabling incremental
reuse if any other syntax changed.
-/
private def withAltsOfOptInductionAlts (optInductionAlts : Syntax)
(cont : Array Syntax TacticM α) : TacticM α :=
Term.withNarrowedTacticReuse (stx := optInductionAlts) (fun optInductionAlts =>
if optInductionAlts.isNone then
-- if there are no alternatives, what to compare is irrelevant as there will be no reuse
(mkNullNode #[], mkNullNode #[])
else
-- `with` and tactic applied to all branches must be unchanged for reuse
(mkNullNode optInductionAlts[0].getArgs[:2], optInductionAlts[0].getArg 2))
(fun alts => cont alts.getArgs)
private def getOptPreTacOfOptInductionAlts (optInductionAlts : Syntax) : Syntax :=
if optInductionAlts.isNone then mkNullNode else optInductionAlts[0][1]
@@ -524,7 +578,7 @@ private def elabTermForElim (stx : Syntax) : TermElabM Expr := do
return e
Term.withoutErrToSorry <| Term.withoutHeedElabAsElim do
let e Term.elabTerm stx none (implicitLambda := false)
Term.synthesizeSyntheticMVars (mayPostpone := false) (ignoreStuckTC := true)
Term.synthesizeSyntheticMVars (postpone := .no) (ignoreStuckTC := true)
let e instantiateMVars e
let e := e.eta
if e.hasMVar then
@@ -582,12 +636,11 @@ private def generalizeTargets (exprs : Array Expr) : TacticM (Array Expr) := do
else
return exprs
@[builtin_tactic Lean.Parser.Tactic.induction] def evalInduction : Tactic := fun stx =>
@[builtin_tactic Lean.Parser.Tactic.induction, builtin_incremental]
def evalInduction : Tactic := fun stx =>
match expandInduction? stx with
| some stxNew => withMacroExpansion stx stxNew <| evalTactic stxNew
| _ => focus do
let optInductionAlts := stx[4]
let alts := getAltsOfOptInductionAlts optInductionAlts
let targets withMainContext <| stx[1].getSepArgs.mapM (elabTerm · none)
let targets generalizeTargets targets
let elimInfo withMainContext <| getElimNameInfo stx[2] targets (induction := true)
@@ -605,10 +658,15 @@ private def generalizeTargets (exprs : Array Expr) : TacticM (Array Expr) := do
ElimApp.mkElimApp elimInfo targets tag
trace[Elab.induction] "elimApp: {result.elimApp}"
ElimApp.setMotiveArg mvarId result.motive targetFVarIds
let optPreTac := getOptPreTacOfOptInductionAlts optInductionAlts
mvarId.assign result.elimApp
ElimApp.evalAlts elimInfo result.alts optPreTac alts initInfo (numGeneralized := n) (toClear := targetFVarIds)
appendGoals result.others.toList
-- drill down into old and new syntax: allow reuse of an rhs only if everything before it is
-- unchanged
-- everything up to the alternatives must be unchanged for reuse
Term.withNarrowedArgTacticReuse (stx := stx) (argIdx := 4) fun optInductionAlts => do
withAltsOfOptInductionAlts optInductionAlts fun alts => do
let optPreTac := getOptPreTacOfOptInductionAlts optInductionAlts
mvarId.assign result.elimApp
ElimApp.evalAlts elimInfo result.alts optPreTac alts initInfo (numGeneralized := n) (toClear := targetFVarIds)
appendGoals result.others.toList
where
checkTargets (targets : Array Expr) : MetaM Unit := do
let mut foundFVars : FVarIdSet := {}
@@ -650,15 +708,13 @@ def elabCasesTargets (targets : Array Syntax) : TacticM (Array Expr × Array (Id
else
return (args.map (·.expr), #[])
@[builtin_tactic Lean.Parser.Tactic.cases] def evalCases : Tactic := fun stx =>
@[builtin_tactic Lean.Parser.Tactic.cases, builtin_incremental]
def evalCases : Tactic := fun stx =>
match expandCases? stx with
| some stxNew => withMacroExpansion stx stxNew <| evalTactic stxNew
| _ => focus do
-- leading_parser nonReservedSymbol "cases " >> sepBy1 (group majorPremise) ", " >> usingRec >> optInductionAlts
let (targets, toTag) elabCasesTargets stx[1].getSepArgs
let optInductionAlts := stx[3]
let optPreTac := getOptPreTacOfOptInductionAlts optInductionAlts
let alts := getAltsOfOptInductionAlts optInductionAlts
let targetRef := stx[1]
let elimInfo withMainContext <| getElimNameInfo stx[2] targets (induction := false)
let mvarId getMainGoal
@@ -676,8 +732,14 @@ def elabCasesTargets (targets : Array Syntax) : TacticM (Array Expr × Array (Id
mvarId.withContext do
ElimApp.setMotiveArg mvarId elimArgs[elimInfo.motivePos]!.mvarId! targetsNew
mvarId.assign result.elimApp
ElimApp.evalAlts elimInfo result.alts optPreTac alts initInfo
(numEqs := targets.size) (toClear := targetsNew) (toTag := toTag)
-- drill down into old and new syntax: allow reuse of an rhs only if everything before it is
-- unchanged
-- everything up to the alternatives must be unchanged for reuse
Term.withNarrowedArgTacticReuse (stx := stx) (argIdx := 3) fun optInductionAlts => do
withAltsOfOptInductionAlts optInductionAlts fun alts => do
let optPreTac := getOptPreTacOfOptInductionAlts optInductionAlts
ElimApp.evalAlts elimInfo result.alts optPreTac alts initInfo
(numEqs := targets.size) (toClear := targetsNew) (toTag := toTag)
builtin_initialize
registerTraceClass `Elab.cases

View File

@@ -14,7 +14,7 @@ open Term
def runTactic (mvarId : MVarId) (tacticCode : Syntax) (ctx : Context := {}) (s : State := {}) : MetaM (List MVarId × State) := do
instantiateMVarDeclMVars mvarId
let go : TermElabM (List MVarId) :=
withSynthesize (mayPostpone := false) do Tactic.run mvarId (Tactic.evalTactic tacticCode *> Tactic.pruneSolvedGoals)
withSynthesize do Tactic.run mvarId (Tactic.evalTactic tacticCode *> Tactic.pruneSolvedGoals)
go.run ctx s
end Lean.Elab

View File

@@ -532,7 +532,9 @@ Helpful error message when omega cannot find a solution
def formatErrorMessage (p : Problem) : OmegaM MessageData := do
if p.possible then
if p.isEmpty then
return m!"it is false"
return m!"No usable constraints found. You may need to unfold definitions so `omega` can see \
linear arithmetic facts about `Nat` and `Int`, which may also involve multiplication, \
division, and modular remainder by constants."
else
let as atoms
let mask mentioned p.constraints

View File

@@ -71,7 +71,7 @@ abbrev OmegaM := StateRefT Cache OmegaM'
/-- Run a computation in the `OmegaM` monad, starting with no recorded atoms. -/
def OmegaM.run (m : OmegaM α) (cfg : OmegaConfig) : MetaM α :=
m.run' HashMap.empty |>.run' {} { cfg } |>.run
m.run' HashMap.empty |>.run' {} { cfg } |>.run'
/-- Retrieve the user-specified configuration options. -/
def cfg : OmegaM OmegaConfig := do pure ( read).cfg
@@ -150,7 +150,7 @@ partial def groundInt? (e : Expr) : Option Int :=
| _, _ => none
| _ => e.int?
where op (f : Int Int Int) (x y : Expr) : Option Int :=
match groundNat? x, groundNat? y with
match groundInt? x, groundInt? y with
| some x', some y' => some (f x' y')
| _, _ => none
@@ -199,7 +199,7 @@ def analyzeAtom (e : Expr) : OmegaM (HashSet Expr) := do
| some _ =>
let b_pos := mkApp4 (.const ``LT.lt [0]) (.const ``Int []) (.const ``Int.instLTInt [])
(toExpr (0 : Int)) b
let pow_pos := mkApp3 (.const ``Int.pos_pow_of_pos []) b exp ( mkDecideProof b_pos)
let pow_pos := mkApp3 (.const ``Lean.Omega.Int.pos_pow_of_pos []) b exp ( mkDecideProof b_pos)
pure <| HashSet.empty.insert
(mkApp3 (.const ``Int.emod_nonneg []) x k
(mkApp3 (.const ``Int.ne_of_gt []) k (toExpr (0 : Int)) pow_pos)) |>.insert

View File

@@ -46,7 +46,7 @@ def tacticToDischarge (tacticCode : Syntax) : TacticM (IO.Ref Term.State × Simp
So, we must not save references to them at `Term.State`.
-/
withoutModifyingStateWithInfoAndMessages do
Term.withSynthesize (mayPostpone := false) do
Term.withSynthesize (postpone := .no) do
Term.runTactic (report := false) mvar.mvarId! tacticCode
let result instantiateMVars mvar
if result.hasExprMVar then
@@ -121,7 +121,7 @@ private def addDeclToUnfoldOrTheorem (thms : SimpTheorems) (id : Origin) (e : Ex
private def addSimpTheorem (thms : SimpTheorems) (id : Origin) (stx : Syntax) (post : Bool) (inv : Bool) : TermElabM SimpTheorems := do
let (levelParams, proof) Term.withoutModifyingElabMetaStateWithInfo <| withRef stx <| Term.withoutErrToSorry do
let e Term.elabTerm stx none
Term.synthesizeSyntheticMVars (mayPostpone := false) (ignoreStuckTC := true)
Term.synthesizeSyntheticMVars (postpone := .no) (ignoreStuckTC := true)
let e instantiateMVars e
let e := e.eta
if e.hasMVar then
@@ -178,9 +178,7 @@ def elabSimpArgs (stx : Syntax) (ctx : Simp.Context) (simprocs : Simp.SimprocsAr
thms := thms.eraseCore (.fvar fvar.fvarId!)
else
let id := arg[1]
let declNames? try pure (some ( realizeGlobalConst id)) catch _ => pure none
if let some declNames := declNames? then
let declName ensureNonAmbiguous id declNames
if let .ok declName observing (realizeGlobalConstNoOverloadWithInfo id) then
if ( Simp.isSimproc declName) then
simprocs := simprocs.erase declName
else if ctx.config.autoUnfold then

View File

@@ -13,6 +13,7 @@ import Lean.Elab.Config
import Lean.Elab.Level
import Lean.Elab.DeclModifiers
import Lean.Elab.PreDefinition.WF.TerminationHint
import Lean.Language.Basic
namespace Lean.Elab
@@ -112,6 +113,14 @@ structure State where
letRecsToLift : List LetRecToLift := []
deriving Inhabited
/--
Backtrackable state for the `TermElabM` monad.
-/
structure SavedState where
meta : Meta.SavedState
«elab» : State
deriving Nonempty
end Term
namespace Tactic
@@ -152,6 +161,42 @@ structure Cache where
post : PHashMap CacheKey Snapshot := {}
deriving Inhabited
section Snapshot
open Language
structure SavedState where
term : Term.SavedState
tactic : State
/-- State after finishing execution of a tactic. -/
structure TacticFinished where
/-- Reusable state, if no fatal exception occurred. -/
state? : Option SavedState
deriving Inhabited
/-- Snapshot just before execution of a tactic. -/
structure TacticParsedSnapshotData extends Language.Snapshot where
/-- Syntax tree of the tactic, stored and compared for incremental reuse. -/
stx : Syntax
/-- Task for state after tactic execution. -/
finished : Task TacticFinished
deriving Inhabited
/-- State after execution of a single synchronous tactic step. -/
inductive TacticParsedSnapshot where
| mk (data : TacticParsedSnapshotData) (next : Array (SnapshotTask TacticParsedSnapshot))
deriving Inhabited
abbrev TacticParsedSnapshot.data : TacticParsedSnapshot TacticParsedSnapshotData
| .mk data _ => data
/-- Potential, potentially parallel, follow-up tactic executions. -/
-- In the first, non-parallel version, each task will depend on its predecessor
abbrev TacticParsedSnapshot.next : TacticParsedSnapshot Array (SnapshotTask TacticParsedSnapshot)
| .mk _ next => next
partial instance : ToSnapshotTree TacticParsedSnapshot where
toSnapshotTree := go where
go := fun s, next => s.toSnapshot, next.map (·.map (sync := true) go)
end Snapshot
end Tactic
namespace Term
@@ -211,6 +256,13 @@ structure Context where
/-- Cache for the `save` tactic. It is only `some` in the LSP server. -/
tacticCache? : Option (IO.Ref Tactic.Cache) := none
/--
Snapshot for incremental processing of current tactic, if any.
Invariant: if the bundle's `old?` is set, then the state *up to the start* of the tactic is
unchanged, i.e. reuse is possible.
-/
tacSnap? : Option (Language.SnapshotBundle Tactic.TacticParsedSnapshot) := none
/--
If `true`, we store in the `Expr` the `Syntax` for recursive applications (i.e., applications
of free variables tagged with `isAuxDecl`). We store the `Syntax` using `mkRecAppWithSyntax`.
We use the `Syntax` object to produce better error messages at `Structural.lean` and `WF.lean`. -/
@@ -241,14 +293,6 @@ open Meta
instance : Inhabited (TermElabM α) where
default := throw default
/--
Backtrackable state for the `TermElabM` monad.
-/
structure SavedState where
meta : Meta.SavedState
«elab» : State
deriving Nonempty
protected def saveState : TermElabM SavedState :=
return { meta := ( Meta.saveState), «elab» := ( get) }
@@ -261,18 +305,87 @@ def SavedState.restore (s : SavedState) (restoreInfo : Bool := false) : TermElab
unless restoreInfo do
setInfoState infoState
/--
Restores full state including sources for unique identifiers. Only intended for incremental reuse
between elaboration runs, not for backtracking within a single run.
-/
def SavedState.restoreFull (s : SavedState) : TermElabM Unit := do
s.meta.restoreFull
set s.elab
@[specialize, inherit_doc Core.withRestoreOrSaveFull]
def withRestoreOrSaveFull (reusableResult? : Option (α × SavedState))
(cont : TermElabM SavedState TermElabM α) : TermElabM α := do
if let some (_, state) := reusableResult? then
set state.elab
let reusableResult? := reusableResult?.map (fun (val, state) => (val, state.meta))
controlAt MetaM fun runInBase =>
Meta.withRestoreOrSaveFull reusableResult? fun restore =>
runInBase <| cont (return { meta := ( restore), «elab» := ( get) })
instance : MonadBacktrack SavedState TermElabM where
saveState := Term.saveState
restoreState b := b.restore
/--
Manages reuse information for nested tactics by `split`ting given syntax into an outer and inner
part. `act` is then run on the inner part but with reuse information adjusted as following:
* If the old (from `tacSnap?`'s `SyntaxGuarded.stx`) and new (from `stx`) outer syntax are not
identical according to `Syntax.structRangeEq`, reuse is disabled.
* Otherwise, the old syntax as stored in `tacSnap?` is updated to the old *inner* syntax.
* In any case, we also use `withRef` on the inner syntax to avoid leakage of the outer syntax into
`act` via this route.
For any tactic that participates in reuse, `withNarrowedTacticReuse` should be applied to the
tactic's syntax and `act` should be used to do recursive tactic evaluation of nested parts.
-/
def withNarrowedTacticReuse [Monad m] [MonadExceptOf Exception m] [MonadWithReaderOf Context m]
[MonadOptions m] [MonadRef m] (split : Syntax Syntax × Syntax) (act : Syntax m α)
(stx : Syntax) : m α := do
let (outer, inner) := split stx
let opts getOptions
withTheReader Term.Context (fun ctx => { ctx with tacSnap? := ctx.tacSnap?.map fun tacSnap =>
{ tacSnap with old? := tacSnap.old?.bind fun old => do
let (oldOuter, oldInner) := split old.stx
guard <| outer.structRangeEqWithTraceReuse opts oldOuter
return { old with stx := oldInner }
}
}) do
withRef inner do
act inner
/--
A variant of `withNarrowedTacticReuse` that uses `stx[argIdx]` as the inner syntax and all `stx`
child nodes before that as the outer syntax, i.e. reuse is disabled if there was any change before
`argIdx`.
NOTE: child nodes after `argIdx` are not tested (which would almost always disable reuse as they are
necessarily shifted by changes at `argIdx`) so it must be ensured that the result of `arg` does not
depend on them (i.e. they should not be inspected beforehand).
-/
def withNarrowedArgTacticReuse [Monad m] [MonadExceptOf Exception m] [MonadWithReaderOf Context m]
[MonadOptions m] [MonadRef m] (argIdx : Nat) (act : Syntax m α) (stx : Syntax) : m α :=
withNarrowedTacticReuse (fun stx => (mkNullNode stx.getArgs[:argIdx], stx[argIdx])) act stx
/--
Disables incremental tactic reuse *and* reporting for `act` if `cond` is true by setting `tacSnap?`
to `none`. This should be done for tactic blocks that are run multiple times as otherwise the
reported progress will jump back and forth (and partial reuse for these kinds of tact blocks is
similarly questionable).
-/
def withoutTacticIncrementality [Monad m] [MonadWithReaderOf Context m] [MonadOptions m] [MonadRef m]
(cond : Bool) (act : m α) : m α := do
let opts getOptions
withTheReader Term.Context (fun ctx => { ctx with tacSnap? := ctx.tacSnap?.filter fun tacSnap => Id.run do
if let some old := tacSnap.old? then
if cond && opts.getBool `trace.Elab.reuse then
dbg_trace "reuse stopped: guard failed at {old.stx}"
return !cond
}) act
/-- Disables incremental tactic reuse for `act` if `cond` is true. -/
def withoutTacticReuse [Monad m] [MonadWithReaderOf Context m] [MonadOptions m] [MonadRef m]
(cond : Bool) (act : m α) : m α := do
let opts getOptions
withTheReader Term.Context (fun ctx => { ctx with tacSnap? := ctx.tacSnap?.map fun tacSnap =>
{ tacSnap with old? := tacSnap.old?.filter fun old => Id.run do
if cond && opts.getBool `trace.Elab.reuse then
dbg_trace "reuse stopped: guard failed at {old.stx}"
return !cond }
}) act
abbrev TermElabResult (α : Type) := EStateM.Result Exception SavedState α
/--
@@ -784,7 +897,7 @@ def synthesizeInstMVarCore (instMVar : MVarId) (maxResultSize? : Option Nat := n
if ( read).ignoreTCFailures then
return false
else
throwError "failed to synthesize instance{indentExpr type}"
throwError "failed to synthesize{indentExpr type}\n{useDiagnosticMsg}"
def mkCoe (expectedType : Expr) (e : Expr) (f? : Option Expr := none) (errorMsgHeader? : Option String := none) : TermElabM Expr := do
withTraceNode `Elab.coe (fun _ => return m!"adding coercion for {e} : {← inferType e} =?= {expectedType}") do
@@ -1523,14 +1636,15 @@ partial def withAutoBoundImplicit (k : TermElabM α) : TermElabM α := do
let flag := autoImplicit.get ( getOptions)
if flag then
withReader (fun ctx => { ctx with autoBoundImplicit := flag, autoBoundImplicits := {} }) do
let rec loop (s : SavedState) : TermElabM α := do
let rec loop (s : SavedState) : TermElabM α := withIncRecDepth do
checkSystem "auto-implicit"
try
k
catch
| ex => match isAutoBoundImplicitLocalException? ex with
| some n =>
-- Restore state, declare `n`, and try again
s.restore
s.restore (restoreInfo := true)
withLocalDecl n .implicit ( mkFreshTypeMVar) fun x =>
withReader (fun ctx => { ctx with autoBoundImplicits := ctx.autoBoundImplicits.push x } ) do
loop ( saveState)
@@ -1768,6 +1882,33 @@ builtin_initialize
registerTraceClass `Elab.debug
registerTraceClass `Elab.reuse
builtin_initialize incrementalAttr : TagAttribute
registerTagAttribute `incremental "Marks an elaborator (tactic or command, currently) as \
supporting incremental elaboration. For unmarked elaborators, the corresponding snapshot bundle \
field in the elaboration context is unset so as to prevent accidental, incorrect reuse."
builtin_initialize builtinIncrementalElabs : IO.Ref NameSet IO.mkRef {}
def addBuiltinIncrementalElab (decl : Name) : IO Unit := do
builtinIncrementalElabs.modify fun s => s.insert decl
builtin_initialize
registerBuiltinAttribute {
name := `builtin_incremental
descr := s!"(builtin) {incrementalAttr.attr.descr}"
applicationTime := .afterCompilation
add := fun decl stx kind => do
Attribute.Builtin.ensureNoArgs stx
unless kind == AttributeKind.global do
throwError "invalid attribute 'builtin_incremental', must be global"
declareBuiltin decl <| mkApp (mkConst ``addBuiltinIncrementalElab) (toExpr decl)
}
/-- Checks whether a declaration is annotated with `[builtin_incremental]` or `[incremental]`. -/
def isIncrementalElab [Monad m] [MonadEnv m] [MonadLiftT IO m] (decl : Name) : m Bool :=
(return ( builtinIncrementalElabs.get (m := IO)).contains decl) <||>
(return incrementalAttr.hasTag ( getEnv) decl)
export Term (TermElabM)
end Lean.Elab

View File

@@ -205,7 +205,7 @@ def logException [Monad m] [MonadLog m] [AddMessageContext m] [MonadOptions m] [
match ex with
| Exception.error ref msg => logErrorAt ref msg
| Exception.internal id _ =>
unless isAbortExceptionId id do
unless isAbortExceptionId id || id == Core.interruptExceptionId do
let name id.getName
logError m!"internal exception: {name}"

View File

@@ -246,7 +246,7 @@ namespace Environment
/-- Type check given declaration and add it to the environment -/
@[extern "lean_add_decl"]
opaque addDecl (env : Environment) (decl : @& Declaration) : Except KernelException Environment
opaque addDeclCore (env : Environment) (maxHeartbeats : USize) (decl : @& Declaration) : Except KernelException Environment
end Environment

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