mirror of
https://github.com/leanprover/lean4.git
synced 2026-03-23 05:14:09 +00:00
Compare commits
9 Commits
upstream_a
...
create_std
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a63acb32b3 | ||
|
|
89c733625d | ||
|
|
0cc849691d | ||
|
|
5694dab653 | ||
|
|
f9bd389ed6 | ||
|
|
cb5a0e9da8 | ||
|
|
cf6d5224d7 | ||
|
|
0fe71e96f9 | ||
|
|
bdde13e01f |
12
.github/workflows/ci.yml
vendored
12
.github/workflows/ci.yml
vendored
@@ -124,11 +124,10 @@ jobs:
|
||||
"release": true,
|
||||
"quick": false,
|
||||
"cross": true,
|
||||
"cross_target": "aarch64-apple-darwin",
|
||||
"shell": "bash -euxo pipefail {0}",
|
||||
"CMAKE_OPTIONS": "-DUSE_GMP=OFF -DLEAN_INSTALL_SUFFIX=-darwin_aarch64",
|
||||
"llvm-url": "https://github.com/leanprover/lean-llvm/releases/download/15.0.1/lean-llvm-aarch64-apple-darwin.tar.zst https://github.com/leanprover/lean-llvm/releases/download/15.0.1/lean-llvm-x86_64-apple-darwin.tar.zst",
|
||||
"prepare-llvm": "../script/prepare-llvm-macos.sh lean-llvm-aarch64-* lean-llvm-x86_64-*",
|
||||
"prepare-llvm": "EXTRA_FLAGS=--target=aarch64-apple-darwin ../script/prepare-llvm-macos.sh lean-llvm-aarch64-* lean-llvm-x86_64-*",
|
||||
"binary-check": "otool -L",
|
||||
"tar": "gtar" // https://github.com/actions/runner-images/issues/2619
|
||||
},
|
||||
@@ -152,10 +151,9 @@ jobs:
|
||||
"release": true,
|
||||
"quick": false,
|
||||
"cross": true,
|
||||
"cross_target": "aarch64-unknown-linux-gnu",
|
||||
"shell": "nix-shell --arg pkgsDist \"import (fetchTarball \\\"channel:nixos-19.03\\\") {{ localSystem.config = \\\"aarch64-unknown-linux-gnu\\\"; }}\" --run \"bash -euxo pipefail {0}\"",
|
||||
"llvm-url": "https://github.com/leanprover/lean-llvm/releases/download/15.0.1/lean-llvm-x86_64-linux-gnu.tar.zst https://github.com/leanprover/lean-llvm/releases/download/15.0.1/lean-llvm-aarch64-linux-gnu.tar.zst",
|
||||
"prepare-llvm": "../script/prepare-llvm-linux.sh lean-llvm-aarch64-* lean-llvm-x86_64-*"
|
||||
"prepare-llvm": "EXTRA_FLAGS=--target=aarch64-unknown-linux-gnu ../script/prepare-llvm-linux.sh lean-llvm-aarch64-* lean-llvm-x86_64-*"
|
||||
},
|
||||
{
|
||||
"name": "Linux 32bit",
|
||||
@@ -323,15 +321,9 @@ jobs:
|
||||
mkdir build
|
||||
cd build
|
||||
ulimit -c unlimited # coredumps
|
||||
# arguments passed to `cmake`
|
||||
# this also enables githash embedding into stage 1 library
|
||||
OPTIONS=(-DCHECK_OLEAN_VERSION=ON)
|
||||
OPTIONS+=(-DLEAN_EXTRA_MAKE_OPTS=-DwarningAsError=true)
|
||||
if [[ -n '${{ matrix.cross_target }}' ]]; then
|
||||
# used by `prepare-llvm`
|
||||
export EXTRA_FLAGS=--target=${{ matrix.cross_target }}
|
||||
OPTIONS+=(-DLEAN_PLATFORM_TARGET=${{ matrix.cross_target }})
|
||||
fi
|
||||
if [[ -n '${{ matrix.prepare-llvm }}' ]]; then
|
||||
wget -q ${{ matrix.llvm-url }}
|
||||
PREPARE="$(${{ matrix.prepare-llvm }})"
|
||||
|
||||
16
.github/workflows/nix-ci.yml
vendored
16
.github/workflows/nix-ci.yml
vendored
@@ -90,14 +90,6 @@ jobs:
|
||||
# https://github.com/netlify/cli/issues/1809
|
||||
cp -r --dereference ./result ./dist
|
||||
if: matrix.name == 'Nix Linux'
|
||||
- name: Check manual for broken links
|
||||
id: lychee
|
||||
uses: lycheeverse/lychee-action@v1.9.0
|
||||
with:
|
||||
fail: false # report errors but do not block CI on temporary failures
|
||||
# gmplib.org consistently times out from GH actions
|
||||
# the GitHub token is to avoid rate limiting
|
||||
args: --base './dist' --no-progress --github-token ${{ secrets.GITHUB_TOKEN }} --exclude 'gmplib.org' './dist/**/*.html'
|
||||
- name: Push to Cachix
|
||||
run: |
|
||||
[ -z "${{ secrets.CACHIX_AUTH_TOKEN }}" ] || cachix push -j4 lean4 ./push-* || true
|
||||
@@ -105,6 +97,13 @@ jobs:
|
||||
run: |
|
||||
rm -rf nix-store-cache || true
|
||||
nix copy ./push-* --to file://$PWD/nix-store-cache?compression=none
|
||||
- name: Publish manual to GH Pages
|
||||
uses: peaceiris/actions-gh-pages@v3
|
||||
with:
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
publish_dir: ./result
|
||||
destination_dir: ./doc
|
||||
if: matrix.name == 'Nix Linux' && github.ref == 'refs/heads/master' && github.event_name == 'push'
|
||||
- id: deploy-info
|
||||
name: Compute Deployment Metadata
|
||||
run: |
|
||||
@@ -113,7 +112,6 @@ jobs:
|
||||
echo "message=`git log -1 --pretty=format:"%s"`" >> "$GITHUB_OUTPUT"
|
||||
- name: Publish manual to Netlify
|
||||
uses: nwtgck/actions-netlify@v2.0
|
||||
id: publish-manual
|
||||
with:
|
||||
publish-dir: ./dist
|
||||
production-branch: master
|
||||
|
||||
@@ -1,8 +1,13 @@
|
||||
This is the repository for **Lean 4**.
|
||||
|
||||
We provide [nightly releases](https://github.com/leanprover/lean4-nightly/releases)
|
||||
and have just begun regular [stable point releases](https://github.com/leanprover/lean4/releases).
|
||||
|
||||
# About
|
||||
|
||||
- [Quickstart](https://lean-lang.org/lean4/doc/quickstart.html)
|
||||
- [Quickstart](https://github.com/leanprover/lean4/blob/master/doc/quickstart.md)
|
||||
- [Walkthrough installation video](https://www.youtube.com/watch?v=yZo6k48L0VY)
|
||||
- [Quick tour video](https://youtu.be/zyXtbb_eYbY)
|
||||
- [Homepage](https://lean-lang.org)
|
||||
- [Theorem Proving Tutorial](https://lean-lang.org/theorem_proving_in_lean4/)
|
||||
- [Functional Programming in Lean](https://lean-lang.org/functional_programming_in_lean/)
|
||||
|
||||
69
RELEASES.md
69
RELEASES.md
@@ -8,10 +8,7 @@ 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.7.0 (development in progress)
|
||||
---------
|
||||
|
||||
v4.6.0
|
||||
v4.6.0 (development in progress)
|
||||
---------
|
||||
|
||||
* Add custom simplification procedures (aka `simproc`s) to `simp`. Simprocs can be triggered by the simplifier on a specified term-pattern. Here is an small example:
|
||||
@@ -25,25 +22,20 @@ def foo (x : Nat) : Nat :=
|
||||
The `simproc` `reduceFoo` is invoked on terms that match the pattern `foo _`.
|
||||
-/
|
||||
simproc reduceFoo (foo _) :=
|
||||
/- A term of type `Expr → SimpM Step -/
|
||||
fun e => do
|
||||
/- A term of type `Expr → SimpM (Option Step) -/
|
||||
fun e => OptionT.run do
|
||||
/- `simp` uses matching modulo reducibility. So, we ensure the term is a `foo`-application. -/
|
||||
guard (e.isAppOfArity ``foo 1)
|
||||
/- `Nat.fromExpr?` tries to convert an expression into a `Nat` value -/
|
||||
let n ← Nat.fromExpr? e.appArg!
|
||||
/-
|
||||
The `Step` type has three constructors: `.done`, `.visit`, `.continue`.
|
||||
The `Step` type has two constructors: `.done` and `.visit`.
|
||||
* The constructor `.done` instructs `simp` that the result does
|
||||
not need to be simplied further.
|
||||
* The constructor `.visit` instructs `simp` to visit the resulting expression.
|
||||
* The constructor `.continue` instructs `simp` to try other simplification procedures.
|
||||
|
||||
All three constructors take a `Result`. The `.continue` contructor may also take `none`.
|
||||
`Result` has two fields `expr` (the new expression), and `proof?` (an optional proof).
|
||||
If the new expression is definitionally equal to the input one, then `proof?` can be omitted or set to `none`.
|
||||
If the result holds definitionally as in this example, the field `proof?` can be omitted.
|
||||
-/
|
||||
/- `simp` uses matching modulo reducibility. So, we ensure the term is a `foo`-application. -/
|
||||
unless e.isAppOfArity ``foo 1 do
|
||||
return .continue
|
||||
/- `Nat.fromExpr?` tries to convert an expression into a `Nat` value -/
|
||||
let some n ← Nat.fromExpr? e.appArg!
|
||||
| return .continue
|
||||
return .done { expr := Lean.mkNatLit (n+10) }
|
||||
```
|
||||
We disable simprocs support by using the command `set_option simprocs false`. This command is particularly useful when porting files to v4.6.0.
|
||||
@@ -51,7 +43,7 @@ Simprocs can be scoped, manually added to `simp` commands, and suppressed using
|
||||
```lean
|
||||
example : x + foo 2 = 12 + x := by
|
||||
set_option simprocs false in
|
||||
/- This `simp` command does not make progress since `simproc`s are disabled. -/
|
||||
/- This `simp` command does make progress since `simproc`s are disabled. -/
|
||||
fail_if_success simp
|
||||
simp_arith
|
||||
|
||||
@@ -72,10 +64,6 @@ example : x + foo 2 = 12 + x := by
|
||||
fail_if_success simp [-reduceFoo]
|
||||
simp_arith
|
||||
```
|
||||
The command `register_simp_attr <id>` now creates a `simp` **and** a `simproc` set with the name `<id>`. The following command instructs Lean to insert the `reduceFoo` simplification procedure into the set `my_simp`. If no set is specified, Lean uses the default `simp` set.
|
||||
```lean
|
||||
simproc [my_simp] reduceFoo (foo _) := ...
|
||||
```
|
||||
|
||||
* The syntax of the `termination_by` and `decreasing_by` termination hints is overhauled:
|
||||
|
||||
@@ -204,43 +192,6 @@ simproc [my_simp] reduceFoo (foo _) := ...
|
||||
ought to be applied to multiple functions, the `decreasing_by` clause has to
|
||||
be repeated at each of these functions.
|
||||
|
||||
* Modify `InfoTree.context` to facilitate augmenting it with partial contexts while elaborating a command. This breaks backwards compatibility with all downstream projects that traverse the `InfoTree` manually instead of going through the functions in `InfoUtils.lean`, as well as those manually creating and saving `InfoTree`s. See [PR #3159](https://github.com/leanprover/lean4/pull/3159) for how to migrate your code.
|
||||
|
||||
* Add language server support for [call hierarchy requests](https://www.youtube.com/watch?v=r5LA7ivUb2c) ([PR #3082](https://github.com/leanprover/lean4/pull/3082)). The change to the .ilean format in this PR means that projects must be fully rebuilt once in order to generate .ilean files with the new format before features like "find references" work correctly again.
|
||||
|
||||
* Structure instances with multiple sources (for example `{a, b, c with x := 0}`) now have their fields filled from these sources
|
||||
in strict left-to-right order. Furthermore, the structure instance elaborator now aggressively use sources to fill in subobject
|
||||
fields, which prevents unnecessary eta expansion of the sources,
|
||||
and hence greatly reduces the reliance on costly structure eta reduction. This has a large impact on mathlib,
|
||||
reducing total CPU instructions by 3% and enabling impactful refactors like leanprover-community/mathlib4#8386
|
||||
which reduces the build time by almost 20%.
|
||||
See PR [#2478](https://github.com/leanprover/lean4/pull/2478) and RFC [#2451](https://github.com/leanprover/lean4/issues/2451).
|
||||
|
||||
* Add pretty printer settings to omit deeply nested terms (`pp.deepTerms false` and `pp.deepTerms.threshold`) ([PR #3201](https://github.com/leanprover/lean4/pull/3201))
|
||||
|
||||
* Add pretty printer options `pp.numeralTypes` and `pp.natLit`.
|
||||
When `pp.numeralTypes` is true, then natural number literals, integer literals, and rational number literals
|
||||
are pretty printed with type ascriptions, such as `(2 : Rat)`, `(-2 : Rat)`, and `(-2 / 3 : Rat)`.
|
||||
When `pp.natLit` is true, then raw natural number literals are pretty printed as `nat_lit 2`.
|
||||
[PR #2933](https://github.com/leanprover/lean4/pull/2933) and [RFC #3021](https://github.com/leanprover/lean4/issues/3021).
|
||||
|
||||
Lake updates:
|
||||
* improved platform information & control [#3226](https://github.com/leanprover/lean4/pull/3226)
|
||||
* `lake update` from unsupported manifest versions [#3149](https://github.com/leanprover/lean4/pull/3149)
|
||||
|
||||
Other improvements:
|
||||
* make `intro` be aware of `let_fun` [#3115](https://github.com/leanprover/lean4/pull/3115)
|
||||
* produce simpler proof terms in `rw` [#3121](https://github.com/leanprover/lean4/pull/3121)
|
||||
* fuse nested `mkCongrArg` calls in proofs generated by `simp` [#3203](https://github.com/leanprover/lean4/pull/3203)
|
||||
* `induction using` followed by a general term [#3188](https://github.com/leanprover/lean4/pull/3188)
|
||||
* allow generalization in `let` [#3060](https://github.com/leanprover/lean4/pull/3060, fixing [#3065](https://github.com/leanprover/lean4/issues/3065)
|
||||
* reducing out-of-bounds `swap!` should return `a`, not `default`` [#3197](https://github.com/leanprover/lean4/pull/3197), fixing [#3196](https://github.com/leanprover/lean4/issues/3196)
|
||||
* derive `BEq` on structure with `Prop`-fields [#3191](https://github.com/leanprover/lean4/pull/3191), fixing [#3140](https://github.com/leanprover/lean4/issues/3140)
|
||||
* refine through more `casesOnApp`/`matcherApp` [#3176](https://github.com/leanprover/lean4/pull/3176), fixing [#3175](https://github.com/leanprover/lean4/pull/3175)
|
||||
* do not strip dotted components from lean module names [#2994](https://github.com/leanprover/lean4/pull/2994), fixing [#2999](https://github.com/leanprover/lean4/issues/2999)
|
||||
* fix `deriving` only deriving the first declaration for some handlers [#3058](https://github.com/leanprover/lean4/pull/3058), fixing [#3057](https://github.com/leanprover/lean4/issues/3057)
|
||||
* do not instantiate metavariables in kabstract/rw for disallowed occurrences [#2539](https://github.com/leanprover/lean4/pull/2539), fixing [#2538](https://github.com/leanprover/lean4/issues/2538)
|
||||
* hover info for `cases h : ...` [#3084](https://github.com/leanprover/lean4/pull/3084)
|
||||
|
||||
v4.5.0
|
||||
---------
|
||||
|
||||
@@ -121,4 +121,4 @@ Thus to e.g. run `#eval` on such a declaration, you need to
|
||||
Note that it is not sufficient to load the foreign library containing the external symbol because the interpreter depends on code that is emitted for each `@[extern]` declaration.
|
||||
Thus it is not possible to interpret an `@[extern]` declaration in the same file.
|
||||
|
||||
See [`tests/compiler/foreign`](https://github.com/leanprover/lean4/tree/master/tests/compiler/foreign/) for an example.
|
||||
See `tests/compiler/foreign` for an example.
|
||||
|
||||
@@ -41,17 +41,17 @@ information is displayed. This option will show all test output.
|
||||
|
||||
All these tests are included by [src/shell/CMakeLists.txt](https://github.com/leanprover/lean4/blob/master/src/shell/CMakeLists.txt):
|
||||
|
||||
- [`tests/lean`](https://github.com/leanprover/lean4/tree/master/tests/lean/): contains tests that come equipped with a
|
||||
.lean.expected.out file. The driver script [`test_single.sh`](https://github.com/leanprover/lean4/tree/master/tests/lean/test_single.sh) runs
|
||||
- `tests/lean`: contains tests that come equipped with a
|
||||
.lean.expected.out file. The driver script `test_single.sh` runs
|
||||
each test and checks the actual output (*.produced.out) with the
|
||||
checked in expected output.
|
||||
|
||||
- [`tests/lean/run`](https://github.com/leanprover/lean4/tree/master/tests/lean/run/): contains tests that are run through the lean
|
||||
- `tests/lean/run`: contains tests that are run through the lean
|
||||
command line one file at a time. These tests only look for error
|
||||
codes and do not check the expected output even though output is
|
||||
produced, it is ignored.
|
||||
|
||||
- [`tests/lean/interactive`](https://github.com/leanprover/lean4/tree/master/tests/lean/interactive/): are designed to test server requests at a
|
||||
- `tests/lean/interactive`: are designed to test server requests at a
|
||||
given position in the input file. Each .lean file contains comments
|
||||
that indicate how to simulate a client request at that position.
|
||||
using a `--^` point to the line position. Example:
|
||||
@@ -61,7 +61,7 @@ All these tests are included by [src/shell/CMakeLists.txt](https://github.com/le
|
||||
Bla.
|
||||
--^ textDocument/completion
|
||||
```
|
||||
In this example, the test driver [`test_single.sh`](https://github.com/leanprover/lean4/tree/master/tests/lean/interactive/test_single.sh) will simulate an
|
||||
In this example, the test driver `test_single.sh` will simulate an
|
||||
auto-completion request at `Bla.`. The expected output is stored in
|
||||
a .lean.expected.out in the json format that is part of the
|
||||
[Language Server
|
||||
@@ -78,21 +78,21 @@ All these tests are included by [src/shell/CMakeLists.txt](https://github.com/le
|
||||
--^ collectDiagnostics
|
||||
```
|
||||
|
||||
- [`tests/lean/server`](https://github.com/leanprover/lean4/tree/master/tests/lean/server/): Tests more of the Lean `--server` protocol.
|
||||
- `tests/lean/server`: Tests more of the Lean `--server` protocol.
|
||||
There are just a few of them, and it uses .log files containing
|
||||
JSON.
|
||||
|
||||
- [`tests/compiler`](https://github.com/leanprover/lean4/tree/master/tests/compiler/): contains tests that will run the Lean compiler and
|
||||
- `tests/compiler`: contains tests that will run the Lean compiler and
|
||||
build an executable that is executed and the output is compared to
|
||||
the .lean.expected.out file. This test also contains a subfolder
|
||||
[`foreign`](https://github.com/leanprover/lean4/tree/master/tests/compiler/foreign/) which shows how to extend Lean using C++.
|
||||
`foreign` which shows how to extend Lean using C++.
|
||||
|
||||
- [`tests/lean/trust0`](https://github.com/leanprover/lean4/tree/master/tests/lean/trust0): tests that run Lean in a mode that Lean doesn't
|
||||
- `tests/lean/trust0`: tests that run Lean in a mode that Lean doesn't
|
||||
even trust the .olean files (i.e., trust 0).
|
||||
|
||||
- [`tests/bench`](https://github.com/leanprover/lean4/tree/master/tests/bench/): contains performance tests.
|
||||
- `tests/bench`: contains performance tests.
|
||||
|
||||
- [`tests/plugin`](https://github.com/leanprover/lean4/tree/master/tests/plugin/): tests that compiled Lean code can be loaded into
|
||||
- `tests/plugin`: tests that compiled Lean code can be loaded into
|
||||
`lean` via the `--plugin` command line option.
|
||||
|
||||
## Writing Good Tests
|
||||
@@ -103,7 +103,7 @@ Every test file should contain:
|
||||
and, if not 100% clear, why that is the desirable behavior
|
||||
|
||||
At the time of writing, most tests do not follow these new guidelines yet.
|
||||
For an example of a conforming test, see [`tests/lean/1971.lean`](https://github.com/leanprover/lean4/tree/master/tests/lean/1971.lean).
|
||||
For an example of a conforming test, see `tests/lean/1971.lean`.
|
||||
|
||||
## Fixing Tests
|
||||
|
||||
@@ -119,7 +119,7 @@ First, we must install [meld](http://meldmerge.org/). On Ubuntu, we can do it by
|
||||
sudo apt-get install meld
|
||||
```
|
||||
|
||||
Now, suppose `bad_class.lean` test is broken. We can see the problem by going to [`tests/lean`](https://github.com/leanprover/lean4/tree/master/tests/lean) directory and
|
||||
Now, suppose `bad_class.lean` test is broken. We can see the problem by going to `tests/lean` directory and
|
||||
executing
|
||||
|
||||
```
|
||||
|
||||
9
doc/flake.lock
generated
9
doc/flake.lock
generated
@@ -69,16 +69,15 @@
|
||||
"leanInk": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1704976501,
|
||||
"narHash": "sha256-FSBUsbX0HxakSnYRYzRBDN2YKmH9EkA0q9p7TSPEJTI=",
|
||||
"owner": "leanprover",
|
||||
"lastModified": 1666154782,
|
||||
"narHash": "sha256-0ELqEca6jZT4BW/mqkDD+uYuxW5QlZUFlNwZkvugsg8=",
|
||||
"owner": "digama0",
|
||||
"repo": "LeanInk",
|
||||
"rev": "51821e3c2c032c88e4b2956483899d373ec090c4",
|
||||
"rev": "12a2aec9b5f4aa84e84fb01a9af1da00d8aaff4e",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "leanprover",
|
||||
"ref": "refs/pull/57/merge",
|
||||
"repo": "LeanInk",
|
||||
"type": "github"
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
flake = false;
|
||||
};
|
||||
inputs.leanInk = {
|
||||
url = "github:leanprover/LeanInk/refs/pull/57/merge";
|
||||
url = "github:leanprover/LeanInk";
|
||||
flake = false;
|
||||
};
|
||||
|
||||
|
||||
@@ -32,7 +32,9 @@ def fact x :=
|
||||
|
||||
#eval fact 100
|
||||
```
|
||||
By default, Lean only accepts total functions.
|
||||
By default, Lean only accepts total functions (see [The Equation
|
||||
Compiler](declarations.md#_the_equation_compiler) for how Lean determines
|
||||
whether functions are total).
|
||||
The `partial` keyword may be used to define a recursive function without a termination proof; `partial` functions compute in compiled programs, but are opaque in proofs and during type checking.
|
||||
```lean
|
||||
partial def g (x : Nat) (p : Nat -> Bool) : Nat :=
|
||||
|
||||
@@ -10,6 +10,7 @@ Platform-Specific Setup
|
||||
|
||||
- [Linux (Ubuntu)](ubuntu.md)
|
||||
- [Windows (msys2)](msys2.md)
|
||||
- [Windows (Visual Studio)](msvc.md)
|
||||
- [Windows (WSL)](wsl.md)
|
||||
- [macOS (homebrew)](osx-10.9.md)
|
||||
- Linux/macOS/WSL via [Nix](https://nixos.org/nix/): Call `nix-shell` in the project root. That's it.
|
||||
|
||||
@@ -60,7 +60,7 @@ While parsing `a * (b + c)`, `(b + c)` is assigned a precedence `60` by the addi
|
||||
the right argument to have precedence **at least** 71. Thus, this parse is invalid. In contrast, `(a * b) + c` assigns
|
||||
a precedence of `70` to `(a * b)`. This is compatible with addition which expects the left argument to have precedence
|
||||
**at least `60` ** (`70` is greater than `60`). Thus, the string `a * b + c` is parsed as `(a * b) + c`.
|
||||
For more details, please look at the [Lean manual on syntax extensions](./notation.md#notations-and-precedence).
|
||||
For more details, please look at the [Lean manual on syntax extensions](../syntax.md#notations-and-precedence).
|
||||
|
||||
To go from strings into `Arith`, we define a macro to
|
||||
translate the syntax category `arith` into an `Arith` inductive value that
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
### Tier 1
|
||||
|
||||
Platforms built & tested by our CI, available as binary releases via elan (see below)
|
||||
Platforms built & tested by our CI, available as nightly releases via elan (see below)
|
||||
|
||||
* x86-64 Linux with glibc 2.27+
|
||||
* x86-64 macOS 10.15+
|
||||
@@ -10,7 +10,7 @@ Platforms built & tested by our CI, available as binary releases via elan (see b
|
||||
|
||||
### Tier 2
|
||||
|
||||
Platforms cross-compiled but not tested by our CI, available as binary releases
|
||||
Platforms cross-compiled but not tested by our CI, available as nightly releases
|
||||
|
||||
Releases may be silently broken due to the lack of automated testing.
|
||||
Issue reports and fixes are welcome.
|
||||
|
||||
@@ -15,7 +15,7 @@ The most fundamental pieces of any Lean program are functions organized into nam
|
||||
[Functions](./functions.md) perform work on inputs to produce outputs,
|
||||
and they are organized under [namespaces](./namespaces.md),
|
||||
which are the primary way you group things in Lean.
|
||||
They are defined using the `def` command,
|
||||
They are defined using the [`def`](./definitions.md) command,
|
||||
which give the function a name and define its arguments.
|
||||
|
||||
```lean
|
||||
|
||||
@@ -37,6 +37,6 @@ Lean has numerous features, including:
|
||||
- [Extensible syntax](./syntax.md)
|
||||
- Hygienic macros
|
||||
- [Dependent types](https://lean-lang.org/theorem_proving_in_lean4/dependent_type_theory.html)
|
||||
- [Metaprogramming](./macro_overview.md)
|
||||
- [Metaprogramming](./metaprogramming.md)
|
||||
- Multithreading
|
||||
- Verification: you can prove properties of your functions using Lean itself
|
||||
|
||||
@@ -9,7 +9,7 @@ endif()
|
||||
include(ExternalProject)
|
||||
project(LEAN CXX C)
|
||||
set(LEAN_VERSION_MAJOR 4)
|
||||
set(LEAN_VERSION_MINOR 7)
|
||||
set(LEAN_VERSION_MINOR 6)
|
||||
set(LEAN_VERSION_PATCH 0)
|
||||
set(LEAN_VERSION_IS_RELEASE 0) # This number is 1 in the release revision, and 0 otherwise.
|
||||
set(LEAN_SPECIAL_VERSION_DESC "" CACHE STRING "Additional version description like 'nightly-2018-03-11'")
|
||||
@@ -18,14 +18,6 @@ if (LEAN_SPECIAL_VERSION_DESC)
|
||||
string(APPEND LEAN_VERSION_STRING "-${LEAN_SPECIAL_VERSION_DESC}")
|
||||
endif()
|
||||
|
||||
set(LEAN_PLATFORM_TARGET "" CACHE STRING "LLVM triple of the target platform")
|
||||
if (NOT LEAN_PLATFORM_TARGET)
|
||||
# this may fail when the compiler is not clang, but this should only happen in local builds where
|
||||
# the value of the variable is not of immediate relevance
|
||||
execute_process(COMMAND ${CMAKE_C_COMPILER} --print-target-triple
|
||||
OUTPUT_VARIABLE LEAN_PLATFORM_TARGET OUTPUT_STRIP_TRAILING_WHITESPACE)
|
||||
endif()
|
||||
|
||||
set(LEAN_EXTRA_LINKER_FLAGS "" CACHE STRING "Additional flags used by the linker")
|
||||
set(LEAN_EXTRA_CXX_FLAGS "" CACHE STRING "Additional flags used by the C++ compiler")
|
||||
set(LEAN_TEST_VARS "LEAN_CC=${CMAKE_C_COMPILER}" CACHE STRING "Additional environment variables used when running tests")
|
||||
|
||||
@@ -411,10 +411,9 @@ set_option linter.unusedVariables.funArgs false in
|
||||
be available and then calls `f` on the result.
|
||||
|
||||
`prio`, if provided, is the priority of the task.
|
||||
If `sync` is set to true, `f` is executed on the current thread if `x` has already finished.
|
||||
-/
|
||||
@[noinline, extern "lean_task_map"]
|
||||
protected def map (f : α → β) (x : Task α) (prio := Priority.default) (sync := false) : Task β :=
|
||||
protected def map {α : Type u} {β : Type v} (f : α → β) (x : Task α) (prio := Priority.default) : Task β :=
|
||||
⟨f x.get⟩
|
||||
|
||||
set_option linter.unusedVariables.funArgs false in
|
||||
@@ -425,11 +424,9 @@ for the value of `x` to be available and then calls `f` on the result,
|
||||
resulting in a new task which is then run for a result.
|
||||
|
||||
`prio`, if provided, is the priority of the task.
|
||||
If `sync` is set to true, `f` is executed on the current thread if `x` has already finished.
|
||||
-/
|
||||
@[noinline, extern "lean_task_bind"]
|
||||
protected def bind (x : Task α) (f : α → Task β) (prio := Priority.default) (sync := false) :
|
||||
Task β :=
|
||||
protected def bind {α : Type u} {β : Type v} (x : Task α) (f : α → Task β) (prio := Priority.default) : Task β :=
|
||||
⟨(f x.get).get⟩
|
||||
|
||||
end Task
|
||||
@@ -1683,92 +1680,40 @@ So, you are mainly losing the capability of type checking your development using
|
||||
-/
|
||||
axiom ofReduceNat (a b : Nat) (h : reduceNat a = b) : a = b
|
||||
|
||||
end Lean
|
||||
|
||||
namespace Std
|
||||
variable {α : Sort u}
|
||||
|
||||
/--
|
||||
`Associative op` indicates `op` is an associative operation,
|
||||
i.e. `(a ∘ b) ∘ c = a ∘ (b ∘ c)`.
|
||||
`IsAssociative op` says that `op` is an associative operation,
|
||||
i.e. `(a ∘ b) ∘ c = a ∘ (b ∘ c)`. It is used by the `ac_rfl` tactic.
|
||||
-/
|
||||
class Associative (op : α → α → α) : Prop where
|
||||
class IsAssociative {α : Sort u} (op : α → α → α) where
|
||||
/-- An associative operation satisfies `(a ∘ b) ∘ c = a ∘ (b ∘ c)`. -/
|
||||
assoc : (a b c : α) → op (op a b) c = op a (op b c)
|
||||
|
||||
/--
|
||||
`Commutative op` says that `op` is a commutative operation,
|
||||
i.e. `a ∘ b = b ∘ a`.
|
||||
`IsCommutative op` says that `op` is a commutative operation,
|
||||
i.e. `a ∘ b = b ∘ a`. It is used by the `ac_rfl` tactic.
|
||||
-/
|
||||
class Commutative (op : α → α → α) : Prop where
|
||||
class IsCommutative {α : Sort u} (op : α → α → α) where
|
||||
/-- A commutative operation satisfies `a ∘ b = b ∘ a`. -/
|
||||
comm : (a b : α) → op a b = op b a
|
||||
|
||||
/--
|
||||
`IdempotentOp op` indicates `op` is an idempotent binary operation.
|
||||
i.e. `a ∘ a = a`.
|
||||
`IsIdempotent op` says that `op` is an idempotent operation,
|
||||
i.e. `a ∘ a = a`. It is used by the `ac_rfl` tactic
|
||||
(which also simplifies up to idempotence when available).
|
||||
-/
|
||||
class IdempotentOp (op : α → α → α) : Prop where
|
||||
class IsIdempotent {α : Sort u} (op : α → α → α) where
|
||||
/-- An idempotent operation satisfies `a ∘ a = a`. -/
|
||||
idempotent : (x : α) → op x x = x
|
||||
|
||||
/--
|
||||
`LeftIdentify op o` indicates `o` is a left identity of `op`.
|
||||
|
||||
This class does not require a proof that `o` is an identity, and
|
||||
is used primarily for infering the identity using class resoluton.
|
||||
`IsNeutral op e` says that `e` is a neutral operation for `op`,
|
||||
i.e. `a ∘ e = a = e ∘ a`. It is used by the `ac_rfl` tactic
|
||||
(which also simplifies neutral elements when available).
|
||||
-/
|
||||
class LeftIdentity (op : α → β → β) (o : outParam α) : Prop
|
||||
class IsNeutral {α : Sort u} (op : α → α → α) (neutral : α) where
|
||||
/-- A neutral element can be cancelled on the left: `e ∘ a = a`. -/
|
||||
left_neutral : (a : α) → op neutral a = a
|
||||
/-- A neutral element can be cancelled on the right: `a ∘ e = a`. -/
|
||||
right_neutral : (a : α) → op a neutral = a
|
||||
|
||||
/--
|
||||
`LawfulLeftIdentify op o` indicates `o` is a verified left identity of
|
||||
`op`.
|
||||
-/
|
||||
class LawfulLeftIdentity (op : α → β → β) (o : outParam α) extends LeftIdentity op o : Prop where
|
||||
/-- Left identity `o` is an identity. -/
|
||||
left_id : ∀ a, op o a = a
|
||||
|
||||
/--
|
||||
`RightIdentify op o` indicates `o` is a right identity `o` of `op`.
|
||||
|
||||
This class does not require a proof that `o` is an identity, and is used
|
||||
primarily for infering the identity using class resoluton.
|
||||
-/
|
||||
class RightIdentity (op : α → β → α) (o : outParam β) : Prop
|
||||
|
||||
/--
|
||||
`LawfulRightIdentify op o` indicates `o` is a verified right identity of
|
||||
`op`.
|
||||
-/
|
||||
class LawfulRightIdentity (op : α → β → α) (o : outParam β) extends RightIdentity op o : Prop where
|
||||
/-- Right identity `o` is an identity. -/
|
||||
right_id : ∀ a, op a o = a
|
||||
|
||||
/--
|
||||
`Identity op o` indicates `o` is a left and right identity of `op`.
|
||||
|
||||
This class does not require a proof that `o` is an identity, and is used
|
||||
primarily for infering the identity using class resoluton.
|
||||
-/
|
||||
class Identity (op : α → α → α) (o : outParam α) extends LeftIdentity op o, RightIdentity op o : Prop
|
||||
|
||||
/--
|
||||
`LawfulIdentity op o` indicates `o` is a verified left and right
|
||||
identity of `op`.
|
||||
-/
|
||||
class LawfulIdentity (op : α → α → α) (o : outParam α) extends Identity op o, LawfulLeftIdentity op o, LawfulRightIdentity op o : Prop
|
||||
|
||||
/--
|
||||
`LawfulCommIdentity` can simplify defining instances of `LawfulIdentity`
|
||||
on commutative functions by requiring only a left or right identity
|
||||
proof.
|
||||
|
||||
This class is intended for simplifying defining instances of
|
||||
`LawfulIdentity` and functions needed commutative operations with
|
||||
identity should just add a `LawfulIdentity` constraint.
|
||||
-/
|
||||
class LawfulCommIdentity (op : α → α → α) (o : outParam α) [hc : Commutative op] extends LawfulIdentity op o : Prop where
|
||||
left_id a := Eq.trans (hc.comm o a) (right_id a)
|
||||
right_id a := Eq.trans (hc.comm a o) (left_id a)
|
||||
|
||||
end Std
|
||||
end Lean
|
||||
|
||||
@@ -14,17 +14,15 @@ inductive Expr
|
||||
| op (lhs rhs : Expr)
|
||||
deriving Inhabited, Repr, BEq
|
||||
|
||||
open Std
|
||||
|
||||
structure Variable {α : Sort u} (op : α → α → α) : Type u where
|
||||
value : α
|
||||
neutral : Option $ PLift (LawfulIdentity op value)
|
||||
neutral : Option $ IsNeutral op value
|
||||
|
||||
structure Context (α : Sort u) where
|
||||
op : α → α → α
|
||||
assoc : Associative op
|
||||
comm : Option $ PLift $ Commutative op
|
||||
idem : Option $ PLift $ IdempotentOp op
|
||||
assoc : IsAssociative op
|
||||
comm : Option $ IsCommutative op
|
||||
idem : Option $ IsIdempotent op
|
||||
vars : List (Variable op)
|
||||
arbitrary : α
|
||||
|
||||
@@ -130,14 +128,7 @@ theorem Context.mergeIdem_head2 (h : x ≠ y) : mergeIdem (x :: y :: ys) = x ::
|
||||
simp [mergeIdem, mergeIdem.loop, h]
|
||||
|
||||
theorem Context.evalList_mergeIdem (ctx : Context α) (h : ContextInformation.isIdem ctx) (e : List Nat) : evalList α ctx (mergeIdem e) = evalList α ctx e := by
|
||||
have h : IdempotentOp ctx.op := by
|
||||
simp [ContextInformation.isIdem, Option.isSome] at h;
|
||||
match h₂ : ctx.idem with
|
||||
| none =>
|
||||
simp [h₂] at h
|
||||
| some val =>
|
||||
simp [h₂] at h
|
||||
exact val.down
|
||||
have h : IsIdempotent ctx.op := by simp [ContextInformation.isIdem, Option.isSome] at h; cases h₂ : ctx.idem <;> simp [h₂] at h; assumption
|
||||
induction e using List.two_step_induction with
|
||||
| empty => rfl
|
||||
| single => rfl
|
||||
@@ -178,7 +169,7 @@ theorem Context.sort_loop_nonEmpty (xs : List Nat) (h : xs ≠ []) : sort.loop x
|
||||
|
||||
theorem Context.evalList_insert
|
||||
(ctx : Context α)
|
||||
(h : Commutative ctx.op)
|
||||
(h : IsCommutative ctx.op)
|
||||
(x : Nat)
|
||||
(xs : List Nat)
|
||||
: evalList α ctx (insert x xs) = evalList α ctx (x::xs) := by
|
||||
@@ -199,7 +190,7 @@ theorem Context.evalList_insert
|
||||
|
||||
theorem Context.evalList_sort_congr
|
||||
(ctx : Context α)
|
||||
(h : Commutative ctx.op)
|
||||
(h : IsCommutative ctx.op)
|
||||
(h₂ : evalList α ctx a = evalList α ctx b)
|
||||
(h₃ : a ≠ [])
|
||||
(h₄ : b ≠ [])
|
||||
@@ -218,7 +209,7 @@ theorem Context.evalList_sort_congr
|
||||
|
||||
theorem Context.evalList_sort_loop_swap
|
||||
(ctx : Context α)
|
||||
(h : Commutative ctx.op)
|
||||
(h : IsCommutative ctx.op)
|
||||
(xs ys : List Nat)
|
||||
: evalList α ctx (sort.loop xs (y::ys)) = evalList α ctx (sort.loop (y::xs) ys) := by
|
||||
induction ys generalizing y xs with
|
||||
@@ -233,7 +224,7 @@ theorem Context.evalList_sort_loop_swap
|
||||
|
||||
theorem Context.evalList_sort_cons
|
||||
(ctx : Context α)
|
||||
(h : Commutative ctx.op)
|
||||
(h : IsCommutative ctx.op)
|
||||
(x : Nat)
|
||||
(xs : List Nat)
|
||||
: evalList α ctx (sort (x :: xs)) = evalList α ctx (x :: sort xs) := by
|
||||
@@ -256,14 +247,7 @@ theorem Context.evalList_sort_cons
|
||||
all_goals simp [insert_nonEmpty]
|
||||
|
||||
theorem Context.evalList_sort (ctx : Context α) (h : ContextInformation.isComm ctx) (e : List Nat) : evalList α ctx (sort e) = evalList α ctx e := by
|
||||
have h : Commutative ctx.op := by
|
||||
simp [ContextInformation.isComm, Option.isSome] at h
|
||||
match h₂ : ctx.comm with
|
||||
| none =>
|
||||
simp only [h₂] at h
|
||||
| some val =>
|
||||
simp [h₂] at h
|
||||
exact val.down
|
||||
have h : IsCommutative ctx.op := by simp [ContextInformation.isComm, Option.isSome] at h; cases h₂ : ctx.comm <;> simp [h₂] at h; assumption
|
||||
induction e using List.two_step_induction with
|
||||
| empty => rfl
|
||||
| single => rfl
|
||||
@@ -285,12 +269,10 @@ theorem Context.toList_nonEmpty (e : Expr) : e.toList ≠ [] := by
|
||||
theorem Context.unwrap_isNeutral
|
||||
{ctx : Context α}
|
||||
{x : Nat}
|
||||
: ContextInformation.isNeutral ctx x = true → LawfulIdentity (EvalInformation.evalOp ctx) (EvalInformation.evalVar (β := α) ctx x) := by
|
||||
: ContextInformation.isNeutral ctx x = true → IsNeutral (EvalInformation.evalOp ctx) (EvalInformation.evalVar (β := α) ctx x) := by
|
||||
simp [ContextInformation.isNeutral, Option.isSome, EvalInformation.evalOp, EvalInformation.evalVar]
|
||||
match (var ctx x).neutral with
|
||||
| some hn =>
|
||||
intro
|
||||
exact hn.down
|
||||
| some hn => intro; assumption
|
||||
| none => intro; contradiction
|
||||
|
||||
theorem Context.evalList_removeNeutrals (ctx : Context α) (e : List Nat) : evalList α ctx (removeNeutrals ctx e) = evalList α ctx e := by
|
||||
@@ -301,12 +283,10 @@ theorem Context.evalList_removeNeutrals (ctx : Context α) (e : List Nat) : eval
|
||||
case h_1 => rfl
|
||||
case h_2 h => split at h <;> simp_all
|
||||
| step x y ys ih =>
|
||||
cases h₁ : ContextInformation.isNeutral ctx x <;>
|
||||
cases h₂ : ContextInformation.isNeutral ctx y <;>
|
||||
cases h₃ : removeNeutrals.loop ctx ys
|
||||
cases h₁ : ContextInformation.isNeutral ctx x <;> cases h₂ : ContextInformation.isNeutral ctx y <;> cases h₃ : removeNeutrals.loop ctx ys
|
||||
<;> simp [removeNeutrals, removeNeutrals.loop, h₁, h₂, h₃, evalList, ←ih]
|
||||
<;> (try simp [unwrap_isNeutral h₂ |>.right_id])
|
||||
<;> (try simp [unwrap_isNeutral h₁ |>.left_id])
|
||||
<;> (try simp [unwrap_isNeutral h₂ |>.2])
|
||||
<;> (try simp [unwrap_isNeutral h₁ |>.1])
|
||||
|
||||
theorem Context.evalList_append
|
||||
(ctx : Context α)
|
||||
|
||||
@@ -21,21 +21,6 @@ def mkArray {α : Type u} (n : Nat) (v : α) : Array α := {
|
||||
data := List.replicate n v
|
||||
}
|
||||
|
||||
/--
|
||||
`ofFn f` with `f : Fin n → α` returns the list whose ith element is `f i`.
|
||||
```
|
||||
ofFn f = #[f 0, f 1, ... , f(n - 1)]
|
||||
``` -/
|
||||
def ofFn {n} (f : Fin n → α) : Array α := go 0 (mkEmpty n) where
|
||||
/-- Auxiliary for `ofFn`. `ofFn.go f i acc = acc ++ #[f i, ..., f(n - 1)]` -/
|
||||
go (i : Nat) (acc : Array α) : Array α :=
|
||||
if h : i < n then go (i+1) (acc.push (f ⟨i, h⟩)) else acc
|
||||
termination_by n - i
|
||||
|
||||
/-- The array `#[0, 1, ..., n - 1]`. -/
|
||||
def range (n : Nat) : Array Nat :=
|
||||
n.fold (flip Array.push) (mkEmpty n)
|
||||
|
||||
@[simp] theorem size_mkArray (n : Nat) (v : α) : (mkArray n v).size = n :=
|
||||
List.length_replicate ..
|
||||
|
||||
@@ -428,10 +413,6 @@ def map {α : Type u} {β : Type v} (f : α → β) (as : Array α) : Array β :
|
||||
def mapIdx {α : Type u} {β : Type v} (as : Array α) (f : Fin as.size → α → β) : Array β :=
|
||||
Id.run <| as.mapIdxM f
|
||||
|
||||
/-- Turns `#[a, b]` into `#[(a, 0), (b, 1)]`. -/
|
||||
def zipWithIndex (arr : Array α) : Array (α × Nat) :=
|
||||
arr.mapIdx fun i a => (a, i)
|
||||
|
||||
@[inline]
|
||||
def find? {α : Type} (as : Array α) (p : α → Bool) : Option α :=
|
||||
Id.run <| as.findM? p
|
||||
@@ -506,11 +487,6 @@ def elem [BEq α] (a : α) (as : Array α) : Bool :=
|
||||
def toList (as : Array α) : List α :=
|
||||
as.foldr List.cons []
|
||||
|
||||
/-- Prepends an `Array α` onto the front of a list. Equivalent to `as.toList ++ l`. -/
|
||||
@[inline]
|
||||
def toListAppend (as : Array α) (l : List α) : List α :=
|
||||
as.foldr List.cons l
|
||||
|
||||
instance {α : Type u} [Repr α] : Repr (Array α) where
|
||||
reprPrec a _ :=
|
||||
let _ : Std.ToFormat α := ⟨repr⟩
|
||||
@@ -540,13 +516,6 @@ def concatMapM [Monad m] (f : α → m (Array β)) (as : Array α) : m (Array β
|
||||
def concatMap (f : α → Array β) (as : Array α) : Array β :=
|
||||
as.foldl (init := empty) fun bs a => bs ++ f a
|
||||
|
||||
/-- Joins array of array into a single array.
|
||||
|
||||
`flatten #[#[a₁, a₂, ⋯], #[b₁, b₂, ⋯], ⋯]` = `#[a₁, a₂, ⋯, b₁, b₂, ⋯]`
|
||||
-/
|
||||
def flatten (as : Array (Array α)) : Array α :=
|
||||
as.foldl (init := empty) fun r a => r ++ a
|
||||
|
||||
end Array
|
||||
|
||||
export Array (mkArray)
|
||||
|
||||
@@ -300,18 +300,11 @@ instance : MonadPrettyFormat (StateM State) where
|
||||
startTag _ := return ()
|
||||
endTags _ := return ()
|
||||
|
||||
/--
|
||||
Renders a `Format` to a string.
|
||||
* `width`: the total width
|
||||
* `indent`: the initial indentation to use for wrapped lines
|
||||
(subsequent wrapping may increase the indentation)
|
||||
* `column`: begin the first line wrap `column` characters earlier than usual
|
||||
(this is useful when the output String will be printed starting at `column`)
|
||||
-/
|
||||
/-- Pretty-print a `Format` object as a string with expected width `w`. -/
|
||||
@[export lean_format_pretty]
|
||||
def pretty (f : Format) (width : Nat := defWidth) (indent : Nat := 0) (column := 0) : String :=
|
||||
let act : StateM State Unit := prettyM f width indent
|
||||
State.out <| act (State.mk "" column) |>.snd
|
||||
def pretty (f : Format) (w : Nat := defWidth) : String :=
|
||||
let act: StateM State Unit := prettyM f w
|
||||
act {} |>.snd.out
|
||||
|
||||
end Format
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ Authors: Leonardo de Moura
|
||||
-/
|
||||
prelude
|
||||
import Init.Data.Fin.Basic
|
||||
import Init.System.Platform
|
||||
|
||||
open Nat
|
||||
|
||||
|
||||
@@ -563,17 +563,8 @@ def SepArray.ofElemsUsingRef [Monad m] [MonadRef m] {sep} (elems : Array Syntax)
|
||||
instance : Coe (Array Syntax) (SepArray sep) where
|
||||
coe := SepArray.ofElems
|
||||
|
||||
/--
|
||||
Constructs a typed separated array from elements.
|
||||
The given array does not include the separators.
|
||||
|
||||
Like `Syntax.SepArray.ofElems` but for typed syntax.
|
||||
-/
|
||||
def TSepArray.ofElems {sep} (elems : Array (TSyntax k)) : TSepArray k sep :=
|
||||
.mk (SepArray.ofElems (sep := sep) (TSyntaxArray.raw elems)).1
|
||||
|
||||
instance : Coe (TSyntaxArray k) (TSepArray k sep) where
|
||||
coe := TSepArray.ofElems
|
||||
coe a := ⟨mkSepArray a.raw (mkAtom sep)⟩
|
||||
|
||||
/-- Create syntax representing a Lean term application, but avoid degenerate empty applications. -/
|
||||
def mkApp (fn : Term) : (args : TSyntaxArray `term) → Term
|
||||
|
||||
@@ -170,20 +170,6 @@ See [Theorem Proving in Lean 4][tpil4] for more information.
|
||||
-/
|
||||
syntax (name := calcTactic) "calc" calcSteps : tactic
|
||||
|
||||
/--
|
||||
Denotes a term that was omitted by the pretty printer.
|
||||
This is only used for pretty printing, and it cannot be elaborated.
|
||||
The presence of `⋯` is controlled by the `pp.deepTerms` and `pp.deepTerms.threshold`
|
||||
options.
|
||||
-/
|
||||
syntax "⋯" : term
|
||||
|
||||
macro_rules | `(⋯) => Macro.throwError "\
|
||||
Error: The '⋯' token is used by the pretty printer to indicate omitted terms, \
|
||||
and it cannot be elaborated. \
|
||||
Its presence in pretty printing output is controlled by the 'pp.deepTerms' and \
|
||||
`pp.deepTerms.threshold` options."
|
||||
|
||||
@[app_unexpander Unit.unit] def unexpandUnit : Lean.PrettyPrinter.Unexpander
|
||||
| `($(_)) => `(())
|
||||
|
||||
@@ -191,13 +177,9 @@ macro_rules | `(⋯) => Macro.throwError "\
|
||||
| `($(_)) => `([])
|
||||
|
||||
@[app_unexpander List.cons] def unexpandListCons : Lean.PrettyPrinter.Unexpander
|
||||
| `($(_) $x $tail) =>
|
||||
match tail with
|
||||
| `([]) => `([$x])
|
||||
| `([$xs,*]) => `([$x, $xs,*])
|
||||
| `(⋯) => `([$x, $tail]) -- Unexpands to `[x, y, z, ⋯]` for `⋯ : List α`
|
||||
| _ => throw ()
|
||||
| _ => throw ()
|
||||
| `($(_) $x []) => `([$x])
|
||||
| `($(_) $x [$xs,*]) => `([$x, $xs,*])
|
||||
| _ => throw ()
|
||||
|
||||
@[app_unexpander List.toArray] def unexpandListToArray : Lean.PrettyPrinter.Unexpander
|
||||
| `($(_) [$xs,*]) => `(#[$xs,*])
|
||||
|
||||
@@ -9,9 +9,9 @@ set_option linter.missingDocs true -- keep it documented
|
||||
/-!
|
||||
# Init.Prelude
|
||||
|
||||
This is the first file in the Lean import hierarchy. It is responsible for setting
|
||||
up basic definitions, most of which Lean already has "built in knowledge" about,
|
||||
so it is important that they be set up in exactly this way. (For example, Lean will
|
||||
This is the first file in the lean import hierarchy. It is responsible for setting
|
||||
up basic definitions, most of which lean already has "built in knowledge" about,
|
||||
so it is important that they be set up in exactly this way. (For example, lean will
|
||||
use `PUnit` in the desugaring of `do` notation, or in the pattern match compiler.)
|
||||
|
||||
-/
|
||||
@@ -24,7 +24,7 @@ The identity function. `id` takes an implicit argument `α : Sort u`
|
||||
|
||||
Although this may look like a useless function, one application of the identity
|
||||
function is to explicitly put a type on an expression. If `e` has type `T`,
|
||||
and `T'` is definitionally equal to `T`, then `@id T' e` typechecks, and Lean
|
||||
and `T'` is definitionally equal to `T`, then `@id T' e` typechecks, and lean
|
||||
knows that this expression has type `T'` rather than `T`. This can make a
|
||||
difference for typeclass inference, since `T` and `T'` may have different
|
||||
typeclass instances on them. `show T' from e` is sugar for an `@id T' e`
|
||||
@@ -287,9 +287,9 @@ inductive Eq : α → α → Prop where
|
||||
same as `Eq.refl` except that it takes `a` implicitly instead of explicitly.
|
||||
|
||||
This is a more powerful theorem than it may appear at first, because although
|
||||
the statement of the theorem is `a = a`, Lean will allow anything that is
|
||||
the statement of the theorem is `a = a`, lean will allow anything that is
|
||||
definitionally equal to that type. So, for instance, `2 + 2 = 4` is proven in
|
||||
Lean by `rfl`, because both sides are the same up to definitional equality.
|
||||
lean by `rfl`, because both sides are the same up to definitional equality.
|
||||
-/
|
||||
@[match_pattern] def rfl {α : Sort u} {a : α} : Eq a a := Eq.refl a
|
||||
|
||||
@@ -597,7 +597,7 @@ For example, the `Membership` class is defined as:
|
||||
class Membership (α : outParam (Type u)) (γ : Type v)
|
||||
```
|
||||
This means that whenever a typeclass goal of the form `Membership ?α ?γ` comes
|
||||
up, Lean will wait to solve it until `?γ` is known, but then it will run
|
||||
up, lean will wait to solve it until `?γ` is known, but then it will run
|
||||
typeclass inference, and take the first solution it finds, for any value of `?α`,
|
||||
which thereby determines what `?α` should be.
|
||||
|
||||
@@ -712,13 +712,13 @@ nonempty, then `fun i => Classical.choice (h i) : ∀ i, α i` is a family of
|
||||
chosen elements. This is actually a bit stronger than the ZFC choice axiom;
|
||||
this is sometimes called "[global choice](https://en.wikipedia.org/wiki/Axiom_of_global_choice)".
|
||||
|
||||
In Lean, we use the axiom of choice to derive the law of excluded middle
|
||||
In lean, we use the axiom of choice to derive the law of excluded middle
|
||||
(see `Classical.em`), so it will often show up in axiom listings where you
|
||||
may not expect. You can use `#print axioms my_thm` to find out if a given
|
||||
theorem depends on this or other axioms.
|
||||
|
||||
This axiom can be used to construct "data", but obviously there is no algorithm
|
||||
to compute it, so Lean will require you to mark any definition that would
|
||||
to compute it, so lean will require you to mark any definition that would
|
||||
involve executing `Classical.choice` or other axioms as `noncomputable`, and
|
||||
will not produce any executable code for such definitions.
|
||||
-/
|
||||
@@ -943,7 +943,7 @@ determines how to evaluate `c` to true or false. Write `if h : c then t else e`
|
||||
instead for a "dependent if-then-else" `dite`, which allows `t`/`e` to use the fact
|
||||
that `c` is true/false.
|
||||
|
||||
Because Lean uses a strict (call-by-value) evaluation strategy, the signature of this
|
||||
Because lean uses a strict (call-by-value) evaluation strategy, the signature of this
|
||||
function is problematic in that it would require `t` and `e` to be evaluated before
|
||||
calling the `ite` function, which would cause both sides of the `if` to be evaluated.
|
||||
Even if the result is discarded, this would be a big performance problem,
|
||||
@@ -1033,7 +1033,7 @@ You can prove a theorem `P n` about `n : Nat` by `induction n`, which will
|
||||
expect a proof of the theorem for `P 0`, and a proof of `P (succ i)` assuming
|
||||
a proof of `P i`. The same method also works to define functions by recursion
|
||||
on natural numbers: induction and recursion are two expressions of the same
|
||||
operation from Lean's point of view.
|
||||
operation from lean's point of view.
|
||||
|
||||
```
|
||||
open Nat
|
||||
@@ -1069,14 +1069,14 @@ instance : Inhabited Nat where
|
||||
|
||||
/--
|
||||
The class `OfNat α n` powers the numeric literal parser. If you write
|
||||
`37 : α`, Lean will attempt to synthesize `OfNat α 37`, and will generate
|
||||
`37 : α`, lean will attempt to synthesize `OfNat α 37`, and will generate
|
||||
the term `(OfNat.ofNat 37 : α)`.
|
||||
|
||||
There is a bit of infinite regress here since the desugaring apparently
|
||||
still contains a literal `37` in it. The type of expressions contains a
|
||||
primitive constructor for "raw natural number literals", which you can directly
|
||||
access using the macro `nat_lit 37`. Raw number literals are always of type `Nat`.
|
||||
So it would be more correct to say that Lean looks for an instance of
|
||||
So it would be more correct to say that lean looks for an instance of
|
||||
`OfNat α (nat_lit 37)`, and it generates the term `(OfNat.ofNat (nat_lit 37) : α)`.
|
||||
-/
|
||||
class OfNat (α : Type u) (_ : Nat) where
|
||||
@@ -1780,7 +1780,7 @@ Gets the word size of the platform. That is, whether the platform is 64 or 32 bi
|
||||
|
||||
This function is opaque because we cannot guarantee at compile time that the target
|
||||
will have the same size as the host, and also because we would like to avoid
|
||||
typechecking being architecture-dependent. Nevertheless, Lean only works on
|
||||
typechecking being architecture-dependent. Nevertheless, lean only works on
|
||||
64 and 32 bit systems so we can encode this as a fact available for proof purposes.
|
||||
-/
|
||||
@[extern "lean_system_platform_nbits"] opaque System.Platform.getNumBits : Unit → Subtype fun (n : Nat) => Or (Eq n 32) (Eq n 64) :=
|
||||
@@ -2518,7 +2518,7 @@ attribute [nospecialize] Inhabited
|
||||
|
||||
/--
|
||||
The class `GetElem cont idx elem dom` implements the `xs[i]` notation.
|
||||
When you write this, given `xs : cont` and `i : idx`, Lean looks for an instance
|
||||
When you write this, given `xs : cont` and `i : idx`, lean looks for an instance
|
||||
of `GetElem cont idx elem dom`. Here `elem` is the type of `xs[i]`, while
|
||||
`dom` is whatever proof side conditions are required to make this applicable.
|
||||
For example, the instance for arrays looks like
|
||||
@@ -2558,7 +2558,7 @@ export GetElem (getElem)
|
||||
with elements from `α`. This type has special support in the runtime.
|
||||
|
||||
An array has a size and a capacity; the size is `Array.size` but the capacity
|
||||
is not observable from Lean code. Arrays perform best when unshared; as long
|
||||
is not observable from lean code. Arrays perform best when unshared; as long
|
||||
as they are used "linearly" all updates will be performed destructively on the
|
||||
array, so it has comparable performance to mutable arrays in imperative
|
||||
programming languages.
|
||||
|
||||
@@ -29,7 +29,7 @@ simproc ↓ reduce_add (_ + _) := fun e => ...
|
||||
```
|
||||
Simplification procedures can be also scoped or local.
|
||||
-/
|
||||
syntax (docComment)? attrKind "simproc " (Tactic.simpPre <|> Tactic.simpPost)? ("[" ident,* "]")? ident " (" term ")" " := " term : command
|
||||
syntax (docComment)? attrKind "simproc " (Tactic.simpPre <|> Tactic.simpPost)? ident " (" term ")" " := " term : command
|
||||
|
||||
/--
|
||||
A user-defined simplification procedure declaration. To activate this procedure in `simp` tactic,
|
||||
@@ -40,7 +40,7 @@ syntax (docComment)? "simproc_decl " ident " (" term ")" " := " term : command
|
||||
/--
|
||||
A builtin simplification procedure.
|
||||
-/
|
||||
syntax (docComment)? attrKind "builtin_simproc " (Tactic.simpPre <|> Tactic.simpPost)? ("[" ident,* "]")? ident " (" term ")" " := " term : command
|
||||
syntax (docComment)? attrKind "builtin_simproc " (Tactic.simpPre <|> Tactic.simpPost)? ident " (" term ")" " := " term : command
|
||||
|
||||
/--
|
||||
A builtin simplification procedure declaration.
|
||||
@@ -63,21 +63,10 @@ Auxiliary attribute for simplification procedures.
|
||||
-/
|
||||
syntax (name := simprocAttr) "simproc" (Tactic.simpPre <|> Tactic.simpPost)? : attr
|
||||
|
||||
/--
|
||||
Auxiliary attribute for symbolic evaluation procedures.
|
||||
-/
|
||||
syntax (name := sevalprocAttr) "sevalproc" (Tactic.simpPre <|> Tactic.simpPost)? : attr
|
||||
|
||||
/--
|
||||
Auxiliary attribute for builtin simplification procedures.
|
||||
-/
|
||||
syntax (name := simprocBuiltinAttr) "builtin_simproc" (Tactic.simpPre <|> Tactic.simpPost)? : attr
|
||||
|
||||
/--
|
||||
Auxiliary attribute for builtin symbolic evaluation procedures.
|
||||
-/
|
||||
syntax (name := sevalprocBuiltinAttr) "builtin_sevalproc" (Tactic.simpPre <|> Tactic.simpPost)? : attr
|
||||
|
||||
end Attr
|
||||
|
||||
macro_rules
|
||||
@@ -93,37 +82,13 @@ macro_rules
|
||||
builtin_simproc_pattern% $pattern => $n)
|
||||
|
||||
macro_rules
|
||||
| `($[$doc?:docComment]? $kind:attrKind simproc $[$pre?]? $[ [ $ids?:ident,* ] ]? $n:ident ($pattern:term) := $body) => do
|
||||
let mut cmds := #[(← `($[$doc?:docComment]? simproc_decl $n ($pattern) := $body))]
|
||||
let pushDefault (cmds : Array (TSyntax `command)) : MacroM (Array (TSyntax `command)) := do
|
||||
return cmds.push (← `(attribute [$kind simproc $[$pre?]?] $n))
|
||||
if let some ids := ids? then
|
||||
for id in ids.getElems do
|
||||
let idName := id.getId
|
||||
let (attrName, attrKey) :=
|
||||
if idName == `simp then
|
||||
(`simprocAttr, "simproc")
|
||||
else if idName == `seval then
|
||||
(`sevalprocAttr, "sevalproc")
|
||||
else
|
||||
let idName := idName.appendAfter "_proc"
|
||||
(`Parser.Attr ++ idName, idName.toString)
|
||||
let attrStx : TSyntax `attr := ⟨mkNode attrName #[mkAtom attrKey, mkOptionalNode pre?]⟩
|
||||
cmds := cmds.push (← `(attribute [$kind $attrStx] $n))
|
||||
else
|
||||
cmds ← pushDefault cmds
|
||||
return mkNullNode cmds
|
||||
| `($[$doc?:docComment]? $kind:attrKind simproc $[$pre?]? $n:ident ($pattern:term) := $body) => do
|
||||
`(simproc_decl $n ($pattern) := $body
|
||||
attribute [$kind simproc $[$pre?]?] $n)
|
||||
|
||||
macro_rules
|
||||
| `($[$doc?:docComment]? $kind:attrKind builtin_simproc $[$pre?]? $n:ident ($pattern:term) := $body) => do
|
||||
`($[$doc?:docComment]? builtin_simproc_decl $n ($pattern) := $body
|
||||
`(builtin_simproc_decl $n ($pattern) := $body
|
||||
attribute [$kind builtin_simproc $[$pre?]?] $n)
|
||||
| `($[$doc?:docComment]? $kind:attrKind builtin_simproc $[$pre?]? [seval] $n:ident ($pattern:term) := $body) => do
|
||||
`($[$doc?:docComment]? builtin_simproc_decl $n ($pattern) := $body
|
||||
attribute [$kind builtin_sevalproc $[$pre?]?] $n)
|
||||
| `($[$doc?:docComment]? $kind:attrKind builtin_simproc $[$pre?]? [simp, seval] $n:ident ($pattern:term) := $body) => do
|
||||
`($[$doc?:docComment]? builtin_simproc_decl $n ($pattern) := $body
|
||||
attribute [$kind builtin_simproc $[$pre?]?] $n
|
||||
attribute [$kind builtin_sevalproc $[$pre?]?] $n)
|
||||
|
||||
end Lean.Parser
|
||||
|
||||
@@ -117,23 +117,20 @@ opaque asTask (act : BaseIO α) (prio := Task.Priority.default) : BaseIO (Task
|
||||
|
||||
/-- See `BaseIO.asTask`. -/
|
||||
@[extern "lean_io_map_task"]
|
||||
opaque mapTask (f : α → BaseIO β) (t : Task α) (prio := Task.Priority.default) (sync := false) :
|
||||
BaseIO (Task β) :=
|
||||
opaque mapTask (f : α → BaseIO β) (t : Task α) (prio := Task.Priority.default) : BaseIO (Task β) :=
|
||||
Task.pure <$> f t.get
|
||||
|
||||
/-- See `BaseIO.asTask`. -/
|
||||
@[extern "lean_io_bind_task"]
|
||||
opaque bindTask (t : Task α) (f : α → BaseIO (Task β)) (prio := Task.Priority.default)
|
||||
(sync := false) : BaseIO (Task β) :=
|
||||
opaque bindTask (t : Task α) (f : α → BaseIO (Task β)) (prio := Task.Priority.default) : BaseIO (Task β) :=
|
||||
f t.get
|
||||
|
||||
def mapTasks (f : List α → BaseIO β) (tasks : List (Task α)) (prio := Task.Priority.default)
|
||||
(sync := false) : BaseIO (Task β) :=
|
||||
def mapTasks (f : List α → BaseIO β) (tasks : List (Task α)) (prio := Task.Priority.default) : BaseIO (Task β) :=
|
||||
go tasks []
|
||||
where
|
||||
go
|
||||
| t::ts, as =>
|
||||
BaseIO.bindTask t (fun a => go ts (a :: as)) prio sync
|
||||
BaseIO.bindTask t (fun a => go ts (a :: as)) prio
|
||||
| [], as => f as.reverse |>.asTask prio
|
||||
|
||||
end BaseIO
|
||||
@@ -145,20 +142,16 @@ namespace EIO
|
||||
act.toBaseIO.asTask prio
|
||||
|
||||
/-- `EIO` specialization of `BaseIO.mapTask`. -/
|
||||
@[inline] def mapTask (f : α → EIO ε β) (t : Task α) (prio := Task.Priority.default)
|
||||
(sync := false) : BaseIO (Task (Except ε β)) :=
|
||||
BaseIO.mapTask (fun a => f a |>.toBaseIO) t prio sync
|
||||
@[inline] def mapTask (f : α → EIO ε β) (t : Task α) (prio := Task.Priority.default) : BaseIO (Task (Except ε β)) :=
|
||||
BaseIO.mapTask (fun a => f a |>.toBaseIO) t prio
|
||||
|
||||
/-- `EIO` specialization of `BaseIO.bindTask`. -/
|
||||
@[inline] def bindTask (t : Task α) (f : α → EIO ε (Task (Except ε β)))
|
||||
(prio := Task.Priority.default) (sync := false) : BaseIO (Task (Except ε β)) :=
|
||||
BaseIO.bindTask t (fun a => f a |>.catchExceptions fun e => return Task.pure <| Except.error e)
|
||||
prio sync
|
||||
@[inline] def bindTask (t : Task α) (f : α → EIO ε (Task (Except ε β))) (prio := Task.Priority.default) : BaseIO (Task (Except ε β)) :=
|
||||
BaseIO.bindTask t (fun a => f a |>.catchExceptions fun e => return Task.pure <| Except.error e) prio
|
||||
|
||||
/-- `EIO` specialization of `BaseIO.mapTasks`. -/
|
||||
@[inline] def mapTasks (f : List α → EIO ε β) (tasks : List (Task α))
|
||||
(prio := Task.Priority.default) (sync := false) : BaseIO (Task (Except ε β)) :=
|
||||
BaseIO.mapTasks (fun as => f as |>.toBaseIO) tasks prio sync
|
||||
@[inline] def mapTasks (f : List α → EIO ε β) (tasks : List (Task α)) (prio := Task.Priority.default) : BaseIO (Task (Except ε β)) :=
|
||||
BaseIO.mapTasks (fun as => f as |>.toBaseIO) tasks prio
|
||||
|
||||
end EIO
|
||||
|
||||
@@ -191,19 +184,16 @@ def sleep (ms : UInt32) : BaseIO Unit :=
|
||||
EIO.asTask act prio
|
||||
|
||||
/-- `IO` specialization of `EIO.mapTask`. -/
|
||||
@[inline] def mapTask (f : α → IO β) (t : Task α) (prio := Task.Priority.default) (sync := false) :
|
||||
BaseIO (Task (Except IO.Error β)) :=
|
||||
EIO.mapTask f t prio sync
|
||||
@[inline] def mapTask (f : α → IO β) (t : Task α) (prio := Task.Priority.default) : BaseIO (Task (Except IO.Error β)) :=
|
||||
EIO.mapTask f t prio
|
||||
|
||||
/-- `IO` specialization of `EIO.bindTask`. -/
|
||||
@[inline] def bindTask (t : Task α) (f : α → IO (Task (Except IO.Error β)))
|
||||
(prio := Task.Priority.default) (sync := false) : BaseIO (Task (Except IO.Error β)) :=
|
||||
EIO.bindTask t f prio sync
|
||||
@[inline] def bindTask (t : Task α) (f : α → IO (Task (Except IO.Error β))) (prio := Task.Priority.default) : BaseIO (Task (Except IO.Error β)) :=
|
||||
EIO.bindTask t f prio
|
||||
|
||||
/-- `IO` specialization of `EIO.mapTasks`. -/
|
||||
@[inline] def mapTasks (f : List α → IO β) (tasks : List (Task α)) (prio := Task.Priority.default)
|
||||
(sync := false) : BaseIO (Task (Except IO.Error β)) :=
|
||||
EIO.mapTasks f tasks prio sync
|
||||
@[inline] def mapTasks (f : List α → IO β) (tasks : List (Task α)) (prio := Task.Priority.default) : BaseIO (Task (Except IO.Error β)) :=
|
||||
EIO.mapTasks f tasks prio
|
||||
|
||||
/-- Check if the task's cancellation flag has been set by calling `IO.cancel` or dropping the last reference to the task. -/
|
||||
@[extern "lean_io_check_canceled"] opaque checkCanceled : BaseIO Bool
|
||||
|
||||
@@ -5,7 +5,6 @@ Authors: Leonardo de Moura
|
||||
-/
|
||||
prelude
|
||||
import Init.Data.Nat.Basic
|
||||
import Init.Data.String.Basic
|
||||
|
||||
namespace System
|
||||
namespace Platform
|
||||
@@ -18,10 +17,5 @@ def isWindows : Bool := getIsWindows ()
|
||||
def isOSX : Bool := getIsOSX ()
|
||||
def isEmscripten : Bool := getIsEmscripten ()
|
||||
|
||||
@[extern "lean_system_platform_target"] opaque getTarget : Unit → String
|
||||
|
||||
/-- The LLVM target triple of the current platform. Empty if missing at Lean compile time. -/
|
||||
def target : String := getTarget ()
|
||||
|
||||
end Platform
|
||||
end System
|
||||
|
||||
@@ -39,75 +39,8 @@ be a `let` or function type.
|
||||
syntax (name := intro) "intro" notFollowedBy("|") (ppSpace colGt term:max)* : tactic
|
||||
|
||||
/--
|
||||
Introduces zero or more hypotheses, optionally naming them.
|
||||
|
||||
- `intros` is equivalent to repeatedly applying `intro`
|
||||
until the goal is not an obvious candidate for `intro`, which is to say
|
||||
that so long as the goal is a `let` or a pi type (e.g. an implication, function, or universal quantifier),
|
||||
the `intros` tactic will introduce an anonymous hypothesis.
|
||||
This tactic does not unfold definitions.
|
||||
|
||||
- `intros x y ...` is equivalent to `intro x y ...`,
|
||||
introducing hypotheses for each supplied argument and unfolding definitions as necessary.
|
||||
Each argument can be either an identifier or a `_`.
|
||||
An identifier indicates a name to use for the corresponding introduced hypothesis,
|
||||
and a `_` indicates that the hypotheses should be introduced anonymously.
|
||||
|
||||
## Examples
|
||||
|
||||
Basic properties:
|
||||
```lean
|
||||
def AllEven (f : Nat → Nat) := ∀ n, f n % 2 = 0
|
||||
|
||||
-- Introduces the two obvious hypotheses automatically
|
||||
example : ∀ (f : Nat → Nat), AllEven f → AllEven (fun k => f (k + 1)) := by
|
||||
intros
|
||||
/- Tactic state
|
||||
f✝ : Nat → Nat
|
||||
a✝ : AllEven f✝
|
||||
⊢ AllEven fun k => f✝ (k + 1) -/
|
||||
sorry
|
||||
|
||||
-- Introduces exactly two hypotheses, naming only the first
|
||||
example : ∀ (f : Nat → Nat), AllEven f → AllEven (fun k => f (k + 1)) := by
|
||||
intros g _
|
||||
/- Tactic state
|
||||
g : Nat → Nat
|
||||
a✝ : AllEven g
|
||||
⊢ AllEven fun k => g (k + 1) -/
|
||||
sorry
|
||||
|
||||
-- Introduces exactly three hypotheses, which requires unfolding `AllEven`
|
||||
example : ∀ (f : Nat → Nat), AllEven f → AllEven (fun k => f (k + 1)) := by
|
||||
intros f h n
|
||||
/- Tactic state
|
||||
f : Nat → Nat
|
||||
h : AllEven f
|
||||
n : Nat
|
||||
⊢ (fun k => f (k + 1)) n % 2 = 0 -/
|
||||
apply h
|
||||
```
|
||||
|
||||
Implications:
|
||||
```lean
|
||||
example (p q : Prop) : p → q → p := by
|
||||
intros
|
||||
/- Tactic state
|
||||
a✝¹ : p
|
||||
a✝ : q
|
||||
⊢ p -/
|
||||
assumption
|
||||
```
|
||||
|
||||
Let bindings:
|
||||
```lean
|
||||
example : let n := 1; let k := 2; n + k = 3 := by
|
||||
intros
|
||||
/- n✝ : Nat := 1
|
||||
k✝ : Nat := 2
|
||||
⊢ n✝ + k✝ = 3 -/
|
||||
rfl
|
||||
```
|
||||
`intros x...` behaves like `intro x...`, but then keeps introducing (anonymous)
|
||||
hypotheses until goal is not of a function type.
|
||||
-/
|
||||
syntax (name := intros) "intros" (ppSpace colGt (ident <|> hole))* : tactic
|
||||
|
||||
@@ -335,8 +268,8 @@ macro "rfl'" : tactic => `(tactic| set_option smartUnfolding false in with_unfol
|
||||
/--
|
||||
`ac_rfl` proves equalities up to application of an associative and commutative operator.
|
||||
```
|
||||
instance : Associative (α := Nat) (.+.) := ⟨Nat.add_assoc⟩
|
||||
instance : Commutative (α := Nat) (.+.) := ⟨Nat.add_comm⟩
|
||||
instance : IsAssociative (α := Nat) (.+.) := ⟨Nat.add_assoc⟩
|
||||
instance : IsCommutative (α := Nat) (.+.) := ⟨Nat.add_comm⟩
|
||||
|
||||
example (a b c d : Nat) : a + b + c + d = d + (b + c) + a := by ac_rfl
|
||||
```
|
||||
@@ -626,7 +559,7 @@ You can use `with` to provide the variables names for each constructor.
|
||||
- `induction e`, where `e` is an expression instead of a variable,
|
||||
generalizes `e` in the goal, and then performs induction on the resulting variable.
|
||||
- `induction e using r` allows the user to specify the principle of induction that should be used.
|
||||
Here `r` should be a term whose result type must be of the form `C t`,
|
||||
Here `r` should be a theorem whose result type must be of the form `C t`,
|
||||
where `C` is a bound variable and `t` is a (possibly empty) sequence of bound variables
|
||||
- `induction e generalizing z₁ ... zₙ`, where `z₁ ... zₙ` are variables in the local context,
|
||||
generalizes over `z₁ ... zₙ` before applying the induction but then introduces them in each goal.
|
||||
@@ -634,7 +567,7 @@ You can use `with` to provide the variables names for each constructor.
|
||||
- Given `x : Nat`, `induction x with | zero => tac₁ | succ x' ih => tac₂`
|
||||
uses tactic `tac₁` for the `zero` case, and `tac₂` for the `succ` case.
|
||||
-/
|
||||
syntax (name := induction) "induction " term,+ (" using " term)?
|
||||
syntax (name := induction) "induction " term,+ (" using " ident)?
|
||||
(" generalizing" (ppSpace colGt term:max)+)? (inductionAlts)? : tactic
|
||||
|
||||
/-- A `generalize` argument, of the form `term = x` or `h : term = x`. -/
|
||||
@@ -677,7 +610,7 @@ You can use `with` to provide the variables names for each constructor.
|
||||
performs cases on `e` as above, but also adds a hypothesis `h : e = ...` to each hypothesis,
|
||||
where `...` is the constructor instance for that particular case.
|
||||
-/
|
||||
syntax (name := cases) "cases " casesTarget,+ (" using " term)? (inductionAlts)? : tactic
|
||||
syntax (name := cases) "cases " casesTarget,+ (" using " ident)? (inductionAlts)? : tactic
|
||||
|
||||
/-- `rename_i x_1 ... x_n` renames the last `n` inaccessible names using the given names. -/
|
||||
syntax (name := renameI) "rename_i" (ppSpace colGt binderIdent)+ : tactic
|
||||
|
||||
@@ -227,17 +227,3 @@ 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
|
||||
|
||||
/--
|
||||
Groups all elements `x`, `y` in `xs` with `key x == key y` into the same array
|
||||
`(xs.groupByKey key).find! (key x)`. Groups preserve the relative order of elements in `xs`.
|
||||
-/
|
||||
def Array.groupByKey [BEq α] [Hashable α] (key : β → α) (xs : Array β)
|
||||
: Lean.HashMap α (Array β) := Id.run do
|
||||
let mut groups := ∅
|
||||
for x in xs do
|
||||
let group := groups.findD (key x) #[]
|
||||
groups := groups.erase (key x) -- make `group` referentially unique
|
||||
groups := groups.insert (key x) (group.push x)
|
||||
return groups
|
||||
|
||||
@@ -8,4 +8,3 @@ import Lean.Data.Json.Stream
|
||||
import Lean.Data.Json.Printer
|
||||
import Lean.Data.Json.Parser
|
||||
import Lean.Data.Json.FromToJson
|
||||
import Lean.Data.Json.Elab
|
||||
|
||||
@@ -1,79 +0,0 @@
|
||||
/-
|
||||
Copyright (c) 2022 E.W.Ayers. All rights reserved.
|
||||
Released under Apache 2.0 license as described in the file LICENSE.
|
||||
Authors: E.W.Ayers, Wojciech Nawrocki
|
||||
-/
|
||||
import Lean.Data.Json.FromToJson
|
||||
import Lean.Syntax
|
||||
|
||||
/-!
|
||||
# JSON-like syntax for Lean.
|
||||
|
||||
Now you can write
|
||||
|
||||
```lean
|
||||
open Lean.Json
|
||||
|
||||
#eval json% {
|
||||
hello : "world",
|
||||
cheese : ["edam", "cheddar", {kind : "spicy", rank : 100.2}],
|
||||
lemonCount : 100e30,
|
||||
isCool : true,
|
||||
isBug : null,
|
||||
lookACalc: $(23 + 54 * 2)
|
||||
}
|
||||
```
|
||||
-/
|
||||
|
||||
namespace Lean.Json
|
||||
|
||||
/-- Json syntactic category -/
|
||||
declare_syntax_cat json (behavior := symbol)
|
||||
/-- Json null value syntax. -/
|
||||
syntax "null" : json
|
||||
/-- Json true value syntax. -/
|
||||
syntax "true" : json
|
||||
/-- Json false value syntax. -/
|
||||
syntax "false" : json
|
||||
/-- Json string syntax. -/
|
||||
syntax str : json
|
||||
/-- Json number negation syntax for ordinary numbers. -/
|
||||
syntax "-"? num : json
|
||||
/-- Json number negation syntax for scientific numbers. -/
|
||||
syntax "-"? scientific : json
|
||||
/-- Json array syntax. -/
|
||||
syntax "[" json,* "]" : json
|
||||
/-- Json identifier syntax. -/
|
||||
syntax jsonIdent := ident <|> str
|
||||
/-- Json key/value syntax. -/
|
||||
syntax jsonField := jsonIdent ": " json
|
||||
/-- Json object syntax. -/
|
||||
syntax "{" jsonField,* "}" : json
|
||||
/-- Allows to use Json syntax in a Lean file. -/
|
||||
syntax "json% " json : term
|
||||
|
||||
|
||||
macro_rules
|
||||
| `(json% null) => `(Lean.Json.null)
|
||||
| `(json% true) => `(Lean.Json.bool Bool.true)
|
||||
| `(json% false) => `(Lean.Json.bool Bool.false)
|
||||
| `(json% $n:str) => `(Lean.Json.str $n)
|
||||
| `(json% $n:num) => `(Lean.Json.num $n)
|
||||
| `(json% $n:scientific) => `(Lean.Json.num $n)
|
||||
| `(json% -$n:num) => `(Lean.Json.num (-$n))
|
||||
| `(json% -$n:scientific) => `(Lean.Json.num (-$n))
|
||||
| `(json% [$[$xs],*]) => `(Lean.Json.arr #[$[json% $xs],*])
|
||||
| `(json% {$[$ks:jsonIdent : $vs:json],*}) => do
|
||||
let ks : Array (TSyntax `term) ← ks.mapM fun
|
||||
| `(jsonIdent| $k:ident) => pure (k.getId |> toString |> quote)
|
||||
| `(jsonIdent| $k:str) => pure k
|
||||
| _ => Macro.throwUnsupported
|
||||
`(Lean.Json.mkObj [$[($ks, json% $vs)],*])
|
||||
| `(json% $stx) =>
|
||||
if stx.raw.isAntiquot then
|
||||
let stx := ⟨stx.raw.getAntiquotTerm⟩
|
||||
`(Lean.toJson $stx)
|
||||
else
|
||||
Macro.throwUnsupported
|
||||
|
||||
end Lean.Json
|
||||
@@ -74,7 +74,6 @@ structure ServerCapabilities where
|
||||
declarationProvider : Bool := false
|
||||
typeDefinitionProvider : Bool := false
|
||||
referencesProvider : Bool := false
|
||||
callHierarchyProvider : Bool := false
|
||||
renameProvider? : Option RenameOptions := none
|
||||
workspaceSymbolProvider : Bool := false
|
||||
foldingRangeProvider : Bool := false
|
||||
|
||||
@@ -8,8 +8,6 @@ Authors: Joscha Mennicken
|
||||
import Lean.Expr
|
||||
import Lean.Data.Lsp.Basic
|
||||
|
||||
set_option linter.missingDocs true -- keep it documented
|
||||
|
||||
/-! This file contains types for communication between the watchdog and the
|
||||
workers. These messages are not visible externally to users of the LSP server.
|
||||
-/
|
||||
@@ -19,27 +17,17 @@ namespace Lean.Lsp
|
||||
/-! Most reference-related types have custom FromJson/ToJson implementations to
|
||||
reduce the size of the resulting JSON. -/
|
||||
|
||||
/--
|
||||
Identifier of a reference.
|
||||
-/
|
||||
inductive RefIdent where
|
||||
/-- Named identifier. These are used in all references that are globally available. -/
|
||||
| const : Name → RefIdent
|
||||
/-- Unnamed identifier. These are used for all local references. -/
|
||||
| fvar : FVarId → RefIdent
|
||||
| fvar : FVarId → RefIdent
|
||||
deriving BEq, Hashable, Inhabited
|
||||
|
||||
namespace RefIdent
|
||||
|
||||
/-- Converts the reference identifier to a string by prefixing it with a symbol. -/
|
||||
def toString : RefIdent → String
|
||||
| RefIdent.const n => s!"c:{n}"
|
||||
| RefIdent.fvar id => s!"f:{id.name}"
|
||||
|
||||
/--
|
||||
Converts the string representation of a reference identifier back to a reference identifier.
|
||||
The string representation must have been created by `RefIdent.toString`.
|
||||
-/
|
||||
def fromString (s : String) : Except String RefIdent := do
|
||||
let sPrefix := s.take 2
|
||||
let sName := s.drop 2
|
||||
@@ -55,92 +43,33 @@ def fromString (s : String) : Except String RefIdent := do
|
||||
| "f:" => return RefIdent.fvar <| FVarId.mk name
|
||||
| _ => throw "string must start with 'c:' or 'f:'"
|
||||
|
||||
instance : FromJson RefIdent where
|
||||
fromJson?
|
||||
| (s : String) => fromString s
|
||||
| j => Except.error s!"expected a String, got {j}"
|
||||
|
||||
instance : ToJson RefIdent where
|
||||
toJson ident := toString ident
|
||||
|
||||
end RefIdent
|
||||
|
||||
/-- Information about the declaration surrounding a reference. -/
|
||||
structure RefInfo.ParentDecl where
|
||||
/-- Name of the declaration surrounding a reference. -/
|
||||
name : Name
|
||||
/-- Range of the declaration surrounding a reference. -/
|
||||
range : Lsp.Range
|
||||
/-- Selection range of the declaration surrounding a reference. -/
|
||||
selectionRange : Lsp.Range
|
||||
deriving ToJson
|
||||
|
||||
/--
|
||||
Denotes the range of a reference, as well as the parent declaration of the reference.
|
||||
If the reference is itself a declaration, then it contains no parent declaration.
|
||||
-/
|
||||
structure RefInfo.Location where
|
||||
/-- Range of the reference. -/
|
||||
range : Lsp.Range
|
||||
/-- Parent declaration of the reference. `none` if the reference is itself a declaration. -/
|
||||
parentDecl? : Option RefInfo.ParentDecl
|
||||
|
||||
/-- Definition site and usage sites of a reference. Obtained from `Lean.Server.RefInfo`. -/
|
||||
structure RefInfo where
|
||||
/-- Definition site of the reference. May be `none` when we cannot find a definition site. -/
|
||||
definition? : Option RefInfo.Location
|
||||
/-- Usage sites of the reference. -/
|
||||
usages : Array RefInfo.Location
|
||||
definition : Option Lsp.Range
|
||||
usages : Array Lsp.Range
|
||||
|
||||
instance : ToJson RefInfo where
|
||||
toJson i :=
|
||||
let rangeToList (r : Lsp.Range) : List Nat :=
|
||||
[r.start.line, r.start.character, r.end.line, r.end.character]
|
||||
let parentDeclToList (d : RefInfo.ParentDecl) : List Json :=
|
||||
let name := d.name.toString |> toJson
|
||||
let range := rangeToList d.range |>.map toJson
|
||||
let selectionRange := rangeToList d.selectionRange |>.map toJson
|
||||
[name] ++ range ++ selectionRange
|
||||
let locationToList (l : RefInfo.Location) : List Json :=
|
||||
let range := rangeToList l.range |>.map toJson
|
||||
let parentDecl := l.parentDecl?.map parentDeclToList |>.getD []
|
||||
range ++ parentDecl
|
||||
Json.mkObj [
|
||||
("definition", toJson $ i.definition?.map locationToList),
|
||||
("usages", toJson $ i.usages.map locationToList)
|
||||
("definition", toJson $ i.definition.map rangeToList),
|
||||
("usages", toJson $ i.usages.map rangeToList)
|
||||
]
|
||||
|
||||
instance : FromJson RefInfo where
|
||||
fromJson? j := do
|
||||
let toRange : List Nat → Except String Lsp.Range
|
||||
let listToRange (l : List Nat) : Except String Lsp.Range := match l with
|
||||
| [sLine, sChar, eLine, eChar] => pure ⟨⟨sLine, sChar⟩, ⟨eLine, eChar⟩⟩
|
||||
| l => throw s!"Expected list of length 4, not {l.length}"
|
||||
let toParentDecl (a : Array Json) : Except String RefInfo.ParentDecl := do
|
||||
let name := String.toName <| ← fromJson? a[0]!
|
||||
let range ← a[1:5].toArray.toList |>.mapM fromJson?
|
||||
let range ← toRange range
|
||||
let selectionRange ← a[5:].toArray.toList |>.mapM fromJson?
|
||||
let selectionRange ← toRange selectionRange
|
||||
return ⟨name, range, selectionRange⟩
|
||||
let toLocation (l : List Json) : Except String RefInfo.Location := do
|
||||
let l := l.toArray
|
||||
if l.size != 4 && l.size != 13 then
|
||||
.error "Expected list of length 4 or 13, not {l.size}"
|
||||
let range ← l[:4].toArray.toList |>.mapM fromJson?
|
||||
let range ← toRange range
|
||||
if l.size == 13 then
|
||||
let parentDecl ← toParentDecl l[4:].toArray
|
||||
return ⟨range, parentDecl⟩
|
||||
else
|
||||
return ⟨range, none⟩
|
||||
|
||||
let definition? ← j.getObjValAs? (Option $ List Json) "definition"
|
||||
let definition? ← match definition? with
|
||||
| _ => throw s!"Expected list of length 4, not {l.length}"
|
||||
let definition ← j.getObjValAs? (Option $ List Nat) "definition"
|
||||
let definition ← match definition with
|
||||
| none => pure none
|
||||
| some list => some <$> toLocation list
|
||||
let usages ← j.getObjValAs? (Array $ List Json) "usages"
|
||||
let usages ← usages.mapM toLocation
|
||||
pure { definition?, usages }
|
||||
| some list => some <$> listToRange list
|
||||
let usages ← j.getObjValAs? (Array $ List Nat) "usages"
|
||||
let usages ← usages.mapM listToRange
|
||||
pure { definition, usages }
|
||||
|
||||
/-- References from a single module/file -/
|
||||
def ModuleRefs := HashMap RefIdent RefInfo
|
||||
@@ -159,8 +88,7 @@ instance : FromJson ModuleRefs where
|
||||
Contains the file's definitions and references. -/
|
||||
structure LeanIleanInfoParams where
|
||||
/-- Version of the file these references are from. -/
|
||||
version : Nat
|
||||
/-- All references for the file. -/
|
||||
version : Nat
|
||||
references : ModuleRefs
|
||||
deriving FromJson, ToJson
|
||||
|
||||
|
||||
@@ -36,16 +36,16 @@ instance : FromJson CompletionItemKind where
|
||||
|
||||
structure InsertReplaceEdit where
|
||||
newText : String
|
||||
insert : Range
|
||||
insert : Range
|
||||
replace : Range
|
||||
deriving FromJson, ToJson
|
||||
|
||||
structure CompletionItem where
|
||||
label : String
|
||||
detail? : Option String := none
|
||||
label : String
|
||||
detail? : Option String := none
|
||||
documentation? : Option MarkupContent := none
|
||||
kind? : Option CompletionItemKind := none
|
||||
textEdit? : Option InsertReplaceEdit := none
|
||||
kind? : Option CompletionItemKind := none
|
||||
textEdit? : Option InsertReplaceEdit := none
|
||||
/-
|
||||
tags? : CompletionItemTag[]
|
||||
deprecated? : boolean
|
||||
@@ -63,7 +63,7 @@ structure CompletionItem where
|
||||
|
||||
structure CompletionList where
|
||||
isIncomplete : Bool
|
||||
items : Array CompletionItem
|
||||
items : Array CompletionItem
|
||||
deriving FromJson, ToJson
|
||||
|
||||
structure CompletionParams extends TextDocumentPositionParams where
|
||||
@@ -74,7 +74,7 @@ structure Hover where
|
||||
/- NOTE we should also accept MarkedString/MarkedString[] here
|
||||
but they are deprecated, so maybe can get away without. -/
|
||||
contents : MarkupContent
|
||||
range? : Option Range := none
|
||||
range? : Option Range := none
|
||||
deriving ToJson, FromJson
|
||||
|
||||
structure HoverParams extends TextDocumentPositionParams
|
||||
@@ -153,76 +153,45 @@ inductive SymbolKind where
|
||||
| event
|
||||
| operator
|
||||
| typeParameter
|
||||
deriving BEq, Hashable, Inhabited
|
||||
|
||||
instance : FromJson SymbolKind where
|
||||
fromJson?
|
||||
| 1 => .ok .file
|
||||
| 2 => .ok .module
|
||||
| 3 => .ok .namespace
|
||||
| 4 => .ok .package
|
||||
| 5 => .ok .class
|
||||
| 6 => .ok .method
|
||||
| 7 => .ok .property
|
||||
| 8 => .ok .field
|
||||
| 9 => .ok .constructor
|
||||
| 10 => .ok .enum
|
||||
| 11 => .ok .interface
|
||||
| 12 => .ok .function
|
||||
| 13 => .ok .variable
|
||||
| 14 => .ok .constant
|
||||
| 15 => .ok .string
|
||||
| 16 => .ok .number
|
||||
| 17 => .ok .boolean
|
||||
| 18 => .ok .array
|
||||
| 19 => .ok .object
|
||||
| 20 => .ok .key
|
||||
| 21 => .ok .null
|
||||
| 22 => .ok .enumMember
|
||||
| 23 => .ok .struct
|
||||
| 24 => .ok .event
|
||||
| 25 => .ok .operator
|
||||
| 26 => .ok .typeParameter
|
||||
| j => .error s!"invalid symbol kind {j}"
|
||||
|
||||
instance : ToJson SymbolKind where
|
||||
toJson
|
||||
| .file => 1
|
||||
| .module => 2
|
||||
| .namespace => 3
|
||||
| .package => 4
|
||||
| .class => 5
|
||||
| .method => 6
|
||||
| .property => 7
|
||||
| .field => 8
|
||||
| .constructor => 9
|
||||
| .enum => 10
|
||||
| .interface => 11
|
||||
| .function => 12
|
||||
| .variable => 13
|
||||
| .constant => 14
|
||||
| .string => 15
|
||||
| .number => 16
|
||||
| .boolean => 17
|
||||
| .array => 18
|
||||
| .object => 19
|
||||
| .key => 20
|
||||
| .null => 21
|
||||
| .enumMember => 22
|
||||
| .struct => 23
|
||||
| .event => 24
|
||||
| .operator => 25
|
||||
| .typeParameter => 26
|
||||
toJson
|
||||
| SymbolKind.file => 1
|
||||
| SymbolKind.module => 2
|
||||
| SymbolKind.namespace => 3
|
||||
| SymbolKind.package => 4
|
||||
| SymbolKind.class => 5
|
||||
| SymbolKind.method => 6
|
||||
| SymbolKind.property => 7
|
||||
| SymbolKind.field => 8
|
||||
| SymbolKind.constructor => 9
|
||||
| SymbolKind.enum => 10
|
||||
| SymbolKind.interface => 11
|
||||
| SymbolKind.function => 12
|
||||
| SymbolKind.variable => 13
|
||||
| SymbolKind.constant => 14
|
||||
| SymbolKind.string => 15
|
||||
| SymbolKind.number => 16
|
||||
| SymbolKind.boolean => 17
|
||||
| SymbolKind.array => 18
|
||||
| SymbolKind.object => 19
|
||||
| SymbolKind.key => 20
|
||||
| SymbolKind.null => 21
|
||||
| SymbolKind.enumMember => 22
|
||||
| SymbolKind.struct => 23
|
||||
| SymbolKind.event => 24
|
||||
| SymbolKind.operator => 25
|
||||
| SymbolKind.typeParameter => 26
|
||||
|
||||
structure DocumentSymbolAux (Self : Type) where
|
||||
name : String
|
||||
detail? : Option String := none
|
||||
kind : SymbolKind
|
||||
name : String
|
||||
detail? : Option String := none
|
||||
kind : SymbolKind
|
||||
-- tags? : Array SymbolTag
|
||||
range : Range
|
||||
range : Range
|
||||
selectionRange : Range
|
||||
children? : Option (Array Self) := none
|
||||
deriving FromJson, ToJson
|
||||
children? : Option (Array Self) := none
|
||||
deriving ToJson
|
||||
|
||||
inductive DocumentSymbol where
|
||||
| mk (sym : DocumentSymbolAux DocumentSymbol)
|
||||
@@ -243,56 +212,18 @@ instance : ToJson DocumentSymbolResult where
|
||||
|
||||
inductive SymbolTag where
|
||||
| deprecated
|
||||
deriving BEq, Hashable, Inhabited
|
||||
|
||||
instance : FromJson SymbolTag where
|
||||
fromJson?
|
||||
| 1 => .ok .deprecated
|
||||
| j => .error s!"unknown symbol tag {j}"
|
||||
|
||||
instance : ToJson SymbolTag where
|
||||
toJson
|
||||
| .deprecated => 1
|
||||
toJson
|
||||
| SymbolTag.deprecated => 1
|
||||
|
||||
structure SymbolInformation where
|
||||
name : String
|
||||
kind : SymbolKind
|
||||
tags : Array SymbolTag := #[]
|
||||
location : Location
|
||||
name : String
|
||||
kind : SymbolKind
|
||||
tags : Array SymbolTag := #[]
|
||||
location : Location
|
||||
containerName? : Option String := none
|
||||
deriving FromJson, ToJson
|
||||
|
||||
structure CallHierarchyPrepareParams extends TextDocumentPositionParams
|
||||
deriving FromJson, ToJson
|
||||
|
||||
structure CallHierarchyItem where
|
||||
name : String
|
||||
kind : SymbolKind
|
||||
tags? : Option (Array SymbolTag) := none
|
||||
detail? : Option String := none
|
||||
uri : DocumentUri
|
||||
range : Range
|
||||
selectionRange : Range
|
||||
-- data? : Option unknown
|
||||
deriving FromJson, ToJson, BEq, Hashable, Inhabited
|
||||
|
||||
structure CallHierarchyIncomingCallsParams where
|
||||
item : CallHierarchyItem
|
||||
deriving FromJson, ToJson
|
||||
|
||||
structure CallHierarchyIncomingCall where
|
||||
«from» : CallHierarchyItem
|
||||
fromRanges : Array Range
|
||||
deriving FromJson, ToJson, Inhabited
|
||||
|
||||
structure CallHierarchyOutgoingCallsParams where
|
||||
item : CallHierarchyItem
|
||||
deriving FromJson, ToJson
|
||||
|
||||
structure CallHierarchyOutgoingCall where
|
||||
to : CallHierarchyItem
|
||||
fromRanges : Array Range
|
||||
deriving FromJson, ToJson, Inhabited
|
||||
deriving ToJson
|
||||
|
||||
inductive SemanticTokenType where
|
||||
-- Used by Lean
|
||||
@@ -373,14 +304,14 @@ example {v : SemanticTokenModifier} : open SemanticTokenModifier in
|
||||
cases v <;> native_decide
|
||||
|
||||
structure SemanticTokensLegend where
|
||||
tokenTypes : Array String
|
||||
tokenTypes : Array String
|
||||
tokenModifiers : Array String
|
||||
deriving FromJson, ToJson
|
||||
|
||||
structure SemanticTokensOptions where
|
||||
legend : SemanticTokensLegend
|
||||
range : Bool
|
||||
full : Bool /- | {
|
||||
range : Bool
|
||||
full : Bool /- | {
|
||||
delta?: boolean;
|
||||
} -/
|
||||
deriving FromJson, ToJson
|
||||
@@ -391,12 +322,12 @@ structure SemanticTokensParams where
|
||||
|
||||
structure SemanticTokensRangeParams where
|
||||
textDocument : TextDocumentIdentifier
|
||||
range : Range
|
||||
range : Range
|
||||
deriving FromJson, ToJson
|
||||
|
||||
structure SemanticTokens where
|
||||
resultId? : Option String := none
|
||||
data : Array Nat
|
||||
data : Array Nat
|
||||
deriving FromJson, ToJson
|
||||
|
||||
structure FoldingRangeParams where
|
||||
@@ -412,12 +343,12 @@ instance : ToJson FoldingRangeKind where
|
||||
toJson
|
||||
| FoldingRangeKind.comment => "comment"
|
||||
| FoldingRangeKind.imports => "imports"
|
||||
| FoldingRangeKind.region => "region"
|
||||
| FoldingRangeKind.region => "region"
|
||||
|
||||
structure FoldingRange where
|
||||
startLine : Nat
|
||||
endLine : Nat
|
||||
kind? : Option FoldingRangeKind := none
|
||||
endLine : Nat
|
||||
kind? : Option FoldingRangeKind := none
|
||||
deriving ToJson
|
||||
|
||||
structure RenameOptions where
|
||||
|
||||
@@ -8,7 +8,6 @@ import Init.Data.String
|
||||
import Init.Data.Array
|
||||
import Lean.Data.Lsp.Basic
|
||||
import Lean.Data.Position
|
||||
import Lean.DeclarationRange
|
||||
|
||||
/-! LSP uses UTF-16 for indexing, so we need to provide some primitives
|
||||
to interact with Lean strings using UTF-16 indices. -/
|
||||
@@ -87,13 +86,3 @@ def utf8PosToLspPos (text : FileMap) (pos : String.Pos) : Lsp.Position :=
|
||||
|
||||
end FileMap
|
||||
end Lean
|
||||
|
||||
/--
|
||||
Convert the Lean `DeclarationRange` to an LSP `Range` by turning the 1-indexed line numbering into a
|
||||
0-indexed line numbering and converting the character offset within the line to a UTF-16 indexed
|
||||
offset.
|
||||
-/
|
||||
def Lean.DeclarationRange.toLspRange (r : Lean.DeclarationRange) : Lsp.Range := {
|
||||
start := ⟨r.pos.line - 1, r.charUtf16⟩
|
||||
«end» := ⟨r.endPos.line - 1, r.endCharUtf16⟩
|
||||
}
|
||||
|
||||
@@ -96,12 +96,6 @@ def quickCmp (n₁ n₂ : Name) : Ordering :=
|
||||
def quickLt (n₁ n₂ : Name) : Bool :=
|
||||
quickCmp n₁ n₂ == Ordering.lt
|
||||
|
||||
/-- Returns true if the name has any numeric components. -/
|
||||
def hasNum : Name → Bool
|
||||
| .anonymous => false
|
||||
| .str p _ => p.hasNum
|
||||
| .num _ _ => true
|
||||
|
||||
/-- The frontend does not allow user declarations to start with `_` in any of its parts.
|
||||
We use name parts starting with `_` internally to create auxiliary names (e.g., `_private`). -/
|
||||
def isInternal : Name → Bool
|
||||
@@ -109,17 +103,6 @@ def isInternal : Name → Bool
|
||||
| num p _ => isInternal p
|
||||
| _ => false
|
||||
|
||||
/--
|
||||
The frontend does not allow user declarations to start with `_` in any of its parts.
|
||||
We use name parts starting with `_` internally to create auxiliary names (e.g., `_private`).
|
||||
|
||||
This function checks if any component of the name starts with `_`, or is numeric.
|
||||
-/
|
||||
def isInternalOrNum : Name → Bool
|
||||
| .str p s => s.get 0 == '_' || isInternalOrNum p
|
||||
| .num _ _ => true
|
||||
| _ => false
|
||||
|
||||
/--
|
||||
Checks whether the name is an implementation-detail hypothesis name.
|
||||
|
||||
|
||||
@@ -29,16 +29,9 @@ instance : ToExpr Position where
|
||||
|
||||
end Position
|
||||
|
||||
/-- Content of a file together with precalculated positions of newlines. -/
|
||||
structure FileMap where
|
||||
/-- The content of the file. -/
|
||||
source : String
|
||||
/-- The positions of newline characters.
|
||||
The first entry is always `0` and the last always the index of the last character.
|
||||
In particular, if the last character is a newline, that index will appear twice. -/
|
||||
positions : Array String.Pos
|
||||
/-- The line numbers associated with the `positions`.
|
||||
Has the same length as `positions` and is always of the form `#[1, 2, …, n-1, n-1]`. -/
|
||||
lines : Array Nat
|
||||
deriving Inhabited
|
||||
|
||||
|
||||
@@ -690,10 +690,10 @@ builtin_initialize elabAsElim : TagAttribute ←
|
||||
(applicationTime := .afterCompilation)
|
||||
fun declName => do
|
||||
let go : MetaM Unit := do
|
||||
discard <| getElimInfo declName
|
||||
let info ← getConstInfo declName
|
||||
if (← hasOptAutoParams info.type) then
|
||||
throwError "[elab_as_elim] attribute cannot be used in declarations containing optional and auto parameters"
|
||||
discard <| getElimInfo declName
|
||||
go.run' {} {}
|
||||
|
||||
/-! # Eliminator-like function application elaborator -/
|
||||
@@ -937,7 +937,6 @@ def elabAppArgs (f : Expr) (namedArgs : Array NamedArg) (args : Array Arg)
|
||||
where
|
||||
/-- Return `some info` if we should elaborate as an eliminator. -/
|
||||
elabAsElim? : TermElabM (Option ElimInfo) := do
|
||||
unless (← read).heedElabAsElim do return none
|
||||
if explicit || ellipsis then return none
|
||||
let .const declName _ := f | return none
|
||||
unless (← shouldElabAsElim declName) do return none
|
||||
@@ -958,7 +957,8 @@ where
|
||||
The idea is that the contribute to motive inference. See comment at `ElamElim.Context.extraArgsPos`.
|
||||
-/
|
||||
getElabAsElimExtraArgsPos (elimInfo : ElimInfo) : MetaM (Array Nat) := do
|
||||
forallTelescope elimInfo.elimType fun xs type => do
|
||||
let cinfo ← getConstInfo elimInfo.name
|
||||
forallTelescope cinfo.type fun xs type => do
|
||||
let resultArgs := type.getAppArgs
|
||||
let mut extraArgsPos := #[]
|
||||
for i in [:xs.size] do
|
||||
|
||||
@@ -238,11 +238,10 @@ private def mkInfoTree (elaborator : Name) (stx : Syntax) (trees : PersistentArr
|
||||
let s ← get
|
||||
let scope := s.scopes.head!
|
||||
let tree := InfoTree.node (Info.ofCommandInfo { elaborator, stx }) trees
|
||||
let ctx := PartialContextInfo.commandCtx {
|
||||
return InfoTree.context {
|
||||
env := s.env, fileMap := ctx.fileMap, mctx := {}, currNamespace := scope.currNamespace,
|
||||
openDecls := scope.openDecls, options := scope.opts, ngen := s.ngen
|
||||
}
|
||||
return InfoTree.context ctx tree
|
||||
} tree
|
||||
|
||||
private def elabCommandUsing (s : State) (stx : Syntax) : List (KeyedDeclsAttribute.AttributeEntry CommandElab) → CommandElabM Unit
|
||||
| [] => withInfoTreeContext (mkInfoTree := mkInfoTree `no_elab stx) <| throwError "unexpected syntax{indentD stx}"
|
||||
|
||||
@@ -1368,7 +1368,7 @@ mutual
|
||||
else
|
||||
pure doElems.toArray
|
||||
let contSeq := mkDoSeq contSeq
|
||||
let auxDo ← `(do match $val:term with | $pattern:term => $contSeq | _ => $elseSeq)
|
||||
let auxDo ← `(do let __discr := $val; match __discr with | $pattern:term => $contSeq | _ => $elseSeq)
|
||||
doSeqToCode <| getDoSeqElems (getDoSeq auxDo)
|
||||
|
||||
/-- Generate `CodeBlock` for `doReassignArrow; doElems`
|
||||
|
||||
@@ -118,7 +118,7 @@ def runFrontend
|
||||
if let some ileanFileName := ileanFileName? then
|
||||
let trees := s.commandState.infoState.trees.toArray
|
||||
let references := Lean.Server.findModuleRefs inputCtx.fileMap trees (localVars := false)
|
||||
let ilean := { module := mainModuleName, references := ← references.toLspModuleRefs : Lean.Server.Ilean }
|
||||
let ilean := { module := mainModuleName, references : Lean.Server.Ilean }
|
||||
IO.FS.writeFile ileanFileName $ Json.compress $ toJson ilean
|
||||
|
||||
pure (s.commandState.env, !s.commandState.messages.hasErrors)
|
||||
|
||||
@@ -6,11 +6,11 @@ Authors: Wojciech Nawrocki, Leonardo de Moura, Sebastian Ullrich
|
||||
-/
|
||||
import Lean.Meta.PPGoal
|
||||
|
||||
namespace Lean.Elab.CommandContextInfo
|
||||
namespace Lean.Elab.ContextInfo
|
||||
|
||||
variable [Monad m] [MonadEnv m] [MonadMCtx m] [MonadOptions m] [MonadResolveName m] [MonadNameGenerator m]
|
||||
|
||||
def saveNoFileMap : m CommandContextInfo := return {
|
||||
def saveNoFileMap : m ContextInfo := return {
|
||||
env := (← getEnv)
|
||||
fileMap := default
|
||||
mctx := (← getMCtx)
|
||||
@@ -20,32 +20,11 @@ def saveNoFileMap : m CommandContextInfo := return {
|
||||
ngen := (← getNGen)
|
||||
}
|
||||
|
||||
def save [MonadFileMap m] : m CommandContextInfo := do
|
||||
def save [MonadFileMap m] : m ContextInfo := do
|
||||
let ctx ← saveNoFileMap
|
||||
return { ctx with fileMap := (← getFileMap) }
|
||||
|
||||
end CommandContextInfo
|
||||
|
||||
/--
|
||||
Merges the `inner` partial context into the `outer` context s.t. fields of the `inner` context
|
||||
overwrite fields of the `outer` context. Panics if the invariant described in the documentation
|
||||
for `PartialContextInfo` is violated.
|
||||
|
||||
When traversing an `InfoTree`, this function should be used to combine the context of outer
|
||||
nodes with the partial context of their subtrees. This ensures that the traversal has the context
|
||||
from the inner node to the root node of the `InfoTree` available, with partial contexts of
|
||||
inner nodes taking priority over contexts of outer nodes.
|
||||
-/
|
||||
def PartialContextInfo.mergeIntoOuter?
|
||||
: (inner : PartialContextInfo) → (outer? : Option ContextInfo) → Option ContextInfo
|
||||
| .commandCtx info, none =>
|
||||
some { info with }
|
||||
| .parentDeclCtx _, none =>
|
||||
panic! "Unexpected incomplete InfoTree context info."
|
||||
| .commandCtx innerInfo, some outer =>
|
||||
some { outer with toCommandContextInfo := innerInfo }
|
||||
| .parentDeclCtx innerParentDecl, some outer =>
|
||||
some { outer with parentDecl? := innerParentDecl }
|
||||
end ContextInfo
|
||||
|
||||
def CompletionInfo.stx : CompletionInfo → Syntax
|
||||
| dot i .. => i.stx
|
||||
@@ -171,9 +150,6 @@ def FVarAliasInfo.format (info : FVarAliasInfo) : Format :=
|
||||
def FieldRedeclInfo.format (ctx : ContextInfo) (info : FieldRedeclInfo) : Format :=
|
||||
f!"FieldRedecl @ {formatStxRange ctx info.stx}"
|
||||
|
||||
def OmissionInfo.format (ctx : ContextInfo) (info : OmissionInfo) : IO Format := do
|
||||
return f!"Omission @ {← TermInfo.format ctx info.toTermInfo}"
|
||||
|
||||
def Info.format (ctx : ContextInfo) : Info → IO Format
|
||||
| ofTacticInfo i => i.format ctx
|
||||
| ofTermInfo i => i.format ctx
|
||||
@@ -186,7 +162,6 @@ def Info.format (ctx : ContextInfo) : Info → IO Format
|
||||
| ofCustomInfo i => pure <| Std.ToFormat.format i
|
||||
| ofFVarAliasInfo i => pure <| i.format
|
||||
| ofFieldRedeclInfo i => pure <| i.format ctx
|
||||
| ofOmissionInfo i => i.format ctx
|
||||
|
||||
def Info.toElabInfo? : Info → Option ElabInfo
|
||||
| ofTacticInfo i => some i.toElabInfo
|
||||
@@ -200,7 +175,6 @@ def Info.toElabInfo? : Info → Option ElabInfo
|
||||
| ofCustomInfo _ => none
|
||||
| ofFVarAliasInfo _ => none
|
||||
| ofFieldRedeclInfo _ => none
|
||||
| ofOmissionInfo i => some i.toElabInfo
|
||||
|
||||
/--
|
||||
Helper function for propagating the tactic metavariable context to its children nodes.
|
||||
@@ -223,7 +197,7 @@ def Info.updateContext? : Option ContextInfo → Info → Option ContextInfo
|
||||
partial def InfoTree.format (tree : InfoTree) (ctx? : Option ContextInfo := none) : IO Format := do
|
||||
match tree with
|
||||
| hole id => return .nestD f!"• ?{toString id.name}"
|
||||
| context i t => format t <| i.mergeIntoOuter? ctx?
|
||||
| context i t => format t i
|
||||
| node i cs => match ctx? with
|
||||
| none => return "• <context-not-available>"
|
||||
| some ctx =>
|
||||
@@ -334,52 +308,20 @@ def withInfoTreeContext [MonadFinally m] (x : m α) (mkInfoTree : PersistentArra
|
||||
@[inline] def withInfoContext [MonadFinally m] (x : m α) (mkInfo : m Info) : m α := do
|
||||
withInfoTreeContext x (fun trees => do return InfoTree.node (← mkInfo) trees)
|
||||
|
||||
private def withSavedPartialInfoContext [MonadFinally m]
|
||||
(x : m α)
|
||||
(ctx? : m (Option PartialContextInfo))
|
||||
: m α := do
|
||||
if !(← getInfoState).enabled then
|
||||
return ← x
|
||||
let treesSaved ← getResetInfoTrees
|
||||
Prod.fst <$> MonadFinally.tryFinally' x fun _ => do
|
||||
let st ← getInfoState
|
||||
let trees ← st.trees.mapM fun tree => do
|
||||
let tree := tree.substitute st.assignment
|
||||
match (← ctx?) with
|
||||
| none =>
|
||||
pure tree
|
||||
| some ctx =>
|
||||
pure <| InfoTree.context ctx tree
|
||||
modifyInfoTrees fun _ => treesSaved ++ trees
|
||||
|
||||
/--
|
||||
Resets the trees state `t₀`, runs `x` to produce a new trees state `t₁` and sets the state to be
|
||||
`t₀ ++ (InfoTree.context (PartialContextInfo.commandCtx Γ) <$> t₁)` where `Γ` is the context derived
|
||||
from the monad state.
|
||||
-/
|
||||
def withSaveInfoContext
|
||||
[MonadNameGenerator m]
|
||||
[MonadFinally m]
|
||||
[MonadEnv m]
|
||||
[MonadOptions m]
|
||||
[MonadMCtx m]
|
||||
[MonadResolveName m]
|
||||
[MonadFileMap m]
|
||||
(x : m α)
|
||||
: m α := do
|
||||
withSavedPartialInfoContext x do
|
||||
return some <| .commandCtx (← CommandContextInfo.save)
|
||||
|
||||
/--
|
||||
Resets the trees state `t₀`, runs `x` to produce a new trees state `t₁` and sets the state to be
|
||||
`t₀ ++ (InfoTree.context (PartialContextInfo.parentDeclCtx Γ) <$> t₁)` where `Γ` is the parent decl
|
||||
name provided by `MonadParentDecl m`.
|
||||
-/
|
||||
def withSaveParentDeclInfoContext [MonadFinally m] [MonadParentDecl m] (x : m α) : m α := do
|
||||
withSavedPartialInfoContext x do
|
||||
let some declName ← getParentDeclName?
|
||||
| return none
|
||||
return some <| .parentDeclCtx declName
|
||||
/-- Resets the trees state `t₀`, runs `x` to produce a new trees
|
||||
state `t₁` and sets the state to be `t₀ ++ (InfoTree.context Γ <$> t₁)`
|
||||
where `Γ` is the context derived from the monad state. -/
|
||||
def withSaveInfoContext [MonadNameGenerator m] [MonadFinally m] [MonadEnv m] [MonadOptions m] [MonadMCtx m] [MonadResolveName m] [MonadFileMap m] (x : m α) : m α := do
|
||||
if (← getInfoState).enabled then
|
||||
let treesSaved ← getResetInfoTrees
|
||||
Prod.fst <$> MonadFinally.tryFinally' x fun _ => do
|
||||
let st ← getInfoState
|
||||
let trees ← st.trees.mapM fun tree => do
|
||||
let tree := tree.substitute st.assignment
|
||||
pure <| InfoTree.context (← ContextInfo.save) tree
|
||||
modifyInfoTrees fun _ => treesSaved ++ trees
|
||||
else
|
||||
x
|
||||
|
||||
def getInfoHoleIdAssignment? (mvarId : MVarId) : m (Option InfoTree) :=
|
||||
return (← getInfoState).assignment[mvarId]
|
||||
|
||||
@@ -14,12 +14,10 @@ import Lean.Widget.Types
|
||||
|
||||
namespace Lean.Elab
|
||||
|
||||
/--
|
||||
Context after executing `liftTermElabM`.
|
||||
Note that the term information collected during elaboration may contain metavariables, and their
|
||||
assignments are stored at `mctx`.
|
||||
-/
|
||||
structure CommandContextInfo where
|
||||
/-- Context after executing `liftTermElabM`.
|
||||
Note that the term information collected during elaboration may contain metavariables, and their
|
||||
assignments are stored at `mctx`. -/
|
||||
structure ContextInfo where
|
||||
env : Environment
|
||||
fileMap : FileMap
|
||||
mctx : MetavarContext := {}
|
||||
@@ -28,31 +26,6 @@ structure CommandContextInfo where
|
||||
openDecls : List OpenDecl := []
|
||||
ngen : NameGenerator -- We must save the name generator to implement `ContextInfo.runMetaM` and making we not create `MVarId`s used in `mctx`.
|
||||
|
||||
/--
|
||||
Context from the root of the `InfoTree` up to this node.
|
||||
Note that the term information collected during elaboration may contain metavariables, and their
|
||||
assignments are stored at `mctx`.
|
||||
-/
|
||||
structure ContextInfo extends CommandContextInfo where
|
||||
parentDecl? : Option Name := none
|
||||
|
||||
/--
|
||||
Context for a sub-`InfoTree`.
|
||||
|
||||
Within `InfoTree`, this must fulfill the invariant that every non-`commandCtx` `PartialContextInfo`
|
||||
node is always contained within a `commandCtx` node.
|
||||
-/
|
||||
inductive PartialContextInfo where
|
||||
| commandCtx (info : CommandContextInfo)
|
||||
/--
|
||||
Context for the name of the declaration that surrounds nodes contained within this `context` node.
|
||||
For example, this makes the name of the surrounding declaration available in `InfoTree` nodes
|
||||
corresponding to the terms within the declaration.
|
||||
-/
|
||||
| parentDeclCtx (parentDecl : Name)
|
||||
-- TODO: More constructors for the different kinds of scopes `commandCtx` is currently
|
||||
-- used for (e.g. eliminating `Info.updateContext?` would be nice!).
|
||||
|
||||
/-- Base structure for `TermInfo`, `CommandInfo` and `TacticInfo`. -/
|
||||
structure ElabInfo where
|
||||
/-- The name of the elaborator that created this info. -/
|
||||
@@ -154,15 +127,6 @@ structure Bar extends Foo :=
|
||||
structure FieldRedeclInfo where
|
||||
stx : Syntax
|
||||
|
||||
/--
|
||||
Denotes information for the term `⋯` that is emitted by the delaborator when omitting a term
|
||||
due to `pp.deepTerms false`. Omission needs to be treated differently from regular terms because
|
||||
it has to be delaborated differently in `Lean.Widget.InteractiveDiagnostics.infoToInteractive`:
|
||||
Regular terms are delaborated explicitly, whereas omitted terms are simply to be expanded with
|
||||
regular delaboration settings.
|
||||
-/
|
||||
structure OmissionInfo extends TermInfo
|
||||
|
||||
/-- Header information for a node in `InfoTree`. -/
|
||||
inductive Info where
|
||||
| ofTacticInfo (i : TacticInfo)
|
||||
@@ -176,7 +140,6 @@ inductive Info where
|
||||
| ofCustomInfo (i : CustomInfo)
|
||||
| ofFVarAliasInfo (i : FVarAliasInfo)
|
||||
| ofFieldRedeclInfo (i : FieldRedeclInfo)
|
||||
| ofOmissionInfo (i : OmissionInfo)
|
||||
deriving Inhabited
|
||||
|
||||
/-- The InfoTree is a structure that is generated during elaboration and used
|
||||
@@ -201,8 +164,8 @@ inductive Info where
|
||||
`hole`s which are filled in later in the same way that unassigned metavariables are.
|
||||
-/
|
||||
inductive InfoTree where
|
||||
/-- The context object is created at appropriate points during elaboration -/
|
||||
| context (i : PartialContextInfo) (t : InfoTree)
|
||||
/-- The context object is created by `liftTermElabM` at `Command.lean` -/
|
||||
| context (i : ContextInfo) (t : InfoTree)
|
||||
/-- The children contain information for nested term elaboration and tactic evaluation -/
|
||||
| node (i : Info) (children : PersistentArray InfoTree)
|
||||
/-- The elaborator creates holes (aka metavariables) for tactics and postponed terms -/
|
||||
@@ -228,7 +191,7 @@ structure InfoState where
|
||||
trees : PersistentArray InfoTree := {}
|
||||
deriving Inhabited
|
||||
|
||||
class MonadInfoTree (m : Type → Type) where
|
||||
class MonadInfoTree (m : Type → Type) where
|
||||
getInfoState : m InfoState
|
||||
modifyInfoState : (InfoState → InfoState) → m Unit
|
||||
|
||||
@@ -241,9 +204,4 @@ instance [MonadLift m n] [MonadInfoTree m] : MonadInfoTree n where
|
||||
def setInfoState [MonadInfoTree m] (s : InfoState) : m Unit :=
|
||||
modifyInfoState fun _ => s
|
||||
|
||||
class MonadParentDecl (m : Type → Type) where
|
||||
getParentDeclName? : m (Option Name)
|
||||
|
||||
export MonadParentDecl (getParentDeclName?)
|
||||
|
||||
end Lean.Elab
|
||||
|
||||
@@ -97,7 +97,7 @@ private def registerLetRecsToLift (views : Array LetRecDeclView) (fvars : Array
|
||||
|
||||
let toLift ← views.mapIdxM fun i view => do
|
||||
let value := values[i]!
|
||||
let termination := view.termination.rememberExtraParams view.binderIds.size value
|
||||
let termination ← view.termination.checkVars view.binderIds.size value
|
||||
pure {
|
||||
ref := view.ref
|
||||
fvarId := fvars[i]!.fvarId!
|
||||
|
||||
@@ -642,7 +642,7 @@ def pushMain (preDefs : Array PreDefinition) (sectionVars : Array Expr) (mainHea
|
||||
mainHeaders.size.foldM (init := preDefs) fun i preDefs => do
|
||||
let header := mainHeaders[i]!
|
||||
let termination ← declValToTerminationHint header.valueStx
|
||||
let termination := termination.rememberExtraParams header.numParams mainVals[i]!
|
||||
let termination ← termination.checkVars header.numParams mainVals[i]!
|
||||
let value ← mkLambdaFVars sectionVars mainVals[i]!
|
||||
let type ← mkForallFVars sectionVars header.type
|
||||
return preDefs.push {
|
||||
|
||||
@@ -41,12 +41,14 @@ def preprocess (e : Expr) (recFnName : Name) : CoreM Expr :=
|
||||
return .visit e.headBeta
|
||||
else
|
||||
return .continue)
|
||||
(post := fun e => do
|
||||
if e.isApp && e.getAppFn.isMData then
|
||||
let .mdata m f := e.getAppFn | unreachable!
|
||||
(post := fun e =>
|
||||
match e with
|
||||
| .app (.mdata m f) a =>
|
||||
if m.isRecApp then
|
||||
return .done (.mdata m (f.beta e.getAppArgs))
|
||||
return .continue)
|
||||
return .done (.mdata m (.app f a))
|
||||
else
|
||||
return .done e
|
||||
| _ => return .done e)
|
||||
|
||||
|
||||
end Lean.Elab.Structural
|
||||
|
||||
@@ -44,18 +44,19 @@ private def rwFixEq (mvarId : MVarId) : MetaM MVarId := mvarId.withContext do
|
||||
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 (targetNew, _) ← Simp.main target (← Split.getSimpMatchContext) (methods := { pre })
|
||||
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
|
||||
let some app ← matchMatcherApp? e | return Simp.Step.visit { expr := e }
|
||||
-- First try to reduce matcher
|
||||
match (← reduceRecMatcher? e) with
|
||||
| some e' => return Simp.Step.done { expr := e' }
|
||||
| none => Simp.simpMatchCore app.matcherName e
|
||||
| none =>
|
||||
match (← Simp.simpMatchCore? app.matcherName e SplitIf.discharge?) with
|
||||
| some r => return r
|
||||
| none => return Simp.Step.visit { expr := e }
|
||||
|
||||
/--
|
||||
Given a goal of the form `|- f.{us} a_1 ... a_n b_1 ... b_m = ...`, return `(us, #[a_1, ..., a_n])`
|
||||
|
||||
@@ -575,7 +575,7 @@ def buildTermWF (originalVarNamess : Array (Array Name)) (varNamess : Array (Arr
|
||||
`($sizeOfIdent $v)
|
||||
| .func funIdx' => if funIdx' == funIdx then `(1) else `(0)
|
||||
let body ← mkTupleSyntax measureStxs
|
||||
return { ref := .missing, vars := idents, body, synthetic := true }
|
||||
return { ref := .missing, vars := idents, body }
|
||||
|
||||
/--
|
||||
The TerminationWF produced by GuessLex may mention more variables than allowed in the surface
|
||||
@@ -585,9 +585,8 @@ The latter works fine in many cases, and is still useful to the user in the tric
|
||||
we do that.
|
||||
-/
|
||||
def trimTermWF (extraParams : Array Nat) (elems : TerminationWF) : TerminationWF :=
|
||||
elems.mapIdx fun funIdx elem => { elem with
|
||||
vars := elem.vars[elem.vars.size - extraParams[funIdx]! : elem.vars.size]
|
||||
synthetic := false }
|
||||
elems.mapIdx fun funIdx elem =>
|
||||
{ elem with vars := elem.vars[elem.vars.size - extraParams[funIdx]! : elem.vars.size] }
|
||||
|
||||
/--
|
||||
Given a matrix (row-major) of strings, arranges them in tabular form.
|
||||
|
||||
@@ -81,8 +81,7 @@ private def isOnlyOneUnaryDef (preDefs : Array PreDefinition) (fixedPrefixSize :
|
||||
return false
|
||||
|
||||
def wfRecursion (preDefs : Array PreDefinition) : TermElabM Unit := do
|
||||
let preDefs ← preDefs.mapM fun preDef =>
|
||||
return { preDef with value := (← preprocess preDef.value) }
|
||||
let preDefs ← preDefs.mapM fun preDef => return { preDef with value := (← preprocess preDef.value) }
|
||||
let (unaryPreDef, fixedPrefixSize) ← withoutModifyingEnv do
|
||||
for preDef in preDefs do
|
||||
addAsAxiom preDef
|
||||
@@ -92,6 +91,7 @@ def wfRecursion (preDefs : Array PreDefinition) : TermElabM Unit := do
|
||||
let unaryPreDefs ← packDomain fixedPrefixSize preDefsDIte
|
||||
return (← packMutual fixedPrefixSize preDefs unaryPreDefs, fixedPrefixSize)
|
||||
|
||||
let extraParamss := preDefs.map (·.termination.extraParams)
|
||||
let wf ← do
|
||||
let (preDefsWith, preDefsWithout) := preDefs.partition (·.termination.termination_by?.isSome)
|
||||
if preDefsWith.isEmpty then
|
||||
@@ -109,7 +109,7 @@ def wfRecursion (preDefs : Array PreDefinition) : TermElabM Unit := do
|
||||
let preDefNonRec ← forallBoundedTelescope unaryPreDef.type fixedPrefixSize fun prefixArgs type => do
|
||||
let type ← whnfForall type
|
||||
let packedArgType := type.bindingDomain!
|
||||
elabWFRel preDefs unaryPreDef.declName fixedPrefixSize packedArgType wf fun wfRel => do
|
||||
elabWFRel preDefs unaryPreDef.declName fixedPrefixSize packedArgType extraParamss wf fun wfRel => do
|
||||
trace[Elab.definition.wf] "wfRel: {wfRel}"
|
||||
let (value, envNew) ← withoutModifyingEnv' do
|
||||
addAsAxiom unaryPreDef
|
||||
|
||||
@@ -9,12 +9,6 @@ import Lean.Elab.RecAppSyntax
|
||||
namespace Lean.Elab.WF
|
||||
open Meta
|
||||
|
||||
private def shouldBetaReduce (e : Expr) (recFnNames : Array Name) : Bool :=
|
||||
if e.isHeadBetaTarget then
|
||||
e.getAppFn.find? (fun e => recFnNames.any (e.isConstOf ·)) |>.isSome
|
||||
else
|
||||
false
|
||||
|
||||
/--
|
||||
Preprocesses the expessions to improve the effectiveness of `wfRecursion`.
|
||||
|
||||
@@ -31,11 +25,13 @@ remove `let_fun`-lambdas that contain explicit termination proofs.
|
||||
-/
|
||||
def preprocess (e : Expr) : CoreM Expr :=
|
||||
Core.transform e
|
||||
(post := fun e => do
|
||||
if e.isApp && e.getAppFn.isMData then
|
||||
let .mdata m f := e.getAppFn | unreachable!
|
||||
(post := fun e =>
|
||||
match e with
|
||||
| .app (.mdata m f) a =>
|
||||
if m.isRecApp then
|
||||
return .done (.mdata m (f.beta e.getAppArgs))
|
||||
return .continue)
|
||||
return .done (.mdata m (.app f a))
|
||||
else
|
||||
return .done e
|
||||
| _ => return .done e)
|
||||
|
||||
end Lean.Elab.WF
|
||||
|
||||
@@ -24,15 +24,16 @@ private partial def unpackMutual (preDefs : Array PreDefinition) (mvarId : MVarI
|
||||
go 0 mvarId fvarId #[]
|
||||
|
||||
private partial def unpackUnary (preDef : PreDefinition) (prefixSize : Nat) (mvarId : MVarId)
|
||||
(fvarId : FVarId) (element : TerminationBy) : TermElabM MVarId := do
|
||||
element.checkVars preDef.declName preDef.termination.extraParams
|
||||
-- If `synthetic := false`, then this is user-provided, and should be interpreted
|
||||
(fvarId : FVarId) (extraParams : Nat) (element : TerminationBy) : TermElabM MVarId := do
|
||||
-- If elements.vars is ≤ extraParams, this is user-provided, and should be interpreted
|
||||
-- as left to right. Else it is provided by GuessLex, and may rename non-extra paramters as well.
|
||||
-- (Not pretty, but it works for now)
|
||||
let implicit_underscores :=
|
||||
if element.synthetic then 0 else preDef.termination.extraParams - element.vars.size
|
||||
if element.vars.size < extraParams then extraParams - element.vars.size else 0
|
||||
let varNames ← lambdaTelescope preDef.value fun xs _ => do
|
||||
let mut varNames ← xs.mapM fun x => x.fvarId!.getUserName
|
||||
if element.vars.size > varNames.size then
|
||||
throwErrorAt element.vars[varNames.size]! "too many variable names"
|
||||
for h : i in [:element.vars.size] do
|
||||
let varStx := element.vars[i]
|
||||
if let `($ident:ident) := varStx then
|
||||
@@ -54,7 +55,8 @@ private partial def unpackUnary (preDef : PreDefinition) (prefixSize : Nat) (mva
|
||||
go 0 mvarId fvarId
|
||||
|
||||
def elabWFRel (preDefs : Array PreDefinition) (unaryPreDefName : Name) (fixedPrefixSize : Nat)
|
||||
(argType : Expr) (wf : TerminationWF) (k : Expr → TermElabM α) : TermElabM α := do
|
||||
(argType : Expr) (extraParamss : Array Nat) (wf : TerminationWF) (k : Expr → TermElabM α) :
|
||||
TermElabM α := do
|
||||
let α := argType
|
||||
let u ← getLevel α
|
||||
let expectedType := mkApp (mkConst ``WellFoundedRelation [u]) α
|
||||
@@ -64,8 +66,8 @@ def elabWFRel (preDefs : Array PreDefinition) (unaryPreDefName : Name) (fixedPre
|
||||
let [fMVarId, wfRelMVarId, _] ← mainMVarId.apply (← mkConstWithFreshMVarLevels ``invImage) | throwError "failed to apply 'invImage'"
|
||||
let (d, fMVarId) ← fMVarId.intro1
|
||||
let subgoals ← unpackMutual preDefs fMVarId d
|
||||
for (d, mvarId) in subgoals, element in wf, preDef in preDefs do
|
||||
let mvarId ← unpackUnary preDef fixedPrefixSize mvarId d element
|
||||
for (d, mvarId) in subgoals, extraParams in extraParamss, element in wf, preDef in preDefs do
|
||||
let mvarId ← unpackUnary preDef fixedPrefixSize mvarId d extraParams element
|
||||
mvarId.withContext do
|
||||
let value ← Term.withSynthesize <| elabTermEnsuringType element.body (← mvarId.getType)
|
||||
mvarId.assign value
|
||||
|
||||
@@ -16,13 +16,6 @@ structure TerminationBy where
|
||||
ref : Syntax
|
||||
vars : TSyntaxArray [`ident, ``Lean.Parser.Term.hole]
|
||||
body : Term
|
||||
/--
|
||||
If `synthetic := true`, then this `termination_by` clause was
|
||||
generated by `GuessLex`, and `vars` refers to *all* parameters
|
||||
of the function, not just the “extra parameters”.
|
||||
Cf. Lean.Elab.WF.unpackUnary
|
||||
-/
|
||||
synthetic : Bool := false
|
||||
deriving Inhabited
|
||||
|
||||
open Parser.Termination in
|
||||
@@ -51,13 +44,14 @@ structure TerminationHints where
|
||||
ref : Syntax
|
||||
termination_by? : Option TerminationBy
|
||||
decreasing_by? : Option DecreasingBy
|
||||
/-- Here we record the number of parameters past the `:`. It is set by
|
||||
`TerminationHints.rememberExtraParams` and used as folows:
|
||||
|
||||
* When we guess the termination argument in `GuessLex` and want to print it in surface-syntax
|
||||
compatible form.
|
||||
/-- Here we record the number of parameters past the `:`. This is
|
||||
* `GuessLex` when there is no `termination_by` annotation, so that
|
||||
we can print the guessed order in the right form.
|
||||
* If there are fewer variables in the `termination_by` annotation than there are extra
|
||||
parameters, we know which parameters they should apply to (`TerminationBy.checkVars`).
|
||||
parameters, we know which parameters they should apply to.
|
||||
|
||||
It it set in `TerminationHints.checkVars`, which is the place where we also check that the user
|
||||
does not bind more extra parameters than present in the predefinition.
|
||||
-/
|
||||
extraParams : Nat
|
||||
deriving Inhabited
|
||||
@@ -75,32 +69,20 @@ def TerminationHints.ensureNone (hints : TerminationHints) (reason : String): Co
|
||||
| .some _, .some _ =>
|
||||
logErrorAt hints.ref m!"unused termination hints, function is {reason}"
|
||||
|
||||
/--
|
||||
Remembers `extraParams` for later use. Needs to happen early enough where we still know
|
||||
how many parameters came from the function header (`headerParams`).
|
||||
-/
|
||||
def TerminationHints.rememberExtraParams (headerParams : Nat) (hints : TerminationHints)
|
||||
(value : Expr) : TerminationHints :=
|
||||
{ hints with extraParams := value.getNumHeadLambdas - headerParams }
|
||||
|
||||
/--
|
||||
Checks that `termination_by` binds at most as many variables are present in the outermost
|
||||
lambda of `value`, and throws appropriate errors.
|
||||
lambda of `value`, and logs (without failing) appropriate errors.
|
||||
|
||||
Also remembers `extraParams` for later use.
|
||||
-/
|
||||
def TerminationBy.checkVars (funName : Name) (extraParams : Nat) (tb : TerminationBy) : MetaM Unit := do
|
||||
unless tb.synthetic do
|
||||
def TerminationHints.checkVars (headerParams : Nat) (hints : TerminationHints) (value : Expr) :
|
||||
MetaM TerminationHints := do
|
||||
let extraParams := value.getNumHeadLambdas - headerParams
|
||||
if let .some tb := hints.termination_by? then
|
||||
if tb.vars.size > extraParams then
|
||||
let mut msg := m!"{parameters tb.vars.size} bound in `termination_by`, but the body of " ++
|
||||
m!"{funName} only binds {parameters extraParams}."
|
||||
if let `($ident:ident) := tb.vars[0]! then
|
||||
if ident.getId.isSuffixOf funName then
|
||||
msg := msg ++ m!" (Since Lean v4.6.0, the `termination_by` clause no longer " ++
|
||||
"expects the function name here.)"
|
||||
throwErrorAt tb.ref msg
|
||||
where
|
||||
parameters : Nat → MessageData
|
||||
| 1 => "one parameter"
|
||||
| n => m!"{n} parameters"
|
||||
logErrorAt tb.ref <| m!"Too many extra parameters bound; the function definition only " ++
|
||||
m!"has {extraParams} extra parameters."
|
||||
return { hints with extraParams := extraParams }
|
||||
|
||||
open Parser.Termination
|
||||
|
||||
|
||||
@@ -491,10 +491,7 @@ mutual
|
||||
let valStx := valStx.setArg 2 (mkNullNode <| mkSepArray args (mkAtom ","))
|
||||
let valStx ← updateSource valStx
|
||||
return { field with lhs := [field.lhs.head!], val := FieldVal.term valStx }
|
||||
/--
|
||||
Adds in the missing fields using the explicit sources.
|
||||
Invariant: a missing field always comes from the first source that can provide it.
|
||||
-/
|
||||
|
||||
private partial def addMissingFields (s : Struct) : TermElabM Struct := do
|
||||
let env ← getEnv
|
||||
let fieldNames := getStructureFields env s.structName
|
||||
@@ -508,36 +505,13 @@ mutual
|
||||
return { ref, lhs := [FieldLHS.fieldName ref fieldName], val := val } :: fields
|
||||
match Lean.isSubobjectField? env s.structName fieldName with
|
||||
| some substructName =>
|
||||
-- Get all leaf fields of `substructName`
|
||||
let downFields := getStructureFieldsFlattened env substructName false
|
||||
-- Filter out all explicit sources that do not share a leaf field keeping
|
||||
-- structure with no fields
|
||||
let filtered := s.source.explicit.filter fun source =>
|
||||
let sourceFields := getStructureFieldsFlattened env source.structName false
|
||||
sourceFields.any (fun name => downFields.contains name) || sourceFields.isEmpty
|
||||
-- Take the first such one remaining
|
||||
match filtered[0]? with
|
||||
| some src =>
|
||||
-- If it is the correct type, use it
|
||||
if src.structName == substructName then
|
||||
addField (FieldVal.term src.stx)
|
||||
-- If a projection of it is the correct type, use it
|
||||
else if let some val ← mkProjStx? src.stx src.structName fieldName then
|
||||
addField (FieldVal.term val)
|
||||
-- No sources could provide this subobject in the proper order.
|
||||
-- Recurse to handle default values for fields.
|
||||
else
|
||||
let substruct := Struct.mk ref substructName #[] [] s.source
|
||||
let substruct ← expandStruct substruct
|
||||
addField (FieldVal.nested substruct)
|
||||
-- No sources could provide this subobject.
|
||||
-- Recurse to handle default values for fields.
|
||||
| none =>
|
||||
-- If one of the sources has the subobject field, use it
|
||||
if let some val ← s.source.explicit.findSomeM? fun source => mkProjStx? source.stx source.structName fieldName then
|
||||
addField (FieldVal.term val)
|
||||
else
|
||||
let substruct := Struct.mk ref substructName #[] [] s.source
|
||||
let substruct ← expandStruct substruct
|
||||
addField (FieldVal.nested substruct)
|
||||
-- Since this is not a subobject field, we are free to use the first source that can
|
||||
-- provide it.
|
||||
| none =>
|
||||
if let some val ← s.source.explicit.findSomeM? fun source => mkProjStx? source.stx source.structName fieldName then
|
||||
addField (FieldVal.term val)
|
||||
|
||||
@@ -82,7 +82,7 @@ end PatternMatchState
|
||||
|
||||
private def pre (pattern : AbstractMVarsResult) (state : IO.Ref PatternMatchState) (e : Expr) : SimpM Simp.Step := do
|
||||
if (← state.get).isDone then
|
||||
return Simp.Step.done { expr := e }
|
||||
return Simp.Step.visit { expr := e }
|
||||
else if let some (e, extraArgs) ← matchPattern? pattern e then
|
||||
if (← state.get).isReady then
|
||||
let (rhs, newGoal) ← mkConvGoalFor e
|
||||
@@ -97,9 +97,9 @@ private def pre (pattern : AbstractMVarsResult) (state : IO.Ref PatternMatchStat
|
||||
-- it is possible for skipping an earlier match to affect what later matches
|
||||
-- refer to. For example, matching `f _` in `f (f a) = f b` with occs `[1, 2]`
|
||||
-- yields `[f (f a), f b]`, but `[2, 3]` yields `[f a, f b]`, and `[1, 3]` is an error.
|
||||
return Simp.Step.continue
|
||||
return Simp.Step.visit { expr := e }
|
||||
else
|
||||
return Simp.Step.continue
|
||||
return Simp.Step.visit { expr := e }
|
||||
|
||||
@[builtin_tactic Lean.Parser.Tactic.Conv.pattern] def evalPattern : Tactic := fun stx => withMainContext do
|
||||
match stx with
|
||||
|
||||
@@ -79,7 +79,11 @@ namespace ElimApp
|
||||
structure Alt where
|
||||
/-- The short name of the alternative, used in `| foo =>` cases -/
|
||||
name : Name
|
||||
info : ElimAltInfo
|
||||
/-- A declaration corresponding to the inductive constructor.
|
||||
(For custom recursors, the alternatives correspond to parameter names in the
|
||||
recursor, so we may not have a declaration to point to.)
|
||||
This is used for go-to-definition on the alternative name. -/
|
||||
declName? : Option Name
|
||||
/-- The subgoal metavariable for the alternative. -/
|
||||
mvarId : MVarId
|
||||
deriving Inhabited
|
||||
@@ -91,7 +95,6 @@ structure Context where
|
||||
structure State where
|
||||
argPos : Nat := 0 -- current argument position
|
||||
targetPos : Nat := 0 -- current target at targetsStx
|
||||
motive : Option MVarId -- motive metavariable
|
||||
f : Expr
|
||||
fType : Expr
|
||||
alts : Array Alt := #[]
|
||||
@@ -114,7 +117,6 @@ private def getFType : M Expr := do
|
||||
|
||||
structure Result where
|
||||
elimApp : Expr
|
||||
motive : MVarId
|
||||
alts : Array Alt := #[]
|
||||
others : Array MVarId := #[]
|
||||
|
||||
@@ -132,13 +134,12 @@ partial def mkElimApp (elimInfo : ElimInfo) (targets : Array Expr) (tag : Name)
|
||||
let argPos := (← get).argPos
|
||||
if ctx.elimInfo.motivePos == argPos then
|
||||
let motive ← mkFreshExprMVar (← getArgExpectedType) MetavarKind.syntheticOpaque
|
||||
modify fun s => { s with motive := motive.mvarId! }
|
||||
addNewArg motive
|
||||
else if ctx.elimInfo.targetsPos.contains argPos then
|
||||
let s ← get
|
||||
let ctx ← read
|
||||
unless s.targetPos < ctx.targets.size do
|
||||
throwError "insufficient number of targets for '{elimInfo.elimExpr}'"
|
||||
throwError "insufficient number of targets for '{elimInfo.name}'"
|
||||
let target := ctx.targets[s.targetPos]!
|
||||
let expectedType ← getArgExpectedType
|
||||
let target ← withAssignableSyntheticOpaque <| Term.ensureHasType expectedType target
|
||||
@@ -159,14 +160,15 @@ partial def mkElimApp (elimInfo : ElimInfo) (targets : Array Expr) (tag : Name)
|
||||
let arg ← mkFreshExprSyntheticOpaqueMVar (← getArgExpectedType) (tag := appendTag tag binderName)
|
||||
let x ← getBindingName
|
||||
modify fun s =>
|
||||
let info := elimInfo.altsInfo[s.alts.size]!
|
||||
{ s with alts := s.alts.push ⟨x, info, arg.mvarId!⟩ }
|
||||
let declName? := elimInfo.altsInfo[s.alts.size]!.declName?
|
||||
{ s with alts := s.alts.push ⟨x, declName?, arg.mvarId!⟩ }
|
||||
addNewArg arg
|
||||
loop
|
||||
| _ =>
|
||||
pure ()
|
||||
let (_, s) ← (loop).run { elimInfo := elimInfo, targets := targets }
|
||||
|>.run { f := elimInfo.elimExpr, fType := elimInfo.elimType, motive := none }
|
||||
let f ← Term.mkConst elimInfo.name
|
||||
let fType ← inferType f
|
||||
let (_, s) ← (loop).run { elimInfo := elimInfo, targets := targets } |>.run { f := f, fType := fType }
|
||||
let mut others := #[]
|
||||
for mvarId in s.insts do
|
||||
try
|
||||
@@ -177,9 +179,7 @@ partial def mkElimApp (elimInfo : ElimInfo) (targets : Array Expr) (tag : Name)
|
||||
mvarId.setKind .syntheticOpaque
|
||||
others := others.push mvarId
|
||||
let alts ← s.alts.filterM fun alt => return !(← alt.mvarId.isAssigned)
|
||||
let some motive := s.motive |
|
||||
throwError "mkElimApp: motive not found"
|
||||
return { elimApp := (← instantiateMVars s.f), alts, others, motive }
|
||||
return { elimApp := (← instantiateMVars s.f), alts, others := others }
|
||||
|
||||
/-- Given a goal `... targets ... |- C[targets]` associated with `mvarId`, assign
|
||||
`motiveArg := fun targets => C[targets]` -/
|
||||
@@ -282,7 +282,7 @@ where
|
||||
let mut usedWildcard := false
|
||||
let mut subgoals := #[] -- when alternatives are not provided, we accumulate subgoals here
|
||||
let mut altsSyntax := altsSyntax
|
||||
for { name := altName, info, mvarId := altMVarId } in alts do
|
||||
for { name := altName, declName?, mvarId := altMVarId } in alts do
|
||||
let numFields ← getAltNumFields elimInfo altName
|
||||
let mut isWildcard := false
|
||||
let altStx? ←
|
||||
@@ -303,11 +303,7 @@ where
|
||||
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'
|
||||
(_, altMVarId) ← altMVarId'.introNP numGeneralized
|
||||
for fvarId in toClear do
|
||||
altMVarId ← altMVarId.tryClear fvarId
|
||||
altMVarId.withContext do
|
||||
@@ -333,7 +329,7 @@ where
|
||||
-- inside tacticInfo for the current alternative (in `evalAlt`)
|
||||
let addInfo : TermElabM Unit := do
|
||||
if (← getInfoState).enabled then
|
||||
if let some declName := info.declName? then
|
||||
if let some declName := declName? then
|
||||
addConstInfo (getAltNameStx altStx) declName
|
||||
saveAltVarsInfo altMVarId altStx fvarIds
|
||||
let unusedAlt := do
|
||||
@@ -345,11 +341,7 @@ where
|
||||
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'
|
||||
(_, altMVarId) ← altMVarId'.introNP numGeneralized
|
||||
for fvarId in toClear do
|
||||
altMVarId ← altMVarId.tryClear fvarId
|
||||
altMVarId.withContext do
|
||||
@@ -507,36 +499,11 @@ def getInductiveValFromMajor (major : Expr) : TacticM InductiveVal :=
|
||||
(fun _ => Meta.throwTacticEx `induction mvarId m!"major premise type is not an inductive type {indentExpr majorType}")
|
||||
(fun val _ => pure val)
|
||||
|
||||
/--
|
||||
Elaborates the term in the `using` clause. We want to allow parameters to be instantiated
|
||||
(e.g. `using foo (p := …)`), but preserve other paramters, like the motives, as parameters,
|
||||
without turning them into MVars. So this uses `abstractMVars` at the end. This is inspired by
|
||||
`Lean.Elab.Tactic.addSimpTheorem`.
|
||||
|
||||
It also elaborates without `heedElabAsElim` so that users can use constants that are marked
|
||||
`elabAsElim` in the `using` clause`.
|
||||
-/
|
||||
private def elabTermForElim (stx : Syntax) : TermElabM Expr := do
|
||||
-- Short-circuit elaborating plain identifiers
|
||||
if stx.isIdent then
|
||||
if let some e ← Term.resolveId? stx (withInfo := true) then
|
||||
return e
|
||||
Term.withoutErrToSorry <| Term.withoutHeedElabAsElim do
|
||||
let e ← Term.elabTerm stx none (implicitLambda := false)
|
||||
Term.synthesizeSyntheticMVars (mayPostpone := false) (ignoreStuckTC := true)
|
||||
let e ← instantiateMVars e
|
||||
let e := e.eta
|
||||
if e.hasMVar then
|
||||
let r ← abstractMVars (levels := false) e
|
||||
return r.expr
|
||||
else
|
||||
return e
|
||||
|
||||
-- `optElimId` is of the form `("using" term)?`
|
||||
-- `optElimId` is of the form `("using" ident)?`
|
||||
private def getElimNameInfo (optElimId : Syntax) (targets : Array Expr) (induction : Bool): TacticM ElimInfo := do
|
||||
if optElimId.isNone then
|
||||
if let some elimName ← getCustomEliminator? targets then
|
||||
return ← getElimInfo elimName
|
||||
if let some elimInfo ← getCustomEliminator? targets then
|
||||
return elimInfo
|
||||
unless targets.size == 1 do
|
||||
throwError "eliminator must be provided when multiple targets are used (use 'using <eliminator-name>'), and no default eliminator has been registered using attribute `[eliminator]`"
|
||||
let indVal ← getInductiveValFromMajor targets[0]!
|
||||
@@ -547,17 +514,12 @@ private def getElimNameInfo (optElimId : Syntax) (targets : Array Expr) (inducti
|
||||
let elimName := if induction then mkRecName indVal.name else mkCasesOnName indVal.name
|
||||
getElimInfo elimName indVal.name
|
||||
else
|
||||
let elimTerm := optElimId[1]
|
||||
let elimExpr ← withRef elimTerm do elabTermForElim elimTerm
|
||||
let elimId := optElimId[1]
|
||||
let elimName ← withRef elimId do resolveGlobalConstNoOverloadWithInfo elimId
|
||||
-- not a precise check, but covers the common cases of T.recOn / T.casesOn
|
||||
-- as well as user defined T.myInductionOn to locate the constructors of T
|
||||
let baseName? ← do
|
||||
let some elimName := elimExpr.getAppFn.constName? | pure none
|
||||
if ← isInductive elimName.getPrefix then
|
||||
pure (some elimName.getPrefix)
|
||||
else
|
||||
pure none
|
||||
withRef elimTerm <| getElimExprInfo elimExpr baseName?
|
||||
let baseName? := if ← isInductive elimName.getPrefix then some elimName.getPrefix else none
|
||||
withRef elimId <| getElimInfo elimName baseName?
|
||||
|
||||
private def shouldGeneralizeTarget (e : Expr) : MetaM Bool := do
|
||||
if let .fvar fvarId .. := e then
|
||||
@@ -595,7 +557,8 @@ private def generalizeTargets (exprs : Array Expr) : TacticM (Array Expr) := do
|
||||
let result ← withRef stx[1] do -- use target position as reference
|
||||
ElimApp.mkElimApp elimInfo targets tag
|
||||
trace[Elab.induction] "elimApp: {result.elimApp}"
|
||||
ElimApp.setMotiveArg mvarId result.motive targetFVarIds
|
||||
let elimArgs := result.elimApp.getAppArgs
|
||||
ElimApp.setMotiveArg mvarId elimArgs[elimInfo.motivePos]!.mvarId! targetFVarIds
|
||||
let optPreTac := getOptPreTacOfOptInductionAlts optInductionAlts
|
||||
mvarId.assign result.elimApp
|
||||
ElimApp.evalAlts elimInfo result.alts optPreTac alts initInfo (numGeneralized := n) (toClear := targetFVarIds)
|
||||
|
||||
@@ -130,28 +130,21 @@ private def addSimpTheorem (thms : SimpTheorems) (id : Origin) (stx : Syntax) (p
|
||||
|
||||
structure ElabSimpArgsResult where
|
||||
ctx : Simp.Context
|
||||
simprocs : Simp.SimprocsArray
|
||||
simprocs : Simprocs
|
||||
starArg : Bool := false
|
||||
|
||||
inductive ResolveSimpIdResult where
|
||||
| none
|
||||
| expr (e : Expr)
|
||||
| simproc (declName : Name)
|
||||
/--
|
||||
Recall that when we declare a `simp` attribute using `register_simp_attr`, we automatically
|
||||
create a `simproc` attribute. However, if the user creates `simp` and `simproc` attributes
|
||||
programmatically, then one of them may be missing. Moreover, when we write `simp [seval]`,
|
||||
we want to retrieve both the simp and simproc sets. We want to hide from users that
|
||||
`simp` and `simproc` sets are stored in different data-structures.
|
||||
-/
|
||||
| ext (ext₁? : Option SimpExtension) (ext₂? : Option Simp.SimprocExtension) (h : ext₁?.isSome || ext₂?.isSome)
|
||||
| ext (ext : SimpExtension)
|
||||
|
||||
/--
|
||||
Elaborate extra simp theorems provided to `simp`. `stx` is of the form `"[" simpTheorem,* "]"`
|
||||
If `eraseLocal == true`, then we consider local declarations when resolving names for erased theorems (`- id`),
|
||||
this option only makes sense for `simp_all` or `*` is used.
|
||||
-/
|
||||
def elabSimpArgs (stx : Syntax) (ctx : Simp.Context) (simprocs : Simp.SimprocsArray) (eraseLocal : Bool) (kind : SimpKind) : TacticM ElabSimpArgsResult := do
|
||||
def elabSimpArgs (stx : Syntax) (ctx : Simp.Context) (simprocs : Simprocs) (eraseLocal : Bool) (kind : SimpKind) : TacticM ElabSimpArgsResult := do
|
||||
if stx.isNone then
|
||||
return { ctx, simprocs }
|
||||
else
|
||||
@@ -195,13 +188,8 @@ def elabSimpArgs (stx : Syntax) (ctx : Simp.Context) (simprocs : Simp.SimprocsAr
|
||||
thms ← addDeclToUnfoldOrTheorem thms (.stx name arg) e post inv kind
|
||||
| .simproc declName =>
|
||||
simprocs ← simprocs.add declName post
|
||||
| .ext (some ext₁) (some ext₂) _ =>
|
||||
thmsArray := thmsArray.push (← ext₁.getTheorems)
|
||||
simprocs := simprocs.push (← ext₂.getSimprocs)
|
||||
| .ext (some ext₁) none _ =>
|
||||
thmsArray := thmsArray.push (← ext₁.getTheorems)
|
||||
| .ext none (some ext₂) _ =>
|
||||
simprocs := simprocs.push (← ext₂.getSimprocs)
|
||||
| .ext ext =>
|
||||
thmsArray := thmsArray.push (← ext.getTheorems)
|
||||
| .none =>
|
||||
let name ← mkFreshId
|
||||
thms ← addSimpTheorem thms (.stx name arg) term post inv
|
||||
@@ -218,10 +206,8 @@ where
|
||||
|
||||
resolveSimpIdTheorem? (simpArgTerm : Term) : TacticM ResolveSimpIdResult := do
|
||||
let resolveExt (n : Name) : TacticM ResolveSimpIdResult := do
|
||||
let ext₁? ← getSimpExtension? n
|
||||
let ext₂? ← Simp.getSimprocExtension? n
|
||||
if h : ext₁?.isSome || ext₂?.isSome then
|
||||
return .ext ext₁? ext₂? h
|
||||
if let some ext ← getSimpExtension? n then
|
||||
return .ext ext
|
||||
else
|
||||
return .none
|
||||
match simpArgTerm with
|
||||
@@ -250,7 +236,7 @@ where
|
||||
|
||||
structure MkSimpContextResult where
|
||||
ctx : Simp.Context
|
||||
simprocs : Simp.SimprocsArray
|
||||
simprocs : Simprocs
|
||||
dischargeWrapper : Simp.DischargeWrapper
|
||||
|
||||
/--
|
||||
@@ -273,7 +259,7 @@ def mkSimpContext (stx : Syntax) (eraseLocal : Bool) (kind := SimpKind.simp) (ig
|
||||
getSimpTheorems
|
||||
let simprocs ← if simpOnly then pure {} else Simp.getSimprocs
|
||||
let congrTheorems ← getSimpCongrTheorems
|
||||
let r ← elabSimpArgs stx[4] (eraseLocal := eraseLocal) (kind := kind) (simprocs := #[simprocs]) {
|
||||
let r ← elabSimpArgs stx[4] (eraseLocal := eraseLocal) (kind := kind) (simprocs := simprocs) {
|
||||
config := (← elabSimpConfig stx[1] (kind := kind))
|
||||
simpTheorems := #[simpTheorems], congrTheorems
|
||||
}
|
||||
@@ -375,7 +361,7 @@ For many tactics other than the simplifier,
|
||||
one should use the `withLocation` tactic combinator
|
||||
when working with a `location`.
|
||||
-/
|
||||
def simpLocation (ctx : Simp.Context) (simprocs : Simp.SimprocsArray) (discharge? : Option Simp.Discharge := none) (loc : Location) : TacticM UsedSimps := do
|
||||
def simpLocation (ctx : Simp.Context) (simprocs : Simprocs) (discharge? : Option Simp.Discharge := none) (loc : Location) : TacticM UsedSimps := do
|
||||
match loc with
|
||||
| Location.targets hyps simplifyTarget =>
|
||||
withMainContext do
|
||||
@@ -394,8 +380,7 @@ where
|
||||
return usedSimps
|
||||
|
||||
/-
|
||||
"simp" (config)? (discharger)? (" only")? (" [" ((simpStar <|> simpErase <|> simpLemma),*,?) "]")?
|
||||
(location)?
|
||||
"simp " (config)? (discharger)? ("only ")? ("[" simpLemma,* "]")? (location)?
|
||||
-/
|
||||
@[builtin_tactic Lean.Parser.Tactic.simp] def evalSimp : Tactic := fun stx => withMainContext do
|
||||
let { ctx, simprocs, dischargeWrapper } ← mkSimpContext stx (eraseLocal := false)
|
||||
|
||||
@@ -52,4 +52,34 @@ namespace Command
|
||||
|
||||
end Command
|
||||
|
||||
builtin_initialize
|
||||
registerBuiltinAttribute {
|
||||
ref := by exact decl_name%
|
||||
name := `simprocAttr
|
||||
descr := "Simplification procedure"
|
||||
erase := eraseSimprocAttr
|
||||
add := fun declName stx attrKind => do
|
||||
let go : MetaM Unit := do
|
||||
let post := if stx[1].isNone then true else stx[1][0].getKind == ``Lean.Parser.Tactic.simpPost
|
||||
addSimprocAttr declName attrKind post
|
||||
go.run' {}
|
||||
applicationTime := AttributeApplicationTime.afterCompilation
|
||||
}
|
||||
|
||||
builtin_initialize
|
||||
registerBuiltinAttribute {
|
||||
ref := by exact decl_name%
|
||||
name := `simprocBuiltinAttr
|
||||
descr := "Builtin simplification procedure"
|
||||
erase := eraseSimprocAttr
|
||||
add := fun declName stx _ => do
|
||||
let go : MetaM Unit := do
|
||||
let post := if stx[1].isNone then true else stx[1][0].getKind == ``Lean.Parser.Tactic.simpPost
|
||||
let val := mkAppN (mkConst ``addSimprocBuiltinAttr) #[toExpr declName, toExpr post, mkConst declName]
|
||||
let initDeclName ← mkFreshUserName (declName ++ `declare)
|
||||
declareBuiltin initDeclName val
|
||||
go.run' {}
|
||||
applicationTime := AttributeApplicationTime.afterCompilation
|
||||
}
|
||||
|
||||
end Lean.Elab
|
||||
|
||||
@@ -198,8 +198,6 @@ structure Context where
|
||||
sectionFVars : NameMap Expr := {}
|
||||
/-- Enable/disable implicit lambdas feature. -/
|
||||
implicitLambda : Bool := true
|
||||
/-- Heed `elab_as_elim` attribute. -/
|
||||
heedElabAsElim : Bool := true
|
||||
/-- Noncomputable sections automatically add the `noncomputable` modifier to any declaration we cannot generate code for. -/
|
||||
isNoncomputableSection : Bool := false
|
||||
/-- When `true` we skip TC failures. We use this option when processing patterns. -/
|
||||
@@ -329,6 +327,33 @@ instance : AddErrorMessageContext TermElabM where
|
||||
let msg ← addMacroStack msg ctx.macroStack
|
||||
pure (ref, msg)
|
||||
|
||||
/--
|
||||
Execute `x` but discard changes performed at `Term.State` and `Meta.State`.
|
||||
Recall that the `Environment` and `InfoState` are at `Core.State`. Thus, any updates to it will
|
||||
be preserved. This method is useful for performing computations where all
|
||||
metavariable must be resolved or discarded.
|
||||
The `InfoTree`s are not discarded, however, and wrapped in `InfoTree.Context`
|
||||
to store their metavariable context. -/
|
||||
def withoutModifyingElabMetaStateWithInfo (x : TermElabM α) : TermElabM α := do
|
||||
let s ← get
|
||||
let sMeta ← getThe Meta.State
|
||||
try
|
||||
withSaveInfoContext x
|
||||
finally
|
||||
set s
|
||||
set sMeta
|
||||
|
||||
/--
|
||||
Execute `x` but discard changes performed to the state.
|
||||
However, the info trees and messages are not discarded. -/
|
||||
private def withoutModifyingStateWithInfoAndMessagesImpl (x : TermElabM α) : TermElabM α := do
|
||||
let saved ← saveState
|
||||
try
|
||||
withSaveInfoContext x
|
||||
finally
|
||||
let saved := { saved with meta.core.infoState := (← getInfoState), meta.core.messages := (← getThe Core.State).messages }
|
||||
restoreState saved
|
||||
|
||||
/--
|
||||
Execute `x` without storing `Syntax` for recursive applications. See `saveRecAppSyntax` field at `Context`.
|
||||
-/
|
||||
@@ -375,12 +400,9 @@ def getLetRecsToLift : TermElabM (List LetRecToLift) := return (← get).letRecs
|
||||
/-- Return the declaration of the given metavariable -/
|
||||
def getMVarDecl (mvarId : MVarId) : TermElabM MetavarDecl := return (← getMCtx).getDecl mvarId
|
||||
|
||||
instance : MonadParentDecl TermElabM where
|
||||
getParentDeclName? := getDeclName?
|
||||
|
||||
/-- Execute `withSaveParentDeclInfoContext x` with `declName? := name`. See `getDeclName?`. -/
|
||||
/-- Execute `x` with `declName? := name`. See `getDeclName?`. -/
|
||||
def withDeclName (name : Name) (x : TermElabM α) : TermElabM α :=
|
||||
withReader (fun ctx => { ctx with declName? := name }) <| withSaveParentDeclInfoContext x
|
||||
withReader (fun ctx => { ctx with declName? := name }) x
|
||||
|
||||
/-- Update the universe level parameter names. -/
|
||||
def setLevelNames (levelNames : List Name) : TermElabM Unit :=
|
||||
@@ -411,44 +433,6 @@ def withoutErrToSorryImp (x : TermElabM α) : TermElabM α :=
|
||||
def withoutErrToSorry [MonadFunctorT TermElabM m] : m α → m α :=
|
||||
monadMap (m := TermElabM) withoutErrToSorryImp
|
||||
|
||||
def withoutHeedElabAsElimImp (x : TermElabM α) : TermElabM α :=
|
||||
withReader (fun ctx => { ctx with heedElabAsElim := false }) x
|
||||
|
||||
/--
|
||||
Execute `x` without heeding the `elab_as_elim` attribute. Useful when there is
|
||||
no expected type (so `elabAppArgs` would fail), but expect that the user wants
|
||||
to use such constants.
|
||||
-/
|
||||
def withoutHeedElabAsElim [MonadFunctorT TermElabM m] : m α → m α :=
|
||||
monadMap (m := TermElabM) withoutHeedElabAsElimImp
|
||||
|
||||
/--
|
||||
Execute `x` but discard changes performed at `Term.State` and `Meta.State`.
|
||||
Recall that the `Environment` and `InfoState` are at `Core.State`. Thus, any updates to it will
|
||||
be preserved. This method is useful for performing computations where all
|
||||
metavariable must be resolved or discarded.
|
||||
The `InfoTree`s are not discarded, however, and wrapped in `InfoTree.Context`
|
||||
to store their metavariable context. -/
|
||||
def withoutModifyingElabMetaStateWithInfo (x : TermElabM α) : TermElabM α := do
|
||||
let s ← get
|
||||
let sMeta ← getThe Meta.State
|
||||
try
|
||||
withSaveInfoContext x
|
||||
finally
|
||||
set s
|
||||
set sMeta
|
||||
|
||||
/--
|
||||
Execute `x` but discard changes performed to the state.
|
||||
However, the info trees and messages are not discarded. -/
|
||||
private def withoutModifyingStateWithInfoAndMessagesImpl (x : TermElabM α) : TermElabM α := do
|
||||
let saved ← saveState
|
||||
try
|
||||
withSaveInfoContext x
|
||||
finally
|
||||
let saved := { saved with meta.core.infoState := (← getInfoState), meta.core.messages := (← getThe Core.State).messages }
|
||||
restoreState saved
|
||||
|
||||
/-- For testing `TermElabM` methods. The #eval command will sign the error. -/
|
||||
def throwErrorIfErrors : TermElabM Unit := do
|
||||
if (← MonadLog.hasErrors) then
|
||||
|
||||
@@ -16,7 +16,7 @@ inductive Exception where
|
||||
| error (ref : Syntax) (msg : MessageData)
|
||||
/--
|
||||
Internal exceptions that are not meant to be seen by users.
|
||||
Examples: "postpone elaboration", "stuck at universe constraint", etc.
|
||||
Examples: "pospone elaboration", "stuck at universe constraint", etc
|
||||
-/
|
||||
| internal (id : InternalExceptionId) (extra : KVMap := {})
|
||||
|
||||
|
||||
@@ -408,7 +408,7 @@ inductive Expr where
|
||||
|
||||
Given an environment, a metavariable context, and a local context,
|
||||
we say a let-expression `let x : t := v; e` is non-dependent when it is equivalent
|
||||
to `(fun x : t => e) v`. In contrast, the dependent let-expression
|
||||
to `(fun x : t => e) v`. Here is an example of a dependent let-expression
|
||||
`let n : Nat := 2; fun (a : Array Nat n) (b : Array Nat 2) => a = b` is type correct,
|
||||
but `(fun (n : Nat) (a : Array Nat n) (b : Array Nat 2) => a = b) 2` is not.
|
||||
|
||||
@@ -655,7 +655,7 @@ def mkProj (structName : Name) (idx : Nat) (struct : Expr) : Expr :=
|
||||
/--
|
||||
`.app f a` is now the preferred form.
|
||||
-/
|
||||
@[match_pattern] def mkApp (f a : Expr) : Expr :=
|
||||
def mkApp (f a : Expr) : Expr :=
|
||||
.app f a
|
||||
|
||||
/--
|
||||
@@ -684,16 +684,16 @@ def mkSimpleThunk (type : Expr) : Expr :=
|
||||
def mkLet (x : Name) (t : Expr) (v : Expr) (b : Expr) (nonDep : Bool := false) : Expr :=
|
||||
.letE x t v b nonDep
|
||||
|
||||
@[match_pattern] def mkAppB (f a b : Expr) := mkApp (mkApp f a) b
|
||||
@[match_pattern] def mkApp2 (f a b : Expr) := mkAppB f a b
|
||||
@[match_pattern] def mkApp3 (f a b c : Expr) := mkApp (mkAppB f a b) c
|
||||
@[match_pattern] def mkApp4 (f a b c d : Expr) := mkAppB (mkAppB f a b) c d
|
||||
@[match_pattern] def mkApp5 (f a b c d e : Expr) := mkApp (mkApp4 f a b c d) e
|
||||
@[match_pattern] def mkApp6 (f a b c d e₁ e₂ : Expr) := mkAppB (mkApp4 f a b c d) e₁ e₂
|
||||
@[match_pattern] def mkApp7 (f a b c d e₁ e₂ e₃ : Expr) := mkApp3 (mkApp4 f a b c d) e₁ e₂ e₃
|
||||
@[match_pattern] def mkApp8 (f a b c d e₁ e₂ e₃ e₄ : Expr) := mkApp4 (mkApp4 f a b c d) e₁ e₂ e₃ e₄
|
||||
@[match_pattern] def mkApp9 (f a b c d e₁ e₂ e₃ e₄ e₅ : Expr) := mkApp5 (mkApp4 f a b c d) e₁ e₂ e₃ e₄ e₅
|
||||
@[match_pattern] def mkApp10 (f a b c d e₁ e₂ e₃ e₄ e₅ e₆ : Expr) := mkApp6 (mkApp4 f a b c d) e₁ e₂ e₃ e₄ e₅ e₆
|
||||
def mkAppB (f a b : Expr) := mkApp (mkApp f a) b
|
||||
def mkApp2 (f a b : Expr) := mkAppB f a b
|
||||
def mkApp3 (f a b c : Expr) := mkApp (mkAppB f a b) c
|
||||
def mkApp4 (f a b c d : Expr) := mkAppB (mkAppB f a b) c d
|
||||
def mkApp5 (f a b c d e : Expr) := mkApp (mkApp4 f a b c d) e
|
||||
def mkApp6 (f a b c d e₁ e₂ : Expr) := mkAppB (mkApp4 f a b c d) e₁ e₂
|
||||
def mkApp7 (f a b c d e₁ e₂ e₃ : Expr) := mkApp3 (mkApp4 f a b c d) e₁ e₂ e₃
|
||||
def mkApp8 (f a b c d e₁ e₂ e₃ e₄ : Expr) := mkApp4 (mkApp4 f a b c d) e₁ e₂ e₃ e₄
|
||||
def mkApp9 (f a b c d e₁ e₂ e₃ e₄ e₅ : Expr) := mkApp5 (mkApp4 f a b c d) e₁ e₂ e₃ e₄ e₅
|
||||
def mkApp10 (f a b c d e₁ e₂ e₃ e₄ e₅ e₆ : Expr) := mkApp6 (mkApp4 f a b c d) e₁ e₂ e₃ e₄ e₅ e₆
|
||||
|
||||
/--
|
||||
`.lit l` is now the preferred form.
|
||||
@@ -735,9 +735,7 @@ def mkStrLit (s : String) : Expr :=
|
||||
@[export lean_expr_mk_mdata] def mkMDataEx : MData → Expr → Expr := mkMData
|
||||
@[export lean_expr_mk_proj] def mkProjEx : Name → Nat → Expr → Expr := mkProj
|
||||
|
||||
/--
|
||||
`mkAppN f #[a₀, ..., aₙ]` constructs the application `f a₀ a₁ ... aₙ`.
|
||||
-/
|
||||
/-- `mkAppN f #[a₀, ..., aₙ]` ==> `f a₀ a₁ .. aₙ`-/
|
||||
def mkAppN (f : Expr) (args : Array Expr) : Expr :=
|
||||
args.foldl mkApp f
|
||||
|
||||
@@ -1229,84 +1227,32 @@ def inferImplicit : Expr → Nat → Bool → Expr
|
||||
| e, _, _ => e
|
||||
|
||||
/--
|
||||
Instantiates the loose bound variables in `e` using the `subst` array,
|
||||
where a loose `Expr.bvar i` at "binding depth" `d` is instantiated with `subst[i - d]` if `0 <= i - d < subst.size`,
|
||||
and otherwise it is replaced with `Expr.bvar (i - subst.size)`; non-loose bound variables are not touched.
|
||||
|
||||
If we imagine all expressions as being able to refer to the infinite list of loose bound variables ..., 3, 2, 1, 0 in that order,
|
||||
then conceptually `instantiate` is instantiating the last `n` of these and reindexing the remaining ones.
|
||||
Warning: `instantiate` uses the de Bruijn indexing to index the `subst` array, which might be the reverse order from what you might expect.
|
||||
See also `Lean.Expr.instantiateRev`.
|
||||
|
||||
**Terminology.** The "binding depth" of a subexpression is the number of bound variables available to that subexpression
|
||||
by virtue of being in the bodies of `Expr.forallE`, `Expr.lam`, and `Expr.letE` expressions.
|
||||
A bound variable `Expr.bvar i` is "loose" if its de Bruijn index `i` is not less than its binding depth.)
|
||||
|
||||
**About instantiation.** Instantiation isn't mere substitution.
|
||||
When an expression from `subst` is being instantiated, its internal loose bound variables have their de Bruijn indices incremented
|
||||
by the binding depth of the replaced loose bound variable.
|
||||
This is necessary for the substituted expression to still refer to the correct binders after instantiation.
|
||||
Similarly, the reason loose bound variables not instantiated using `subst` have their de Bruijn indices decremented like `Expr.bvar (i - subst.size)`
|
||||
is that `instantiate` can be used to eliminate binding expressions internal to a larger expression,
|
||||
and this adjustment keeps these bound variables referring to the same binders.
|
||||
Instantiate the loose bound variables in `e` using `subst`.
|
||||
That is, a loose `Expr.bvar i` is replaced with `subst[i]`.
|
||||
-/
|
||||
@[extern "lean_expr_instantiate"]
|
||||
opaque instantiate (e : @& Expr) (subst : @& Array Expr) : Expr
|
||||
|
||||
/--
|
||||
Instantiates loose bound variable `0` in `e` using the expression `subst`,
|
||||
where in particular a loose `Expr.bvar i` at binding depth `d` is instantiated with `subst` if `i = d`,
|
||||
and otherwise it is replaced with `Expr.bvar (i - 1)`; non-loose bound variables are not touched.
|
||||
|
||||
If we imagine all expressions as being able to refer to the infinite list of loose bound variables ..., 3, 2, 1, 0 in that order,
|
||||
then conceptually `instantiate1` is instantiating the last one of these and reindexing the remaining ones.
|
||||
|
||||
This function is equivalent to `instantiate e #[subst]`, but it avoids allocating an array.
|
||||
|
||||
See the documentation for `Lean.Expr.instantiate` for a description of instantiation.
|
||||
In short, during instantiation the loose bound variables in `subst` have their own de Bruijn indices updated to account
|
||||
for the binding depth of the replaced loose bound variable.
|
||||
-/
|
||||
@[extern "lean_expr_instantiate1"]
|
||||
opaque instantiate1 (e : @& Expr) (subst : @& Expr) : Expr
|
||||
|
||||
/--
|
||||
Instantiates the loose bound variables in `e` using the `subst` array.
|
||||
This is equivalent to `Lean.Expr.instantiate e subst.reverse`, but it avoids reversing the array.
|
||||
In particular, rather than instantiating `Expr.bvar i` with `subst[i - d]` it instantiates with `subst[subst.size - 1 - (i - d)]`,
|
||||
where `d` is the binding depth.
|
||||
|
||||
This function instantiates with the "forwards" indexing scheme.
|
||||
For example, if `e` represents the expression `fun x y => x + y`,
|
||||
then `instantiateRev e.bindingBody!.bindingBody! #[a, b]` yields `a + b`.
|
||||
The `instantiate` function on the other hand would yield `b + a`, since de Bruijn indices count outwards.
|
||||
-/
|
||||
/-- Similar to instantiate, but `Expr.bvar i` is replaced with `subst[subst.size - i - 1]` -/
|
||||
@[extern "lean_expr_instantiate_rev"]
|
||||
opaque instantiateRev (e : @& Expr) (subst : @& Array Expr) : Expr
|
||||
|
||||
/--
|
||||
Similar to `Lean.Expr.instantiate`, but considers only the substitutions `subst` in the range `[beginIdx, endIdx)`.
|
||||
Function panics if `beginIdx <= endIdx <= subst.size` does not hold.
|
||||
|
||||
This function is equivalent to `instantiate e (subst.extract beginIdx endIdx)`, but it does not allocate a new array.
|
||||
|
||||
This instantiates with the "backwards" indexing scheme.
|
||||
See also `Lean.Expr.instantiateRevRange`, which instantiates with the "forwards" indexing scheme.
|
||||
Similar to `instantiate`, but consider only the variables `xs` in the range `[beginIdx, endIdx)`.
|
||||
Function panics if `beginIdx <= endIdx <= xs.size` does not hold.
|
||||
-/
|
||||
@[extern "lean_expr_instantiate_range"]
|
||||
opaque instantiateRange (e : @& Expr) (beginIdx endIdx : @& Nat) (subst : @& Array Expr) : Expr
|
||||
opaque instantiateRange (e : @& Expr) (beginIdx endIdx : @& Nat) (xs : @& Array Expr) : Expr
|
||||
|
||||
/--
|
||||
Similar to `Lean.Expr.instantiateRev`, but considers only the substitutions `subst` in the range `[beginIdx, endIdx)`.
|
||||
Function panics if `beginIdx <= endIdx <= subst.size` does not hold.
|
||||
|
||||
This function is equivalent to `instantiateRev e (subst.extract beginIdx endIdx)`, but it does not allocate a new array.
|
||||
|
||||
This instantiates with the "forwards" indexing scheme (see the docstring for `Lean.Expr.instantiateRev` for an example).
|
||||
See also `Lean.Expr.instantiateRange`, which instantiates with the "backwards" indexing scheme.
|
||||
Similar to `instantiateRev`, but consider only the variables `xs` in the range `[beginIdx, endIdx)`.
|
||||
Function panics if `beginIdx <= endIdx <= xs.size` does not hold.
|
||||
-/
|
||||
@[extern "lean_expr_instantiate_rev_range"]
|
||||
opaque instantiateRevRange (e : @& Expr) (beginIdx endIdx : @& Nat) (subst : @& Array Expr) : Expr
|
||||
opaque instantiateRevRange (e : @& Expr) (beginIdx endIdx : @& Nat) (xs : @& Array Expr) : Expr
|
||||
|
||||
/-- Replace free (or meta) variables `xs` with loose bound variables. -/
|
||||
@[extern "lean_expr_abstract"]
|
||||
|
||||
@@ -4,7 +4,7 @@ Released under Apache 2.0 license as described in the file LICENSE.
|
||||
|
||||
Authors: Mario Carneiro
|
||||
-/
|
||||
import Lean.Meta.Tactic.Simp.RegisterCommand
|
||||
import Lean.Meta.Tactic.Simp.SimpTheorems
|
||||
import Lean.Elab.Command
|
||||
import Lean.Elab.SetOption
|
||||
import Lean.Linter.Util
|
||||
|
||||
@@ -25,59 +25,15 @@ def getLinterUnusedVariablesPatternVars (o : Options) : Bool := o.get linter.unu
|
||||
|
||||
abbrev IgnoreFunction := Syntax → Syntax.Stack → Options → Bool
|
||||
|
||||
unsafe def mkIgnoreFnImpl (constName : Name) : ImportM IgnoreFunction := do
|
||||
let { env, opts, .. } ← read
|
||||
match env.find? constName with
|
||||
| none => throw ↑s!"unknown constant '{constName}'"
|
||||
| some info =>
|
||||
unless info.type.isConstOf ``IgnoreFunction do
|
||||
throw ↑s!"unexpected unused_variables_ignore_fn at '{constName}', must be of type `Lean.Linter.IgnoreFunction`"
|
||||
IO.ofExcept <| env.evalConst IgnoreFunction opts constName
|
||||
|
||||
@[implemented_by mkIgnoreFnImpl]
|
||||
opaque mkIgnoreFn (constName : Name) : ImportM IgnoreFunction
|
||||
|
||||
builtin_initialize builtinUnusedVariablesIgnoreFnsRef : IO.Ref <| Array IgnoreFunction ← IO.mkRef #[]
|
||||
|
||||
def addBuiltinUnusedVariablesIgnoreFn (h : IgnoreFunction) : IO Unit :=
|
||||
builtinUnusedVariablesIgnoreFnsRef.modify (·.push h)
|
||||
def addBuiltinUnusedVariablesIgnoreFn (ignoreFn : IgnoreFunction) : IO Unit := do
|
||||
(← builtinUnusedVariablesIgnoreFnsRef.get) |> (·.push ignoreFn) |> builtinUnusedVariablesIgnoreFnsRef.set
|
||||
|
||||
builtin_initialize unusedVariablesIgnoreFnsExt :
|
||||
PersistentEnvExtension Name (Name × IgnoreFunction) (List Name × Array IgnoreFunction) ←
|
||||
registerPersistentEnvExtension {
|
||||
mkInitial := return ([], ← builtinUnusedVariablesIgnoreFnsRef.get)
|
||||
addImportedFn := fun as => do
|
||||
([], ·) <$> as.foldlM (init := ← builtinUnusedVariablesIgnoreFnsRef.get) fun s as =>
|
||||
as.foldlM (init := s) fun s n => s.push <$> mkIgnoreFn n
|
||||
addEntryFn := fun (entries, s) (n, h) => (n::entries, s.push h)
|
||||
exportEntriesFn := fun s => s.1.reverse.toArray
|
||||
statsFn := fun s => format "number of local entries: " ++ format s.1.length
|
||||
}
|
||||
|
||||
builtin_initialize
|
||||
let mkAttr (builtin : Bool) (name : Name) := registerBuiltinAttribute {
|
||||
name
|
||||
descr := (if builtin then "(builtin) " else "") ++
|
||||
"Marks a function of type `Lean.Linter.IgnoreFunction` for suppressing unused variable warnings"
|
||||
applicationTime := .afterCompilation
|
||||
add := fun decl stx kind => do
|
||||
Attribute.Builtin.ensureNoArgs stx
|
||||
unless kind == AttributeKind.global do throwError "invalid attribute '{name}', must be global"
|
||||
unless (← getConstInfo decl).type.isConstOf ``IgnoreFunction do
|
||||
throwError "invalid attribute '{name}', must be of type `Lean.Linter.IgnoreFunction`"
|
||||
let env ← getEnv
|
||||
if builtin then
|
||||
let h := mkConst decl
|
||||
declareBuiltin decl <| mkApp (mkConst ``addBuiltinUnusedVariablesIgnoreFn) h
|
||||
else
|
||||
setEnv <| unusedVariablesIgnoreFnsExt.addEntry env (decl, ← mkIgnoreFn decl)
|
||||
}
|
||||
mkAttr true `builtin_unused_variables_ignore_fn
|
||||
mkAttr false `unused_variables_ignore_fn
|
||||
|
||||
-- matches builtinUnused variable pattern
|
||||
builtin_initialize addBuiltinUnusedVariablesIgnoreFn fun stx _ _ =>
|
||||
stx.getId.toString.startsWith "_"
|
||||
builtin_initialize addBuiltinUnusedVariablesIgnoreFn (fun stx _ _ =>
|
||||
stx.getId.toString.startsWith "_")
|
||||
|
||||
-- is variable
|
||||
builtin_initialize addBuiltinUnusedVariablesIgnoreFn (fun _ stack _ =>
|
||||
@@ -149,8 +105,29 @@ builtin_initialize addBuiltinUnusedVariablesIgnoreFn (fun _ stack opts =>
|
||||
(stx.isOfKind ``Lean.Parser.Term.matchAlt && pos == 1) ||
|
||||
(stx.isOfKind ``Lean.Parser.Tactic.inductionAltLHS && pos == 2))
|
||||
|
||||
builtin_initialize unusedVariablesIgnoreFnsExt : SimplePersistentEnvExtension Name Unit ←
|
||||
registerSimplePersistentEnvExtension {
|
||||
addEntryFn := fun _ _ => ()
|
||||
addImportedFn := fun _ => ()
|
||||
}
|
||||
|
||||
builtin_initialize
|
||||
registerBuiltinAttribute {
|
||||
name := `unused_variables_ignore_fn
|
||||
descr := "Marks a function of type `Lean.Linter.IgnoreFunction` for suppressing unused variable warnings"
|
||||
add := fun decl stx kind => do
|
||||
Attribute.Builtin.ensureNoArgs stx
|
||||
unless kind == AttributeKind.global do throwError "invalid attribute 'unused_variables_ignore_fn', must be global"
|
||||
unless (← getConstInfo decl).type.isConstOf ``IgnoreFunction do
|
||||
throwError "invalid attribute 'unused_variables_ignore_fn', must be of type `Lean.Linter.IgnoreFunction`"
|
||||
let env ← getEnv
|
||||
setEnv <| unusedVariablesIgnoreFnsExt.addEntry env decl
|
||||
}
|
||||
|
||||
unsafe def getUnusedVariablesIgnoreFnsImpl : CommandElabM (Array IgnoreFunction) := do
|
||||
return (unusedVariablesIgnoreFnsExt.getState (← getEnv)).2
|
||||
let ents := unusedVariablesIgnoreFnsExt.getEntries (← getEnv)
|
||||
let ents ← ents.mapM (evalConstCheck IgnoreFunction ``IgnoreFunction)
|
||||
return (← builtinUnusedVariablesIgnoreFnsRef.get) ++ ents
|
||||
|
||||
@[implemented_by getUnusedVariablesIgnoreFnsImpl]
|
||||
opaque getUnusedVariablesIgnoreFns : CommandElabM (Array IgnoreFunction)
|
||||
@@ -215,10 +192,8 @@ def unusedVariables : Linter where
|
||||
get
|
||||
|
||||
-- collect ignore functions
|
||||
let ignoreFns ← getUnusedVariablesIgnoreFns
|
||||
let ignoreFns declStx stack opts :=
|
||||
isTopLevelDecl constDecls declStx stack opts ||
|
||||
ignoreFns.any (· declStx stack opts)
|
||||
let ignoreFns := (← getUnusedVariablesIgnoreFns)
|
||||
|>.insertAt! 0 (isTopLevelDecl constDecls)
|
||||
|
||||
-- determine unused variables
|
||||
let mut unused := #[]
|
||||
@@ -245,7 +220,7 @@ def unusedVariables : Linter where
|
||||
|
||||
-- evaluate ignore functions on original syntax
|
||||
if let some ((id', _) :: stack) := cmdStx.findStack? (·.getRange?.any (·.includes range)) then
|
||||
if id'.isIdent && ignoreFns declStx stack opts then
|
||||
if id'.isIdent && ignoreFns.any (· declStx stack opts) then
|
||||
continue
|
||||
else
|
||||
continue
|
||||
@@ -256,7 +231,7 @@ def unusedVariables : Linter where
|
||||
return macroExpansions.any fun expansion =>
|
||||
-- in a macro expansion, there may be multiple leafs whose (synthetic) range includes `range`, so accept strict matches only
|
||||
if let some (_ :: stack) := expansion.output.findStack? (·.getRange?.any (·.includes range)) (fun stx => stx.isIdent && stx.getRange?.any (· == range)) then
|
||||
ignoreFns declStx stack opts
|
||||
ignoreFns.any (· declStx stack opts)
|
||||
else
|
||||
false
|
||||
else
|
||||
|
||||
@@ -16,15 +16,14 @@ structure AbstractMVarsResult where
|
||||
namespace AbstractMVars
|
||||
|
||||
structure State where
|
||||
ngen : NameGenerator
|
||||
lctx : LocalContext
|
||||
mctx : MetavarContext
|
||||
nextParamIdx : Nat := 0
|
||||
paramNames : Array Name := #[]
|
||||
fvars : Array Expr := #[]
|
||||
lmap : HashMap LMVarId Level := {}
|
||||
emap : HashMap MVarId Expr := {}
|
||||
abstractLevels : Bool -- whether to abstract level mvars
|
||||
ngen : NameGenerator
|
||||
lctx : LocalContext
|
||||
mctx : MetavarContext
|
||||
nextParamIdx : Nat := 0
|
||||
paramNames : Array Name := #[]
|
||||
fvars : Array Expr := #[]
|
||||
lmap : HashMap LMVarId Level := {}
|
||||
emap : HashMap MVarId Expr := {}
|
||||
|
||||
abbrev M := StateM State
|
||||
|
||||
@@ -43,8 +42,6 @@ def mkFreshFVarId : M FVarId :=
|
||||
return { name := (← mkFreshId) }
|
||||
|
||||
private partial def abstractLevelMVars (u : Level) : M Level := do
|
||||
if !(← get).abstractLevels then
|
||||
return u
|
||||
if !u.hasMVar then
|
||||
return u
|
||||
else
|
||||
@@ -127,13 +124,10 @@ end AbstractMVars
|
||||
new fresh universe metavariables, and instantiate the `(m_i : A_i)` in the lambda-expression
|
||||
with new fresh metavariables.
|
||||
|
||||
If `levels := false`, then level metavariables are not abstracted.
|
||||
|
||||
Application: we use this method to cache the results of type class resolution. -/
|
||||
def abstractMVars (e : Expr) (levels : Bool := true): MetaM AbstractMVarsResult := do
|
||||
def abstractMVars (e : Expr) : MetaM AbstractMVarsResult := do
|
||||
let e ← instantiateMVars e
|
||||
let (e, s) := AbstractMVars.abstractExprMVars e
|
||||
{ mctx := (← getMCtx), lctx := (← getLCtx), ngen := (← getNGen), abstractLevels := levels }
|
||||
let (e, s) := AbstractMVars.abstractExprMVars e { mctx := (← getMCtx), lctx := (← getLCtx), ngen := (← getNGen) }
|
||||
setNGen s.ngen
|
||||
setMCtx s.mctx
|
||||
let e := s.lctx.mkLambda s.fvars e
|
||||
|
||||
@@ -123,17 +123,6 @@ def mkEqTrans (h₁ h₂ : Expr) : MetaM Expr := do
|
||||
| none, _ => throwAppBuilderException ``Eq.trans ("equality proof expected" ++ hasTypeMsg h₁ hType₁)
|
||||
| _, none => throwAppBuilderException ``Eq.trans ("equality proof expected" ++ hasTypeMsg h₂ hType₂)
|
||||
|
||||
/--
|
||||
Similar to `mkEqTrans`, but arguments can be `none`.
|
||||
`none` is treated as a reflexivity proof.
|
||||
-/
|
||||
def mkEqTrans? (h₁? h₂? : Option Expr) : MetaM (Option Expr) :=
|
||||
match h₁?, h₂? with
|
||||
| none, none => return none
|
||||
| none, some h => return h
|
||||
| some h, none => return h
|
||||
| some h₁, some h₂ => mkEqTrans h₁ h₂
|
||||
|
||||
/-- Given `h : HEq a b`, returns a proof of `HEq b a`. -/
|
||||
def mkHEqSymm (h : Expr) : MetaM Expr := do
|
||||
if h.isAppOf ``HEq.refl then
|
||||
@@ -175,41 +164,10 @@ def mkEqOfHEq (h : Expr) : MetaM Expr := do
|
||||
| _ =>
|
||||
throwAppBuilderException ``HEq.trans m!"heterogeneous equality proof expected{indentExpr h}"
|
||||
|
||||
/--
|
||||
If `e` is `@Eq.refl α a`, return `a`.
|
||||
-/
|
||||
def isRefl? (e : Expr) : Option Expr := do
|
||||
if e.isAppOfArity ``Eq.refl 2 then
|
||||
some e.appArg!
|
||||
else
|
||||
none
|
||||
|
||||
/--
|
||||
If `e` is `@congrArg α β a b f h`, return `α`, `f` and `h`.
|
||||
Also works if `e` can be turned into such an application (e.g. `congrFun`).
|
||||
-/
|
||||
def congrArg? (e : Expr) : MetaM (Option (Expr × Expr × Expr )) := do
|
||||
if e.isAppOfArity ``congrArg 6 then
|
||||
let #[α, _β, _a, _b, f, h] := e.getAppArgs | unreachable!
|
||||
return some (α, f, h)
|
||||
if e.isAppOfArity ``congrFun 6 then
|
||||
let #[α, β, _f, _g, h, a] := e.getAppArgs | unreachable!
|
||||
let α' ← withLocalDecl `x .default α fun x => do
|
||||
mkForallFVars #[x] (β.beta #[x])
|
||||
let f' ← withLocalDecl `x .default α' fun f => do
|
||||
mkLambdaFVars #[f] (f.app a)
|
||||
return some (α', f', h)
|
||||
return none
|
||||
|
||||
/-- Given `f : α → β` and `h : a = b`, returns a proof of `f a = f b`.-/
|
||||
partial def mkCongrArg (f h : Expr) : MetaM Expr := do
|
||||
if let some a := isRefl? h then
|
||||
mkEqRefl (mkApp f a)
|
||||
else if let some (α, f₁, h₁) ← congrArg? h then
|
||||
-- Fuse nested `congrArg` for smaller proof terms, e.g. when using simp
|
||||
let f' ← withLocalDecl `x .default α fun x => do
|
||||
mkLambdaFVars #[x] (f.beta #[f₁.beta #[x]])
|
||||
mkCongrArg f' h₁
|
||||
def mkCongrArg (f h : Expr) : MetaM Expr := do
|
||||
if h.isAppOf ``Eq.refl then
|
||||
mkEqRefl (mkApp f h.appArg!)
|
||||
else
|
||||
let hType ← infer h
|
||||
let fType ← infer f
|
||||
@@ -223,13 +181,8 @@ partial def mkCongrArg (f h : Expr) : MetaM Expr := do
|
||||
|
||||
/-- Given `h : f = g` and `a : α`, returns a proof of `f a = g a`.-/
|
||||
def mkCongrFun (h a : Expr) : MetaM Expr := do
|
||||
if let some f := isRefl? h then
|
||||
mkEqRefl (mkApp f a)
|
||||
else if let some (α, f₁, h₁) ← congrArg? h then
|
||||
-- Fuse nested `congrArg` for smaller proof terms, e.g. when using simp
|
||||
let f' ← withLocalDecl `x .default α fun x => do
|
||||
mkLambdaFVars #[x] (f₁.beta #[x, a])
|
||||
mkCongrArg f' h₁
|
||||
if h.isAppOf ``Eq.refl then
|
||||
mkEqRefl (mkApp h.appArg! a)
|
||||
else
|
||||
let hType ← infer h
|
||||
match hType.eq? with
|
||||
@@ -333,7 +286,7 @@ private def withAppBuilderTrace [ToMessageData α] [ToMessageData β]
|
||||
Remark:
|
||||
``mkAppM `arbitrary #[α]`` returns `@arbitrary.{u} α` without synthesizing
|
||||
the implicit argument occurring after `α`.
|
||||
Given a `x : ([Decidable p] → Bool) × Nat`, ``mkAppM `Prod.fst #[x]`` returns `@Prod.fst ([Decidable p] → Bool) Nat x`.
|
||||
Given a `x : (([Decidable p] → Bool) × Nat`, ``mkAppM `Prod.fst #[x]`` returns `@Prod.fst ([Decidable p] → Bool) Nat x`
|
||||
-/
|
||||
def mkAppM (constName : Name) (xs : Array Expr) : MetaM Expr := do
|
||||
withAppBuilderTrace constName xs do withNewMCtxDepth do
|
||||
|
||||
@@ -19,7 +19,7 @@ This module provides four (mutually dependent) goodies that are needed for build
|
||||
3- Type inference.
|
||||
4- Type class resolution.
|
||||
|
||||
They are packed into the `MetaM` monad.
|
||||
They are packed into the MetaM monad.
|
||||
-/
|
||||
|
||||
namespace Lean.Meta
|
||||
|
||||
@@ -80,26 +80,25 @@ def unfoldNamedPattern (e : Expr) : MetaM Expr := do
|
||||
|
||||
We use the `mask` to build the splitter proof. See `mkSplitterProof`.
|
||||
-/
|
||||
partial def forallAltTelescope (altType : Expr) (altNumParams numDiscrEqs : Nat)
|
||||
partial def forallAltTelescope (altType : Expr) (numNonEqParams : Nat)
|
||||
(k : (ys : Array Expr) → (eqs : Array Expr) → (args : Array Expr) → (mask : Array Bool) → (type : Expr) → MetaM α)
|
||||
: MetaM α := do
|
||||
go #[] #[] #[] #[] 0 altType
|
||||
where
|
||||
go (ys : Array Expr) (eqs : Array Expr) (args : Array Expr) (mask : Array Bool) (i : Nat) (type : Expr) : MetaM α := do
|
||||
let type ← whnfForall type
|
||||
if i < altNumParams then
|
||||
let Expr.forallE n d b .. := type
|
||||
| throwError "expecting {altNumParams} parameters, including {numDiscrEqs} equalities, but found type{indentExpr altType}"
|
||||
if i < altNumParams - numDiscrEqs then
|
||||
match type with
|
||||
| Expr.forallE n d b .. =>
|
||||
if i < numNonEqParams then
|
||||
let d ← unfoldNamedPattern d
|
||||
withLocalDeclD n d fun y => do
|
||||
let typeNew := b.instantiate1 y
|
||||
if let some (_, lhs, rhs) ← matchEq? d then
|
||||
if lhs.isFVar && ys.contains lhs && args.contains lhs && isNamedPatternProof typeNew y then
|
||||
let some j := ys.getIdx? lhs | unreachable!
|
||||
let ys := ys.eraseIdx j
|
||||
let some k := args.getIdx? lhs | unreachable!
|
||||
let mask := mask.set! k false
|
||||
let some i := ys.getIdx? lhs | unreachable!
|
||||
let ys := ys.eraseIdx i
|
||||
let some j := args.getIdx? lhs | unreachable!
|
||||
let mask := mask.set! j false
|
||||
let args := args.map fun arg => if arg == lhs then rhs else arg
|
||||
let args := args.push (← mkEqRefl rhs)
|
||||
let typeNew := typeNew.replaceFVar lhs rhs
|
||||
@@ -115,7 +114,7 @@ where
|
||||
withLocalDeclD n d fun eq => do
|
||||
let typeNew := b.instantiate1 eq
|
||||
go ys (eqs.push eq) (args.push arg) (mask.push false) (i+1) typeNew
|
||||
else
|
||||
| _ =>
|
||||
let type ← unfoldNamedPattern type
|
||||
/- Recall that alternatives that do not have variables have a `Unit` parameter to ensure
|
||||
they are not eagerly evaluated. -/
|
||||
@@ -629,11 +628,10 @@ private partial def mkEquationsFor (matchDeclName : Name) : MetaM MatchEqns :=
|
||||
let mut altArgMasks := #[] -- masks produced by `forallAltTelescope`
|
||||
for i in [:alts.size] do
|
||||
let altNumParams := matchInfo.altNumParams[i]!
|
||||
let altNonEqNumParams := altNumParams - numDiscrEqs
|
||||
let thmName := baseName ++ ((`eq).appendIndexAfter idx)
|
||||
eqnNames := eqnNames.push thmName
|
||||
let (notAlt, splitterAltType, splitterAltNumParam, argMask) ←
|
||||
forallAltTelescope (← inferType alts[i]!) altNumParams numDiscrEqs
|
||||
fun ys eqs rhsArgs argMask altResultType => do
|
||||
let (notAlt, splitterAltType, splitterAltNumParam, argMask) ← forallAltTelescope (← inferType alts[i]!) altNonEqNumParams fun ys eqs rhsArgs argMask altResultType => do
|
||||
let patterns := altResultType.getAppArgs
|
||||
let mut hs := #[]
|
||||
for notAlt in notAlts do
|
||||
|
||||
@@ -11,7 +11,6 @@ import Lean.Elab.Tactic.Rewrite
|
||||
namespace Lean.Meta.AC
|
||||
open Lean.Data.AC
|
||||
open Lean.Elab.Tactic
|
||||
open Std
|
||||
|
||||
abbrev ACExpr := Lean.Data.AC.Expr
|
||||
|
||||
@@ -44,13 +43,13 @@ def getInstance (cls : Name) (exprs : Array Expr) : MetaM (Option Expr) := do
|
||||
| _ => return none
|
||||
|
||||
def preContext (expr : Expr) : MetaM (Option PreContext) := do
|
||||
if let some assoc := ←getInstance ``Associative #[expr] then
|
||||
if let some assoc := ←getInstance ``IsAssociative #[expr] then
|
||||
return some
|
||||
{ assoc,
|
||||
op := expr
|
||||
id := 0
|
||||
comm := ←getInstance ``Commutative #[expr]
|
||||
idem := ←getInstance ``IdempotentOp #[expr] }
|
||||
comm := ←getInstance ``IsCommutative #[expr]
|
||||
idem := ←getInstance ``IsIdempotent #[expr] }
|
||||
|
||||
return none
|
||||
|
||||
@@ -100,14 +99,13 @@ where
|
||||
mkContext (α : Expr) (u : Level) (vars : Array Expr) : MetaM (Array Bool × Expr) := do
|
||||
let arbitrary := vars[0]!
|
||||
let zero := mkLevelZeroEx ()
|
||||
let plift := mkApp (mkConst ``PLift [zero])
|
||||
let pliftUp := mkApp2 (mkConst ``PLift.up [zero])
|
||||
let noneE tp := mkApp (mkConst ``Option.none [zero]) (plift tp)
|
||||
let someE tp v := mkApp2 (mkConst ``Option.some [zero]) (plift tp) (pliftUp tp v)
|
||||
let noneE := mkApp (mkConst ``Option.none [zero])
|
||||
let someE := mkApp2 (mkConst ``Option.some [zero])
|
||||
|
||||
let vars ← vars.mapM fun x => do
|
||||
let isNeutral :=
|
||||
let isNeutralClass := mkApp3 (mkConst ``LawfulIdentity [u]) α preContext.op x
|
||||
match ←getInstance ``LawfulIdentity #[preContext.op, x] with
|
||||
let isNeutralClass := mkApp3 (mkConst ``IsNeutral [u]) α preContext.op x
|
||||
match ←getInstance ``IsNeutral #[preContext.op, x] with
|
||||
| none => (false, noneE isNeutralClass)
|
||||
| some isNeutral => (true, someE isNeutralClass isNeutral)
|
||||
|
||||
@@ -118,13 +116,13 @@ where
|
||||
let vars ← mkListLit (mkApp2 (mkConst ``Variable [u]) α preContext.op) vars
|
||||
|
||||
let comm :=
|
||||
let commClass := mkApp2 (mkConst ``Commutative [u]) α preContext.op
|
||||
let commClass := mkApp2 (mkConst ``IsCommutative [u]) α preContext.op
|
||||
match preContext.comm with
|
||||
| none => noneE commClass
|
||||
| some comm => someE commClass comm
|
||||
|
||||
let idem :=
|
||||
let idemClass := mkApp2 (mkConst ``IdempotentOp [u]) α preContext.op
|
||||
let idemClass := mkApp2 (mkConst ``IsIdempotent [u]) α preContext.op
|
||||
match preContext.idem with
|
||||
| none => noneE idemClass
|
||||
| some idem => someE idemClass idem
|
||||
@@ -132,12 +130,12 @@ where
|
||||
return (isNeutrals, mkApp7 (mkConst ``Lean.Data.AC.Context.mk [u]) α preContext.op preContext.assoc comm idem vars arbitrary)
|
||||
|
||||
convert : ACExpr → Expr
|
||||
| .op l r => mkApp2 (mkConst ``Data.AC.Expr.op) (convert l) (convert r)
|
||||
| .var x => mkApp (mkConst ``Data.AC.Expr.var) $ mkNatLit x
|
||||
| Data.AC.Expr.op l r => mkApp2 (mkConst ``Data.AC.Expr.op) (convert l) (convert r)
|
||||
| Data.AC.Expr.var x => mkApp (mkConst ``Data.AC.Expr.var) $ mkNatLit x
|
||||
|
||||
convertTarget (vars : Array Expr) : ACExpr → Expr
|
||||
| .op l r => mkApp2 preContext.op (convertTarget vars l) (convertTarget vars r)
|
||||
| .var x => vars[x]!
|
||||
| Data.AC.Expr.op l r => mkApp2 preContext.op (convertTarget vars l) (convertTarget vars r)
|
||||
| Data.AC.Expr.var x => vars[x]!
|
||||
|
||||
def rewriteUnnormalized (mvarId : MVarId) : MetaM Unit := do
|
||||
let simpCtx :=
|
||||
|
||||
@@ -11,35 +11,20 @@ namespace Lean.Meta
|
||||
|
||||
structure ElimAltInfo where
|
||||
name : Name
|
||||
/-- A declaration corresponding to the inductive constructor.
|
||||
(For custom recursors, the alternatives correspond to parameter names in the
|
||||
recursor, so we may not have a declaration to point to.)
|
||||
This is used for go-to-definition on the alternative name. -/
|
||||
declName? : Option Name
|
||||
numFields : Nat
|
||||
/-- If `provesMotive := true`, then this alternative has `motive` as its conclusion.
|
||||
Only for those alternatives the `induction` tactic should introduce reverted hypotheses. -/
|
||||
provesMotive : Bool
|
||||
deriving Repr, Inhabited
|
||||
|
||||
/--
|
||||
Information about an eliminator as used by `induction` or `cases`.
|
||||
|
||||
Created from an expression by `getElimInfo`. This typically contains level metavariables that
|
||||
are instantiated as we go (e.g. in `addImplicitTargets`), so this is single use.
|
||||
-/
|
||||
structure ElimInfo where
|
||||
elimExpr : Expr
|
||||
elimType : Expr
|
||||
name : Name
|
||||
motivePos : Nat
|
||||
targetsPos : Array Nat := #[]
|
||||
altsInfo : Array ElimAltInfo := #[]
|
||||
deriving Repr, Inhabited
|
||||
|
||||
def getElimExprInfo (elimExpr : Expr) (baseDeclName? : Option Name := none) : MetaM ElimInfo := do
|
||||
let elimType ← inferType elimExpr
|
||||
trace[Elab.induction] "eliminator {indentExpr elimExpr}\nhas type{indentExpr elimType}"
|
||||
forallTelescopeReducing elimType fun xs type => do
|
||||
def getElimInfo (declName : Name) (baseDeclName? : Option Name := none) : MetaM ElimInfo := do
|
||||
let declInfo ← getConstInfo declName
|
||||
forallTelescopeReducing declInfo.type fun xs type => do
|
||||
let motive := type.getAppFn
|
||||
let targets := type.getAppArgs
|
||||
unless motive.isFVar && targets.all (·.isFVar) && targets.size > 0 do
|
||||
@@ -51,10 +36,10 @@ def getElimExprInfo (elimExpr : Expr) (baseDeclName? : Option Name := none) : Me
|
||||
unless motiveResultType.isSort do
|
||||
throwError "motive result type must be a sort{indentExpr motiveType}"
|
||||
let some motivePos ← pure (xs.indexOf? motive) |
|
||||
throwError "unexpected eliminator type{indentExpr elimType}"
|
||||
throwError "unexpected eliminator type{indentExpr declInfo.type}"
|
||||
let targetsPos ← targets.mapM fun target => do
|
||||
match xs.indexOf? target with
|
||||
| none => throwError "unexpected eliminator type{indentExpr elimType}"
|
||||
| none => throwError "unexpected eliminator type{indentExpr declInfo.type}"
|
||||
| some targetPos => pure targetPos.val
|
||||
let mut altsInfo := #[]
|
||||
let env ← getEnv
|
||||
@@ -63,68 +48,62 @@ def getElimExprInfo (elimExpr : Expr) (baseDeclName? : Option Name := none) : Me
|
||||
if x != motive && !targets.contains x then
|
||||
let xDecl ← x.fvarId!.getDecl
|
||||
if xDecl.binderInfo.isExplicit then
|
||||
let (numFields, provesMotive) ← forallTelescopeReducing xDecl.type fun args concl =>
|
||||
pure (args.size, concl.getAppFn == motive)
|
||||
let numFields ← forallTelescopeReducing xDecl.type fun args _ => pure args.size
|
||||
let name := xDecl.userName
|
||||
let declName? := do
|
||||
let base ← baseDeclName?
|
||||
let altDeclName := base ++ name
|
||||
if env.contains altDeclName then some altDeclName else none
|
||||
altsInfo := altsInfo.push { name, declName?, numFields, provesMotive }
|
||||
pure { elimExpr, elimType, motivePos, targetsPos, altsInfo }
|
||||
|
||||
def getElimInfo (elimName : Name) (baseDeclName? : Option Name := none) : MetaM ElimInfo := do
|
||||
getElimExprInfo (← mkConstWithFreshMVarLevels elimName) baseDeclName?
|
||||
altsInfo := altsInfo.push { name, declName?, numFields }
|
||||
pure { name := declName, motivePos, targetsPos, altsInfo }
|
||||
|
||||
/--
|
||||
Eliminators/recursors may have implicit targets. For builtin recursors, all indices are implicit targets.
|
||||
Given an eliminator and the sequence of explicit targets, this methods returns a new sequence containing
|
||||
implicit and explicit targets.
|
||||
-/
|
||||
partial def addImplicitTargets (elimInfo : ElimInfo) (targets : Array Expr) : MetaM (Array Expr) := do
|
||||
let (implicitMVars, targets) ← collect elimInfo.elimType 0 0 #[] #[]
|
||||
for mvar in implicitMVars do
|
||||
unless ← mvar.isAssigned do
|
||||
let name := (←mvar.getDecl).userName
|
||||
if name.isAnonymous || name.hasMacroScopes then
|
||||
throwError "failed to infer implicit target"
|
||||
else
|
||||
throwError "failed to infer implicit target {(←mvar.getDecl).userName}"
|
||||
targets.mapM instantiateMVars
|
||||
partial def addImplicitTargets (elimInfo : ElimInfo) (targets : Array Expr) : MetaM (Array Expr) :=
|
||||
withNewMCtxDepth do
|
||||
let f ← mkConstWithFreshMVarLevels elimInfo.name
|
||||
let targets ← collect (← inferType f) 0 0 #[]
|
||||
let targets ← targets.mapM instantiateMVars
|
||||
for target in targets do
|
||||
if (← hasAssignableMVar target) then
|
||||
throwError "failed to infer implicit target, it contains unresolved metavariables{indentExpr target}"
|
||||
return targets
|
||||
where
|
||||
collect (type : Expr) (argIdx targetIdx : Nat) (implicits : Array MVarId) (targets' : Array Expr) :
|
||||
MetaM (Array MVarId × Array Expr) := do
|
||||
collect (type : Expr) (argIdx targetIdx : Nat) (targets' : Array Expr) : MetaM (Array Expr) := do
|
||||
match (← whnfD type) with
|
||||
| Expr.forallE n d b bi =>
|
||||
| Expr.forallE _ d b bi =>
|
||||
if elimInfo.targetsPos.contains argIdx then
|
||||
if bi.isExplicit then
|
||||
unless targetIdx < targets.size do
|
||||
throwError "insufficient number of targets for '{elimInfo.elimExpr}'"
|
||||
throwError "insufficient number of targets for '{elimInfo.name}'"
|
||||
let target := targets[targetIdx]!
|
||||
let targetType ← inferType target
|
||||
unless (← isDefEq d targetType) do
|
||||
throwError "target{indentExpr target}\n{← mkHasTypeButIsExpectedMsg targetType d}"
|
||||
collect (b.instantiate1 target) (argIdx+1) (targetIdx+1) implicits (targets'.push target)
|
||||
collect (b.instantiate1 target) (argIdx+1) (targetIdx+1) (targets'.push target)
|
||||
else
|
||||
let implicitTarget ← mkFreshExprMVar (type? := d) (userName := n)
|
||||
collect (b.instantiate1 implicitTarget) (argIdx+1) targetIdx (implicits.push implicitTarget.mvarId!) (targets'.push implicitTarget)
|
||||
let implicitTarget ← mkFreshExprMVar d
|
||||
collect (b.instantiate1 implicitTarget) (argIdx+1) targetIdx (targets'.push implicitTarget)
|
||||
else
|
||||
collect (b.instantiate1 (← mkFreshExprMVar d)) (argIdx+1) targetIdx implicits targets'
|
||||
collect (b.instantiate1 (← mkFreshExprMVar d)) (argIdx+1) targetIdx targets'
|
||||
| _ =>
|
||||
return (implicits, targets')
|
||||
return targets'
|
||||
|
||||
structure CustomEliminator where
|
||||
typeNames : Array Name
|
||||
elimName : Name -- NB: Do not store the ElimInfo, it can contain MVars
|
||||
elimInfo : ElimInfo
|
||||
deriving Inhabited, Repr
|
||||
|
||||
structure CustomEliminators where
|
||||
map : SMap (Array Name) Name := {}
|
||||
map : SMap (Array Name) ElimInfo := {}
|
||||
deriving Inhabited, Repr
|
||||
|
||||
def addCustomEliminatorEntry (es : CustomEliminators) (e : CustomEliminator) : CustomEliminators :=
|
||||
match es with
|
||||
| { map := map } => { map := map.insert e.typeNames e.elimName }
|
||||
| { map := map } => { map := map.insert e.typeNames e.elimInfo }
|
||||
|
||||
builtin_initialize customEliminatorExt : SimpleScopedEnvExtension CustomEliminator CustomEliminators ←
|
||||
registerSimpleScopedEnvExtension {
|
||||
@@ -133,9 +112,9 @@ builtin_initialize customEliminatorExt : SimpleScopedEnvExtension CustomEliminat
|
||||
finalizeImport := fun { map := map } => { map := map.switch }
|
||||
}
|
||||
|
||||
def mkCustomEliminator (elimName : Name) : MetaM CustomEliminator := do
|
||||
let elimInfo ← getElimInfo elimName
|
||||
let info ← getConstInfo elimName
|
||||
def mkCustomEliminator (declName : Name) : MetaM CustomEliminator := do
|
||||
let info ← getConstInfo declName
|
||||
let elimInfo ← getElimInfo declName
|
||||
forallTelescopeReducing info.type fun xs _ => do
|
||||
let mut typeNames := #[]
|
||||
for i in [:elimInfo.targetsPos.size] do
|
||||
@@ -155,7 +134,7 @@ def mkCustomEliminator (elimName : Name) : MetaM CustomEliminator := do
|
||||
let xType ← inferType x
|
||||
let .const typeName .. := xType.getAppFn | throwError "unexpected eliminator target type{indentExpr xType}"
|
||||
typeNames := typeNames.push typeName
|
||||
return { typeNames, elimName}
|
||||
return { typeNames, elimInfo }
|
||||
|
||||
def addCustomEliminator (declName : Name) (attrKind : AttributeKind) : MetaM Unit := do
|
||||
let e ← mkCustomEliminator declName
|
||||
@@ -172,7 +151,7 @@ builtin_initialize
|
||||
def getCustomEliminators : CoreM CustomEliminators := do
|
||||
return customEliminatorExt.getState (← getEnv)
|
||||
|
||||
def getCustomEliminator? (targets : Array Expr) : MetaM (Option Name) := do
|
||||
def getCustomEliminator? (targets : Array Expr) : MetaM (Option ElimInfo) := do
|
||||
let mut key := #[]
|
||||
for target in targets do
|
||||
let targetType := (← instantiateMVars (← inferType target)).headBeta
|
||||
|
||||
@@ -44,35 +44,24 @@ namespace Lean.Meta
|
||||
let fvars := fvars.push fvar
|
||||
loop i lctx fvars j s body
|
||||
| i+1, type =>
|
||||
if let some (n, type, val, body) := type.letFun? then
|
||||
let type := type.instantiateRevRange j fvars.size fvars
|
||||
let type := type.headBeta
|
||||
let val := val.instantiateRevRange j fvars.size fvars
|
||||
let fvarId ← mkFreshFVarId
|
||||
let (n, s) ← mkName lctx n true s
|
||||
let lctx := lctx.mkLetDecl fvarId n type val
|
||||
let fvar := mkFVar fvarId
|
||||
let fvars := fvars.push fvar
|
||||
loop i lctx fvars j s body
|
||||
else
|
||||
let type := type.instantiateRevRange j fvars.size fvars
|
||||
withReader (fun ctx => { ctx with lctx := lctx }) do
|
||||
withNewLocalInstances fvars j do
|
||||
/- We used to use just `whnf`, but it produces counterintuitive behavior if
|
||||
- `type` is a metavariable `?m` such that `?m := let x := v; b`, or
|
||||
- `type` has `MData` or annotations such as `optParam` around a `let`-expression.
|
||||
let type := type.instantiateRevRange j fvars.size fvars
|
||||
withReader (fun ctx => { ctx with lctx := lctx }) do
|
||||
withNewLocalInstances fvars j do
|
||||
/- We used to use just `whnf`, but it produces counterintuitive behavior if
|
||||
- `type` is a metavariable `?m` such that `?m := let x := v; b`, or
|
||||
- `type` has `MData` or annotations such as `optParam` around a `let`-expression.
|
||||
|
||||
`whnf` instantiates metavariables, and consumes `MData`, but it also expands the `let`.
|
||||
-/
|
||||
let newType := (← instantiateMVars type).cleanupAnnotations
|
||||
if newType.isForall || newType.isLet || newType.isLetFun then
|
||||
`whnf` instantiates metavariables, and consumes `MData`, but it also expands the `let`.
|
||||
-/
|
||||
let newType := (← instantiateMVars type).cleanupAnnotations
|
||||
if newType.isForall || newType.isLet then
|
||||
loop (i+1) lctx fvars fvars.size s newType
|
||||
else
|
||||
let newType ← whnf newType
|
||||
if newType.isForall then
|
||||
loop (i+1) lctx fvars fvars.size s newType
|
||||
else
|
||||
let newType ← whnf newType
|
||||
if newType.isForall then
|
||||
loop (i+1) lctx fvars fvars.size s newType
|
||||
else
|
||||
throwTacticEx `introN mvarId "insufficient number of binders"
|
||||
throwTacticEx `introN mvarId "insufficient number of binders"
|
||||
let (fvars, mvarId) ← loop n lctx #[] 0 s mvarType
|
||||
return (fvars.map Expr.fvarId!, mvarId)
|
||||
|
||||
@@ -108,9 +97,6 @@ private def mkAuxNameImp (preserveBinderNames : Bool) (hygienic : Bool) (useName
|
||||
mkAuxNameWithoutGivenName rest
|
||||
where
|
||||
mkAuxNameWithoutGivenName (rest : List Name) : MetaM (Name × List Name) := do
|
||||
-- Use a nicer binder name than `[anonymous]`, which can appear in for example `letFun x f` when `f` is not a lambda expression.
|
||||
-- In this case, we make sure the name is hygienic.
|
||||
let binderName ← if binderName.isAnonymous then mkFreshUserName `a else pure binderName
|
||||
if preserveBinderNames then
|
||||
return (binderName, rest)
|
||||
else
|
||||
@@ -183,15 +169,11 @@ abbrev _root_.Lean.MVarId.intro1P (mvarId : MVarId) : MetaM (FVarId × MVarId) :
|
||||
abbrev intro1P (mvarId : MVarId) : MetaM (FVarId × MVarId) :=
|
||||
mvarId.intro1P
|
||||
|
||||
private partial def getIntrosSize : Expr → Nat
|
||||
private def getIntrosSize : Expr → Nat
|
||||
| .forallE _ _ b _ => getIntrosSize b + 1
|
||||
| .letE _ _ _ b _ => getIntrosSize b + 1
|
||||
| .mdata _ b => getIntrosSize b
|
||||
| e =>
|
||||
if let some (_, _, _, b) := e.letFun? then
|
||||
getIntrosSize b + 1
|
||||
else
|
||||
0
|
||||
| _ => 0
|
||||
|
||||
/--
|
||||
Introduce as many binders as possible without unfolding definitions.
|
||||
|
||||
@@ -8,7 +8,7 @@ import Lean.Meta.Tactic.LinearArith.Nat.Simp
|
||||
|
||||
namespace Lean.Meta.Linear
|
||||
|
||||
def parentIsTarget (parent? : Option Expr) : Bool :=
|
||||
private def parentIsTarget (parent? : Option Expr) : Bool :=
|
||||
match parent? with
|
||||
| none => false
|
||||
| some parent => isLinearTerm parent || isLinearCnstr parent
|
||||
|
||||
@@ -11,7 +11,6 @@ import Lean.Meta.Tactic.Simp.Rewrite
|
||||
import Lean.Meta.Tactic.Simp.SimpAll
|
||||
import Lean.Meta.Tactic.Simp.Simproc
|
||||
import Lean.Meta.Tactic.Simp.BuiltinSimprocs
|
||||
import Lean.Meta.Tactic.Simp.RegisterCommand
|
||||
|
||||
namespace Lean
|
||||
|
||||
|
||||
@@ -7,8 +7,8 @@ import Lean.Meta.Tactic.Simp.Simproc
|
||||
|
||||
open Lean Meta Simp
|
||||
|
||||
builtin_simproc ↓ [simp, seval] reduceIte (ite _ _ _) := fun e => do
|
||||
unless e.isAppOfArity ``ite 5 do return .continue
|
||||
builtin_simproc ↓ reduceIte (ite _ _ _) := fun e => OptionT.run do
|
||||
guard (e.isAppOfArity ``ite 5)
|
||||
let c := e.getArg! 1
|
||||
let r ← simp c
|
||||
if r.expr.isConstOf ``True then
|
||||
@@ -19,10 +19,10 @@ builtin_simproc ↓ [simp, seval] reduceIte (ite _ _ _) := fun e => do
|
||||
let eNew := e.getArg! 4
|
||||
let pr := mkApp (mkAppN (mkConst ``ite_cond_eq_false e.getAppFn.constLevels!) e.getAppArgs) (← r.getProof)
|
||||
return .visit { expr := eNew, proof? := pr }
|
||||
return .continue
|
||||
failure
|
||||
|
||||
builtin_simproc ↓ [simp, seval] reduceDite (dite _ _ _) := fun e => do
|
||||
unless e.isAppOfArity ``dite 5 do return .continue
|
||||
builtin_simproc ↓ reduceDite (dite _ _ _) := fun e => OptionT.run do
|
||||
guard (e.isAppOfArity ``dite 5)
|
||||
let c := e.getArg! 1
|
||||
let r ← simp c
|
||||
if r.expr.isConstOf ``True then
|
||||
@@ -37,4 +37,4 @@ builtin_simproc ↓ [simp, seval] reduceDite (dite _ _ _) := fun e => do
|
||||
let eNew := mkApp (e.getArg! 4) h |>.headBeta
|
||||
let prNew := mkApp (mkAppN (mkConst ``dite_cond_eq_false e.getAppFn.constLevels!) e.getAppArgs) pr
|
||||
return .visit { expr := eNew, proof? := prNew }
|
||||
return .continue
|
||||
failure
|
||||
|
||||
@@ -14,7 +14,7 @@ structure Value where
|
||||
size : Nat
|
||||
value : Nat
|
||||
|
||||
def fromExpr? (e : Expr) : SimpM (Option Value) := OptionT.run do
|
||||
def fromExpr? (e : Expr) : OptionT SimpM Value := do
|
||||
guard (e.isAppOfArity ``OfNat.ofNat 3)
|
||||
let type ← whnf e.appFn!.appFn!.appArg!
|
||||
guard (type.isAppOfArity ``Fin 1)
|
||||
@@ -28,39 +28,43 @@ def Value.toExpr (v : Value) : Expr :=
|
||||
let vExpr := mkRawNatLit v.value
|
||||
mkApp2 v.ofNatFn vExpr (mkApp2 (mkConst ``Fin.instOfNat) (Lean.toExpr (v.size - 1)) vExpr)
|
||||
|
||||
@[inline] def reduceBin (declName : Name) (arity : Nat) (op : Nat → Nat → Nat) (e : Expr) : SimpM Step := do
|
||||
unless e.isAppOfArity declName arity do return .continue
|
||||
let some v₁ ← fromExpr? e.appFn!.appArg! | return .continue
|
||||
let some v₂ ← fromExpr? e.appArg! | return .continue
|
||||
unless v₁.size == v₂.size do return .continue
|
||||
@[inline] def reduceBin (declName : Name) (arity : Nat) (op : Nat → Nat → Nat) (e : Expr) : OptionT SimpM Step := do
|
||||
guard (e.isAppOfArity declName arity)
|
||||
let v₁ ← fromExpr? e.appFn!.appArg!
|
||||
let v₂ ← fromExpr? e.appArg!
|
||||
guard (v₁.size == v₂.size)
|
||||
let v := { v₁ with value := op v₁.value v₂.value % v₁.size }
|
||||
return .done { expr := v.toExpr }
|
||||
|
||||
@[inline] def reduceBinPred (declName : Name) (arity : Nat) (op : Nat → Nat → Bool) (e : Expr) : SimpM Step := do
|
||||
unless e.isAppOfArity declName arity do return .continue
|
||||
let some v₁ ← fromExpr? e.appFn!.appArg! | return .continue
|
||||
let some v₂ ← fromExpr? e.appArg! | return .continue
|
||||
evalPropStep e (op v₁.value v₂.value)
|
||||
@[inline] def reduceBinPred (declName : Name) (arity : Nat) (op : Nat → Nat → Bool) (e : Expr) : OptionT SimpM Step := do
|
||||
guard (e.isAppOfArity declName arity)
|
||||
let v₁ ← fromExpr? e.appFn!.appArg!
|
||||
let v₂ ← fromExpr? e.appArg!
|
||||
let d ← mkDecide e
|
||||
if op v₁.value v₂.value then
|
||||
return .done { expr := mkConst ``True, proof? := mkAppN (mkConst ``eq_true_of_decide) #[e, d.appArg!, (← mkEqRefl (mkConst ``true))] }
|
||||
else
|
||||
return .done { expr := mkConst ``False, proof? := mkAppN (mkConst ``eq_false_of_decide) #[e, d.appArg!, (← mkEqRefl (mkConst ``false))] }
|
||||
|
||||
/-
|
||||
The following code assumes users did not override the `Fin n` instances for the arithmetic operators.
|
||||
If they do, they must disable the following `simprocs`.
|
||||
-/
|
||||
|
||||
builtin_simproc [simp, seval] reduceAdd ((_ + _ : Fin _)) := reduceBin ``HAdd.hAdd 6 (· + ·)
|
||||
builtin_simproc [simp, seval] reduceMul ((_ * _ : Fin _)) := reduceBin ``HMul.hMul 6 (· * ·)
|
||||
builtin_simproc [simp, seval] reduceSub ((_ - _ : Fin _)) := reduceBin ``HSub.hSub 6 (· - ·)
|
||||
builtin_simproc [simp, seval] reduceDiv ((_ / _ : Fin _)) := reduceBin ``HDiv.hDiv 6 (· / ·)
|
||||
builtin_simproc [simp, seval] reduceMod ((_ % _ : Fin _)) := reduceBin ``HMod.hMod 6 (· % ·)
|
||||
builtin_simproc reduceAdd ((_ + _ : Fin _)) := reduceBin ``HAdd.hAdd 6 (· + ·)
|
||||
builtin_simproc reduceMul ((_ * _ : Fin _)) := reduceBin ``HMul.hMul 6 (· * ·)
|
||||
builtin_simproc reduceSub ((_ - _ : Fin _)) := reduceBin ``HSub.hSub 6 (· - ·)
|
||||
builtin_simproc reduceDiv ((_ / _ : Fin _)) := reduceBin ``HDiv.hDiv 6 (· / ·)
|
||||
builtin_simproc reduceMod ((_ % _ : Fin _)) := reduceBin ``HMod.hMod 6 (· % ·)
|
||||
|
||||
builtin_simproc [simp, seval] reduceLT (( _ : Fin _) < _) := reduceBinPred ``LT.lt 4 (. < .)
|
||||
builtin_simproc [simp, seval] reduceLE (( _ : Fin _) ≤ _) := reduceBinPred ``LE.le 4 (. ≤ .)
|
||||
builtin_simproc [simp, seval] reduceGT (( _ : Fin _) > _) := reduceBinPred ``GT.gt 4 (. > .)
|
||||
builtin_simproc [simp, seval] reduceGE (( _ : Fin _) ≥ _) := reduceBinPred ``GE.ge 4 (. ≥ .)
|
||||
builtin_simproc reduceLT (( _ : Fin _) < _) := reduceBinPred ``LT.lt 4 (. < .)
|
||||
builtin_simproc reduceLE (( _ : Fin _) ≤ _) := reduceBinPred ``LE.le 4 (. ≤ .)
|
||||
builtin_simproc reduceGT (( _ : Fin _) > _) := reduceBinPred ``GT.gt 4 (. > .)
|
||||
builtin_simproc reduceGE (( _ : Fin _) ≥ _) := reduceBinPred ``GE.ge 4 (. ≥ .)
|
||||
|
||||
/-- Return `.done` for Fin values. We don't want to unfold in the symbolic evaluator. -/
|
||||
builtin_simproc [seval] isValue ((OfNat.ofNat _ : Fin _)) := fun e => do
|
||||
unless e.isAppOfArity ``OfNat.ofNat 3 do return .continue
|
||||
/-- Return `.done` for Fin values. We don't want to unfold them when `ground := true`. -/
|
||||
builtin_simproc isValue ((OfNat.ofNat _ : Fin _)) := fun e => OptionT.run do
|
||||
guard (e.isAppOfArity ``OfNat.ofNat 3)
|
||||
return .done { expr := e }
|
||||
|
||||
end Fin
|
||||
|
||||
@@ -9,7 +9,7 @@ import Lean.Meta.Tactic.Simp.BuiltinSimprocs.Nat
|
||||
namespace Int
|
||||
open Lean Meta Simp
|
||||
|
||||
def fromExpr? (e : Expr) : SimpM (Option Int) := OptionT.run do
|
||||
def fromExpr? (e : Expr) : OptionT SimpM Int := do
|
||||
let mut e := e
|
||||
let mut isNeg := false
|
||||
if e.isAppOfArity ``Neg.neg 3 then
|
||||
@@ -32,66 +32,71 @@ def toExpr (v : Int) : Expr :=
|
||||
else
|
||||
e
|
||||
|
||||
@[inline] def reduceUnary (declName : Name) (arity : Nat) (op : Int → Int) (e : Expr) : SimpM Step := do
|
||||
unless e.isAppOfArity declName arity do return .continue
|
||||
let some n ← fromExpr? e.appArg! | return .continue
|
||||
@[inline] def reduceUnary (declName : Name) (arity : Nat) (op : Int → Int) (e : Expr) : OptionT SimpM Step := do
|
||||
guard (e.isAppOfArity declName arity)
|
||||
let n ← fromExpr? e.appArg!
|
||||
return .done { expr := toExpr (op n) }
|
||||
|
||||
@[inline] def reduceBin (declName : Name) (arity : Nat) (op : Int → Int → Int) (e : Expr) : SimpM Step := do
|
||||
unless e.isAppOfArity declName arity do return .continue
|
||||
let some v₁ ← fromExpr? e.appFn!.appArg! | return .continue
|
||||
let some v₂ ← fromExpr? e.appArg! | return .continue
|
||||
@[inline] def reduceBin (declName : Name) (arity : Nat) (op : Int → Int → Int) (e : Expr) : OptionT SimpM Step := do
|
||||
guard (e.isAppOfArity declName arity)
|
||||
let v₁ ← fromExpr? e.appFn!.appArg!
|
||||
let v₂ ← fromExpr? e.appArg!
|
||||
return .done { expr := toExpr (op v₁ v₂) }
|
||||
|
||||
@[inline] def reduceBinPred (declName : Name) (arity : Nat) (op : Int → Int → Bool) (e : Expr) : SimpM Step := do
|
||||
unless e.isAppOfArity declName arity do return .continue
|
||||
let some v₁ ← fromExpr? e.appFn!.appArg! | return .continue
|
||||
let some v₂ ← fromExpr? e.appArg! | return .continue
|
||||
evalPropStep e (op v₁ v₂)
|
||||
@[inline] def reduceBinPred (declName : Name) (arity : Nat) (op : Int → Int → Bool) (e : Expr) : OptionT SimpM Step := OptionT.run do
|
||||
guard (e.isAppOfArity declName arity)
|
||||
let v₁ ← fromExpr? e.appFn!.appArg!
|
||||
let v₂ ← fromExpr? e.appArg!
|
||||
let d ← mkDecide e
|
||||
if op v₁ v₂ then
|
||||
return .done { expr := mkConst ``True, proof? := mkAppN (mkConst ``eq_true_of_decide) #[e, d.appArg!, (← mkEqRefl (mkConst ``true))] }
|
||||
else
|
||||
return .done { expr := mkConst ``False, proof? := mkAppN (mkConst ``eq_false_of_decide) #[e, d.appArg!, (← mkEqRefl (mkConst ``false))] }
|
||||
|
||||
/-
|
||||
The following code assumes users did not override the `Int` instances for the arithmetic operators.
|
||||
If they do, they must disable the following `simprocs`.
|
||||
-/
|
||||
|
||||
builtin_simproc [simp, seval] reduceNeg ((- _ : Int)) := fun e => do
|
||||
unless e.isAppOfArity ``Neg.neg 3 do return .continue
|
||||
builtin_simproc reduceNeg ((- _ : Int)) := fun e => OptionT.run do
|
||||
guard (e.isAppOfArity ``Neg.neg 3)
|
||||
let arg := e.appArg!
|
||||
if arg.isAppOfArity ``OfNat.ofNat 3 then
|
||||
-- We return .done to ensure `Neg.neg` is not unfolded even when `ground := true`.
|
||||
guard (← getContext).unfoldGround
|
||||
return .done { expr := e }
|
||||
else
|
||||
let some v ← fromExpr? arg | return .continue
|
||||
let v ← fromExpr? arg
|
||||
if v < 0 then
|
||||
return .done { expr := toExpr (- v) }
|
||||
else
|
||||
return .done { expr := toExpr v }
|
||||
|
||||
/-- Return `.done` for positive Int values. We don't want to unfold in the symbolic evaluator. -/
|
||||
builtin_simproc [seval] isPosValue ((OfNat.ofNat _ : Int)) := fun e => do
|
||||
unless e.isAppOfArity ``OfNat.ofNat 3 do return .continue
|
||||
/-- Return `.done` for positive Int values. We don't want to unfold them when `ground := true`. -/
|
||||
builtin_simproc isPosValue ((OfNat.ofNat _ : Int)) := fun e => OptionT.run do
|
||||
guard (e.isAppOfArity ``OfNat.ofNat 3)
|
||||
return .done { expr := e }
|
||||
|
||||
builtin_simproc [simp, seval] reduceAdd ((_ + _ : Int)) := reduceBin ``HAdd.hAdd 6 (· + ·)
|
||||
builtin_simproc [simp, seval] reduceMul ((_ * _ : Int)) := reduceBin ``HMul.hMul 6 (· * ·)
|
||||
builtin_simproc [simp, seval] reduceSub ((_ - _ : Int)) := reduceBin ``HSub.hSub 6 (· - ·)
|
||||
builtin_simproc [simp, seval] reduceDiv ((_ / _ : Int)) := reduceBin ``HDiv.hDiv 6 (· / ·)
|
||||
builtin_simproc [simp, seval] reduceMod ((_ % _ : Int)) := reduceBin ``HMod.hMod 6 (· % ·)
|
||||
builtin_simproc reduceAdd ((_ + _ : Int)) := reduceBin ``HAdd.hAdd 6 (· + ·)
|
||||
builtin_simproc reduceMul ((_ * _ : Int)) := reduceBin ``HMul.hMul 6 (· * ·)
|
||||
builtin_simproc reduceSub ((_ - _ : Int)) := reduceBin ``HSub.hSub 6 (· - ·)
|
||||
builtin_simproc reduceDiv ((_ / _ : Int)) := reduceBin ``HDiv.hDiv 6 (· / ·)
|
||||
builtin_simproc reduceMod ((_ % _ : Int)) := reduceBin ``HMod.hMod 6 (· % ·)
|
||||
|
||||
builtin_simproc [simp, seval] reducePow ((_ : Int) ^ (_ : Nat)) := fun e => do
|
||||
unless e.isAppOfArity ``HPow.hPow 6 do return .continue
|
||||
let some v₁ ← fromExpr? e.appFn!.appArg! | return .continue
|
||||
let some v₂ ← Nat.fromExpr? e.appArg! | return .continue
|
||||
builtin_simproc reducePow ((_ : Int) ^ (_ : Nat)) := fun e => OptionT.run do
|
||||
guard (e.isAppOfArity ``HPow.hPow 6)
|
||||
let v₁ ← fromExpr? e.appFn!.appArg!
|
||||
let v₂ ← Nat.fromExpr? e.appArg!
|
||||
return .done { expr := toExpr (v₁ ^ v₂) }
|
||||
|
||||
builtin_simproc [simp, seval] reduceAbs (natAbs _) := fun e => do
|
||||
unless e.isAppOfArity ``natAbs 1 do return .continue
|
||||
let some v ← fromExpr? e.appArg! | return .continue
|
||||
builtin_simproc reduceAbs (natAbs _) := fun e => OptionT.run do
|
||||
guard (e.isAppOfArity ``natAbs 1)
|
||||
let v ← fromExpr? e.appArg!
|
||||
return .done { expr := mkNatLit (natAbs v) }
|
||||
|
||||
builtin_simproc [simp, seval] reduceLT (( _ : Int) < _) := reduceBinPred ``LT.lt 4 (. < .)
|
||||
builtin_simproc [simp, seval] reduceLE (( _ : Int) ≤ _) := reduceBinPred ``LE.le 4 (. ≤ .)
|
||||
builtin_simproc [simp, seval] reduceGT (( _ : Int) > _) := reduceBinPred ``GT.gt 4 (. > .)
|
||||
builtin_simproc [simp, seval] reduceGE (( _ : Int) ≥ _) := reduceBinPred ``GE.ge 4 (. ≥ .)
|
||||
builtin_simproc reduceLT (( _ : Int) < _) := reduceBinPred ``LT.lt 4 (. < .)
|
||||
builtin_simproc reduceLE (( _ : Int) ≤ _) := reduceBinPred ``LE.le 4 (. ≤ .)
|
||||
builtin_simproc reduceGT (( _ : Int) > _) := reduceBinPred ``GT.gt 4 (. > .)
|
||||
builtin_simproc reduceGE (( _ : Int) ≥ _) := reduceBinPred ``GE.ge 4 (. ≥ .)
|
||||
|
||||
end Int
|
||||
|
||||
@@ -5,7 +5,6 @@ Authors: Leonardo de Moura
|
||||
-/
|
||||
import Lean.Meta.Offset
|
||||
import Lean.Meta.Tactic.Simp.Simproc
|
||||
import Lean.Meta.Tactic.Simp.BuiltinSimprocs.Util
|
||||
|
||||
namespace Nat
|
||||
open Lean Meta Simp
|
||||
@@ -14,46 +13,51 @@ def fromExpr? (e : Expr) : SimpM (Option Nat) := do
|
||||
let some n ← evalNat e |>.run | return none
|
||||
return n
|
||||
|
||||
@[inline] def reduceUnary (declName : Name) (arity : Nat) (op : Nat → Nat) (e : Expr) : SimpM Step := do
|
||||
unless e.isAppOfArity declName arity do return .continue
|
||||
let some n ← fromExpr? e.appArg! | return .continue
|
||||
@[inline] def reduceUnary (declName : Name) (arity : Nat) (op : Nat → Nat) (e : Expr) : OptionT SimpM Step := do
|
||||
guard (e.isAppOfArity declName arity)
|
||||
let n ← fromExpr? e.appArg!
|
||||
return .done { expr := mkNatLit (op n) }
|
||||
|
||||
@[inline] def reduceBin (declName : Name) (arity : Nat) (op : Nat → Nat → Nat) (e : Expr) : SimpM Step := do
|
||||
unless e.isAppOfArity declName arity do return .continue
|
||||
let some n ← fromExpr? e.appFn!.appArg! | return .continue
|
||||
let some m ← fromExpr? e.appArg! | return .continue
|
||||
@[inline] def reduceBin (declName : Name) (arity : Nat) (op : Nat → Nat → Nat) (e : Expr) : OptionT SimpM Step := do
|
||||
guard (e.isAppOfArity declName arity)
|
||||
let n ← fromExpr? e.appFn!.appArg!
|
||||
let m ← fromExpr? e.appArg!
|
||||
return .done { expr := mkNatLit (op n m) }
|
||||
|
||||
@[inline] def reduceBinPred (declName : Name) (arity : Nat) (op : Nat → Nat → Bool) (e : Expr) : SimpM Step := do
|
||||
unless e.isAppOfArity declName arity do return .continue
|
||||
let some n ← fromExpr? e.appFn!.appArg! | return .continue
|
||||
let some m ← fromExpr? e.appArg! | return .continue
|
||||
evalPropStep e (op n m)
|
||||
@[inline] def reduceBinPred (declName : Name) (arity : Nat) (op : Nat → Nat → Bool) (e : Expr) : OptionT SimpM Step := do
|
||||
guard (e.isAppOfArity declName arity)
|
||||
let n ← fromExpr? e.appFn!.appArg!
|
||||
let m ← fromExpr? e.appArg!
|
||||
let d ← mkDecide e
|
||||
if op n m then
|
||||
return .done { expr := mkConst ``True, proof? := mkAppN (mkConst ``eq_true_of_decide) #[e, d.appArg!, (← mkEqRefl (mkConst ``true))] }
|
||||
else
|
||||
return .done { expr := mkConst ``False, proof? := mkAppN (mkConst ``eq_false_of_decide) #[e, d.appArg!, (← mkEqRefl (mkConst ``false))] }
|
||||
|
||||
builtin_simproc [simp, seval] reduceSucc (Nat.succ _) := reduceUnary ``Nat.succ 1 (· + 1)
|
||||
builtin_simproc reduceSucc (Nat.succ _) := reduceUnary ``Nat.succ 1 (· + 1)
|
||||
|
||||
/-
|
||||
The following code assumes users did not override the `Nat` instances for the arithmetic operators.
|
||||
If they do, they must disable the following `simprocs`.
|
||||
-/
|
||||
|
||||
builtin_simproc [simp, seval] reduceAdd ((_ + _ : Nat)) := reduceBin ``HAdd.hAdd 6 (· + ·)
|
||||
builtin_simproc [simp, seval] reduceMul ((_ * _ : Nat)) := reduceBin ``HMul.hMul 6 (· * ·)
|
||||
builtin_simproc [simp, seval] reduceSub ((_ - _ : Nat)) := reduceBin ``HSub.hSub 6 (· - ·)
|
||||
builtin_simproc [simp, seval] reduceDiv ((_ / _ : Nat)) := reduceBin ``HDiv.hDiv 6 (· / ·)
|
||||
builtin_simproc [simp, seval] reduceMod ((_ % _ : Nat)) := reduceBin ``HMod.hMod 6 (· % ·)
|
||||
builtin_simproc [simp, seval] reducePow ((_ ^ _ : Nat)) := reduceBin ``HPow.hPow 6 (· ^ ·)
|
||||
builtin_simproc [simp, seval] reduceGcd (gcd _ _) := reduceBin ``gcd 2 gcd
|
||||
builtin_simproc reduceAdd ((_ + _ : Nat)) := reduceBin ``HAdd.hAdd 6 (· + ·)
|
||||
builtin_simproc reduceMul ((_ * _ : Nat)) := reduceBin ``HMul.hMul 6 (· * ·)
|
||||
builtin_simproc reduceSub ((_ - _ : Nat)) := reduceBin ``HSub.hSub 6 (· - ·)
|
||||
builtin_simproc reduceDiv ((_ / _ : Nat)) := reduceBin ``HDiv.hDiv 6 (· / ·)
|
||||
builtin_simproc reduceMod ((_ % _ : Nat)) := reduceBin ``HMod.hMod 6 (· % ·)
|
||||
builtin_simproc reducePow ((_ ^ _ : Nat)) := reduceBin ``HPow.hPow 6 (· ^ ·)
|
||||
builtin_simproc reduceGcd (gcd _ _) := reduceBin ``gcd 2 gcd
|
||||
|
||||
builtin_simproc [simp, seval] reduceLT (( _ : Nat) < _) := reduceBinPred ``LT.lt 4 (. < .)
|
||||
builtin_simproc [simp, seval] reduceLE (( _ : Nat) ≤ _) := reduceBinPred ``LE.le 4 (. ≤ .)
|
||||
builtin_simproc [simp, seval] reduceGT (( _ : Nat) > _) := reduceBinPred ``GT.gt 4 (. > .)
|
||||
builtin_simproc [simp, seval] reduceGE (( _ : Nat) ≥ _) := reduceBinPred ``GE.ge 4 (. ≥ .)
|
||||
builtin_simproc reduceLT (( _ : Nat) < _) := reduceBinPred ``LT.lt 4 (. < .)
|
||||
builtin_simproc reduceLE (( _ : Nat) ≤ _) := reduceBinPred ``LE.le 4 (. ≤ .)
|
||||
builtin_simproc reduceGT (( _ : Nat) > _) := reduceBinPred ``GT.gt 4 (. > .)
|
||||
builtin_simproc reduceGE (( _ : Nat) ≥ _) := reduceBinPred ``GE.ge 4 (. ≥ .)
|
||||
|
||||
/-- Return `.done` for Nat values. We don't want to unfold in the symbolic evaluator. -/
|
||||
builtin_simproc [seval] isValue ((OfNat.ofNat _ : Nat)) := fun e => do
|
||||
unless e.isAppOfArity ``OfNat.ofNat 3 do return .continue
|
||||
/-- Return `.done` for Nat values. We don't want to unfold them when `ground := true`. -/
|
||||
builtin_simproc isValue ((OfNat.ofNat _ : Nat)) := fun e => OptionT.run do
|
||||
guard (← getContext).unfoldGround
|
||||
guard (e.isAppOfArity ``OfNat.ofNat 3)
|
||||
return .done { expr := e }
|
||||
|
||||
end Nat
|
||||
|
||||
@@ -30,33 +30,38 @@ def $toExpr (v : Value) : Expr :=
|
||||
let vExpr := mkRawNatLit v.value.val
|
||||
mkApp2 v.ofNatFn vExpr (mkApp (mkConst $(quote (typeName.getId ++ `instOfNat))) vExpr)
|
||||
|
||||
@[inline] def reduceBin (declName : Name) (arity : Nat) (op : $typeName → $typeName → $typeName) (e : Expr) : SimpM Step := do
|
||||
unless e.isAppOfArity declName arity do return .continue
|
||||
let some n ← ($fromExpr e.appFn!.appArg!) | return .continue
|
||||
let some m ← ($fromExpr e.appArg!) | return .continue
|
||||
@[inline] def reduceBin (declName : Name) (arity : Nat) (op : $typeName → $typeName → $typeName) (e : Expr) : OptionT SimpM Step := do
|
||||
guard (e.isAppOfArity declName arity)
|
||||
let n ← ($fromExpr e.appFn!.appArg!)
|
||||
let m ← ($fromExpr e.appArg!)
|
||||
let r := { n with value := op n.value m.value }
|
||||
return .done { expr := $toExpr r }
|
||||
|
||||
@[inline] def reduceBinPred (declName : Name) (arity : Nat) (op : $typeName → $typeName → Bool) (e : Expr) : SimpM Step := do
|
||||
unless e.isAppOfArity declName arity do return .continue
|
||||
let some n ← ($fromExpr e.appFn!.appArg!) | return .continue
|
||||
let some m ← ($fromExpr e.appArg!) | return .continue
|
||||
evalPropStep e (op n.value m.value)
|
||||
@[inline] def reduceBinPred (declName : Name) (arity : Nat) (op : $typeName → $typeName → Bool) (e : Expr) : OptionT SimpM Step := do
|
||||
guard (e.isAppOfArity declName arity)
|
||||
let n ← ($fromExpr e.appFn!.appArg!)
|
||||
let m ← ($fromExpr e.appArg!)
|
||||
let d ← mkDecide e
|
||||
if op n.value m.value then
|
||||
return .done { expr := mkConst ``True, proof? := mkAppN (mkConst ``eq_true_of_decide) #[e, d.appArg!, (← mkEqRefl (mkConst ``true))] }
|
||||
else
|
||||
return .done { expr := mkConst ``False, proof? := mkAppN (mkConst ``eq_false_of_decide) #[e, d.appArg!, (← mkEqRefl (mkConst ``false))] }
|
||||
|
||||
builtin_simproc [simp, seval] $(mkIdent `reduceAdd):ident ((_ + _ : $typeName)) := reduceBin ``HAdd.hAdd 6 (· + ·)
|
||||
builtin_simproc [simp, seval] $(mkIdent `reduceMul):ident ((_ * _ : $typeName)) := reduceBin ``HMul.hMul 6 (· * ·)
|
||||
builtin_simproc [simp, seval] $(mkIdent `reduceSub):ident ((_ - _ : $typeName)) := reduceBin ``HSub.hSub 6 (· - ·)
|
||||
builtin_simproc [simp, seval] $(mkIdent `reduceDiv):ident ((_ / _ : $typeName)) := reduceBin ``HDiv.hDiv 6 (· / ·)
|
||||
builtin_simproc [simp, seval] $(mkIdent `reduceMod):ident ((_ % _ : $typeName)) := reduceBin ``HMod.hMod 6 (· % ·)
|
||||
builtin_simproc $(mkIdent `reduceAdd):ident ((_ + _ : $typeName)) := reduceBin ``HAdd.hAdd 6 (· + ·)
|
||||
builtin_simproc $(mkIdent `reduceMul):ident ((_ * _ : $typeName)) := reduceBin ``HMul.hMul 6 (· * ·)
|
||||
builtin_simproc $(mkIdent `reduceSub):ident ((_ - _ : $typeName)) := reduceBin ``HSub.hSub 6 (· - ·)
|
||||
builtin_simproc $(mkIdent `reduceDiv):ident ((_ / _ : $typeName)) := reduceBin ``HDiv.hDiv 6 (· / ·)
|
||||
builtin_simproc $(mkIdent `reduceMod):ident ((_ % _ : $typeName)) := reduceBin ``HMod.hMod 6 (· % ·)
|
||||
|
||||
builtin_simproc [simp, seval] $(mkIdent `reduceLT):ident (( _ : $typeName) < _) := reduceBinPred ``LT.lt 4 (. < .)
|
||||
builtin_simproc [simp, seval] $(mkIdent `reduceLE):ident (( _ : $typeName) ≤ _) := reduceBinPred ``LE.le 4 (. ≤ .)
|
||||
builtin_simproc [simp, seval] $(mkIdent `reduceGT):ident (( _ : $typeName) > _) := reduceBinPred ``GT.gt 4 (. > .)
|
||||
builtin_simproc [simp, seval] $(mkIdent `reduceGE):ident (( _ : $typeName) ≥ _) := reduceBinPred ``GE.ge 4 (. ≥ .)
|
||||
builtin_simproc $(mkIdent `reduceLT):ident (( _ : $typeName) < _) := reduceBinPred ``LT.lt 4 (. < .)
|
||||
builtin_simproc $(mkIdent `reduceLE):ident (( _ : $typeName) ≤ _) := reduceBinPred ``LE.le 4 (. ≤ .)
|
||||
builtin_simproc $(mkIdent `reduceGT):ident (( _ : $typeName) > _) := reduceBinPred ``GT.gt 4 (. > .)
|
||||
builtin_simproc $(mkIdent `reduceGE):ident (( _ : $typeName) ≥ _) := reduceBinPred ``GE.ge 4 (. ≥ .)
|
||||
|
||||
/-- Return `.done` for UInt values. We don't want to unfold in the symbolic evaluator. -/
|
||||
builtin_simproc [seval] isValue ((OfNat.ofNat _ : $typeName)) := fun e => do
|
||||
unless (e.isAppOfArity ``OfNat.ofNat 3) do return .continue
|
||||
/-- Return `.done` for UInt values. We don't want to unfold them when `ground := true`. -/
|
||||
builtin_simproc isValue ((OfNat.ofNat _ : $typeName)) := fun e => OptionT.run do
|
||||
guard (← getContext).unfoldGround
|
||||
guard (e.isAppOfArity ``OfNat.ofNat 3)
|
||||
return .done { expr := e }
|
||||
|
||||
end $typeName
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
/-
|
||||
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
|
||||
-/
|
||||
import Lean.Meta.Tactic.Simp.Simproc
|
||||
|
||||
namespace Lean.Meta.Simp
|
||||
|
||||
/--
|
||||
Let `result` be the result of evaluating proposition `p`, return a `.done` step where
|
||||
the resulting expression is `True`(`False`) if `result is `true`(`false`), and the
|
||||
proof is uses `Decidable p` and the auxiliary theorems `eq_true_of_decide`/`eq_false_of_decide`.
|
||||
-/
|
||||
def evalPropStep (p : Expr) (result : Bool) : SimpM Step := do
|
||||
let d ← mkDecide p
|
||||
if result then
|
||||
return .done { expr := mkConst ``True, proof? := mkAppN (mkConst ``eq_true_of_decide) #[p, d.appArg!, (← mkEqRefl (mkConst ``true))] }
|
||||
else
|
||||
return .done { expr := mkConst ``False, proof? := mkAppN (mkConst ``eq_false_of_decide) #[p, d.appArg!, (← mkEqRefl (mkConst ``false))] }
|
||||
|
||||
end Lean.Meta.Simp
|
||||
@@ -34,6 +34,11 @@ def Config.updateArith (c : Config) : CoreM Config := do
|
||||
def isOfNatNatLit (e : Expr) : Bool :=
|
||||
e.isAppOfArity ``OfNat.ofNat 3 && e.appFn!.appArg!.isNatLit
|
||||
|
||||
private def reduceProj (e : Expr) : MetaM Expr := do
|
||||
match (← reduceProj? e) with
|
||||
| some e => return e
|
||||
| _ => return e
|
||||
|
||||
private def reduceProjFn? (e : Expr) : SimpM (Option Expr) := do
|
||||
matchConst e.getAppFn (fun _ => pure none) fun cinfo _ => do
|
||||
match (← getProjectionFnInfo? cinfo.name) with
|
||||
@@ -398,12 +403,12 @@ private partial def dsimpImpl (e : Expr) : SimpM Expr := do
|
||||
unless cfg.dsimp do
|
||||
return e
|
||||
let pre (e : Expr) : SimpM TransformStep := do
|
||||
if let Step.visit r ← rewritePre (rflOnly := true) e then
|
||||
if let Step.visit r ← rewritePre e (fun _ => pure none) (rflOnly := true) then
|
||||
if r.expr != e then
|
||||
return .visit r.expr
|
||||
return .continue
|
||||
let post (e : Expr) : SimpM TransformStep := do
|
||||
if let Step.visit r ← rewritePost (rflOnly := true) e then
|
||||
if let some r ← rewritePost? e (fun _ => pure none) (rflOnly := true) then
|
||||
if r.expr != e then
|
||||
return .visit r.expr
|
||||
let mut eNew ← reduce e
|
||||
@@ -428,7 +433,7 @@ def visitFn (e : Expr) : SimpM Result := do
|
||||
|
||||
def congrDefault (e : Expr) : SimpM Result := do
|
||||
if let some result ← tryAutoCongrTheorem? e then
|
||||
result.mkEqTrans (← visitFn result.expr)
|
||||
mkEqTrans result (← visitFn result.expr)
|
||||
else
|
||||
withParent e <| e.withApp fun f args => do
|
||||
congrArgs (← simp f) args
|
||||
@@ -499,7 +504,7 @@ def trySimpCongrTheorem? (c : SimpCongrTheorem) (e : Expr) : SimpM (Option Resul
|
||||
unless modified do
|
||||
trace[Meta.Tactic.simp.congr] "{c.theoremName} not modified"
|
||||
return none
|
||||
unless (← synthesizeArgs (.decl c.theoremName) xs bis) do
|
||||
unless (← synthesizeArgs (.decl c.theoremName) xs bis discharge?) do
|
||||
trace[Meta.Tactic.simp.congr] "{c.theoremName} synthesizeArgs failed"
|
||||
return none
|
||||
let eNew ← instantiateMVars rhs
|
||||
@@ -528,11 +533,14 @@ def congr (e : Expr) : SimpM Result := do
|
||||
congrDefault e
|
||||
|
||||
def simpApp (e : Expr) : SimpM Result := do
|
||||
if isOfNatNatLit e then
|
||||
let e' ← reduceStep e
|
||||
if e' != e then
|
||||
simp e'
|
||||
else if isOfNatNatLit e' then
|
||||
-- Recall that we expand "orphan" kernel nat literals `n` into `ofNat n`
|
||||
return { expr := e }
|
||||
return { expr := e' }
|
||||
else
|
||||
congr e
|
||||
congr e'
|
||||
|
||||
def simpStep (e : Expr) : SimpM Result := do
|
||||
match e with
|
||||
@@ -550,55 +558,54 @@ def simpStep (e : Expr) : SimpM Result := do
|
||||
| .fvar .. => return { expr := (← reduceFVar (← getConfig) (← getSimpTheorems) e) }
|
||||
|
||||
def cacheResult (e : Expr) (cfg : Config) (r : Result) : SimpM Result := do
|
||||
if cfg.memoize && r.cache then
|
||||
if cfg.memoize then
|
||||
let ctx ← readThe Simp.Context
|
||||
let dischargeDepth := ctx.dischargeDepth
|
||||
modify fun s => { s with cache := s.cache.insert e { r with dischargeDepth } }
|
||||
if ctx.unfoldGround then
|
||||
modify fun s => { s with cacheGround := s.cacheGround.insert e { r with dischargeDepth } }
|
||||
else
|
||||
modify fun s => { s with cache := s.cache.insert e { r with dischargeDepth } }
|
||||
return r
|
||||
|
||||
partial def simpLoop (e : Expr) : SimpM Result := withIncRecDepth do
|
||||
partial def simpLoop (e : Expr) (r : Result) : SimpM Result := do
|
||||
let cfg ← getConfig
|
||||
if (← get).numSteps > cfg.maxSteps then
|
||||
throwError "simp failed, maximum number of steps exceeded"
|
||||
else
|
||||
checkSystem "simp"
|
||||
let init := r.expr
|
||||
modify fun s => { s with numSteps := s.numSteps + 1 }
|
||||
match (← pre e) with
|
||||
| .done r => cacheResult e cfg r
|
||||
| .visit r => cacheResult e cfg (← r.mkEqTrans (← simpLoop r.expr))
|
||||
| .continue none => visitPreContinue cfg { expr := e }
|
||||
| .continue (some r) => visitPreContinue cfg r
|
||||
where
|
||||
visitPreContinue (cfg : Config) (r : Result) : SimpM Result := do
|
||||
let eNew ← reduceStep r.expr
|
||||
if eNew != r.expr then
|
||||
let r := { r with expr := eNew }
|
||||
cacheResult e cfg (← r.mkEqTrans (← simpLoop r.expr))
|
||||
else
|
||||
let r ← r.mkEqTrans (← simpStep r.expr)
|
||||
visitPost cfg r
|
||||
visitPost (cfg : Config) (r : Result) : SimpM Result := do
|
||||
match (← post r.expr) with
|
||||
| .done r' => cacheResult e cfg (← r.mkEqTrans r')
|
||||
| .continue none => visitPostContinue cfg r
|
||||
| .visit r' | .continue (some r') => visitPostContinue cfg (← r.mkEqTrans r')
|
||||
visitPostContinue (cfg : Config) (r : Result) : SimpM Result := do
|
||||
let mut r := r
|
||||
unless cfg.singlePass || e == r.expr do
|
||||
r ← r.mkEqTrans (← simpLoop r.expr)
|
||||
cacheResult e cfg r
|
||||
match (← pre r.expr) with
|
||||
| Step.done r' => cacheResult e cfg (← mkEqTrans r r')
|
||||
| Step.visit r' =>
|
||||
let r ← mkEqTrans r r'
|
||||
let r ← mkEqTrans r (← simpStep r.expr)
|
||||
match (← post r.expr) with
|
||||
| Step.done r' => cacheResult e cfg (← mkEqTrans r r')
|
||||
| Step.visit r' =>
|
||||
let r ← mkEqTrans r r'
|
||||
if cfg.singlePass || init == r.expr then
|
||||
cacheResult e cfg r
|
||||
else
|
||||
simpLoop e r
|
||||
|
||||
@[export lean_simp]
|
||||
def simpImpl (e : Expr) : SimpM Result := withIncRecDepth do
|
||||
checkSystem "simp"
|
||||
if (← isProof e) then
|
||||
return { expr := e }
|
||||
let ctx ← getContext
|
||||
trace[Meta.debug] "visit [{ctx.unfoldGround}]: {e}"
|
||||
if ctx.unfoldGround then
|
||||
if (← isType e) then
|
||||
unless (← isProp e) do
|
||||
-- Recall that we set `unfoldGround := false` if `e` is a type that is not a proposition.
|
||||
return (← withTheReader Context (fun ctx => { ctx with unfoldGround := false }) go)
|
||||
go
|
||||
where
|
||||
go : SimpM Result := do
|
||||
let cfg ← getConfig
|
||||
if cfg.memoize then
|
||||
let cache := (← get).cache
|
||||
let cache ← if (← getContext).unfoldGround then pure ((← get).cacheGround) else pure ((← get).cache)
|
||||
if let some result := cache.find? e then
|
||||
/-
|
||||
If the result was cached at a dischargeDepth > the current one, it may not be valid.
|
||||
@@ -607,7 +614,7 @@ where
|
||||
if result.dischargeDepth ≤ (← readThe Simp.Context).dischargeDepth then
|
||||
return result
|
||||
trace[Meta.Tactic.simp.heads] "{repr e.toHeadIndex}"
|
||||
simpLoop e
|
||||
simpLoop e { expr := e }
|
||||
|
||||
@[inline] def withSimpConfig (ctx : Context) (x : MetaM α) : MetaM α :=
|
||||
withConfig (fun c => { c with etaStruct := ctx.config.etaStruct }) <| withReducible x
|
||||
@@ -633,9 +640,9 @@ def dsimpMain (e : Expr) (ctx : Context) (usedSimps : UsedSimps := {}) (methods
|
||||
if ex.isRuntime then throwNestedTacticEx `dsimp ex else throw ex
|
||||
|
||||
end Simp
|
||||
open Simp (UsedSimps SimprocsArray)
|
||||
open Simp (UsedSimps Simprocs)
|
||||
|
||||
def simp (e : Expr) (ctx : Simp.Context) (simprocs : SimprocsArray := #[]) (discharge? : Option Simp.Discharge := none)
|
||||
def simp (e : Expr) (ctx : Simp.Context) (simprocs : Simprocs := {}) (discharge? : Option Simp.Discharge := none)
|
||||
(usedSimps : UsedSimps := {}) : MetaM (Simp.Result × UsedSimps) := do profileitM Exception "simp" (← getOptions) do
|
||||
match discharge? with
|
||||
| none => Simp.main e ctx usedSimps (methods := Simp.mkDefaultMethodsCore simprocs)
|
||||
@@ -646,7 +653,7 @@ def dsimp (e : Expr) (ctx : Simp.Context)
|
||||
Simp.dsimpMain e ctx usedSimps (methods := Simp.mkDefaultMethodsCore {})
|
||||
|
||||
/-- See `simpTarget`. This method assumes `mvarId` is not assigned, and we are already using `mvarId`s local context. -/
|
||||
def simpTargetCore (mvarId : MVarId) (ctx : Simp.Context) (simprocs : SimprocsArray := #[]) (discharge? : Option Simp.Discharge := none)
|
||||
def simpTargetCore (mvarId : MVarId) (ctx : Simp.Context) (simprocs : Simprocs := {}) (discharge? : Option Simp.Discharge := none)
|
||||
(mayCloseGoal := true) (usedSimps : UsedSimps := {}) : MetaM (Option MVarId × UsedSimps) := do
|
||||
let target ← instantiateMVars (← mvarId.getType)
|
||||
let (r, usedSimps) ← simp target ctx simprocs discharge? usedSimps
|
||||
@@ -661,7 +668,7 @@ def simpTargetCore (mvarId : MVarId) (ctx : Simp.Context) (simprocs : SimprocsAr
|
||||
/--
|
||||
Simplify the given goal target (aka type). Return `none` if the goal was closed. Return `some mvarId'` otherwise,
|
||||
where `mvarId'` is the simplified new goal. -/
|
||||
def simpTarget (mvarId : MVarId) (ctx : Simp.Context) (simprocs : SimprocsArray := #[]) (discharge? : Option Simp.Discharge := none)
|
||||
def simpTarget (mvarId : MVarId) (ctx : Simp.Context) (simprocs : Simprocs := {}) (discharge? : Option Simp.Discharge := none)
|
||||
(mayCloseGoal := true) (usedSimps : UsedSimps := {}) : MetaM (Option MVarId × UsedSimps) :=
|
||||
mvarId.withContext do
|
||||
mvarId.checkNotAssigned `simp
|
||||
@@ -696,7 +703,7 @@ def applySimpResultToFVarId (mvarId : MVarId) (fvarId : FVarId) (r : Simp.Result
|
||||
otherwise, where `proof' : prop'` and `prop'` is the simplified `prop`.
|
||||
|
||||
This method assumes `mvarId` is not assigned, and we are already using `mvarId`s local context. -/
|
||||
def simpStep (mvarId : MVarId) (proof : Expr) (prop : Expr) (ctx : Simp.Context) (simprocs : SimprocsArray := #[]) (discharge? : Option Simp.Discharge := none)
|
||||
def simpStep (mvarId : MVarId) (proof : Expr) (prop : Expr) (ctx : Simp.Context) (simprocs : Simprocs := {}) (discharge? : Option Simp.Discharge := none)
|
||||
(mayCloseGoal := true) (usedSimps : UsedSimps := {}) : MetaM (Option (Expr × Expr) × UsedSimps) := do
|
||||
let (r, usedSimps) ← simp prop ctx simprocs discharge? usedSimps
|
||||
return (← applySimpResultToProp mvarId proof prop r (mayCloseGoal := mayCloseGoal), usedSimps)
|
||||
@@ -729,7 +736,7 @@ def applySimpResultToLocalDecl (mvarId : MVarId) (fvarId : FVarId) (r : Simp.Res
|
||||
else
|
||||
applySimpResultToLocalDeclCore mvarId fvarId (← applySimpResultToFVarId mvarId fvarId r mayCloseGoal)
|
||||
|
||||
def simpLocalDecl (mvarId : MVarId) (fvarId : FVarId) (ctx : Simp.Context) (simprocs : SimprocsArray := #[]) (discharge? : Option Simp.Discharge := none)
|
||||
def simpLocalDecl (mvarId : MVarId) (fvarId : FVarId) (ctx : Simp.Context) (simprocs : Simprocs := {}) (discharge? : Option Simp.Discharge := none)
|
||||
(mayCloseGoal := true) (usedSimps : UsedSimps := {}) : MetaM (Option (FVarId × MVarId) × UsedSimps) := do
|
||||
mvarId.withContext do
|
||||
mvarId.checkNotAssigned `simp
|
||||
@@ -737,7 +744,7 @@ def simpLocalDecl (mvarId : MVarId) (fvarId : FVarId) (ctx : Simp.Context) (simp
|
||||
let (r, usedSimps) ← simpStep mvarId (mkFVar fvarId) type ctx simprocs discharge? mayCloseGoal usedSimps
|
||||
return (← applySimpResultToLocalDeclCore mvarId fvarId r, usedSimps)
|
||||
|
||||
def simpGoal (mvarId : MVarId) (ctx : Simp.Context) (simprocs : SimprocsArray := #[]) (discharge? : Option Simp.Discharge := none)
|
||||
def simpGoal (mvarId : MVarId) (ctx : Simp.Context) (simprocs : Simprocs := {}) (discharge? : Option Simp.Discharge := none)
|
||||
(simplifyTarget : Bool := true) (fvarIdsToSimp : Array FVarId := #[])
|
||||
(usedSimps : UsedSimps := {}) : MetaM (Option (Array FVarId × MVarId) × UsedSimps) := do
|
||||
mvarId.withContext do
|
||||
@@ -776,7 +783,7 @@ def simpGoal (mvarId : MVarId) (ctx : Simp.Context) (simprocs : SimprocsArray :=
|
||||
throwError "simp made no progress"
|
||||
return (some (fvarIdsNew, mvarIdNew), usedSimps)
|
||||
|
||||
def simpTargetStar (mvarId : MVarId) (ctx : Simp.Context) (simprocs : SimprocsArray := #[]) (discharge? : Option Simp.Discharge := none)
|
||||
def simpTargetStar (mvarId : MVarId) (ctx : Simp.Context) (simprocs : Simprocs := {}) (discharge? : Option Simp.Discharge := none)
|
||||
(usedSimps : UsedSimps := {}) : MetaM (TacticResultCNM × UsedSimps) := mvarId.withContext do
|
||||
let mut ctx := ctx
|
||||
for h in (← getPropHyps) do
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
/-
|
||||
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
|
||||
-/
|
||||
import Lean.Meta.Tactic.Simp.SimpTheorems
|
||||
import Lean.Meta.Tactic.Simp.Simproc
|
||||
|
||||
namespace Lean.Meta.Simp
|
||||
|
||||
macro (name := _root_.Lean.Parser.Command.registerSimpAttr) doc:(docComment)?
|
||||
"register_simp_attr" id:ident : command => do
|
||||
let str := id.getId.toString
|
||||
let idParser := mkIdentFrom id (`Parser.Attr ++ id.getId)
|
||||
let descr := quote (removeLeadingSpaces (doc.map (·.getDocString) |>.getD s!"simp set for {id.getId.toString}"))
|
||||
let procId := mkIdentFrom id (simpAttrNameToSimprocAttrName id.getId)
|
||||
let procStr := procId.getId.toString
|
||||
let procIdParser := mkIdentFrom procId (`Parser.Attr ++ procId.getId)
|
||||
let procDescr := quote s!"simproc set for {procId.getId.toString}"
|
||||
-- TODO: better docDomment for simprocs
|
||||
`($[$doc:docComment]? initialize ext : SimpExtension ← registerSimpAttr $(quote id.getId) $descr $(quote id.getId)
|
||||
$[$doc:docComment]? syntax (name := $idParser:ident) $(quote str):str (Parser.Tactic.simpPre <|> Parser.Tactic.simpPost)? (prio)? : attr
|
||||
/-- Simplification procedure -/
|
||||
initialize extProc : SimprocExtension ← registerSimprocAttr $(quote procId.getId) $procDescr none $(quote procId.getId)
|
||||
/-- Simplification procedure -/
|
||||
syntax (name := $procIdParser:ident) $(quote procStr):str (Parser.Tactic.simpPre <|> Parser.Tactic.simpPost)? : attr)
|
||||
|
||||
end Lean.Meta.Simp
|
||||
@@ -14,7 +14,14 @@ import Lean.Meta.Tactic.Simp.Simproc
|
||||
|
||||
namespace Lean.Meta.Simp
|
||||
|
||||
def synthesizeArgs (thmId : Origin) (xs : Array Expr) (bis : Array BinderInfo) : SimpM Bool := do
|
||||
def mkEqTrans (r₁ r₂ : Result) : MetaM Result := do
|
||||
match r₁.proof? with
|
||||
| none => return r₂
|
||||
| some p₁ => match r₂.proof? with
|
||||
| none => return { r₂ with proof? := r₁.proof? }
|
||||
| some p₂ => return { r₂ with proof? := (← Meta.mkEqTrans p₁ p₂) }
|
||||
|
||||
def synthesizeArgs (thmId : Origin) (xs : Array Expr) (bis : Array BinderInfo) (discharge? : Expr → SimpM (Option Expr)) : SimpM Bool := do
|
||||
for x in xs, bi in bis do
|
||||
let type ← inferType x
|
||||
-- Note that the binderInfo may be misleading here:
|
||||
@@ -59,10 +66,10 @@ where
|
||||
trace[Meta.Tactic.simp.discharge] "{← ppOrigin thmId}, failed to synthesize instance{indentExpr type}"
|
||||
return false
|
||||
|
||||
private def tryTheoremCore (lhs : Expr) (xs : Array Expr) (bis : Array BinderInfo) (val : Expr) (type : Expr) (e : Expr) (thm : SimpTheorem) (numExtraArgs : Nat) : SimpM (Option Result) := do
|
||||
private def tryTheoremCore (lhs : Expr) (xs : Array Expr) (bis : Array BinderInfo) (val : Expr) (type : Expr) (e : Expr) (thm : SimpTheorem) (numExtraArgs : Nat) (discharge? : Expr → SimpM (Option Expr)) : SimpM (Option Result) := do
|
||||
let rec go (e : Expr) : SimpM (Option Result) := do
|
||||
if (← isDefEq lhs e) then
|
||||
unless (← synthesizeArgs thm.origin xs bis) do
|
||||
unless (← synthesizeArgs thm.origin xs bis discharge?) do
|
||||
return none
|
||||
let proof? ← if thm.rfl then
|
||||
pure none
|
||||
@@ -109,36 +116,36 @@ private def tryTheoremCore (lhs : Expr) (xs : Array Expr) (bis : Array BinderInf
|
||||
return none
|
||||
r.addExtraArgs extraArgs
|
||||
|
||||
def tryTheoremWithExtraArgs? (e : Expr) (thm : SimpTheorem) (numExtraArgs : Nat) : SimpM (Option Result) :=
|
||||
def tryTheoremWithExtraArgs? (e : Expr) (thm : SimpTheorem) (numExtraArgs : Nat) (discharge? : Expr → SimpM (Option Expr)) : SimpM (Option Result) :=
|
||||
withNewMCtxDepth do
|
||||
let val ← thm.getValue
|
||||
let type ← inferType val
|
||||
let (xs, bis, type) ← forallMetaTelescopeReducing type
|
||||
let type ← whnf (← instantiateMVars type)
|
||||
let lhs := type.appFn!.appArg!
|
||||
tryTheoremCore lhs xs bis val type e thm numExtraArgs
|
||||
tryTheoremCore lhs xs bis val type e thm numExtraArgs discharge?
|
||||
|
||||
def tryTheorem? (e : Expr) (thm : SimpTheorem) : SimpM (Option Result) := do
|
||||
def tryTheorem? (e : Expr) (thm : SimpTheorem) (discharge? : Expr → SimpM (Option Expr)) : SimpM (Option Result) := do
|
||||
withNewMCtxDepth do
|
||||
let val ← thm.getValue
|
||||
let type ← inferType val
|
||||
let (xs, bis, type) ← forallMetaTelescopeReducing type
|
||||
let type ← whnf (← instantiateMVars type)
|
||||
let lhs := type.appFn!.appArg!
|
||||
match (← tryTheoremCore lhs xs bis val type e thm 0) with
|
||||
match (← tryTheoremCore lhs xs bis val type e thm 0 discharge?) with
|
||||
| some result => return some result
|
||||
| none =>
|
||||
let lhsNumArgs := lhs.getAppNumArgs
|
||||
let eNumArgs := e.getAppNumArgs
|
||||
if eNumArgs > lhsNumArgs then
|
||||
tryTheoremCore lhs xs bis val type e thm (eNumArgs - lhsNumArgs)
|
||||
tryTheoremCore lhs xs bis val type e thm (eNumArgs - lhsNumArgs) discharge?
|
||||
else
|
||||
return none
|
||||
|
||||
/--
|
||||
Remark: the parameter tag is used for creating trace messages. It is irrelevant otherwise.
|
||||
-/
|
||||
def rewrite? (e : Expr) (s : SimpTheoremTree) (erased : PHashSet Origin) (tag : String) (rflOnly : Bool) : SimpM (Option Result) := do
|
||||
def rewrite? (e : Expr) (s : SimpTheoremTree) (erased : PHashSet Origin) (discharge? : Expr → SimpM (Option Expr)) (tag : String) (rflOnly : Bool) : SimpM (Option Result) := do
|
||||
let candidates ← s.getMatchWithExtra e (getDtConfig (← getConfig))
|
||||
if candidates.isEmpty then
|
||||
trace[Debug.Meta.Tactic.simp] "no theorems found for {tag}-rewriting {e}"
|
||||
@@ -147,7 +154,7 @@ def rewrite? (e : Expr) (s : SimpTheoremTree) (erased : PHashSet Origin) (tag :
|
||||
let candidates := candidates.insertionSort fun e₁ e₂ => e₁.1.priority > e₂.1.priority
|
||||
for (thm, numExtraArgs) in candidates do
|
||||
unless inErasedSet thm || (rflOnly && !thm.rfl) do
|
||||
if let some result ← tryTheoremWithExtraArgs? e thm numExtraArgs then
|
||||
if let some result ← tryTheoremWithExtraArgs? e thm numExtraArgs discharge? then
|
||||
trace[Debug.Meta.Tactic.simp] "rewrite result {e} => {result.expr}"
|
||||
return some result
|
||||
return none
|
||||
@@ -155,63 +162,71 @@ where
|
||||
inErasedSet (thm : SimpTheorem) : Bool :=
|
||||
erased.contains thm.origin
|
||||
|
||||
-- TODO: workaround for `Expr.constructorApp?` limitations. We should handle `OfNat.ofNat` there
|
||||
private def reduceOfNatNat (e : Expr) : MetaM Expr := do
|
||||
unless e.isAppOfArity ``OfNat.ofNat 3 do
|
||||
return e
|
||||
unless (← whnfD (e.getArg! 0)).isConstOf ``Nat do
|
||||
return e
|
||||
return e.getArg! 1
|
||||
@[inline] def andThen' (s : Step) (f? : Expr → SimpM Step) : SimpM Step := do
|
||||
match s with
|
||||
| Step.done _ => return s
|
||||
| Step.visit r =>
|
||||
let s' ← f? r.expr
|
||||
return s'.updateResult (← mkEqTrans r s'.result)
|
||||
|
||||
def simpCtorEq : Simproc := fun e => withReducibleAndInstances do
|
||||
@[inline] def andThen (s : Step) (f? : Expr → SimpM (Option Step)) : SimpM Step := do
|
||||
match s with
|
||||
| Step.done _ => return s
|
||||
| Step.visit r =>
|
||||
if let some s' ← f? r.expr then
|
||||
return s'.updateResult (← mkEqTrans r s'.result)
|
||||
else
|
||||
return s
|
||||
|
||||
def rewriteCtorEq? (e : Expr) : MetaM (Option Result) := withReducibleAndInstances do
|
||||
match e.eq? with
|
||||
| none => return .continue
|
||||
| none => return none
|
||||
| some (_, lhs, rhs) =>
|
||||
let lhs ← reduceOfNatNat (← whnf lhs)
|
||||
let rhs ← reduceOfNatNat (← whnf rhs)
|
||||
let lhs ← whnf lhs
|
||||
let rhs ← whnf rhs
|
||||
let env ← getEnv
|
||||
match lhs.constructorApp? env, rhs.constructorApp? env with
|
||||
| some (c₁, _), some (c₂, _) =>
|
||||
if c₁.name != c₂.name then
|
||||
withLocalDeclD `h e fun h =>
|
||||
return .done { expr := mkConst ``False, proof? := (← withDefault <| mkEqFalse' (← mkLambdaFVars #[h] (← mkNoConfusion (mkConst ``False) h))) }
|
||||
return some { expr := mkConst ``False, proof? := (← mkEqFalse' (← mkLambdaFVars #[h] (← mkNoConfusion (mkConst ``False) h))) }
|
||||
else
|
||||
return .continue
|
||||
| _, _ => return .continue
|
||||
return none
|
||||
| _, _ => return none
|
||||
|
||||
@[inline] def simpUsingDecide : Simproc := fun e => do
|
||||
unless (← getConfig).decide do
|
||||
return .continue
|
||||
@[inline] def tryRewriteCtorEq? (e : Expr) : SimpM (Option Step) := do
|
||||
match (← rewriteCtorEq? e) with
|
||||
| some r => return Step.done r
|
||||
| none => return none
|
||||
|
||||
def rewriteUsingDecide? (e : Expr) : MetaM (Option Result) := withReducibleAndInstances do
|
||||
if e.hasFVar || e.hasMVar || e.consumeMData.isConstOf ``True || e.consumeMData.isConstOf ``False then
|
||||
return .continue
|
||||
try
|
||||
let d ← mkDecide e
|
||||
let r ← withDefault <| whnf d
|
||||
if r.isConstOf ``true then
|
||||
return .done { expr := mkConst ``True, proof? := mkAppN (mkConst ``eq_true_of_decide) #[e, d.appArg!, (← mkEqRefl (mkConst ``true))] }
|
||||
else if r.isConstOf ``false then
|
||||
return .done { expr := mkConst ``False, proof? := mkAppN (mkConst ``eq_false_of_decide) #[e, d.appArg!, (← mkEqRefl (mkConst ``false))] }
|
||||
else
|
||||
return .continue
|
||||
catch _ =>
|
||||
return .continue
|
||||
|
||||
def simpArith (e : Expr) : SimpM Step := do
|
||||
unless (← getConfig).arith do
|
||||
return .continue
|
||||
if Linear.isLinearCnstr e then
|
||||
let some (e', h) ← Linear.Nat.simpCnstr? e
|
||||
| return .continue
|
||||
return .visit { expr := e', proof? := h }
|
||||
else if Linear.isLinearTerm e then
|
||||
if Linear.parentIsTarget (← getContext).parent? then
|
||||
-- We mark `cache := false` to ensure we do not miss simplifications.
|
||||
return .continue (some { expr := e, cache := false })
|
||||
let some (e', h) ← Linear.Nat.simpExpr? e
|
||||
| return .continue
|
||||
return .visit { expr := e', proof? := h }
|
||||
return none
|
||||
else
|
||||
return .continue
|
||||
try
|
||||
let d ← mkDecide e
|
||||
let r ← withDefault <| whnf d
|
||||
if r.isConstOf ``true then
|
||||
return some { expr := mkConst ``True, proof? := mkAppN (mkConst ``eq_true_of_decide) #[e, d.appArg!, (← mkEqRefl (mkConst ``true))] }
|
||||
else if r.isConstOf ``false then
|
||||
return some { expr := mkConst ``False, proof? := mkAppN (mkConst ``eq_false_of_decide) #[e, d.appArg!, (← mkEqRefl (mkConst ``false))] }
|
||||
else
|
||||
return none
|
||||
catch _ =>
|
||||
return none
|
||||
|
||||
@[inline] def tryRewriteUsingDecide? (e : Expr) : SimpM (Option Step) := do
|
||||
if (← getConfig).decide then
|
||||
match (← rewriteUsingDecide? e) with
|
||||
| some r => return Step.done r
|
||||
| none => return none
|
||||
else
|
||||
return none
|
||||
|
||||
def simpArith? (e : Expr) : SimpM (Option Step) := do
|
||||
if !(← getConfig).arith then return none
|
||||
let some (e', h) ← Linear.simp? e (← getContext).parent? | return none
|
||||
return Step.visit { expr := e', proof? := h }
|
||||
|
||||
/--
|
||||
Given a match-application `e` with `MatcherInfo` `info`, return `some result`
|
||||
@@ -249,162 +264,122 @@ def simpMatchDiscrs? (info : MatcherInfo) (e : Expr) : SimpM (Option Result) :=
|
||||
r ← mkCongrFun r arg
|
||||
return some r
|
||||
|
||||
def simpMatchCore (matcherName : Name) (e : Expr) : SimpM Step := do
|
||||
def simpMatchCore? (matcherName : Name) (e : Expr) (discharge? : Expr → SimpM (Option Expr)) : SimpM (Option Step) := do
|
||||
for matchEq in (← Match.getEquationsFor matcherName).eqnNames do
|
||||
-- Try lemma
|
||||
match (← withReducible <| Simp.tryTheorem? e { origin := .decl matchEq, proof := mkConst matchEq, rfl := (← isRflTheorem matchEq) }) with
|
||||
match (← withReducible <| Simp.tryTheorem? e { origin := .decl matchEq, proof := mkConst matchEq, rfl := (← isRflTheorem matchEq) } discharge?) with
|
||||
| none => pure ()
|
||||
| some r => return .visit r
|
||||
return .continue
|
||||
| some r => return some (Simp.Step.done r)
|
||||
return none
|
||||
|
||||
def simpMatch : Simproc := fun e => do
|
||||
unless (← getConfig).iota do
|
||||
return .continue
|
||||
if let some e ← reduceRecMatcher? e then
|
||||
return .visit { expr := e }
|
||||
let .const declName _ := e.getAppFn
|
||||
| return .continue
|
||||
let some info ← getMatcherInfo? declName
|
||||
| return .continue
|
||||
if let some r ← simpMatchDiscrs? info e then
|
||||
return .visit r
|
||||
simpMatchCore declName e
|
||||
|
||||
def rewritePre (rflOnly := false) : Simproc := fun e => do
|
||||
for thms in (← getContext).simpTheorems do
|
||||
if let some r ← rewrite? e thms.pre thms.erased (tag := "pre") (rflOnly := rflOnly) then
|
||||
return .visit r
|
||||
return .continue
|
||||
|
||||
def rewritePost (rflOnly := false) : Simproc := fun e => do
|
||||
for thms in (← getContext).simpTheorems do
|
||||
if let some r ← rewrite? e thms.post thms.erased (tag := "post") (rflOnly := rflOnly) then
|
||||
return .visit r
|
||||
return .continue
|
||||
|
||||
/--
|
||||
Discharge procedure for the ground/symbolic evaluator.
|
||||
-/
|
||||
def dischargeGround (e : Expr) : SimpM (Option Expr) := do
|
||||
trace[Meta.Tactic.simp.discharge] ">> discharge?: {e}"
|
||||
let r ← simp e
|
||||
if r.expr.consumeMData.isConstOf ``True then
|
||||
try
|
||||
return some (← mkOfEqTrue (← r.getProof))
|
||||
catch _ =>
|
||||
def simpMatch? (discharge? : Expr → SimpM (Option Expr)) (e : Expr) : SimpM (Option Step) := do
|
||||
if (← getConfig).iota then
|
||||
if let some e ← reduceRecMatcher? e then
|
||||
return some (.visit { expr := e })
|
||||
let .const declName _ := e.getAppFn
|
||||
| return none
|
||||
if let some info ← getMatcherInfo? declName then
|
||||
if let some r ← simpMatchDiscrs? info e then
|
||||
return some (.visit r)
|
||||
simpMatchCore? declName e discharge?
|
||||
else
|
||||
return none
|
||||
else
|
||||
return none
|
||||
|
||||
def rewritePre (e : Expr) (discharge? : Expr → SimpM (Option Expr)) (rflOnly := false) : SimpM Step := do
|
||||
for thms in (← getContext).simpTheorems do
|
||||
if let some r ← rewrite? e thms.pre thms.erased discharge? (tag := "pre") (rflOnly := rflOnly) then
|
||||
return Step.visit r
|
||||
return Step.visit { expr := e }
|
||||
|
||||
partial def preDefault (e : Expr) (discharge? : Expr → SimpM (Option Expr)) : SimpM Step := do
|
||||
let s ← rewritePre e discharge?
|
||||
let s ← andThen s (simpMatch? discharge?)
|
||||
let s ← andThen s preSimproc?
|
||||
let s ← andThen s tryRewriteUsingDecide?
|
||||
if s.result.expr == e then
|
||||
return s
|
||||
else
|
||||
andThen s (preDefault · discharge?)
|
||||
|
||||
def rewritePost? (e : Expr) (discharge? : Expr → SimpM (Option Expr)) (rflOnly := false) : SimpM (Option Result) := do
|
||||
for thms in (← getContext).simpTheorems do
|
||||
if let some r ← rewrite? e thms.post thms.erased discharge? (tag := "post") (rflOnly := rflOnly) then
|
||||
return r
|
||||
return none
|
||||
|
||||
/--
|
||||
Try to unfold ground term in the ground/symbolic evaluator.
|
||||
Try to unfold ground term when `Context.unfoldGround := true`.
|
||||
-/
|
||||
def sevalGround : Simproc := fun e => do
|
||||
def unfoldGround? (discharge? : Expr → SimpM (Option Expr)) (e : Expr) : SimpM (Option Step) := do
|
||||
-- Ground term unfolding is disabled.
|
||||
unless (← getContext).unfoldGround do return none
|
||||
-- `e` is not a ground term.
|
||||
unless !e.hasExprMVar && !e.hasFVar do return .continue
|
||||
unless !e.hasExprMVar && !e.hasFVar do return none
|
||||
trace[Meta.debug] "unfoldGround? {e}"
|
||||
-- Check whether `e` is a constant application
|
||||
let f := e.getAppFn
|
||||
let .const declName lvls := f | return .continue
|
||||
let .const declName lvls := f | return none
|
||||
-- If declaration has been marked to not be unfolded, return none.
|
||||
let ctx ← getContext
|
||||
if ctx.simpTheorems.isErased (.decl declName) then return .continue
|
||||
if ctx.simpTheorems.isErased (.decl declName) then return none
|
||||
-- Matcher applications should have been reduced before we get here.
|
||||
if (← isMatcher declName) then return .continue
|
||||
if (← isMatcher declName) then return none
|
||||
if let some eqns ← withDefault <| getEqnsFor? declName then
|
||||
-- `declName` has equation theorems associated with it.
|
||||
for eqn in eqns do
|
||||
-- TODO: cache SimpTheorem to avoid calls to `isRflTheorem`
|
||||
if let some result ← Simp.tryTheorem? e { origin := .decl eqn, proof := mkConst eqn, rfl := (← isRflTheorem eqn) } then
|
||||
if let some result ← Simp.tryTheorem? e { origin := .decl eqn, proof := mkConst eqn, rfl := (← isRflTheorem eqn) } discharge? then
|
||||
trace[Meta.Tactic.simp.ground] "unfolded, {e} => {result.expr}"
|
||||
return .visit result
|
||||
return .continue
|
||||
return some (.visit result)
|
||||
return none
|
||||
-- `declName` does not have equation theorems associated with it.
|
||||
if e.isConst then
|
||||
-- We don't unfold constants that take arguments
|
||||
if let .forallE .. ← whnfD (← inferType e) then
|
||||
return .continue
|
||||
return none
|
||||
let info ← getConstInfo declName
|
||||
unless info.hasValue && info.levelParams.length == lvls.length do return .continue
|
||||
unless info.hasValue && info.levelParams.length == lvls.length do return none
|
||||
let fBody ← instantiateValueLevelParams info lvls
|
||||
let eNew := fBody.betaRev e.getAppRevArgs (useZeta := true)
|
||||
trace[Meta.Tactic.simp.ground] "delta, {e} => {eNew}"
|
||||
return .visit { expr := eNew }
|
||||
return some (.visit { expr := eNew })
|
||||
|
||||
partial def preSEval (s : SimprocsArray) : Simproc :=
|
||||
rewritePre >>
|
||||
simpMatch >>
|
||||
userPreSimprocs s
|
||||
def postDefault (e : Expr) (discharge? : Expr → SimpM (Option Expr)) : SimpM Step := do
|
||||
/-
|
||||
Remark 1:
|
||||
`rewritePost?` used to return a `Step`, and we would try other methods even if it succeeded in rewriting the term.
|
||||
This behavior was problematic, especially when `ground := true`, because we have rewriting rules such as
|
||||
`List.append as bs = as ++ bs`, which are rules for folding polymorphic functions.
|
||||
This type of rule can trigger nontermination in the context of `ground := true`.
|
||||
For example, the method `unfoldGround?` would reduce `[] ++ [1]` to `List.append [] [1]`, and
|
||||
`rewritePost` would refold it back to `[] ++ [1]`, leading to an endless loop.
|
||||
|
||||
def postSEval (s : SimprocsArray) : Simproc :=
|
||||
rewritePost >>
|
||||
userPostSimprocs s >>
|
||||
sevalGround >>
|
||||
simpCtorEq
|
||||
Initially, we considered always reducing ground terms first. However, this approach would
|
||||
prevent us from adding auxiliary lemmas that could short-circuit the evaluation.
|
||||
Ultimately, we settled on the following compromise: if a `rewritePost?` succeeds and produces a result `r`,
|
||||
we return with `.visit r`. This allows pre-methods to be applied again along with other rewriting rules.
|
||||
This strategy helps avoid non-termination, as we have `[simp]` theorems specifically for reducing `List.append`
|
||||
```lean
|
||||
@[simp] theorem nil_append (as : List α) : [] ++ as = as := ...
|
||||
@[simp] theorem cons_append (a : α) (as bs : List α) : (a::as) ++ bs = a::(as ++ bs) := ...
|
||||
```
|
||||
|
||||
def mkSEvalMethods : CoreM Methods := do
|
||||
let s ← getSEvalSimprocs
|
||||
return {
|
||||
pre := preSEval #[s]
|
||||
post := postSEval #[s]
|
||||
discharge? := dischargeGround
|
||||
}
|
||||
|
||||
def mkSEvalContext : CoreM Context := do
|
||||
let s ← getSEvalTheorems
|
||||
let c ← Meta.getSimpCongrTheorems
|
||||
return { simpTheorems := #[s], congrTheorems := c, config := { ground := true } }
|
||||
|
||||
/--
|
||||
Invoke ground/symbolic evaluator from `simp`.
|
||||
It uses the `seval` theorems and simprocs.
|
||||
-/
|
||||
def seval (e : Expr) : SimpM Result := do
|
||||
let m ← mkSEvalMethods
|
||||
let ctx ← mkSEvalContext
|
||||
let cacheSaved := (← get).cache
|
||||
let usedTheoremsSaved := (← get).usedTheorems
|
||||
try
|
||||
withReader (fun _ => m.toMethodsRef) do
|
||||
withTheReader Simp.Context (fun _ => ctx) do
|
||||
modify fun s => { s with cache := {}, usedTheorems := {} }
|
||||
simp e
|
||||
finally
|
||||
modify fun s => { s with cache := cacheSaved, usedTheorems := usedTheoremsSaved }
|
||||
|
||||
/--
|
||||
Try to unfold ground term in the ground/symbolic evaluator.
|
||||
-/
|
||||
def simpGround : Simproc := fun e => do
|
||||
-- Ground term unfolding is disabled.
|
||||
unless (← getConfig).ground do return .continue
|
||||
-- `e` is not a ground term.
|
||||
unless !e.hasExprMVar && !e.hasFVar do return .continue
|
||||
-- Check whether `e` is a constant application
|
||||
let f := e.getAppFn
|
||||
let .const declName _ := f | return .continue
|
||||
-- If declaration has been marked to not be unfolded, return none.
|
||||
let ctx ← getContext
|
||||
if ctx.simpTheorems.isErased (.decl declName) then return .continue
|
||||
-- Matcher applications should have been reduced before we get here.
|
||||
if (← isMatcher declName) then return .continue
|
||||
trace[Meta.Tactic.Simp.ground] "seval: {e}"
|
||||
let r ← seval e
|
||||
trace[Meta.Tactic.Simp.ground] "seval result: {e} => {r.expr}"
|
||||
return .done r
|
||||
|
||||
def preDefault (s : SimprocsArray) : Simproc :=
|
||||
rewritePre >>
|
||||
simpMatch >>
|
||||
userPreSimprocs s >>
|
||||
simpUsingDecide
|
||||
|
||||
def postDefault (s : SimprocsArray) : Simproc :=
|
||||
rewritePost >>
|
||||
userPostSimprocs s >>
|
||||
simpGround >>
|
||||
simpArith >>
|
||||
simpCtorEq >>
|
||||
simpUsingDecide
|
||||
Remark 2:
|
||||
In the simplifier, the ground value for some inductive types is *not* a constructor application.
|
||||
Examples: `Nat`, `Int`, `Fin _`, `UInt?`. These types are represented using `OfNat.ofNat`.
|
||||
To ensure `unfoldGround?` does not unfold `OfNat.ofNat` applications for these types, we
|
||||
have `simproc` that return `.done ..` for these ground values. Thus, `unfoldGround?` is not
|
||||
even tried. Alternative design: we could add an extensible ground value predicate.
|
||||
-/
|
||||
if let some r ← rewritePost? e discharge? then
|
||||
return .visit r
|
||||
let s ← andThen (.visit { expr := e }) postSimproc?
|
||||
let s ← andThen s (unfoldGround? discharge?)
|
||||
let s ← andThen s simpArith?
|
||||
let s ← andThen s tryRewriteUsingDecide?
|
||||
andThen s tryRewriteCtorEq?
|
||||
|
||||
/--
|
||||
Return true if `e` is of the form `(x : α) → ... → s = t → ... → False`
|
||||
@@ -494,18 +469,19 @@ def dischargeDefault? (e : Expr) : SimpM (Option Expr) := do
|
||||
|
||||
abbrev Discharge := Expr → SimpM (Option Expr)
|
||||
|
||||
def mkMethods (s : SimprocsArray) (discharge? : Discharge) : Methods := {
|
||||
pre := preDefault s
|
||||
post := postDefault s
|
||||
def mkMethods (simprocs : Simprocs) (discharge? : Discharge) : Methods := {
|
||||
pre := (preDefault · discharge?)
|
||||
post := (postDefault · discharge?)
|
||||
discharge? := discharge?
|
||||
simprocs := simprocs
|
||||
}
|
||||
|
||||
def mkDefaultMethodsCore (simprocs : SimprocsArray) : Methods :=
|
||||
def mkDefaultMethodsCore (simprocs : Simprocs) : Methods :=
|
||||
mkMethods simprocs dischargeDefault?
|
||||
|
||||
def mkDefaultMethods : CoreM Methods := do
|
||||
if simprocs.get (← getOptions) then
|
||||
return mkDefaultMethodsCore #[(← getSimprocs)]
|
||||
return mkDefaultMethodsCore (← getSimprocs)
|
||||
else
|
||||
return mkDefaultMethodsCore {}
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ import Lean.Meta.Tactic.Simp.Main
|
||||
|
||||
namespace Lean.Meta
|
||||
|
||||
open Simp (UsedSimps SimprocsArray)
|
||||
open Simp (UsedSimps)
|
||||
|
||||
namespace SimpAll
|
||||
|
||||
@@ -27,7 +27,7 @@ structure State where
|
||||
mvarId : MVarId
|
||||
entries : Array Entry := #[]
|
||||
ctx : Simp.Context
|
||||
simprocs : SimprocsArray
|
||||
simprocs : Simprocs
|
||||
usedSimps : UsedSimps := {}
|
||||
|
||||
abbrev M := StateRefT State MetaM
|
||||
@@ -142,7 +142,7 @@ def main : M (Option MVarId) := do
|
||||
|
||||
end SimpAll
|
||||
|
||||
def simpAll (mvarId : MVarId) (ctx : Simp.Context) (simprocs : SimprocsArray := #[]) (usedSimps : UsedSimps := {}) : MetaM (Option MVarId × UsedSimps) := do
|
||||
def simpAll (mvarId : MVarId) (ctx : Simp.Context) (simprocs : Simprocs := {}) (usedSimps : UsedSimps := {}) : MetaM (Option MVarId × UsedSimps) := do
|
||||
mvarId.withContext do
|
||||
let (r, s) ← SimpAll.main.run { mvarId, ctx, usedSimps, simprocs }
|
||||
if let .some mvarIdNew := r then
|
||||
|
||||
@@ -117,7 +117,7 @@ builtin_initialize
|
||||
discard <| addSimpCongrTheorem declName attrKind prio |>.run {} {}
|
||||
}
|
||||
|
||||
def getSimpCongrTheorems : CoreM SimpCongrTheorems :=
|
||||
def getSimpCongrTheorems : MetaM SimpCongrTheorems :=
|
||||
return congrExtension.getState (← getEnv)
|
||||
|
||||
end Lean.Meta
|
||||
|
||||
@@ -417,17 +417,12 @@ def registerSimpAttr (attrName : Name) (attrDescr : String)
|
||||
|
||||
builtin_initialize simpExtension : SimpExtension ← registerSimpAttr `simp "simplification theorem"
|
||||
|
||||
builtin_initialize sevalSimpExtension : SimpExtension ← registerSimpAttr `seval "symbolic evaluator theorem"
|
||||
|
||||
def getSimpExtension? (attrName : Name) : IO (Option SimpExtension) :=
|
||||
return (← simpExtensionMapRef.get).find? attrName
|
||||
|
||||
def getSimpTheorems : CoreM SimpTheorems :=
|
||||
simpExtension.getTheorems
|
||||
|
||||
def getSEvalTheorems : CoreM SimpTheorems :=
|
||||
sevalSimpExtension.getTheorems
|
||||
|
||||
/-- Auxiliary method for adding a global declaration to a `SimpTheorems` datastructure. -/
|
||||
def SimpTheorems.addConst (s : SimpTheorems) (declName : Name) (post := true) (inv := false) (prio : Nat := eval_prio default) : MetaM SimpTheorems := do
|
||||
let s := { s with erased := s.erased.erase (.decl declName post inv) }
|
||||
@@ -496,4 +491,14 @@ def SimpTheoremsArray.isDeclToUnfold (thmsArray : SimpTheoremsArray) (declName :
|
||||
def SimpTheoremsArray.isLetDeclToUnfold (thmsArray : SimpTheoremsArray) (fvarId : FVarId) : Bool :=
|
||||
thmsArray.any fun thms => thms.isLetDeclToUnfold fvarId
|
||||
|
||||
end Lean.Meta
|
||||
macro (name := _root_.Lean.Parser.Command.registerSimpAttr) doc:(docComment)?
|
||||
"register_simp_attr" id:ident : command => do
|
||||
let str := id.getId.toString
|
||||
let idParser := mkIdentFrom id (`Parser.Attr ++ id.getId)
|
||||
let descr := quote (removeLeadingSpaces (doc.map (·.getDocString) |>.getD s!"simp set for {id.getId.toString}"))
|
||||
`($[$doc:docComment]? initialize ext : SimpExtension ← registerSimpAttr $(quote id.getId) $descr $(quote id.getId)
|
||||
$[$doc:docComment]? syntax (name := $idParser:ident) $(quote str):str (Parser.Tactic.simpPre <|> Parser.Tactic.simpPost)? (prio)? : attr)
|
||||
|
||||
end Meta
|
||||
|
||||
end Lean
|
||||
|
||||
@@ -4,30 +4,16 @@ Released under Apache 2.0 license as described in the file LICENSE.
|
||||
Authors: Leonardo de Moura
|
||||
-/
|
||||
import Lean.ScopedEnvExtension
|
||||
import Lean.Compiler.InitAttr
|
||||
import Lean.Meta.DiscrTree
|
||||
import Lean.Meta.Tactic.Simp.Types
|
||||
|
||||
namespace Lean.Meta.Simp
|
||||
|
||||
/--
|
||||
Builtin simproc declaration extension state.
|
||||
|
||||
It contains:
|
||||
- The discrimination tree keys for each simproc (including symbolic evaluation procedures) name.
|
||||
- The actual procedure associated with a name.
|
||||
-/
|
||||
structure BuiltinSimprocs where
|
||||
keys : HashMap Name (Array SimpTheoremKey) := {}
|
||||
procs : HashMap Name Simproc := {}
|
||||
deriving Inhabited
|
||||
|
||||
/--
|
||||
This global reference is populated using the command `builtin_simproc_pattern%`.
|
||||
|
||||
This reference is then used to process the attributes `builtin_simproc` and `builtin_sevalproc`.
|
||||
Both attributes need the keys and the actual procedure registered using the command `builtin_simproc_pattern%`.
|
||||
-/
|
||||
builtin_initialize builtinSimprocDeclsRef : IO.Ref BuiltinSimprocs ← IO.mkRef {}
|
||||
|
||||
structure SimprocDecl where
|
||||
@@ -73,11 +59,6 @@ def isBuiltinSimproc (declName : Name) : CoreM Bool := do
|
||||
def isSimproc (declName : Name) : CoreM Bool :=
|
||||
return (← getSimprocDeclKeys? declName).isSome
|
||||
|
||||
/--
|
||||
Given a declaration name `declName`, store the discrimination tree keys and the actual procedure.
|
||||
|
||||
This method is invoked by the command `builtin_simproc_pattern%` elaborator.
|
||||
-/
|
||||
def registerBuiltinSimproc (declName : Name) (key : Array SimpTheoremKey) (proc : Simproc) : IO Unit := do
|
||||
unless (← initializing) do
|
||||
throw (IO.userError s!"invalid builtin simproc declaration, it can only be registered during initialization")
|
||||
@@ -103,12 +84,8 @@ instance : ToFormat SimprocEntry where
|
||||
def Simprocs.erase (s : Simprocs) (declName : Name) : Simprocs :=
|
||||
{ s with erased := s.erased.insert declName, simprocNames := s.simprocNames.erase declName }
|
||||
|
||||
/-- Builtin simprocs. -/
|
||||
builtin_initialize builtinSimprocsRef : IO.Ref Simprocs ← IO.mkRef {}
|
||||
|
||||
/-- Builtin symbolic evaluation procedures. -/
|
||||
builtin_initialize builtinSEvalprocsRef : IO.Ref Simprocs ← IO.mkRef {}
|
||||
|
||||
abbrev SimprocExtension := ScopedEnvExtension SimprocOLeanEntry SimprocEntry Simprocs
|
||||
|
||||
unsafe def getSimprocFromDeclImpl (declName : Name) : ImportM Simproc := do
|
||||
@@ -123,34 +100,41 @@ opaque getSimprocFromDecl (declName: Name) : ImportM Simproc
|
||||
def toSimprocEntry (e : SimprocOLeanEntry) : ImportM SimprocEntry := do
|
||||
return { toSimprocOLeanEntry := e, proc := (← getSimprocFromDecl e.declName) }
|
||||
|
||||
def eraseSimprocAttr (ext : SimprocExtension) (declName : Name) : AttrM Unit := do
|
||||
let s := ext.getState (← getEnv)
|
||||
builtin_initialize simprocExtension : SimprocExtension ←
|
||||
registerScopedEnvExtension {
|
||||
name := `simproc
|
||||
mkInitial := builtinSimprocsRef.get
|
||||
ofOLeanEntry := fun _ => toSimprocEntry
|
||||
toOLeanEntry := fun e => e.toSimprocOLeanEntry
|
||||
addEntry := fun s e =>
|
||||
if e.post then
|
||||
{ s with post := s.post.insertCore e.keys e }
|
||||
else
|
||||
{ s with pre := s.pre.insertCore e.keys e }
|
||||
}
|
||||
|
||||
def eraseSimprocAttr (declName : Name) : AttrM Unit := do
|
||||
let s := simprocExtension.getState (← getEnv)
|
||||
unless s.simprocNames.contains declName do
|
||||
throwError "'{declName}' does not have [simproc] attribute"
|
||||
modifyEnv fun env => ext.modifyState env fun s => s.erase declName
|
||||
modifyEnv fun env => simprocExtension.modifyState env fun s => s.erase declName
|
||||
|
||||
def addSimprocAttr (ext : SimprocExtension) (declName : Name) (kind : AttributeKind) (post : Bool) : CoreM Unit := do
|
||||
def addSimprocAttr (declName : Name) (kind : AttributeKind) (post : Bool) : CoreM Unit := do
|
||||
let proc ← getSimprocFromDecl declName
|
||||
let some keys ← getSimprocDeclKeys? declName |
|
||||
throwError "invalid [simproc] attribute, '{declName}' is not a simproc"
|
||||
ext.add { declName, post, keys, proc } kind
|
||||
simprocExtension.add { declName, post, keys, proc } kind
|
||||
|
||||
/--
|
||||
Implements attributes `builtin_simproc` and `builtin_sevalproc`.
|
||||
-/
|
||||
def addSimprocBuiltinAttrCore (ref : IO.Ref Simprocs) (declName : Name) (post : Bool) (proc : Simproc) : IO Unit := do
|
||||
def addSimprocBuiltinAttr (declName : Name) (post : Bool) (proc : Simproc) : IO Unit := do
|
||||
let some keys := (← builtinSimprocDeclsRef.get).keys.find? declName |
|
||||
throw (IO.userError "invalid [builtin_simproc] attribute, '{declName}' is not a builtin simproc")
|
||||
if post then
|
||||
ref.modify fun s => { s with post := s.post.insertCore keys { declName, keys, post, proc } }
|
||||
builtinSimprocsRef.modify fun s => { s with post := s.post.insertCore keys { declName, keys, post, proc } }
|
||||
else
|
||||
ref.modify fun s => { s with pre := s.pre.insertCore keys { declName, keys, post, proc } }
|
||||
builtinSimprocsRef.modify fun s => { s with pre := s.pre.insertCore keys { declName, keys, post, proc } }
|
||||
|
||||
def addSimprocBuiltinAttr (declName : Name) (post : Bool) (proc : Simproc) : IO Unit :=
|
||||
addSimprocBuiltinAttrCore builtinSimprocsRef declName post proc
|
||||
|
||||
def addSEvalprocBuiltinAttr (declName : Name) (post : Bool) (proc : Simproc) : IO Unit :=
|
||||
addSimprocBuiltinAttrCore builtinSEvalprocsRef declName post proc
|
||||
def getSimprocs : CoreM Simprocs :=
|
||||
return simprocExtension.getState (← getEnv)
|
||||
|
||||
def Simprocs.add (s : Simprocs) (declName : Name) (post : Bool) : CoreM Simprocs := do
|
||||
let proc ←
|
||||
@@ -170,202 +154,45 @@ def Simprocs.add (s : Simprocs) (declName : Name) (post : Bool) : CoreM Simprocs
|
||||
else
|
||||
return { s with pre := s.pre.insertCore keys { declName, keys, post, proc } }
|
||||
|
||||
def SimprocEntry.try (s : SimprocEntry) (numExtraArgs : Nat) (e : Expr) : SimpM Step := do
|
||||
def SimprocEntry.try? (s : SimprocEntry) (numExtraArgs : Nat) (e : Expr) : SimpM (Option Step) := do
|
||||
let mut extraArgs := #[]
|
||||
let mut e := e
|
||||
for _ in [:numExtraArgs] do
|
||||
extraArgs := extraArgs.push e.appArg!
|
||||
e := e.appFn!
|
||||
extraArgs := extraArgs.reverse
|
||||
let s ← s.proc e
|
||||
s.addExtraArgs extraArgs
|
||||
match (← s.proc e) with
|
||||
| none => return none
|
||||
| some step => return some (← step.addExtraArgs extraArgs)
|
||||
|
||||
def simprocCore (post : Bool) (s : SimprocTree) (erased : PHashSet Name) (e : Expr) : SimpM Step := do
|
||||
def simproc? (post : Bool) (s : SimprocTree) (erased : PHashSet Name) (e : Expr) : SimpM (Option Step) := do
|
||||
let candidates ← s.getMatchWithExtra e (getDtConfig (← getConfig))
|
||||
if candidates.isEmpty then
|
||||
let tag := if post then "post" else "pre"
|
||||
trace[Debug.Meta.Tactic.simp] "no {tag}-simprocs found for {e}"
|
||||
return .continue
|
||||
return none
|
||||
else
|
||||
let mut e := e
|
||||
let mut proof? : Option Expr := none
|
||||
let mut found := false
|
||||
let mut cache := true
|
||||
for (simprocEntry, numExtraArgs) in candidates do
|
||||
unless erased.contains simprocEntry.declName do
|
||||
let s ← simprocEntry.try numExtraArgs e
|
||||
match s with
|
||||
| .visit r =>
|
||||
trace[Debug.Meta.Tactic.simp] "simproc result {e} => {r.expr}"
|
||||
if let some step ← simprocEntry.try? numExtraArgs e then
|
||||
trace[Debug.Meta.Tactic.simp] "simproc result {e} => {step.result.expr}"
|
||||
recordSimpTheorem (.decl simprocEntry.declName post)
|
||||
return .visit (← mkEqTransOptProofResult proof? cache r)
|
||||
| .done r =>
|
||||
trace[Debug.Meta.Tactic.simp] "simproc result {e} => {r.expr}"
|
||||
recordSimpTheorem (.decl simprocEntry.declName post)
|
||||
return .done (← mkEqTransOptProofResult proof? cache r)
|
||||
| .continue (some r) =>
|
||||
trace[Debug.Meta.Tactic.simp] "simproc result {e} => {r.expr}"
|
||||
recordSimpTheorem (.decl simprocEntry.declName post)
|
||||
e := r.expr
|
||||
proof? ← mkEqTrans? proof? r.proof?
|
||||
cache := cache && r.cache
|
||||
found := true
|
||||
| .continue none =>
|
||||
pure ()
|
||||
if found then
|
||||
return .continue (some { expr := e, proof?, cache })
|
||||
else
|
||||
return .continue
|
||||
|
||||
abbrev SimprocsArray := Array Simprocs
|
||||
|
||||
def SimprocsArray.add (ss : SimprocsArray) (declName : Name) (post : Bool) : CoreM SimprocsArray :=
|
||||
if ss.isEmpty then
|
||||
let s : Simprocs := {}
|
||||
return #[ (← s.add declName post) ]
|
||||
else
|
||||
ss.modifyM 0 fun s => s.add declName post
|
||||
|
||||
def SimprocsArray.erase (ss : SimprocsArray) (declName : Name) : SimprocsArray :=
|
||||
ss.map fun s => s.erase declName
|
||||
|
||||
def SimprocsArray.isErased (ss : SimprocsArray) (declName : Name) : Bool :=
|
||||
ss.any fun s => s.erased.contains declName
|
||||
|
||||
def simprocArrayCore (post : Bool) (ss : SimprocsArray) (e : Expr) : SimpM Step := do
|
||||
let mut found := false
|
||||
let mut e := e
|
||||
let mut proof? : Option Expr := none
|
||||
let mut cache := true
|
||||
for s in ss do
|
||||
match (← simprocCore (post := post) (if post then s.post else s.pre) s.erased e) with
|
||||
| .visit r => return .visit (← mkEqTransOptProofResult proof? cache r)
|
||||
| .done r => return .done (← mkEqTransOptProofResult proof? cache r)
|
||||
| .continue none => pure ()
|
||||
| .continue (some r) =>
|
||||
e := r.expr
|
||||
proof? ← mkEqTrans? proof? r.proof?
|
||||
cache := cache && r.cache
|
||||
found := true
|
||||
if found then
|
||||
return .continue (some { expr := e, proof? })
|
||||
else
|
||||
return .continue
|
||||
return some step
|
||||
return none
|
||||
|
||||
register_builtin_option simprocs : Bool := {
|
||||
defValue := true
|
||||
descr := "Enable/disable `simproc`s (simplification procedures)."
|
||||
}
|
||||
|
||||
def userPreSimprocs (s : SimprocsArray) : Simproc := fun e => do
|
||||
unless simprocs.get (← getOptions) do return .continue
|
||||
simprocArrayCore (post := false) s e
|
||||
def preSimproc? (e : Expr) : SimpM (Option Step) := do
|
||||
unless simprocs.get (← getOptions) do return none
|
||||
let s := (← getMethods).simprocs
|
||||
simproc? (post := false) s.pre s.erased e
|
||||
|
||||
def userPostSimprocs (s : SimprocsArray) : Simproc := fun e => do
|
||||
unless simprocs.get (← getOptions) do return .continue
|
||||
simprocArrayCore (post := true) s e
|
||||
|
||||
def mkSimprocExt (name : Name := by exact decl_name%) (ref? : Option (IO.Ref Simprocs)) : IO SimprocExtension :=
|
||||
registerScopedEnvExtension {
|
||||
name := name
|
||||
mkInitial :=
|
||||
if let some ref := ref? then
|
||||
ref.get
|
||||
else
|
||||
return {}
|
||||
ofOLeanEntry := fun _ => toSimprocEntry
|
||||
toOLeanEntry := fun e => e.toSimprocOLeanEntry
|
||||
addEntry := fun s e =>
|
||||
if e.post then
|
||||
{ s with post := s.post.insertCore e.keys e }
|
||||
else
|
||||
{ s with pre := s.pre.insertCore e.keys e }
|
||||
}
|
||||
|
||||
def mkSimprocAttr (attrName : Name) (attrDescr : String) (ext : SimprocExtension) (name : Name) : IO Unit := do
|
||||
registerBuiltinAttribute {
|
||||
ref := name
|
||||
name := attrName
|
||||
descr := attrDescr
|
||||
applicationTime := AttributeApplicationTime.afterCompilation
|
||||
add := fun declName stx attrKind =>
|
||||
let go : MetaM Unit := do
|
||||
let post := if stx[1].isNone then true else stx[1][0].getKind == ``Lean.Parser.Tactic.simpPost
|
||||
addSimprocAttr ext declName attrKind post
|
||||
discard <| go.run {} {}
|
||||
erase := eraseSimprocAttr ext
|
||||
}
|
||||
|
||||
abbrev SimprocExtensionMap := HashMap Name SimprocExtension
|
||||
|
||||
builtin_initialize simprocExtensionMapRef : IO.Ref SimprocExtensionMap ← IO.mkRef {}
|
||||
|
||||
def registerSimprocAttr (attrName : Name) (attrDescr : String) (ref? : Option (IO.Ref Simprocs))
|
||||
(name : Name := by exact decl_name%) : IO SimprocExtension := do
|
||||
let ext ← mkSimprocExt name ref?
|
||||
mkSimprocAttr attrName attrDescr ext name
|
||||
simprocExtensionMapRef.modify fun map => map.insert attrName ext
|
||||
return ext
|
||||
|
||||
builtin_initialize simprocExtension : SimprocExtension ← registerSimprocAttr `simprocAttr "Simplification procedure" (some builtinSimprocsRef)
|
||||
|
||||
builtin_initialize simprocSEvalExtension : SimprocExtension ← registerSimprocAttr `sevalprocAttr "Symbolic evaluator procedure" (some builtinSEvalprocsRef)
|
||||
|
||||
private def addBuiltin (declName : Name) (stx : Syntax) (addDeclName : Name) : AttrM Unit := do
|
||||
let go : MetaM Unit := do
|
||||
let post := if stx[1].isNone then true else stx[1][0].getKind == ``Lean.Parser.Tactic.simpPost
|
||||
let val := mkAppN (mkConst addDeclName) #[toExpr declName, toExpr post, mkConst declName]
|
||||
let initDeclName ← mkFreshUserName (declName ++ `declare)
|
||||
declareBuiltin initDeclName val
|
||||
go.run' {}
|
||||
|
||||
builtin_initialize
|
||||
registerBuiltinAttribute {
|
||||
ref := by exact decl_name%
|
||||
name := `simprocBuiltinAttr
|
||||
descr := "Builtin simplification procedure"
|
||||
applicationTime := AttributeApplicationTime.afterCompilation
|
||||
erase := fun _ => throwError "Not implemented yet, [-builtin_simproc]"
|
||||
add := fun declName stx _ => addBuiltin declName stx ``addSimprocBuiltinAttr
|
||||
}
|
||||
|
||||
builtin_initialize
|
||||
registerBuiltinAttribute {
|
||||
ref := by exact decl_name%
|
||||
name := `sevalprocBuiltinAttr
|
||||
descr := "Builtin symbolic evaluation procedure"
|
||||
applicationTime := AttributeApplicationTime.afterCompilation
|
||||
erase := fun _ => throwError "Not implemented yet, [-builtin_sevalproc]"
|
||||
add := fun declName stx _ => addBuiltin declName stx ``addSEvalprocBuiltinAttr
|
||||
}
|
||||
|
||||
def getSimprocs : CoreM Simprocs :=
|
||||
return simprocExtension.getState (← getEnv)
|
||||
|
||||
def getSEvalSimprocs : CoreM Simprocs :=
|
||||
return simprocSEvalExtension.getState (← getEnv)
|
||||
|
||||
def getSimprocExtensionCore? (attrName : Name) : IO (Option SimprocExtension) :=
|
||||
return (← simprocExtensionMapRef.get).find? attrName
|
||||
|
||||
def simpAttrNameToSimprocAttrName (attrName : Name) : Name :=
|
||||
if attrName == `simp then `simprocAttr
|
||||
else if attrName == `seval then `sevalprocAttr
|
||||
else attrName.appendAfter "_proc"
|
||||
|
||||
/--
|
||||
Try to retrieve the simproc set using the `simp` or `simproc` attribute names.
|
||||
Recall that when we declare a `simp` set using `register_simp_attr`, an associated
|
||||
`simproc` set is automatically created. We use the function `simpAttrNameToSimprocAttrName` to
|
||||
find the `simproc` associated with the `simp` attribute.
|
||||
-/
|
||||
def getSimprocExtension? (simprocAttrNameOrsimpAttrName : Name)
|
||||
: IO (Option SimprocExtension) := do
|
||||
let some ext ← getSimprocExtensionCore? simprocAttrNameOrsimpAttrName
|
||||
| getSimprocExtensionCore? (simpAttrNameToSimprocAttrName simprocAttrNameOrsimpAttrName)
|
||||
return some ext
|
||||
|
||||
def SimprocExtension.getSimprocs (ext : SimprocExtension) : CoreM Simprocs :=
|
||||
return ext.getState (← getEnv)
|
||||
def postSimproc? (e : Expr) : SimpM (Option Step) := do
|
||||
unless simprocs.get (← getOptions) do return none
|
||||
let s := (← getMethods).simprocs
|
||||
simproc? (post := true) s.post s.erased e
|
||||
|
||||
end Lean.Meta.Simp
|
||||
|
||||
@@ -17,62 +17,29 @@ structure Result where
|
||||
proof? : Option Expr := none -- If none, proof is assumed to be `refl`
|
||||
/-- Save the field `dischargeDepth` at `Simp.Context` because it impacts the simplifier result. -/
|
||||
dischargeDepth : UInt32 := 0
|
||||
/-- If `cache := true` the result is cached. -/
|
||||
cache : Bool := true
|
||||
deriving Inhabited
|
||||
|
||||
def mkEqTransOptProofResult (h? : Option Expr) (cache : Bool) (r : Result) : MetaM Result :=
|
||||
match h?, cache with
|
||||
| none, true => return r
|
||||
| none, false => return { r with cache := false }
|
||||
| some p₁, cache => match r.proof? with
|
||||
| none => return { r with proof? := some p₁, cache := cache && r.cache }
|
||||
| some p₂ => return { r with proof? := (← Meta.mkEqTrans p₁ p₂), cache := cache && r.cache }
|
||||
|
||||
def Result.mkEqTrans (r₁ r₂ : Result) : MetaM Result :=
|
||||
mkEqTransOptProofResult r₁.proof? r₁.cache r₂
|
||||
|
||||
abbrev Cache := ExprMap Result
|
||||
|
||||
abbrev CongrCache := ExprMap (Option CongrTheorem)
|
||||
|
||||
structure Context where
|
||||
config : Config := {}
|
||||
/--
|
||||
We initialize this field using `config.ground`.
|
||||
Here is how we use this flag.
|
||||
- When `unfoldGround := false` for a term `t`, it will remain false for every `t`-subterm.
|
||||
- When term is a proof, this flag has no effect since `simp` does not try to simplify proofs.
|
||||
- When `unfoldGround := true` and visited term is type but not a proposition, we set `unfoldGround := false`.
|
||||
- When `unfoldGround := true` and term is not ground, we set `unfoldGround := false` when visiting instance implicit
|
||||
arguments. Reason: We don't want to unfold instance implicit arguments of non-ground applications.
|
||||
- When `unfoldGround := true` and term is ground, we try to unfold it during post-visit.
|
||||
-/
|
||||
unfoldGround : Bool := config.ground
|
||||
/-- `maxDischargeDepth` from `config` as an `UInt32`. -/
|
||||
maxDischargeDepth : UInt32 := UInt32.ofNatTruncate config.maxDischargeDepth
|
||||
simpTheorems : SimpTheoremsArray := {}
|
||||
congrTheorems : SimpCongrTheorems := {}
|
||||
/--
|
||||
Stores the "parent" term for the term being simplified.
|
||||
If a simplification procedure result depends on this value,
|
||||
then it is its reponsability to set `Result.cache := false`.
|
||||
|
||||
Motivation for this field:
|
||||
Suppose we have a simplication procedure for normalizing arithmetic terms.
|
||||
Then, given a term such as `t_1 + ... + t_n`, we don't want to apply the procedure
|
||||
to every subterm `t_1 + ... + t_i` for `i < n` for performance issues. The procedure
|
||||
can accomplish this by checking whether the parent term is also an arithmetical expression
|
||||
and do nothing if it is. However, it should set `Result.cache := false` to ensure
|
||||
we don't miss simplification opportunities. For example, consider the following:
|
||||
```
|
||||
example (x y : Nat) (h : y = 0) : id ((x + x) + y) = id (x + x) := by
|
||||
simp_arith only
|
||||
...
|
||||
```
|
||||
If we don't set `Result.cache := false` for the first `x + x`, then we get
|
||||
the resulting state:
|
||||
```
|
||||
... |- id (2*x + y) = id (x + x)
|
||||
```
|
||||
instead of
|
||||
```
|
||||
... |- id (2*x + y) = id (2*x)
|
||||
```
|
||||
as expected.
|
||||
|
||||
Remark: given an application `f a b c` the "parent" term for `f`, `a`, `b`, and `c`
|
||||
is `f a b c`.
|
||||
-/
|
||||
parent? : Option Expr := none
|
||||
dischargeDepth : UInt32 := 0
|
||||
deriving Inhabited
|
||||
@@ -87,6 +54,8 @@ abbrev UsedSimps := HashMap Origin Nat
|
||||
|
||||
structure State where
|
||||
cache : Cache := {}
|
||||
/-- Cache for `unfoldGround := true` -/
|
||||
cacheGround : Cache := {}
|
||||
congrCache : CongrCache := {}
|
||||
usedTheorems : UsedSimps := {}
|
||||
numSteps : Nat := 0
|
||||
@@ -105,62 +74,24 @@ opaque simp (e : Expr) : SimpM Result
|
||||
@[extern "lean_dsimp"]
|
||||
opaque dsimp (e : Expr) : SimpM Expr
|
||||
|
||||
/--
|
||||
Result type for a simplification procedure. We have `pre` and `post` simplication procedures.
|
||||
-/
|
||||
@[always_inline]
|
||||
def withoutUnfoldGround (x : SimpM α) : SimpM α :=
|
||||
withTheReader Context (fun ctx => { ctx with unfoldGround := false }) x
|
||||
|
||||
inductive Step where
|
||||
/--
|
||||
For `pre` procedures, it returns the result without visiting any subexpressions.
|
||||
|
||||
For `post` procedures, it returns the result.
|
||||
-/
|
||||
| done (r : Result)
|
||||
/--
|
||||
For `pre` procedures, the resulting expression is passed to `pre` again.
|
||||
|
||||
For `post` procedures, the resulting expression is passed to `pre` again IF
|
||||
`Simp.Config.singlePass := false` and resulting expression is not equal to initial expression.
|
||||
-/
|
||||
| visit (e : Result)
|
||||
/--
|
||||
For `pre` procedures, continue transformation by visiting subexpressions, and then
|
||||
executing `post` procedures.
|
||||
|
||||
For `post` procedures, this is equivalent to returning `visit`.
|
||||
-/
|
||||
| continue (e? : Option Result := none)
|
||||
| visit : Result → Step
|
||||
| done : Result → Step
|
||||
deriving Inhabited
|
||||
|
||||
/--
|
||||
A simplification procedure. Recall that we have `pre` and `post` procedures.
|
||||
See `Step`.
|
||||
-/
|
||||
abbrev Simproc := Expr → SimpM Step
|
||||
def Step.result : Step → Result
|
||||
| Step.visit r => r
|
||||
| Step.done r => r
|
||||
|
||||
def mkEqTransResultStep (r : Result) (s : Step) : MetaM Step :=
|
||||
match s with
|
||||
| .done r' => return .done (← mkEqTransOptProofResult r.proof? r.cache r')
|
||||
| .visit r' => return .visit (← mkEqTransOptProofResult r.proof? r.cache r')
|
||||
| .continue none => return .continue r
|
||||
| .continue (some r') => return .continue (some (← mkEqTransOptProofResult r.proof? r.cache r'))
|
||||
def Step.updateResult : Step → Result → Step
|
||||
| Step.visit _, r => Step.visit r
|
||||
| Step.done _, r => Step.done r
|
||||
|
||||
/--
|
||||
"Compose" the two given simplification procedures. We use the following semantics.
|
||||
- If `f` produces `done` or `visit`, then return `f`'s result.
|
||||
- If `f` produces `continue`, then apply `g` to new expression returned by `f`.
|
||||
|
||||
See `Simp.Step` type.
|
||||
-/
|
||||
@[always_inline]
|
||||
def andThen (f g : Simproc) : Simproc := fun e => do
|
||||
match (← f e) with
|
||||
| .done r => return .done r
|
||||
| .continue none => g e
|
||||
| .continue (some r) => mkEqTransResultStep r (← g r.expr)
|
||||
| .visit r => return .visit r
|
||||
|
||||
instance : AndThen Simproc where
|
||||
andThen s₁ s₂ := andThen s₁ (s₂ ())
|
||||
abbrev Simproc := Expr → SimpM (Option Step)
|
||||
|
||||
/--
|
||||
`Simproc` .olean entry.
|
||||
@@ -192,9 +123,10 @@ structure Simprocs where
|
||||
deriving Inhabited
|
||||
|
||||
structure Methods where
|
||||
pre : Simproc := fun _ => return .continue
|
||||
post : Simproc := fun e => return .done { expr := e }
|
||||
pre : Expr → SimpM Step := fun e => return Step.visit { expr := e }
|
||||
post : Expr → SimpM Step := fun e => return Step.done { expr := e }
|
||||
discharge? : Expr → SimpM (Option Expr) := fun _ => return none
|
||||
simprocs : Simprocs := {}
|
||||
deriving Inhabited
|
||||
|
||||
unsafe def Methods.toMethodsRefImpl (m : Methods) : MethodsRef :=
|
||||
@@ -236,16 +168,13 @@ def getSimpTheorems : SimpM SimpTheoremsArray :=
|
||||
def getSimpCongrTheorems : SimpM SimpCongrTheorems :=
|
||||
return (← readThe Context).congrTheorems
|
||||
|
||||
@[inline] def savingCache (x : SimpM α) : SimpM α := do
|
||||
@[inline] def withSimpTheorems (s : SimpTheoremsArray) (x : SimpM α) : SimpM α := do
|
||||
let cacheSaved := (← get).cache
|
||||
modify fun s => { s with cache := {} }
|
||||
try x finally modify fun s => { s with cache := cacheSaved }
|
||||
|
||||
@[inline] def withSimpTheorems (s : SimpTheoremsArray) (x : SimpM α) : SimpM α := do
|
||||
savingCache <| withTheReader Context (fun ctx => { ctx with simpTheorems := s }) x
|
||||
|
||||
@[inline] def withDischarger (discharge? : Expr → SimpM (Option Expr)) (x : SimpM α) : SimpM α :=
|
||||
savingCache <| withReader (fun r => { MethodsRef.toMethods r with discharge? }.toMethodsRef) x
|
||||
try
|
||||
withTheReader Context (fun ctx => { ctx with simpTheorems := s }) x
|
||||
finally
|
||||
modify fun s => { s with cache := cacheSaved }
|
||||
|
||||
def recordSimpTheorem (thmId : Origin) : SimpM Unit :=
|
||||
modify fun s => if s.usedTheorems.contains thmId then s else
|
||||
@@ -317,32 +246,33 @@ where
|
||||
/--
|
||||
Given a simplified function result `r` and arguments `args`, simplify arguments using `simp` and `dsimp`.
|
||||
The resulting proof is built using `congr` and `congrFun` theorems.
|
||||
|
||||
Recall that, if the term is not ground and `Context.unfoldGround := true`, then we set `Context.unfoldGround := false`
|
||||
for instance implicit arguments.
|
||||
-/
|
||||
def congrArgs (r : Result) (args : Array Expr) : SimpM Result := do
|
||||
if args.isEmpty then
|
||||
return r
|
||||
else
|
||||
let cfg ← getConfig
|
||||
let ctx ← getContext
|
||||
let infos := (← getFunInfoNArgs r.expr args.size).paramInfo
|
||||
let mut r := r
|
||||
let mut i := 0
|
||||
for arg in args do
|
||||
trace[Debug.Meta.Tactic.simp] "app [{i}] {infos.size} {arg} hasFwdDeps: {infos[i]!.hasFwdDeps}"
|
||||
if h : i < infos.size then
|
||||
trace[Debug.Meta.Tactic.simp] "app [{i}] {infos.size} {arg} hasFwdDeps: {infos[i].hasFwdDeps}"
|
||||
let info := infos[i]
|
||||
if cfg.ground && info.isInstImplicit then
|
||||
-- We don't visit instance implicit arguments when we are reducing ground terms.
|
||||
-- Motivation: many instance implicit arguments are ground, and it does not make sense
|
||||
-- to reduce them if the parent term is not ground.
|
||||
-- TODO: consider using it as the default behavior.
|
||||
-- We have considered it at https://github.com/leanprover/lean4/pull/3151
|
||||
r ← mkCongrFun r arg
|
||||
else if !info.hasFwdDeps then
|
||||
r ← mkCongr r (← simp arg)
|
||||
else if (← whnfD (← inferType r.expr)).isArrow then
|
||||
r ← mkCongr r (← simp arg)
|
||||
let go : SimpM Result := do
|
||||
if !info.hasFwdDeps then
|
||||
mkCongr r (← simp arg)
|
||||
else if (← whnfD (← inferType r.expr)).isArrow then
|
||||
mkCongr r (← simp arg)
|
||||
else
|
||||
mkCongrFun r (← dsimp arg)
|
||||
if ctx.unfoldGround && info.isInstImplicit then
|
||||
r ← withoutUnfoldGround go
|
||||
else
|
||||
r ← mkCongrFun r (← dsimp arg)
|
||||
r ← go
|
||||
else if (← whnfD (← inferType r.expr)).isArrow then
|
||||
r ← mkCongr r (← simp arg)
|
||||
else
|
||||
@@ -372,6 +302,17 @@ def mkCongrSimp? (f : Expr) : SimpM (Option CongrTheorem) := do
|
||||
modify fun s => { s with congrCache := s.congrCache.insert f thm? }
|
||||
return thm?
|
||||
|
||||
/--
|
||||
Set `unfoldGround := false` when executing `x` IF `infos[i].isInstImplicit`.
|
||||
-/
|
||||
@[always_inline]
|
||||
def withoutUnfoldGroundIfInstImplicit (infos : Array ParamInfo) (i : Nat) (x : SimpM α) : SimpM α := do
|
||||
if (← getContext).unfoldGround then
|
||||
if h : i < infos.size then
|
||||
if infos[i].isInstImplicit then
|
||||
return (← withoutUnfoldGround x)
|
||||
x
|
||||
|
||||
/--
|
||||
Try to use automatically generated congruence theorems. See `mkCongrSimp?`.
|
||||
-/
|
||||
@@ -382,7 +323,6 @@ def tryAutoCongrTheorem? (e : Expr) : SimpM (Option Result) := do
|
||||
if cgrThm.argKinds.size != e.getAppNumArgs then return none
|
||||
let args := e.getAppArgs
|
||||
let infos := (← getFunInfoNArgs f args.size).paramInfo
|
||||
let config ← getConfig
|
||||
let mut simplified := false
|
||||
let mut hasProof := false
|
||||
let mut hasCast := false
|
||||
@@ -390,19 +330,12 @@ def tryAutoCongrTheorem? (e : Expr) : SimpM (Option Result) := do
|
||||
let mut argResults := #[]
|
||||
let mut i := 0 -- index at args
|
||||
for arg in args, kind in cgrThm.argKinds do
|
||||
if h : config.ground ∧ i < infos.size then
|
||||
if (infos[i]'h.2).isInstImplicit then
|
||||
-- Do not visit instance implict arguments when `ground := true`
|
||||
-- See comment at `congrArgs`
|
||||
argsNew := argsNew.push arg
|
||||
i := i + 1
|
||||
continue
|
||||
match kind with
|
||||
| CongrArgKind.fixed => argsNew := argsNew.push (← dsimp arg)
|
||||
| CongrArgKind.fixed => argsNew := argsNew.push (← withoutUnfoldGroundIfInstImplicit infos i (dsimp arg))
|
||||
| CongrArgKind.cast => hasCast := true; argsNew := argsNew.push arg
|
||||
| CongrArgKind.subsingletonInst => argsNew := argsNew.push arg
|
||||
| CongrArgKind.eq =>
|
||||
let argResult ← simp arg
|
||||
let argResult ← withoutUnfoldGroundIfInstImplicit infos i (simp arg)
|
||||
argResults := argResults.push argResult
|
||||
argsNew := argsNew.push argResult.expr
|
||||
if argResult.proof?.isSome then hasProof := true
|
||||
@@ -500,8 +433,6 @@ def Step.addExtraArgs (s : Step) (extraArgs : Array Expr) : MetaM Step := do
|
||||
match s with
|
||||
| .visit r => return .visit (← r.addExtraArgs extraArgs)
|
||||
| .done r => return .done (← r.addExtraArgs extraArgs)
|
||||
| .continue none => return .continue none
|
||||
| .continue (some r) => return .continue (← r.addExtraArgs extraArgs)
|
||||
|
||||
end Simp
|
||||
|
||||
|
||||
@@ -19,17 +19,19 @@ def getSimpMatchContext : MetaM Simp.Context :=
|
||||
}
|
||||
|
||||
def simpMatch (e : Expr) : MetaM Simp.Result := do
|
||||
let discharge? ← SplitIf.mkDischarge?
|
||||
(·.1) <$> Simp.main e (← getSimpMatchContext) (methods := { pre, discharge? })
|
||||
(·.1) <$> Simp.main e (← getSimpMatchContext) (methods := { pre })
|
||||
where
|
||||
pre (e : Expr) : SimpM Simp.Step := do
|
||||
unless (← isMatcherApp e) do
|
||||
return Simp.Step.continue
|
||||
return Simp.Step.visit { expr := e }
|
||||
let matcherDeclName := e.getAppFn.constName!
|
||||
-- First try to reduce matcher
|
||||
match (← reduceRecMatcher? e) with
|
||||
| some e' => return Simp.Step.done { expr := e' }
|
||||
| none => Simp.simpMatchCore matcherDeclName e
|
||||
| none =>
|
||||
match (← Simp.simpMatchCore? matcherDeclName e SplitIf.discharge?) with
|
||||
| some r => return r
|
||||
| none => return Simp.Step.visit { expr := e }
|
||||
|
||||
def simpMatchTarget (mvarId : MVarId) : MetaM MVarId := mvarId.withContext do
|
||||
let target ← instantiateMVars (← mvarId.getType)
|
||||
@@ -37,14 +39,13 @@ def simpMatchTarget (mvarId : MVarId) : MetaM MVarId := mvarId.withContext do
|
||||
applySimpResultToTarget mvarId target r
|
||||
|
||||
private def simpMatchCore (matchDeclName : Name) (matchEqDeclName : Name) (e : Expr) : MetaM Simp.Result := do
|
||||
let discharge? ← SplitIf.mkDischarge?
|
||||
(·.1) <$> Simp.main e (← getSimpMatchContext) (methods := { pre, discharge? })
|
||||
(·.1) <$> Simp.main e (← getSimpMatchContext) (methods := { pre })
|
||||
where
|
||||
pre (e : Expr) : SimpM Simp.Step := do
|
||||
if e.isAppOf matchDeclName then
|
||||
-- First try to reduce matcher
|
||||
match (← reduceRecMatcher? e) with
|
||||
| some e' => return .done { expr := e' }
|
||||
| some e' => return Simp.Step.done { expr := e' }
|
||||
| none =>
|
||||
-- Try lemma
|
||||
let simpTheorem := {
|
||||
@@ -52,11 +53,11 @@ where
|
||||
proof := mkConst matchEqDeclName
|
||||
rfl := (← isRflTheorem matchEqDeclName)
|
||||
}
|
||||
match (← withReducible <| Simp.tryTheorem? e simpTheorem) with
|
||||
| none => return .continue
|
||||
| some r => return .done r
|
||||
match (← withReducible <| Simp.tryTheorem? e simpTheorem SplitIf.discharge?) with
|
||||
| none => return Simp.Step.visit { expr := e }
|
||||
| some r => return Simp.Step.done r
|
||||
else
|
||||
return .continue
|
||||
return Simp.Step.visit { expr := e }
|
||||
|
||||
private def simpMatchTargetCore (mvarId : MVarId) (matchDeclName : Name) (matchEqDeclName : Name) : MetaM MVarId := do
|
||||
mvarId.withContext do
|
||||
|
||||
@@ -31,17 +31,9 @@ def getSimpContext : MetaM Simp.Context :=
|
||||
|
||||
/--
|
||||
Default `discharge?` function for `simpIf` methods.
|
||||
It only uses hypotheses from the local context that have `index < numIndices`.
|
||||
It is effective after a case-split.
|
||||
|
||||
Remark: when `simp` goes inside binders it adds new local declarations to the
|
||||
local context. We don't want to use these local declarations since the cached result
|
||||
would depend on them (see issue #3229). `numIndices` is the size of the local
|
||||
context `decls` field before we start the simplifying the expression.
|
||||
|
||||
Remark: this function is now private, and we should use `mkDischarge?`.
|
||||
-/
|
||||
private def discharge? (numIndices : Nat) (useDecide : Bool) : Simp.Discharge := fun prop => do
|
||||
It only uses hypotheses from the local context. It is effective
|
||||
after a case-split. -/
|
||||
def discharge? (useDecide := false) : Simp.Discharge := fun prop => do
|
||||
let prop ← instantiateMVars prop
|
||||
trace[Meta.Tactic.splitIf] "discharge? {prop}, {prop.notNot?}"
|
||||
if useDecide then
|
||||
@@ -52,7 +44,7 @@ private def discharge? (numIndices : Nat) (useDecide : Bool) : Simp.Discharge :=
|
||||
if r.isConstOf ``true then
|
||||
return some <| mkApp3 (mkConst ``of_decide_eq_true) prop d.appArg! (← mkEqRefl (mkConst ``true))
|
||||
(← getLCtx).findDeclRevM? fun localDecl => do
|
||||
if localDecl.index ≥ numIndices || localDecl.isAuxDecl then
|
||||
if localDecl.isAuxDecl then
|
||||
return none
|
||||
else if (← isDefEq prop localDecl.type) then
|
||||
return some localDecl.toExpr
|
||||
@@ -64,9 +56,6 @@ private def discharge? (numIndices : Nat) (useDecide : Bool) : Simp.Discharge :=
|
||||
else
|
||||
return none
|
||||
|
||||
def mkDischarge? (useDecide := false) : MetaM Simp.Discharge :=
|
||||
return discharge? (← getLCtx).numIndices useDecide
|
||||
|
||||
/-- Return the condition of an `if` expression to case split. -/
|
||||
partial def findIfToSplit? (e : Expr) : Option Expr :=
|
||||
if let some iteApp := e.find? fun e => (e.isIte || e.isDIte) && !(e.getArg! 1 5).hasLooseBVars then
|
||||
@@ -93,14 +82,14 @@ open SplitIf
|
||||
|
||||
def simpIfTarget (mvarId : MVarId) (useDecide := false) : MetaM MVarId := do
|
||||
let mut ctx ← getSimpContext
|
||||
if let (some mvarId', _) ← simpTarget mvarId ctx {} (← mvarId.withContext <| mkDischarge? useDecide) (mayCloseGoal := false) then
|
||||
if let (some mvarId', _) ← simpTarget mvarId ctx {} (discharge? useDecide) (mayCloseGoal := false) then
|
||||
return mvarId'
|
||||
else
|
||||
unreachable!
|
||||
|
||||
def simpIfLocalDecl (mvarId : MVarId) (fvarId : FVarId) : MetaM MVarId := do
|
||||
let mut ctx ← getSimpContext
|
||||
if let (some (_, mvarId'), _) ← simpLocalDecl mvarId fvarId ctx {} (← mvarId.withContext <| mkDischarge?) (mayCloseGoal := false) then
|
||||
if let (some (_, mvarId'), _) ← simpLocalDecl mvarId fvarId ctx {} discharge? (mayCloseGoal := false) then
|
||||
return mvarId'
|
||||
else
|
||||
unreachable!
|
||||
|
||||
@@ -22,12 +22,12 @@ def unfold (e : Expr) (declName : Name) : MetaM Simp.Result := do
|
||||
return { expr := (← deltaExpand e (· == declName)) }
|
||||
where
|
||||
pre (unfoldThm : Name) (e : Expr) : SimpM Simp.Step := do
|
||||
match (← withReducible <| Simp.tryTheorem? e { origin := .decl unfoldThm, proof := mkConst unfoldThm, rfl := (← isRflTheorem unfoldThm) }) with
|
||||
match (← withReducible <| Simp.tryTheorem? e { origin := .decl unfoldThm, proof := mkConst unfoldThm, rfl := (← isRflTheorem unfoldThm) } (fun _ => return none)) with
|
||||
| none => pure ()
|
||||
| some r => match (← reduceMatcher? r.expr) with
|
||||
| .reduced e' => return .done { r with expr := e' }
|
||||
| _ => return .done r
|
||||
return .continue
|
||||
| .reduced e' => return Simp.Step.done { r with expr := e' }
|
||||
| _ => return Simp.Step.done r
|
||||
return Simp.Step.visit { expr := e }
|
||||
|
||||
def unfoldTarget (mvarId : MVarId) (declName : Name) : MetaM MVarId := mvarId.withContext do
|
||||
let target ← instantiateMVars (← mvarId.getType)
|
||||
|
||||
@@ -40,8 +40,6 @@ structure Context where
|
||||
openDecls : List OpenDecl
|
||||
inPattern : Bool := false -- true when delaborating `match` patterns
|
||||
subExpr : SubExpr
|
||||
/-- Current recursion depth during delaboration. Used by the `pp.deepTerms false` option. -/
|
||||
depth : Nat := 0
|
||||
|
||||
structure State where
|
||||
/-- We attach `Elab.Info` at various locations in the `Syntax` output in order to convey
|
||||
@@ -83,10 +81,8 @@ instance (priority := low) : MonadWithReaderOf SubExpr DelabM where
|
||||
|
||||
instance (priority := low) : MonadStateOf SubExpr.HoleIterator DelabM where
|
||||
get := State.holeIter <$> get
|
||||
set iter := modify fun s => { s with holeIter := iter }
|
||||
modifyGet f := modifyGet fun s =>
|
||||
let (ret, holeIter') := f s.holeIter
|
||||
(ret, { s with holeIter := holeIter' })
|
||||
set iter := modify fun ⟨infos, _⟩ => ⟨infos, iter⟩
|
||||
modifyGet f := modifyGet fun ⟨infos, iter⟩ => let (ret, iter') := f iter; (ret, ⟨infos, iter'⟩)
|
||||
|
||||
-- Macro scopes in the delaborator output are ultimately ignored by the pretty printer,
|
||||
-- so give a trivial implementation.
|
||||
@@ -151,7 +147,7 @@ def getOptionsAtCurrPos : DelabM Options := do
|
||||
return opts
|
||||
|
||||
/-- Evaluate option accessor, using subterm-specific options if set. -/
|
||||
def getPPOption (opt : Options → α) : DelabM α := do
|
||||
def getPPOption (opt : Options → Bool) : DelabM Bool := do
|
||||
return opt (← getOptionsAtCurrPos)
|
||||
|
||||
def whenPPOption (opt : Options → Bool) (d : Delab) : Delab := do
|
||||
@@ -207,10 +203,10 @@ def withBindingBodyUnusedName {α} (d : Syntax → DelabM α) : DelabM α := do
|
||||
liftM x
|
||||
|
||||
def addTermInfo (pos : Pos) (stx : Syntax) (e : Expr) (isBinder : Bool := false) : DelabM Unit := do
|
||||
let info := Info.ofTermInfo <| ← mkTermInfo stx e isBinder
|
||||
let info ← mkTermInfo stx e isBinder
|
||||
modify fun s => { s with infos := s.infos.insert pos info }
|
||||
where
|
||||
mkTermInfo stx e isBinder := return {
|
||||
mkTermInfo stx e isBinder := return Info.ofTermInfo {
|
||||
elaborator := `Delab,
|
||||
stx := stx,
|
||||
lctx := (← getLCtx),
|
||||
@@ -220,10 +216,10 @@ where
|
||||
}
|
||||
|
||||
def addFieldInfo (pos : Pos) (projName fieldName : Name) (stx : Syntax) (val : Expr) : DelabM Unit := do
|
||||
let info := Info.ofFieldInfo <| ← mkFieldInfo projName fieldName stx val
|
||||
let info ← mkFieldInfo projName fieldName stx val
|
||||
modify fun s => { s with infos := s.infos.insert pos info }
|
||||
where
|
||||
mkFieldInfo projName fieldName stx val := return {
|
||||
mkFieldInfo projName fieldName stx val := return Info.ofFieldInfo {
|
||||
projName := projName,
|
||||
fieldName := fieldName,
|
||||
lctx := (← getLCtx),
|
||||
@@ -231,60 +227,11 @@ where
|
||||
stx := stx
|
||||
}
|
||||
|
||||
def addOmissionInfo (pos : Pos) (stx : Syntax) (e : Expr) : DelabM Unit := do
|
||||
let info := Info.ofOmissionInfo <| ← mkOmissionInfo stx e
|
||||
modify fun s => { s with infos := s.infos.insert pos info }
|
||||
where
|
||||
mkOmissionInfo stx e := return {
|
||||
toTermInfo := ← addTermInfo.mkTermInfo stx e (isBinder := false)
|
||||
}
|
||||
|
||||
/--
|
||||
Runs the delaborator `act` with increased depth.
|
||||
The depth is used when `pp.deepTerms` is `false` to determine what is a deep term.
|
||||
See also `Lean.PrettyPrinter.Delaborator.Context.depth`.
|
||||
-/
|
||||
def withIncDepth (act : DelabM α) : DelabM α := fun ctx =>
|
||||
act { ctx with depth := ctx.depth + 1 }
|
||||
|
||||
/--
|
||||
Returns `true` if, at the current depth, we should omit the term and use `⋯` rather than
|
||||
delaborating it. This function can only return `true` if `pp.deepTerms` is set to `false`.
|
||||
It also contains a heuristic to allow "shallow terms" to be delaborated, even if they appear deep in
|
||||
an expression, which prevents terms such as atomic expressions or `OfNat.ofNat` literals from being
|
||||
delaborated as `⋯`.
|
||||
-/
|
||||
def shouldOmitExpr (e : Expr) : DelabM Bool := do
|
||||
if ← getPPOption getPPDeepTerms then
|
||||
return false
|
||||
|
||||
let depth := (← read).depth
|
||||
let depthThreshold ← getPPOption getPPDeepTermsThreshold
|
||||
let approxDepth := e.approxDepth.toNat
|
||||
let depthExcess := depth - depthThreshold
|
||||
|
||||
let isMaxedOutApproxDepth := approxDepth >= 255
|
||||
let isShallowExpression :=
|
||||
!isMaxedOutApproxDepth && approxDepth <= depthThreshold/4 - depthExcess
|
||||
|
||||
return depthExcess > 0 && !isShallowExpression
|
||||
|
||||
def annotateTermInfo (stx : Term) : Delab := do
|
||||
let stx ← annotateCurPos stx
|
||||
addTermInfo (← getPos) stx (← getExpr)
|
||||
pure stx
|
||||
|
||||
|
||||
/--
|
||||
Delaborates the current expression as `⋯` and attaches `Elab.OmissionInfo`, which influences how the
|
||||
subterm omitted by `⋯` is delaborated when hovered over.
|
||||
-/
|
||||
def omission : Delab := do
|
||||
let stx ← `(⋯)
|
||||
let stx ← annotateCurPos stx
|
||||
addOmissionInfo (← getPos) stx (← getExpr)
|
||||
pure stx
|
||||
|
||||
partial def delabFor : Name → Delab
|
||||
| Name.anonymous => failure
|
||||
| k =>
|
||||
@@ -296,9 +243,6 @@ partial def delab : Delab := do
|
||||
checkSystem "delab"
|
||||
let e ← getExpr
|
||||
|
||||
if ← shouldOmitExpr e then
|
||||
return ← omission
|
||||
|
||||
-- no need to hide atomic proofs
|
||||
if ← pure !e.isAtomic <&&> pure !(← getPPOption getPPProofs) <&&> (try Meta.isProof e catch _ => pure false) then
|
||||
if ← getPPOption getPPProofsWithType then
|
||||
@@ -307,7 +251,7 @@ partial def delab : Delab := do
|
||||
else
|
||||
return ← annotateTermInfo (← ``(_))
|
||||
let k ← getExprKind
|
||||
let stx ← withIncDepth <| delabFor k <|> (liftM $ show MetaM _ from throwError "don't know how to delaborate '{k}'")
|
||||
let stx ← delabFor k <|> (liftM $ show MetaM _ from throwError "don't know how to delaborate '{k}'")
|
||||
if ← getPPOption getPPAnalyzeTypeAscriptions <&&> getPPOption getPPAnalysisNeedsType <&&> pure !e.isMData then
|
||||
let typeStx ← withType delab
|
||||
`(($stx : $typeStx)) >>= annotateCurPos
|
||||
|
||||
@@ -680,81 +680,14 @@ def delabLetE : Delab := do
|
||||
def delabLit : Delab := do
|
||||
let Expr.lit l ← getExpr | unreachable!
|
||||
match l with
|
||||
| Literal.natVal n =>
|
||||
if ← getPPOption getPPNatLit then
|
||||
`(nat_lit $(quote n))
|
||||
else
|
||||
return quote n
|
||||
| Literal.strVal s => return quote s
|
||||
| Literal.natVal n => pure $ quote n
|
||||
| Literal.strVal s => pure $ quote s
|
||||
|
||||
/--
|
||||
Core function that delaborates a natural number (an `OfNat.ofNat` literal).
|
||||
-/
|
||||
def delabOfNatCore (showType : Bool := false) : Delab := do
|
||||
let .app (.app (.app (.const ``OfNat.ofNat ..) _) (.lit (.natVal n))) _ ← getExpr | failure
|
||||
let stx ← annotateTermInfo <| quote n
|
||||
if showType then
|
||||
let ty ← withNaryArg 0 delab
|
||||
`(($stx : $ty))
|
||||
else
|
||||
return stx
|
||||
|
||||
/--
|
||||
Core function that delaborates a negative integer literal (a `Neg.neg` applied to `OfNat.ofNat`).
|
||||
-/
|
||||
def delabNegIntCore (showType : Bool := false) : Delab := do
|
||||
guard <| (← getExpr).isAppOfArity ``Neg.neg 3
|
||||
let n ← withAppArg delabOfNatCore
|
||||
let stx ← annotateTermInfo (← `(- $n))
|
||||
if showType then
|
||||
let ty ← withNaryArg 0 delab
|
||||
`(($stx : $ty))
|
||||
else
|
||||
return stx
|
||||
|
||||
/--
|
||||
Core function that delaborates a rational literal that is the division of an integer literal
|
||||
by a natural number literal.
|
||||
The division must be homogeneous for it to count as a rational literal.
|
||||
-/
|
||||
def delabDivRatCore (showType : Bool := false) : Delab := do
|
||||
let e ← getExpr
|
||||
guard <| e.isAppOfArity ``HDiv.hDiv 6
|
||||
guard <| e.getArg! 0 == e.getArg! 1
|
||||
guard <| e.getArg! 0 == e.getArg! 2
|
||||
let p ← withAppFn <| withAppArg <| (delabOfNatCore <|> delabNegIntCore)
|
||||
let q ← withAppArg <| delabOfNatCore
|
||||
let stx ← annotateTermInfo (← `($p / $q))
|
||||
if showType then
|
||||
let ty ← withNaryArg 0 delab
|
||||
`(($stx : $ty))
|
||||
else
|
||||
return stx
|
||||
|
||||
/--
|
||||
Delaborates an `OfNat.ofNat` literal.
|
||||
`@OfNat.ofNat _ n _` ~> `n`.
|
||||
-/
|
||||
-- `@OfNat.ofNat _ n _` ~> `n`
|
||||
@[builtin_delab app.OfNat.ofNat]
|
||||
def delabOfNat : Delab := whenPPOption getPPCoercions <| withOverApp 3 do
|
||||
delabOfNatCore (showType := (← getPPOption getPPNumericTypes))
|
||||
|
||||
/--
|
||||
Delaborates the negative of an `OfNat.ofNat` literal.
|
||||
`-@OfNat.ofNat _ n _` ~> `-n`
|
||||
-/
|
||||
@[builtin_delab app.Neg.neg]
|
||||
def delabNeg : Delab := whenPPOption getPPCoercions do
|
||||
delabNegIntCore (showType := (← getPPOption getPPNumericTypes))
|
||||
|
||||
/--
|
||||
Delaborates a rational number literal.
|
||||
`@OfNat.ofNat _ n _ / @OfNat.ofNat _ m` ~> `n / m`
|
||||
and `-@OfNat.ofNat _ n _ / @OfNat.ofNat _ m` ~> `-n / m`
|
||||
-/
|
||||
@[builtin_delab app.HDiv.hDiv]
|
||||
def delabHDiv : Delab := whenPPOption getPPCoercions do
|
||||
delabDivRatCore (showType := (← getPPOption getPPNumericTypes))
|
||||
let .app (.app _ (.lit (.natVal n))) _ ← getExpr | failure
|
||||
return quote n
|
||||
|
||||
-- `@OfDecimal.ofDecimal _ _ m s e` ~> `m*10^(sign * e)` where `sign == 1` if `s = false` and `sign = -1` if `s = true`
|
||||
@[builtin_delab app.OfScientific.ofScientific]
|
||||
|
||||
@@ -63,16 +63,6 @@ register_builtin_option pp.letVarTypes : Bool := {
|
||||
group := "pp"
|
||||
descr := "(pretty printer) display types of let-bound variables"
|
||||
}
|
||||
register_builtin_option pp.natLit : Bool := {
|
||||
defValue := false
|
||||
group := "pp"
|
||||
descr := "(pretty printer) display raw natural number literals with `nat_lit` prefix"
|
||||
}
|
||||
register_builtin_option pp.numericTypes : Bool := {
|
||||
defValue := false
|
||||
group := "pp"
|
||||
descr := "(pretty printer) display types of numeric literals"
|
||||
}
|
||||
register_builtin_option pp.instantiateMVars : Bool := {
|
||||
defValue := false -- TODO: default to true?
|
||||
group := "pp"
|
||||
@@ -135,16 +125,6 @@ register_builtin_option pp.instanceTypes : Bool := {
|
||||
group := "pp"
|
||||
descr := "(pretty printer) when printing explicit applications, show the types of inst-implicit arguments"
|
||||
}
|
||||
register_builtin_option pp.deepTerms : Bool := {
|
||||
defValue := true
|
||||
group := "pp"
|
||||
descr := "(pretty printer) display deeply nested terms, replacing them with `⋯` if set to false"
|
||||
}
|
||||
register_builtin_option pp.deepTerms.threshold : Nat := {
|
||||
defValue := 20
|
||||
group := "pp"
|
||||
descr := "(pretty printer) when `pp.deepTerms` is false, the depth at which terms start being replaced with `⋯`"
|
||||
}
|
||||
register_builtin_option pp.motives.pi : Bool := {
|
||||
defValue := true
|
||||
group := "pp"
|
||||
@@ -203,8 +183,6 @@ def getPPAll (o : Options) : Bool := o.get pp.all.name false
|
||||
def getPPFunBinderTypes (o : Options) : Bool := o.get pp.funBinderTypes.name (getPPAll o)
|
||||
def getPPPiBinderTypes (o : Options) : Bool := o.get pp.piBinderTypes.name pp.piBinderTypes.defValue
|
||||
def getPPLetVarTypes (o : Options) : Bool := o.get pp.letVarTypes.name (getPPAll o)
|
||||
def getPPNumericTypes (o : Options) : Bool := o.get pp.numericTypes.name pp.numericTypes.defValue
|
||||
def getPPNatLit (o : Options) : Bool := o.get pp.natLit.name (getPPNumericTypes o && !getPPAll o)
|
||||
def getPPCoercions (o : Options) : Bool := o.get pp.coercions.name (!getPPAll o)
|
||||
def getPPExplicit (o : Options) : Bool := o.get pp.explicit.name (getPPAll o)
|
||||
def getPPNotation (o : Options) : Bool := o.get pp.notation.name (!getPPAll o)
|
||||
@@ -227,7 +205,5 @@ def getPPMotivesNonConst (o : Options) : Bool := o.get pp.motives.nonConst.name
|
||||
def getPPMotivesAll (o : Options) : Bool := o.get pp.motives.all.name pp.motives.all.defValue
|
||||
def getPPInstances (o : Options) : Bool := o.get pp.instances.name pp.instances.defValue
|
||||
def getPPInstanceTypes (o : Options) : Bool := o.get pp.instanceTypes.name pp.instanceTypes.defValue
|
||||
def getPPDeepTerms (o : Options) : Bool := o.get pp.deepTerms.name pp.deepTerms.defValue
|
||||
def getPPDeepTermsThreshold (o : Options) : Nat := o.get pp.deepTerms.threshold.name pp.deepTerms.threshold.defValue
|
||||
|
||||
end Lean
|
||||
|
||||
@@ -76,7 +76,7 @@ section Elab
|
||||
private def publishIleanInfo (method : String) (m : DocumentMeta) (hOut : FS.Stream)
|
||||
(snaps : Array Snapshot) : IO Unit := do
|
||||
let trees := snaps.map fun snap => snap.infoTree
|
||||
let references ← findModuleRefs m.text trees (localVars := true) |>.toLspModuleRefs
|
||||
let references := findModuleRefs m.text trees (localVars := true)
|
||||
let param := { version := m.version, references : LeanIleanInfoParams }
|
||||
hOut.writeLspNotification { method, param }
|
||||
|
||||
@@ -196,7 +196,7 @@ section Initialization
|
||||
(headerMsgLog : MessageLog)
|
||||
(opts : Options)
|
||||
: Elab.Command.State :=
|
||||
let headerContextInfo : Elab.CommandContextInfo := {
|
||||
let headerContextInfo : Elab.ContextInfo := {
|
||||
env := headerEnv
|
||||
fileMap := m.text
|
||||
ngen := { namePrefix := `_worker }
|
||||
@@ -210,7 +210,7 @@ section Initialization
|
||||
let headerInfoTree := Elab.InfoTree.node headerInfo headerInfoNodes.toPArray'
|
||||
let headerInfoState := {
|
||||
enabled := true
|
||||
trees := #[Elab.InfoTree.context (.commandCtx headerContextInfo) headerInfoTree].toPArray'
|
||||
trees := #[Elab.InfoTree.context headerContextInfo headerInfoTree].toPArray'
|
||||
}
|
||||
{ Elab.Command.mkState headerEnv headerMsgLog opts with infoState := headerInfoState }
|
||||
|
||||
|
||||
@@ -290,23 +290,23 @@ partial def handleDocumentHighlight (p : DocumentHighlightParams)
|
||||
| `(do%$i $elems) => highlightReturn? (i.getRange?.get!.toLspRange text) elems
|
||||
| stx => stx.getArgs.findSome? (highlightReturn? doRange?)
|
||||
|
||||
let highlightRefs? (snaps : Array Snapshot) : IO (Option (Array DocumentHighlight)) := do
|
||||
let highlightRefs? (snaps : Array Snapshot) : Option (Array DocumentHighlight) := Id.run do
|
||||
let trees := snaps.map (·.infoTree)
|
||||
let refs : Lsp.ModuleRefs ← findModuleRefs text trees |>.toLspModuleRefs
|
||||
let refs : Lsp.ModuleRefs := findModuleRefs text trees
|
||||
let mut ranges := #[]
|
||||
for ident in refs.findAt p.position do
|
||||
if let some info := refs.find? ident then
|
||||
if let some ⟨definitionRange, _⟩ := info.definition? then
|
||||
ranges := ranges.push definitionRange
|
||||
ranges := ranges.append <| info.usages.map (·.range)
|
||||
for ident in ← refs.findAt p.position do
|
||||
if let some info ← refs.find? ident then
|
||||
if let some definition := info.definition then
|
||||
ranges := ranges.push definition
|
||||
ranges := ranges.append info.usages
|
||||
if ranges.isEmpty then
|
||||
return none
|
||||
return some <| ranges.map ({ range := ·, kind? := DocumentHighlightKind.text })
|
||||
some <| ranges.map ({ range := ·, kind? := DocumentHighlightKind.text })
|
||||
|
||||
withWaitFindSnap doc (fun s => s.endPos > pos)
|
||||
(notFoundX := pure #[]) fun snap => do
|
||||
let (snaps, _) ← doc.cmdSnaps.getFinishedPrefix
|
||||
if let some his ← highlightRefs? snaps.toArray then
|
||||
if let some his := highlightRefs? snaps.toArray then
|
||||
return his
|
||||
if let some hi := highlightReturn? none snap.stx then
|
||||
return #[hi]
|
||||
|
||||
@@ -53,12 +53,11 @@ def makePopup : WithRpcRef InfoWithCtx → RequestM (RequestTask InfoPopup)
|
||||
| none => pure none
|
||||
let exprExplicit? ← match i.info with
|
||||
| Elab.Info.ofTermInfo ti =>
|
||||
pure <| some <| ← ppExprTaggedWithoutTopLevelHighlight ti.expr (explicit := true)
|
||||
| Elab.Info.ofOmissionInfo { toTermInfo := ti } =>
|
||||
-- Omitted terms are simply to be expanded, not printed explicitly.
|
||||
-- Keep the top-level tag so that users can also see the explicit version
|
||||
-- of the omitted term.
|
||||
pure <| some <| ← ppExprTagged ti.expr (explicit := false)
|
||||
let ti ← ppExprTagged ti.expr (explicit := true)
|
||||
-- remove top-level expression highlight
|
||||
pure <| some <| match ti with
|
||||
| .tag _ tt => tt
|
||||
| tt => tt
|
||||
| Elab.Info.ofFieldInfo fi => pure <| some <| TaggedText.text fi.fieldName.toString
|
||||
| _ => pure none
|
||||
return {
|
||||
@@ -66,12 +65,6 @@ def makePopup : WithRpcRef InfoWithCtx → RequestM (RequestTask InfoPopup)
|
||||
exprExplicit := exprExplicit?
|
||||
doc := ← i.info.docString? : InfoPopup
|
||||
}
|
||||
where
|
||||
ppExprTaggedWithoutTopLevelHighlight (e : Expr) (explicit : Bool) : MetaM CodeWithInfos := do
|
||||
let pp ← ppExprTagged e (explicit := explicit)
|
||||
return match pp with
|
||||
| .tag _ tt => tt
|
||||
| tt => tt
|
||||
|
||||
builtin_initialize
|
||||
registerBuiltinRpcProcedure
|
||||
|
||||
@@ -34,11 +34,14 @@ def locationLinksFromDecl (srcSearchPath : SearchPath) (uri : DocumentUri) (n :
|
||||
|
||||
let ranges? ← findDeclarationRanges? n
|
||||
if let (some ranges, some modUri) := (ranges?, modUri?) then
|
||||
let declRangeToLspRange (r : DeclarationRange) : Lsp.Range :=
|
||||
{ start := ⟨r.pos.line - 1, r.charUtf16⟩
|
||||
«end» := ⟨r.endPos.line - 1, r.endCharUtf16⟩ }
|
||||
let ll : LocationLink := {
|
||||
originSelectionRange? := originRange?
|
||||
targetUri := modUri
|
||||
targetRange := ranges.range.toLspRange
|
||||
targetSelectionRange := ranges.selectionRange.toLspRange
|
||||
targetRange := declRangeToLspRange ranges.range
|
||||
targetSelectionRange := declRangeToLspRange ranges.selectionRange
|
||||
}
|
||||
return #[ll]
|
||||
return #[]
|
||||
|
||||
@@ -35,15 +35,14 @@ structure InfoWithCtx where
|
||||
info : Elab.Info
|
||||
children : PersistentArray InfoTree
|
||||
|
||||
/-- Visit nodes, passing in a surrounding context (the innermost one combined with all outer ones)
|
||||
and accumulating results on the way back up. -/
|
||||
/-- Visit nodes, passing in a surrounding context (the innermost one) and accumulating results on the way back up. -/
|
||||
partial def InfoTree.visitM [Monad m]
|
||||
(preNode : ContextInfo → Info → (children : PersistentArray InfoTree) → m Unit := fun _ _ _ => pure ())
|
||||
(postNode : ContextInfo → Info → (children : PersistentArray InfoTree) → List (Option α) → m α)
|
||||
: InfoTree → m (Option α) :=
|
||||
go none
|
||||
where go
|
||||
| ctx?, context ctx t => go (ctx.mergeIntoOuter? ctx?) t
|
||||
| _, context ctx t => go ctx t
|
||||
| some ctx, node i cs => do
|
||||
preNode ctx i cs
|
||||
let as ← cs.toList.mapM (go <| i.updateContext? ctx)
|
||||
@@ -78,34 +77,13 @@ partial def InfoTree.deepestNodes (p : ContextInfo → Info → PersistentArray
|
||||
partial def InfoTree.foldInfo (f : ContextInfo → Info → α → α) (init : α) : InfoTree → α :=
|
||||
go none init
|
||||
where go ctx? a
|
||||
| context ctx t => go (ctx.mergeIntoOuter? ctx?) a t
|
||||
| context ctx t => go ctx a t
|
||||
| node i ts =>
|
||||
let a := match ctx? with
|
||||
| none => a
|
||||
| some ctx => f ctx i a
|
||||
ts.foldl (init := a) (go <| i.updateContext? ctx?)
|
||||
| hole _ => a
|
||||
|
||||
/--
|
||||
Fold an info tree as follows, while ensuring that the correct `ContextInfo` is supplied at each stage:
|
||||
|
||||
* Nodes are combined with the initial value `init` using `f`, and the result is then combined with the children using a left fold
|
||||
* On InfoTree holes, we just return the initial value.
|
||||
|
||||
This is like `InfoTree.foldInfo`, but it also passes the whole node to `f` instead of just the head.
|
||||
-/
|
||||
partial def InfoTree.foldInfoTree (init : α) (f : ContextInfo → InfoTree → α → α) : InfoTree → α :=
|
||||
go none init
|
||||
where
|
||||
/-- `foldInfoTree.go` is like `foldInfoTree` but with an additional outer context parameter `ctx?`. -/
|
||||
go ctx? a
|
||||
| context ctx t => go (ctx.mergeIntoOuter? ctx?) a t
|
||||
| t@(node i ts) =>
|
||||
let a := match ctx? with
|
||||
| none => a
|
||||
| some ctx => f ctx t a
|
||||
ts.foldl (init := a) (go <| i.updateContext? ctx?)
|
||||
| hole _ => a
|
||||
| _ => a
|
||||
|
||||
def Info.isTerm : Info → Bool
|
||||
| ofTermInfo _ => true
|
||||
@@ -133,13 +111,11 @@ def Info.stx : Info → Syntax
|
||||
| ofUserWidgetInfo i => i.stx
|
||||
| ofFVarAliasInfo _ => .missing
|
||||
| ofFieldRedeclInfo i => i.stx
|
||||
| ofOmissionInfo i => i.stx
|
||||
|
||||
def Info.lctx : Info → LocalContext
|
||||
| Info.ofTermInfo i => i.lctx
|
||||
| Info.ofFieldInfo i => i.lctx
|
||||
| Info.ofOmissionInfo i => i.lctx
|
||||
| _ => LocalContext.empty
|
||||
| Info.ofTermInfo i => i.lctx
|
||||
| Info.ofFieldInfo i => i.lctx
|
||||
| _ => LocalContext.empty
|
||||
|
||||
def Info.pos? (i : Info) : Option String.Pos :=
|
||||
i.stx.getPos? (canonicalOnly := true)
|
||||
@@ -233,15 +209,14 @@ partial def InfoTree.hoverableInfoAt? (t : InfoTree) (hoverPos : String.Pos) (in
|
||||
|
||||
def Info.type? (i : Info) : MetaM (Option Expr) :=
|
||||
match i with
|
||||
| Info.ofTermInfo ti => Meta.inferType ti.expr
|
||||
| Info.ofFieldInfo fi => Meta.inferType fi.val
|
||||
| Info.ofOmissionInfo oi => Meta.inferType oi.expr
|
||||
| Info.ofTermInfo ti => Meta.inferType ti.expr
|
||||
| Info.ofFieldInfo fi => Meta.inferType fi.val
|
||||
| _ => return none
|
||||
|
||||
def Info.docString? (i : Info) : MetaM (Option String) := do
|
||||
let env ← getEnv
|
||||
match i with
|
||||
| .ofTermInfo ti =>
|
||||
| Info.ofTermInfo ti =>
|
||||
if let some n := ti.expr.constName? then
|
||||
return ← findDocString? env n
|
||||
| .ofFieldInfo fi => return ← findDocString? env fi.projName
|
||||
@@ -251,7 +226,6 @@ def Info.docString? (i : Info) : MetaM (Option String) := do
|
||||
if let some decl := (← getOptionDecls).find? oi.optionName then
|
||||
return decl.descr
|
||||
return none
|
||||
| .ofOmissionInfo _ => return none -- Do not display the docstring of ⋯ for omitted terms
|
||||
| _ => pure ()
|
||||
if let some ei := i.toElabInfo? then
|
||||
return ← findDocString? env ei.stx.getKind <||> findDocString? env ei.elaborator
|
||||
@@ -393,16 +367,14 @@ partial def InfoTree.termGoalAt? (t : InfoTree) (hoverPos : String.Pos) : Option
|
||||
partial def InfoTree.hasSorry : InfoTree → IO Bool :=
|
||||
go none
|
||||
where go ci?
|
||||
| .context ci t => go (ci.mergeIntoOuter? ci?) t
|
||||
| .context ci t => go ci t
|
||||
| .node i cs =>
|
||||
match ci?, i with
|
||||
| some ci, .ofTermInfo ti
|
||||
| some ci, .ofOmissionInfo { toTermInfo := ti } => do
|
||||
if let (some ci, .ofTermInfo ti) := (ci?, i) then do
|
||||
let expr ← ti.runMetaM ci (instantiateMVars ti.expr)
|
||||
return expr.hasSorry
|
||||
-- we assume that `cs` are subterms of `ti.expr` and
|
||||
-- thus do not have to be checked as well
|
||||
| _, _ =>
|
||||
else
|
||||
cs.anyM (go ci?)
|
||||
| _ => return false
|
||||
|
||||
|
||||
@@ -13,18 +13,18 @@ namespace Lean.Server
|
||||
open Lsp Lean.Elab Std
|
||||
|
||||
structure Reference where
|
||||
ident : RefIdent
|
||||
ident : RefIdent
|
||||
/-- FVarIds that are logically identical to this reference -/
|
||||
aliases : Array RefIdent := #[]
|
||||
range : Lsp.Range
|
||||
stx : Syntax
|
||||
ci : ContextInfo
|
||||
info : Info
|
||||
aliases : Array RefIdent := #[]
|
||||
range : Lsp.Range
|
||||
stx : Syntax
|
||||
ci : ContextInfo
|
||||
info : Info
|
||||
isBinder : Bool
|
||||
|
||||
structure RefInfo where
|
||||
definition : Option Reference
|
||||
usages : Array Reference
|
||||
usages : Array Reference
|
||||
|
||||
namespace RefInfo
|
||||
|
||||
@@ -37,26 +37,11 @@ def addRef : RefInfo → Reference → RefInfo
|
||||
{ i with usages := usages.push ref }
|
||||
| i, _ => i
|
||||
|
||||
def toLspRefInfo (i : RefInfo) : IO Lsp.RefInfo := do
|
||||
let refToRefInfoLocation (ref : Reference) : IO RefInfo.Location := do
|
||||
let parentDeclName? := ref.ci.parentDecl?
|
||||
let parentDeclRanges? ← ref.ci.runMetaM ref.info.lctx do
|
||||
let some parentDeclName := parentDeclName?
|
||||
| return none
|
||||
findDeclarationRanges? parentDeclName
|
||||
return {
|
||||
range := ref.range
|
||||
parentDecl? := do
|
||||
let parentDeclName ← parentDeclName?
|
||||
let parentDeclRange := (← parentDeclRanges?).range.toLspRange
|
||||
let parentDeclSelectionRange := (← parentDeclRanges?).selectionRange.toLspRange
|
||||
return ⟨parentDeclName, parentDeclRange, parentDeclSelectionRange⟩
|
||||
}
|
||||
let definition? ← i.definition.mapM refToRefInfoLocation
|
||||
let usages ← i.usages.mapM refToRefInfoLocation
|
||||
return {
|
||||
definition? := definition?
|
||||
usages := usages
|
||||
instance : Coe RefInfo Lsp.RefInfo where
|
||||
coe self :=
|
||||
{
|
||||
definition := self.definition.map (·.range)
|
||||
usages := self.usages.map (·.range)
|
||||
}
|
||||
|
||||
end RefInfo
|
||||
@@ -69,10 +54,8 @@ def addRef (self : ModuleRefs) (ref : Reference) : ModuleRefs :=
|
||||
let refInfo := self.findD ref.ident RefInfo.empty
|
||||
self.insert ref.ident (refInfo.addRef ref)
|
||||
|
||||
def toLspModuleRefs (refs : ModuleRefs) : IO Lsp.ModuleRefs := do
|
||||
let refs ← refs.toList.mapM fun (k, v) => do
|
||||
return (k, ← v.toLspRefInfo)
|
||||
return HashMap.ofList refs
|
||||
instance : Coe ModuleRefs Lsp.ModuleRefs where
|
||||
coe self := HashMap.ofList <| List.map (fun (k, v) => (k, v)) <| self.toList
|
||||
|
||||
end ModuleRefs
|
||||
|
||||
@@ -85,15 +68,15 @@ def empty : RefInfo := ⟨ none, #[] ⟩
|
||||
|
||||
def merge (a : RefInfo) (b : RefInfo) : RefInfo :=
|
||||
{
|
||||
definition? := b.definition?.orElse fun _ => a.definition?
|
||||
definition := b.definition.orElse fun _ => a.definition
|
||||
usages := a.usages.append b.usages
|
||||
}
|
||||
|
||||
def findRange? (self : RefInfo) (pos : Lsp.Position) (includeStop := false) : Option Range := do
|
||||
if let some ⟨range, _⟩ := self.definition? then
|
||||
if let some range := self.definition then
|
||||
if contains range pos then
|
||||
return range
|
||||
for ⟨range, _⟩ in self.usages do
|
||||
for range in self.usages do
|
||||
if contains range pos then
|
||||
return range
|
||||
none
|
||||
@@ -134,8 +117,8 @@ open Elab
|
||||
|
||||
/-- Content of individual `.ilean` files -/
|
||||
structure Ilean where
|
||||
version : Nat := 2
|
||||
module : Name
|
||||
version : Nat := 1
|
||||
module : Name
|
||||
references : Lsp.ModuleRefs
|
||||
deriving FromJson, ToJson
|
||||
|
||||
@@ -169,98 +152,64 @@ def findReferences (text : FileMap) (trees : Array InfoTree) : Array Reference :
|
||||
get
|
||||
|
||||
/--
|
||||
There are several different identifiers that should be considered equal for the purpose of finding
|
||||
all references of an identifier:
|
||||
- `FVarId`s of a function parameter in the function's signature and body
|
||||
- Chains of helper definitions like those created for do-reassignment `x := e`
|
||||
- Overlapping definitions like those defined by `where` declarations that define both an FVar
|
||||
(for local usage) and a constant (for non-local usage)
|
||||
- Identifiers connected by `FVarAliasInfo` such as variables before and after `match` generalization
|
||||
The `FVarId`s of a function parameter in the function's signature and body
|
||||
differ. However, they have `TermInfo` nodes with `binder := true` in the exact
|
||||
same position. Moreover, macros such as do-reassignment `x := e` may create
|
||||
chains of variable definitions where a helper definition overlaps with a use
|
||||
of a variable.
|
||||
|
||||
In the first three cases that are not explicitly denoted as aliases with an `FVarAliasInfo`, the
|
||||
corresponding `Reference`s have the exact same range.
|
||||
This function finds all definitions that have the exact same range as another definition or usage
|
||||
and collapses them into a single identifier. It also collapses identifiers connected by
|
||||
an `FVarAliasInfo`.
|
||||
When collapsing identifiers, it prefers using a `RefIdent.const name` over a `RefIdent.fvar id` for
|
||||
all identifiers that are being collapsed into one.
|
||||
This function changes every such group to use a single `FVarId` (the head of the
|
||||
chain/DAG) and gets rid of duplicate definitions.
|
||||
-/
|
||||
partial def combineIdents (trees : Array InfoTree) (refs : Array Reference) : Array Reference := Id.run do
|
||||
partial def combineFvars (trees : Array InfoTree) (refs : Array Reference) : Array Reference := Id.run do
|
||||
-- Deduplicate definitions based on their exact range
|
||||
let mut posMap : HashMap Lsp.Range RefIdent := HashMap.empty
|
||||
let mut posMap : HashMap Lsp.Range FVarId := HashMap.empty
|
||||
for ref in refs do
|
||||
if let { ident, range, isBinder := true, .. } := ref then
|
||||
posMap := posMap.insert range ident
|
||||
if let { ident := RefIdent.fvar id, range, isBinder := true, .. } := ref then
|
||||
posMap := posMap.insert range id
|
||||
|
||||
let idMap := useConstRepresentatives <| buildIdMap posMap
|
||||
let idMap := buildIdMap posMap
|
||||
|
||||
let mut refs' := #[]
|
||||
for ref in refs do
|
||||
let id := ref.ident
|
||||
if idMap.contains id then
|
||||
refs' := refs'.push { ref with ident := findCanonicalRepresentative idMap id, aliases := #[id] }
|
||||
else if !idMap.contains id then
|
||||
match ref with
|
||||
| { ident := ident@(RefIdent.fvar id), .. } =>
|
||||
if idMap.contains id then
|
||||
refs' := refs'.push { ref with ident := applyIdMap idMap ident, aliases := #[ident] }
|
||||
else if !idMap.contains id then
|
||||
refs' := refs'.push ref
|
||||
| _ =>
|
||||
refs' := refs'.push ref
|
||||
refs'
|
||||
where
|
||||
useConstRepresentatives (idMap : HashMap RefIdent RefIdent)
|
||||
: HashMap RefIdent RefIdent := Id.run do
|
||||
let insertIntoClass classesById id :=
|
||||
let representative := findCanonicalRepresentative idMap id
|
||||
let «class» := classesById.findD representative ∅
|
||||
let classesById := classesById.erase representative -- make `«class»` referentially unique
|
||||
let «class» := «class».insert id
|
||||
classesById.insert representative «class»
|
||||
findCanonicalBinder (idMap : HashMap FVarId FVarId) (id : FVarId) : FVarId :=
|
||||
match idMap.find? id with
|
||||
| some id' => findCanonicalBinder idMap id' -- recursion depth is expected to be very low
|
||||
| none => id
|
||||
|
||||
-- collect equivalence classes
|
||||
let mut classesById : HashMap RefIdent (HashSet RefIdent) := ∅
|
||||
for ⟨id, baseId⟩ in idMap.toArray do
|
||||
classesById := insertIntoClass classesById id
|
||||
classesById := insertIntoClass classesById baseId
|
||||
|
||||
let mut r := ∅
|
||||
for ⟨currentRepresentative, «class»⟩ in classesById.toArray do
|
||||
-- find best representative (ideally a const if available)
|
||||
let mut bestRepresentative := currentRepresentative
|
||||
for id in «class» do
|
||||
bestRepresentative :=
|
||||
match bestRepresentative, id with
|
||||
| .fvar a, .fvar _ => .fvar a
|
||||
| .fvar _, .const b => .const b
|
||||
| .const a, .fvar _ => .const a
|
||||
| .const a, .const _ => .const a
|
||||
|
||||
-- compress `idMap` so that all identifiers in a class point to the best representative
|
||||
for id in «class» do
|
||||
if id != bestRepresentative then
|
||||
r := r.insert id bestRepresentative
|
||||
return r
|
||||
|
||||
findCanonicalRepresentative (idMap : HashMap RefIdent RefIdent) (id : RefIdent) : RefIdent := Id.run do
|
||||
let mut canonicalRepresentative := id
|
||||
while idMap.contains canonicalRepresentative do
|
||||
canonicalRepresentative := idMap.find! canonicalRepresentative
|
||||
return canonicalRepresentative
|
||||
applyIdMap : HashMap FVarId FVarId → RefIdent → RefIdent
|
||||
| m, RefIdent.fvar id => RefIdent.fvar <| findCanonicalBinder m id
|
||||
| _, ident => ident
|
||||
|
||||
buildIdMap posMap := Id.run <| StateT.run' (s := HashMap.empty) do
|
||||
-- map fvar defs to overlapping fvar defs/uses
|
||||
for ref in refs do
|
||||
let baseId := ref.ident
|
||||
if let some id := posMap.find? ref.range then
|
||||
insertIdMap id baseId
|
||||
if let { ident := RefIdent.fvar baseId, range, .. } := ref then
|
||||
if let some id := posMap.find? range then
|
||||
insertIdMap id baseId
|
||||
|
||||
-- apply `FVarAliasInfo`
|
||||
trees.forM (·.visitM' (postNode := fun _ info _ => do
|
||||
if let .ofFVarAliasInfo ai := info then
|
||||
insertIdMap (.fvar ai.id) (.fvar ai.baseId)))
|
||||
insertIdMap ai.id ai.baseId))
|
||||
|
||||
get
|
||||
|
||||
-- poor man's union-find; see also `findCanonicalBinder`
|
||||
-- NOTE: poor man's union-find; see also `findCanonicalBinder`
|
||||
insertIdMap id baseId := do
|
||||
let idMap ← get
|
||||
let id := findCanonicalRepresentative idMap id
|
||||
let baseId := findCanonicalRepresentative idMap baseId
|
||||
let id := findCanonicalBinder idMap id
|
||||
let baseId := findCanonicalBinder idMap baseId
|
||||
if baseId != id then
|
||||
modify (·.insert id baseId)
|
||||
|
||||
@@ -280,7 +229,7 @@ def findModuleRefs (text : FileMap) (trees : Array InfoTree) (localVars : Bool :
|
||||
(allowSimultaneousBinderUse := false) : ModuleRefs := Id.run do
|
||||
let mut refs :=
|
||||
dedupReferences (allowSimultaneousBinderUse := allowSimultaneousBinderUse) <|
|
||||
combineIdents trees <|
|
||||
combineFvars trees <|
|
||||
findReferences text trees
|
||||
if !localVars then
|
||||
refs := refs.filter fun
|
||||
@@ -342,12 +291,8 @@ def findRange? (self : References) (module : Name) (pos : Lsp.Position) (include
|
||||
let refs ← self.allRefs.find? module
|
||||
refs.findRange? pos includeStop
|
||||
|
||||
structure DocumentRefInfo where
|
||||
location : Location
|
||||
parentInfo? : Option RefInfo.ParentDecl
|
||||
|
||||
def referringTo (self : References) (identModule : Name) (ident : RefIdent) (srcSearchPath : SearchPath)
|
||||
(includeDefinition : Bool := true) : IO (Array DocumentRefInfo) := do
|
||||
(includeDefinition : Bool := true) : IO (Array Location) := do
|
||||
let refsToCheck := match ident with
|
||||
| RefIdent.const _ => self.allRefs.toList
|
||||
| RefIdent.fvar _ => match self.allRefs.find? identModule with
|
||||
@@ -361,22 +306,22 @@ def referringTo (self : References) (identModule : Name) (ident : RefIdent) (src
|
||||
-- opened in the right folder
|
||||
let uri := System.Uri.pathToUri <| ← IO.FS.realPath path
|
||||
if includeDefinition then
|
||||
if let some ⟨range, parentDeclInfo?⟩ := info.definition? then
|
||||
result := result.push ⟨⟨uri, range⟩, parentDeclInfo?⟩
|
||||
for ⟨range, parentDeclInfo?⟩ in info.usages do
|
||||
result := result.push ⟨⟨uri, range⟩, parentDeclInfo?⟩
|
||||
if let some range := info.definition then
|
||||
result := result.push ⟨uri, range⟩
|
||||
for range in info.usages do
|
||||
result := result.push ⟨uri, range⟩
|
||||
return result
|
||||
|
||||
def definitionOf? (self : References) (ident : RefIdent) (srcSearchPath : SearchPath)
|
||||
: IO (Option DocumentRefInfo) := do
|
||||
: IO (Option Location) := do
|
||||
for (module, refs) in self.allRefs.toList do
|
||||
if let some info := refs.find? ident then
|
||||
if let some ⟨definitionRange, definitionParentDeclInfo?⟩ := info.definition? then
|
||||
if let some definition := info.definition then
|
||||
if let some path ← srcSearchPath.findModuleWithExt "lean" module then
|
||||
-- Resolve symlinks (such as `src` in the build dir) so that files are
|
||||
-- opened in the right folder
|
||||
let uri := System.Uri.pathToUri <| ← IO.FS.realPath path
|
||||
return some ⟨⟨uri, definitionRange⟩, definitionParentDeclInfo?⟩
|
||||
return some ⟨uri, definition⟩
|
||||
return none
|
||||
|
||||
def definitionsMatching (self : References) (srcSearchPath : SearchPath) (filter : Name → Option α)
|
||||
@@ -386,9 +331,9 @@ def definitionsMatching (self : References) (srcSearchPath : SearchPath) (filter
|
||||
if let some path ← srcSearchPath.findModuleWithExt "lean" module then
|
||||
let uri := System.Uri.pathToUri <| ← IO.FS.realPath path
|
||||
for (ident, info) in refs.toList do
|
||||
if let (RefIdent.const name, some ⟨definitionRange, _⟩) := (ident, info.definition?) then
|
||||
if let (RefIdent.const name, some definition) := (ident, info.definition) then
|
||||
if let some a := filter name then
|
||||
result := result.push (a, ⟨uri, definitionRange⟩)
|
||||
result := result.push (a, ⟨uri, definition⟩)
|
||||
if let some maxAmount := maxAmount? then
|
||||
if result.size >= maxAmount then
|
||||
return result
|
||||
|
||||
@@ -22,49 +22,42 @@ For general server architecture, see `README.md`. This module implements the wat
|
||||
|
||||
## Watchdog state
|
||||
|
||||
Most LSP clients only send us file diffs, so to facilitate sending entire file contents to freshly
|
||||
restarted workers, the watchdog needs to maintain the current state of each file. It can also use
|
||||
this state to detect changes to the header and thus restart the corresponding worker, freeing its
|
||||
imports.
|
||||
Most LSP clients only send us file diffs, so to facilitate sending entire file contents to freshly restarted
|
||||
workers, the watchdog needs to maintain the current state of each file. It can also use this state to detect changes
|
||||
to the header and thus restart the corresponding worker, freeing its imports.
|
||||
|
||||
TODO(WN):
|
||||
We may eventually want to keep track of approximately (since this isn't knowable exactly) where in
|
||||
the file a worker crashed. Then on restart, we tell said worker to only parse up to that point and
|
||||
query the user about how to proceed (continue OR allow the user to fix the bug and then continue OR
|
||||
..). Without this, if the crash is deterministic, users may be confused about why the server
|
||||
seemingly stopped working for a single file.
|
||||
We may eventually want to keep track of approximately (since this isn't knowable exactly) where in the file a worker
|
||||
crashed. Then on restart, we tell said worker to only parse up to that point and query the user about how to proceed
|
||||
(continue OR allow the user to fix the bug and then continue OR ..). Without this, if the crash is deterministic,
|
||||
users may be confused about why the server seemingly stopped working for a single file.
|
||||
|
||||
## Watchdog <-> worker communication
|
||||
|
||||
The watchdog process and its file worker processes communicate via LSP. If the necessity arises, we
|
||||
might add non-standard commands similarly based on JSON-RPC. Most requests and notifications are
|
||||
forwarded to the corresponding file worker process, with the exception of these notifications:
|
||||
The watchdog process and its file worker processes communicate via LSP. If the necessity arises,
|
||||
we might add non-standard commands similarly based on JSON-RPC. Most requests and notifications
|
||||
are forwarded to the corresponding file worker process, with the exception of these notifications:
|
||||
|
||||
- textDocument/didOpen: Launch the file worker, create the associated watchdog state and launch a
|
||||
task to asynchronously receive LSP packets from the worker (e.g. request
|
||||
responses).
|
||||
- textDocument/didOpen: Launch the file worker, create the associated watchdog state and launch a task to
|
||||
asynchronously receive LSP packets from the worker (e.g. request responses).
|
||||
- textDocument/didChange: Update the local file state so that it can be resent to restarted workers.
|
||||
Then forward the `didChange` notification.
|
||||
- textDocument/didClose: Signal a shutdown to the file worker and remove the associated watchdog
|
||||
state.
|
||||
- textDocument/didClose: Signal a shutdown to the file worker and remove the associated watchdog state.
|
||||
|
||||
Moreover, we don't implement the full protocol at this level:
|
||||
|
||||
- Upon starting, the `initialize` request is forwarded to the worker, but it must not respond with
|
||||
its server capabilities. Consequently, the watchdog will not send an `initialized` notification to
|
||||
the worker.
|
||||
- After `initialize`, the watchdog sends the corresponding `didOpen` notification with the full
|
||||
current state of the file. No additional `didOpen` notifications will be forwarded to the worker
|
||||
process.
|
||||
- Upon starting, the `initialize` request is forwarded to the worker, but it must not respond with its server
|
||||
capabilities. Consequently, the watchdog will not send an `initialized` notification to the worker.
|
||||
- After `initialize`, the watchdog sends the corresponding `didOpen` notification with the full current state of
|
||||
the file. No additional `didOpen` notifications will be forwarded to the worker process.
|
||||
- `$/cancelRequest` notifications are forwarded to all file workers.
|
||||
- File workers are always terminated with an `exit` notification, without previously receiving a
|
||||
`shutdown` request. Similarly, they never receive a `didClose` notification.
|
||||
- File workers are always terminated with an `exit` notification, without previously receiving a `shutdown` request.
|
||||
Similarly, they never receive a `didClose` notification.
|
||||
|
||||
## Watchdog <-> client communication
|
||||
|
||||
The watchdog itself should implement the LSP standard as closely as possible. However we reserve the
|
||||
right to add non-standard extensions in case they're needed, for example to communicate tactic
|
||||
state.
|
||||
The watchdog itself should implement the LSP standard as closely as possible. However we reserve the right to add
|
||||
non-standard extensions in case they're needed, for example to communicate tactic state.
|
||||
-/
|
||||
|
||||
namespace Lean.Server.Watchdog
|
||||
@@ -90,13 +83,13 @@ section Utils
|
||||
| ioError (e : IO.Error)
|
||||
|
||||
inductive WorkerState where
|
||||
/-- The watchdog can detect a crashed file worker in two places: When trying to send a message
|
||||
to the file worker and when reading a request reply.
|
||||
In the latter case, the forwarding task terminates and delegates a `crashed` event to the
|
||||
main task. Then, in both cases, the file worker has its state set to `crashed` and requests
|
||||
that are in-flight are errored. Upon receiving the next packet for that file worker, the file
|
||||
worker is restarted and the packet is forwarded to it. If the crash was detected while writing
|
||||
a packet, we queue that packet until the next packet for the file worker arrives. -/
|
||||
/-- The watchdog can detect a crashed file worker in two places: When trying to send a message to the file worker
|
||||
and when reading a request reply.
|
||||
In the latter case, the forwarding task terminates and delegates a `crashed` event to the main task.
|
||||
Then, in both cases, the file worker has its state set to `crashed` and requests that are in-flight are errored.
|
||||
Upon receiving the next packet for that file worker, the file worker is restarted and the packet is forwarded
|
||||
to it. If the crash was detected while writing a packet, we queue that packet until the next packet for the file
|
||||
worker arrives. -/
|
||||
| crashed (queuedMsgs : Array JsonRpc.Message)
|
||||
| running
|
||||
|
||||
@@ -109,12 +102,11 @@ section FileWorker
|
||||
proc : Process.Child workerCfg
|
||||
commTask : Task WorkerEvent
|
||||
state : WorkerState
|
||||
-- This should not be mutated outside of namespace FileWorker,
|
||||
-- as it is used as shared mutable state
|
||||
/-- The pending requests map contains all requests that have been received from the LSP client,
|
||||
but were not answered yet.
|
||||
We need them for forwarding cancellation requests to the correct worker as well as cleanly
|
||||
aborting requests on worker crashes. -/
|
||||
-- This should not be mutated outside of namespace FileWorker, as it is used as shared mutable state
|
||||
/-- The pending requests map contains all requests
|
||||
that have been received from the LSP client, but were not answered yet.
|
||||
We need them for forwarding cancellation requests to the correct worker as well as cleanly aborting
|
||||
requests on worker crashes. -/
|
||||
pendingRequestsRef : IO.Ref PendingRequestMap
|
||||
|
||||
namespace FileWorker
|
||||
@@ -128,10 +120,8 @@ section FileWorker
|
||||
def erasePendingRequest (fw : FileWorker) (id : RequestID) : IO Unit :=
|
||||
fw.pendingRequestsRef.modify fun pendingRequests => pendingRequests.erase id
|
||||
|
||||
def errorPendingRequests (fw : FileWorker) (hError : FS.Stream) (code : ErrorCode) (msg : String)
|
||||
: IO Unit := do
|
||||
let pendingRequests ← fw.pendingRequestsRef.modifyGet
|
||||
fun pendingRequests => (pendingRequests, RBMap.empty)
|
||||
def errorPendingRequests (fw : FileWorker) (hError : FS.Stream) (code : ErrorCode) (msg : String) : IO Unit := do
|
||||
let pendingRequests ← fw.pendingRequestsRef.modifyGet (fun pendingRequests => (pendingRequests, RBMap.empty))
|
||||
for ⟨id, _⟩ in pendingRequests do
|
||||
hError.writeLspResponseError { id := id, code := code, message := msg }
|
||||
|
||||
@@ -183,15 +173,13 @@ section ServerM
|
||||
let s ← read
|
||||
if let some path := fileUriToPath? fw.doc.uri then
|
||||
if let some module ← searchModuleNameOfFileName path s.srcSearchPath then
|
||||
s.references.modify fun refs =>
|
||||
refs.updateWorkerRefs module params.version params.references
|
||||
s.references.modify fun refs => refs.updateWorkerRefs module params.version params.references
|
||||
|
||||
def handleIleanInfoFinal (fw : FileWorker) (params : LeanIleanInfoParams) : ServerM Unit := do
|
||||
let s ← read
|
||||
if let some path := fileUriToPath? fw.doc.uri then
|
||||
if let some module ← searchModuleNameOfFileName path s.srcSearchPath then
|
||||
s.references.modify fun refs =>
|
||||
refs.finalizeWorkerRefs module params.version params.references
|
||||
s.references.modify fun refs => refs.finalizeWorkerRefs module params.version params.references
|
||||
|
||||
/-- Creates a Task which forwards a worker's messages into the output stream until an event
|
||||
which must be handled in the main watchdog thread (e.g. an I/O error) happens. -/
|
||||
@@ -229,9 +217,8 @@ section ServerM
|
||||
| 0 =>
|
||||
-- Worker was terminated
|
||||
fw.errorPendingRequests o ErrorCode.contentModified
|
||||
(s!"The file worker for {fw.doc.uri} has been terminated. "
|
||||
++ "Either the header has changed, or the file was closed, "
|
||||
++ " or the server is shutting down.")
|
||||
(s!"The file worker for {fw.doc.uri} has been terminated. Either the header has changed,"
|
||||
++ " or the file was closed, or the server is shutting down.")
|
||||
-- one last message to clear the diagnostics for this file so that stale errors
|
||||
-- do not remain in the editor forever.
|
||||
publishDiagnostics fw.doc #[] o
|
||||
@@ -240,13 +227,8 @@ section ServerM
|
||||
return .importsChanged
|
||||
| _ =>
|
||||
-- Worker crashed
|
||||
let (errorCode, errorCausePointer) :=
|
||||
if exitCode = 1 then
|
||||
(ErrorCode.workerExited, "see stderr for exception")
|
||||
else
|
||||
(ErrorCode.workerCrashed, "likely due to a stack overflow or a bug")
|
||||
fw.errorPendingRequests o errorCode
|
||||
s!"Server process for {fw.doc.uri} crashed, {errorCausePointer}."
|
||||
fw.errorPendingRequests o (if exitCode = 1 then ErrorCode.workerExited else ErrorCode.workerCrashed)
|
||||
s!"Server process for {fw.doc.uri} crashed, {if exitCode = 1 then "see stderr for exception" else "likely due to a stack overflow or a bug"}."
|
||||
publishProgressAtPos fw.doc 0 o (kind := LeanFileProgressKind.fatalError)
|
||||
return WorkerEvent.crashed err
|
||||
loop
|
||||
@@ -318,22 +300,15 @@ section ServerM
|
||||
updateFileWorkers { ←findFileWorker! uri with state := WorkerState.crashed queuedMsgs }
|
||||
|
||||
/-- Tries to write a message, sets the state of the FileWorker to `crashed` if it does not succeed
|
||||
and restarts the file worker if the `crashed` flag was already set. Just logs an error if
|
||||
there is no FileWorker at this `uri`.
|
||||
and restarts the file worker if the `crashed` flag was already set. Just logs an error if there
|
||||
is no FileWorker at this `uri`.
|
||||
Messages that couldn't be sent can be queued up via the queueFailedMessage flag and
|
||||
will be discharged after the FileWorker is restarted. -/
|
||||
def tryWriteMessage
|
||||
(uri : DocumentUri)
|
||||
(msg : JsonRpc.Message)
|
||||
(queueFailedMessage := true)
|
||||
(restartCrashedWorker := false)
|
||||
: ServerM Unit := do
|
||||
def tryWriteMessage (uri : DocumentUri) (msg : JsonRpc.Message) (queueFailedMessage := true) (restartCrashedWorker := false) :
|
||||
ServerM Unit := do
|
||||
let some fw ← findFileWorker? uri
|
||||
| do
|
||||
let errorMsg :=
|
||||
s!"Cannot send message to unknown document '{uri}':\n"
|
||||
++ s!"{(toJson msg).compress}"
|
||||
(←read).hLog.putStrLn errorMsg
|
||||
(←read).hLog.putStrLn s!"Cannot send message to unknown document '{uri}':\n{(toJson msg).compress}"
|
||||
return
|
||||
match fw.state with
|
||||
| WorkerState.crashed queuedMsgs =>
|
||||
@@ -378,8 +353,8 @@ def findDefinitions (p : TextDocumentPositionParams) : ServerM <| Array Location
|
||||
if let some module ← searchModuleNameOfFileName path srcSearchPath then
|
||||
let references ← (← read).references.get
|
||||
for ident in references.findAt module p.position (includeStop := true) do
|
||||
if let some ⟨definitionLocation, _⟩ ← references.definitionOf? ident srcSearchPath then
|
||||
definitions := definitions.push definitionLocation
|
||||
if let some definition ← references.definitionOf? ident srcSearchPath then
|
||||
definitions := definitions.push definition
|
||||
return definitions
|
||||
|
||||
def handleReference (p : ReferenceParams) : ServerM (Array Location) := do
|
||||
@@ -389,142 +364,10 @@ def handleReference (p : ReferenceParams) : ServerM (Array Location) := do
|
||||
if let some module ← searchModuleNameOfFileName path srcSearchPath then
|
||||
let references ← (← read).references.get
|
||||
for ident in references.findAt module p.position (includeStop := true) do
|
||||
let identRefs ← references.referringTo module ident srcSearchPath
|
||||
p.context.includeDeclaration
|
||||
result := result.append <| identRefs.map (·.location)
|
||||
let identRefs ← references.referringTo module ident srcSearchPath p.context.includeDeclaration
|
||||
result := result.append identRefs
|
||||
return result
|
||||
|
||||
private def callHierarchyItemOf? (refs : References) (ident : RefIdent) (srcSearchPath : SearchPath)
|
||||
: IO (Option CallHierarchyItem) := do
|
||||
let some ⟨definitionLocation, parentDecl?⟩ ← refs.definitionOf? ident srcSearchPath
|
||||
| return none
|
||||
|
||||
match ident with
|
||||
| .const definitionName =>
|
||||
-- If we have a constant with a proper name, use it.
|
||||
-- If `callHierarchyItemOf?` is used either on the name of a definition itself or e.g. an
|
||||
-- `inductive` constructor, this is the right thing to do and using the parent decl is
|
||||
-- the wrong thing to do.
|
||||
return some {
|
||||
name := definitionName.toString
|
||||
kind := SymbolKind.constant
|
||||
uri := definitionLocation.uri
|
||||
range := definitionLocation.range,
|
||||
selectionRange := definitionLocation.range
|
||||
}
|
||||
| _ =>
|
||||
let some ⟨parentDeclName, parentDeclRange, parentDeclSelectionRange⟩ := parentDecl?
|
||||
| return none
|
||||
|
||||
return some {
|
||||
name := parentDeclName.toString
|
||||
kind := SymbolKind.constant
|
||||
uri := definitionLocation.uri
|
||||
range := parentDeclRange,
|
||||
selectionRange := parentDeclSelectionRange
|
||||
}
|
||||
|
||||
def handlePrepareCallHierarchy (p : CallHierarchyPrepareParams)
|
||||
: ServerM (Array CallHierarchyItem) := do
|
||||
let some path := fileUriToPath? p.textDocument.uri
|
||||
| return #[]
|
||||
|
||||
let srcSearchPath := (← read).srcSearchPath
|
||||
let some module ← searchModuleNameOfFileName path srcSearchPath
|
||||
| return #[]
|
||||
|
||||
let references ← (← read).references.get
|
||||
let idents := references.findAt module p.position (includeStop := true)
|
||||
|
||||
let items ← idents.filterMapM fun ident => callHierarchyItemOf? references ident srcSearchPath
|
||||
return items
|
||||
|
||||
def handleCallHierarchyIncomingCalls (p : CallHierarchyIncomingCallsParams)
|
||||
: ServerM (Array CallHierarchyIncomingCall) := do
|
||||
let some path := fileUriToPath? p.item.uri
|
||||
| return #[]
|
||||
|
||||
let srcSearchPath := (← read).srcSearchPath
|
||||
let some module ← searchModuleNameOfFileName path srcSearchPath
|
||||
| return #[]
|
||||
|
||||
let references ← (← read).references.get
|
||||
let identRefs ← references.referringTo module (.const p.item.name.toName) srcSearchPath false
|
||||
|
||||
let incomingCalls := identRefs.filterMap fun ⟨location, parentDecl?⟩ => Id.run do
|
||||
|
||||
let some ⟨parentDeclName, parentDeclRange, parentDeclSelectionRange⟩ := parentDecl?
|
||||
| return none
|
||||
return some {
|
||||
«from» := {
|
||||
name := parentDeclName.toString
|
||||
kind := SymbolKind.constant
|
||||
uri := location.uri
|
||||
range := parentDeclRange
|
||||
selectionRange := parentDeclSelectionRange
|
||||
}
|
||||
fromRanges := #[location.range]
|
||||
}
|
||||
|
||||
return collapseSameIncomingCalls incomingCalls
|
||||
|
||||
where
|
||||
|
||||
collapseSameIncomingCalls (incomingCalls : Array CallHierarchyIncomingCall)
|
||||
: Array CallHierarchyIncomingCall :=
|
||||
let grouped := incomingCalls.groupByKey (·.«from»)
|
||||
let collapsed := grouped.toArray.map fun ⟨_, group⟩ => {
|
||||
«from» := group[0]!.«from»
|
||||
fromRanges := group.concatMap (·.fromRanges)
|
||||
}
|
||||
collapsed
|
||||
|
||||
def handleCallHierarchyOutgoingCalls (p : CallHierarchyOutgoingCallsParams)
|
||||
: ServerM (Array CallHierarchyOutgoingCall) := do
|
||||
let some path := fileUriToPath? p.item.uri
|
||||
| return #[]
|
||||
|
||||
let srcSearchPath := (← read).srcSearchPath
|
||||
let some module ← searchModuleNameOfFileName path srcSearchPath
|
||||
| return #[]
|
||||
|
||||
let references ← (← read).references.get
|
||||
|
||||
let some refs := references.allRefs.find? module
|
||||
| return #[]
|
||||
|
||||
let items ← refs.toArray.filterMapM fun ⟨ident, info⟩ => do
|
||||
let outgoingUsages := info.usages.filter fun usage => Id.run do
|
||||
let some parentDecl := usage.parentDecl?
|
||||
| return false
|
||||
return p.item.name.toName == parentDecl.name
|
||||
|
||||
let outgoingUsages := outgoingUsages.map (·.range)
|
||||
if outgoingUsages.isEmpty then
|
||||
return none
|
||||
|
||||
let some item ← callHierarchyItemOf? references ident srcSearchPath
|
||||
| return none
|
||||
|
||||
-- filter local defs from outgoing calls
|
||||
if item.name == p.item.name then
|
||||
return none
|
||||
|
||||
return some ⟨item, outgoingUsages⟩
|
||||
|
||||
return collapseSameOutgoingCalls items
|
||||
|
||||
where
|
||||
|
||||
collapseSameOutgoingCalls (outgoingCalls : Array CallHierarchyOutgoingCall)
|
||||
: Array CallHierarchyOutgoingCall :=
|
||||
let grouped := outgoingCalls.groupByKey (·.to)
|
||||
let collapsed := grouped.toArray.map fun ⟨_, group⟩ => {
|
||||
to := group[0]!.to
|
||||
fromRanges := group.concatMap (·.fromRanges)
|
||||
}
|
||||
collapsed
|
||||
|
||||
def handleWorkspaceSymbol (p : WorkspaceSymbolParams) : ServerM (Array SymbolInformation) := do
|
||||
if p.query.isEmpty then
|
||||
return #[]
|
||||
@@ -593,8 +436,7 @@ section NotificationHandling
|
||||
let newDocText := foldDocumentChanges changes oldDoc.text
|
||||
let newDoc : DocumentMeta := ⟨doc.uri, newVersion, newDocText, oldDoc.dependencyBuildMode⟩
|
||||
updateFileWorkers { fw with doc := newDoc }
|
||||
let notification := Notification.mk "textDocument/didChange" p
|
||||
tryWriteMessage doc.uri notification (restartCrashedWorker := true)
|
||||
tryWriteMessage doc.uri (Notification.mk "textDocument/didChange" p) (restartCrashedWorker := true)
|
||||
|
||||
def handleDidClose (p : DidCloseTextDocumentParams) : ServerM Unit :=
|
||||
terminateFileWorker p.textDocument.uri
|
||||
@@ -627,18 +469,15 @@ section NotificationHandling
|
||||
if (← fw.pendingRequestsRef.get).contains p.id then
|
||||
tryWriteMessage uri (Notification.mk "$/cancelRequest" p) (queueFailedMessage := false)
|
||||
|
||||
def forwardNotification {α : Type} [ToJson α] [FileSource α] (method : String) (params : α)
|
||||
: ServerM Unit :=
|
||||
def forwardNotification {α : Type} [ToJson α] [FileSource α] (method : String) (params : α) : ServerM Unit :=
|
||||
tryWriteMessage (fileSource params) (Notification.mk method params) (queueFailedMessage := true)
|
||||
end NotificationHandling
|
||||
|
||||
section MessageHandling
|
||||
def parseParams (paramType : Type) [FromJson paramType] (params : Json) : ServerM paramType :=
|
||||
match fromJson? params with
|
||||
| Except.ok parsed =>
|
||||
pure parsed
|
||||
| Except.error inner =>
|
||||
throwServerError s!"Got param with wrong structure: {params.compress}\n{inner}"
|
||||
| Except.ok parsed => pure parsed
|
||||
| Except.error inner => throwServerError s!"Got param with wrong structure: {params.compress}\n{inner}"
|
||||
|
||||
def forwardRequestToWorker (id : RequestID) (method : String) (params : Json) : ServerM Unit := do
|
||||
let uri: DocumentUri ←
|
||||
@@ -693,48 +532,25 @@ section MessageHandling
|
||||
(← read).hOut.writeLspResponse ⟨id, definitions⟩
|
||||
return
|
||||
match method with
|
||||
| "textDocument/references" =>
|
||||
handle ReferenceParams (Array Location) handleReference
|
||||
| "workspace/symbol" =>
|
||||
handle WorkspaceSymbolParams (Array SymbolInformation) handleWorkspaceSymbol
|
||||
| "textDocument/prepareCallHierarchy" =>
|
||||
handle CallHierarchyPrepareParams (Array CallHierarchyItem) handlePrepareCallHierarchy
|
||||
| "callHierarchy/incomingCalls" =>
|
||||
handle CallHierarchyIncomingCallsParams (Array CallHierarchyIncomingCall)
|
||||
handleCallHierarchyIncomingCalls
|
||||
| "callHierarchy/outgoingCalls" =>
|
||||
handle Lsp.CallHierarchyOutgoingCallsParams (Array CallHierarchyOutgoingCall)
|
||||
handleCallHierarchyOutgoingCalls
|
||||
| "textDocument/prepareRename" =>
|
||||
handle PrepareRenameParams (Option Range) handlePrepareRename
|
||||
| "textDocument/rename" =>
|
||||
handle RenameParams WorkspaceEdit handleRename
|
||||
| _ =>
|
||||
forwardRequestToWorker id method params
|
||||
| "textDocument/references" => handle ReferenceParams (Array Location) handleReference
|
||||
| "workspace/symbol" => handle WorkspaceSymbolParams (Array SymbolInformation) handleWorkspaceSymbol
|
||||
| "textDocument/prepareRename" => handle PrepareRenameParams (Option Range) handlePrepareRename
|
||||
| "textDocument/rename" => handle RenameParams WorkspaceEdit handleRename
|
||||
| _ => forwardRequestToWorker id method params
|
||||
|
||||
def handleNotification (method : String) (params : Json) : ServerM Unit := do
|
||||
let handle := fun α [FromJson α] (handler : α → ServerM Unit) =>
|
||||
parseParams α params >>= handler
|
||||
let handle := (fun α [FromJson α] (handler : α → ServerM Unit) => parseParams α params >>= handler)
|
||||
match method with
|
||||
| "textDocument/didOpen" =>
|
||||
handle _ handleDidOpen
|
||||
| "textDocument/didChange" =>
|
||||
handle DidChangeTextDocumentParams handleDidChange
|
||||
| "textDocument/didClose" =>
|
||||
handle DidCloseTextDocumentParams handleDidClose
|
||||
| "workspace/didChangeWatchedFiles" =>
|
||||
handle DidChangeWatchedFilesParams handleDidChangeWatchedFiles
|
||||
| "$/cancelRequest" =>
|
||||
handle CancelParams handleCancelRequest
|
||||
| "$/lean/rpc/connect" =>
|
||||
handle RpcConnectParams (forwardNotification method)
|
||||
| "$/lean/rpc/release" =>
|
||||
handle RpcReleaseParams (forwardNotification method)
|
||||
| "$/lean/rpc/keepAlive" =>
|
||||
handle RpcKeepAliveParams (forwardNotification method)
|
||||
| _ =>
|
||||
-- implementation-dependent notifications can be safely ignored
|
||||
if !"$/".isPrefixOf method then
|
||||
| "textDocument/didOpen" => handle _ handleDidOpen
|
||||
| "textDocument/didChange" => handle DidChangeTextDocumentParams handleDidChange
|
||||
| "textDocument/didClose" => handle DidCloseTextDocumentParams handleDidClose
|
||||
| "workspace/didChangeWatchedFiles" => handle DidChangeWatchedFilesParams handleDidChangeWatchedFiles
|
||||
| "$/cancelRequest" => handle CancelParams handleCancelRequest
|
||||
| "$/lean/rpc/connect" => handle RpcConnectParams (forwardNotification method)
|
||||
| "$/lean/rpc/release" => handle RpcReleaseParams (forwardNotification method)
|
||||
| "$/lean/rpc/keepAlive" => handle RpcKeepAliveParams (forwardNotification method)
|
||||
| _ =>
|
||||
if !"$/".isPrefixOf method then -- implementation-dependent notifications can be safely ignored
|
||||
(←read).hLog.putStrLn s!"Got unsupported notification: {method}"
|
||||
end MessageHandling
|
||||
|
||||
@@ -798,8 +614,7 @@ section MainLoop
|
||||
handleCrash fw.doc.uri #[]
|
||||
mainLoop clientTask
|
||||
| WorkerEvent.terminated =>
|
||||
throwServerError <| "Internal server error: got termination event for worker that "
|
||||
++ "should have been removed"
|
||||
throwServerError "Internal server error: got termination event for worker that should have been removed"
|
||||
| .importsChanged =>
|
||||
startFileWorker fw.doc
|
||||
mainLoop clientTask
|
||||
@@ -822,7 +637,6 @@ def mkLeanServerCapabilities : ServerCapabilities := {
|
||||
definitionProvider := true
|
||||
typeDefinitionProvider := true
|
||||
referencesProvider := true
|
||||
callHierarchyProvider := true
|
||||
renameProvider? := some {
|
||||
prepareProvider := true
|
||||
}
|
||||
@@ -863,8 +677,7 @@ def initAndRunWatchdogAux : ServerM Unit := do
|
||||
def findWorkerPath : IO System.FilePath := do
|
||||
let mut workerPath ← IO.appPath
|
||||
if let some path := (←IO.getEnv "LEAN_SYSROOT") then
|
||||
workerPath := System.FilePath.mk path / "bin" / "lean"
|
||||
|>.addExtension System.FilePath.exeExtension
|
||||
workerPath := System.FilePath.mk path / "bin" / "lean" |>.addExtension System.FilePath.exeExtension
|
||||
if let some path := (←IO.getEnv "LEAN_WORKER_PATH") then
|
||||
workerPath := System.FilePath.mk path
|
||||
return workerPath
|
||||
|
||||
@@ -530,5 +530,4 @@ def Stack.matches (stack : Syntax.Stack) (pattern : List $ Option SyntaxNodeKind
|
||||
|>.all id)
|
||||
|
||||
end Syntax
|
||||
|
||||
end Lean
|
||||
|
||||
@@ -131,8 +131,6 @@ is `ConstructorVal` for `Nat.succ`, and the array `#[1]`. The parameter `useRaw`
|
||||
numeral is represented. If `useRaw := false`, then `mkNatLit` is used, otherwise `mkRawNatLit`.
|
||||
Recall that `mkNatLit` uses the `OfNat.ofNat` application which is the canonical way of representing numerals
|
||||
in the elaborator and tactic framework. We `useRaw := false` in the compiler (aka code generator).
|
||||
|
||||
Remark: This function does not treat `(ofNat 0 : Nat)` applications as constructors.
|
||||
-/
|
||||
def constructorApp? (env : Environment) (e : Expr) (useRaw := false) : Option (ConstructorVal × Array Expr) := do
|
||||
match e with
|
||||
|
||||
@@ -95,7 +95,7 @@ def InteractiveGoalCore.pretty (g : InteractiveGoalCore) (userName? : Option Str
|
||||
where
|
||||
addLine (fmt : Format) : Format :=
|
||||
if fmt.isNil then fmt else fmt ++ Format.line
|
||||
|
||||
|
||||
def InteractiveGoal.pretty (g : InteractiveGoal) : Format :=
|
||||
g.toInteractiveGoalCore.pretty g.userName? g.goalPrefix
|
||||
|
||||
@@ -191,7 +191,7 @@ def goalToInteractive (mvarId : MVarId) : MetaM InteractiveGoal := do
|
||||
return {
|
||||
hyps
|
||||
type := goalFmt
|
||||
ctx := ⟨{← Elab.CommandContextInfo.save with }⟩
|
||||
ctx := ⟨← Elab.ContextInfo.save⟩
|
||||
userName?
|
||||
goalPrefix := getGoalPrefix mvarDecl
|
||||
mvarId
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user