mirror of
https://github.com/leanprover/lean4.git
synced 2026-03-19 03:14:08 +00:00
Compare commits
101 Commits
deprecated
...
variable_s
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
84f266f2e9 | ||
|
|
2bc41d8f3a | ||
|
|
f97a7d4234 | ||
|
|
23a202b6be | ||
|
|
ff37e5d512 | ||
|
|
c2b8a1e618 | ||
|
|
8ca00918fb | ||
|
|
6155513c60 | ||
|
|
d6709eb157 | ||
|
|
e6be8b90f5 | ||
|
|
82401938cf | ||
|
|
3de60bb1f6 | ||
|
|
8c03650359 | ||
|
|
2faa81d41f | ||
|
|
097a4d5b6b | ||
|
|
f512826b9a | ||
|
|
7c053259d3 | ||
|
|
f3ccd6b023 | ||
|
|
7ece5d56e3 | ||
|
|
a7338c5ad8 | ||
|
|
b278f9dd30 | ||
|
|
f0471a519b | ||
|
|
42215cc072 | ||
|
|
f53b778c0d | ||
|
|
72b345c621 | ||
|
|
6171070deb | ||
|
|
7c5249278e | ||
|
|
239ade80dc | ||
|
|
47c8e340d6 | ||
|
|
c8b72beb4d | ||
|
|
9803c5dd63 | ||
|
|
d66d00dece | ||
|
|
9fde33a09f | ||
|
|
b639d102d1 | ||
|
|
02b6fb3f41 | ||
|
|
9f6bbfa106 | ||
|
|
1ff0e7a2f2 | ||
|
|
3cb6eb0ae6 | ||
|
|
489d2d11ec | ||
|
|
7648bf255c | ||
|
|
4d2ff6fb04 | ||
|
|
ee0bcc8321 | ||
|
|
1382e9fbc4 | ||
|
|
e8c4540f87 | ||
|
|
f2a304e555 | ||
|
|
3a457e6ad6 | ||
|
|
2a966b46f2 | ||
|
|
8204b79b3c | ||
|
|
f63616891f | ||
|
|
9a8e7a6411 | ||
|
|
c7741607fb | ||
|
|
82666e5e7c | ||
|
|
aeea7fdf5d | ||
|
|
3035d2f8f6 | ||
|
|
3493d066e4 | ||
|
|
d0e34aaed5 | ||
|
|
367b97885a | ||
|
|
f3538dbdfa | ||
|
|
770235855f | ||
|
|
0a515e2ec9 | ||
|
|
91244b2dd9 | ||
|
|
de5e039c83 | ||
|
|
61a84c96db | ||
|
|
98b2681d0e | ||
|
|
7c4284aa91 | ||
|
|
842280321b | ||
|
|
d833f82fe8 | ||
|
|
a17c3f424c | ||
|
|
799923d145 | ||
|
|
f74980ccee | ||
|
|
b8f2f28e0d | ||
|
|
0d9af1b777 | ||
|
|
7db8e6482e | ||
|
|
147aeaea45 | ||
|
|
a875ae3acf | ||
|
|
25e94f916f | ||
|
|
a1be9ec850 | ||
|
|
e237e12478 | ||
|
|
a6d186a81d | ||
|
|
6c6b56e7fc | ||
|
|
228ff58f3a | ||
|
|
dcdc3db3d4 | ||
|
|
39286862e3 | ||
|
|
ca6437df71 | ||
|
|
3491c56c49 | ||
|
|
368adaf847 | ||
|
|
6a040ab068 | ||
|
|
fe7b96d8a0 | ||
|
|
ec87283465 | ||
|
|
d7c6920550 | ||
|
|
227e861719 | ||
|
|
e9c302c17e | ||
|
|
5814a45d44 | ||
|
|
dcf74b0d89 | ||
|
|
a257767417 | ||
|
|
b8e67d87a8 | ||
|
|
2a5ca00ad6 | ||
|
|
ec27b3760d | ||
|
|
e5b7dc819b | ||
|
|
93c06c0552 | ||
|
|
bb7e6e4769 |
16
.github/ISSUE_TEMPLATE/bug_report.md
vendored
16
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@@ -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
|
||||
|
||||
|
||||
17
.github/workflows/ci.yml
vendored
17
.github/workflows/ci.yml
vendored
@@ -6,7 +6,6 @@ on:
|
||||
tags:
|
||||
- '*'
|
||||
pull_request:
|
||||
types: [opened, synchronize, reopened, labeled]
|
||||
merge_group:
|
||||
schedule:
|
||||
- cron: '0 7 * * *' # 8AM CET/11PM PT
|
||||
@@ -41,12 +40,18 @@ jobs:
|
||||
steps:
|
||||
- name: Run quick CI?
|
||||
id: set-quick
|
||||
env:
|
||||
quick: ${{
|
||||
github.event_name == 'pull_request' && !contains( github.event.pull_request.labels.*.name, 'full-ci')
|
||||
}}
|
||||
# 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 settings the `full-ci` label.
|
||||
run: |
|
||||
echo "quick=${{env.quick}}" >> "$GITHUB_OUTPUT"
|
||||
if [ "${{ github.event_name }}" == 'pull_request' ]
|
||||
then
|
||||
echo "quick=$(gh api repos/${{ github.repository_owner }}/${{ github.event.repository.name }}/pulls/${{ github.event.pull_request.number }} --jq '.labels | any(.name == "full-ci") | not')" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "quick=false" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
|
||||
- name: Configure build matrix
|
||||
id: set-matrix
|
||||
|
||||
1
.github/workflows/nix-ci.yml
vendored
1
.github/workflows/nix-ci.yml
vendored
@@ -6,7 +6,6 @@ on:
|
||||
tags:
|
||||
- '*'
|
||||
pull_request:
|
||||
types: [opened, synchronize, reopened, labeled]
|
||||
merge_group:
|
||||
|
||||
concurrency:
|
||||
|
||||
32
.github/workflows/pr-release.yml
vendored
32
.github/workflows/pr-release.yml
vendored
@@ -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
31
.github/workflows/restart-on-label.yml
vendored
Normal 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, 'full-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 }}
|
||||
482
RELEASES.md
482
RELEASES.md
@@ -8,10 +8,34 @@ This file contains work-in-progress notes for the upcoming release, as well as p
|
||||
Please check the [releases](https://github.com/leanprover/lean4/releases) page for the current status
|
||||
of each version.
|
||||
|
||||
v4.8.0
|
||||
v4.9.0 (development in progress)
|
||||
---------
|
||||
|
||||
v4.8.0
|
||||
* 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.
|
||||
|
||||
* 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`.
|
||||
|
||||
v4.8.0
|
||||
---------
|
||||
|
||||
* **Executables configured with `supportInterpreter := true` on Windows should now be run via `lake exe` to function properly.**
|
||||
@@ -108,8 +132,462 @@ v4.8.0
|
||||
* 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.
|
||||
### Language features, tactics, and metaprograms
|
||||
|
||||
Breaking changes:
|
||||
* **Functional induction principles.**
|
||||
[#3432](https://github.com/leanprover/lean4/pull/3432), [#3620](https://github.com/leanprover/lean4/pull/3620),
|
||||
[#3754](https://github.com/leanprover/lean4/pull/3754), [#3762](https://github.com/leanprover/lean4/pull/3762),
|
||||
[#3738](https://github.com/leanprover/lean4/pull/3738), [#3776](https://github.com/leanprover/lean4/pull/3776),
|
||||
[#3898](https://github.com/leanprover/lean4/pull/3898).
|
||||
|
||||
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`.
|
||||
* [#3630](https://github.com/leanprover/lean4/pull/3630) makes `termination_by?` not use `sizeOf` when not needed
|
||||
* [#3652](https://github.com/leanprover/lean4/pull/3652) improves the `termination_by` syntax.
|
||||
* [#3658](https://github.com/leanprover/lean4/pull/3658) changes how termination arguments are elaborated.
|
||||
* [#3665](https://github.com/leanprover/lean4/pull/3665) refactors GuessLex to allow inferring more complex termination arguments
|
||||
* [#3666](https://github.com/leanprover/lean4/pull/3666) infers termination arguments such as `xs.size - i`
|
||||
* [#3629](https://github.com/leanprover/lean4/pull/3629),
|
||||
[#3655](https://github.com/leanprover/lean4/pull/3655),
|
||||
[#3747](https://github.com/leanprover/lean4/pull/3747):
|
||||
Adds `@[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`.
|
||||
* **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).
|
||||
[#3089](https://github.com/leanprover/lean4/pull/3089)
|
||||
and [#3934](https://github.com/leanprover/lean4/pull/3934).
|
||||
* [8d2adf](https://github.com/leanprover/lean4/commit/8d2adf521d2b7636347a5b01bfe473bf0fcfaf31)
|
||||
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).
|
||||
* [84b091](https://github.com/leanprover/lean4/commit/84b0919a116e9be12f933e764474f45d964ce85c)
|
||||
Lean now generates an error if the type of a theorem is **not** a proposition.
|
||||
* **Definition transparency.** [47a343](https://github.com/leanprover/lean4/commit/47a34316fc03ce936fddd2d3dce44784c5bcdfa9). `@[reducible]`, `@[semireducible]`, and `@[irreducible]` are now scoped and able to be set for imported declarations.
|
||||
* `simp`/`dsimp`
|
||||
* [#3607](https://github.com/leanprover/lean4/pull/3607) enables kernel projection reduction in `dsimp`
|
||||
* [b24fbf](https://github.com/leanprover/lean4/commit/b24fbf44f3aaa112f5d799ef2a341772d1eb222d)
|
||||
and [acdb00](https://github.com/leanprover/lean4/commit/acdb0054d5a0efa724cff596ac26852fad5724c4):
|
||||
`dsimproc` command
|
||||
to define defeq-preserving simplification procedures.
|
||||
* [#3624](https://github.com/leanprover/lean4/pull/3624) makes `dsimp` normalize raw nat literals as `OfNat.ofNat` applications.
|
||||
* [#3628](https://github.com/leanprover/lean4/pull/3628) makes `simp` correctly handle `OfScientific.ofScientific` literals.
|
||||
* [#3654](https://github.com/leanprover/lean4/pull/3654) makes `dsimp?` report used simprocs.
|
||||
* [dee074](https://github.com/leanprover/lean4/commit/dee074dcde03a37b7895a4901df2e4fa490c73c7) fixes equation theorem
|
||||
handling in `simp` for non-recursive definitions.
|
||||
* [#3819](https://github.com/leanprover/lean4/pull/3819) improved performance when simp encounters a loop.
|
||||
* [#3821](https://github.com/leanprover/lean4/pull/3821) fixes discharger/cache interaction.
|
||||
* [#3824](https://github.com/leanprover/lean4/pull/3824) keeps `simp` from breaking `Char` literals.
|
||||
* [#3838](https://github.com/leanprover/lean4/pull/3838) allows `Nat` instances matching to be more lenient.
|
||||
* [#3870](https://github.com/leanprover/lean4/pull/3870) documentation for `simp` configuration options.
|
||||
* [#3972](https://github.com/leanprover/lean4/pull/3972) fixes simp caching.
|
||||
* [#4044](https://github.com/leanprover/lean4/pull/4044) improves cache behavior for "well-behaved" dischargers.
|
||||
* `omega`
|
||||
* [#3639](https://github.com/leanprover/lean4/pull/3639), [#3766](https://github.com/leanprover/lean4/pull/3766),
|
||||
[#3853](https://github.com/leanprover/lean4/pull/3853), [#3875](https://github.com/leanprover/lean4/pull/3875):
|
||||
introduces a term canonicalizer.
|
||||
* [#3736](https://github.com/leanprover/lean4/pull/3736) improves handling of positivity for the modulo operator for `Int`.
|
||||
* [#3828](https://github.com/leanprover/lean4/pull/3828) makes it work as a `simp` discharger.
|
||||
* [#3847](https://github.com/leanprover/lean4/pull/3847) adds helpful error messages.
|
||||
* `rfl`
|
||||
* [#3671](https://github.com/leanprover/lean4/pull/3671), [#3708](https://github.com/leanprover/lean4/pull/3708): upstreams the `@[refl]` attribute and the `rfl` tactic.
|
||||
* [#3751](https://github.com/leanprover/lean4/pull/3751) makes `apply_rfl` not operate on `Eq` itself.
|
||||
* [#4067](https://github.com/leanprover/lean4/pull/4067) improves error message when there are no goals.
|
||||
* [#3719](https://github.com/leanprover/lean4/pull/3719) upstreams the `rw?` tactic, with fixes and improvements in
|
||||
[#3783](https://github.com/leanprover/lean4/pull/3783), [#3794](https://github.com/leanprover/lean4/pull/3794),
|
||||
[#3911](https://github.com/leanprover/lean4/pull/3911).
|
||||
* `conv`
|
||||
* [#3659](https://github.com/leanprover/lean4/pull/3659) adds a `conv` version of the `calc` tactic.
|
||||
* [#3763](https://github.com/leanprover/lean4/pull/3763) makes `conv` clean up using `try with_reducible rfl` instead of `try rfl`.
|
||||
* `#guard_msgs`
|
||||
* [#3617](https://github.com/leanprover/lean4/pull/3617) introduces whitespace protection using the `⏎` character.
|
||||
* [#3883](https://github.com/leanprover/lean4/pull/3883):
|
||||
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.
|
||||
* [#3931](https://github.com/leanprover/lean4/pull/3931) adds an unused variables ignore function for `#guard_msgs`.
|
||||
* [#3912](https://github.com/leanprover/lean4/pull/3912) adds 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.
|
||||
* `do` **notation**
|
||||
* [#3820](https://github.com/leanprover/lean4/pull/3820) makes it an error to lift `(<- ...)` out of a pure `if ... then ... else ...`
|
||||
* **Lazy discrimination trees**
|
||||
* [#3610](https://github.com/leanprover/lean4/pull/3610) fixes a name collision for `LazyDiscrTree` that could lead to cache poisoning.
|
||||
* [#3677](https://github.com/leanprover/lean4/pull/3677) simplifies and fixes `LazyDiscrTree` handling for `exact?`/`apply?`.
|
||||
* [#3685](https://github.com/leanprover/lean4/pull/3685) moves general `exact?`/`apply?` functionality into `LazyDiscrTree`.
|
||||
* [#3769](https://github.com/leanprover/lean4/pull/3769) has lemma selection improvements for `rw?` and `LazyDiscrTree`.
|
||||
* [#3818](https://github.com/leanprover/lean4/pull/3818) improves ordering of matches.
|
||||
* [#3590](https://github.com/leanprover/lean4/pull/3590) adds `inductive.autoPromoteIndices` option to be able to disable auto promotion of indices in the `inductive` command.
|
||||
* **Miscellaneous bug fixes and improvements**
|
||||
* [#3606](https://github.com/leanprover/lean4/pull/3606) preserves `cache` and `dischargeDepth` fields in `Lean.Meta.Simp.Result.mkEqSymm`.
|
||||
* [#3633](https://github.com/leanprover/lean4/pull/3633) makes `elabTermEnsuringType` respect `errToSorry`, improving error recovery of the `have` tactic.
|
||||
* [#3647](https://github.com/leanprover/lean4/pull/3647) enables `noncomputable unsafe` definitions, for deferring implementations until later.
|
||||
* [#3672](https://github.com/leanprover/lean4/pull/3672) adjust namespaces of tactics.
|
||||
* [#3725](https://github.com/leanprover/lean4/pull/3725) fixes `Ord` derive handler for indexed inductive types with unused alternatives.
|
||||
* [#3893](https://github.com/leanprover/lean4/pull/3893) improves performance of derived `Ord` instances.
|
||||
* [#3771](https://github.com/leanprover/lean4/pull/3771) changes error reporting for failing tactic macros. Improves `rfl` error message.
|
||||
* [#3745](https://github.com/leanprover/lean4/pull/3745) fixes elaboration of generalized field notation if the object of the notation is an optional parameter.
|
||||
* [#3799](https://github.com/leanprover/lean4/pull/3799) makes commands such as `universe`, `variable`, `namespace`, etc. require that their argument appear in a later column.
|
||||
Commands that can optionally parse an `ident` or parse any number of `ident`s generally should require
|
||||
that the `ident` use `colGt`. This keeps typos in commands from being interpreted as identifiers.
|
||||
* [#3815](https://github.com/leanprover/lean4/pull/3815) lets the `split` tactic be used for writing code.
|
||||
* [#3822](https://github.com/leanprover/lean4/pull/3822) adds missing info in `induction` tactic for `with` clauses of the form `| cstr a b c => ?_`.
|
||||
* [#3806](https://github.com/leanprover/lean4/pull/3806) fixes `withSetOptionIn` combinator.
|
||||
* [#3844](https://github.com/leanprover/lean4/pull/3844) removes unused `trace.Elab.syntax` option.
|
||||
* [#3896](https://github.com/leanprover/lean4/pull/3896) improves hover and go-to-def for `attribute` command.
|
||||
* [#3989](https://github.com/leanprover/lean4/pull/3989) makes linter options more discoverable.
|
||||
* [#3916](https://github.com/leanprover/lean4/pull/3916) fixes go-to-def for syntax defined with `@[builtin_term_parser]`.
|
||||
* [#3962](https://github.com/leanprover/lean4/pull/3962) fixes how `solveByElim` handles `symm` lemmas, making `exact?`/`apply?` usable again.
|
||||
* [#3968](https://github.com/leanprover/lean4/pull/3968) improves the `@[deprecated]` attribute, adding `(since := "<date>")` field.
|
||||
* [#3768](https://github.com/leanprover/lean4/pull/3768) makes `#print` command show structure fields.
|
||||
* [#3974](https://github.com/leanprover/lean4/pull/3974) makes `exact?%` behave like `by exact?` rather than `by apply?`.
|
||||
* [#3994](https://github.com/leanprover/lean4/pull/3994) makes elaboration of `he ▸ h` notation more predictable.
|
||||
* [#3991](https://github.com/leanprover/lean4/pull/3991) adjusts transparency for `decreasing_trivial` macros.
|
||||
* [#4092](https://github.com/leanprover/lean4/pull/4092) improves performance of `binop%` and `binrel%` expression tree elaborators.
|
||||
* **Docs:** [#3748](https://github.com/leanprover/lean4/pull/3748), [#3796](https://github.com/leanprover/lean4/pull/3796),
|
||||
[#3800](https://github.com/leanprover/lean4/pull/3800), [#3874](https://github.com/leanprover/lean4/pull/3874),
|
||||
[#3863](https://github.com/leanprover/lean4/pull/3863), [#3862](https://github.com/leanprover/lean4/pull/3862),
|
||||
[#3891](https://github.com/leanprover/lean4/pull/3891), [#3873](https://github.com/leanprover/lean4/pull/3873),
|
||||
[#3908](https://github.com/leanprover/lean4/pull/3908), [#3872](https://github.com/leanprover/lean4/pull/3872).
|
||||
|
||||
### Language server and IDE extensions
|
||||
|
||||
* [#3432](https://github.com/leanprover/lean4/pull/3432) enables `import` auto-completions.
|
||||
* [#3608](https://github.com/leanprover/lean4/pull/3608) fixes issue [leanprover/vscode-lean4#392](https://github.com/leanprover/vscode-lean4/issues/392).
|
||||
Diagnostic ranges had an off-by-one error that would misplace goal states for example.
|
||||
* [#3014](https://github.com/leanprover/lean4/pull/3014) introduces snapshot trees, foundational work for incremental tactics and parallelism.
|
||||
[#3849](https://github.com/leanprover/lean4/pull/3849) adds basic incrementality API.
|
||||
* [#3271](https://github.com/leanprover/lean4/pull/3271) adds support for server-to-client requests.
|
||||
* [#3656](https://github.com/leanprover/lean4/pull/3656) fixes jump to definition when there are conflicting names from different files.
|
||||
Fixes issue [#1170](https://github.com/leanprover/lean4/issues/1170).
|
||||
* [#3691](https://github.com/leanprover/lean4/pull/3691), [#3925](https://github.com/leanprover/lean4/pull/3925),
|
||||
[#3932](https://github.com/leanprover/lean4/pull/3932) keep semantic tokens synchronized (used for semantic highlighting), with performance improvements.
|
||||
* [#3247](https://github.com/leanprover/lean4/pull/3247) and [#3730](https://github.com/leanprover/lean4/pull/3730)
|
||||
add diagnostics to run "Restart File" when a file dependency is saved.
|
||||
* [#3722](https://github.com/leanprover/lean4/pull/3722) uses the correct module names when displaying references.
|
||||
* [#3728](https://github.com/leanprover/lean4/pull/3728) makes errors in header reliably appear and makes the "Import out of date" warning be at "hint" severity.
|
||||
[#3739](https://github.com/leanprover/lean4/pull/3739) simplifies the text of this warning.
|
||||
* [#3778](https://github.com/leanprover/lean4/pull/3778) fixes [#3462](https://github.com/leanprover/lean4/issues/3462),
|
||||
where info nodes from before the cursor would be used for computing completions.
|
||||
* [#3985](https://github.com/leanprover/lean4/pull/3985) makes trace timings appear in Infoview.
|
||||
|
||||
### Pretty printing
|
||||
|
||||
* [#3797](https://github.com/leanprover/lean4/pull/3797) fixes the hovers over binders so that they show their types.
|
||||
* [#3640](https://github.com/leanprover/lean4/pull/3640) and [#3735](https://github.com/leanprover/lean4/pull/3735): Adds attribute `@[pp_using_anonymous_constructor]` to make structures pretty print as `⟨x, y, z⟩`
|
||||
rather than as `{a := x, b := y, c := z}`.
|
||||
This attribute is applied to `Sigma`, `PSigma`, `PProd`, `Subtype`, `And`, and `Fin`.
|
||||
* [#3749](https://github.com/leanprover/lean4/pull/3749)
|
||||
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.
|
||||
* [#3737](https://github.com/leanprover/lean4/pull/3737), [#3744](https://github.com/leanprover/lean4/pull/3744)
|
||||
and [#3750](https://github.com/leanprover/lean4/pull/3750):
|
||||
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.
|
||||
The notation is not used for theorems.
|
||||
* [#4071](https://github.com/leanprover/lean4/pull/4071) fixes interaction between app unexpanders and `pp.fieldNotation.generalized`
|
||||
* [#3625](https://github.com/leanprover/lean4/pull/3625) makes `delabConstWithSignature` (used by `#check`) have the ability to put arguments "after the colon"
|
||||
to avoid printing inaccessible names.
|
||||
* [#3798](https://github.com/leanprover/lean4/pull/3798),
|
||||
[#3978](https://github.com/leanprover/lean4/pull/3978),
|
||||
[#3798](https://github.com/leanprover/lean4/pull/3980):
|
||||
Adds 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.
|
||||
* [#3917](https://github.com/leanprover/lean4/pull/3917) makes binders hoverable and gives them docstrings.
|
||||
* [#4034](https://github.com/leanprover/lean4/pull/4034) makes hovers for RHS terms in `match` expressions in the Infoview reliably show the correct term.
|
||||
|
||||
### Library
|
||||
|
||||
* `Bool`/`Prop`
|
||||
* [#3508](https://github.com/leanprover/lean4/pull/3508) improves `simp` confluence for `Bool` and `Prop` terms.
|
||||
* Theorems: [#3604](https://github.com/leanprover/lean4/pull/3604)
|
||||
* `Nat`
|
||||
* [#3579](https://github.com/leanprover/lean4/pull/3579) makes `Nat.succ_eq_add_one` be a simp lemma, now that `induction`/`cases` uses `n + 1` instead of `Nat.succ n`.
|
||||
* [#3808](https://github.com/leanprover/lean4/pull/3808) replaces `Nat.succ` simp rules with simprocs.
|
||||
* [#3876](https://github.com/leanprover/lean4/pull/3876) adds faster `Nat.repr` implementation in C.
|
||||
* `Int`
|
||||
* Theorems: [#3890](https://github.com/leanprover/lean4/pull/3890)
|
||||
* `UInt`s
|
||||
* [#3960](https://github.com/leanprover/lean4/pull/3960) improves performance of upcasting.
|
||||
* `Array` and `Subarray`
|
||||
* [#3676](https://github.com/leanprover/lean4/pull/3676) removes `Array.eraseIdxAux`, `Array.eraseIdxSzAux`, and `Array.eraseIdx'`.
|
||||
* [#3648](https://github.com/leanprover/lean4/pull/3648) simplifies `Array.findIdx?`.
|
||||
* [#3851](https://github.com/leanprover/lean4/pull/3851) renames fields of `Subarray`.
|
||||
* `List`
|
||||
* [#3785](https://github.com/leanprover/lean4/pull/3785) upstreams tail-recursive List operations and `@[csimp]` lemmas.
|
||||
* `BitVec`
|
||||
* Theorems: [#3593](https://github.com/leanprover/lean4/pull/3593),
|
||||
[#3593](https://github.com/leanprover/lean4/pull/3593), [#3597](https://github.com/leanprover/lean4/pull/3597),
|
||||
[#3598](https://github.com/leanprover/lean4/pull/3598), [#3721](https://github.com/leanprover/lean4/pull/3721),
|
||||
[#3729](https://github.com/leanprover/lean4/pull/3729), [#3880](https://github.com/leanprover/lean4/pull/3880),
|
||||
[#4039](https://github.com/leanprover/lean4/pull/4039).
|
||||
* [#3884](https://github.com/leanprover/lean4/pull/3884) protects `Std.BitVec`.
|
||||
* `String`
|
||||
* [#3832](https://github.com/leanprover/lean4/pull/3832) fixes `String.splitOn`.
|
||||
* [#3959](https://github.com/leanprover/lean4/pull/3959) adds `String.Pos.isValid`.
|
||||
* [#3959](https://github.com/leanprover/lean4/pull/3959) UTF-8 string validation.
|
||||
* [#3961](https://github.com/leanprover/lean4/pull/3961) adds a model implementation for UTF-8 encoding and decoding.
|
||||
* `IO`
|
||||
* [#4097](https://github.com/leanprover/lean4/pull/4097) adds `IO.getTaskState` which returns whether a task is finished, actively running, or waiting on other Tasks to finish.
|
||||
|
||||
* **Refactors**
|
||||
* [#3605](https://github.com/leanprover/lean4/pull/3605) reduces imports for `Init.Data.Nat` and `Init.Data.Int`.
|
||||
* [#3613](https://github.com/leanprover/lean4/pull/3613) reduces imports for `Init.Omega.Int`.
|
||||
* [#3634](https://github.com/leanprover/lean4/pull/3634) upstreams `Std.Data.Nat`
|
||||
and [#3635](https://github.com/leanprover/lean4/pull/3635) upstreams `Std.Data.Int`.
|
||||
* [#3790](https://github.com/leanprover/lean4/pull/3790) reduces more imports for `omega`.
|
||||
* [#3694](https://github.com/leanprover/lean4/pull/3694) extends `GetElem` interface with `getElem!` and `getElem?` to simplify containers like `RBMap`.
|
||||
* [#3865](https://github.com/leanprover/lean4/pull/3865) renames `Option.toMonad` (see breaking changes below).
|
||||
* [#3882](https://github.com/leanprover/lean4/pull/3882) unifies `lexOrd` with `compareLex`.
|
||||
* **Other fixes or improvements**
|
||||
* [#3765](https://github.com/leanprover/lean4/pull/3765) makes `Quotient.sound` be a `theorem`.
|
||||
* [#3645](https://github.com/leanprover/lean4/pull/3645) fixes `System.FilePath.parent` in the case of absolute paths.
|
||||
* [#3660](https://github.com/leanprover/lean4/pull/3660) `ByteArray.toUInt64LE!` and `ByteArray.toUInt64BE!` were swapped.
|
||||
* [#3881](https://github.com/leanprover/lean4/pull/3881), [#3887](https://github.com/leanprover/lean4/pull/3887) fix linearity issues in `HashMap.insertIfNew`, `HashSet.erase`, and `HashMap.erase`.
|
||||
The `HashMap.insertIfNew` fix improves `import` performance.
|
||||
* [#3830](https://github.com/leanprover/lean4/pull/3830) ensures linearity in `Parsec.many*Core`.
|
||||
* [#3930](https://github.com/leanprover/lean4/pull/3930) adds `FS.Stream.isTty` field.
|
||||
* [#3866](https://github.com/leanprover/lean4/pull/3866) deprecates `Option.toBool` in favor of `Option.isSome`.
|
||||
* [#3975](https://github.com/leanprover/lean4/pull/3975) upstreams `Data.List.Init` and `Data.Array.Init` material from Std.
|
||||
* [#3942](https://github.com/leanprover/lean4/pull/3942) adds instances that make `ac_rfl` work without Mathlib.
|
||||
* [#4010](https://github.com/leanprover/lean4/pull/4010) changes `Fin.induction` to use structural induction.
|
||||
* [02753f](https://github.com/leanprover/lean4/commit/02753f6e4c510c385efcbf71fa9a6bec50fce9ab)
|
||||
fixes bug in `reduceLeDiff` simproc.
|
||||
* [#4097](https://github.com/leanprover/lean4/pull/4097)
|
||||
adds `IO.TaskState` and `IO.getTaskState` to get the task from the Lean runtime's task manager.
|
||||
* **Docs:** [#3615](https://github.com/leanprover/lean4/pull/3615), [#3664](https://github.com/leanprover/lean4/pull/3664),
|
||||
[#3707](https://github.com/leanprover/lean4/pull/3707), [#3734](https://github.com/leanprover/lean4/pull/3734),
|
||||
[#3868](https://github.com/leanprover/lean4/pull/3868), [#3861](https://github.com/leanprover/lean4/pull/3861),
|
||||
[#3869](https://github.com/leanprover/lean4/pull/3869), [#3858](https://github.com/leanprover/lean4/pull/3858),
|
||||
[#3856](https://github.com/leanprover/lean4/pull/3856), [#3857](https://github.com/leanprover/lean4/pull/3857),
|
||||
[#3867](https://github.com/leanprover/lean4/pull/3867), [#3864](https://github.com/leanprover/lean4/pull/3864),
|
||||
[#3860](https://github.com/leanprover/lean4/pull/3860), [#3859](https://github.com/leanprover/lean4/pull/3859),
|
||||
[#3871](https://github.com/leanprover/lean4/pull/3871), [#3919](https://github.com/leanprover/lean4/pull/3919).
|
||||
|
||||
### Lean internals
|
||||
|
||||
* **Defeq and WHNF algorithms**
|
||||
* [#3616](https://github.com/leanprover/lean4/pull/3616) gives better support for reducing `Nat.rec` expressions.
|
||||
* [#3774](https://github.com/leanprover/lean4/pull/3774) add tracing for "non-easy" WHNF cases.
|
||||
* [#3807](https://github.com/leanprover/lean4/pull/3807) fixes an `isDefEq` performance issue, now trying structure eta *after* lazy delta reduction.
|
||||
* [#3816](https://github.com/leanprover/lean4/pull/3816) fixes `.yesWithDeltaI` behavior to prevent increasing transparency level when reducing projections.
|
||||
* [#3837](https://github.com/leanprover/lean4/pull/3837) improves heuristic at `isDefEq`.
|
||||
* [#3965](https://github.com/leanprover/lean4/pull/3965) improves `isDefEq` for constraints of the form `t.i =?= s.i`.
|
||||
* [#3977](https://github.com/leanprover/lean4/pull/3977) improves `isDefEqProj`.
|
||||
* [#3981](https://github.com/leanprover/lean4/pull/3981) adds universe constraint approximations to be able to solve `u =?= max u ?v` using `?v = u`.
|
||||
These approximations are only applied when universe constraints cannot be postponed anymore.
|
||||
* [#4004](https://github.com/leanprover/lean4/pull/4004) improves `isDefEqProj` during typeclass resolution.
|
||||
* [#4012](https://github.com/leanprover/lean4/pull/4012) adds `backward.isDefEq.lazyProjDelta` and `backward.isDefEq.lazyWhnfCore` backwards compatibility flags.
|
||||
* **Kernel**
|
||||
* [#3966](https://github.com/leanprover/lean4/pull/3966) removes dead code.
|
||||
* [#4035](https://github.com/leanprover/lean4/pull/4035) fixes mismatch for `TheoremVal` between Lean and C++.
|
||||
* **Discrimination trees**
|
||||
* [423fed](https://github.com/leanprover/lean4/commit/423fed79a9de75705f34b3e8648db7e076c688d7)
|
||||
and [3218b2](https://github.com/leanprover/lean4/commit/3218b25974d33e92807af3ce42198911c256ff1d):
|
||||
simplify handling of dependent/non-dependent pi types.
|
||||
* **Typeclass instance synthesis**
|
||||
* [#3638](https://github.com/leanprover/lean4/pull/3638) eta-reduces synthesized instances
|
||||
* [ce350f](https://github.com/leanprover/lean4/commit/ce350f348161e63fccde6c4a5fe1fd2070e7ce0f) fixes a linearity issue
|
||||
* [917a31](https://github.com/leanprover/lean4/commit/917a31f694f0db44d6907cc2b1485459afe74d49)
|
||||
improves performance by considering at most one answer for subgoals not containing metavariables.
|
||||
[#4008](https://github.com/leanprover/lean4/pull/4008) adds `backward.synthInstance.canonInstances` backward compatibility flag.
|
||||
* **Definition processing**
|
||||
* [#3661](https://github.com/leanprover/lean4/pull/3661), [#3767](https://github.com/leanprover/lean4/pull/3767) changes automatically generated equational theorems to be named
|
||||
using suffix `.eq_<idx>` instead of `._eq_<idx>`, and `.eq_def` instead of `._unfold`. (See breaking changes below.)
|
||||
[#3675](https://github.com/leanprover/lean4/pull/3675) adds a mechanism to reserve names.
|
||||
[#3803](https://github.com/leanprover/lean4/pull/3803) fixes reserved name resolution inside namespaces and fixes handling of `match`er declarations and equation lemmas.
|
||||
* [#3662](https://github.com/leanprover/lean4/pull/3662) causes auxiliary definitions nested inside theorems to become `def`s if they are not proofs.
|
||||
* [#4006](https://github.com/leanprover/lean4/pull/4006) makes proposition fields of `structure`s be theorems.
|
||||
* [#4018](https://github.com/leanprover/lean4/pull/4018) makes it an error for a theorem to be `extern`.
|
||||
* [#4047](https://github.com/leanprover/lean4/pull/4047) improves performance making equations for well-founded recursive definitions.
|
||||
* **Refactors**
|
||||
* [#3614](https://github.com/leanprover/lean4/pull/3614) avoids unfolding in `Lean.Meta.evalNat`.
|
||||
* [#3621](https://github.com/leanprover/lean4/pull/3621) centralizes functionality for `Fix`/`GuessLex`/`FunInd` in the `ArgsPacker` module.
|
||||
* [#3186](https://github.com/leanprover/lean4/pull/3186) rewrites the UnusedVariable linter to be more performant.
|
||||
* [#3589](https://github.com/leanprover/lean4/pull/3589) removes coercion from `String` to `Name` (see breaking changes below).
|
||||
* [#3237](https://github.com/leanprover/lean4/pull/3237) removes the `lines` field from `FileMap`.
|
||||
* [#3951](https://github.com/leanprover/lean4/pull/3951) makes msg parameter to `throwTacticEx` optional.
|
||||
* **Diagnostics**
|
||||
* [#4016](https://github.com/leanprover/lean4/pull/4016), [#4019](https://github.com/leanprover/lean4/pull/4019),
|
||||
[#4020](https://github.com/leanprover/lean4/pull/4020), [#4030](https://github.com/leanprover/lean4/pull/4030),
|
||||
[#4031](https://github.com/leanprover/lean4/pull/4031),
|
||||
[c3714b](https://github.com/leanprover/lean4/commit/c3714bdc6d46845c0428735b283c5b48b23cbcf7),
|
||||
[#4049](https://github.com/leanprover/lean4/pull/4049) adds `set_option diagnostics true` for diagnostic counters.
|
||||
Tracks number of unfolded declarations, instances, reducible declarations, used instances, recursor reductions,
|
||||
`isDefEq` heuristic applications, among others.
|
||||
This option is suggested in exceptional situations, such as at deterministic timeout and maximum recursion depth.
|
||||
* [283587](https://github.com/leanprover/lean4/commit/283587987ab2eb3b56fbc3a19d5f33ab9e04a2ef)
|
||||
adds diagnostic information for `simp`.
|
||||
* [#4043](https://github.com/leanprover/lean4/pull/4043) adds diagnostic information for congruence theorems.
|
||||
* [#4048](https://github.com/leanprover/lean4/pull/4048) display diagnostic information
|
||||
for `set_option diagnostics true in <tactic>` and `set_option diagnostics true in <term>`.
|
||||
* **Other features**
|
||||
* [#3800](https://github.com/leanprover/lean4/pull/3800) adds environment extension to record which definitions use structural or well-founded recursion.
|
||||
* [#3801](https://github.com/leanprover/lean4/pull/3801) `trace.profiler` can now export to Firefox Profiler.
|
||||
* [#3918](https://github.com/leanprover/lean4/pull/3918), [#3953](https://github.com/leanprover/lean4/pull/3953) adds `@[builtin_doc]` attribute to make docs and location of a declaration available as a builtin.
|
||||
* [#3939](https://github.com/leanprover/lean4/pull/3939) adds the `lean --json` CLI option to print messages as JSON.
|
||||
* [#3075](https://github.com/leanprover/lean4/pull/3075) improves `test_extern` command.
|
||||
* [#3970](https://github.com/leanprover/lean4/pull/3970) gives monadic generalization of `FindExpr`.
|
||||
* **Docs:** [#3743](https://github.com/leanprover/lean4/pull/3743), [#3921](https://github.com/leanprover/lean4/pull/3921),
|
||||
[#3954](https://github.com/leanprover/lean4/pull/3954).
|
||||
* **Other fixes:** [#3622](https://github.com/leanprover/lean4/pull/3622),
|
||||
[#3726](https://github.com/leanprover/lean4/pull/3726), [#3823](https://github.com/leanprover/lean4/pull/3823),
|
||||
[#3897](https://github.com/leanprover/lean4/pull/3897), [#3964](https://github.com/leanprover/lean4/pull/3964),
|
||||
[#3946](https://github.com/leanprover/lean4/pull/3946), [#4007](https://github.com/leanprover/lean4/pull/4007),
|
||||
[#4026](https://github.com/leanprover/lean4/pull/4026).
|
||||
|
||||
### Compiler, runtime, and FFI
|
||||
|
||||
* [#3632](https://github.com/leanprover/lean4/pull/3632) makes it possible to allocate and free thread-local runtime resources for threads not started by Lean itself.
|
||||
* [#3627](https://github.com/leanprover/lean4/pull/3627) improves error message about compacting closures.
|
||||
* [#3692](https://github.com/leanprover/lean4/pull/3692) fixes deadlock in `IO.Promise.resolve`.
|
||||
* [#3753](https://github.com/leanprover/lean4/pull/3753) catches error code from `MoveFileEx` on Windows.
|
||||
* [#4028](https://github.com/leanprover/lean4/pull/4028) fixes a double `reset` bug in `ResetReuse` transformation.
|
||||
* [6e731b](https://github.com/leanprover/lean4/commit/6e731b4370000a8e7a5cfb675a7f3d7635d21f58)
|
||||
removes `interpreter` copy constructor to avoid potential memory safety issues.
|
||||
|
||||
### Lake
|
||||
|
||||
* **TOML Lake configurations**. [#3298](https://github.com/leanprover/lean4/pull/3298), [#4104](https://github.com/leanprover/lean4/pull/4104).
|
||||
|
||||
Lake packages can now use TOML as a alternative configuration file format instead of Lean. If the default `lakefile.lean` is missing, Lake will also look for a `lakefile.toml`. The TOML version of the configuration supports a restricted set of the Lake configuration options, only including those which can easily mapped to a TOML data structure. The TOML syntax itself fully compiles with the TOML v1.0.0 specification.
|
||||
|
||||
As part of the introduction of this new feature, we have been helping maintainers of some major packages within the ecosystem switch to this format. For example, the following is Aesop's new `lakefile.toml`:
|
||||
|
||||
|
||||
**[leanprover-community/aesop/lakefile.toml](https://raw.githubusercontent.com/leanprover-community/aesop/de11e0ecf372976e6d627c210573146153090d2d/lakefile.toml)**
|
||||
```toml
|
||||
name = "aesop"
|
||||
defaultTargets = ["Aesop"]
|
||||
testRunner = "test"
|
||||
precompileModules = false
|
||||
|
||||
[[require]]
|
||||
name = "batteries"
|
||||
git = "https://github.com/leanprover-community/batteries"
|
||||
rev = "main"
|
||||
|
||||
[[lean_lib]]
|
||||
name = "Aesop"
|
||||
|
||||
[[lean_lib]]
|
||||
name = "AesopTest"
|
||||
globs = ["AesopTest.+"]
|
||||
leanOptions = {linter.unusedVariables = false}
|
||||
|
||||
[[lean_exe]]
|
||||
name = "test"
|
||||
srcDir = "scripts"
|
||||
```
|
||||
|
||||
To assist users who wish to transition their packages between configuration file formats, there is also a new `lake translate-config` command for migrating to/from TOML.
|
||||
|
||||
Running `lake translate-config toml` will produce a `lakefile.toml` version of a package's `lakefile.lean`. Any configuration options unsupported by the TOML format will be discarded during translation, but the original `lakefile.lean` will remain so that you can verify the translation looks good before deleting it.
|
||||
|
||||
* **Build progress overhaul.** [#3835](https://github.com/leanprover/lean4/pull/3835), [#4115](https://github.com/leanprover/lean4/pull/4115), [#4127](https://github.com/leanprover/lean4/pull/4127), [#4220](https://github.com/leanprover/lean4/pull/4220), [#4232](https://github.com/leanprover/lean4/pull/4232), [#4236](https://github.com/leanprover/lean4/pull/4236).
|
||||
|
||||
Builds are now managed by a top-level Lake build monitor, this makes the output of Lake builds more standardized and enables producing prettier and more configurable progress reports.
|
||||
|
||||
As part of this change, job isolation has improved. Stray I/O and other build related errors in custom targets are now properly isolated and caught as part of their job. Import errors no longer cause Lake to abort the entire build and are instead localized to the build jobs of the modules in question.
|
||||
|
||||
Lake also now uses ANSI escape sequences to add color and produce progress lines that update in-place; this can be toggled on and off using `--ansi` / `--no-ansi`.
|
||||
|
||||
|
||||
`--wfail` and `--iofail` options have been added that causes a build to fail if any of the jobs log a warning (`--wfail`) or produce any output or log information messages (`--iofail`). Unlike some other build systems, these options do **NOT** convert these logs into errors, and Lake does not abort jobs on such a log (i.e., dependent jobs will still continue unimpeded).
|
||||
|
||||
* `lake test`. [#3779](https://github.com/leanprover/lean4/pull/3779).
|
||||
|
||||
Lake now has a built-in `test` command which will run a script or executable labelled `@[test_runner]` (in Lean) or defined as the `testRunner` (in TOML) in the root package.
|
||||
|
||||
Lake also provides a `lake check-test` command which will exit with code `0` if the package has a properly configured test runner or error with `1` otherwise.
|
||||
|
||||
* `lake lean`. [#3793](https://github.com/leanprover/lean4/pull/3793).
|
||||
|
||||
The new command `lake lean <file> [-- <args...>]` functions like `lake env lean <file> <args...>`, except that it builds the imports of `file` before running `lean`. This makes it very useful for running test or example code that imports modules that are not guaranteed to have been built beforehand.
|
||||
|
||||
* **Miscellaneous bug fixes and improvements**
|
||||
* [#3609](https://github.com/leanprover/lean4/pull/3609) `LEAN_GITHASH` environment variable to override the detected Git hash for Lean when computing traces, useful for testing custom builds of Lean.
|
||||
* [#3795](https://github.com/leanprover/lean4/pull/3795) improves relative package directory path normalization in the pre-rename check.
|
||||
* [#3957](https://github.com/leanprover/lean4/pull/3957) fixes handling of packages that appear multiple times in a dependency tree.
|
||||
* [#3999](https://github.com/leanprover/lean4/pull/3999) makes it an error for there to be a mismatch between a package name and what it is required as. Also adds a special message for the `std`-to-`batteries` rename.
|
||||
* [#4033](https://github.com/leanprover/lean4/pull/4033) fixes quiet mode.
|
||||
* **Docs:** [#3704](https://github.com/leanprover/lean4/pull/3704).
|
||||
|
||||
### DevOps
|
||||
|
||||
* [#3536](https://github.com/leanprover/lean4/pull/3536) and [#3833](https://github.com/leanprover/lean4/pull/3833)
|
||||
add a checklist for the release process.
|
||||
* [#3600](https://github.com/leanprover/lean4/pull/3600) runs nix-ci more uniformly.
|
||||
* [#3612](https://github.com/leanprover/lean4/pull/3612) avoids argument limits when building on Windows.
|
||||
* [#3682](https://github.com/leanprover/lean4/pull/3682) builds Lean's `.o` files in parallel to rest of core.
|
||||
* [#3601](https://github.com/leanprover/lean4/pull/3601)
|
||||
changes the way Lean is built on Windows (see breaking changes below).
|
||||
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.
|
||||
* [#3690](https://github.com/leanprover/lean4/pull/3690) marks "Build matrix complete" as canceled if the build is canceled.
|
||||
* [#3700](https://github.com/leanprover/lean4/pull/3700), [#3702](https://github.com/leanprover/lean4/pull/3702),
|
||||
[#3701](https://github.com/leanprover/lean4/pull/3701), [#3834](https://github.com/leanprover/lean4/pull/3834),
|
||||
[#3923](https://github.com/leanprover/lean4/pull/3923): fixes and improvements for std and mathlib CI.
|
||||
* [#3712](https://github.com/leanprover/lean4/pull/3712) fixes `nix build .` on macOS.
|
||||
* [#3717](https://github.com/leanprover/lean4/pull/3717) replaces `shell.nix` in devShell with `flake.nix`.
|
||||
* [#3715](https://github.com/leanprover/lean4/pull/3715) and [#3790](https://github.com/leanprover/lean4/pull/3790) add test result summaries.
|
||||
* [#3971](https://github.com/leanprover/lean4/pull/3971) prevents stage0 changes via the merge queue.
|
||||
* [#3979](https://github.com/leanprover/lean4/pull/3979) adds handling for `changes-stage0` label.
|
||||
* [#3952](https://github.com/leanprover/lean4/pull/3952) adds a script to summarize GitHub issues.
|
||||
* [18a699](https://github.com/leanprover/lean4/commit/18a69914da53dbe37c91bc2b9ce65e1dc01752b6)
|
||||
fixes asan linking
|
||||
|
||||
### Breaking changes
|
||||
|
||||
* Due to the major Lake build refactor, code using the affected parts of the Lake API or relying on the previous output format of Lake builds is likely to have been broken. We have tried to minimize the breakages and, were possible, old definitions have been marked `@[deprecated]` with a reference to the new alternative.
|
||||
|
||||
* Automatically generated equational theorems are now named using suffix `.eq_<idx>` instead of `._eq_<idx>`, and `.def` instead of `._unfold`. Example:
|
||||
```
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
open Std
|
||||
open Batteries
|
||||
open Lean
|
||||
|
||||
inductive BoolExpr where
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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'),
|
||||
],
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -11,7 +11,7 @@ project(LEAN CXX C)
|
||||
set(LEAN_VERSION_MAJOR 4)
|
||||
set(LEAN_VERSION_MINOR 9)
|
||||
set(LEAN_VERSION_PATCH 0)
|
||||
set(LEAN_VERSION_IS_RELEASE 0) # This number is 1 in the release revision, and 0 otherwise.
|
||||
set(LEAN_VERSION_IS_RELEASE 1) # This number is 1 in the release revision, and 0 otherwise.
|
||||
set(LEAN_SPECIAL_VERSION_DESC "" CACHE STRING "Additional version description like 'nightly-2018-03-11'")
|
||||
set(LEAN_VERSION_STRING "${LEAN_VERSION_MAJOR}.${LEAN_VERSION_MINOR}.${LEAN_VERSION_PATCH}")
|
||||
if (LEAN_SPECIAL_VERSION_DESC)
|
||||
|
||||
@@ -34,3 +34,4 @@ import Init.BinderPredicates
|
||||
import Init.Ext
|
||||
import Init.Omega
|
||||
import Init.MacroTrace
|
||||
import Init.Grind
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -184,4 +184,18 @@ 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]
|
||||
|
||||
end BitVec
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -221,9 +223,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 +259,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 +622,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 +718,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 +955,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 +1056,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
|
||||
|
||||
@@ -360,7 +360,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 _)
|
||||
|
||||
@@ -5,3 +5,4 @@ Authors: Leonardo de Moura
|
||||
-/
|
||||
prelude
|
||||
import Init.Data.Char.Basic
|
||||
import Init.Data.Char.Lemmas
|
||||
|
||||
25
src/Init/Data/Char/Lemmas.lean
Normal file
25
src/Init/Data/Char/Lemmas.lean
Normal 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
|
||||
@@ -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
|
||||
|
||||
@@ -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], _, _ => ⟨_⟩
|
||||
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -50,7 +50,10 @@ 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]
|
||||
@@ -188,8 +191,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 +234,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 +258,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 +403,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]
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -6,3 +6,4 @@ Authors: Leonardo de Moura
|
||||
prelude
|
||||
import Init.Data.String.Basic
|
||||
import Init.Data.String.Extra
|
||||
import Init.Data.String.Lemmas
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
21
src/Init/Data/String/Lemmas.lean
Normal file
21
src/Init/Data/String/Lemmas.lean
Normal 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
|
||||
@@ -6,3 +6,4 @@ Authors: Henrik Böving
|
||||
prelude
|
||||
import Init.Data.UInt.Basic
|
||||
import Init.Data.UInt.Log2
|
||||
import Init.Data.UInt.Lemmas
|
||||
|
||||
@@ -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
|
||||
|
||||
66
src/Init/Data/UInt/Lemmas.lean
Normal file
66
src/Init/Data/UInt/Lemmas.lean
Normal 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
10
src/Init/Grind.lean
Normal 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
15
src/Init/Grind/Cases.lean
Normal 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
|
||||
14
src/Init/Grind/Lemmas.lean
Normal file
14
src/Init/Grind/Lemmas.lean
Normal 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
110
src/Init/Grind/Norm.lean
Normal 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
|
||||
25
src/Init/Grind/Tactics.lean
Normal file
25
src/Init/Grind/Tactics.lean
Normal 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
|
||||
@@ -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`
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
/--
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
31
src/Lean/AddDecl.lean
Normal 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
|
||||
@@ -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
|
||||
|
||||
@@ -4,6 +4,7 @@ Released under Apache 2.0 license as described in the file LICENSE.
|
||||
Authors: Leonardo de Moura
|
||||
-/
|
||||
prelude
|
||||
import Lean.AddDecl
|
||||
import Lean.Elab.InfoTree.Main
|
||||
|
||||
namespace Lean
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
/--
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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))
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 }
|
||||
@@ -321,11 +350,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 +499,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 +570,7 @@ def liftCommandElabM (cmd : CommandElabM α) : CoreM α := do
|
||||
ref := ← getRef
|
||||
tacticCache? := none
|
||||
snap? := none
|
||||
cancelTk? := (← read).cancelTk?
|
||||
} |>.run {
|
||||
env := ← getEnv
|
||||
maxRecDepth := ← getMaxRecDepth
|
||||
@@ -537,7 +580,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
|
||||
|
||||
|
||||
@@ -28,6 +28,81 @@ 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
|
||||
/--
|
||||
Input substring uniquely identifying header elaboration result given the same `Environment`.
|
||||
If missing, results should never be reused.
|
||||
-/
|
||||
headerSubstr? : Option Substring
|
||||
/-- 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
|
||||
@@ -36,6 +111,13 @@ structure DefView where
|
||||
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
|
||||
|
||||
|
||||
@@ -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*))
|
||||
|
||||
@@ -5,7 +5,7 @@ Authors: Leonardo de Moura
|
||||
-/
|
||||
prelude
|
||||
import Lean.InternalExceptionId
|
||||
import Lean.Meta.Basic
|
||||
import Lean.Exception
|
||||
|
||||
namespace Lean.Elab
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,14 +123,65 @@ 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
|
||||
/--
|
||||
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.ref 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
|
||||
-- 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.ref do
|
||||
addDeclarationRanges declName view.ref
|
||||
applyAttributesAt declName view.modifiers.attrs .beforeElaboration
|
||||
withDeclName declName <| withAutoBoundImplicit <| withLevelNames levelNames <|
|
||||
@@ -164,21 +211,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 +338,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 +757,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 +913,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 +961,45 @@ 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
|
||||
withAlwaysResolvedPromises ds.size fun headerPromises => do
|
||||
let substr? := (mkNullNode ds).getSubstring?
|
||||
let snap? := (← read).snap?
|
||||
let mut views := #[]
|
||||
let mut defs := #[]
|
||||
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]
|
||||
if let some snap := snap? then
|
||||
-- overapproximation: includes previous bodies as well
|
||||
let headerSubstr? := return { (← substr?) with stopPos := (← view.value.getPos?) }
|
||||
view := { view with headerSnap? := some {
|
||||
old? := do
|
||||
-- transitioning from `Context.snap?` to `DefView.snap?` invariant: if the elaboration
|
||||
-- context and state are unchanged, and the substring from the beginning of the first
|
||||
-- header to the beginning of the current body is unchanged, then the elaboration result for
|
||||
-- this header (which includes state from elaboration of previous headers!) should be
|
||||
-- unchanged.
|
||||
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 <| (← headerSubstr?).sameAs (← oldParsed.headerSubstr?)
|
||||
-- no syntax guard to store, we already did the necessary checks
|
||||
return ⟨.missing, oldParsed.headerProcessedSnap⟩
|
||||
new := headerPromise
|
||||
} }
|
||||
defs := defs.push {
|
||||
headerSubstr?
|
||||
headerProcessedSnap := { range? := d.getRange?, task := headerPromise.result }
|
||||
}
|
||||
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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 :=
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -231,15 +237,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
|
||||
@@ -432,6 +438,12 @@ def getNameOfIdent' (id : Syntax) : Name :=
|
||||
def withCaseRef [Monad m] [MonadRef m] (arrow body : Syntax) (x : m α) : m α :=
|
||||
withRef (mkNullNode #[arrow, body]) x
|
||||
|
||||
-- TODO: attribute(s)
|
||||
builtin_initialize builtinIncrementalTactics : IO.Ref NameSet ← IO.mkRef {}
|
||||
|
||||
def registerBuiltinIncrementalTactic (kind : Name) : IO Unit := do
|
||||
builtinIncrementalTactics.modify fun s => s.insert kind
|
||||
|
||||
builtin_initialize registerTraceClass `Elab.tactic
|
||||
builtin_initialize registerTraceClass `Elab.tactic.backtrack
|
||||
|
||||
|
||||
@@ -29,13 +29,95 @@ 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
|
||||
-- allow nested reuse for allowlisted tactics
|
||||
withTheReader Term.Context ({ · with
|
||||
tacSnap? :=
|
||||
guard ((← builtinIncrementalTactics.get).contains tac.getKind) *>
|
||||
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 +186,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] def evalTacticSeq1Indented : Tactic := fun stx =>
|
||||
evalSepByIndentTactic stx[0]
|
||||
builtin_initialize registerBuiltinIncrementalTactic ``tacticSeq1Indented
|
||||
@[builtin_tactic tacticSeq1Indented] def evalTacticSeq1Indented : Tactic :=
|
||||
Term.withNarrowedArgTacticReuse (argIdx := 0) evalSepTactics
|
||||
|
||||
builtin_initialize registerBuiltinIncrementalTactic ``tacticSeqBracketed
|
||||
@[builtin_tactic tacticSeqBracketed] 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_initialize registerBuiltinIncrementalTactic ``cdot
|
||||
@[builtin_tactic Lean.cdot] 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 +208,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 +281,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_initialize registerBuiltinIncrementalTactic ``tacticSeq
|
||||
@[builtin_tactic tacticSeq] 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 +347,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 +469,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 +503,16 @@ where
|
||||
.group <| .nest 2 <|
|
||||
.ofFormat .line ++ .joinSep items sep
|
||||
|
||||
|
||||
builtin_initialize registerBuiltinIncrementalTactic ``case
|
||||
@[builtin_tactic «case»] def evalCase : Tactic
|
||||
| stx@`(tactic| case $[$tag $hs*]|* =>%$arr $tac:tacticSeq) =>
|
||||
| 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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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). -/
|
||||
|
||||
@@ -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_initialize registerBuiltinIncrementalTactic ``Lean.Parser.Tactic.induction
|
||||
@[builtin_tactic Lean.Parser.Tactic.induction] 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_initialize registerBuiltinIncrementalTactic ``Lean.Parser.Tactic.cases
|
||||
@[builtin_tactic Lean.Parser.Tactic.cases] 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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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}"
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -130,7 +130,7 @@ been defined yet.
|
||||
-/
|
||||
def Exception.isMaxRecDepth (ex : Exception) : Bool :=
|
||||
match ex with
|
||||
| error _ (MessageData.ofFormat (Std.Format.text msg)) => msg == maxRecDepthErrorMessage
|
||||
| error _ (MessageData.ofFormatWithInfos ⟨Std.Format.text msg, _⟩) => msg == maxRecDepthErrorMessage
|
||||
| _ => false
|
||||
|
||||
/--
|
||||
|
||||
@@ -17,8 +17,13 @@ set_option linter.missingDocs true
|
||||
|
||||
namespace Lean.Language
|
||||
|
||||
/-- `MessageLog` with interactive diagnostics. -/
|
||||
/--
|
||||
`MessageLog` with interactive diagnostics.
|
||||
|
||||
Can be created using `Diagnostics.empty` or `Diagnostics.ofMessageLog`.
|
||||
-/
|
||||
structure Snapshot.Diagnostics where
|
||||
private mk ::
|
||||
/-- Non-interactive message log. -/
|
||||
msgLog : MessageLog
|
||||
/--
|
||||
@@ -133,8 +138,7 @@ checking if we can reuse `old?` if set or else redoing the corresponding elabora
|
||||
case, we derive new bundles for nested snapshots, if any, and finally `resolve` `new` to the result.
|
||||
|
||||
Note that failing to `resolve` a created promise will block the language server indefinitely!
|
||||
Corresponding `IO.Promise.new` calls should come with a "definitely resolved in ..." comment
|
||||
explaining how this is avoided in each case.
|
||||
We use `withAlwaysResolvedPromise`/`withAlwaysResolvedPromises` to ensure this doesn't happen.
|
||||
|
||||
In the future, the 1-element history `old?` may be replaced with a global cache indexed by strong
|
||||
hashes but the promise will still need to be passed through the elaborator.
|
||||
@@ -151,6 +155,36 @@ structure SnapshotBundle (α : Type) where
|
||||
-/
|
||||
new : IO.Promise α
|
||||
|
||||
/--
|
||||
Runs `act` with a newly created promise and finally resolves it to `default` if not done by `act`.
|
||||
|
||||
Always resolving promises involved in the snapshot tree is important to avoid deadlocking the
|
||||
language server.
|
||||
-/
|
||||
def withAlwaysResolvedPromise [Monad m] [MonadLiftT BaseIO m] [MonadFinally m] [Inhabited α]
|
||||
(act : IO.Promise α → m Unit) : m Unit := do
|
||||
let p ← IO.Promise.new
|
||||
try
|
||||
act p
|
||||
finally
|
||||
p.resolve default
|
||||
|
||||
/--
|
||||
Runs `act` with `count` newly created promises and finally resolves them to `default` if not done by
|
||||
`act`.
|
||||
|
||||
Always resolving promises involved in the snapshot tree is important to avoid deadlocking the
|
||||
language server.
|
||||
-/
|
||||
def withAlwaysResolvedPromises [Monad m] [MonadLiftT BaseIO m] [MonadFinally m] [Inhabited α]
|
||||
(count : Nat) (act : Array (IO.Promise α) → m Unit) : m Unit := do
|
||||
let ps ← List.iota count |>.toArray.mapM fun _ => IO.Promise.new
|
||||
try
|
||||
act ps
|
||||
finally
|
||||
for p in ps do
|
||||
p.resolve default
|
||||
|
||||
/--
|
||||
Tree of snapshots where each snapshot comes with an array of asynchronous further subtrees. Used
|
||||
for asynchronously collecting information about the entirety of snapshots in the language server.
|
||||
@@ -245,17 +279,8 @@ def SnapshotTree.runAndReport (s : SnapshotTree) (opts : Options) (json := false
|
||||
def SnapshotTree.getAll (s : SnapshotTree) : Array Snapshot :=
|
||||
s.forM (m := StateM _) (fun s => modify (·.push s)) |>.run #[] |>.2
|
||||
|
||||
/-- Metadata that does not change during the lifetime of the language processing process. -/
|
||||
structure ModuleProcessingContext where
|
||||
/-- Module name of the file being processed. -/
|
||||
mainModuleName : Name
|
||||
/-- Options provided outside of the file content, e.g. on the cmdline or in the lakefile. -/
|
||||
opts : Options
|
||||
/-- Kernel trust level. -/
|
||||
trustLevel : UInt32 := 0
|
||||
|
||||
/-- Context of an input processing invocation. -/
|
||||
structure ProcessingContext extends ModuleProcessingContext, Parser.InputContext
|
||||
structure ProcessingContext extends Parser.InputContext
|
||||
|
||||
/-- Monad transformer holding all relevant data for processing. -/
|
||||
abbrev ProcessingT m := ReaderT ProcessingContext m
|
||||
@@ -296,10 +321,10 @@ end Language
|
||||
/--
|
||||
Builds a function for processing a language using incremental snapshots by passing the previous
|
||||
snapshot to `Language.process` on subsequent invocations. -/
|
||||
def Language.mkIncrementalProcessor (process : Option InitSnap → ProcessingM InitSnap)
|
||||
(ctx : ModuleProcessingContext) : BaseIO (Parser.InputContext → BaseIO InitSnap) := do
|
||||
def Language.mkIncrementalProcessor (process : Option InitSnap → ProcessingM InitSnap) :
|
||||
BaseIO (Parser.InputContext → BaseIO InitSnap) := do
|
||||
let oldRef ← IO.mkRef none
|
||||
return fun ictx => do
|
||||
let snap ← process (← oldRef.get) { ctx, ictx with }
|
||||
let snap ← process (← oldRef.get) { ictx with }
|
||||
oldRef.set (some snap)
|
||||
return snap
|
||||
|
||||
@@ -63,36 +63,61 @@ we remain at "go two commands up" at this point.
|
||||
|
||||
Because of Lean's use of persistent data structures, incremental reuse of fully elaborated commands
|
||||
is easy because we can simply snapshot the entire state after each command and then restart
|
||||
elaboration using the stored state at the point of change. However, incrementality within
|
||||
elaboration of a single command such as between tactic steps is much harder because we cannot simply
|
||||
return from those points to the language processor in a way that we can later resume from there.
|
||||
Instead, we exchange the need for continuations with some limited mutability: by allocating an
|
||||
`IO.Promise` "cell" in the language processor, we can both pass it to the elaborator to eventually
|
||||
fill it using `Promise.resolve` as well as convert it to a `Task` that will wait on that resolution
|
||||
using `Promise.result` and return it as part of the command snapshot created by the language
|
||||
processor. The elaborator can then create new promises itself and store their `result` when
|
||||
resolving an outer promise to create an arbitrary tree of promise-backed snapshot tasks. Thus, we
|
||||
can enable incremental reporting and reuse inside the elaborator using the same snapshot tree data
|
||||
structures as outside without having to change the elaborator's control flow.
|
||||
elaboration using the stored state at the next command above the point of change. However,
|
||||
incrementality *within* elaboration of a single command such as between tactic steps is much harder
|
||||
because the existing control flow does not allow us to simply return from those points to the
|
||||
language processor in a way that we can later resume from there. Instead, we exchange the need for
|
||||
continuations with some limited mutability: by allocating an `IO.Promise` "cell" in the language
|
||||
processor, we can both pass it to the elaborator to eventually fill it using `Promise.resolve` as
|
||||
well as convert it to a `Task` that will wait on that resolution using `Promise.result` and return
|
||||
it as part of the command snapshot created by the language processor. The elaborator can then in
|
||||
turn create new promises itself and store their `result` when resolving an outer promise to create
|
||||
an arbitrary tree of promise-backed snapshot tasks. Thus, we can enable incremental reporting and
|
||||
reuse inside the elaborator using the same snapshot tree data structures as outside without having
|
||||
to change the elaborator's control flow.
|
||||
|
||||
While ideally we would decide what can be reused during command elaboration using strong hashes over
|
||||
the state and inputs, currently we rely on simpler syntactic checks: if all the syntax inspected up
|
||||
to a certain point is unchanged, we can assume that the old state can be reused. The central
|
||||
`SnapshotBundle` type passed inwards through the elaborator for this purpose combines the following
|
||||
data:
|
||||
the full state and inputs, currently we rely on simpler syntactic checks: if all the syntax
|
||||
inspected up to a certain point is unchanged, we can assume that the old state can be reused. The
|
||||
central `SnapshotBundle` type passed inwards through the elaborator for this purpose combines the
|
||||
following data:
|
||||
* the `IO.Promise` to be resolved to an elaborator snapshot (whose type depends on the specific
|
||||
elaborator part we're in, e.g. `)
|
||||
elaborator part we're in, e.g. `TacticParsedSnapshot`, `BodyProcessedSnapshot`)
|
||||
* if there was a previous run:
|
||||
* a `SnapshotTask` holding the corresponding snapshot of the run
|
||||
* the relevant `Syntax` of the previous run to be compared before any reuse
|
||||
|
||||
Note that as we do not wait for the previous run to finish before starting to elaborate the next
|
||||
one, the `SnapshotTask` task may not be finished yet. Indeed, if we do find that we can reuse the
|
||||
contained state, we will want to explicitly wait for it instead of redoing the work. On the other
|
||||
hand, the `Syntax` is not surrounded by a task so that we can immediately access it for comparisons,
|
||||
even if the snapshot task may, eventually, give access to the same syntax tree.
|
||||
one, the old `SnapshotTask` task may not be finished yet. Indeed, if we do find that we can reuse
|
||||
the contained state because of a successful syntax comparison, we always want to explicitly wait for
|
||||
the task instead of redoing the work. On the other hand, the `Syntax` is not surrounded by a task so
|
||||
that we can immediately access it for comparisons, even if the snapshot task may, eventually, give
|
||||
access to the same syntax tree.
|
||||
|
||||
TODO: tactic examples
|
||||
For the most part, inside an elaborator participating in incrementality, we just have to ensure that
|
||||
we stop forwarding the old run's data as soon as we notice a relevant difference between old and new
|
||||
syntax tree. For example, allowing incrementality inside the cdot tactic combinator is as simple as
|
||||
```
|
||||
builtin_initialize registerBuiltinIncrementalTactic ``cdot
|
||||
@[builtin_tactic cdot] def evalTacticCDot : Tactic := fun stx => do
|
||||
...
|
||||
closeUsingOrAdmit do
|
||||
-- save state before/after entering focus on `·`
|
||||
...
|
||||
Term.withNarrowedArgTacticReuse (argIdx := 1) evalTactic stx
|
||||
```
|
||||
The `Term.withNarrowedArgTacticReuse` combinator will focus on the given argument of `stx`, which in
|
||||
this case is the nested tactic sequence, and run `evalTactic` on it. But crucially, it will first
|
||||
compare all preceding arguments, in this case the cdot token itself, with the old syntax in the
|
||||
current snapshot bundle, which in the case of tactics is stored in `Term.Context.tacSnap?`. Indeed
|
||||
it is important here to check if the cdot token is identical because its position has been saved in
|
||||
the info tree, so it would be bad if we later restored some old state that uses a different position
|
||||
for it even if everything else is unchanged. If there is any mismatch, the bundle's old value is
|
||||
set to `none` in order to prevent reuse from this point on. Note that in any case we still want to
|
||||
forward the "new" promise in order to provide incremental reporting as well as to construct a
|
||||
snapshot tree for reuse in future document versions! Note also that we explicitly opted into
|
||||
incrementality using `registerBuiltinIncrementalTactic` as any tactic combinator not written with
|
||||
these concerns in mind would likely misbehave under incremental reuse.
|
||||
|
||||
While it is generally true that we can provide incremental reporting even without reuse, we
|
||||
generally want to avoid that when it would be confusing/annoying, e.g. when a tactic block is run
|
||||
@@ -101,12 +126,24 @@ purpose, we can disable both incremental modes using `Term.withoutTacticIncremen
|
||||
opted into incrementality because of other parts of the combinator. `induction` is an example of
|
||||
this because there are some induction alternatives that are run multiple times, so we disable all of
|
||||
incrementality for them.
|
||||
|
||||
Using `induction` as a more complex example than `cdot` as it calls into `evalTactic` multiple
|
||||
times, here is a summary of what it has to do to implement incrementality:
|
||||
* `Narrow` down to the syntax of alternatives, disabling reuse if anything before them changed
|
||||
* allocate one new promise for each given alternative, immediately resolve passed promise to a new
|
||||
snapshot tree node holding them so that the language server can wait on them
|
||||
* when executing an alternative,
|
||||
* we put the corresponding promise into the context
|
||||
* we disable reuse if anything in front of the contained tactic block has changed, including
|
||||
previous alternatives
|
||||
* we disable reuse *and reporting* if the tactic block is run multiple times, e.g. in the case of
|
||||
a wildcard pattern
|
||||
-/
|
||||
|
||||
set_option linter.missingDocs true
|
||||
|
||||
namespace Lean.Language.Lean
|
||||
open Lean.Elab
|
||||
open Lean.Elab Command
|
||||
open Lean.Parser
|
||||
|
||||
private def pushOpt (a? : Option α) (as : Array α) : Array α :=
|
||||
@@ -121,12 +158,6 @@ register_builtin_option stderrAsMessages : Bool := {
|
||||
descr := "(server) capture output to the Lean stderr channel (such as from `dbg_trace`) during elaboration of a command as a diagnostic message"
|
||||
}
|
||||
|
||||
/-- 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)"
|
||||
}
|
||||
|
||||
/-! The hierarchy of Lean snapshot types -/
|
||||
|
||||
/-- Snapshot after elaboration of the entire command. -/
|
||||
@@ -165,7 +196,7 @@ deriving Nonempty
|
||||
abbrev CommandParsedSnapshot.data : CommandParsedSnapshot → CommandParsedSnapshotData
|
||||
| mk data _ => data
|
||||
/-- Next command, unless this is a terminal command. -/
|
||||
abbrev CommandParsedSnapshot.next? : CommandParsedSnapshot →
|
||||
abbrev CommandParsedSnapshot.nextCmdSnap? : CommandParsedSnapshot →
|
||||
Option (SnapshotTask CommandParsedSnapshot)
|
||||
| mk _ next? => next?
|
||||
partial instance : ToSnapshotTree CommandParsedSnapshot where
|
||||
@@ -173,18 +204,7 @@ partial instance : ToSnapshotTree CommandParsedSnapshot where
|
||||
go s := ⟨s.data.toSnapshot,
|
||||
#[s.data.elabSnap.map (sync := true) toSnapshotTree,
|
||||
s.data.finishedSnap.map (sync := true) toSnapshotTree] |>
|
||||
pushOpt (s.next?.map (·.map (sync := true) go))⟩
|
||||
|
||||
|
||||
/-- Cancels all significant computations from this snapshot onwards. -/
|
||||
partial def CommandParsedSnapshot.cancel (snap : CommandParsedSnapshot) : BaseIO Unit := do
|
||||
-- This is the only relevant computation right now, everything else is promises
|
||||
-- TODO: cancel additional elaboration tasks (which will be tricky with `DynamicSnapshot`) if we
|
||||
-- add them without switching to implicit cancellation
|
||||
snap.data.finishedSnap.cancel
|
||||
if let some next := snap.next? then
|
||||
-- recurse on next command (which may have been spawned just before we cancelled above)
|
||||
let _ ← IO.mapTask (sync := true) (·.cancel) next.task
|
||||
pushOpt (s.nextCmdSnap?.map (·.map (sync := true) go))⟩
|
||||
|
||||
/-- State after successful importing. -/
|
||||
structure HeaderProcessedState where
|
||||
@@ -218,6 +238,8 @@ structure HeaderParsedSnapshot extends Snapshot where
|
||||
/-- State after successful parsing. -/
|
||||
result? : Option HeaderParsedState
|
||||
isFatal := result?.isNone
|
||||
/-- Cancellation token for interrupting processing of this run. -/
|
||||
cancelTk? : Option IO.CancelToken
|
||||
|
||||
instance : ToSnapshotTree HeaderParsedSnapshot where
|
||||
toSnapshotTree s := ⟨s.toSnapshot,
|
||||
@@ -235,6 +257,10 @@ abbrev InitialSnapshot := HeaderParsedSnapshot
|
||||
structure LeanProcessingContext extends ProcessingContext where
|
||||
/-- Position of the first file difference if there was a previous invocation. -/
|
||||
firstDiffPos? : Option String.Pos
|
||||
/-- Cancellation token of the previous invocation, if any. -/
|
||||
oldCancelTk? : Option IO.CancelToken
|
||||
/-- Cancellation token of the current run. -/
|
||||
newCancelTk : IO.CancelToken
|
||||
|
||||
/-- Monad transformer holding all relevant data for Lean processing. -/
|
||||
abbrev LeanProcessingT m := ReaderT LeanProcessingContext m
|
||||
@@ -247,6 +273,18 @@ instance : MonadLift LeanProcessingM (LeanProcessingT IO) where
|
||||
instance : MonadLift (ProcessingT m) (LeanProcessingT m) where
|
||||
monadLift := fun act ctx => act ctx.toProcessingContext
|
||||
|
||||
/--
|
||||
Embeds a `LeanProcessingM` action into `ProcessingM`, optionally using the old input string to speed
|
||||
up reuse analysis and supplying a cancellation token that should be triggered as soon as reuse is
|
||||
ruled out.
|
||||
-/
|
||||
def LeanProcessingM.run (act : LeanProcessingM α) (oldInputCtx? : Option InputContext)
|
||||
(oldCancelTk? : Option IO.CancelToken := none) : ProcessingM α := do
|
||||
-- compute position of syntactic change once
|
||||
let firstDiffPos? := oldInputCtx?.map (·.input.firstDiffPos (← read).input)
|
||||
let newCancelTk ← IO.CancelToken.new
|
||||
ReaderT.adapt ({ · with firstDiffPos?, oldCancelTk?, newCancelTk }) act
|
||||
|
||||
/--
|
||||
Returns true if there was a previous run and the given position is before any textual change
|
||||
compared to it.
|
||||
@@ -263,57 +301,83 @@ private def withHeaderExceptions (ex : Snapshot → α) (act : LeanProcessingT I
|
||||
| .error e => return ex { diagnostics := (← diagnosticsOfHeaderError e.toString) }
|
||||
| .ok a => return a
|
||||
|
||||
/-- Entry point of the Lean language processor. -/
|
||||
/--
|
||||
Result of retrieving additional metadata about the current file after parsing imports. In the
|
||||
language server, these are derived from the `lake setup-file` result. On the cmdline and for similar
|
||||
simple uses, these can be computed eagerly without looking at the imports.
|
||||
-/
|
||||
structure SetupImportsResult where
|
||||
/-- Module name of the file being processed. -/
|
||||
mainModuleName : Name
|
||||
/-- Options provided outside of the file content, e.g. on the cmdline or in the lakefile. -/
|
||||
opts : Options
|
||||
/-- Kernel trust level. -/
|
||||
trustLevel : UInt32 := 0
|
||||
|
||||
/--
|
||||
Entry point of the Lean language processor.
|
||||
|
||||
The `setupImports` function is called after the header has been parsed and before the first command
|
||||
is parsed in order to supply additional file metadata (or abort with a given terminal snapshot); see
|
||||
`SetupImportsResult`.
|
||||
|
||||
`old?` is a previous resulting snapshot, if any, to be reused for incremental processing.
|
||||
-/
|
||||
/-
|
||||
General notes:
|
||||
* For each processing function we pass in the previous state, if any, in order to reuse still-valid
|
||||
state. As there is no cheap way to check whether the `Environment` is unchanged, i.e. *semantic*
|
||||
change detection is currently not possible, we must make sure to pass `none` as all follow-up
|
||||
"previous states" from the first *syntactic* change onwards.
|
||||
* We must make sure to use `CommandParsedSnapshot.cancel` on such tasks when discarding them, i.e.
|
||||
when not passing them along in `old?`.
|
||||
* We must make sure to trigger `oldCancelTk?` as soon as discarding `old?`.
|
||||
* Control flow up to finding the last still-valid snapshot (which should be quick) is synchronous so
|
||||
as not to report this "fast forwarding" to the user as well as to make sure the next run sees all
|
||||
fast-forwarded snapshots without having to wait on tasks.
|
||||
-/
|
||||
partial def process
|
||||
(setupImports : Syntax → ProcessingT IO (Except HeaderProcessedSnapshot Options) :=
|
||||
fun _ => pure <| .ok {})
|
||||
(setupImports : Syntax → ProcessingT IO (Except HeaderProcessedSnapshot SetupImportsResult))
|
||||
(old? : Option InitialSnapshot) : ProcessingM InitialSnapshot := do
|
||||
-- compute position of syntactic change once
|
||||
let firstDiffPos? := old?.map (·.ictx.input.firstDiffPos (← read).input)
|
||||
ReaderT.adapt ({ · with firstDiffPos? }) do
|
||||
parseHeader old?
|
||||
parseHeader old? |>.run (old?.map (·.ictx)) (old?.bind (·.cancelTk?))
|
||||
where
|
||||
parseHeader (old? : Option HeaderParsedSnapshot) : LeanProcessingM HeaderParsedSnapshot := do
|
||||
let ctx ← read
|
||||
let ictx := ctx.toInputContext
|
||||
let unchanged old :=
|
||||
let unchanged old newParserState :=
|
||||
-- when header syntax is unchanged, reuse import processing task as is and continue with
|
||||
-- parsing the first command, synchronously if possible
|
||||
-- NOTE: even if the syntax tree is functionally unchanged, the new parser state may still
|
||||
-- have changed because of trailing whitespace and comments etc., so it is passed separately
|
||||
-- from `old`
|
||||
if let some oldSuccess := old.result? then
|
||||
return { old with ictx, result? := some { oldSuccess with
|
||||
processedSnap := (← oldSuccess.processedSnap.bindIO (sync := true) fun oldProcessed => do
|
||||
if let some oldProcSuccess := oldProcessed.result? then
|
||||
-- also wait on old command parse snapshot as parsing is cheap and may allow for
|
||||
-- elaboration reuse
|
||||
oldProcSuccess.firstCmdSnap.bindIO (sync := true) fun oldCmd =>
|
||||
return .pure { oldProcessed with result? := some { oldProcSuccess with
|
||||
firstCmdSnap := (← parseCmd oldCmd oldSuccess.parserState oldProcSuccess.cmdState ctx) } }
|
||||
else
|
||||
return .pure oldProcessed) } }
|
||||
return {
|
||||
ictx
|
||||
stx := old.stx
|
||||
diagnostics := old.diagnostics
|
||||
cancelTk? := ctx.newCancelTk
|
||||
result? := some { oldSuccess with
|
||||
processedSnap := (← oldSuccess.processedSnap.bindIO (sync := true) fun oldProcessed => do
|
||||
if let some oldProcSuccess := oldProcessed.result? then
|
||||
-- also wait on old command parse snapshot as parsing is cheap and may allow for
|
||||
-- elaboration reuse
|
||||
oldProcSuccess.firstCmdSnap.bindIO (sync := true) fun oldCmd =>
|
||||
return .pure { oldProcessed with result? := some { oldProcSuccess with
|
||||
firstCmdSnap := (← parseCmd oldCmd newParserState oldProcSuccess.cmdState ctx) } }
|
||||
else
|
||||
return .pure oldProcessed) } }
|
||||
else return old
|
||||
|
||||
-- fast path: if we have parsed the header successfully...
|
||||
if let some old := old? then
|
||||
if let some (some processed) ← old.processedResult.get? then
|
||||
-- ...and the edit location is after the next command (see note [Incremental Parsing])...
|
||||
if let some nextCom ← processed.firstCmdSnap.get? then
|
||||
if (← isBeforeEditPos nextCom.data.parserState.pos) then
|
||||
-- ...go immediately to next snapshot
|
||||
return (← unchanged old)
|
||||
if let some oldSuccess := old.result? then
|
||||
if let some (some processed) ← old.processedResult.get? then
|
||||
-- ...and the edit location is after the next command (see note [Incremental Parsing])...
|
||||
if let some nextCom ← processed.firstCmdSnap.get? then
|
||||
if (← isBeforeEditPos nextCom.data.parserState.pos) then
|
||||
-- ...go immediately to next snapshot
|
||||
return (← unchanged old oldSuccess.parserState)
|
||||
|
||||
withHeaderExceptions ({ · with ictx, stx := .missing, result? := none }) do
|
||||
withHeaderExceptions ({ · with
|
||||
ictx, stx := .missing, result? := none, cancelTk? := none }) do
|
||||
-- parsing the header should be cheap enough to do synchronously
|
||||
let (stx, parserState, msgLog) ← Parser.parseHeader ictx
|
||||
if msgLog.hasErrors then
|
||||
@@ -321,6 +385,7 @@ where
|
||||
ictx, stx
|
||||
diagnostics := (← Snapshot.Diagnostics.ofMessageLog msgLog)
|
||||
result? := none
|
||||
cancelTk? := none
|
||||
}
|
||||
|
||||
-- semi-fast path: go to next snapshot if syntax tree is unchanged AND we're still in front
|
||||
@@ -331,14 +396,11 @@ where
|
||||
-- influence the range of error messages such as from a trailing `exact`
|
||||
if let some old := old? then
|
||||
if (← isBeforeEditPos parserState.pos) && old.stx == stx then
|
||||
return (← unchanged old)
|
||||
-- on first change, make sure to cancel all further old tasks
|
||||
if let some oldSuccess := old.result? then
|
||||
oldSuccess.processedSnap.cancel
|
||||
let _ ← BaseIO.mapTask (t := oldSuccess.processedSnap.task) fun processed => do
|
||||
if let some oldProcSuccess := processed.result? then
|
||||
let _ ← BaseIO.mapTask (·.cancel) oldProcSuccess.firstCmdSnap.task
|
||||
|
||||
-- Here we must make sure to pass the *new* parser state; see NOTE in `unchanged`
|
||||
return (← unchanged old parserState)
|
||||
-- on first change, make sure to cancel old invocation
|
||||
if let some tk := ctx.oldCancelTk? then
|
||||
tk.set
|
||||
return {
|
||||
ictx, stx
|
||||
diagnostics := (← Snapshot.Diagnostics.ofMessageLog msgLog)
|
||||
@@ -346,6 +408,7 @@ where
|
||||
parserState
|
||||
processedSnap := (← processHeader stx parserState)
|
||||
}
|
||||
cancelTk? := ctx.newCancelTk
|
||||
}
|
||||
|
||||
processHeader (stx : Syntax) (parserState : Parser.ModuleParserState) :
|
||||
@@ -354,20 +417,18 @@ where
|
||||
SnapshotTask.ofIO (some ⟨0, ctx.input.endPos⟩) <|
|
||||
ReaderT.run (r := ctx) <| -- re-enter reader in new task
|
||||
withHeaderExceptions (α := HeaderProcessedSnapshot) ({ · with result? := none }) do
|
||||
let opts ← match (← setupImports stx) with
|
||||
| .ok opts => pure opts
|
||||
let setup ← match (← setupImports stx) with
|
||||
| .ok setup => pure setup
|
||||
| .error snap => return snap
|
||||
-- override context options with file options
|
||||
let opts := ctx.opts.mergeBy (fun _ _ fileOpt => fileOpt) opts
|
||||
-- allows `headerEnv` to be leaked, which would live until the end of the process anyway
|
||||
let (headerEnv, msgLog) ← Elab.processHeader (leakEnv := true) stx opts .empty
|
||||
ctx.toInputContext ctx.trustLevel
|
||||
let (headerEnv, msgLog) ← Elab.processHeader (leakEnv := true) stx setup.opts .empty
|
||||
ctx.toInputContext setup.trustLevel
|
||||
let diagnostics := (← Snapshot.Diagnostics.ofMessageLog msgLog)
|
||||
if msgLog.hasErrors then
|
||||
return { diagnostics, result? := none }
|
||||
|
||||
let headerEnv := headerEnv.setMainModule ctx.mainModuleName
|
||||
let cmdState := Elab.Command.mkState headerEnv msgLog opts
|
||||
let headerEnv := headerEnv.setMainModule setup.mainModuleName
|
||||
let cmdState := Elab.Command.mkState headerEnv msgLog setup.opts
|
||||
let cmdState := { cmdState with infoState := {
|
||||
enabled := true
|
||||
trees := #[Elab.InfoTree.context (.commandCtx {
|
||||
@@ -399,7 +460,7 @@ where
|
||||
|
||||
-- check for cancellation, most likely during elaboration of previous command, before starting
|
||||
-- processing of next command
|
||||
if (← IO.checkCanceled) then
|
||||
if (← ctx.newCancelTk.isSet) then
|
||||
-- this is a bit ugly as we don't want to adjust our API with `Option`s just for cancellation
|
||||
-- (as no-one should look at this result in that case) but anything containing `Environment`
|
||||
-- is not `Inhabited`
|
||||
@@ -410,22 +471,25 @@ where
|
||||
tacticCache := (← IO.mkRef {})
|
||||
}
|
||||
|
||||
let unchanged old : BaseIO CommandParsedSnapshot :=
|
||||
let unchanged old newParserState : BaseIO CommandParsedSnapshot :=
|
||||
-- when syntax is unchanged, reuse command processing task as is
|
||||
if let some oldNext := old.next? then
|
||||
-- NOTE: even if the syntax tree is functionally unchanged, the new parser state may still
|
||||
-- have changed because of trailing whitespace and comments etc., so it is passed separately
|
||||
-- from `old`
|
||||
if let some oldNext := old.nextCmdSnap? then
|
||||
return .mk (data := old.data)
|
||||
(nextCmdSnap? := (← old.data.finishedSnap.bindIO (sync := true) fun oldFinished =>
|
||||
-- also wait on old command parse snapshot as parsing is cheap and may allow for
|
||||
-- elaboration reuse
|
||||
oldNext.bindIO (sync := true) fun oldNext => do
|
||||
parseCmd oldNext old.data.parserState oldFinished.cmdState ctx))
|
||||
-- also wait on old command parse snapshot as parsing is cheap and may allow for
|
||||
-- elaboration reuse
|
||||
oldNext.bindIO (sync := true) fun oldNext => do
|
||||
parseCmd oldNext newParserState oldFinished.cmdState ctx))
|
||||
else return old -- terminal command, we're done!
|
||||
|
||||
-- fast path, do not even start new task for this snapshot
|
||||
if let some old := old? then
|
||||
if let some nextCom ← old.next?.bindM (·.get?) then
|
||||
if let some nextCom ← old.nextCmdSnap?.bindM (·.get?) then
|
||||
if (← isBeforeEditPos nextCom.data.parserState.pos) then
|
||||
return .pure (← unchanged old)
|
||||
return .pure (← unchanged old old.data.parserState)
|
||||
|
||||
SnapshotTask.ofIO (some ⟨parserState.pos, ctx.input.endPos⟩) do
|
||||
let beginPos := parserState.pos
|
||||
@@ -440,15 +504,19 @@ where
|
||||
-- semi-fast path
|
||||
if let some old := old? then
|
||||
if (← isBeforeEditPos parserState.pos ctx) && old.data.stx == stx then
|
||||
return (← unchanged old)
|
||||
-- on first change, make sure to cancel all further old tasks
|
||||
old.cancel
|
||||
-- Here we must make sure to pass the *new* parser state; see NOTE in `unchanged`
|
||||
return (← unchanged old parserState)
|
||||
-- on first change, make sure to cancel old invocation
|
||||
-- TODO: pass token into incrementality-aware elaborators to improve reuse of still-valid,
|
||||
-- still-running elaboration steps?
|
||||
if let some tk := ctx.oldCancelTk? then
|
||||
tk.set
|
||||
|
||||
-- definitely resolved in `doElab` task
|
||||
let elabPromise ← IO.Promise.new
|
||||
let tacticCache ← old?.map (·.data.tacticCache) |>.getDM (IO.mkRef {})
|
||||
let finishedSnap ←
|
||||
doElab stx cmdState msgLog.hasErrors beginPos
|
||||
doElab stx cmdState beginPos
|
||||
{ old? := old?.map fun old => ⟨old.data.stx, old.data.elabSnap⟩, new := elabPromise }
|
||||
tacticCache
|
||||
ctx
|
||||
@@ -461,19 +529,24 @@ where
|
||||
diagnostics := (← Snapshot.Diagnostics.ofMessageLog msgLog)
|
||||
stx
|
||||
parserState
|
||||
elabSnap := { range? := finishedSnap.range?, task := elabPromise.result }
|
||||
elabSnap := { range? := stx.getRange?, task := elabPromise.result }
|
||||
finishedSnap
|
||||
tacticCache
|
||||
}
|
||||
|
||||
doElab (stx : Syntax) (cmdState : Command.State) (hasParseError : Bool) (beginPos : String.Pos)
|
||||
doElab (stx : Syntax) (cmdState : Command.State) (beginPos : String.Pos)
|
||||
(snap : SnapshotBundle DynamicSnapshot) (tacticCache : IO.Ref Tactic.Cache) :
|
||||
LeanProcessingM (SnapshotTask CommandFinishedSnapshot) := do
|
||||
let ctx ← read
|
||||
|
||||
-- signature elaboration task; for now, does full elaboration
|
||||
-- TODO: do tactic snapshots, reuse old state for them
|
||||
SnapshotTask.ofIO (stx.getRange?.getD ⟨beginPos, beginPos⟩) do
|
||||
-- (Try to) use last line of command as range for final snapshot task. This ensures we do not
|
||||
-- retract the progress bar to a previous position in case the command support incremental
|
||||
-- reporting but has significant work after resolving its last incremental promise, such as
|
||||
-- final type checking; if it does not support incrementality, `elabSnap` constructed in
|
||||
-- `parseCmd` and containing the entire range of the command will determine the reported
|
||||
-- progress and be resolved effectively at the same time as this snapshot task, so `tailPos` is
|
||||
-- irrelevant in this case.
|
||||
let tailPos := stx.getTailPos? |>.getD beginPos
|
||||
SnapshotTask.ofIO (some ⟨tailPos, tailPos⟩) do
|
||||
let scope := cmdState.scopes.head!
|
||||
let cmdStateRef ← IO.mkRef { cmdState with messages := .empty }
|
||||
/-
|
||||
@@ -487,26 +560,18 @@ where
|
||||
cmdPos := beginPos
|
||||
tacticCache? := some tacticCacheNew
|
||||
snap? := some snap
|
||||
cancelTk? := some ctx.newCancelTk
|
||||
}
|
||||
let (output, _) ←
|
||||
IO.FS.withIsolatedStreams (isolateStderr := stderrAsMessages.get scope.opts) do
|
||||
liftM (m := BaseIO) do
|
||||
Elab.Command.catchExceptions
|
||||
withLoggingExceptions
|
||||
(getResetInfoTrees *> Elab.Command.elabCommandTopLevel stx)
|
||||
cmdCtx cmdStateRef
|
||||
let postNew := (← tacticCacheNew.get).post
|
||||
tacticCache.modify fun _ => { pre := postNew, post := {} }
|
||||
let cmdState ← cmdStateRef.get
|
||||
let mut messages := cmdState.messages
|
||||
-- `stx.hasMissing` should imply `hasParseError`, but the latter should be cheaper to check in
|
||||
-- general
|
||||
if !showPartialSyntaxErrors.get cmdState.scopes[0]!.opts && hasParseError &&
|
||||
stx.hasMissing then
|
||||
-- discard elaboration errors, except for a few important and unlikely misleading ones, on
|
||||
-- parse error
|
||||
messages := ⟨messages.msgs.filter fun msg =>
|
||||
msg.data.hasTag (fun tag => tag == `Elab.synthPlaceholder ||
|
||||
tag == `Tactic.unsolvedGoals || (`_traceMsg).isSuffixOf tag)⟩
|
||||
if !output.isEmpty then
|
||||
messages := messages.add {
|
||||
fileName := ctx.fileName
|
||||
@@ -523,13 +588,25 @@ where
|
||||
cmdState
|
||||
}
|
||||
|
||||
/--
|
||||
Convenience function for tool uses of the language processor that skips header handling.
|
||||
-/
|
||||
def processCommands (inputCtx : Parser.InputContext) (parserState : Parser.ModuleParserState)
|
||||
(commandState : Command.State)
|
||||
(old? : Option (Parser.InputContext × CommandParsedSnapshot) := none) :
|
||||
BaseIO (SnapshotTask CommandParsedSnapshot) := do
|
||||
process.parseCmd (old?.map (·.2)) parserState commandState
|
||||
|>.run (old?.map (·.1))
|
||||
|>.run { inputCtx with }
|
||||
|
||||
|
||||
/-- Waits for and returns final environment, if importing was successful. -/
|
||||
partial def waitForFinalEnv? (snap : InitialSnapshot) : Option Environment := do
|
||||
let snap ← snap.result?
|
||||
let snap ← snap.processedSnap.get.result?
|
||||
goCmd snap.firstCmdSnap.get
|
||||
where goCmd snap :=
|
||||
if let some next := snap.next? then
|
||||
if let some next := snap.nextCmdSnap? then
|
||||
goCmd next.get
|
||||
else
|
||||
snap.data.finishedSnap.get.cmdState.env
|
||||
|
||||
@@ -39,13 +39,6 @@ structure NamingContext where
|
||||
currNamespace : Name
|
||||
openDecls : List OpenDecl
|
||||
|
||||
/-- Lazily formatted text to be used in `MessageData`. -/
|
||||
structure PPFormat where
|
||||
/-- Pretty-prints text using surrounding context, if any. -/
|
||||
pp : Option PPContext → IO FormatWithInfos
|
||||
/-- Searches for synthetic sorries in original input. Used to filter out certain messages. -/
|
||||
hasSyntheticSorry : MetavarContext → Bool := fun _ => false
|
||||
|
||||
structure TraceData where
|
||||
/-- Trace class, e.g. `Elab.step`. -/
|
||||
cls : Name
|
||||
@@ -60,10 +53,9 @@ structure TraceData where
|
||||
|
||||
/-- Structured message data. We use it for reporting errors, trace messages, etc. -/
|
||||
inductive MessageData where
|
||||
/-- Eagerly formatted text. We inspect this in various hacks, so it is not immediately subsumed by `ofPPFormat`. -/
|
||||
| ofFormat : Format → MessageData
|
||||
/-- Lazily formatted text. -/
|
||||
| ofPPFormat : PPFormat → MessageData
|
||||
/-- Eagerly formatted text with info annotations.
|
||||
This constructor is inspected in various hacks. -/
|
||||
| ofFormatWithInfos : FormatWithInfos → MessageData
|
||||
| ofGoal : MVarId → MessageData
|
||||
/-- `withContext ctx d` specifies the pretty printing context `(env, mctx, lctx, opts)` for the nested expressions in `d`. -/
|
||||
| withContext : MessageDataContext → MessageData → MessageData
|
||||
@@ -78,12 +70,45 @@ inductive MessageData where
|
||||
Example: an inspector that tries to find "definitional equality failures" may look for the tag "DefEqFailure". -/
|
||||
| tagged : Name → MessageData → MessageData
|
||||
| trace (data : TraceData) (msg : MessageData) (children : Array MessageData)
|
||||
deriving Inhabited
|
||||
/-- A lazy message.
|
||||
The provided thunk will not be run until it is about to be displayed.
|
||||
This can save computation in cases where the message may never be seen,
|
||||
e.g. when nested inside a collapsed trace.
|
||||
|
||||
The `Dynamic` value is expected to be a `MessageData`,
|
||||
which is a workaround for the positivity restriction.
|
||||
|
||||
If the thunked message is produced for a term that contains a synthetic sorry,
|
||||
`hasSyntheticSorry` should return `true`.
|
||||
This is used to filter out certain messages. -/
|
||||
| ofLazy (f : Option PPContext → IO Dynamic) (hasSyntheticSorry : MetavarContext → Bool)
|
||||
deriving Inhabited, TypeName
|
||||
|
||||
namespace MessageData
|
||||
|
||||
/-- Eagerly formatted text. -/
|
||||
def ofFormat (fmt : Format) : MessageData := .ofFormatWithInfos ⟨fmt, .empty⟩
|
||||
|
||||
/--
|
||||
Lazy message data production, with access to the context as given by
|
||||
a surrounding `MessageData.withContext` (which is expected to exist).
|
||||
-/
|
||||
def lazy (f : PPContext → IO MessageData)
|
||||
(hasSyntheticSorry : MetavarContext → Bool := fun _ => false) : MessageData :=
|
||||
.ofLazy (hasSyntheticSorry := hasSyntheticSorry) fun ctx? => do
|
||||
let msg ← match ctx? with
|
||||
| .none => pure (.ofFormat "(invalid MessageData.lazy, missing context)")
|
||||
| .some ctx => f ctx
|
||||
return Dynamic.mk msg
|
||||
|
||||
variable (p : Name → Bool) in
|
||||
/-- Returns true when the message contains a `MessageData.tagged tag ..` constructor where `p tag` is true. -/
|
||||
/-- Returns true when the message contains a `MessageData.tagged tag ..` constructor where `p tag`
|
||||
is true.
|
||||
|
||||
This does not descend into lazily generated subtress (`.ofLazy`); message tags
|
||||
of interest (like those added by `logLinter`) are expected to be near the root
|
||||
of the `MessageData`, and not hidden inside `.ofLazy`.
|
||||
-/
|
||||
partial def hasTag : MessageData → Bool
|
||||
| withContext _ msg => hasTag msg
|
||||
| withNamingContext _ msg => hasTag msg
|
||||
@@ -106,26 +131,14 @@ def mkPPContext (nCtx : NamingContext) (ctx : MessageDataContext) : PPContext :=
|
||||
def ofSyntax (stx : Syntax) : MessageData :=
|
||||
-- discard leading/trailing whitespace
|
||||
let stx := stx.copyHeadTailInfoFrom .missing
|
||||
.ofPPFormat {
|
||||
pp := fun
|
||||
| some ctx => ppTerm ctx ⟨stx⟩ -- HACK: might not be a term
|
||||
| none => return stx.formatStx
|
||||
}
|
||||
.lazy fun ctx => ofFormat <$> ppTerm ctx ⟨stx⟩ -- HACK: might not be a term
|
||||
|
||||
def ofExpr (e : Expr) : MessageData :=
|
||||
.ofPPFormat {
|
||||
pp := fun
|
||||
| some ctx => ppExprWithInfos ctx e
|
||||
| none => return format (toString e)
|
||||
hasSyntheticSorry := (instantiateMVarsCore · e |>.1.hasSyntheticSorry)
|
||||
}
|
||||
.lazy (fun ctx => ofFormatWithInfos <$> ppExprWithInfos ctx e)
|
||||
(fun mctx => instantiateMVarsCore mctx e |>.1.hasSyntheticSorry)
|
||||
|
||||
def ofLevel (l : Level) : MessageData :=
|
||||
.ofPPFormat {
|
||||
pp := fun
|
||||
| some ctx => ppLevel ctx l
|
||||
| none => return format l
|
||||
}
|
||||
.lazy fun ctx => ofFormat <$> ppLevel ctx l
|
||||
|
||||
def ofName (n : Name) : MessageData := ofFormat (format n)
|
||||
|
||||
@@ -133,7 +146,7 @@ partial def hasSyntheticSorry (msg : MessageData) : Bool :=
|
||||
visit none msg
|
||||
where
|
||||
visit (mctx? : Option MetavarContext) : MessageData → Bool
|
||||
| ofPPFormat f => f.hasSyntheticSorry (mctx?.getD {})
|
||||
| ofLazy _ f => f (mctx?.getD {})
|
||||
| withContext ctx msg => visit ctx.mctx msg
|
||||
| withNamingContext _ msg => visit mctx? msg
|
||||
| nest _ msg => visit mctx? msg
|
||||
@@ -144,8 +157,7 @@ where
|
||||
| _ => false
|
||||
|
||||
partial def formatAux : NamingContext → Option MessageDataContext → MessageData → IO Format
|
||||
| _, _, ofFormat fmt => return fmt
|
||||
| nCtx, ctx?, ofPPFormat f => (·.fmt) <$> f.pp (ctx?.map (mkPPContext nCtx))
|
||||
| _, _, ofFormatWithInfos fmt => return fmt.1
|
||||
| _, none, ofGoal mvarId => return "goal " ++ format (mkMVar mvarId)
|
||||
| nCtx, some ctx, ofGoal mvarId => ppGoal (mkPPContext nCtx ctx) mvarId
|
||||
| nCtx, _, withContext ctx d => formatAux nCtx ctx d
|
||||
@@ -161,6 +173,11 @@ partial def formatAux : NamingContext → Option MessageDataContext → MessageD
|
||||
msg := f!"{msg} {(← formatAux nCtx ctx header).nest 2}"
|
||||
let children ← children.mapM (formatAux nCtx ctx)
|
||||
return .nest 2 (.joinSep (msg::children.toList) "\n")
|
||||
| nCtx, ctx?, ofLazy pp _ => do
|
||||
let dyn ← pp (ctx?.map (mkPPContext nCtx))
|
||||
let some msg := dyn.get? MessageData
|
||||
| panic! s!"MessageData.ofLazy: expected MessageData in Dynamic, got {dyn.typeName}"
|
||||
formatAux nCtx ctx? msg
|
||||
|
||||
protected def format (msgData : MessageData) : IO Format :=
|
||||
formatAux { currNamespace := Name.anonymous, openDecls := [] } none msgData
|
||||
@@ -281,47 +298,69 @@ protected def toJson (msg : Message) : IO Json := do
|
||||
|
||||
end Message
|
||||
|
||||
/-- A persistent array of messages. -/
|
||||
/--
|
||||
A persistent array of messages.
|
||||
|
||||
In the Lean elaborator, we use a fresh message log per command but may also report diagnostics at
|
||||
various points inside a command, which will empty `unreported` and updated `hadErrors` accordingly
|
||||
(see `CoreM.getAndEmptyMessageLog`).
|
||||
-/
|
||||
structure MessageLog where
|
||||
msgs : PersistentArray Message := {}
|
||||
/--
|
||||
If true, there was an error in the log previously that has already been reported to the user and
|
||||
removed from the log. Thus we say that in the current context (usually the current command), we
|
||||
"have errors" if either this flag is set or there is an error in `msgs`; see
|
||||
`MessageLog.hasErrors`. If we have errors, we suppress some error messages that are often the
|
||||
result of a previous error.
|
||||
-/
|
||||
/-
|
||||
Design note: We considered introducing a `hasErrors` field instead that already includes the
|
||||
presence of errors in `msgs` but this would not be compatible with e.g.
|
||||
`MessageLog.errorsToWarnings`.
|
||||
-/
|
||||
hadErrors : Bool := false
|
||||
/-- The list of messages not already reported, in insertion order. -/
|
||||
unreported : PersistentArray Message := {}
|
||||
deriving Inhabited
|
||||
|
||||
namespace MessageLog
|
||||
def empty : MessageLog := ⟨{}⟩
|
||||
def empty : MessageLog := {}
|
||||
|
||||
def isEmpty (log : MessageLog) : Bool :=
|
||||
log.msgs.isEmpty
|
||||
@[deprecated "renamed to `unreported`; direct access should in general be avoided in favor of \
|
||||
using `MessageLog.toList/toArray`"]
|
||||
def msgs : MessageLog → PersistentArray Message := unreported
|
||||
|
||||
def hasUnreported (log : MessageLog) : Bool :=
|
||||
!log.unreported.isEmpty
|
||||
|
||||
def add (msg : Message) (log : MessageLog) : MessageLog :=
|
||||
⟨log.msgs.push msg⟩
|
||||
{ log with unreported := log.unreported.push msg }
|
||||
|
||||
protected def append (l₁ l₂ : MessageLog) : MessageLog :=
|
||||
⟨l₁.msgs ++ l₂.msgs⟩
|
||||
{ hadErrors := l₁.hadErrors || l₂.hadErrors, unreported := l₁.unreported ++ l₂.unreported }
|
||||
|
||||
instance : Append MessageLog :=
|
||||
⟨MessageLog.append⟩
|
||||
|
||||
def hasErrors (log : MessageLog) : Bool :=
|
||||
log.msgs.any fun m => match m.severity with
|
||||
| MessageSeverity.error => true
|
||||
| _ => false
|
||||
log.hadErrors || log.unreported.any (·.severity matches .error)
|
||||
|
||||
def errorsToWarnings (log : MessageLog) : MessageLog :=
|
||||
{ msgs := log.msgs.map (fun m => match m.severity with | MessageSeverity.error => { m with severity := MessageSeverity.warning } | _ => m) }
|
||||
{ unreported := log.unreported.map (fun m => match m.severity with | MessageSeverity.error => { m with severity := MessageSeverity.warning } | _ => m) }
|
||||
|
||||
def getInfoMessages (log : MessageLog) : MessageLog :=
|
||||
{ msgs := log.msgs.filter fun m => match m.severity with | MessageSeverity.information => true | _ => false }
|
||||
{ unreported := log.unreported.filter fun m => match m.severity with | MessageSeverity.information => true | _ => false }
|
||||
|
||||
def forM {m : Type → Type} [Monad m] (log : MessageLog) (f : Message → m Unit) : m Unit :=
|
||||
log.msgs.forM f
|
||||
log.unreported.forM f
|
||||
|
||||
/-- Converts the log to a list, oldest message first. -/
|
||||
/-- Converts the unreported messages to a list, oldest message first. -/
|
||||
def toList (log : MessageLog) : List Message :=
|
||||
log.msgs.toList
|
||||
log.unreported.toList
|
||||
|
||||
/-- Converts the log to an array, oldest message first. -/
|
||||
/-- Converts the unreported messages to an array, oldest message first. -/
|
||||
def toArray (log : MessageLog) : Array Message :=
|
||||
log.msgs.toArray
|
||||
log.unreported.toArray
|
||||
|
||||
end MessageLog
|
||||
|
||||
|
||||
@@ -212,7 +212,17 @@ instance : Hashable InfoCacheKey :=
|
||||
⟨fun ⟨transparency, expr, nargs⟩ => mixHash (hash transparency) <| mixHash (hash expr) (hash nargs)⟩
|
||||
end InfoCacheKey
|
||||
|
||||
abbrev SynthInstanceCache := PersistentHashMap (LocalInstances × Expr) (Option Expr)
|
||||
structure SynthInstanceCacheKey where
|
||||
localInsts : LocalInstances
|
||||
type : Expr
|
||||
/--
|
||||
Value of `synthPendingDepth` when instance was synthesized or failed to be synthesized.
|
||||
See issue #2522.
|
||||
-/
|
||||
synthPendingDepth : Nat
|
||||
deriving Hashable, BEq
|
||||
|
||||
abbrev SynthInstanceCache := PersistentHashMap SynthInstanceCacheKey (Option Expr)
|
||||
|
||||
abbrev InferTypeCache := PersistentExprStructMap Expr
|
||||
abbrev FunInfoCache := PersistentHashMap InfoCacheKey FunInfo
|
||||
@@ -273,6 +283,8 @@ structure Diagnostics where
|
||||
heuristicCounter : PHashMap Name Nat := {}
|
||||
/-- Number of times a TC instance is used. -/
|
||||
instanceCounter : PHashMap Name Nat := {}
|
||||
/-- Pending instances that were not synthesized because `maxSynthPendingDepth` has been reached. -/
|
||||
synthPendingFailures : PHashMap Expr MessageData := {}
|
||||
deriving Inhabited
|
||||
|
||||
/--
|
||||
@@ -292,10 +304,15 @@ structure State where
|
||||
Backtrackable state for the `MetaM` monad.
|
||||
-/
|
||||
structure SavedState where
|
||||
core : Core.State
|
||||
core : Core.SavedState
|
||||
meta : State
|
||||
deriving Nonempty
|
||||
|
||||
register_builtin_option maxSynthPendingDepth : Nat := {
|
||||
defValue := 1
|
||||
descr := "maximum number of nested `synthPending` invocations. When resolving unification constraints, pending type class problems may need to be synthesized. These type class problems may create new unification constraints that again require solving new type class problems. This option puts a threshold on how many nested problems are created."
|
||||
}
|
||||
|
||||
/--
|
||||
Contextual information for the `MetaM` monad.
|
||||
-/
|
||||
@@ -311,8 +328,8 @@ structure Context where
|
||||
Track the number of nested `synthPending` invocations. Nested invocations can happen
|
||||
when the type class resolution invokes `synthPending`.
|
||||
|
||||
Remark: in the current implementation, `synthPending` fails if `synthPendingDepth > 0`.
|
||||
We will add a configuration option if necessary. -/
|
||||
Remark: `synthPending` fails if `synthPendingDepth > maxSynthPendingDepth`.
|
||||
-/
|
||||
synthPendingDepth : Nat := 0
|
||||
/--
|
||||
A predicate to control whether a constant can be unfolded or not at `whnf`.
|
||||
@@ -393,20 +410,22 @@ instance : AddMessageContext MetaM where
|
||||
addMessageContext := addMessageContextFull
|
||||
|
||||
protected def saveState : MetaM SavedState :=
|
||||
return { core := (← getThe Core.State), meta := (← get) }
|
||||
return { core := (← Core.saveState), meta := (← get) }
|
||||
|
||||
/-- Restore backtrackable parts of the state. -/
|
||||
def SavedState.restore (b : SavedState) : MetaM Unit := do
|
||||
Core.restore b.core
|
||||
b.core.restore
|
||||
modify fun s => { s with mctx := b.meta.mctx, zetaDeltaFVarIds := b.meta.zetaDeltaFVarIds, postponed := b.meta.postponed }
|
||||
|
||||
/--
|
||||
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 (b : SavedState) : MetaM Unit := do
|
||||
Core.restoreFull b.core
|
||||
set b.meta
|
||||
@[specialize, inherit_doc Core.withRestoreOrSaveFull]
|
||||
def withRestoreOrSaveFull (reusableResult? : Option (α × SavedState))
|
||||
(cont : MetaM SavedState → MetaM α) : MetaM α := do
|
||||
if let some (_, state) := reusableResult? then
|
||||
set state.meta
|
||||
let reusableResult? := reusableResult?.map (fun (val, state) => (val, state.core))
|
||||
controlAt CoreM fun runInCoreM =>
|
||||
Core.withRestoreOrSaveFull reusableResult? fun restore =>
|
||||
runInCoreM <| cont (return { core := (← restore), meta := (← get) })
|
||||
|
||||
instance : MonadBacktrack SavedState MetaM where
|
||||
saveState := Meta.saveState
|
||||
@@ -470,21 +489,30 @@ variable [MonadControlT MetaM n] [Monad n]
|
||||
|
||||
/-- If diagnostics are enabled, record that `declName` has been unfolded. -/
|
||||
def recordUnfold (declName : Name) : MetaM Unit := do
|
||||
modifyDiag fun { unfoldCounter, heuristicCounter, instanceCounter } =>
|
||||
modifyDiag fun { unfoldCounter, heuristicCounter, instanceCounter, synthPendingFailures } =>
|
||||
let newC := if let some c := unfoldCounter.find? declName then c + 1 else 1
|
||||
{ unfoldCounter := unfoldCounter.insert declName newC, heuristicCounter, instanceCounter }
|
||||
{ unfoldCounter := unfoldCounter.insert declName newC, heuristicCounter, instanceCounter, synthPendingFailures }
|
||||
|
||||
/-- If diagnostics are enabled, record that heuristic for solving `f a =?= f b` has been used. -/
|
||||
def recordDefEqHeuristic (declName : Name) : MetaM Unit := do
|
||||
modifyDiag fun { unfoldCounter, heuristicCounter, instanceCounter } =>
|
||||
modifyDiag fun { unfoldCounter, heuristicCounter, instanceCounter, synthPendingFailures } =>
|
||||
let newC := if let some c := heuristicCounter.find? declName then c + 1 else 1
|
||||
{ unfoldCounter, heuristicCounter := heuristicCounter.insert declName newC, instanceCounter }
|
||||
{ unfoldCounter, heuristicCounter := heuristicCounter.insert declName newC, instanceCounter, synthPendingFailures }
|
||||
|
||||
/-- If diagnostics are enabled, record that instance `declName` was used during TC resolution. -/
|
||||
def recordInstance (declName : Name) : MetaM Unit := do
|
||||
modifyDiag fun { unfoldCounter, heuristicCounter, instanceCounter } =>
|
||||
modifyDiag fun { unfoldCounter, heuristicCounter, instanceCounter, synthPendingFailures } =>
|
||||
let newC := if let some c := instanceCounter.find? declName then c + 1 else 1
|
||||
{ unfoldCounter, heuristicCounter, instanceCounter := instanceCounter.insert declName newC }
|
||||
{ unfoldCounter, heuristicCounter, instanceCounter := instanceCounter.insert declName newC, synthPendingFailures }
|
||||
|
||||
/-- If diagnostics are enabled, record that synth pending failures. -/
|
||||
def recordSynthPendingFailure (type : Expr) : MetaM Unit := do
|
||||
if (← isDiagnosticsEnabled) then
|
||||
unless (← get).diag.synthPendingFailures.contains type do
|
||||
-- We need to save the full context since type class resolution uses multiple metavar contexts and different local contexts
|
||||
let msg ← addMessageContextFull m!"{type}"
|
||||
modifyDiag fun { unfoldCounter, heuristicCounter, instanceCounter, synthPendingFailures } =>
|
||||
{ unfoldCounter, heuristicCounter, instanceCounter, synthPendingFailures := synthPendingFailures.insert type msg }
|
||||
|
||||
def getLocalInstances : MetaM LocalInstances :=
|
||||
return (← read).localInstances
|
||||
@@ -629,7 +657,7 @@ Return `none` if `mvarId` has no declaration in the current metavariable context
|
||||
def _root_.Lean.MVarId.findDecl? (mvarId : MVarId) : MetaM (Option MetavarDecl) :=
|
||||
return (← getMCtx).findDecl? mvarId
|
||||
|
||||
@[deprecated MVarId.findDecl?]
|
||||
@[deprecated MVarId.findDecl? (since := "2022-07-15")]
|
||||
def findMVarDecl? (mvarId : MVarId) : MetaM (Option MetavarDecl) :=
|
||||
mvarId.findDecl?
|
||||
|
||||
@@ -642,7 +670,7 @@ def _root_.Lean.MVarId.getDecl (mvarId : MVarId) : MetaM MetavarDecl := do
|
||||
| some d => pure d
|
||||
| none => throwError "unknown metavariable '?{mvarId.name}'"
|
||||
|
||||
@[deprecated MVarId.getDecl]
|
||||
@[deprecated MVarId.getDecl (since := "2022-07-15")]
|
||||
def getMVarDecl (mvarId : MVarId) : MetaM MetavarDecl := do
|
||||
mvarId.getDecl
|
||||
|
||||
@@ -652,7 +680,7 @@ Return `mvarId` kind. Throw an exception if `mvarId` is not declared in the curr
|
||||
def _root_.Lean.MVarId.getKind (mvarId : MVarId) : MetaM MetavarKind :=
|
||||
return (← mvarId.getDecl).kind
|
||||
|
||||
@[deprecated MVarId.getKind]
|
||||
@[deprecated MVarId.getKind (since := "2022-07-15")]
|
||||
def getMVarDeclKind (mvarId : MVarId) : MetaM MetavarKind :=
|
||||
mvarId.getKind
|
||||
|
||||
@@ -669,7 +697,7 @@ Set `mvarId` kind in the current metavariable context.
|
||||
def _root_.Lean.MVarId.setKind (mvarId : MVarId) (kind : MetavarKind) : MetaM Unit :=
|
||||
modifyMCtx fun mctx => mctx.setMVarKind mvarId kind
|
||||
|
||||
@[deprecated MVarId.setKind]
|
||||
@[deprecated MVarId.setKind (since := "2022-07-15")]
|
||||
def setMVarKind (mvarId : MVarId) (kind : MetavarKind) : MetaM Unit :=
|
||||
mvarId.setKind kind
|
||||
|
||||
@@ -678,7 +706,7 @@ def setMVarKind (mvarId : MVarId) (kind : MetavarKind) : MetaM Unit :=
|
||||
def _root_.Lean.MVarId.setType (mvarId : MVarId) (type : Expr) : MetaM Unit := do
|
||||
modifyMCtx fun mctx => mctx.setMVarType mvarId type
|
||||
|
||||
@[deprecated MVarId.setType]
|
||||
@[deprecated MVarId.setType (since := "2022-07-15")]
|
||||
def setMVarType (mvarId : MVarId) (type : Expr) : MetaM Unit := do
|
||||
mvarId.setType type
|
||||
|
||||
@@ -689,22 +717,25 @@ That is, its `depth` is different from the current metavariable context depth.
|
||||
def _root_.Lean.MVarId.isReadOnly (mvarId : MVarId) : MetaM Bool := do
|
||||
return (← mvarId.getDecl).depth != (← getMCtx).depth
|
||||
|
||||
@[deprecated MVarId.isReadOnly]
|
||||
@[deprecated MVarId.isReadOnly (since := "2022-07-15")]
|
||||
def isReadOnlyExprMVar (mvarId : MVarId) : MetaM Bool := do
|
||||
mvarId.isReadOnly
|
||||
|
||||
/--
|
||||
Return true if `mvarId.isReadOnly` return true or if `mvarId` is a synthetic opaque metavariable.
|
||||
Returns true if `mvarId.isReadOnly` returns true or if `mvarId` is a synthetic opaque metavariable.
|
||||
|
||||
Recall `isDefEq` will not assign a value to `mvarId` if `mvarId.isReadOnlyOrSyntheticOpaque`.
|
||||
-/
|
||||
def _root_.Lean.MVarId.isReadOnlyOrSyntheticOpaque (mvarId : MVarId) : MetaM Bool := do
|
||||
let mvarDecl ← mvarId.getDecl
|
||||
match mvarDecl.kind with
|
||||
| MetavarKind.syntheticOpaque => return !(← getConfig).assignSyntheticOpaque
|
||||
| _ => return mvarDecl.depth != (← getMCtx).depth
|
||||
if mvarDecl.depth != (← getMCtx).depth then
|
||||
return true
|
||||
else
|
||||
match mvarDecl.kind with
|
||||
| MetavarKind.syntheticOpaque => return !(← getConfig).assignSyntheticOpaque
|
||||
| _ => return false
|
||||
|
||||
@[deprecated MVarId.isReadOnlyOrSyntheticOpaque]
|
||||
@[deprecated MVarId.isReadOnlyOrSyntheticOpaque (since := "2022-07-15")]
|
||||
def isReadOnlyOrSyntheticOpaqueExprMVar (mvarId : MVarId) : MetaM Bool := do
|
||||
mvarId.isReadOnlyOrSyntheticOpaque
|
||||
|
||||
@@ -716,7 +747,7 @@ def _root_.Lean.LMVarId.getLevel (mvarId : LMVarId) : MetaM Nat := do
|
||||
| some depth => return depth
|
||||
| _ => throwError "unknown universe metavariable '?{mvarId.name}'"
|
||||
|
||||
@[deprecated LMVarId.getLevel]
|
||||
@[deprecated LMVarId.getLevel (since := "2022-07-15")]
|
||||
def getLevelMVarDepth (mvarId : LMVarId) : MetaM Nat :=
|
||||
mvarId.getLevel
|
||||
|
||||
@@ -727,7 +758,7 @@ That is, its `depth` is different from the current metavariable context depth.
|
||||
def _root_.Lean.LMVarId.isReadOnly (mvarId : LMVarId) : MetaM Bool :=
|
||||
return (← mvarId.getLevel) < (← getMCtx).levelAssignDepth
|
||||
|
||||
@[deprecated LMVarId.isReadOnly]
|
||||
@[deprecated LMVarId.isReadOnly (since := "2022-07-15")]
|
||||
def isReadOnlyLevelMVar (mvarId : LMVarId) : MetaM Bool := do
|
||||
mvarId.isReadOnly
|
||||
|
||||
@@ -737,7 +768,7 @@ Set the user-facing name for the given metavariable.
|
||||
def _root_.Lean.MVarId.setUserName (mvarId : MVarId) (newUserName : Name) : MetaM Unit :=
|
||||
modifyMCtx fun mctx => mctx.setMVarUserName mvarId newUserName
|
||||
|
||||
@[deprecated MVarId.setUserName]
|
||||
@[deprecated MVarId.setUserName (since := "2022-07-15")]
|
||||
def setMVarUserName (mvarId : MVarId) (userNameNew : Name) : MetaM Unit :=
|
||||
mvarId.setUserName userNameNew
|
||||
|
||||
@@ -747,7 +778,7 @@ Throw an exception saying `fvarId` is not declared in the current local context.
|
||||
def _root_.Lean.FVarId.throwUnknown (fvarId : FVarId) : CoreM α :=
|
||||
throwError "unknown free variable '{mkFVar fvarId}'"
|
||||
|
||||
@[deprecated FVarId.throwUnknown]
|
||||
@[deprecated FVarId.throwUnknown (since := "2022-07-15")]
|
||||
def throwUnknownFVar (fvarId : FVarId) : MetaM α :=
|
||||
fvarId.throwUnknown
|
||||
|
||||
@@ -757,7 +788,7 @@ Return `some decl` if `fvarId` is declared in the current local context.
|
||||
def _root_.Lean.FVarId.findDecl? (fvarId : FVarId) : MetaM (Option LocalDecl) :=
|
||||
return (← getLCtx).find? fvarId
|
||||
|
||||
@[deprecated FVarId.findDecl?]
|
||||
@[deprecated FVarId.findDecl? (since := "2022-07-15")]
|
||||
def findLocalDecl? (fvarId : FVarId) : MetaM (Option LocalDecl) :=
|
||||
fvarId.findDecl?
|
||||
|
||||
@@ -770,7 +801,7 @@ def _root_.Lean.FVarId.getDecl (fvarId : FVarId) : MetaM LocalDecl := do
|
||||
| some d => return d
|
||||
| none => fvarId.throwUnknown
|
||||
|
||||
@[deprecated FVarId.getDecl]
|
||||
@[deprecated FVarId.getDecl (since := "2022-07-15")]
|
||||
def getLocalDecl (fvarId : FVarId) : MetaM LocalDecl := do
|
||||
fvarId.getDecl
|
||||
|
||||
@@ -799,6 +830,17 @@ context. Fails if the given expression is not a fvar or if no such declaration e
|
||||
def getFVarLocalDecl (fvar : Expr) : MetaM LocalDecl :=
|
||||
fvar.fvarId!.getDecl
|
||||
|
||||
/--
|
||||
Returns `true` if another local declaration in the local context depends on `fvarId`.
|
||||
-/
|
||||
def _root_.Lean.FVarId.hasForwardDeps (fvarId : FVarId) : MetaM Bool := do
|
||||
let decl ← fvarId.getDecl
|
||||
(← getLCtx).foldlM (init := false) (start := decl.index + 1) fun found other =>
|
||||
if found then
|
||||
return true
|
||||
else
|
||||
localDeclDependsOn other fvarId
|
||||
|
||||
/--
|
||||
Given a user-facing name for a free variable, return its declaration in the current local context.
|
||||
Throw an exception if free variable is not declared.
|
||||
@@ -837,7 +879,7 @@ contain a metavariable `?m` s.t. local context of `?m` contains a free variable
|
||||
def _root_.Lean.Expr.abstractRangeM (e : Expr) (n : Nat) (xs : Array Expr) : MetaM Expr :=
|
||||
liftMkBindingM <| MetavarContext.abstractRange e n xs
|
||||
|
||||
@[deprecated Expr.abstractRangeM]
|
||||
@[deprecated Expr.abstractRangeM (since := "2022-07-15")]
|
||||
def abstractRange (e : Expr) (n : Nat) (xs : Array Expr) : MetaM Expr :=
|
||||
e.abstractRangeM n xs
|
||||
|
||||
@@ -848,7 +890,7 @@ Similar to `Expr.abstract`, but handles metavariables correctly.
|
||||
def _root_.Lean.Expr.abstractM (e : Expr) (xs : Array Expr) : MetaM Expr :=
|
||||
e.abstractRangeM xs.size xs
|
||||
|
||||
@[deprecated Expr.abstractM]
|
||||
@[deprecated Expr.abstractM (since := "2022-07-15")]
|
||||
def abstract (e : Expr) (xs : Array Expr) : MetaM Expr :=
|
||||
e.abstractM xs
|
||||
|
||||
@@ -1061,16 +1103,20 @@ mutual
|
||||
|
||||
if `maxFVars?` is `some max`, then we interrupt the telescope construction
|
||||
when `fvars.size == max`
|
||||
|
||||
|
||||
If `cleanupAnnotations` is `true`, we apply `Expr.cleanupAnnotations` to each type in the telescope.
|
||||
-/
|
||||
private partial def forallTelescopeReducingAuxAux
|
||||
(reducing : Bool) (maxFVars? : Option Nat)
|
||||
(type : Expr)
|
||||
(k : Array Expr → Expr → MetaM α) : MetaM α := do
|
||||
(k : Array Expr → Expr → MetaM α) (cleanupAnnotations : Bool) : MetaM α := do
|
||||
let rec process (lctx : LocalContext) (fvars : Array Expr) (j : Nat) (type : Expr) : MetaM α := do
|
||||
match type with
|
||||
| .forallE n d b bi =>
|
||||
if fvarsSizeLtMaxFVars fvars maxFVars? then
|
||||
let d := d.instantiateRevRange j fvars.size fvars
|
||||
let d := if cleanupAnnotations then d.cleanupAnnotations else d
|
||||
let fvarId ← mkFreshFVarId
|
||||
let lctx := lctx.mkLocalDecl fvarId n d bi
|
||||
let fvar := mkFVar fvarId
|
||||
@@ -1095,13 +1141,13 @@ mutual
|
||||
k fvars type
|
||||
process (← getLCtx) #[] 0 type
|
||||
|
||||
private partial def forallTelescopeReducingAux (type : Expr) (maxFVars? : Option Nat) (k : Array Expr → Expr → MetaM α) : MetaM α := do
|
||||
private partial def forallTelescopeReducingAux (type : Expr) (maxFVars? : Option Nat) (k : Array Expr → Expr → MetaM α) (cleanupAnnotations : Bool) : MetaM α := do
|
||||
match maxFVars? with
|
||||
| some 0 => k #[] type
|
||||
| _ => do
|
||||
let newType ← whnf type
|
||||
if newType.isForall then
|
||||
forallTelescopeReducingAuxAux true maxFVars? newType k
|
||||
forallTelescopeReducingAuxAux true maxFVars? newType k cleanupAnnotations
|
||||
else
|
||||
k #[] type
|
||||
|
||||
@@ -1125,7 +1171,7 @@ mutual
|
||||
|
||||
private partial def isClassExpensive? (type : Expr) : MetaM (Option Name) :=
|
||||
withReducible do -- when testing whether a type is a type class, we only unfold reducible constants.
|
||||
forallTelescopeReducingAux type none fun _ type => isClassApp? type
|
||||
forallTelescopeReducingAux type none (cleanupAnnotations := false) fun _ type => isClassApp? type
|
||||
|
||||
private partial def isClassImp? (type : Expr) : MetaM (Option Name) := do
|
||||
match (← isClassQuick? type) with
|
||||
@@ -1154,15 +1200,18 @@ private def withNewLocalInstancesImpAux (fvars : Array Expr) (j : Nat) : n α
|
||||
partial def withNewLocalInstances (fvars : Array Expr) (j : Nat) : n α → n α :=
|
||||
mapMetaM <| withNewLocalInstancesImpAux fvars j
|
||||
|
||||
@[inline] private def forallTelescopeImp (type : Expr) (k : Array Expr → Expr → MetaM α) : MetaM α := do
|
||||
forallTelescopeReducingAuxAux (reducing := false) (maxFVars? := none) type k
|
||||
@[inline] private def forallTelescopeImp (type : Expr) (k : Array Expr → Expr → MetaM α) (cleanupAnnotations : Bool) : MetaM α := do
|
||||
forallTelescopeReducingAuxAux (reducing := false) (maxFVars? := none) type k cleanupAnnotations
|
||||
|
||||
/--
|
||||
Given `type` of the form `forall xs, A`, execute `k xs A`.
|
||||
This combinator will declare local declarations, create free variables for them,
|
||||
execute `k` with updated local context, and make sure the cache is restored after executing `k`. -/
|
||||
def forallTelescope (type : Expr) (k : Array Expr → Expr → n α) : n α :=
|
||||
map2MetaM (fun k => forallTelescopeImp type k) k
|
||||
execute `k` with updated local context, and make sure the cache is restored after executing `k`.
|
||||
|
||||
If `cleanupAnnotations` is `true`, we apply `Expr.cleanupAnnotations` to each type in the telescope.
|
||||
-/
|
||||
def forallTelescope (type : Expr) (k : Array Expr → Expr → n α) (cleanupAnnotations := false) : n α :=
|
||||
map2MetaM (fun k => forallTelescopeImp type k cleanupAnnotations) k
|
||||
|
||||
/--
|
||||
Given a monadic function `f` that takes a type and a term of that type and produces a new term,
|
||||
@@ -1181,23 +1230,29 @@ and then builds the lambda telescope term for the new term.
|
||||
def mapForallTelescope (f : Expr → MetaM Expr) (forallTerm : Expr) : MetaM Expr := do
|
||||
mapForallTelescope' (fun _ e => f e) forallTerm
|
||||
|
||||
private def forallTelescopeReducingImp (type : Expr) (k : Array Expr → Expr → MetaM α) : MetaM α :=
|
||||
forallTelescopeReducingAux type (maxFVars? := none) k
|
||||
private def forallTelescopeReducingImp (type : Expr) (k : Array Expr → Expr → MetaM α) (cleanupAnnotations : Bool) : MetaM α :=
|
||||
forallTelescopeReducingAux type (maxFVars? := none) k cleanupAnnotations
|
||||
|
||||
/--
|
||||
Similar to `forallTelescope`, but given `type` of the form `forall xs, A`,
|
||||
it reduces `A` and continues building the telescope if it is a `forall`. -/
|
||||
def forallTelescopeReducing (type : Expr) (k : Array Expr → Expr → n α) : n α :=
|
||||
map2MetaM (fun k => forallTelescopeReducingImp type k) k
|
||||
it reduces `A` and continues building the telescope if it is a `forall`.
|
||||
|
||||
private def forallBoundedTelescopeImp (type : Expr) (maxFVars? : Option Nat) (k : Array Expr → Expr → MetaM α) : MetaM α :=
|
||||
forallTelescopeReducingAux type maxFVars? k
|
||||
If `cleanupAnnotations` is `true`, we apply `Expr.cleanupAnnotations` to each type in the telescope.
|
||||
-/
|
||||
def forallTelescopeReducing (type : Expr) (k : Array Expr → Expr → n α) (cleanupAnnotations := false) : n α :=
|
||||
map2MetaM (fun k => forallTelescopeReducingImp type k cleanupAnnotations) k
|
||||
|
||||
private def forallBoundedTelescopeImp (type : Expr) (maxFVars? : Option Nat) (k : Array Expr → Expr → MetaM α) (cleanupAnnotations : Bool) : MetaM α :=
|
||||
forallTelescopeReducingAux type maxFVars? k cleanupAnnotations
|
||||
|
||||
/--
|
||||
Similar to `forallTelescopeReducing`, stops constructing the telescope when
|
||||
it reaches size `maxFVars`. -/
|
||||
def forallBoundedTelescope (type : Expr) (maxFVars? : Option Nat) (k : Array Expr → Expr → n α) : n α :=
|
||||
map2MetaM (fun k => forallBoundedTelescopeImp type maxFVars? k) k
|
||||
it reaches size `maxFVars`.
|
||||
|
||||
If `cleanupAnnotations` is `true`, we apply `Expr.cleanupAnnotations` to each type in the telescope.
|
||||
-/
|
||||
def forallBoundedTelescope (type : Expr) (maxFVars? : Option Nat) (k : Array Expr → Expr → n α) (cleanupAnnotations := false) : n α :=
|
||||
map2MetaM (fun k => forallBoundedTelescopeImp type maxFVars? k cleanupAnnotations) k
|
||||
|
||||
private partial def lambdaTelescopeImp (e : Expr) (consumeLet : Bool) (k : Array Expr → Expr → MetaM α) (cleanupAnnotations := false) : MetaM α := do
|
||||
process consumeLet (← getLCtx) #[] 0 e
|
||||
@@ -1407,7 +1462,9 @@ def withLocalInstancesImp (decls : List LocalDecl) (k : MetaM α) : MetaM α :=
|
||||
for decl in decls do
|
||||
unless decl.isImplementationDetail do
|
||||
if let some className ← isClass? decl.type then
|
||||
localInsts := localInsts.push { className, fvar := decl.toExpr }
|
||||
-- Ensure we don't add the same local instance multiple times.
|
||||
unless localInsts.any fun localInst => localInst.fvar.fvarId! == decl.fvarId do
|
||||
localInsts := localInsts.push { className, fvar := decl.toExpr }
|
||||
if localInsts.size == size then
|
||||
k
|
||||
else
|
||||
@@ -1487,7 +1544,7 @@ private def withMVarContextImp (mvarId : MVarId) (x : MetaM α) : MetaM α := do
|
||||
def _root_.Lean.MVarId.withContext (mvarId : MVarId) : n α → n α :=
|
||||
mapMetaM <| withMVarContextImp mvarId
|
||||
|
||||
@[deprecated MVarId.withContext]
|
||||
@[deprecated MVarId.withContext (since := "2022-07-15")]
|
||||
def withMVarContext (mvarId : MVarId) : n α → n α :=
|
||||
mvarId.withContext
|
||||
|
||||
|
||||
@@ -23,16 +23,14 @@ even if the definitional equality test were inexpensive.
|
||||
|
||||
This module aims to efficiently identify terms that are structurally different, definitionally equal, and structurally equal
|
||||
when we disregard implicit arguments like `@id (Id Nat) x` and `@id Nat x`. The procedure is straightforward. For each atom,
|
||||
we create a new abstracted atom by erasing all implicit information. We refer to this abstracted atom as a 'key.' For the two
|
||||
terms mentioned, the key would be `@id _ x`, where `_` denotes a placeholder for a dummy term. To preserve any
|
||||
pre-existing directed acyclic graph (DAG) structure and prevent exponential blowups while constructing the key, we employ
|
||||
unsafe techniques, such as pointer equality. Additionally, we maintain a mapping from keys to lists of terms, where each
|
||||
list contains terms sharing the same key but not definitionally equal. We posit that these lists will be small in practice.
|
||||
we create a hash that ignores all implicit information. Thus the hash for terms are equal `@id (Id Nat) x` and `@id Nat x`.
|
||||
To preserve any pre-existing directed acyclic graph (DAG) structure and prevent exponential blowups while computing the hash code,
|
||||
we employ unsafe techniques, such as pointer equality. Additionally, we maintain a mapping from hash to lists of terms, where each
|
||||
list contains terms sharing the same hash but not definitionally equal. We posit that these lists will be small in practice.
|
||||
-/
|
||||
|
||||
/--
|
||||
Auxiliary structure for creating a pointer-equality mapping from `Expr` to `Key`.
|
||||
We use this mapping to ensure we preserve the dag-structure of input expressions.
|
||||
Auxiliary structure for creating a pointer-equality.
|
||||
-/
|
||||
structure ExprVisited where
|
||||
e : Expr
|
||||
@@ -44,21 +42,17 @@ unsafe instance : BEq ExprVisited where
|
||||
unsafe instance : Hashable ExprVisited where
|
||||
hash a := USize.toUInt64 (ptrAddrUnsafe a)
|
||||
|
||||
abbrev Key := ExprVisited
|
||||
|
||||
/--
|
||||
State for the `CanonM` monad.
|
||||
-/
|
||||
structure State where
|
||||
/-- "Set" of all keys created so far. This is a hash-consing helper structure available in Lean. -/
|
||||
keys : ShareCommon.State.{0} Lean.ShareCommon.objectFactory := ShareCommon.State.mk Lean.ShareCommon.objectFactory
|
||||
/-- Mapping from `Expr` to `Key`. See comment at `ExprVisited`. -/
|
||||
/-- Mapping from `Expr` to hash. -/
|
||||
-- We use `HashMapImp` to ensure we don't have to tag `State` as `unsafe`.
|
||||
cache : HashMapImp ExprVisited Key := mkHashMapImp
|
||||
cache : HashMapImp ExprVisited UInt64 := mkHashMapImp
|
||||
/--
|
||||
Given a key `k` and `keyToExprs.find? k = some es`, we have that all `es` share key `k`, and
|
||||
Given a hashcode `k` and `keyToExprs.find? h = some es`, we have that all `es` have hashcode `k`, and
|
||||
are not definitionally equal modulo the transparency setting used. -/
|
||||
keyToExprs : HashMapImp Key (List Expr) := mkHashMapImp
|
||||
keyToExprs : HashMap UInt64 (List Expr) := mkHashMap
|
||||
|
||||
instance : Inhabited State where
|
||||
default := {}
|
||||
@@ -69,29 +63,26 @@ abbrev CanonM := ReaderT TransparencyMode $ StateRefT State MetaM
|
||||
The definitionally equality tests are performed using the given transparency mode.
|
||||
We claim `TransparencyMode.instances` is a good setting for most applications.
|
||||
-/
|
||||
def CanonM.run (x : CanonM α) (transparency := TransparencyMode.instances) : MetaM α :=
|
||||
StateRefT'.run' (x transparency) {}
|
||||
def CanonM.run' (x : CanonM α) (transparency := TransparencyMode.instances) (s : State := {}) : MetaM α :=
|
||||
StateRefT'.run' (x transparency) s
|
||||
|
||||
private def shareCommon (a : α) : CanonM α :=
|
||||
modifyGet fun { keys, cache, keyToExprs } =>
|
||||
let (a, keys) := ShareCommon.State.shareCommon keys a
|
||||
(a, { keys, cache, keyToExprs })
|
||||
def CanonM.run (x : CanonM α) (transparency := TransparencyMode.instances) (s : State := {}) : MetaM (α × State) :=
|
||||
StateRefT'.run (x transparency) s
|
||||
|
||||
private partial def mkKey (e : Expr) : CanonM Key := do
|
||||
if let some key := unsafe (← get).cache.find? { e } then
|
||||
return key
|
||||
private partial def mkKey (e : Expr) : CanonM UInt64 := do
|
||||
if let some hash := unsafe (← get).cache.find? { e } then
|
||||
return hash
|
||||
else
|
||||
let key ← match e with
|
||||
| .sort .. | .fvar .. | .bvar .. | .lit .. =>
|
||||
pure { e := (← shareCommon e) }
|
||||
return hash e
|
||||
| .const n _ =>
|
||||
pure { e := (← shareCommon (.const n [])) }
|
||||
return n.hash
|
||||
| .mvar .. =>
|
||||
-- We instantiate assigned metavariables because the
|
||||
-- pretty-printer also instantiates them.
|
||||
let eNew ← instantiateMVars e
|
||||
if eNew == e then pure { e := (← shareCommon e) }
|
||||
else mkKey eNew
|
||||
if eNew == e then return hash e else mkKey eNew
|
||||
| .mdata _ a => mkKey a
|
||||
| .app .. =>
|
||||
let f := e.getAppFn
|
||||
@@ -100,26 +91,23 @@ private partial def mkKey (e : Expr) : CanonM Key := do
|
||||
unless eNew == e do
|
||||
return (← mkKey eNew)
|
||||
let info ← getFunInfo f
|
||||
let args ← e.getAppArgs.mapIdxM fun i arg => do
|
||||
let mut k ← mkKey f
|
||||
for i in [:e.getAppNumArgs] do
|
||||
if h : i < info.paramInfo.size then
|
||||
let info := info.paramInfo[i]
|
||||
if info.isExplicit && !info.isProp then
|
||||
pure (← mkKey arg).e
|
||||
else
|
||||
pure (mkSort 0) -- some dummy value for erasing implicit
|
||||
k := mixHash k (← mkKey (e.getArg! i))
|
||||
else
|
||||
pure (← mkKey arg).e
|
||||
let f' := (← mkKey f).e
|
||||
pure { e := (← shareCommon (mkAppN f' args)) }
|
||||
| .lam n t b i =>
|
||||
pure { e := (← shareCommon (.lam n (← mkKey t).e (← mkKey b).e i)) }
|
||||
| .forallE n t b i =>
|
||||
pure { e := (← shareCommon (.forallE n (← mkKey t).e (← mkKey b).e i)) }
|
||||
| .letE n t v b d =>
|
||||
pure { e := (← shareCommon (.letE n (← mkKey t).e (← mkKey v).e (← mkKey b).e d)) }
|
||||
| .proj t i s =>
|
||||
pure { e := (← shareCommon (.proj t i (← mkKey s).e)) }
|
||||
unsafe modify fun { keys, cache, keyToExprs} => { keys, keyToExprs, cache := cache.insert { e } key |>.1 }
|
||||
k := mixHash k (← mkKey (e.getArg! i))
|
||||
return k
|
||||
| .lam _ t b _
|
||||
| .forallE _ t b _ =>
|
||||
return mixHash (← mkKey t) (← mkKey b)
|
||||
| .letE _ _ v b _ =>
|
||||
return mixHash (← mkKey v) (← mkKey b)
|
||||
| .proj _ i s =>
|
||||
return mixHash i.toUInt64 (← mkKey s)
|
||||
unsafe modify fun { cache, keyToExprs} => { keyToExprs, cache := cache.insert { e } key |>.1 }
|
||||
return key
|
||||
|
||||
/--
|
||||
@@ -135,11 +123,11 @@ def canon (e : Expr) : CanonM Expr := do
|
||||
if (← isDefEq e e') then
|
||||
return e'
|
||||
-- `e` is not definitionally equal to any expression in `es'`. We claim this should be rare.
|
||||
unsafe modify fun { keys, cache, keyToExprs } => { keys, cache, keyToExprs := keyToExprs.insert k (e :: es') |>.1 }
|
||||
unsafe modify fun { cache, keyToExprs } => { cache, keyToExprs := keyToExprs.insert k (e :: es') }
|
||||
return e
|
||||
else
|
||||
-- `e` is the first expression we found with key `k`.
|
||||
unsafe modify fun { keys, cache, keyToExprs } => { keys, cache, keyToExprs := keyToExprs.insert k [e] |>.1 }
|
||||
unsafe modify fun { cache, keyToExprs } => { cache, keyToExprs := keyToExprs.insert k [e] }
|
||||
return e
|
||||
|
||||
end Canonicalizer
|
||||
|
||||
@@ -6,6 +6,7 @@ Authors: Leonardo de Moura
|
||||
prelude
|
||||
import Lean.MetavarContext
|
||||
import Lean.Environment
|
||||
import Lean.AddDecl
|
||||
import Lean.Util.FoldConsts
|
||||
import Lean.Meta.Basic
|
||||
import Lean.Meta.Check
|
||||
|
||||
@@ -55,8 +55,8 @@ private def setBinderInfosD (ys : Array Expr) (lctx : LocalContext) : LocalConte
|
||||
|
||||
partial def mkHCongrWithArity (f : Expr) (numArgs : Nat) : MetaM CongrTheorem := do
|
||||
let fType ← inferType f
|
||||
forallBoundedTelescope fType numArgs fun xs _ =>
|
||||
forallBoundedTelescope fType numArgs fun ys _ => do
|
||||
forallBoundedTelescope fType numArgs (cleanupAnnotations := true) fun xs _ =>
|
||||
forallBoundedTelescope fType numArgs (cleanupAnnotations := true) fun ys _ => do
|
||||
if xs.size != numArgs then
|
||||
throwError "failed to generate hcongr theorem, insufficient number of arguments"
|
||||
else
|
||||
@@ -80,8 +80,8 @@ where
|
||||
if i < xs.size then
|
||||
let x := xs[i]!
|
||||
let y := ys[i]!
|
||||
let xType := (← inferType x).consumeTypeAnnotations
|
||||
let yType := (← inferType y).consumeTypeAnnotations
|
||||
let xType := (← inferType x).cleanupAnnotations
|
||||
let yType := (← inferType y).cleanupAnnotations
|
||||
if xType == yType then
|
||||
withLocalDeclD ((`e).appendIndexAfter (i+1)) (← mkEq x y) fun h =>
|
||||
loop (i+1) (eqs.push h) (kinds.push CongrArgKind.eq)
|
||||
@@ -98,9 +98,9 @@ where
|
||||
else if let some (_, lhs, _, _) := type.heq? then
|
||||
mkHEqRefl lhs
|
||||
else
|
||||
forallBoundedTelescope type (some 1) fun a type =>
|
||||
forallBoundedTelescope type (some 1) (cleanupAnnotations := true) fun a type =>
|
||||
let a := a[0]!
|
||||
forallBoundedTelescope type (some 1) fun b motive =>
|
||||
forallBoundedTelescope type (some 1) (cleanupAnnotations := true) fun b motive =>
|
||||
let b := b[0]!
|
||||
let type := type.bindingBody!.instantiate1 a
|
||||
withLocalDeclD motive.bindingName! motive.bindingDomain! fun eqPr => do
|
||||
@@ -159,7 +159,7 @@ private def hasCastLike (kinds : Array CongrArgKind) : Bool :=
|
||||
kinds.any fun kind => kind matches CongrArgKind.cast || kind matches CongrArgKind.subsingletonInst
|
||||
|
||||
private def withNext (type : Expr) (k : Expr → Expr → MetaM α) : MetaM α := do
|
||||
forallBoundedTelescope type (some 1) fun xs type => k xs[0]! type
|
||||
forallBoundedTelescope type (some 1) (cleanupAnnotations := true) fun xs type => k xs[0]! type
|
||||
|
||||
/--
|
||||
Test whether we should use `subsingletonInst` kind for instances which depend on `eq`.
|
||||
@@ -182,7 +182,7 @@ private def getClassSubobjectMask? (f : Expr) : MetaM (Option (Array Bool)) := d
|
||||
let .const declName _ := f | return none
|
||||
let .ctorInfo val ← getConstInfo declName | return none
|
||||
unless isClass (← getEnv) val.induct do return none
|
||||
forallTelescopeReducing val.type fun xs _ => do
|
||||
forallTelescopeReducing val.type (cleanupAnnotations := true) fun xs _ => do
|
||||
let env ← getEnv
|
||||
let mut mask := #[]
|
||||
for i in [:xs.size] do
|
||||
@@ -255,7 +255,7 @@ where
|
||||
mk? (f : Expr) (info : FunInfo) (kinds : Array CongrArgKind) : MetaM (Option CongrTheorem) := do
|
||||
try
|
||||
let fType ← inferType f
|
||||
forallBoundedTelescope fType kinds.size fun lhss _ => do
|
||||
forallBoundedTelescope fType kinds.size (cleanupAnnotations := true) fun lhss _ => do
|
||||
if lhss.size != kinds.size then return none
|
||||
let rec go (i : Nat) (rhss : Array Expr) (eqs : Array (Option Expr)) (hyps : Array Expr) : MetaM CongrTheorem := do
|
||||
if i == kinds.size then
|
||||
|
||||
@@ -5,6 +5,7 @@ Authors: Leonardo de Moura
|
||||
-/
|
||||
prelude
|
||||
import Lean.AuxRecursor
|
||||
import Lean.AddDecl
|
||||
import Lean.Meta.AppBuilder
|
||||
|
||||
namespace Lean
|
||||
|
||||
@@ -14,13 +14,11 @@ private def getConstructorVal? (env : Environment) (ctorName : Name) : Option Co
|
||||
| some (.ctorInfo v) => v
|
||||
| _ => none
|
||||
|
||||
|
||||
/--
|
||||
If `e` is a constructor application or a builtin literal defeq to a constructor application,
|
||||
If `e` is a constructor application,
|
||||
then return the corresponding `ConstructorVal`.
|
||||
-/
|
||||
def isConstructorApp? (e : Expr) : MetaM (Option ConstructorVal) := do
|
||||
let e ← litToCtor e
|
||||
def isConstructorAppCore? (e : Expr) : MetaM (Option ConstructorVal) := do
|
||||
let .const n _ := e.getAppFn | return none
|
||||
let some v := getConstructorVal? (← getEnv) n | return none
|
||||
if v.numParams + v.numFields == e.getAppNumArgs then
|
||||
@@ -28,6 +26,13 @@ def isConstructorApp? (e : Expr) : MetaM (Option ConstructorVal) := do
|
||||
else
|
||||
return none
|
||||
|
||||
/--
|
||||
If `e` is a constructor application or a builtin literal defeq to a constructor application,
|
||||
then return the corresponding `ConstructorVal`.
|
||||
-/
|
||||
def isConstructorApp? (e : Expr) : MetaM (Option ConstructorVal) := do
|
||||
isConstructorAppCore? (← litToCtor e)
|
||||
|
||||
/--
|
||||
Similar to `isConstructorApp?`, but uses `whnf`.
|
||||
-/
|
||||
|
||||
@@ -60,11 +60,20 @@ def mkDiagSummaryForUnfoldedReducible (counters : PHashMap Name Nat) : MetaM Dia
|
||||
def mkDiagSummaryForUsedInstances : MetaM DiagSummary := do
|
||||
mkDiagSummary (← get).diag.instanceCounter
|
||||
|
||||
def appendSection (m : MessageData) (cls : Name) (header : String) (s : DiagSummary) : MessageData :=
|
||||
def mkDiagSynthPendingFailure (failures : PHashMap Expr MessageData) : MetaM DiagSummary := do
|
||||
if failures.isEmpty then
|
||||
return {}
|
||||
else
|
||||
let mut data := #[]
|
||||
for (_, msg) in failures do
|
||||
data := data.push m!"{if data.isEmpty then " " else "\n"}{msg}"
|
||||
return { data }
|
||||
|
||||
def appendSection (m : MessageData) (cls : Name) (header : String) (s : DiagSummary) (resultSummary := true) : MessageData :=
|
||||
if s.isEmpty then
|
||||
m
|
||||
else
|
||||
let header := s!"{header} (max: {s.max}, num: {s.data.size}):"
|
||||
let header := if resultSummary then s!"{header} (max: {s.max}, num: {s.data.size}):" else header
|
||||
m ++ .trace { cls } header s.data
|
||||
|
||||
def reportDiag : MetaM Unit := do
|
||||
@@ -75,13 +84,17 @@ def reportDiag : MetaM Unit := do
|
||||
let unfoldReducible ← mkDiagSummaryForUnfoldedReducible unfoldCounter
|
||||
let heu ← mkDiagSummary (← get).diag.heuristicCounter
|
||||
let inst ← mkDiagSummaryForUsedInstances
|
||||
let synthPending ← mkDiagSynthPendingFailure (← get).diag.synthPendingFailures
|
||||
let unfoldKernel ← mkDiagSummary (Kernel.getDiagnostics (← getEnv)).unfoldCounter
|
||||
unless unfoldDefault.isEmpty && unfoldInstance.isEmpty && unfoldReducible.isEmpty && heu.isEmpty && inst.isEmpty do
|
||||
unless unfoldDefault.isEmpty && unfoldInstance.isEmpty && unfoldReducible.isEmpty && heu.isEmpty && inst.isEmpty && synthPending.isEmpty do
|
||||
let m := MessageData.nil
|
||||
let m := appendSection m `reduction "unfolded declarations" unfoldDefault
|
||||
let m := appendSection m `reduction "unfolded instances" unfoldInstance
|
||||
let m := appendSection m `reduction "unfolded reducible declarations" unfoldReducible
|
||||
let m := appendSection m `type_class "used instances" inst
|
||||
let m := appendSection m `type_class
|
||||
s!"max synth pending failures (maxSynthPendingDepth: {maxSynthPendingDepth.get (← getOptions)}), use `set_option maxSynthPendingDepth <limit>`"
|
||||
synthPending (resultSummary := false)
|
||||
let m := appendSection m `def_eq "heuristic for solving `f a =?= f b`" heu
|
||||
let m := appendSection m `kernel "unfolded declarations" unfoldKernel
|
||||
let m := m ++ "use `set_option diagnostics.threshold <num>` to control threshold for reporting counters"
|
||||
|
||||
@@ -80,6 +80,51 @@ def Key.format : Key → Format
|
||||
|
||||
instance : ToFormat Key := ⟨Key.format⟩
|
||||
|
||||
/--
|
||||
Helper function for converting an entry (i.e., `Array Key`) to the discrimination tree into
|
||||
`MessageData` that is more user-friendly. We use this function to implement diagnostic information.
|
||||
-/
|
||||
partial def keysAsPattern (keys : Array Key) : CoreM MessageData := do
|
||||
go (parenIfNonAtomic := false) |>.run' keys.toList
|
||||
where
|
||||
next? : StateRefT (List Key) CoreM (Option Key) := do
|
||||
let key :: keys ← get | return none
|
||||
set keys
|
||||
return some key
|
||||
|
||||
mkApp (f : MessageData) (args : Array MessageData) (parenIfNonAtomic : Bool) : CoreM MessageData := do
|
||||
if args.isEmpty then
|
||||
return f
|
||||
else
|
||||
let mut r := f
|
||||
for arg in args do
|
||||
r := r ++ m!" {arg}"
|
||||
if parenIfNonAtomic then
|
||||
return m!"({r})"
|
||||
else
|
||||
return r
|
||||
|
||||
go (parenIfNonAtomic := true) : StateRefT (List Key) CoreM MessageData := do
|
||||
let some key ← next? | return .nil
|
||||
match key with
|
||||
| .const declName nargs =>
|
||||
mkApp m!"{← mkConstWithLevelParams declName}" (← goN nargs) parenIfNonAtomic
|
||||
| .fvar fvarId nargs =>
|
||||
mkApp m!"{mkFVar fvarId}" (← goN nargs) parenIfNonAtomic
|
||||
| .proj _ i nargs =>
|
||||
mkApp m!"{← go}.{i+1}" (← goN nargs) parenIfNonAtomic
|
||||
| .arrow => return "<arrow>"
|
||||
| .star => return "_"
|
||||
| .other => return "<other>"
|
||||
| .lit (.natVal v) => return m!"{v}"
|
||||
| .lit (.strVal v) => return m!"{v}"
|
||||
|
||||
goN (num : Nat) : StateRefT (List Key) CoreM (Array MessageData) := do
|
||||
let mut r := #[]
|
||||
for _ in [: num] do
|
||||
r := r.push (← go)
|
||||
return r
|
||||
|
||||
def Key.arity : Key → Nat
|
||||
| .const _ a => a
|
||||
| .fvar _ a => a
|
||||
@@ -634,6 +679,55 @@ where
|
||||
else
|
||||
return result
|
||||
|
||||
/--
|
||||
Return the root symbol for `e`, and the number of arguments after `reduceDT`.
|
||||
-/
|
||||
def getMatchKeyRootFor (e : Expr) (config : WhnfCoreConfig) : MetaM (Key × Nat) := do
|
||||
let e ← reduceDT e (root := true) config
|
||||
let numArgs := e.getAppNumArgs
|
||||
let key := match e.getAppFn with
|
||||
| .lit v => .lit v
|
||||
| .fvar fvarId => .fvar fvarId numArgs
|
||||
| .mvar _ => .other
|
||||
| .proj s i _ .. => .proj s i numArgs
|
||||
| .forallE .. => .arrow
|
||||
| .const c _ =>
|
||||
-- This method is used by the simplifier only, we do **not** support
|
||||
-- (← getConfig).isDefEqStuckEx
|
||||
.const c numArgs
|
||||
| _ => .other
|
||||
return (key, numArgs)
|
||||
|
||||
/--
|
||||
Get all results under key `k`.
|
||||
-/
|
||||
private partial def getAllValuesForKey (d : DiscrTree α) (k : Key) (result : Array α) : Array α :=
|
||||
match d.root.find? k with
|
||||
| none => result
|
||||
| some trie => go trie result
|
||||
where
|
||||
go (trie : Trie α) (result : Array α) : Array α := Id.run do
|
||||
match trie with
|
||||
| .node vs cs =>
|
||||
let mut result := result ++ vs
|
||||
for (_, trie) in cs do
|
||||
result := go trie result
|
||||
return result
|
||||
|
||||
/--
|
||||
A liberal version of `getMatch` which only takes the root symbol of `e` into account.
|
||||
We use this method to simulate Lean 3's indexing.
|
||||
|
||||
The natural number in the result is the number of arguments in `e` after `reduceDT`.
|
||||
-/
|
||||
def getMatchLiberal (d : DiscrTree α) (e : Expr) (config : WhnfCoreConfig) : MetaM (Array α × Nat) := do
|
||||
withReducible do
|
||||
let result := getStarResult d
|
||||
let (k, numArgs) ← getMatchKeyRootFor e config
|
||||
match k with
|
||||
| .star => return (result, numArgs)
|
||||
| _ => return (getAllValuesForKey d k result, numArgs)
|
||||
|
||||
partial def getUnify (d : DiscrTree α) (e : Expr) (config : WhnfCoreConfig) : MetaM (Array α) :=
|
||||
withReducible do
|
||||
let (k, args) ← getUnifyKeyArgs e (root := true) config
|
||||
|
||||
@@ -5,6 +5,7 @@ Authors: Leonardo de Moura
|
||||
-/
|
||||
prelude
|
||||
import Lean.ReservedNameAction
|
||||
import Lean.AddDecl
|
||||
import Lean.Meta.Basic
|
||||
import Lean.Meta.AppBuilder
|
||||
import Lean.Meta.Match.MatcherInfo
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user