Compare commits

..

9 Commits

Author SHA1 Message Date
Scott Morrison
a63acb32b3 more fixes 2024-01-22 13:45:47 +11:00
Scott Morrison
89c733625d merge master 2024-01-22 13:43:39 +11:00
Scott Morrison
0cc849691d followup to review suggestion 2024-01-22 13:40:55 +11:00
Scott Morrison
5694dab653 Update .github/workflows/pr-release.yml
Co-authored-by: Joachim Breitner <mail@joachim-breitner.de>
2024-01-22 13:39:37 +11:00
Scott Morrison
f9bd389ed6 more good advice from the linter 2024-01-19 13:32:42 +11:00
Scott Morrison
cb5a0e9da8 advice from linter 2024-01-19 12:57:57 +11:00
Scott Morrison
cf6d5224d7 chore: create lean-pr-testing-NNNN branches at Std too 2024-01-19 12:49:20 +11:00
Scott Morrison
0fe71e96f9 add comment 2024-01-19 12:48:35 +11:00
Scott Morrison
bdde13e01f chore: CI looks for nightly-testing-YYYY-MM-DD at Mathlib as either a branch or tag 2024-01-19 12:33:19 +11:00
315 changed files with 1384 additions and 5317 deletions

View File

@@ -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 }})"

View File

@@ -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

View File

@@ -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/)

View File

@@ -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
---------

View File

@@ -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.

View File

@@ -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
View File

@@ -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"
}

View File

@@ -12,7 +12,7 @@
flake = false;
};
inputs.leanInk = {
url = "github:leanprover/LeanInk/refs/pull/57/merge";
url = "github:leanprover/LeanInk";
flake = false;
};

View File

@@ -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 :=

View File

@@ -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.

View File

@@ -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

View File

@@ -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.

View File

@@ -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

View File

@@ -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

View File

@@ -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")

View File

@@ -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

View File

@@ -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 α)

View File

@@ -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)

View File

@@ -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

View File

@@ -5,6 +5,7 @@ Authors: Leonardo de Moura
-/
prelude
import Init.Data.Fin.Basic
import Init.System.Platform
open Nat

View File

@@ -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

View File

@@ -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,*])

View File

@@ -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.

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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
}

View File

@@ -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.

View File

@@ -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

View File

@@ -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

View File

@@ -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}"

View File

@@ -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`

View File

@@ -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)

View File

@@ -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]

View File

@@ -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

View File

@@ -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!

View File

@@ -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 {

View File

@@ -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

View File

@@ -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])`

View File

@@ -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.

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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)

View File

@@ -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

View File

@@ -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)

View File

@@ -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)

View File

@@ -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

View File

@@ -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

View File

@@ -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 := {})

View File

@@ -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"]

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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 :=

View File

@@ -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

View File

@@ -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.

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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 {}

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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!

View File

@@ -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)

View File

@@ -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

View File

@@ -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]

View File

@@ -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

View File

@@ -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 }

View File

@@ -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]

View File

@@ -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

View File

@@ -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 #[]

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -530,5 +530,4 @@ def Stack.matches (stack : Syntax.Stack) (pattern : List $ Option SyntaxNodeKind
|>.all id)
end Syntax
end Lean

View File

@@ -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

View File

@@ -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