Compare commits

...

46 Commits

Author SHA1 Message Date
Kim Morrison
e1fcd9ac52 deprecations 2024-11-20 11:53:41 +11:00
Kim Morrison
87f94f2297 add find?_pmap 2024-11-20 11:43:16 +11:00
Kim Morrison
287dc6de43 feat: duplicate List.attach/attachWith/pmap API for Array 2024-11-20 11:40:17 +11:00
Kyle Miller
5eef3d27fb feat: have #print show precise fields of structures (#6096)
This PR improves the `#print` command for structures to show all fields
and which parents the fields were inherited from, hiding internal
details such as which parents are represented as subobjects. This
information is still present in the constructor if needed. The pretty
printer for private constants is also improved, and it now handles
private names from the current module like any other name; private names
from other modules are made hygienic.

Example output for `#print Monad`:
```
class Monad.{u, v} (m : Type u → Type v) : Type (max (u + 1) v)
number of parameters: 1
parents:
  Monad.toApplicative : Applicative m
  Monad.toBind : Bind m
fields:
  Functor.map : {α β : Type u} → (α → β) → m α → m β
  Functor.mapConst : {α β : Type u} → α → m β → m α
  Pure.pure : {α : Type u} → α → m α
  Seq.seq : {α β : Type u} → m (α → β) → (Unit → m α) → m β
  SeqLeft.seqLeft : {α β : Type u} → m α → (Unit → m β) → m α
  SeqRight.seqRight : {α β : Type u} → m α → (Unit → m β) → m β
  Bind.bind : {α β : Type u} → m α → (α → m β) → m β
constructor:
  Monad.mk.{u, v} {m : Type u → Type v} [toApplicative : Applicative m] [toBind : Bind m] : Monad m
resolution order:
  Monad, Applicative, Bind, Functor, Pure, Seq, SeqLeft, SeqRight
```

Suggested by Floris van Doorn [on
Zulip](https://leanprover.zulipchat.com/#narrow/channel/270676-lean4/topic/.23print.20command.20for.20structures/near/482503637).
2024-11-19 21:54:45 +00:00
Leonardo de Moura
75d1504af2 fix: isDefEq for constants with different universe parameters (#6131)
This PR fixes a bug at the definitional equality test (`isDefEq`). At
unification constraints of the form `c.{u} =?= c.{v}`, it was not trying
to unfold `c`. This bug did not affect the kernel.

closes #6117
2024-11-19 21:39:13 +00:00
Mario Carneiro
a00cf6330f fix: add a missing case to Level.geq (#2689)
This PR adds a case to `Level.geq` that is present in the kernel's level
`is_geq` procedure, making them consistent with one another.

This came up during testing of `lean4lean`. Currently `Level.geq`
differs from `level::is_geq` in the case of `max u v >= imax u v`. The
elaborator function is overly pessimistic and yields `false` on this
while the kernel function yields true. This comes up concretely in the
`Trans` class:
```lean
class Trans (r : α → β → Sort u) (s : β → γ → Sort v) (t : outParam (α → γ → Sort w)) where
  trans : r a b → s b c → t a c
```
The type of this class is `Sort (max (max (max (max (max (max 1 u) u_1)
u_2) u_3) v) w)` (where `u_1 u_2 u_3` are the levels of `α β γ`), but if
you try writing that type explicitly then the `class` command fails.
Omitting the type leaves the `class` to infer the universe level (the
command assumes the level is correct, and the kernel agrees it is), but
including the type then the elaborator checks the level inequality with
`Level.geq` and fails.

---------

Co-authored-by: Kyle Miller <kmill31415@gmail.com>
2024-11-19 21:27:00 +00:00
Leonardo de Moura
1f32477385 fix: isDefEq when zetaDelta := false (#6129)
This PR fixes a bug at `isDefEq` when `zetaDelta := false`. See new test
for a small example that exposes the issue.
2024-11-19 21:22:02 +00:00
Thomas Köppe
91c14c7ee9 fix: only consider salient bytes in sharecommon eq, hash (#5840)
This PR changes `lean_sharecommon_{eq,hash}` to only consider the
salient bytes of an object, and not any bytes of any
unspecified/uninitialized unused capacity.

Accessing uninitialized storage results in undefined behaviour.

This does not seem to have any semantics disadvantages: If objects
compare equal after this change, their salient bytes are still equal. By
contrast, if the actual identity of allocations needs to be
distinguished, that can be done by just comparing pointers to the
storage.

If we wanted to retain the current logic, we would need initialize the
otherwise unused parts to some specific value to avoid the undefined
behaviour.

Closes #5831
2024-11-19 13:56:46 +00:00
Lean stage0 autoupdater
69530afdf9 chore: update stage0 2024-11-19 13:06:43 +00:00
Marc Huisinga
b7667c1604 fix: don't issue atomic id completions when there is a dangling dot (#5837)
This PR fixes an old auto-completion bug where `x.` would issue
nonsensical completions when `x.` could not be elaborated as a dot
completion.
2024-11-19 12:23:41 +00:00
Eric Wieser
d6f898001b chore: generalize List.get_mem (#6095)
This is syntactically more general than before, though up to eta
expansion it make no difference.
2024-11-19 11:08:10 +00:00
Marc Huisinga
a38566693b test: fix brittle structure instance completion test (#6127)
#5835 contains a brittle test that uses an FVar ID, which caused a
failure on master. This PR changes that test to use a declaration
instead.
2024-11-19 10:13:51 +00:00
Marc Huisinga
4bef3588b5 chore: update stage0 2024-11-19 09:26:58 +01:00
Marc Huisinga
64538cf6e8 chore: prepare for bootstrap
Co-Authored-By: Sebastian Ullrich <sebasti@nullri.ch>
2024-11-19 09:26:58 +01:00
Marc Huisinga
aadf3f1d2c feat: use new structInstFields parser to tag structure instance fields 2024-11-19 09:26:58 +01:00
Marc Huisinga
95bf45ff8b refactor: split Completion.lean 2024-11-19 09:26:58 +01:00
Marc Huisinga
2a02c121cf feat: structure auto-completion & partial InfoTrees 2024-11-19 09:26:58 +01:00
Mac Malone
4600bb16fc feat: use BaseIO at IO.rand (#6102)
This PR moves `IO.rand` and `IO.setRandSeed` to be in the `BaseIO`
monad.

This is their proper monad as neither can error.
2024-11-19 05:26:03 +00:00
Kim Morrison
7ccdfc30ff chore: turn off pp.mvars in apply? results (#6108)
Per request on
[zulip](https://leanprover.zulipchat.com/#narrow/channel/270676-lean4/topic/apply.3F.20using.20tombstones/near/482895588).
2024-11-19 02:02:32 +00:00
Kim Morrison
7f0bdefb6e chore: fix apply? error reporting when out of heartbeats (#6121) 2024-11-19 00:57:59 +00:00
Joachim Breitner
799b2b6628 fix: handle reordered indices in structural recursion (#6116)
This PR fixes a bug where structural recursion did not work when indices
of the recursive argument appeared as function parameters in a different
order than in the argument's type's definition.

Fixes #6015.
2024-11-18 11:28:02 +00:00
David Thrane Christiansen
b8d6e44c4f fix: liberalize rules for atoms by allowing leading '' (#6114)
This PR liberalizes atom rules by allowing `''` to be a prefix of an
atom, after #6012 only added an exception for `''` alone, and also adds
some unit tests for atom validation.
2024-11-18 10:19:20 +00:00
Kim Morrison
5a99cb326c chore: make Lean.Elab.Command.mkMetaContext public (#6113) 2024-11-18 06:14:34 +00:00
Kim Morrison
e10fac93a6 feat: lemmas for Array.findSome? and find? (#6111)
This PR fills in the API for `Array.findSome?` and `Array.find?`,
transferring proofs from the corresponding List statements.
2024-11-18 04:19:56 +00:00
Kyle Miller
62ae320e1c chore: document Lean.Elab.StructInst, refactor (#6110)
This PR does some mild refactoring of the `Lean.Elab.StructInst` module
while adding documentation.

Documentation is drawn from @thorimur's #1928.
2024-11-18 02:57:22 +00:00
Leonardo de Moura
98b1edfc1f fix: backtrack at injection failure (#6109)
This PR fixes an issue in the `injection` tactic. This tactic may
execute multiple sub-tactics. If any of them fail, we must backtrack the
partial assignment. This issue was causing the error: "`mvarId` is
already assigned" in issue #6066. The issue is not yet resolved, as the
equation generator for the match expressions is failing in the example
provided in this issue.
2024-11-18 02:26:06 +00:00
Leonardo de Moura
ab162b3f52 fix: isDefEq, whnf, simp caching and configuration (#6053)
This PR fixes the caching infrastructure for `whnf` and `isDefEq`,
ensuring the cache accounts for all relevant configuration flags. It
also cleans up the `WHNF.lean` module and improves the configuration of
`whnf`.
2024-11-18 01:17:26 +00:00
Kim Morrison
b8a13ab755 chore: fix naming of left/right injectivity lemmas (#6106)
We've been internally inconsistent on the naming of these lemmas in
Lean; this changes them to match Mathlib (which, moreover, I think is
correct).
2024-11-18 00:53:46 +00:00
Sebastian Ullrich
405593ea28 chore: avoid stack overflow in debug tests (#6103) 2024-11-17 14:54:49 +00:00
Kim Morrison
24f305c0e3 chore: fix canonicalizer handling over forall/lambda (#6082)
This PR changes how the canonicalizer handles `forall` and `lambda`,
replacing bvars with temporary fvars. Fixes a bug reported by @hrmacbeth
on
[zulip](https://leanprover.zulipchat.com/#narrow/channel/270676-lean4/topic/Quantifiers.20in.20CanonM/near/482483448).
2024-11-17 07:34:45 +00:00
Leonardo de Moura
5d553d6369 fix: circular assignment at structure instance elaborator (#6105)
This PR fixes a stack overflow caused by a cyclic assignment in the
metavariable context. The cycle is unintentionally introduced by the
structure instance elaborator.

closes #3150
2024-11-17 00:56:52 +00:00
Sebastian Ullrich
a449e3fdd6 feat: IO.getTID (#6049)
This PR adds a primitive for accessing the current thread ID

To be used in a thread-aware trace profiler
2024-11-16 19:13:11 +00:00
Kyle Miller
764386734c fix: improvements to change tactic (#6022)
This PR makes the `change` tactic and conv tactic use the same
elaboration strategy. It works uniformly for both the target and local
hypotheses. Now `change` can assign metavariables, for example:
```lean
example (x y z : Nat) : x + y = z := by
  change ?a = _
  let w := ?a
  -- now `w : Nat := x + y`
```
2024-11-16 07:08:29 +00:00
Kyle Miller
7f1d7a595b fix: use Expr.equal instead of == in MVarId.replaceTargetDefEq and MVarId.replaceLocalDeclDefEq (#6098)
This PR modifies `Lean.MVarId.replaceTargetDefEq` and
`Lean.MVarId.replaceLocalDeclDefEq` to use `Expr.equal` instead of
`Expr.eqv` when determining whether the expression has changed. This is
justified on the grounds that binder names and binder infos are
user-visible and affect elaboration.
2024-11-16 02:03:16 +00:00
Leonardo de Moura
f13e5ca852 chore: naming convention and NaN normalization (#6097)
Changes:
- `Float.fromBits` => `Float.ofBits`
- NaN normalization
2024-11-16 00:14:28 +00:00
Leonardo de Moura
ecbaeff24b feat: add Float.toBits and Float.fromBits (#6094)
This PR adds raw transmutation of floating-point numbers to and from
`UInt64`. Floats and UInts share the same endianness across all
supported platforms. The IEEE 754 standard precisely specifies the bit
layout of floats. Note that `Float.toBits` is distinct from
`Float.toUInt64`, which attempts to preserve the numeric value rather
than the bitwise value.

closes #6071
2024-11-15 19:45:19 +00:00
Kyle Miller
691acde696 feat: pp.parens option to pretty print with all parentheses (#2934)
This PR adds the option `pp.parens` (default: false) that causes the
pretty printer to eagerly insert parentheses, which can be useful for
teaching and for understanding the structure of expressions. For
example, it causes `p → q → r` to pretty print as `p → (q → r)`.

Any notations with precedence greater than or equal to `maxPrec` do not
receive such discretionary parentheses, since this precedence level is
considered to be infinity.

This option was a feature in the Lean 3 community edition.
2024-11-15 19:11:54 +00:00
Kyle Miller
b1e0c1b594 chore: remove decide! tactic (#6016)
This PR removes the `decide!` tactic in favor of `decide +kernel`
(breaking change).
2024-11-15 17:49:33 +00:00
Joachim Breitner
93b4ec0351 refactor: use mkFreshUserName in ArgsPacker (#6093)
and other small refinements done while investigating an issue; not
actually user-visible.
2024-11-15 15:59:14 +00:00
JovanGerb
f06fc30c0b perf: remove @[specialize] from mkBinding (#6019)
This PR removes @[specilize] from `MkBinding.mkBinding`, which is a
function that cannot be specialized (as none of its arguments are
functions). As a result, the specializable function `Nat.foldRevM.loop`
doesn't get specialized, which leads to worse performing code.

As expected, the mathlib bench shows a very small improvement. About 95%
of files show a speedup.
(http://speed.lean-fro.org/mathlib4/compare/e7b27246-a3e6-496a-b552-ff4b45c7236e/to/6033df75-aa53-44d9-819d-51f93fc05e94?hash1=b28f0d7f7e9cc3949a9a3556a6b36513f37f690d)
2024-11-15 15:06:49 +00:00
Markus Himmel
64b35a8c19 perf: add LEAN_ALWAYS_INLINE to some functions (#6045)
Otherwise, clang refuses to inline them for large functions which leads
to a performance cliff.
2024-11-15 15:05:32 +00:00
Markus Himmel
688ee4c887 fix: constant folding for Nat.ble and Nat.blt (#6087)
This PR fixes a bug in the constant folding for the `Nat.ble` and
`Nat.blt` function in the old code generator, leading to a
miscompilation.

Closes #6086
2024-11-15 12:09:52 +00:00
Henrik Böving
9a3dd615e0 chore: bv_decide remove noop rewrites (#6080)
Merely removes rules that are actually just syntactic aliases but equal
at the `Expr` level.
2024-11-15 11:41:54 +00:00
Violeta Hernández
7e6363dc05 chore: join → flatten in docstring (#6040)
Update the docstring of `List.flatten`.
2024-11-15 10:11:42 +00:00
Kim Morrison
a074bd9a2b feat: implementation of Array.pmap (#6052)
This PR adds `Array.pmap`, as well as a `@[csimp]` lemma in terms of the
no-copy `Array.attachWith`.
2024-11-15 02:10:04 +00:00
Kyle Miller
498d41633b fix: pretty print .coeFun with terminfo of coercee (#6085)
This PR improves the term info for coercions marked with
`CoeFnType.coeFun` (such as `DFunLike.coe` in Mathlib), making "go to
definition" on the function name work. Hovering over such a coerced
function will show the coercee rather than the coercion expression. The
coercion expression can still be seen by hovering over the whitespace in
the function application.
2024-11-15 01:45:38 +00:00
453 changed files with 5999 additions and 2462 deletions

View File

@@ -10,6 +10,17 @@ import Init.Data.List.Attach
namespace Array
/--
`O(n)`. Partial map. If `f : Π a, P a → β` is a partial function defined on
`a : α` satisfying `P`, then `pmap f l h` is essentially the same as `map f l`
but is defined only when all members of `l` satisfy `P`, using the proof
to apply `f`.
We replace this at runtime with a more efficient version via the `csimp` lemma `pmap_eq_pmapImpl`.
-/
def pmap {P : α Prop} (f : a, P a β) (l : Array α) (H : a l, P a) : Array β :=
(l.toList.pmap f (fun a m => H a (mem_def.mpr m))).toArray
/--
Unsafe implementation of `attachWith`, taking advantage of the fact that the representation of
`Array {x // P x}` is the same as the input `Array α`.
@@ -35,6 +46,10 @@ Unsafe implementation of `attachWith`, taking advantage of the fact that the rep
l.toArray.attach = (l.attachWith (· l.toArray) (by simp)).toArray := by
simp [attach]
@[simp] theorem _root_.List.pmap_toArray {l : List α} {P : α Prop} {f : a, P a β} {H : a l.toArray, P a} :
l.toArray.pmap f H = (l.pmap f (by simpa using H)).toArray := by
simp [pmap]
@[simp] theorem toList_attachWith {l : Array α} {P : α Prop} {H : x l, P x} :
(l.attachWith P H).toList = l.toList.attachWith P (by simpa [mem_toList] using H) := by
simp [attachWith]
@@ -43,6 +58,33 @@ Unsafe implementation of `attachWith`, taking advantage of the fact that the rep
l.attach.toList = l.toList.attachWith (· l) (by simp [mem_toList]) := by
simp [attach]
@[simp] theorem toList_pmap {l : Array α} {P : α Prop} {f : a, P a β} {H : a l, P a} :
(l.pmap f H).toList = l.toList.pmap f (fun a m => H a (mem_def.mpr m)) := by
simp [pmap]
/-- Implementation of `pmap` using the zero-copy version of `attach`. -/
@[inline] private def pmapImpl {P : α Prop} (f : a, P a β) (l : Array α) (H : a l, P a) :
Array β := (l.attachWith _ H).map fun x, h' => f x h'
@[csimp] private theorem pmap_eq_pmapImpl : @pmap = @pmapImpl := by
funext α β p f L h'
cases L
simp only [pmap, pmapImpl, List.attachWith_toArray, List.map_toArray, mk.injEq, List.map_attachWith]
apply List.pmap_congr_left
intro a m h₁ h₂
congr
@[simp] theorem pmap_empty {P : α Prop} (f : a, P a β) : pmap f #[] (by simp) = #[] := rfl
@[simp] theorem pmap_push {P : α Prop} (f : a, P a β) (a : α) (l : Array α) (h : b l.push a, P b) :
pmap f (l.push a) h =
(pmap f l (fun a m => by simp at h; exact h a (.inl m))).push (f a (h a (by simp))) := by
simp [pmap]
@[simp] theorem attach_empty : (#[] : Array α).attach = #[] := rfl
@[simp] theorem attachWith_empty {P : α Prop} (H : x #[], P x) : (#[] : Array α).attachWith P H = #[] := rfl
@[simp] theorem _root_.List.attachWith_mem_toArray {l : List α} :
l.attachWith (fun x => x l.toArray) (fun x h => by simpa using h) =
l.attach.map fun x, h => x, by simpa using h := by
@@ -50,6 +92,353 @@ Unsafe implementation of `attachWith`, taking advantage of the fact that the rep
apply List.pmap_congr_left
simp
@[simp]
theorem pmap_eq_map (p : α Prop) (f : α β) (l : Array α) (H) :
@pmap _ _ p (fun a _ => f a) l H = map f l := by
cases l; simp
theorem pmap_congr_left {p q : α Prop} {f : a, p a β} {g : a, q a β} (l : Array α) {H₁ H₂}
(h : a l, (h₁ h₂), f a h₁ = g a h₂) : pmap f l H₁ = pmap g l H₂ := by
cases l
simp only [mem_toArray] at h
simp only [List.pmap_toArray, mk.injEq]
rw [List.pmap_congr_left _ h]
theorem map_pmap {p : α Prop} (g : β γ) (f : a, p a β) (l H) :
map g (pmap f l H) = pmap (fun a h => g (f a h)) l H := by
cases l
simp [List.map_pmap]
theorem pmap_map {p : β Prop} (g : b, p b γ) (f : α β) (l H) :
pmap g (map f l) H = pmap (fun a h => g (f a) h) l fun _ h => H _ (mem_map_of_mem _ h) := by
cases l
simp [List.pmap_map]
theorem attach_congr {l₁ l₂ : Array α} (h : l₁ = l₂) :
l₁.attach = l₂.attach.map (fun x => x.1, h x.2) := by
subst h
simp
theorem attachWith_congr {l₁ l₂ : Array α} (w : l₁ = l₂) {P : α Prop} {H : x l₁, P x} :
l₁.attachWith P H = l₂.attachWith P fun _ h => H _ (w h) := by
subst w
simp
@[simp] theorem attach_push {a : α} {l : Array α} :
(l.push a).attach =
(l.attach.map (fun x, h => x, mem_push_of_mem a h)).push a, by simp := by
cases l
rw [attach_congr (List.push_toArray _ _)]
simp [Function.comp_def]
@[simp] theorem attachWith_push {a : α} {l : Array α} {P : α Prop} {H : x l.push a, P x} :
(l.push a).attachWith P H =
(l.attachWith P (fun x h => by simp at H; exact H x (.inl h))).push a, H a (by simp) := by
cases l
simp [attachWith_congr (List.push_toArray _ _)]
theorem pmap_eq_map_attach {p : α Prop} (f : a, p a β) (l H) :
pmap f l H = l.attach.map fun x => f x.1 (H _ x.2) := by
cases l
simp [List.pmap_eq_map_attach]
theorem attach_map_coe (l : Array α) (f : α β) :
(l.attach.map fun (i : {i // i l}) => f i) = l.map f := by
cases l
simp [List.attach_map_coe]
theorem attach_map_val (l : Array α) (f : α β) : (l.attach.map fun i => f i.val) = l.map f :=
attach_map_coe _ _
@[simp]
theorem attach_map_subtype_val (l : Array α) : l.attach.map Subtype.val = l := by
cases l; simp
theorem attachWith_map_coe {p : α Prop} (f : α β) (l : Array α) (H : a l, p a) :
((l.attachWith p H).map fun (i : { i // p i}) => f i) = l.map f := by
cases l; simp
theorem attachWith_map_val {p : α Prop} (f : α β) (l : Array α) (H : a l, p a) :
((l.attachWith p H).map fun i => f i.val) = l.map f :=
attachWith_map_coe _ _ _
@[simp]
theorem attachWith_map_subtype_val {p : α Prop} (l : Array α) (H : a l, p a) :
(l.attachWith p H).map Subtype.val = l := by
cases l; simp
@[simp]
theorem mem_attach (l : Array α) : x, x l.attach
| a, h => by
have := mem_map.1 (by rw [attach_map_subtype_val] <;> exact h)
rcases this with _, _, m, rfl
exact m
@[simp]
theorem mem_pmap {p : α Prop} {f : a, p a β} {l H b} :
b pmap f l H (a : _) (h : a l), f a (H a h) = b := by
simp only [pmap_eq_map_attach, mem_map, mem_attach, true_and, Subtype.exists, eq_comm]
theorem mem_pmap_of_mem {p : α Prop} {f : a, p a β} {l H} {a} (h : a l) :
f a (H a h) pmap f l H := by
rw [mem_pmap]
exact a, h, rfl
@[simp]
theorem size_pmap {p : α Prop} {f : a, p a β} {l H} : (pmap f l H).size = l.size := by
cases l; simp
@[simp]
theorem size_attach {L : Array α} : L.attach.size = L.size := by
cases L; simp
@[simp]
theorem size_attachWith {p : α Prop} {l : Array α} {H} : (l.attachWith p H).size = l.size := by
cases l; simp
@[simp]
theorem pmap_eq_empty_iff {p : α Prop} {f : a, p a β} {l H} : pmap f l H = #[] l = #[] := by
cases l; simp
theorem pmap_ne_empty_iff {P : α Prop} (f : (a : α) P a β) {xs : Array α}
(H : (a : α), a xs P a) : xs.pmap f H #[] xs #[] := by
cases xs; simp
theorem pmap_eq_self {l : Array α} {p : α Prop} (hp : (a : α), a l p a)
(f : (a : α) p a α) : l.pmap f hp = l a (h : a l), f a (hp a h) = a := by
cases l; simp [List.pmap_eq_self]
@[simp]
theorem attach_eq_empty_iff {l : Array α} : l.attach = #[] l = #[] := by
cases l; simp
theorem attach_ne_empty_iff {l : Array α} : l.attach #[] l #[] := by
cases l; simp
@[simp]
theorem attachWith_eq_empty_iff {l : Array α} {P : α Prop} {H : a l, P a} :
l.attachWith P H = #[] l = #[] := by
cases l; simp
theorem attachWith_ne_empty_iff {l : Array α} {P : α Prop} {H : a l, P a} :
l.attachWith P H #[] l #[] := by
cases l; simp
@[simp]
theorem getElem?_pmap {p : α Prop} (f : a, p a β) {l : Array α} (h : a l, p a) (n : Nat) :
(pmap f l h)[n]? = Option.pmap f l[n]? fun x H => h x (mem_of_getElem? H) := by
cases l; simp
@[simp]
theorem getElem_pmap {p : α Prop} (f : a, p a β) {l : Array α} (h : a l, p a) {n : Nat}
(hn : n < (pmap f l h).size) :
(pmap f l h)[n] =
f (l[n]'(@size_pmap _ _ p f l h hn))
(h _ (getElem_mem (@size_pmap _ _ p f l h hn))) := by
cases l; simp
@[simp]
theorem getElem?_attachWith {xs : Array α} {i : Nat} {P : α Prop} {H : a xs, P a} :
(xs.attachWith P H)[i]? = xs[i]?.pmap Subtype.mk (fun _ a => H _ (mem_of_getElem? a)) :=
getElem?_pmap ..
@[simp]
theorem getElem?_attach {xs : Array α} {i : Nat} :
xs.attach[i]? = xs[i]?.pmap Subtype.mk (fun _ a => mem_of_getElem? a) :=
getElem?_attachWith
@[simp]
theorem getElem_attachWith {xs : Array α} {P : α Prop} {H : a xs, P a}
{i : Nat} (h : i < (xs.attachWith P H).size) :
(xs.attachWith P H)[i] = xs[i]'(by simpa using h), H _ (getElem_mem (by simpa using h)) :=
getElem_pmap ..
@[simp]
theorem getElem_attach {xs : Array α} {i : Nat} (h : i < xs.attach.size) :
xs.attach[i] = xs[i]'(by simpa using h), getElem_mem (by simpa using h) :=
getElem_attachWith h
theorem foldl_pmap (l : Array α) {P : α Prop} (f : (a : α) P a β)
(H : (a : α), a l P a) (g : γ β γ) (x : γ) :
(l.pmap f H).foldl g x = l.attach.foldl (fun acc a => g acc (f a.1 (H _ a.2))) x := by
rw [pmap_eq_map_attach, foldl_map]
theorem foldr_pmap (l : Array α) {P : α Prop} (f : (a : α) P a β)
(H : (a : α), a l P a) (g : β γ γ) (x : γ) :
(l.pmap f H).foldr g x = l.attach.foldr (fun a acc => g (f a.1 (H _ a.2)) acc) x := by
rw [pmap_eq_map_attach, foldr_map]
/--
If we fold over `l.attach` with a function that ignores the membership predicate,
we get the same results as folding over `l` directly.
This is useful when we need to use `attach` to show termination.
Unfortunately this can't be applied by `simp` because of the higher order unification problem,
and even when rewriting we need to specify the function explicitly.
See however `foldl_subtype` below.
-/
theorem foldl_attach (l : Array α) (f : β α β) (b : β) :
l.attach.foldl (fun acc t => f acc t.1) b = l.foldl f b := by
rcases l with l
simp only [List.attach_toArray, List.attachWith_mem_toArray, List.map_attach, size_toArray,
List.length_pmap, List.foldl_toArray', mem_toArray, List.foldl_subtype]
congr
ext
simpa using fun a => List.mem_of_getElem? a
/--
If we fold over `l.attach` with a function that ignores the membership predicate,
we get the same results as folding over `l` directly.
This is useful when we need to use `attach` to show termination.
Unfortunately this can't be applied by `simp` because of the higher order unification problem,
and even when rewriting we need to specify the function explicitly.
See however `foldr_subtype` below.
-/
theorem foldr_attach (l : Array α) (f : α β β) (b : β) :
l.attach.foldr (fun t acc => f t.1 acc) b = l.foldr f b := by
rcases l with l
simp only [List.attach_toArray, List.attachWith_mem_toArray, List.map_attach, size_toArray,
List.length_pmap, List.foldr_toArray', mem_toArray, List.foldr_subtype]
congr
ext
simpa using fun a => List.mem_of_getElem? a
theorem attach_map {l : Array α} (f : α β) :
(l.map f).attach = l.attach.map (fun x, h => f x, mem_map_of_mem f h) := by
cases l
ext <;> simp
theorem attachWith_map {l : Array α} (f : α β) {P : β Prop} {H : (b : β), b l.map f P b} :
(l.map f).attachWith P H = (l.attachWith (P f) (fun _ h => H _ (mem_map_of_mem f h))).map
fun x, h => f x, h := by
cases l
ext
· simp
· simp only [List.map_toArray, List.attachWith_toArray, List.getElem_toArray,
List.getElem_attachWith, List.getElem_map, Function.comp_apply]
erw [List.getElem_attachWith] -- Why is `erw` needed here?
theorem map_attachWith {l : Array α} {P : α Prop} {H : (a : α), a l P a}
(f : { x // P x } β) :
(l.attachWith P H).map f =
l.pmap (fun a (h : a l P a) => f a, H _ h.1) (fun a h => h, H a h) := by
cases l
ext <;> simp
/-- See also `pmap_eq_map_attach` for writing `pmap` in terms of `map` and `attach`. -/
theorem map_attach {l : Array α} (f : { x // x l } β) :
l.attach.map f = l.pmap (fun a h => f a, h) (fun _ => id) := by
cases l
ext <;> simp
theorem attach_filterMap {l : Array α} {f : α Option β} :
(l.filterMap f).attach = l.attach.filterMap
fun x, h => (f x).pbind (fun b m => some b, mem_filterMap.mpr x, h, m) := by
cases l
rw [attach_congr (List.filterMap_toArray f _)]
simp [List.attach_filterMap, List.map_filterMap, Function.comp_def]
theorem attach_filter {l : Array α} (p : α Bool) :
(l.filter p).attach = l.attach.filterMap
fun x => if w : p x.1 then some x.1, mem_filter.mpr x.2, w else none := by
cases l
rw [attach_congr (List.filter_toArray p _)]
simp [List.attach_filter, List.map_filterMap, Function.comp_def]
-- We are still missing here `attachWith_filterMap` and `attachWith_filter`.
-- Also missing are `filterMap_attach`, `filter_attach`, `filterMap_attachWith` and `filter_attachWith`.
theorem pmap_pmap {p : α Prop} {q : β Prop} (g : a, p a β) (f : b, q b γ) (l H₁ H₂) :
pmap f (pmap g l H₁) H₂ =
pmap (α := { x // x l }) (fun a h => f (g a h) (H₂ (g a h) (mem_pmap_of_mem a.2))) l.attach
(fun a _ => H₁ a a.2) := by
cases l
simp [List.pmap_pmap, List.pmap_map]
@[simp] theorem pmap_append {p : ι Prop} (f : a : ι, p a α) (l₁ l₂ : Array ι)
(h : a l₁ ++ l₂, p a) :
(l₁ ++ l₂).pmap f h =
(l₁.pmap f fun a ha => h a (mem_append_left l₂ ha)) ++
l₂.pmap f fun a ha => h a (mem_append_right l₁ ha) := by
cases l₁
cases l₂
simp
theorem pmap_append' {p : α Prop} (f : a : α, p a β) (l₁ l₂ : Array α)
(h₁ : a l₁, p a) (h₂ : a l₂, p a) :
((l₁ ++ l₂).pmap f fun a ha => (mem_append.1 ha).elim (h₁ a) (h₂ a)) =
l₁.pmap f h₁ ++ l₂.pmap f h₂ :=
pmap_append f l₁ l₂ _
@[simp] theorem attach_append (xs ys : Array α) :
(xs ++ ys).attach = xs.attach.map (fun x, h => x, mem_append_left ys h) ++
ys.attach.map fun x, h => x, mem_append_right xs h := by
cases xs
cases ys
rw [attach_congr (List.append_toArray _ _)]
simp [List.attach_append, Function.comp_def]
@[simp] theorem attachWith_append {P : α Prop} {xs ys : Array α}
{H : (a : α), a xs ++ ys P a} :
(xs ++ ys).attachWith P H = xs.attachWith P (fun a h => H a (mem_append_left ys h)) ++
ys.attachWith P (fun a h => H a (mem_append_right xs h)) := by
simp [attachWith, attach_append, map_pmap, pmap_append]
@[simp] theorem pmap_reverse {P : α Prop} (f : (a : α) P a β) (xs : Array α)
(H : (a : α), a xs.reverse P a) :
xs.reverse.pmap f H = (xs.pmap f (fun a h => H a (by simpa using h))).reverse := by
induction xs <;> simp_all
theorem reverse_pmap {P : α Prop} (f : (a : α) P a β) (xs : Array α)
(H : (a : α), a xs P a) :
(xs.pmap f H).reverse = xs.reverse.pmap f (fun a h => H a (by simpa using h)) := by
rw [pmap_reverse]
@[simp] theorem attachWith_reverse {P : α Prop} {xs : Array α}
{H : (a : α), a xs.reverse P a} :
xs.reverse.attachWith P H =
(xs.attachWith P (fun a h => H a (by simpa using h))).reverse := by
cases xs
simp
theorem reverse_attachWith {P : α Prop} {xs : Array α}
{H : (a : α), a xs P a} :
(xs.attachWith P H).reverse = (xs.reverse.attachWith P (fun a h => H a (by simpa using h))) := by
cases xs
simp
@[simp] theorem attach_reverse (xs : Array α) :
xs.reverse.attach = xs.attach.reverse.map fun x, h => x, by simpa using h := by
cases xs
rw [attach_congr (List.reverse_toArray _)]
simp
theorem reverse_attach (xs : Array α) :
xs.attach.reverse = xs.reverse.attach.map fun x, h => x, by simpa using h := by
cases xs
simp
@[simp] theorem back?_pmap {P : α Prop} (f : (a : α) P a β) (xs : Array α)
(H : (a : α), a xs P a) :
(xs.pmap f H).back? = xs.attach.back?.map fun a, m => f a (H a m) := by
cases xs
simp
@[simp] theorem back?_attachWith {P : α Prop} {xs : Array α}
{H : (a : α), a xs P a} :
(xs.attachWith P H).back? = xs.back?.pbind (fun a h => some a, H _ (mem_of_back?_eq_some h)) := by
cases xs
simp
@[simp]
theorem back?_attach {xs : Array α} :
xs.attach.back? = xs.back?.pbind fun a h => some a, mem_of_back?_eq_some h := by
cases xs
simp
/-! ## unattach
`Array.unattach` is the (one-sided) inverse of `Array.attach`. It is a synonym for `Array.map Subtype.val`.
@@ -98,6 +487,15 @@ def unattach {α : Type _} {p : α → Prop} (l : Array { x // p x }) := l.map (
cases l
simp
@[simp] theorem getElem?_unattach {p : α Prop} {l : Array { x // p x }} (i : Nat) :
l.unattach[i]? = l[i]?.map Subtype.val := by
simp [unattach]
@[simp] theorem getElem_unattach
{p : α Prop} {l : Array { x // p x }} (i : Nat) (h : i < l.unattach.size) :
l.unattach[i] = (l[i]'(by simpa using h)).1 := by
simp [unattach]
/-! ### Recognizing higher order functions using a function that only depends on the value. -/
/--

View File

@@ -0,0 +1,281 @@
/-
Copyright (c) 2024 Lean FRO, LLC. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Kim Morrison
-/
prelude
import Init.Data.List.Find
import Init.Data.Array.Lemmas
import Init.Data.Array.Attach
/-!
# Lemmas about `Array.findSome?`, `Array.find?`.
-/
namespace Array
open Nat
/-! ### findSome? -/
@[simp] theorem findSomeRev?_push_of_isSome (l : Array α) (h : (f a).isSome) : (l.push a).findSomeRev? f = f a := by
cases l; simp_all
@[simp] theorem findSomeRev?_push_of_isNone (l : Array α) (h : (f a).isNone) : (l.push a).findSomeRev? f = l.findSomeRev? f := by
cases l; simp_all
theorem exists_of_findSome?_eq_some {f : α Option β} {l : Array α} (w : l.findSome? f = some b) :
a, a l f a = b := by
cases l; simp_all [List.exists_of_findSome?_eq_some]
@[simp] theorem findSome?_eq_none_iff : findSome? p l = none x l, p x = none := by
cases l; simp
@[simp] theorem findSome?_isSome_iff {f : α Option β} {l : Array α} :
(l.findSome? f).isSome x, x l (f x).isSome := by
cases l; simp
theorem findSome?_eq_some_iff {f : α Option β} {l : Array α} {b : β} :
l.findSome? f = some b (l₁ : Array α) (a : α) (l₂ : Array α), l = l₁.push a ++ l₂ f a = some b x l₁, f x = none := by
cases l
simp only [List.findSome?_toArray, List.findSome?_eq_some_iff]
constructor
· rintro l₁, a, l₂, rfl, h₁, h₂
exact l₁.toArray, a, l₂.toArray, by simp_all
· rintro l₁, a, l₂, h₀, h₁, h₂
exact l₁.toList, a, l₂.toList, by simpa using congrArg toList h₀, h₁, by simpa
@[simp] theorem findSome?_guard (l : Array α) : findSome? (Option.guard fun x => p x) l = find? p l := by
cases l; simp
@[simp] theorem getElem?_zero_filterMap (f : α Option β) (l : Array α) : (l.filterMap f)[0]? = l.findSome? f := by
cases l; simp [ List.head?_eq_getElem?]
@[simp] theorem getElem_zero_filterMap (f : α Option β) (l : Array α) (h) :
(l.filterMap f)[0] = (l.findSome? f).get (by cases l; simpa [List.length_filterMap_eq_countP] using h) := by
cases l; simp [ List.head_eq_getElem, getElem?_zero_filterMap]
@[simp] theorem back?_filterMap (f : α Option β) (l : Array α) : (l.filterMap f).back? = l.findSomeRev? f := by
cases l; simp
@[simp] theorem back!_filterMap [Inhabited β] (f : α Option β) (l : Array α) :
(l.filterMap f).back! = (l.findSomeRev? f).getD default := by
cases l; simp
@[simp] theorem map_findSome? (f : α Option β) (g : β γ) (l : Array α) :
(l.findSome? f).map g = l.findSome? (Option.map g f) := by
cases l; simp
theorem findSome?_map (f : β γ) (l : Array β) : findSome? p (l.map f) = l.findSome? (p f) := by
cases l; simp [List.findSome?_map]
theorem findSome?_append {l₁ l₂ : Array α} : (l₁ ++ l₂).findSome? f = (l₁.findSome? f).or (l₂.findSome? f) := by
cases l₁; cases l₂; simp [List.findSome?_append]
theorem getElem?_zero_flatten (L : Array (Array α)) :
(flatten L)[0]? = L.findSome? fun l => l[0]? := by
cases L using array_array_induction
simp [ List.head?_eq_getElem?, List.head?_flatten, List.findSome?_map, Function.comp_def]
theorem getElem_zero_flatten.proof {L : Array (Array α)} (h : 0 < L.flatten.size) :
(L.findSome? fun l => l[0]?).isSome := by
cases L using array_array_induction
simp only [List.findSome?_toArray, List.findSome?_map, Function.comp_def, List.getElem?_toArray,
List.findSome?_isSome_iff, List.isSome_getElem?]
simp only [flatten_toArray_map_toArray, size_toArray, List.length_flatten,
Nat.sum_pos_iff_exists_pos, List.mem_map] at h
obtain _, xs, m, rfl, h := h
exact xs, m, by simpa using h
theorem getElem_zero_flatten {L : Array (Array α)} (h) :
(flatten L)[0] = (L.findSome? fun l => l[0]?).get (getElem_zero_flatten.proof h) := by
have t := getElem?_zero_flatten L
simp [getElem?_eq_getElem, h] at t
simp [ t]
theorem back?_flatten {L : Array (Array α)} :
(flatten L).back? = (L.findSomeRev? fun l => l.back?) := by
cases L using array_array_induction
simp [List.getLast?_flatten, List.map_reverse, List.findSome?_map, Function.comp_def]
theorem findSome?_mkArray : findSome? f (mkArray n a) = if n = 0 then none else f a := by
simp [mkArray_eq_toArray_replicate, List.findSome?_replicate]
@[simp] theorem findSome?_mkArray_of_pos (h : 0 < n) : findSome? f (mkArray n a) = f a := by
simp [findSome?_mkArray, Nat.ne_of_gt h]
-- Argument is unused, but used to decide whether `simp` should unfold.
@[simp] theorem findSome?_mkArray_of_isSome (_ : (f a).isSome) :
findSome? f (mkArray n a) = if n = 0 then none else f a := by
simp [findSome?_mkArray]
@[simp] theorem findSome?_mkArray_of_isNone (h : (f a).isNone) :
findSome? f (mkArray n a) = none := by
rw [Option.isNone_iff_eq_none] at h
simp [findSome?_mkArray, h]
/-! ### find? -/
@[simp] theorem find?_singleton (a : α) (p : α Bool) :
#[a].find? p = if p a then some a else none := by
simp [singleton_eq_toArray_singleton]
@[simp] theorem findRev?_push_of_pos (l : Array α) (h : p a) :
findRev? p (l.push a) = some a := by
cases l; simp [h]
@[simp] theorem findRev?_cons_of_neg (l : Array α) (h : ¬p a) :
findRev? p (l.push a) = findRev? p l := by
cases l; simp [h]
@[simp] theorem find?_eq_none : find? p l = none x l, ¬ p x := by
cases l; simp
theorem find?_eq_some_iff_append {xs : Array α} :
xs.find? p = some b p b (as bs : Array α), xs = as.push b ++ bs a as, !p a := by
rcases xs with xs
simp only [List.find?_toArray, List.find?_eq_some_iff_append, Bool.not_eq_eq_eq_not,
Bool.not_true, exists_and_right, and_congr_right_iff]
intro w
constructor
· rintro as, x, rfl, h
exact as.toArray, x.toArray, by simp , by simpa using h
· rintro as, x, h', h
exact as.toList, x.toList, by simpa using congrArg Array.toList h',
by simpa using h
@[simp]
theorem find?_push_eq_some {xs : Array α} :
(xs.push a).find? p = some b xs.find? p = some b (xs.find? p = none (p a a = b)) := by
cases xs; simp
@[simp] theorem find?_isSome {xs : Array α} {p : α Bool} : (xs.find? p).isSome x, x xs p x := by
cases xs; simp
theorem find?_some {xs : Array α} (h : find? p xs = some a) : p a := by
cases xs
simp at h
exact List.find?_some h
theorem mem_of_find?_eq_some {xs : Array α} (h : find? p xs = some a) : a xs := by
cases xs
simp at h
simpa using List.mem_of_find?_eq_some h
theorem get_find?_mem {xs : Array α} (h) : (xs.find? p).get h xs := by
cases xs
simp [List.get_find?_mem]
@[simp] theorem find?_filter {xs : Array α} (p q : α Bool) :
(xs.filter p).find? q = xs.find? (fun a => p a q a) := by
cases xs; simp
@[simp] theorem getElem?_zero_filter (p : α Bool) (l : Array α) :
(l.filter p)[0]? = l.find? p := by
cases l; simp [ List.head?_eq_getElem?]
@[simp] theorem getElem_zero_filter (p : α Bool) (l : Array α) (h) :
(l.filter p)[0] =
(l.find? p).get (by cases l; simpa [ List.countP_eq_length_filter] using h) := by
cases l
simp [List.getElem_zero_eq_head]
@[simp] theorem back?_filter (p : α Bool) (l : Array α) : (l.filter p).back? = l.findRev? p := by
cases l; simp
@[simp] theorem back!_filter [Inhabited α] (p : α Bool) (l : Array α) :
(l.filter p).back! = (l.findRev? p).get! := by
cases l; simp [Option.get!_eq_getD]
@[simp] theorem find?_filterMap (xs : Array α) (f : α Option β) (p : β Bool) :
(xs.filterMap f).find? p = (xs.find? (fun a => (f a).any p)).bind f := by
cases xs; simp
@[simp] theorem find?_map (f : β α) (xs : Array β) :
find? p (xs.map f) = (xs.find? (p f)).map f := by
cases xs; simp
@[simp] theorem find?_append {l₁ l₂ : Array α} :
(l₁ ++ l₂).find? p = (l₁.find? p).or (l₂.find? p) := by
cases l₁
cases l₂
simp
@[simp] theorem find?_flatten (xs : Array (Array α)) (p : α Bool) :
xs.flatten.find? p = xs.findSome? (·.find? p) := by
cases xs using array_array_induction
simp [List.findSome?_map, Function.comp_def]
theorem find?_flatten_eq_none {xs : Array (Array α)} {p : α Bool} :
xs.flatten.find? p = none ys xs, x ys, !p x := by
simp
/--
If `find? p` returns `some a` from `xs.flatten`, then `p a` holds, and
some array in `xs` contains `a`, and no earlier element of that array satisfies `p`.
Moreover, no earlier array in `xs` has an element satisfying `p`.
-/
theorem find?_flatten_eq_some {xs : Array (Array α)} {p : α Bool} {a : α} :
xs.flatten.find? p = some a
p a (as : Array (Array α)) (ys zs : Array α) (bs : Array (Array α)),
xs = as.push (ys.push a ++ zs) ++ bs
( a as, x a, !p x) ( x ys, !p x) := by
cases xs using array_array_induction
simp only [flatten_toArray_map_toArray, List.find?_toArray, List.find?_flatten_eq_some]
simp only [Bool.not_eq_eq_eq_not, Bool.not_true, exists_and_right, and_congr_right_iff]
intro w
constructor
· rintro as, ys, zs, bs, rfl, h₁, h₂
exact as.toArray.map List.toArray, ys.toArray,
zs.toArray, bs.toArray.map List.toArray, by simp, by simpa using h₁, by simpa using h₂
· rintro as, ys, zs, bs, h, h₁, h₂
replace h := congrArg (·.map Array.toList) (congrArg Array.toList h)
simp [Function.comp_def] at h
exact as.toList.map Array.toList, ys.toList,
zs.toList, bs.toList.map Array.toList, by simpa using h,
by simpa using h₁, by simpa using h₂
@[simp] theorem find?_flatMap (xs : Array α) (f : α Array β) (p : β Bool) :
(xs.flatMap f).find? p = xs.findSome? (fun x => (f x).find? p) := by
cases xs
simp [List.find?_flatMap, Array.flatMap_toArray]
theorem find?_flatMap_eq_none {xs : Array α} {f : α Array β} {p : β Bool} :
(xs.flatMap f).find? p = none x xs, y f x, !p y := by
simp
theorem find?_mkArray :
find? p (mkArray n a) = if n = 0 then none else if p a then some a else none := by
simp [mkArray_eq_toArray_replicate, List.find?_replicate]
@[simp] theorem find?_mkArray_of_length_pos (h : 0 < n) :
find? p (mkArray n a) = if p a then some a else none := by
simp [find?_mkArray, Nat.ne_of_gt h]
@[simp] theorem find?_mkArray_of_pos (h : p a) :
find? p (mkArray n a) = if n = 0 then none else some a := by
simp [find?_mkArray, h]
@[simp] theorem find?_mkArray_of_neg (h : ¬ p a) : find? p (mkArray n a) = none := by
simp [find?_mkArray, h]
-- This isn't a `@[simp]` lemma since there is already a lemma for `l.find? p = none` for any `l`.
theorem find?_mkArray_eq_none {n : Nat} {a : α} {p : α Bool} :
(mkArray n a).find? p = none n = 0 !p a := by
simp [mkArray_eq_toArray_replicate, List.find?_replicate_eq_none, Classical.or_iff_not_imp_left]
@[simp] theorem find?_mkArray_eq_some {n : Nat} {a b : α} {p : α Bool} :
(mkArray n a).find? p = some b n 0 p a a = b := by
simp [mkArray_eq_toArray_replicate]
@[simp] theorem get_find?_mkArray (n : Nat) (a : α) (p : α Bool) (h) :
((mkArray n a).find? p).get h = a := by
simp [mkArray_eq_toArray_replicate]
theorem find?_pmap {P : α Prop} (f : (a : α) P a β) (xs : Array α)
(H : (a : α), a xs P a) (p : β Bool) :
(xs.pmap f H).find? p = (xs.attach.find? (fun a, m => p (f a (H a m)))).map fun a, m => f a (H a m) := by
simp only [pmap_eq_map_attach, find?_map]
rfl
end Array

View File

@@ -23,6 +23,9 @@ import Init.TacticsExtra
namespace Array
@[simp] theorem mem_toArray {a : α} {l : List α} : a l.toArray a l := by
simp [mem_def]
@[simp] theorem getElem_mk {xs : List α} {i : Nat} (h : i < xs.length) : (Array.mk xs)[i] = xs[i] := rfl
theorem getElem_eq_getElem_toList {a : Array α} (h : i < a.size) : a[i] = a.toList[i] := rfl
@@ -36,12 +39,21 @@ theorem getElem?_eq_getElem {a : Array α} {i : Nat} (h : i < a.size) : a[i]? =
· rw [getElem?_neg a i h]
simp_all
@[simp] theorem none_eq_getElem?_iff {a : Array α} {i : Nat} : none = a[i]? a.size i := by
simp [eq_comm (a := none)]
theorem getElem?_eq {a : Array α} {i : Nat} :
a[i]? = if h : i < a.size then some a[i] else none := by
split
· simp_all [getElem?_eq_getElem]
· simp_all
theorem getElem?_eq_some_iff {a : Array α} : a[i]? = some b h : i < a.size, a[i] = b := by
simp [getElem?_eq]
theorem some_eq_getElem?_iff {a : Array α} : some b = a[i]? h : i < a.size, a[i] = b := by
rw [eq_comm, getElem?_eq_some_iff]
theorem getElem?_eq_getElem?_toList (a : Array α) (i : Nat) : a[i]? = a.toList[i]? := by
rw [getElem?_eq]
split <;> simp_all
@@ -66,6 +78,35 @@ theorem getElem_push (a : Array α) (x : α) (i : Nat) (h : i < (a.push x).size)
@[deprecated getElem_push_lt (since := "2024-10-21")] abbrev get_push_lt := @getElem_push_lt
@[deprecated getElem_push_eq (since := "2024-10-21")] abbrev get_push_eq := @getElem_push_eq
@[simp] theorem mem_push {a : Array α} {x y : α} : x a.push y x a x = y := by
simp [mem_def]
theorem mem_push_self {a : Array α} {x : α} : x a.push x :=
mem_push.2 (Or.inr rfl)
theorem mem_push_of_mem {a : Array α} {x : α} (y : α) (h : x a) : x a.push y :=
mem_push.2 (Or.inl h)
theorem getElem_of_mem {a} {l : Array α} (h : a l) : (n : Nat) (h : n < l.size), l[n]'h = a := by
cases l
simp [List.getElem_of_mem (by simpa using h)]
theorem getElem?_of_mem {a} {l : Array α} (h : a l) : n : Nat, l[n]? = some a :=
let n, _, e := getElem_of_mem h; n, e getElem?_eq_getElem _
theorem mem_of_getElem? {l : Array α} {n : Nat} {a : α} (e : l[n]? = some a) : a l :=
let _, e := getElem?_eq_some_iff.1 e; e getElem_mem ..
theorem mem_iff_getElem {a} {l : Array α} : a l (n : Nat) (h : n < l.size), l[n]'h = a :=
getElem_of_mem, fun _, _, e => e getElem_mem ..
theorem mem_iff_getElem? {a} {l : Array α} : a l n : Nat, l[n]? = some a := by
simp [getElem?_eq_some_iff, mem_iff_getElem]
theorem forall_getElem {l : Array α} {p : α Prop} :
( (n : Nat) h, p (l[n]'h)) a, a l p a := by
cases l; simp [List.forall_getElem]
@[simp] theorem get!_eq_getElem! [Inhabited α] (a : Array α) (i : Nat) : a.get! i = a[i]! := by
simp [getElem!_def, get!, getD]
split <;> rename_i h
@@ -93,9 +134,6 @@ We prefer to pull `List.toArray` outwards.
(a.toArrayAux b).size = b.size + a.length := by
simp [size]
@[simp] theorem mem_toArray {a : α} {l : List α} : a l.toArray a l := by
simp [mem_def]
@[simp] theorem push_toArray (l : List α) (a : α) : l.toArray.push a = (l ++ [a]).toArray := by
apply ext'
simp
@@ -601,38 +639,25 @@ theorem getElem?_mkArray (n : Nat) (v : α) (i : Nat) :
/-- # mem -/
theorem mem_toList {a : α} {l : Array α} : a l.toList a l := mem_def.symm
@[simp] theorem mem_toList {a : α} {l : Array α} : a l.toList a l := mem_def.symm
theorem not_mem_nil (a : α) : ¬ a #[] := nofun
theorem getElem_of_mem {a : α} {as : Array α} :
a as ( (n : Nat) (h : n < as.size), as[n]'h = a) := by
intro ha
rcases List.getElem_of_mem ha.val with i, hbound, hi
exists i
exists hbound
theorem getElem?_of_mem {a : α} {as : Array α} :
a as (n : Nat), as[n]? = some a := by
intro ha
rcases List.getElem?_of_mem ha.val with i, hi
exists i
@[simp] theorem mem_dite_empty_left {x : α} [Decidable p] {l : ¬ p Array α} :
(x if h : p then #[] else l h) h : ¬ p, x l h := by
split <;> simp_all [mem_def]
split <;> simp_all
@[simp] theorem mem_dite_empty_right {x : α} [Decidable p] {l : p Array α} :
(x if h : p then l h else #[]) h : p, x l h := by
split <;> simp_all [mem_def]
split <;> simp_all
@[simp] theorem mem_ite_empty_left {x : α} [Decidable p] {l : Array α} :
(x if p then #[] else l) ¬ p x l := by
split <;> simp_all [mem_def]
split <;> simp_all
@[simp] theorem mem_ite_empty_right {x : α} [Decidable p] {l : Array α} :
(x if p then l else #[]) p x l := by
split <;> simp_all [mem_def]
split <;> simp_all
/-- # get lemmas -/
@@ -659,10 +684,6 @@ theorem get?_eq_get?_toList (a : Array α) (i : Nat) : a.get? i = a.toList.get?
theorem get!_eq_get? [Inhabited α] (a : Array α) : a.get! n = (a.get? n).getD default := by
simp only [get!_eq_getElem?, get?_eq_getElem?]
theorem getElem?_eq_some_iff {as : Array α} : as[n]? = some a h : n < as.size, as[n] = a := by
cases as
simp [List.getElem?_eq_some_iff]
theorem back!_eq_back? [Inhabited α] (a : Array α) : a.back! = a.back?.getD default := by
simp only [back!, get!_eq_getElem?, get?_eq_getElem?, back?]
@@ -672,6 +693,10 @@ theorem back!_eq_back? [Inhabited α] (a : Array α) : a.back! = a.back?.getD de
@[simp] theorem back!_push [Inhabited α] (a : Array α) : (a.push x).back! = x := by
simp [back!_eq_back?]
theorem mem_of_back?_eq_some {xs : Array α} {a : α} (h : xs.back? = some a) : a xs := by
cases xs
simpa using List.mem_of_getLast?_eq_some (by simpa using h)
theorem getElem?_push_lt (a : Array α) (x : α) (i : Nat) (h : i < a.size) :
(a.push x)[i]? = some a[i] := by
rw [getElem?_pos, getElem_push_lt]
@@ -1025,6 +1050,10 @@ theorem foldr_congr {as bs : Array α} (h₀ : as = bs) {f g : α → β → β}
@[simp] theorem mem_map {f : α β} {l : Array α} : b l.map f a, a l f a = b := by
simp only [mem_def, toList_map, List.mem_map]
theorem exists_of_mem_map (h : b map f l) : a, a l f a = b := mem_map.1 h
theorem mem_map_of_mem (f : α β) (h : a l) : f a map f l := mem_map.2 _, h, rfl
theorem mapM_eq_mapM_toList [Monad m] [LawfulMonad m] (f : α m β) (arr : Array α) :
arr.mapM f = List.toArray <$> (arr.toList.mapM f) := by
rw [mapM_eq_foldlM, foldlM_toList, List.foldrM_reverse]
@@ -1215,9 +1244,23 @@ theorem push_eq_append_singleton (as : Array α) (x) : as.push x = as ++ #[x] :=
@[simp] theorem mem_append {a : α} {s t : Array α} : a s ++ t a s a t := by
simp only [mem_def, toList_append, List.mem_append]
theorem mem_append_left {a : α} {l₁ : Array α} (l₂ : Array α) (h : a l₁) : a l₁ ++ l₂ :=
mem_append.2 (Or.inl h)
theorem mem_append_right {a : α} (l₁ : Array α) {l₂ : Array α} (h : a l₂) : a l₁ ++ l₂ :=
mem_append.2 (Or.inr h)
@[simp] theorem size_append (as bs : Array α) : (as ++ bs).size = as.size + bs.size := by
simp only [size, toList_append, List.length_append]
@[simp] theorem empty_append (as : Array α) : #[] ++ as = as := by
cases as
simp
@[simp] theorem append_empty (as : Array α) : as ++ #[] = as := by
cases as
simp
theorem getElem_append {as bs : Array α} (h : i < (as ++ bs).size) :
(as ++ bs)[i] = if h' : i < as.size then as[i] else bs[i - as.size]'(by simp at h; omega) := by
cases as; cases bs
@@ -1876,6 +1919,76 @@ namespace Array
induction as
simp
/-! ### map -/
@[simp] theorem map_map {f : α β} {g : β γ} {as : Array α} :
(as.map f).map g = as.map (g f) := by
cases as; simp
@[simp] theorem map_id_fun : map (id : α α) = id := by
funext l
induction l <;> simp_all
/-- `map_id_fun'` differs from `map_id_fun` by representing the identity function as a lambda, rather than `id`. -/
@[simp] theorem map_id_fun' : map (fun (a : α) => a) = id := map_id_fun
-- This is not a `@[simp]` lemma because `map_id_fun` will apply.
theorem map_id (as : Array α) : map (id : α α) as = as := by
cases as <;> simp_all
/-- `map_id'` differs from `map_id` by representing the identity function as a lambda, rather than `id`. -/
-- This is not a `@[simp]` lemma because `map_id_fun'` will apply.
theorem map_id' (as : Array α) : map (fun (a : α) => a) as = as := map_id as
/-- Variant of `map_id`, with a side condition that the function is pointwise the identity. -/
theorem map_id'' {f : α α} (h : x, f x = x) (as : Array α) : map f as = as := by
simp [show f = id from funext h]
theorem array_array_induction (P : Array (Array α) Prop) (h : (xss : List (List α)), P (xss.map List.toArray).toArray)
(ass : Array (Array α)) : P ass := by
specialize h (ass.toList.map toList)
simpa [ toList_map, Function.comp_def, map_id] using h
theorem foldl_map (f : β₁ β₂) (g : α β₂ α) (l : Array β₁) (init : α) :
(l.map f).foldl g init = l.foldl (fun x y => g x (f y)) init := by
cases l; simp [List.foldl_map]
theorem foldr_map (f : α₁ α₂) (g : α₂ β β) (l : Array α₁) (init : β) :
(l.map f).foldr g init = l.foldr (fun x y => g (f x) y) init := by
cases l; simp [List.foldr_map]
theorem foldl_filterMap (f : α Option β) (g : γ β γ) (l : Array α) (init : γ) :
(l.filterMap f).foldl g init = l.foldl (fun x y => match f y with | some b => g x b | none => x) init := by
cases l
simp [List.foldl_filterMap]
rfl
theorem foldr_filterMap (f : α Option β) (g : β γ γ) (l : Array α) (init : γ) :
(l.filterMap f).foldr g init = l.foldr (fun x y => match f x with | some b => g b y | none => y) init := by
cases l
simp [List.foldr_filterMap]
rfl
/-! ### flatten -/
@[simp] theorem flatten_empty : flatten (#[] : Array (Array α)) = #[] := rfl
@[simp] theorem flatten_toArray_map_toArray (xss : List (List α)) :
(xss.map List.toArray).toArray.flatten = xss.flatten.toArray := by
simp [flatten]
suffices as, List.foldl (fun r a => r ++ a) as (List.map List.toArray xss) = as ++ xss.flatten.toArray by
simpa using this #[]
intro as
induction xss generalizing as with
| nil => simp
| cons xs xss ih => simp [ih]
/-! ### reverse -/
@[simp] theorem mem_reverse {x : α} {as : Array α} : x as.reverse x as := by
cases as
simp
/-! ### findSomeRevM?, findRevM?, findSomeRev?, findRev? -/
@[simp] theorem findSomeRevM?_eq_findSomeM?_reverse
@@ -1940,6 +2053,27 @@ namespace Array
cases as
simp
@[simp] theorem flatMap_empty {β} (f : α Array β) : (#[] : Array α).flatMap f = #[] := rfl
@[simp] theorem flatMap_toArray_cons {β} (f : α Array β) (a : α) (as : List α) :
(a :: as).toArray.flatMap f = f a ++ as.toArray.flatMap f := by
simp [flatMap]
suffices cs, List.foldl (fun bs a => bs ++ f a) (f a ++ cs) as =
f a ++ List.foldl (fun bs a => bs ++ f a) cs as by
erw [empty_append] -- Why doesn't this work via `simp`?
simpa using this #[]
intro cs
induction as generalizing cs <;> simp_all
@[simp] theorem flatMap_toArray {β} (f : α Array β) (as : List α) :
as.toArray.flatMap f = (as.flatMap (fun a => (f a).toList)).toArray := by
induction as with
| nil => simp
| cons a as ih =>
apply ext'
simp [ih]
end Array
/-! ### Deprecations -/

View File

@@ -403,7 +403,7 @@ theorem getLsbD_neg {i : Nat} {x : BitVec w} :
rw [carry_succ_one _ _ (by omega), Bool.xor_not, decide_not]
simp only [add_one_ne_zero, decide_false, getLsbD_not, and_eq_true, decide_eq_true_eq,
not_eq_eq_eq_not, Bool.not_true, false_bne, not_exists, _root_.not_and, not_eq_true,
bne_left_inj, decide_eq_decide]
bne_right_inj, decide_eq_decide]
constructor
· rintro h j hj; exact And.right <| h j (by omega)
· rintro h j hj; exact by omega, h j (by omega)
@@ -419,7 +419,7 @@ theorem getMsbD_neg {i : Nat} {x : BitVec w} :
simp [hi]; omega
case pos =>
have h₁ : w - 1 - i < w := by omega
simp only [hi, decide_true, h₁, Bool.true_and, Bool.bne_left_inj, decide_eq_decide]
simp only [hi, decide_true, h₁, Bool.true_and, Bool.bne_right_inj, decide_eq_decide]
constructor
· rintro j, hj, h
refine w - 1 - j, by omega, by omega, by omega, _root_.cast ?_ h

View File

@@ -238,8 +238,8 @@ theorem not_bne_not : ∀ (x y : Bool), ((!x) != (!y)) = (x != y) := by simp
@[simp] theorem bne_assoc : (x y z : Bool), ((x != y) != z) = (x != (y != z)) := by decide
instance : Std.Associative (· != ·) := bne_assoc
@[simp] theorem bne_left_inj : {x y z : Bool}, (x != y) = (x != z) y = z := by decide
@[simp] theorem bne_right_inj : {x y z : Bool}, (x != z) = (y != z) x = y := by decide
@[simp] theorem bne_right_inj : {x y z : Bool}, (x != y) = (x != z) y = z := by decide
@[simp] theorem bne_left_inj : {x y z : Bool}, (x != z) = (y != z) x = y := by decide
theorem eq_not_of_ne : {x y : Bool}, x y x = !y := by decide
@@ -295,9 +295,9 @@ theorem xor_right_comm : ∀ (x y z : Bool), ((x ^^ y) ^^ z) = ((x ^^ z) ^^ y) :
theorem xor_assoc : (x y z : Bool), ((x ^^ y) ^^ z) = (x ^^ (y ^^ z)) := bne_assoc
theorem xor_left_inj : {x y z : Bool}, (x ^^ y) = (x ^^ z) y = z := bne_left_inj
theorem xor_right_inj : {x y z : Bool}, (x ^^ y) = (x ^^ z) y = z := bne_right_inj
theorem xor_right_inj : {x y z : Bool}, (x ^^ z) = (y ^^ z) x = y := bne_right_inj
theorem xor_left_inj : {x y z : Bool}, (x ^^ z) = (y ^^ z) x = y := bne_left_inj
/-! ### le/lt -/

View File

@@ -47,6 +47,25 @@ def Float.lt : Float → Float → Prop := fun a b =>
def Float.le : Float Float Prop := fun a b =>
floatSpec.le a.val b.val
/--
Raw transmutation from `UInt64`.
Floats and UInts have the same endianness on all supported platforms.
IEEE 754 very precisely specifies the bit layout of floats.
-/
@[extern "lean_float_of_bits"] opaque Float.ofBits : UInt64 Float
/--
Raw transmutation to `UInt64`.
Floats and UInts have the same endianness on all supported platforms.
IEEE 754 very precisely specifies the bit layout of floats.
Note that this function is distinct from `Float.toUInt64`, which attempts
to preserve the numeric value, and not the bitwise value.
-/
@[extern "lean_float_to_bits"] opaque Float.toBits : Float UInt64
instance : Add Float := Float.add
instance : Sub Float := Float.sub
instance : Mul Float := Float.mul

View File

@@ -329,22 +329,22 @@ theorem toNat_sub (m n : Nat) : toNat (m - n) = m - n := by
/- ## add/sub injectivity -/
@[simp]
protected theorem add_right_inj {i j : Int} (k : Int) : (i + k = j + k) i = j := by
protected theorem add_left_inj {i j : Int} (k : Int) : (i + k = j + k) i = j := by
apply Iff.intro
· intro p
rw [Int.add_sub_cancel i k, Int.add_sub_cancel j k, p]
· exact congrArg (· + k)
@[simp]
protected theorem add_left_inj {i j : Int} (k : Int) : (k + i = k + j) i = j := by
protected theorem add_right_inj {i j : Int} (k : Int) : (k + i = k + j) i = j := by
simp [Int.add_comm k]
@[simp]
protected theorem sub_left_inj {i j : Int} (k : Int) : (k - i = k - j) i = j := by
protected theorem sub_right_inj {i j : Int} (k : Int) : (k - i = k - j) i = j := by
simp [Int.sub_eq_add_neg, Int.neg_inj]
@[simp]
protected theorem sub_right_inj {i j : Int} (k : Int) : (i - k = j - k) i = j := by
protected theorem sub_left_inj {i j : Int} (k : Int) : (i - k = j - k) i = j := by
simp [Int.sub_eq_add_neg]
/- ## Ring properties -/

View File

@@ -13,7 +13,7 @@ namespace List
`a : α` satisfying `P`, then `pmap f l h` is essentially the same as `map f l`
but is defined only when all members of `l` satisfy `P`, using the proof
to apply `f`. -/
@[simp] def pmap {P : α Prop} (f : a, P a β) : l : List α, (H : a l, P a) List β
def pmap {P : α Prop} (f : a, P a β) : l : List α, (H : a l, P a) List β
| [], _ => []
| a :: l, H => f a (forall_mem_cons.1 H).1 :: pmap f l (forall_mem_cons.1 H).2
@@ -46,6 +46,11 @@ Unsafe implementation of `attachWith`, taking advantage of the fact that the rep
| cons _ L', hL' => congrArg _ <| go L' fun _ hx => hL' (.tail _ hx)
exact go L h'
@[simp] theorem pmap_nil {P : α Prop} (f : a, P a β) : pmap f [] (by simp) = [] := rfl
@[simp] theorem pmap_cons {P : α Prop} (f : a, P a β) (a : α) (l : List α) (h : b a :: l, P b) :
pmap f (a :: l) h = f a (forall_mem_cons.1 h).1 :: pmap f l (forall_mem_cons.1 h).2 := rfl
@[simp] theorem attach_nil : ([] : List α).attach = [] := rfl
@[simp] theorem attachWith_nil : ([] : List α).attachWith P H = [] := rfl
@@ -148,7 +153,7 @@ theorem mem_pmap_of_mem {p : α → Prop} {f : ∀ a, p a → β} {l H} {a} (h :
exact a, h, rfl
@[simp]
theorem length_pmap {p : α Prop} {f : a, p a β} {l H} : length (pmap f l H) = length l := by
theorem length_pmap {p : α Prop} {f : a, p a β} {l H} : (pmap f l H).length = l.length := by
induction l
· rfl
· simp only [*, pmap, length]
@@ -199,7 +204,7 @@ theorem attachWith_ne_nil_iff {l : List α} {P : α → Prop} {H : ∀ a ∈ l,
@[simp]
theorem getElem?_pmap {p : α Prop} (f : a, p a β) {l : List α} (h : a l, p a) (n : Nat) :
(pmap f l h)[n]? = Option.pmap f l[n]? fun x H => h x (getElem?_mem H) := by
(pmap f l h)[n]? = Option.pmap f l[n]? fun x H => h x (mem_of_getElem? H) := by
induction l generalizing n with
| nil => simp
| cons hd tl hl =>
@@ -215,7 +220,7 @@ theorem getElem?_pmap {p : α → Prop} (f : ∀ a, p a → β) {l : List α} (h
· simp_all
theorem get?_pmap {p : α Prop} (f : a, p a β) {l : List α} (h : a l, p a) (n : Nat) :
get? (pmap f l h) n = Option.pmap f (get? l n) fun x H => h x (get?_mem H) := by
get? (pmap f l h) n = Option.pmap f (get? l n) fun x H => h x (mem_of_get? H) := by
simp only [get?_eq_getElem?]
simp [getElem?_pmap, h]
@@ -238,18 +243,18 @@ theorem get_pmap {p : α → Prop} (f : ∀ a, p a → β) {l : List α} (h :
(hn : n < (pmap f l h).length) :
get (pmap f l h) n, hn =
f (get l n, @length_pmap _ _ p f l h hn)
(h _ (get_mem l n (@length_pmap _ _ p f l h hn))) := by
(h _ (getElem_mem (@length_pmap _ _ p f l h hn))) := by
simp only [get_eq_getElem]
simp [getElem_pmap]
@[simp]
theorem getElem?_attachWith {xs : List α} {i : Nat} {P : α Prop} {H : a xs, P a} :
(xs.attachWith P H)[i]? = xs[i]?.pmap Subtype.mk (fun _ a => H _ (getElem?_mem a)) :=
(xs.attachWith P H)[i]? = xs[i]?.pmap Subtype.mk (fun _ a => H _ (mem_of_getElem? a)) :=
getElem?_pmap ..
@[simp]
theorem getElem?_attach {xs : List α} {i : Nat} :
xs.attach[i]? = xs[i]?.pmap Subtype.mk (fun _ a => getElem?_mem a) :=
xs.attach[i]? = xs[i]?.pmap Subtype.mk (fun _ a => mem_of_getElem? a) :=
getElem?_attachWith
@[simp]
@@ -333,6 +338,7 @@ This is useful when we need to use `attach` to show termination.
Unfortunately this can't be applied by `simp` because of the higher order unification problem,
and even when rewriting we need to specify the function explicitly.
See however `foldl_subtype` below.
-/
theorem foldl_attach (l : List α) (f : β α β) (b : β) :
l.attach.foldl (fun acc t => f acc t.1) b = l.foldl f b := by
@@ -348,6 +354,7 @@ This is useful when we need to use `attach` to show termination.
Unfortunately this can't be applied by `simp` because of the higher order unification problem,
and even when rewriting we need to specify the function explicitly.
See however `foldr_subtype` below.
-/
theorem foldr_attach (l : List α) (f : α β β) (b : β) :
l.attach.foldr (fun t acc => f t.1 acc) b = l.foldr f b := by
@@ -452,16 +459,16 @@ theorem pmap_append' {p : α → Prop} (f : ∀ a : α, p a → β) (l₁ l₂ :
pmap_append f l₁ l₂ _
@[simp] theorem attach_append (xs ys : List α) :
(xs ++ ys).attach = xs.attach.map (fun x, h => x, mem_append_of_mem_left ys h) ++
ys.attach.map fun x, h => x, mem_append_of_mem_right xs h := by
(xs ++ ys).attach = xs.attach.map (fun x, h => x, mem_append_left ys h) ++
ys.attach.map fun x, h => x, mem_append_right xs h := by
simp only [attach, attachWith, pmap, map_pmap, pmap_append]
congr 1 <;>
exact pmap_congr_left _ fun _ _ _ _ => rfl
@[simp] theorem attachWith_append {P : α Prop} {xs ys : List α}
{H : (a : α), a xs ++ ys P a} :
(xs ++ ys).attachWith P H = xs.attachWith P (fun a h => H a (mem_append_of_mem_left ys h)) ++
ys.attachWith P (fun a h => H a (mem_append_of_mem_right xs h)) := by
(xs ++ ys).attachWith P H = xs.attachWith P (fun a h => H a (mem_append_left ys h)) ++
ys.attachWith P (fun a h => H a (mem_append_right xs h)) := by
simp only [attachWith, attach_append, map_pmap, pmap_append]
@[simp] theorem pmap_reverse {P : α Prop} (f : (a : α) P a β) (xs : List α)
@@ -598,6 +605,15 @@ def unattach {α : Type _} {p : α → Prop} (l : List { x // p x }) := l.map (
| nil => simp
| cons a l ih => simp [ih, Function.comp_def]
@[simp] theorem getElem?_unattach {p : α Prop} {l : List { x // p x }} (i : Nat) :
l.unattach[i]? = l[i]?.map Subtype.val := by
simp [unattach]
@[simp] theorem getElem_unattach
{p : α Prop} {l : List { x // p x }} (i : Nat) (h : i < l.unattach.length) :
l.unattach[i] = (l[i]'(by simpa using h)).1 := by
simp [unattach]
/-! ### Recognizing higher order functions on subtypes using a function that only depends on the value. -/
/--

View File

@@ -551,7 +551,7 @@ theorem reverseAux_eq_append (as bs : List α) : reverseAux as bs = reverseAux a
/-! ### flatten -/
/--
`O(|flatten L|)`. `join L` concatenates all the lists in `L` into one list.
`O(|flatten L|)`. `flatten L` concatenates all the lists in `L` into one list.
* `flatten [[a], [], [b, c], [d, e, f]] = [a, b, c, d, e, f]`
-/
def flatten : List (List α) List α
@@ -726,13 +726,13 @@ theorem elem_eq_true_of_mem [BEq α] [LawfulBEq α] {a : α} {as : List α} (h :
instance [BEq α] [LawfulBEq α] (a : α) (as : List α) : Decidable (a as) :=
decidable_of_decidable_of_iff (Iff.intro mem_of_elem_eq_true elem_eq_true_of_mem)
theorem mem_append_of_mem_left {a : α} {as : List α} (bs : List α) : a as a as ++ bs := by
theorem mem_append_left {a : α} {as : List α} (bs : List α) : a as a as ++ bs := by
intro h
induction h with
| head => apply Mem.head
| tail => apply Mem.tail; assumption
theorem mem_append_of_mem_right {b : α} {bs : List α} (as : List α) : b bs b as ++ bs := by
theorem mem_append_right {b : α} {bs : List α} (as : List α) : b bs b as ++ bs := by
intro h
induction as with
| nil => simp [h]

View File

@@ -256,7 +256,7 @@ theorem findM?_eq_findSomeM? [Monad m] [LawfulMonad m] (p : α → m Bool) (as :
have : a as := by
have bs, h := h
subst h
exact mem_append_of_mem_right _ (Mem.head ..)
exact mem_append_right _ (Mem.head ..)
match ( f a this b) with
| ForInStep.done b => pure b
| ForInStep.yield b =>

View File

@@ -372,6 +372,17 @@ theorem getElem?_concat_length (l : List α) (a : α) : (l ++ [a])[l.length]? =
@[deprecated getElem?_concat_length (since := "2024-06-12")]
theorem get?_concat_length (l : List α) (a : α) : (l ++ [a]).get? l.length = some a := by simp
@[simp] theorem isSome_getElem? {l : List α} {n : Nat} : l[n]?.isSome n < l.length := by
by_cases h : n < l.length
· simp_all
· simp [h]
simp_all
@[simp] theorem isNone_getElem? {l : List α} {n : Nat} : l[n]?.isNone l.length n := by
by_cases h : n < l.length
· simp_all
· simp [h]
/-! ### mem -/
@[simp] theorem not_mem_nil (a : α) : ¬ a [] := nofun
@@ -383,9 +394,9 @@ theorem get?_concat_length (l : List α) (a : α) : (l ++ [a]).get? l.length = s
theorem mem_cons_self (a : α) (l : List α) : a a :: l := .head ..
theorem mem_concat_self (xs : List α) (a : α) : a xs ++ [a] :=
mem_append_of_mem_right xs (mem_cons_self a _)
mem_append_right xs (mem_cons_self a _)
theorem mem_append_cons_self : a xs ++ a :: ys := mem_append_of_mem_right _ (mem_cons_self _ _)
theorem mem_append_cons_self : a xs ++ a :: ys := mem_append_right _ (mem_cons_self _ _)
theorem eq_append_cons_of_mem {a : α} {xs : List α} (h : a xs) :
as bs, xs = as ++ a :: bs a as := by
@@ -492,16 +503,20 @@ theorem getElem?_of_mem {a} {l : List α} (h : a ∈ l) : ∃ n : Nat, l[n]? = s
theorem get?_of_mem {a} {l : List α} (h : a l) : n, l.get? n = some a :=
let n, _, e := get_of_mem h; n, e get?_eq_get _
theorem get_mem : (l : List α) n h, get l n, h l
| _ :: _, 0, _ => .head ..
| _ :: l, _+1, _ => .tail _ (get_mem l ..)
theorem get_mem : (l : List α) n, get l n l
| _ :: _, 0, _ => .head ..
| _ :: l, _+1, _ => .tail _ (get_mem l ..)
theorem getElem?_mem {l : List α} {n : Nat} {a : α} (e : l[n]? = some a) : a l :=
theorem mem_of_getElem? {l : List α} {n : Nat} {a : α} (e : l[n]? = some a) : a l :=
let _, e := getElem?_eq_some_iff.1 e; e getElem_mem ..
theorem get?_mem {l : List α} {n a} (e : l.get? n = some a) : a l :=
@[deprecated mem_of_getElem? (since := "2024-09-06")] abbrev getElem?_mem := @mem_of_getElem?
theorem mem_of_get? {l : List α} {n a} (e : l.get? n = some a) : a l :=
let _, e := get?_eq_some.1 e; e get_mem ..
@[deprecated mem_of_get? (since := "2024-09-06")] abbrev get?_mem := @mem_of_get?
theorem mem_iff_getElem {a} {l : List α} : a l (n : Nat) (h : n < l.length), l[n]'h = a :=
getElem_of_mem, fun _, _, e => e getElem_mem ..
@@ -1025,6 +1040,10 @@ theorem getLast_eq_getElem : ∀ (l : List α) (h : l ≠ []),
| _ :: _ :: _, _ => by
simp [getLast, get, Nat.succ_sub_succ, getLast_eq_getElem]
theorem getElem_length_sub_one_eq_getLast (l : List α) (h) :
l[l.length - 1] = getLast l (by cases l; simp at h; simp) := by
rw [ getLast_eq_getElem]
@[deprecated getLast_eq_getElem (since := "2024-07-15")]
theorem getLast_eq_get (l : List α) (h : l []) :
getLast l h = l.get l.length - 1, by
@@ -1149,6 +1168,11 @@ theorem head_eq_getElem (l : List α) (h : l ≠ []) : head l h = l[0]'(length_p
| nil => simp at h
| cons _ _ => simp
theorem getElem_zero_eq_head (l : List α) (h) : l[0] = head l (by simpa [length_pos] using h) := by
cases l with
| nil => simp at h
| cons _ _ => simp
theorem head_eq_iff_head?_eq_some {xs : List α} (h) : xs.head h = a xs.head? = some a := by
cases xs with
| nil => simp at h
@@ -1977,11 +2001,8 @@ theorem not_mem_append {a : α} {s t : List α} (h₁ : a ∉ s) (h₂ : a ∉ t
theorem mem_append_eq (a : α) (s t : List α) : (a s ++ t) = (a s a t) :=
propext mem_append
theorem mem_append_left {a : α} {l₁ : List α} (l₂ : List α) (h : a l₁) : a l₁ ++ l₂ :=
mem_append.2 (Or.inl h)
theorem mem_append_right {a : α} (l₁ : List α) {l₂ : List α} (h : a l₂) : a l₁ ++ l₂ :=
mem_append.2 (Or.inr h)
@[deprecated mem_append_left (since := "2024-11-20")] abbrev mem_append_of_mem_left := @mem_append_left
@[deprecated mem_append_right (since := "2024-11-20")] abbrev mem_append_of_mem_right := @mem_append_right
theorem mem_iff_append {a : α} {l : List α} : a l s t : List α, l = s ++ a :: t :=
append_of_mem, fun s, t, e => e by simp
@@ -2395,7 +2416,7 @@ theorem forall_mem_replicate {p : α → Prop} {a : α} {n} :
@[simp] theorem getElem_replicate (a : α) {n : Nat} {m} (h : m < (replicate n a).length) :
(replicate n a)[m] = a :=
eq_of_mem_replicate (get_mem _ _ _)
eq_of_mem_replicate (getElem_mem _)
@[deprecated getElem_replicate (since := "2024-06-12")]
theorem get_replicate (a : α) {n : Nat} (m : Fin _) : (replicate n a).get m = a := by

View File

@@ -417,7 +417,7 @@ theorem Sublist.of_sublist_append_left (w : ∀ a, a ∈ l → a ∉ l₂) (h :
obtain l₁', l₂', rfl, h₁, h₂ := h
have : l₂' = [] := by
rw [eq_nil_iff_forall_not_mem]
exact fun x m => w x (mem_append_of_mem_right l₁' m) (h₂.mem m)
exact fun x m => w x (mem_append_right l₁' m) (h₂.mem m)
simp_all
theorem Sublist.of_sublist_append_right (w : a, a l a l₁) (h : l <+ l₁ ++ l₂) : l <+ l₂ := by
@@ -425,7 +425,7 @@ theorem Sublist.of_sublist_append_right (w : ∀ a, a ∈ l → a ∉ l₁) (h :
obtain l₁', l₂', rfl, h₁, h₂ := h
have : l₁' = [] := by
rw [eq_nil_iff_forall_not_mem]
exact fun x m => w x (mem_append_of_mem_left l₂' m) (h₁.mem m)
exact fun x m => w x (mem_append_left l₂' m) (h₁.mem m)
simp_all
theorem Sublist.middle {l : List α} (h : l <+ l₁ ++ l₂) (a : α) : l <+ l₁ ++ a :: l₂ := by

View File

@@ -1029,3 +1029,12 @@ instance decidableExistsLT [h : DecidablePred p] : DecidablePred fun n => ∃ m
instance decidableExistsLE [DecidablePred p] : DecidablePred fun n => m : Nat, m n p m :=
fun n => decidable_of_iff ( m, m < n + 1 p m)
(exists_congr fun _ => and_congr_left' Nat.lt_succ_iff)
/-! ### Results about `List.sum` specialized to `Nat` -/
protected theorem sum_pos_iff_exists_pos {l : List Nat} : 0 < l.sum x l, 0 < x := by
induction l with
| nil => simp
| cons x xs ih =>
simp [ ih]
omega

View File

@@ -55,7 +55,9 @@ theorem get_eq_getD {fallback : α} : (o : Option α) → {h : o.isSome} → o.g
theorem some_get! [Inhabited α] : (o : Option α) o.isSome some (o.get!) = o
| some _, _ => rfl
theorem get!_eq_getD_default [Inhabited α] (o : Option α) : o.get! = o.getD default := rfl
theorem get!_eq_getD [Inhabited α] (o : Option α) : o.get! = o.getD default := rfl
@[deprecated get!_eq_getD (since := "2024-11-18")] abbrev get!_eq_getD_default := @get!_eq_getD
theorem mem_unique {o : Option α} {a b : α} (ha : a o) (hb : b o) : a = b :=
some.inj <| ha hb

View File

@@ -113,10 +113,10 @@ initialize IO.stdGenRef : IO.Ref StdGen ←
let seed := UInt64.toNat (ByteArray.toUInt64LE! ( IO.getRandomBytes 8))
IO.mkRef (mkStdGen seed)
def IO.setRandSeed (n : Nat) : IO Unit :=
def IO.setRandSeed (n : Nat) : BaseIO Unit :=
IO.stdGenRef.set (mkStdGen n)
def IO.rand (lo hi : Nat) : IO Nat := do
def IO.rand (lo hi : Nat) : BaseIO Nat := do
let gen IO.stdGenRef.get
let (r, gen) := randNat gen lo hi
IO.stdGenRef.set gen

View File

@@ -374,6 +374,9 @@ partial def structEq : Syntax → Syntax → Bool
instance : BEq Lean.Syntax := structEq
instance : BEq (Lean.TSyntax k) := (·.raw == ·.raw)
/--
Finds the first `SourceInfo` from the back of `stx` or `none` if no `SourceInfo` can be found.
-/
partial def getTailInfo? : Syntax Option SourceInfo
| atom info _ => info
| ident info .. => info
@@ -382,14 +385,39 @@ partial def getTailInfo? : Syntax → Option SourceInfo
| node info _ _ => info
| _ => none
/--
Finds the first `SourceInfo` from the back of `stx` or `SourceInfo.none`
if no `SourceInfo` can be found.
-/
def getTailInfo (stx : Syntax) : SourceInfo :=
stx.getTailInfo?.getD SourceInfo.none
/--
Finds the trailing size of the first `SourceInfo` from the back of `stx`.
If no `SourceInfo` can be found or the first `SourceInfo` from the back of `stx` contains no
trailing whitespace, the result is `0`.
-/
def getTrailingSize (stx : Syntax) : Nat :=
match stx.getTailInfo? with
| some (SourceInfo.original (trailing := trailing) ..) => trailing.bsize
| _ => 0
/--
Finds the trailing whitespace substring of the first `SourceInfo` from the back of `stx`.
If no `SourceInfo` can be found or the first `SourceInfo` from the back of `stx` contains
no trailing whitespace, the result is `none`.
-/
def getTrailing? (stx : Syntax) : Option Substring :=
stx.getTailInfo.getTrailing?
/--
Finds the tail position of the trailing whitespace of the first `SourceInfo` from the back of `stx`.
If no `SourceInfo` can be found or the first `SourceInfo` from the back of `stx` contains
no trailing whitespace and lacks a tail position, the result is `none`.
-/
def getTrailingTailPos? (stx : Syntax) (canonicalOnly := false) : Option String.Pos :=
stx.getTailInfo.getTrailingTailPos? canonicalOnly
/--
Return substring of original input covering `stx`.
Result is meaningful only if all involved `SourceInfo.original`s refer to the same string (as is the case after parsing). -/

View File

@@ -3654,7 +3654,8 @@ namespace SourceInfo
/--
Gets the position information from a `SourceInfo`, if available.
If `originalOnly` is true, then `.synthetic` syntax will also return `none`.
If `canonicalOnly` is true, then `.synthetic` syntax with `canonical := false`
will also return `none`.
-/
def getPos? (info : SourceInfo) (canonicalOnly := false) : Option String.Pos :=
match info, canonicalOnly with
@@ -3665,7 +3666,8 @@ def getPos? (info : SourceInfo) (canonicalOnly := false) : Option String.Pos :=
/--
Gets the end position information from a `SourceInfo`, if available.
If `originalOnly` is true, then `.synthetic` syntax will also return `none`.
If `canonicalOnly` is true, then `.synthetic` syntax with `canonical := false`
will also return `none`.
-/
def getTailPos? (info : SourceInfo) (canonicalOnly := false) : Option String.Pos :=
match info, canonicalOnly with
@@ -3674,6 +3676,24 @@ def getTailPos? (info : SourceInfo) (canonicalOnly := false) : Option String.Pos
| synthetic (endPos := endPos) .., false => some endPos
| _, _ => none
/--
Gets the substring representing the trailing whitespace of a `SourceInfo`, if available.
-/
def getTrailing? (info : SourceInfo) : Option Substring :=
match info with
| original (trailing := trailing) .. => some trailing
| _ => none
/--
Gets the end position information of the trailing whitespace of a `SourceInfo`, if available.
If `canonicalOnly` is true, then `.synthetic` syntax with `canonical := false`
will also return `none`.
-/
def getTrailingTailPos? (info : SourceInfo) (canonicalOnly := false) : Option String.Pos :=
match info.getTrailing? with
| some trailing => some trailing.stopPos
| none => info.getTailPos? canonicalOnly
end SourceInfo
/--
@@ -3972,7 +3992,6 @@ position information.
def getPos? (stx : Syntax) (canonicalOnly := false) : Option String.Pos :=
stx.getHeadInfo.getPos? canonicalOnly
/--
Get the ending position of the syntax, if possible.
If `canonicalOnly` is true, non-canonical `synthetic` nodes are treated as not carrying

View File

@@ -802,6 +802,9 @@ def run (args : SpawnArgs) : IO String := do
end Process
/-- Returns the thread ID of the calling thread. -/
@[extern "lean_io_get_tid"] opaque getTID : BaseIO UInt64
structure AccessRight where
read : Bool := false
write : Bool := false

View File

@@ -1155,7 +1155,7 @@ Configuration for the `decide` tactic family.
structure DecideConfig where
/-- If true (default: false), then use only kernel reduction when reducing the `Decidable` instance.
This is more efficient, since the default mode reduces twice (once in the elaborator and again in the kernel),
however kernel reduction ignores transparency settings. The `decide!` tactic is a synonym for `decide +kernel`. -/
however kernel reduction ignores transparency settings. -/
kernel : Bool := false
/-- If true (default: false), then uses the native code compiler to evaluate the `Decidable` instance,
admitting the result via the axiom `Lean.ofReduceBool`. This can be significantly more efficient,
@@ -1165,7 +1165,9 @@ structure DecideConfig where
native : Bool := false
/-- If true (default: true), then when preprocessing the goal, do zeta reduction to attempt to eliminate free variables. -/
zetaReduce : Bool := true
/-- If true (default: false), then when preprocessing reverts free variables. -/
/-- If true (default: false), then when preprocessing, removes irrelevant variables and reverts the local context.
A variable is *relevant* if it appears in the target, if it appears in a relevant variable,
or if it is a proposition that refers to a relevant variable. -/
revert : Bool := false
/--
@@ -1240,17 +1242,6 @@ example : 1 + 1 = 2 := by rfl
-/
syntax (name := decide) "decide" optConfig : tactic
/--
`decide!` is a variant of the `decide` tactic that uses kernel reduction to prove the goal.
It has the following properties:
- Since it uses kernel reduction instead of elaborator reduction, it ignores transparency and can unfold everything.
- While `decide` needs to reduce the `Decidable` instance twice (once during elaboration to verify whether the tactic succeeds,
and once during kernel type checking), the `decide!` tactic reduces it exactly once.
The `decide!` syntax is short for `decide +kernel`.
-/
syntax (name := decideBang) "decide!" optConfig : tactic
/--
`native_decide` is a synonym for `decide +native`.
It will attempt to prove a goal of type `p` by synthesizing an instance

View File

@@ -133,8 +133,8 @@ def foldNatBinBoolPred (fn : Nat → Nat → Bool) (a₁ a₂ : Expr) : Option E
return mkConst ``Bool.false
def foldNatBeq := fun _ : Bool => foldNatBinBoolPred (fun a b => a == b)
def foldNatBle := fun _ : Bool => foldNatBinBoolPred (fun a b => a < b)
def foldNatBlt := fun _ : Bool => foldNatBinBoolPred (fun a b => a b)
def foldNatBlt := fun _ : Bool => foldNatBinBoolPred (fun a b => a < b)
def foldNatBle := fun _ : Bool => foldNatBinBoolPred (fun a b => a b)
def natFoldFns : List (Name × BinFoldFn) :=
[(``Nat.add, foldNatAdd),

View File

@@ -365,6 +365,7 @@ structure TextDocumentRegistrationOptions where
inductive MarkupKind where
| plaintext | markdown
deriving DecidableEq, Hashable
instance : FromJson MarkupKind := fun
| str "plaintext" => Except.ok MarkupKind.plaintext
@@ -378,7 +379,7 @@ instance : ToJson MarkupKind := ⟨fun
structure MarkupContent where
kind : MarkupKind
value : String
deriving ToJson, FromJson
deriving ToJson, FromJson, DecidableEq, Hashable
/-- Reference to the progress of some in-flight piece of work.

View File

@@ -25,7 +25,7 @@ inductive CompletionItemKind where
| unit | value | enum | keyword | snippet
| color | file | reference | folder | enumMember
| constant | struct | event | operator | typeParameter
deriving Inhabited, DecidableEq, Repr
deriving Inhabited, DecidableEq, Repr, Hashable
instance : ToJson CompletionItemKind where
toJson a := toJson (a.toCtorIdx + 1)
@@ -39,11 +39,11 @@ structure InsertReplaceEdit where
newText : String
insert : Range
replace : Range
deriving FromJson, ToJson
deriving FromJson, ToJson, BEq, Hashable
inductive CompletionItemTag where
| deprecated
deriving Inhabited, DecidableEq, Repr
deriving Inhabited, DecidableEq, Repr, Hashable
instance : ToJson CompletionItemTag where
toJson t := toJson (t.toCtorIdx + 1)
@@ -73,7 +73,7 @@ structure CompletionItem where
commitCharacters? : string[]
command? : Command
-/
deriving FromJson, ToJson, Inhabited
deriving FromJson, ToJson, Inhabited, BEq, Hashable
structure CompletionList where
isIncomplete : Bool

View File

@@ -1399,8 +1399,8 @@ private def elabAppLValsAux (namedArgs : Array NamedArg) (args : Array Arg) (exp
let rec loop : Expr List LVal TermElabM Expr
| f, [] => elabAppArgs f namedArgs args expectedType? explicit ellipsis
| f, lval::lvals => do
if let LVal.fieldName (fullRef := fullRef) .. := lval then
addDotCompletionInfo fullRef f expectedType?
if let LVal.fieldName (ref := ref) .. := lval then
addDotCompletionInfo ref f expectedType?
let hasArgs := !namedArgs.isEmpty || !args.isEmpty
let (f, lvalRes) resolveLVal f lval hasArgs
match lvalRes with
@@ -1650,6 +1650,14 @@ private def getSuccesses (candidates : Array (TermElabResult Expr)) : TermElabM
-/
private def mergeFailures (failures : Array (TermElabResult Expr)) : TermElabM α := do
let exs := failures.map fun | .error ex _ => ex | _ => unreachable!
let trees := failures.map (fun | .error _ s => s.meta.core.infoState.trees | _ => unreachable!)
|>.filterMap (·[0]?)
-- Retain partial `InfoTree` subtrees in an `.ofChoiceInfo` node in case of multiple failures.
-- This ensures that the language server still has `Info` to work with when multiple overloaded
-- elaborators fail.
withInfoContext (mkInfo := pure <| .ofChoiceInfo { elaborator := .anonymous, stx := getRef }) do
for tree in trees do
pushInfoTree tree
throwErrorWithNestedErrors "overloaded" exs
private def elabAppAux (f : Syntax) (namedArgs : Array NamedArg) (args : Array Arg) (ellipsis : Bool) (expectedType? : Option Expr) : TermElabM Expr := do

View File

@@ -42,16 +42,15 @@ private def elabOptLevel (stx : Syntax) : TermElabM Level :=
@[builtin_term_elab «completion»] def elabCompletion : TermElab := fun stx expectedType? => do
/- `ident.` is ambiguous in Lean, we may try to be completing a declaration name or access a "field". -/
if stx[0].isIdent then
/- If we can elaborate the identifier successfully, we assume it is a dot-completion. Otherwise, we treat it as
identifier completion with a dangling `.`.
Recall that the server falls back to identifier completion when dot-completion fails. -/
-- Add both an `id` and a `dot` `CompletionInfo` and have the language server figure out which
-- one to use.
addCompletionInfo <| CompletionInfo.id stx stx[0].getId (danglingDot := true) ( getLCtx) expectedType?
let s saveState
try
let e elabTerm stx[0] none
addDotCompletionInfo stx e expectedType?
catch _ =>
s.restore
addCompletionInfo <| CompletionInfo.id stx stx[0].getId (danglingDot := true) ( getLCtx) expectedType?
throwErrorAt stx[1] "invalid field notation, identifier or numeral expected"
else
elabPipeCompletion stx expectedType?
@@ -328,7 +327,7 @@ private def mkSilentAnnotationIfHole (e : Expr) : TermElabM Expr := do
@[builtin_term_elab withAnnotateTerm] def elabWithAnnotateTerm : TermElab := fun stx expectedType? => do
match stx with
| `(with_annotate_term $stx $e) =>
withInfoContext' stx (elabTerm e expectedType?) (mkTermInfo .anonymous (expectedType? := expectedType?) stx)
withTermInfoContext' .anonymous stx (expectedType? := expectedType?) (elabTerm e expectedType?)
| _ => throwUnsupportedSyntax
private unsafe def evalFilePathUnsafe (stx : Syntax) : TermElabM System.FilePath :=

View File

@@ -555,7 +555,11 @@ private def getVarDecls (s : State) : Array Syntax :=
instance {α} : Inhabited (CommandElabM α) where
default := throw default
private def mkMetaContext : Meta.Context := {
/--
The environment linter framework needs to be able to run linters with the same context
as `liftTermElabM`, so we expose that context as a public function here.
-/
def mkMetaContext : Meta.Context := {
config := { foApprox := true, ctxApprox := true, quasiPatternApprox := true }
}

View File

@@ -327,15 +327,18 @@ private def toExprCore (t : Tree) : TermElabM Expr := do
| .term _ trees e =>
modifyInfoState (fun s => { s with trees := s.trees ++ trees }); return e
| .binop ref kind f lhs rhs =>
withRef ref <| withInfoContext' ref (mkInfo := mkTermInfo .anonymous ref) do
mkBinOp (kind == .lazy) f ( toExprCore lhs) ( toExprCore rhs)
withRef ref <|
withTermInfoContext' .anonymous ref do
mkBinOp (kind == .lazy) f ( toExprCore lhs) ( toExprCore rhs)
| .unop ref f arg =>
withRef ref <| withInfoContext' ref (mkInfo := mkTermInfo .anonymous ref) do
mkUnOp f ( toExprCore arg)
withRef ref <|
withTermInfoContext' .anonymous ref do
mkUnOp f ( toExprCore arg)
| .macroExpansion macroName stx stx' nested =>
withRef stx <| withInfoContext' stx (mkInfo := mkTermInfo macroName stx) do
withMacroExpansion stx stx' do
toExprCore nested
withRef stx <|
withTermInfoContext' macroName stx <|
withMacroExpansion stx stx' <|
toExprCore nested
/--
Auxiliary function to decide whether we should coerce `f`'s argument to `maxType` or not.

View File

@@ -139,12 +139,16 @@ def TermInfo.runMetaM (info : TermInfo) (ctx : ContextInfo) (x : MetaM α) : IO
def TermInfo.format (ctx : ContextInfo) (info : TermInfo) : IO Format := do
info.runMetaM ctx do
let ty : Format try
Meta.ppExpr ( Meta.inferType info.expr)
catch _ =>
pure "<failed-to-infer-type>"
let ty : Format
try
Meta.ppExpr ( Meta.inferType info.expr)
catch _ =>
pure "<failed-to-infer-type>"
return f!"{← Meta.ppExpr info.expr} {if info.isBinder then "(isBinder := true) " else ""}: {ty} @ {formatElabInfo ctx info.toElabInfo}"
def PartialTermInfo.format (ctx : ContextInfo) (info : PartialTermInfo) : Format :=
f!"Partial term @ {formatElabInfo ctx info.toElabInfo}"
def CompletionInfo.format (ctx : ContextInfo) (info : CompletionInfo) : IO Format :=
match info with
| .dot i (expectedType? := expectedType?) .. => return f!"[.] {← i.format ctx} : {expectedType?}"
@@ -191,9 +195,13 @@ def FieldRedeclInfo.format (ctx : ContextInfo) (info : FieldRedeclInfo) : Format
def OmissionInfo.format (ctx : ContextInfo) (info : OmissionInfo) : IO Format := do
return f!"Omission @ {← TermInfo.format ctx info.toTermInfo}\nReason: {info.reason}"
def ChoiceInfo.format (ctx : ContextInfo) (info : ChoiceInfo) : Format :=
f!"Choice @ {formatElabInfo ctx info.toElabInfo}"
def Info.format (ctx : ContextInfo) : Info IO Format
| ofTacticInfo i => i.format ctx
| ofTermInfo i => i.format ctx
| ofPartialTermInfo i => pure <| i.format ctx
| ofCommandInfo i => i.format ctx
| ofMacroExpansionInfo i => i.format ctx
| ofOptionInfo i => i.format ctx
@@ -204,10 +212,12 @@ def Info.format (ctx : ContextInfo) : Info → IO Format
| ofFVarAliasInfo i => pure <| i.format
| ofFieldRedeclInfo i => pure <| i.format ctx
| ofOmissionInfo i => i.format ctx
| ofChoiceInfo i => pure <| i.format ctx
def Info.toElabInfo? : Info Option ElabInfo
| ofTacticInfo i => some i.toElabInfo
| ofTermInfo i => some i.toElabInfo
| ofPartialTermInfo i => some i.toElabInfo
| ofCommandInfo i => some i.toElabInfo
| ofMacroExpansionInfo _ => none
| ofOptionInfo _ => none
@@ -218,6 +228,7 @@ def Info.toElabInfo? : Info → Option ElabInfo
| ofFVarAliasInfo _ => none
| ofFieldRedeclInfo _ => none
| ofOmissionInfo i => some i.toElabInfo
| ofChoiceInfo i => some i.toElabInfo
/--
Helper function for propagating the tactic metavariable context to its children nodes.
@@ -311,24 +322,36 @@ def realizeGlobalNameWithInfos (ref : Syntax) (id : Name) : CoreM (List (Name ×
addConstInfo ref n
return ns
/-- Use this to descend a node on the infotree that is being built.
/--
Adds a node containing the `InfoTree`s generated by `x` to the `InfoTree`s in `m`.
It saves the current list of trees `t₀` and resets it and then runs `x >>= mkInfo`, producing either an `i : Info` or a hole id.
Running `x >>= mkInfo` will modify the trees state and produce a new list of trees `t₁`.
In the `i : Info` case, `t₁` become the children of a node `node i t₁` that is appended to `t₀`.
-/
def withInfoContext' [MonadFinally m] (x : m α) (mkInfo : α m (Sum Info MVarId)) : m α := do
If `x` succeeds and `mkInfo` yields an `Info`, the `InfoTree`s of `x` become subtrees of a node
containing the `Info` produced by `mkInfo`, which is then added to the `InfoTree`s in `m`.
If `x` succeeds and `mkInfo` yields an `MVarId`, the `InfoTree`s of `x` are discarded and a `hole`
node is added to the `InfoTree`s in `m`.
If `x` fails, the `InfoTree`s of `x` become subtrees of a node containing the `Info` produced by
`mkInfoOnError`, which is then added to the `InfoTree`s in `m`.
The `InfoTree`s in `m` are reset before `x` is executed and restored with the addition of a new tree
after `x` is executed.
-/
def withInfoContext'
[MonadFinally m]
(x : m α)
(mkInfo : α m (Sum Info MVarId))
(mkInfoOnError : m Info) :
m α := do
if ( getInfoState).enabled then
let treesSaved getResetInfoTrees
Prod.fst <$> MonadFinally.tryFinally' x fun a? => do
match a? with
| none => modifyInfoTrees fun _ => treesSaved
| some a =>
let info mkInfo a
modifyInfoTrees fun trees =>
match info with
| Sum.inl info => treesSaved.push <| InfoTree.node info trees
| Sum.inr mvarId => treesSaved.push <| InfoTree.hole mvarId
let info do
match a? with
| none => pure <| .inl <| mkInfoOnError
| some a => mkInfo a
modifyInfoTrees fun trees =>
match info with
| Sum.inl info => treesSaved.push <| InfoTree.node info trees
| Sum.inr mvarId => treesSaved.push <| InfoTree.hole mvarId
else
x

View File

@@ -70,6 +70,18 @@ structure TermInfo extends ElabInfo where
isBinder : Bool := false
deriving Inhabited
/--
Used instead of `TermInfo` when a term couldn't successfully be elaborated,
and so there is no complete expression available.
The main purpose of `PartialTermInfo` is to ensure that the sub-`InfoTree`s of a failed elaborator
are retained so that they can still be used in the language server.
-/
structure PartialTermInfo extends ElabInfo where
lctx : LocalContext -- The local context when the term was elaborated.
expectedType? : Option Expr
deriving Inhabited
structure CommandInfo extends ElabInfo where
deriving Inhabited
@@ -79,7 +91,7 @@ inductive CompletionInfo where
| dot (termInfo : TermInfo) (expectedType? : Option Expr)
| id (stx : Syntax) (id : Name) (danglingDot : Bool) (lctx : LocalContext) (expectedType? : Option Expr)
| dotId (stx : Syntax) (id : Name) (lctx : LocalContext) (expectedType? : Option Expr)
| fieldId (stx : Syntax) (id : Name) (lctx : LocalContext) (structName : Name)
| fieldId (stx : Syntax) (id : Option Name) (lctx : LocalContext) (structName : Name)
| namespaceId (stx : Syntax)
| option (stx : Syntax)
| endSection (stx : Syntax) (scopeNames : List String)
@@ -165,10 +177,18 @@ regular delaboration settings.
structure OmissionInfo extends TermInfo where
reason : String
/--
Indicates that all overloaded elaborators failed. The subtrees of a `ChoiceInfo` node are the
partial `InfoTree`s of those failed elaborators. Retaining these partial `InfoTree`s helps
the language server provide interactivity even when all overloaded elaborators failed.
-/
structure ChoiceInfo extends ElabInfo where
/-- Header information for a node in `InfoTree`. -/
inductive Info where
| ofTacticInfo (i : TacticInfo)
| ofTermInfo (i : TermInfo)
| ofPartialTermInfo (i : PartialTermInfo)
| ofCommandInfo (i : CommandInfo)
| ofMacroExpansionInfo (i : MacroExpansionInfo)
| ofOptionInfo (i : OptionInfo)
@@ -179,6 +199,7 @@ inductive Info where
| ofFVarAliasInfo (i : FVarAliasInfo)
| ofFieldRedeclInfo (i : FieldRedeclInfo)
| ofOmissionInfo (i : OmissionInfo)
| ofChoiceInfo (i : ChoiceInfo)
deriving Inhabited
/-- The InfoTree is a structure that is generated during elaboration and used

View File

@@ -90,9 +90,12 @@ private def elabLetRecDeclValues (view : LetRecView) : TermElabM (Array Expr) :=
for i in [0:view.binderIds.size] do
addLocalVarInfo view.binderIds[i]! xs[i]!
withDeclName view.declName do
withInfoContext' view.valStx (mkInfo := (pure <| .inl <| mkBodyInfo view.valStx ·)) do
let value elabTermEnsuringType view.valStx type
mkLambdaFVars xs value
withInfoContext' view.valStx
(mkInfo := (pure <| .inl <| mkBodyInfo view.valStx ·))
(mkInfoOnError := (pure <| mkBodyInfo view.valStx none))
do
let value elabTermEnsuringType view.valStx type
mkLambdaFVars xs value
private def registerLetRecsToLift (views : Array LetRecDeclView) (fvars : Array Expr) (values : Array Expr) : TermElabM Unit := do
let letRecsToLiftCurr := ( get).letRecsToLift

View File

@@ -644,7 +644,7 @@ where
if inaccessible? p |>.isSome then
return mkMData k ( withReader (fun _ => true) (go b))
else if let some (stx, p) := patternWithRef? p then
Elab.withInfoContext' (go p) fun p => do
Elab.withInfoContext' (go p) (mkInfoOnError := mkPartialTermInfo .anonymous stx) fun p => do
/- If `p` is a free variable and we are not inside of an "inaccessible" pattern, this `p` is a binder. -/
mkTermInfo Name.anonymous stx p (isBinder := p.isFVar && !( read))
else

View File

@@ -283,7 +283,7 @@ private partial def withFunLocalDecls {α} (headers : Array DefViewElabHeader) (
loop 0 #[]
private def expandWhereStructInst : Macro
| `(Parser.Command.whereStructInst|where $[$decls:letDecl];* $[$whereDecls?:whereDecls]?) => do
| whereStx@`(Parser.Command.whereStructInst|where%$whereTk $[$decls:letDecl];* $[$whereDecls?:whereDecls]?) => do
let letIdDecls decls.mapM fun stx => match stx with
| `(letDecl|$_decl:letPatDecl) => Macro.throwErrorAt stx "patterns are not allowed here"
| `(letDecl|$decl:letEqnsDecl) => expandLetEqnsDecl decl (useExplicit := false)
@@ -300,7 +300,30 @@ private def expandWhereStructInst : Macro
`(structInstField|$id:ident := $val)
| stx@`(letIdDecl|_ $_* $[: $_]? := $_) => Macro.throwErrorAt stx "'_' is not allowed here"
| _ => Macro.throwUnsupported
let startOfStructureTkInfo : SourceInfo :=
match whereTk.getPos? with
| some pos => .synthetic pos pos.byteIdx + 1 true
| none => .none
-- Position the closing `}` at the end of the trailing whitespace of `where $[$_:letDecl];*`.
-- We need an accurate range of the generated structure instance in the generated `TermInfo`
-- so that we can determine the expected type in structure field completion.
let structureStxTailInfo :=
whereStx[1].getTailInfo?
<|> whereStx[0].getTailInfo?
let endOfStructureTkInfo : SourceInfo :=
match structureStxTailInfo with
| some (SourceInfo.original _ _ trailing _) =>
let tokenPos := trailing.str.prev trailing.stopPos
let tokenEndPos := trailing.stopPos
.synthetic tokenPos tokenEndPos true
| _ => .none
let body `(structInst| { $structInstFields,* })
let body := body.raw.setInfo <|
match startOfStructureTkInfo.getPos?, endOfStructureTkInfo.getTailPos? with
| some startPos, some endPos => .synthetic startPos endPos true
| _, _ => .none
match whereDecls? with
| some whereDecls => expandWhereDecls whereDecls body
| none => return body
@@ -417,12 +440,15 @@ private def elabFunValues (headers : Array DefViewElabHeader) (vars : Array Expr
-- Store instantiated body in info tree for the benefit of the unused variables linter
-- and other metaprograms that may want to inspect it without paying for the instantiation
-- again
withInfoContext' valStx (mkInfo := (pure <| .inl <| mkBodyInfo valStx ·)) do
-- synthesize mvars here to force the top-level tactic block (if any) to run
let val elabTermEnsuringType valStx type <* synthesizeSyntheticMVarsNoPostponing
-- NOTE: without this `instantiatedMVars`, `mkLambdaFVars` may leave around a redex that
-- leads to more section variables being included than necessary
instantiateMVarsProfiling val
withInfoContext' valStx
(mkInfo := (pure <| .inl <| mkBodyInfo valStx ·))
(mkInfoOnError := (pure <| mkBodyInfo valStx none))
do
-- synthesize mvars here to force the top-level tactic block (if any) to run
let val elabTermEnsuringType valStx type <* synthesizeSyntheticMVarsNoPostponing
-- NOTE: without this `instantiatedMVars`, `mkLambdaFVars` may leave around a redex that
-- leads to more section variables being included than necessary
instantiateMVarsProfiling val
let val mkLambdaFVars xs val
if linter.unusedSectionVars.get ( getOptions) && !header.type.hasSorry && !val.hasSorry then
let unusedVars vars.filterMapM fun var => do

View File

@@ -243,7 +243,7 @@ def tryAllArgs (fnNames : Array Name) (xs : Array Expr) (values : Array Expr)
recArgInfoss := recArgInfoss.push recArgInfos
-- Put non-indices first
recArgInfoss := recArgInfoss.map nonIndicesFirst
trace[Elab.definition.structural] "recArgInfoss: {recArgInfoss.map (·.map (·.recArgPos))}"
trace[Elab.definition.structural] "recArgInfos:{indentD (.joinSep (recArgInfoss.flatten.toList.map (repr ·)) Format.line)}"
-- Inductive groups to consider
let groups inductiveGroups recArgInfoss.flatten
trace[Elab.definition.structural] "inductive groups: {groups}"

View File

@@ -27,7 +27,7 @@ constituents.
structure IndGroupInfo where
all : Array Name
numNested : Nat
deriving BEq, Inhabited
deriving BEq, Inhabited, Repr
def IndGroupInfo.ofInductiveVal (indInfo : InductiveVal) : IndGroupInfo where
all := indInfo.all.toArray
@@ -56,7 +56,7 @@ mutual structural recursion on such incompatible types.
structure IndGroupInst extends IndGroupInfo where
levels : List Level
params : Array Expr
deriving Inhabited
deriving Inhabited, Repr
def IndGroupInst.toMessageData (igi : IndGroupInst) : MessageData :=
mkAppN (.const igi.all[0]! igi.levels) igi.params

View File

@@ -23,9 +23,9 @@ structure RecArgInfo where
fnName : Name
/-- the fixed prefix of arguments of the function we are trying to justify termination using structural recursion. -/
numFixed : Nat
/-- position of the argument (counted including fixed prefix) we are recursing on -/
/-- position (counted including fixed prefix) of the argument we are recursing on -/
recArgPos : Nat
/-- position of the indices (counted including fixed prefix) of the inductive datatype indices we are recursing on -/
/-- position (counted including fixed prefix) of the indices of the inductive datatype we are recursing on -/
indicesPos : Array Nat
/-- The inductive group (with parameters) of the argument's type -/
indGroupInst : IndGroupInst
@@ -34,20 +34,23 @@ structure RecArgInfo where
If `< indAll.all`, a normal data type, else an auxiliary data type due to nested recursion
-/
indIdx : Nat
deriving Inhabited
deriving Inhabited, Repr
/--
If `xs` are the parameters of the functions (excluding fixed prefix), partitions them
into indices and major arguments, and other parameters.
-/
def RecArgInfo.pickIndicesMajor (info : RecArgInfo) (xs : Array Expr) : (Array Expr × Array Expr) := Id.run do
-- First indices and major arg, using the order they appear in `info.indicesPos`
let mut indexMajorArgs := #[]
let indexMajorPos := info.indicesPos.push info.recArgPos
for j in indexMajorPos do
assert! info.numFixed j && j - info.numFixed < xs.size
indexMajorArgs := indexMajorArgs.push xs[j - info.numFixed]!
-- Then the other arguments, in the order they appear in `xs`
let mut otherArgs := #[]
for h : i in [:xs.size] do
let j := i + info.numFixed
if j = info.recArgPos || info.indicesPos.contains j then
indexMajorArgs := indexMajorArgs.push xs[i]
else
unless indexMajorPos.contains (i + info.numFixed) do
otherArgs := otherArgs.push xs[i]
return (indexMajorArgs, otherArgs)

View File

@@ -22,7 +22,7 @@ private def levelParamsToMessageData (levelParams : List Name) : MessageData :=
m := m ++ ", " ++ toMessageData u
return m ++ "}"
private def mkHeader (kind : String) (id : Name) (levelParams : List Name) (type : Expr) (safety : DefinitionSafety) : CommandElabM MessageData := do
private def mkHeader (kind : String) (id : Name) (levelParams : List Name) (type : Expr) (safety : DefinitionSafety) (sig : Bool := true) : CommandElabM MessageData := do
let m : MessageData :=
match ( getReducibilityStatus id) with
| ReducibilityStatus.irreducible => "@[irreducible] "
@@ -38,11 +38,13 @@ private def mkHeader (kind : String) (id : Name) (levelParams : List Name) (type
let (m, id) := match privateToUserName? id with
| some id => (m ++ "private ", id)
| none => (m, id)
let m := m ++ kind ++ " " ++ id ++ levelParamsToMessageData levelParams ++ " : " ++ type
pure m
if sig then
return m!"{m}{kind} {id}{levelParamsToMessageData levelParams} : {type}"
else
return m!"{m}{kind}"
private def mkHeader' (kind : String) (id : Name) (levelParams : List Name) (type : Expr) (isUnsafe : Bool) : CommandElabM MessageData :=
mkHeader kind id levelParams type (if isUnsafe then DefinitionSafety.unsafe else DefinitionSafety.safe)
private def mkHeader' (kind : String) (id : Name) (levelParams : List Name) (type : Expr) (isUnsafe : Bool) (sig : Bool := true) : CommandElabM MessageData :=
mkHeader kind id levelParams type (if isUnsafe then DefinitionSafety.unsafe else DefinitionSafety.safe) (sig := sig)
private def printDefLike (kind : String) (id : Name) (levelParams : List Name) (type : Expr) (value : Expr) (safety := DefinitionSafety.safe) : CommandElabM Unit := do
let m mkHeader kind id levelParams type safety
@@ -65,32 +67,63 @@ private def printInduct (id : Name) (levelParams : List Name) (numParams : Nat)
m := m ++ Format.line ++ ctor ++ " : " ++ cinfo.type
logInfo m
/--
Computes the origin of a field. Returns its projection function at the origin.
Multiple parents could be the origin of a field, but we say the first parent that provides it is the one that determines the origin.
-/
private partial def getFieldOrigin (structName field : Name) : MetaM Name := do
let env getEnv
for parent in getStructureParentInfo env structName do
if (findField? env parent.structName field).isSome then
return getFieldOrigin parent.structName field
let some fi := getFieldInfo? env structName field
| throwError "no such field {field} in {structName}"
return fi.projFn
open Meta in
private def printStructure (id : Name) (levelParams : List Name) (numParams : Nat) (type : Expr)
(ctor : Name) (fields : Array Name) (isUnsafe : Bool) (isClass : Bool) : CommandElabM Unit := do
let kind := if isClass then "class" else "structure"
let mut m mkHeader' kind id levelParams type isUnsafe
m := m ++ Format.line ++ "number of parameters: " ++ toString numParams
m := m ++ Format.line ++ "constructor:"
let cinfo getConstInfo ctor
m := m ++ Format.line ++ ctor ++ " : " ++ cinfo.type
m := m ++ Format.line ++ "fields:" ++ ( doFields)
logInfo m
where
doFields := liftTermElabM do
forallTelescope ( getConstInfo id).type fun params _ =>
withLocalDeclD `self (mkAppN (Expr.const id (levelParams.map .param)) params) fun self => do
let params := params.push self
let mut m : MessageData := ""
(isUnsafe : Bool) : CommandElabM Unit := do
let env getEnv
let kind := if isClass env id then "class" else "structure"
let header mkHeader' kind id levelParams type isUnsafe (sig := false)
liftTermElabM <| forallTelescope ( getConstInfo id).type fun params _ =>
let s := Expr.const id (levelParams.map .param)
withLocalDeclD `self (mkAppN s params) fun self => do
let mut m : MessageData := header
-- Signature
m := m ++ " " ++ .ofFormatWithInfosM do
let (stx, infos) PrettyPrinter.delabCore s (delab := PrettyPrinter.Delaborator.delabConstWithSignature)
pure PrettyPrinter.ppTerm stx, infos
m := m ++ Format.line ++ m!"number of parameters: {numParams}"
-- Parents
let parents := getStructureParentInfo env id
unless parents.isEmpty do
m := m ++ Format.line ++ "parents:"
for parent in parents do
let ptype inferType (mkApp (mkAppN (.const parent.projFn (levelParams.map .param)) params) self)
m := m ++ indentD m!"{.ofConstName parent.projFn (fullNames := true)} : {ptype}"
-- Fields
let fields := getStructureFieldsFlattened env id (includeSubobjectFields := false)
if fields.isEmpty then
m := m ++ Format.line ++ "fields: (none)"
else
m := m ++ Format.line ++ "fields:"
for field in fields do
match getProjFnForField? ( getEnv) id field with
| some proj =>
let field : Format := if isPrivateName proj then "private " ++ toString field else toString field
let cinfo getConstInfo proj
let ftype instantiateForall cinfo.type params
m := m ++ Format.line ++ field ++ " : " ++ ftype
| none => panic! "missing structure field info"
addMessageContext m
let some source := findField? env id field | panic! "missing structure field info"
let proj getFieldOrigin source field
let modifier := if isPrivateName proj then "private " else ""
let ftype inferType ( mkProjection self field)
m := m ++ indentD (m!"{modifier}{.ofConstName proj (fullNames := true)} : {ftype}")
-- Constructor
let cinfo := getStructureCtor ( getEnv) id
let ctorModifier := if isPrivateName cinfo.name then "private " else ""
m := m ++ Format.line ++ "constructor:" ++ indentD (ctorModifier ++ .signature cinfo.name)
-- Resolution order
let resOrder getStructureResolutionOrder id
if resOrder.size > 1 then
m := m ++ Format.line ++ "resolution order:"
++ indentD (MessageData.joinSep (resOrder.map (.ofConstName · (fullNames := true))).toList ", ")
logInfo m
private def printIdCore (id : Name) : CommandElabM Unit := do
let env getEnv
@@ -103,11 +136,10 @@ private def printIdCore (id : Name) : CommandElabM Unit := do
| ConstantInfo.ctorInfo { levelParams := us, type := t, isUnsafe := u, .. } => printAxiomLike "constructor" id us t u
| ConstantInfo.recInfo { levelParams := us, type := t, isUnsafe := u, .. } => printAxiomLike "recursor" id us t u
| ConstantInfo.inductInfo { levelParams := us, numParams, type := t, ctors, isUnsafe := u, .. } =>
match getStructureInfo? env id with
| some { fieldNames, .. } =>
let [ctor] := ctors | panic! "structures have only one constructor"
printStructure id us numParams t ctor fieldNames u (isClass env id)
| none => printInduct id us numParams t ctors u
if isStructure env id then
printStructure id us numParams t u
else
printInduct id us numParams t ctors u
| none => throwUnknownId id
private def printId (id : Syntax) : CommandElabM Unit := do

View File

@@ -11,21 +11,40 @@ import Lean.Elab.App
import Lean.Elab.Binders
import Lean.PrettyPrinter
/-!
# Structure instance elaborator
A *structure instance* is notation to construct a term of a `structure`.
Examples: `{ x := 2, y.z := true }`, `{ s with cache := c' }`, and `{ s with values[2] := v }`.
Structure instances are the preferred way to invoke a `structure`'s constructor,
since they hide Lean implementation details such as whether parents are represented as subobjects,
and also they do correct processing of default values, which are complicated due to the fact that `structure`s can override default values of their parents.
This module elaborates structure instance notation.
Note that the `where` syntax to define structures (`Lean.Parser.Command.whereStructInst`)
macro expands into the structure instance notation elaborated by this module.
-/
namespace Lean.Elab.Term.StructInst
open Meta
open TSyntax.Compat
/-
Structure instances are of the form:
"{" >> optional (atomic (sepBy1 termParser ", " >> " with "))
>> manyIndent (group ((structInstFieldAbbrev <|> structInstField) >> optional ", "))
>> optEllipsis
>> optional (" : " >> termParser)
>> " }"
/-!
Recall that structure instances are of the form:
```
"{" >> optional (atomic (sepBy1 termParser ", " >> " with "))
>> manyIndent (group ((structInstFieldAbbrev <|> structInstField) >> optional ", "))
>> optEllipsis
>> optional (" : " >> termParser)
>> " }"
```
-/
/--
Transforms structure instances such as `{ x := 0 : Foo }` into `({ x := 0 } : Foo)`.
Structure instance notation makes use of the expected type.
-/
@[builtin_macro Lean.Parser.Term.structInst] def expandStructInstExpectedType : Macro := fun stx =>
let expectedArg := stx[4]
if expectedArg.isNone then
@@ -35,7 +54,10 @@ open TSyntax.Compat
let stxNew := stx.setArg 4 mkNullNode
`(($stxNew : $expected))
/-- Expand field abbreviations. Example: `{ x, y := 0 }` expands to `{ x := x, y := 0 }` -/
/--
Expands field abbreviation notation.
Example: `{ x, y := 0 }` expands to `{ x := x, y := 0 }`.
-/
@[builtin_macro Lean.Parser.Term.structInst] def expandStructInstFieldAbbrev : Macro
| `({ $[$srcs,* with]? $fields,* $[..%$ell]? $[: $ty]? }) =>
if fields.getElems.raw.any (·.getKind == ``Lean.Parser.Term.structInstFieldAbbrev) then do
@@ -49,9 +71,12 @@ open TSyntax.Compat
| _ => Macro.throwUnsupported
/--
If `stx` is of the form `{ s₁, ..., sₙ with ... }` and `sᵢ` is not a local variable, expand into `let src := sᵢ; { ..., src, ... with ... }`.
If `stx` is of the form `{ s₁, ..., sₙ with ... }` and `sᵢ` is not a local variable,
expands into `let __src := sᵢ; { ..., __src, ... with ... }`.
The significance of `__src` is that the variable is treated as an implementation-detail local variable,
which can be unfolded by `simp` when `zetaDelta := false`.
Note that this one is not a `Macro` because we need to access the local context.
Note that this one is not a `Macro` because we need to access the local context.
-/
private def expandNonAtomicExplicitSources (stx : Syntax) : TermElabM (Option Syntax) := do
let sourcesOpt := stx[1]
@@ -100,27 +125,44 @@ where
let r go sources (sourcesNew.push sourceNew)
`(let __src := $source; $r)
structure ExplicitSourceInfo where
/--
An *explicit source* is one of the structures `sᵢ` that appear in `{ s₁, …, sₙ with … }`.
-/
structure ExplicitSourceView where
/-- The syntax of the explicit source. -/
stx : Syntax
/-- The name of the structure for the type of the explicit source. -/
structName : Name
deriving Inhabited
structure Source where
explicit : Array ExplicitSourceInfo -- `s₁ ... sₙ with`
implicit : Option Syntax -- `..`
/--
A view of the sources of fields for the structure instance notation.
-/
structure SourcesView where
/-- Explicit sources (i.e., one of the structures `sᵢ` that appear in `{ s₁, …, sₙ with … }`). -/
explicit : Array ExplicitSourceView
/-- The syntax for a trailing `..`. This is "ellipsis mode" for missing fields, similar to ellipsis mode for applications. -/
implicit : Option Syntax
deriving Inhabited
def Source.isNone : Source Bool
/-- Returns `true` if the structure instance has no sources (neither explicit sources nor a `..`). -/
def SourcesView.isNone : SourcesView Bool
| { explicit := #[], implicit := none } => true
| _ => false
/-- `optional (atomic (sepBy1 termParser ", " >> " with ")` -/
/--
Given an array of explicit sources, returns syntax of the form
`optional (atomic (sepBy1 termParser ", " >> " with ")`
-/
private def mkSourcesWithSyntax (sources : Array Syntax) : Syntax :=
let ref := sources[0]!
let stx := Syntax.mkSep sources (mkAtomFrom ref ", ")
mkNullNode #[stx, mkAtomFrom ref "with "]
private def getStructSource (structStx : Syntax) : TermElabM Source :=
/--
Creates a structure source view from structure instance notation.
-/
private def getStructSources (structStx : Syntax) : TermElabM SourcesView :=
withRef structStx do
let explicitSource := structStx[1]
let implicitSource := structStx[3]
@@ -138,13 +180,13 @@ private def getStructSource (structStx : Syntax) : TermElabM Source :=
return { explicit, implicit }
/--
We say a `{ ... }` notation is a `modifyOp` if it contains only one
```
def structInstArrayRef := leading_parser "[" >> termParser >>"]"
```
We say a structure instance notation is a "modifyOp" if it contains only a single array update.
```lean
def structInstArrayRef := leading_parser "[" >> termParser >>"]"
```
-/
private def isModifyOp? (stx : Syntax) : TermElabM (Option Syntax) := do
let s? stx[2].getSepArgs.foldlM (init := none) fun s? arg => do
let s? stx[2][0].getSepArgs.foldlM (init := none) fun s? arg => do
/- arg is of the form `structInstFieldAbbrev <|> structInstField` -/
if arg.getKind == ``Lean.Parser.Term.structInstField then
/- Remark: the syntax for `structInstField` is
@@ -177,7 +219,11 @@ private def isModifyOp? (stx : Syntax) : TermElabM (Option Syntax) := do
| none => return none
| some s => if s[0][0].getKind == ``Lean.Parser.Term.structInstArrayRef then return s? else return none
private def elabModifyOp (stx modifyOp : Syntax) (sources : Array ExplicitSourceInfo) (expectedType? : Option Expr) : TermElabM Expr := do
/--
Given a `stx` that is a structure instance notation that's a modifyOp (according to `isModifyOp?`), elaborates it.
Only supports structure instances with a single source.
-/
private def elabModifyOp (stx modifyOp : Syntax) (sources : Array ExplicitSourceView) (expectedType? : Option Expr) : TermElabM Expr := do
if sources.size > 1 then
throwError "invalid \{...} notation, multiple sources and array update is not supported."
let cont (val : Syntax) : TermElabM Expr := do
@@ -199,17 +245,18 @@ private def elabModifyOp (stx modifyOp : Syntax) (sources : Array ExplicitSource
let valField := modifyOp.setArg 0 <| mkNode ``Parser.Term.structInstLVal #[valFirst, valRest]
let valSource := mkSourcesWithSyntax #[s]
let val := stx.setArg 1 valSource
let val := val.setArg 2 <| mkNullNode #[valField]
let val := val.setArg 2 <| mkNode ``Parser.Term.structInstFields #[mkNullNode #[valField]]
trace[Elab.struct.modifyOp] "{stx}\nval: {val}"
cont val
/--
Get structure name.
This method triest to postpone execution if the expected type is not available.
Gets the structure name for the structure instance from the expected type and the sources.
This method tries to postpone execution if the expected type is not available.
If the expected type is available and it is a structure, then we use it.
Otherwise, we use the type of the first source. -/
private def getStructName (expectedType? : Option Expr) (sourceView : Source) : TermElabM Name := do
If the expected type is available and it is a structure, then we use it.
Otherwise, we use the type of the first source.
-/
private def getStructName (expectedType? : Option Expr) (sourceView : SourcesView) : TermElabM Name := do
tryPostponeIfNoneOrMVar expectedType?
let useSource : Unit TermElabM Name := fun _ => do
unless sourceView.explicit.isEmpty do
@@ -226,7 +273,7 @@ private def getStructName (expectedType? : Option Expr) (sourceView : Source) :
unless isStructure ( getEnv) constName do
throwError "invalid \{...} notation, structure type expected{indentExpr expectedType}"
return constName
| _ => useSource ()
| _ => useSource ()
where
throwUnknownExpectedType :=
throwError "invalid \{...} notation, expected type is not known"
@@ -237,72 +284,92 @@ where
else
throwError "invalid \{...} notation, {kind} type is not of the form (C ...){indentExpr type}"
/--
A component of a left-hand side for a field appearing in structure instance syntax.
-/
inductive FieldLHS where
/-- A name component for a field left-hand side. For example, `x` and `y` in `{ x.y := v }`. -/
| fieldName (ref : Syntax) (name : Name)
/-- A numeric index component for a field left-hand side. For example `3` in `{ x.3 := v }`. -/
| fieldIndex (ref : Syntax) (idx : Nat)
/-- An array indexing component for a field left-hand side. For example `[3]` in `{ arr[3] := v }`. -/
| modifyOp (ref : Syntax) (index : Syntax)
deriving Inhabited
instance : ToFormat FieldLHS := fun lhs =>
match lhs with
| .fieldName _ n => format n
| .fieldIndex _ i => format i
| .modifyOp _ i => "[" ++ i.prettyPrint ++ "]"
instance : ToFormat FieldLHS where
format
| .fieldName _ n => format n
| .fieldIndex _ i => format i
| .modifyOp _ i => "[" ++ i.prettyPrint ++ "]"
/--
`FieldVal StructInstView` is a representation of a field value in the structure instance.
-/
inductive FieldVal (σ : Type) where
| term (stx : Syntax) : FieldVal σ
/-- A `term` to use for the value of the field. -/
| term (stx : Syntax) : FieldVal σ
/-- A `StructInstView` to use for the value of a subobject field. -/
| nested (s : σ) : FieldVal σ
| default : FieldVal σ -- mark that field must be synthesized using default value
/-- A field that was not provided and should be synthesized using default values. -/
| default : FieldVal σ
deriving Inhabited
/--
`Field StructInstView` is a representation of a field in the structure instance.
-/
structure Field (σ : Type) where
/-- The whole field syntax. -/
ref : Syntax
/-- The LHS decomposed into components. -/
lhs : List FieldLHS
/-- The value of the field. -/
val : FieldVal σ
/-- The elaborated field value, filled in at `elabStruct`.
Missing fields use a metavariable for the elaborated value and are later solved for in `DefaultFields.propagate`. -/
expr? : Option Expr := none
deriving Inhabited
/--
Returns if the field has a single component in its LHS.
-/
def Field.isSimple {σ} : Field σ Bool
| { lhs := [_], .. } => true
| _ => false
inductive Struct where
/-- Remark: the field `params` is use for default value propagation. It is initially empty, and then set at `elabStruct`. -/
| mk (ref : Syntax) (structName : Name) (params : Array (Name × Expr)) (fields : List (Field Struct)) (source : Source)
/--
The view for structure instance notation.
-/
structure StructInstView where
/-- The syntax for the whole structure instance. -/
ref : Syntax
/-- The name of the structure for the type of the structure instance. -/
structName : Name
/-- Used for default values, to propagate structure type parameters. It is initially empty, and then set at `elabStruct`. -/
params : Array (Name × Expr)
/-- The fields of the structure instance. -/
fields : List (Field StructInstView)
/-- The additional sources for fields for the structure instance. -/
sources : SourcesView
deriving Inhabited
abbrev Fields := List (Field Struct)
def Struct.ref : Struct Syntax
| ref, _, _, _, _ => ref
def Struct.structName : Struct Name
| _, structName, _, _, _ => structName
def Struct.params : Struct Array (Name × Expr)
| _, _, params, _, _ => params
def Struct.fields : Struct Fields
| _, _, _, fields, _ => fields
def Struct.source : Struct Source
| _, _, _, _, s => s
/-- Abbreviation for the type of `StructInstView.fields`, namely `List (Field StructInstView)`. -/
abbrev Fields := List (Field StructInstView)
/-- `true` iff all fields of the given structure are marked as `default` -/
partial def Struct.allDefault (s : Struct) : Bool :=
partial def StructInstView.allDefault (s : StructInstView) : Bool :=
s.fields.all fun { val := val, .. } => match val with
| .term _ => false
| .default => true
| .nested s => allDefault s
def formatField (formatStruct : Struct Format) (field : Field Struct) : Format :=
def formatField (formatStruct : StructInstView Format) (field : Field StructInstView) : Format :=
Format.joinSep field.lhs " . " ++ " := " ++
match field.val with
| .term v => v.prettyPrint
| .nested s => formatStruct s
| .default => "<default>"
partial def formatStruct : Struct Format
partial def formatStruct : StructInstView Format
| _, _, _, fields, source =>
let fieldsFmt := Format.joinSep (fields.map (formatField formatStruct)) ", "
let implicitFmt := if source.implicit.isSome then " .. " else ""
@@ -311,31 +378,39 @@ partial def formatStruct : Struct → Format
else
"{" ++ format (source.explicit.map (·.stx)) ++ " with " ++ fieldsFmt ++ implicitFmt ++ "}"
instance : ToFormat Struct := formatStruct
instance : ToString Struct := toString format
instance : ToFormat StructInstView := formatStruct
instance : ToString StructInstView := toString format
instance : ToFormat (Field Struct) := formatField formatStruct
instance : ToString (Field Struct) := toString format
instance : ToFormat (Field StructInstView) := formatField formatStruct
instance : ToString (Field StructInstView) := toString format
/--
Converts a `FieldLHS` back into syntax. This assumes the `ref` fields have the correct structure.
/-
Recall that `structInstField` elements have the form
```
def structInstField := leading_parser structInstLVal >> " := " >> termParser
def structInstLVal := leading_parser (ident <|> numLit <|> structInstArrayRef) >> many (("." >> (ident <|> numLit)) <|> structInstArrayRef)
def structInstArrayRef := leading_parser "[" >> termParser >>"]"
```lean
def structInstField := leading_parser structInstLVal >> " := " >> termParser
def structInstLVal := leading_parser (ident <|> numLit <|> structInstArrayRef) >> many (("." >> (ident <|> numLit)) <|> structInstArrayRef)
def structInstArrayRef := leading_parser "[" >> termParser >>"]"
```
-/
-- Remark: this code relies on the fact that `expandStruct` only transforms `fieldLHS.fieldName`
def FieldLHS.toSyntax (first : Bool) : FieldLHS Syntax
private def FieldLHS.toSyntax (first : Bool) : FieldLHS Syntax
| .modifyOp stx _ => stx
| .fieldName stx name => if first then mkIdentFrom stx name else mkGroupNode #[mkAtomFrom stx ".", mkIdentFrom stx name]
| .fieldIndex stx _ => if first then stx else mkGroupNode #[mkAtomFrom stx ".", stx]
def FieldVal.toSyntax : FieldVal Struct Syntax
/--
Converts a `FieldVal StructInstView` back into syntax. Only supports `.term`, and it assumes the `stx` field has the correct structure.
-/
private def FieldVal.toSyntax : FieldVal Struct Syntax
| .term stx => stx
| _ => unreachable!
| _ => unreachable!
def Field.toSyntax : Field Struct Syntax
/--
Converts a `Field StructInstView` back into syntax. Used to construct synthetic structure instance notation for subobjects in `StructInst.expandStruct` processing.
-/
private def Field.toSyntax : Field Struct Syntax
| field =>
let stx := field.ref
let stx := stx.setArg 2 field.val.toSyntax
@@ -343,6 +418,7 @@ def Field.toSyntax : Field Struct → Syntax
| first::rest => stx.setArg 0 <| mkNullNode #[first.toSyntax true, mkNullNode <| rest.toArray.map (FieldLHS.toSyntax false) ]
| _ => unreachable!
/-- Creates a view of a field left-hand side. -/
private def toFieldLHS (stx : Syntax) : MacroM FieldLHS :=
if stx.getKind == ``Lean.Parser.Term.structInstArrayRef then
return FieldLHS.modifyOp stx stx[1]
@@ -355,11 +431,16 @@ private def toFieldLHS (stx : Syntax) : MacroM FieldLHS :=
| some idx => return FieldLHS.fieldIndex stx idx
| none => Macro.throwError "unexpected structure syntax"
private def mkStructView (stx : Syntax) (structName : Name) (source : Source) : MacroM Struct := do
/--
Creates a structure instance view from structure instance notation
and the computed structure name (from `Lean.Elab.Term.StructInst.getStructName`)
and structure source view (from `Lean.Elab.Term.StructInst.getStructSources`).
-/
private def mkStructView (stx : Syntax) (structName : Name) (sources : SourcesView) : MacroM StructInstView := do
/- Recall that `stx` is of the form
```
leading_parser "{" >> optional (atomic (sepBy1 termParser ", " >> " with "))
>> sepByIndent (structInstFieldAbbrev <|> structInstField) ...
>> structInstFields (sepByIndent (structInstFieldAbbrev <|> structInstField) ...)
>> optional ".."
>> optional (" : " >> termParser)
>> " }"
@@ -367,28 +448,22 @@ private def mkStructView (stx : Syntax) (structName : Name) (source : Source) :
This method assumes that `structInstFieldAbbrev` had already been expanded.
-/
let fields stx[2].getSepArgs.toList.mapM fun fieldStx => do
let fields stx[2][0].getSepArgs.toList.mapM fun fieldStx => do
let val := fieldStx[2]
let first toFieldLHS fieldStx[0][0]
let rest fieldStx[0][1].getArgs.toList.mapM toFieldLHS
return { ref := fieldStx, lhs := first :: rest, val := FieldVal.term val : Field Struct }
return stx, structName, #[], fields, source
return { ref := fieldStx, lhs := first :: rest, val := FieldVal.term val : Field StructInstView }
return { ref := stx, structName, params := #[], fields, sources }
def Struct.modifyFieldsM {m : Type Type} [Monad m] (s : Struct) (f : Fields m Fields) : m Struct :=
def StructInstView.modifyFieldsM {m : Type Type} [Monad m] (s : StructInstView) (f : Fields m Fields) : m StructInstView :=
match s with
| ref, structName, params, fields, source => return ref, structName, params, ( f fields), source
| { ref, structName, params, fields, sources } => return { ref, structName, params, fields := ( f fields), sources }
def Struct.modifyFields (s : Struct) (f : Fields Fields) : Struct :=
def StructInstView.modifyFields (s : StructInstView) (f : Fields Fields) : StructInstView :=
Id.run <| s.modifyFieldsM f
def Struct.setFields (s : Struct) (fields : Fields) : Struct :=
s.modifyFields fun _ => fields
def Struct.setParams (s : Struct) (ps : Array (Name × Expr)) : Struct :=
match s with
| ref, structName, _, fields, source => ref, structName, ps, fields, source
private def expandCompositeFields (s : Struct) : Struct :=
/-- Expands name field LHSs with multi-component names into multi-component LHSs. -/
private def expandCompositeFields (s : StructInstView) : StructInstView :=
s.modifyFields fun fields => fields.map fun field => match field with
| { lhs := .fieldName _ (.str Name.anonymous ..) :: _, .. } => field
| { lhs := .fieldName ref n@(.str ..) :: rest, .. } =>
@@ -396,7 +471,8 @@ private def expandCompositeFields (s : Struct) : Struct :=
{ field with lhs := newEntries ++ rest }
| _ => field
private def expandNumLitFields (s : Struct) : TermElabM Struct :=
/-- Replaces numeric index field LHSs with the corresponding named field, or throws an error if no such field exists. -/
private def expandNumLitFields (s : StructInstView) : TermElabM StructInstView :=
s.modifyFieldsM fun fields => do
let env getEnv
let fieldNames := getStructureFields env s.structName
@@ -407,28 +483,31 @@ private def expandNumLitFields (s : Struct) : TermElabM Struct :=
else return { field with lhs := .fieldName ref fieldNames[idx - 1]! :: rest }
| _ => return field
/-- For example, consider the following structures:
```
structure A where
x : Nat
/--
Expands fields that are actually represented as fields of subobject fields.
structure B extends A where
y : Nat
For example, consider the following structures:
```
structure A where
x : Nat
structure C extends B where
z : Bool
```
This method expands parent structure fields using the path to the parent structure.
For example,
```
{ x := 0, y := 0, z := true : C }
```
is expanded into
```
{ toB.toA.x := 0, toB.y := 0, z := true : C }
```
structure B extends A where
y : Nat
structure C extends B where
z : Bool
```
This method expands parent structure fields using the path to the parent structure.
For example,
```
{ x := 0, y := 0, z := true : C }
```
is expanded into
```
{ toB.toA.x := 0, toB.y := 0, z := true : C }
```
-/
private def expandParentFields (s : Struct) : TermElabM Struct := do
private def expandParentFields (s : StructInstView) : TermElabM StructInstView := do
let env getEnv
s.modifyFieldsM fun fields => fields.mapM fun field => do match field with
| { lhs := .fieldName ref fieldName :: _, .. } =>
@@ -448,6 +527,11 @@ private def expandParentFields (s : Struct) : TermElabM Struct := do
private abbrev FieldMap := Std.HashMap Name Fields
/--
Creates a hash map collecting all fields with the same first name component.
Throws an error if there are multiple simple fields with the same name.
Used by `StructInst.expandStruct` processing.
-/
private def mkFieldMap (fields : Fields) : TermElabM FieldMap :=
fields.foldlM (init := {}) fun fieldMap field =>
match field.lhs with
@@ -461,15 +545,16 @@ private def mkFieldMap (fields : Fields) : TermElabM FieldMap :=
| _ => return fieldMap.insert fieldName [field]
| _ => unreachable!
private def isSimpleField? : Fields Option (Field Struct)
/--
Given a value of the hash map created by `mkFieldMap`, returns true if the value corresponds to a simple field.
-/
private def isSimpleField? : Fields Option (Field StructInstView)
| [field] => if field.isSimple then some field else none
| _ => none
private def getFieldIdx (structName : Name) (fieldNames : Array Name) (fieldName : Name) : TermElabM Nat := do
match fieldNames.findIdx? fun n => n == fieldName with
| some idx => return idx
| none => throwError "field '{fieldName}' is not a valid field of '{structName}'"
/--
Creates projection notation for the given structure field. Used
-/
def mkProjStx? (s : Syntax) (structName : Name) (fieldName : Name) : TermElabM (Option Syntax) := do
if (findField? ( getEnv) structName fieldName).isNone then
return none
@@ -478,7 +563,10 @@ def mkProjStx? (s : Syntax) (structName : Name) (fieldName : Name) : TermElabM (
#[mkAtomFrom s "@",
mkNode ``Parser.Term.proj #[s, mkAtomFrom s ".", mkIdentFrom s fieldName]]
def findField? (fields : Fields) (fieldName : Name) : Option (Field Struct) :=
/--
Finds a simple field of the given name.
-/
def findField? (fields : Fields) (fieldName : Name) : Option (Field StructInstView) :=
fields.find? fun field =>
match field.lhs with
| [.fieldName _ n] => n == fieldName
@@ -486,7 +574,10 @@ def findField? (fields : Fields) (fieldName : Name) : Option (Field Struct) :=
mutual
private partial def groupFields (s : Struct) : TermElabM Struct := do
/--
Groups compound fields according to which subobject they are from.
-/
private partial def groupFields (s : StructInstView) : TermElabM StructInstView := do
let env getEnv
withRef s.ref do
s.modifyFieldsM fun fields => do
@@ -499,26 +590,28 @@ mutual
let field := fields.head!
match Lean.isSubobjectField? env s.structName fieldName with
| some substructName =>
let substruct := Struct.mk s.ref substructName #[] substructFields s.source
let substruct := { ref := s.ref, structName := substructName, params := #[], fields := substructFields, sources := s.sources }
let substruct expandStruct substruct
pure { field with lhs := [field.lhs.head!], val := FieldVal.nested substruct }
| none =>
let updateSource (structStx : Syntax) : TermElabM Syntax := do
let sourcesNew s.source.explicit.filterMapM fun source => mkProjStx? source.stx source.structName fieldName
let sourcesNew s.sources.explicit.filterMapM fun source => mkProjStx? source.stx source.structName fieldName
let explicitSourceStx := if sourcesNew.isEmpty then mkNullNode else mkSourcesWithSyntax sourcesNew
let implicitSourceStx := s.source.implicit.getD mkNullNode
let implicitSourceStx := s.sources.implicit.getD mkNullNode
return (structStx.setArg 1 explicitSourceStx).setArg 3 implicitSourceStx
let valStx := s.ref -- construct substructure syntax using s.ref as template
let valStx := valStx.setArg 4 mkNullNode -- erase optional expected type
let args := substructFields.toArray.map (·.toSyntax)
let valStx := valStx.setArg 2 (mkNullNode <| mkSepArray args (mkAtom ","))
let fieldsStx := mkNode ``Parser.Term.structInstFields
#[mkNullNode <| mkSepArray args (mkAtom ",")]
let valStx := valStx.setArg 2 fieldsStx
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
private partial def addMissingFields (s : StructInstView) : TermElabM StructInstView := do
let env getEnv
let fieldNames := getStructureFields env s.structName
let ref := s.ref.mkSynthetic
@@ -527,7 +620,7 @@ mutual
match findField? s.fields fieldName with
| some field => return field::fields
| none =>
let addField (val : FieldVal Struct) : TermElabM Fields := do
let addField (val : FieldVal StructInstView) : TermElabM Fields := do
return { ref, lhs := [FieldLHS.fieldName ref fieldName], val := val } :: fields
match Lean.isSubobjectField? env s.structName fieldName with
| some substructName =>
@@ -535,8 +628,8 @@ mutual
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
let filtered := s.sources.explicit.filter fun sources =>
let sourceFields := getStructureFieldsFlattened env sources.structName false
sourceFields.any (fun name => downFields.contains name) || sourceFields.isEmpty
-- Take the first such one remaining
match filtered[0]? with
@@ -550,27 +643,30 @@ mutual
-- 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 := { ref, structName := substructName, params := #[], fields := [], sources := s.sources }
let substruct expandStruct substruct
addField (FieldVal.nested substruct)
-- No sources could provide this subobject.
-- Recurse to handle default values for fields.
| none =>
let substruct := Struct.mk ref substructName #[] [] s.source
let substruct := { ref, structName := substructName, params := #[], fields := [], sources := s.sources }
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
if let some val s.sources.explicit.findSomeM? fun source => mkProjStx? source.stx source.structName fieldName then
addField (FieldVal.term val)
else if s.source.implicit.isSome then
else if s.sources.implicit.isSome then
addField (FieldVal.term (mkHole ref))
else
addField FieldVal.default
return s.setFields fields.reverse
return { s with fields := fields.reverse }
private partial def expandStruct (s : Struct) : TermElabM Struct := do
/--
Expands all fields of the structure instance, consolidates compound fields into subobject fields, and adds missing fields.
-/
private partial def expandStruct (s : StructInstView) : TermElabM StructInstView := do
let s := expandCompositeFields s
let s expandNumLitFields s
let s expandParentFields s
@@ -579,10 +675,17 @@ mutual
end
/--
The constructor to use for the structure instance notation.
-/
structure CtorHeaderResult where
/-- The constructor function with applied structure parameters. -/
ctorFn : Expr
/-- The type of `ctorFn` -/
ctorFnType : Expr
/-- Instance metavariables for structure parameters that are instance implicit. -/
instMVars : Array MVarId
/-- Type parameter names and metavariables for each parameter. Used to seed `StructInstView.params`. -/
params : Array (Name × Expr)
private def mkCtorHeaderAux : Nat Expr Expr Array MVarId Array (Name × Expr) TermElabM CtorHeaderResult
@@ -604,6 +707,7 @@ private partial def getForallBody : Nat → Expr → Option Expr
| _+1, _ => none
| 0, type => type
/-- Attempts to use the expected type to solve for structure parameters. -/
private def propagateExpectedType (type : Expr) (numFields : Nat) (expectedType? : Option Expr) : TermElabM Unit := do
match expectedType? with
| none => return ()
@@ -614,6 +718,7 @@ private def propagateExpectedType (type : Expr) (numFields : Nat) (expectedType?
unless typeBody.hasLooseBVars do
discard <| isDefEq expectedType typeBody
/-- Elaborates the structure constructor using the expected type, filling in all structure parameters. -/
private def mkCtorHeader (ctorVal : ConstructorVal) (expectedType? : Option Expr) : TermElabM CtorHeaderResult := do
let us mkFreshLevelMVars ctorVal.levelParams.length
let val := Lean.mkConst ctorVal.name us
@@ -623,32 +728,43 @@ private def mkCtorHeader (ctorVal : ConstructorVal) (expectedType? : Option Expr
synthesizeAppInstMVars r.instMVars r.ctorFn
return r
/-- Annotates an expression that it is a value for a missing field. -/
def markDefaultMissing (e : Expr) : Expr :=
mkAnnotation `structInstDefault e
/-- If the expression has been annotated by `markDefaultMissing`, returns the unannotated expression. -/
def defaultMissing? (e : Expr) : Option Expr :=
annotation? `structInstDefault e
/-- Throws "failed to elaborate field" error. -/
def throwFailedToElabField {α} (fieldName : Name) (structName : Name) (msgData : MessageData) : TermElabM α :=
throwError "failed to elaborate field '{fieldName}' of '{structName}, {msgData}"
def trySynthStructInstance? (s : Struct) (expectedType : Expr) : TermElabM (Option Expr) := do
/-- If the struct has all-missing fields, tries to synthesize the structure using typeclass inference. -/
def trySynthStructInstance? (s : StructInstView) (expectedType : Expr) : TermElabM (Option Expr) := do
if !s.allDefault then
return none
else
try synthInstance? expectedType catch _ => return none
/-- The result of elaborating a `StructInstView` structure instance view. -/
structure ElabStructResult where
/-- The elaborated value. -/
val : Expr
struct : Struct
/-- The modified `StructInstView` view after elaboration. -/
struct : StructInstView
/-- Metavariables for instance implicit fields. These will be registered after default value propagation. -/
instMVars : Array MVarId
private partial def elabStruct (s : Struct) (expectedType? : Option Expr) : TermElabM ElabStructResult := withRef s.ref do
/--
Main elaborator for structure instances.
-/
private partial def elabStructInstView (s : StructInstView) (expectedType? : Option Expr) : TermElabM ElabStructResult := withRef s.ref do
let env getEnv
let ctorVal := getStructureCtor env s.structName
if isPrivateNameFromImportedModule env ctorVal.name then
throwError "invalid \{...} notation, constructor for `{s.structName}` is marked as private"
-- We store the parameters at the resulting `Struct`. We use this information during default value propagation.
-- We store the parameters at the resulting `StructInstView`. We use this information during default value propagation.
let { ctorFn, ctorFnType, params, .. } mkCtorHeader ctorVal expectedType?
let (e, _, fields, instMVars) s.fields.foldlM (init := (ctorFn, ctorFnType, [], #[])) fun (e, type, fields, instMVars) field => do
match field.lhs with
@@ -657,7 +773,7 @@ private partial def elabStruct (s : Struct) (expectedType? : Option Expr) : Term
trace[Elab.struct] "elabStruct {field}, {type}"
match type with
| .forallE _ d b bi =>
let cont (val : Expr) (field : Field Struct) (instMVars := instMVars) : TermElabM (Expr × Expr × Fields × Array MVarId) := do
let cont (val : Expr) (field : Field StructInstView) (instMVars := instMVars) : TermElabM (Expr × Expr × Fields × Array MVarId) := do
pushInfoTree <| InfoTree.node (children := {}) <| Info.ofFieldInfo {
projName := s.structName.append fieldName, fieldName, lctx := ( getLCtx), val, stx := ref }
let e := mkApp e val
@@ -671,7 +787,7 @@ private partial def elabStruct (s : Struct) (expectedType? : Option Expr) : Term
match ( trySynthStructInstance? s d) with
| some val => cont val { field with val := FieldVal.term (mkHole field.ref) }
| none =>
let { val, struct := sNew, instMVars := instMVarsNew } elabStruct s (some d)
let { val, struct := sNew, instMVars := instMVarsNew } elabStructInstView s (some d)
let val ensureHasType d val
cont val { field with val := FieldVal.nested sNew } (instMVars ++ instMVarsNew)
| .default =>
@@ -700,17 +816,21 @@ private partial def elabStruct (s : Struct) (expectedType? : Option Expr) : Term
cont (markDefaultMissing val) field
| _ => withRef field.ref <| throwFailedToElabField fieldName s.structName m!"unexpected constructor type{indentExpr type}"
| _ => throwErrorAt field.ref "unexpected unexpanded structure field"
return { val := e, struct := s.setFields fields.reverse |>.setParams params, instMVars }
return { val := e, struct := { s with fields := fields.reverse, params }, instMVars }
namespace DefaultFields
/--
Context for default value propagation.
-/
structure Context where
-- We must search for default values overridden in derived structures
structs : Array Struct := #[]
/-- The current path through `.nested` subobject structures. We must search for default values overridden in derived structures. -/
structs : Array StructInstView := #[]
/-- The collection of structures that could provide a default value. -/
allStructNames : Array Name := #[]
/--
Consider the following example:
```
```lean
structure A where
x : Nat := 1
@@ -736,22 +856,29 @@ structure Context where
-/
maxDistance : Nat := 0
/--
State for default value propagation
-/
structure State where
/-- Whether progress has been made so far on this round of the propagation loop. -/
progress : Bool := false
partial def collectStructNames (struct : Struct) (names : Array Name) : Array Name :=
/-- Collects all structures that may provide default values for fields. -/
partial def collectStructNames (struct : StructInstView) (names : Array Name) : Array Name :=
let names := names.push struct.structName
struct.fields.foldl (init := names) fun names field =>
match field.val with
| .nested struct => collectStructNames struct names
| _ => names
partial def getHierarchyDepth (struct : Struct) : Nat :=
/-- Gets the maximum nesting depth of subobjects. -/
partial def getHierarchyDepth (struct : StructInstView) : Nat :=
struct.fields.foldl (init := 0) fun max field =>
match field.val with
| .nested struct => Nat.max max (getHierarchyDepth struct + 1)
| _ => max
/-- Returns whether the field is still missing. -/
def isDefaultMissing? [Monad m] [MonadMCtx m] (field : Field Struct) : m Bool := do
if let some expr := field.expr? then
if let some (.mvar mvarId) := defaultMissing? expr then
@@ -759,40 +886,51 @@ def isDefaultMissing? [Monad m] [MonadMCtx m] (field : Field Struct) : m Bool :=
return true
return false
partial def findDefaultMissing? [Monad m] [MonadMCtx m] (struct : Struct) : m (Option (Field Struct)) :=
/-- Returns a field that is still missing. -/
partial def findDefaultMissing? [Monad m] [MonadMCtx m] (struct : StructInstView) : m (Option (Field StructInstView)) :=
struct.fields.findSomeM? fun field => do
match field.val with
| .nested struct => findDefaultMissing? struct
| _ => return if ( isDefaultMissing? field) then field else none
partial def allDefaultMissing [Monad m] [MonadMCtx m] (struct : Struct) : m (Array (Field Struct)) :=
/-- Returns all fields that are still missing. -/
partial def allDefaultMissing [Monad m] [MonadMCtx m] (struct : StructInstView) : m (Array (Field StructInstView)) :=
go struct *> get |>.run' #[]
where
go (struct : Struct) : StateT (Array (Field Struct)) m Unit :=
go (struct : StructInstView) : StateT (Array (Field StructInstView)) m Unit :=
for field in struct.fields do
if let .nested struct := field.val then
go struct
else if ( isDefaultMissing? field) then
modify (·.push field)
def getFieldName (field : Field Struct) : Name :=
/-- Returns the name of the field. Assumes all fields under consideration are simple and named. -/
def getFieldName (field : Field StructInstView) : Name :=
match field.lhs with
| [.fieldName _ fieldName] => fieldName
| _ => unreachable!
abbrev M := ReaderT Context (StateRefT State TermElabM)
/-- Returns whether we should interrupt the round because we have made progress allowing nonzero depth. -/
def isRoundDone : M Bool := do
return ( get).progress && ( read).maxDistance > 0
def getFieldValue? (struct : Struct) (fieldName : Name) : Option Expr :=
/-- Returns the `expr?` for the given field. -/
def getFieldValue? (struct : StructInstView) (fieldName : Name) : Option Expr :=
struct.fields.findSome? fun field =>
if getFieldName field == fieldName then
field.expr?
else
none
partial def mkDefaultValueAux? (struct : Struct) : Expr TermElabM (Option Expr)
/-- Instantiates a default value from the given default value declaration, if applicable. -/
partial def mkDefaultValue? (struct : StructInstView) (cinfo : ConstantInfo) : TermElabM (Option Expr) :=
withRef struct.ref do
let us mkFreshLevelMVarsFor cinfo
process ( instantiateValueLevelParams cinfo us)
where
process : Expr TermElabM (Option Expr)
| .lam n d b c => withRef struct.ref do
if c.isExplicit then
let fieldName := n
@@ -801,29 +939,26 @@ partial def mkDefaultValueAux? (struct : Struct) : Expr → TermElabM (Option Ex
| some val =>
let valType inferType val
if ( isDefEq valType d) then
mkDefaultValueAux? struct (b.instantiate1 val)
process (b.instantiate1 val)
else
return none
else
if let some (_, param) := struct.params.find? fun (paramName, _) => paramName == n then
-- Recall that we did not use to have support for parameter propagation here.
if ( isDefEq ( inferType param) d) then
mkDefaultValueAux? struct (b.instantiate1 param)
process (b.instantiate1 param)
else
return none
else
let arg mkFreshExprMVar d
mkDefaultValueAux? struct (b.instantiate1 arg)
process (b.instantiate1 arg)
| e =>
let_expr id _ a := e | return some e
return some a
def mkDefaultValue? (struct : Struct) (cinfo : ConstantInfo) : TermElabM (Option Expr) :=
withRef struct.ref do
let us mkFreshLevelMVarsFor cinfo
mkDefaultValueAux? struct ( instantiateValueLevelParams cinfo us)
/-- Reduce default value. It performs beta reduction and projections of the given structures. -/
/--
Reduces a default value. It performs beta reduction and projections of the given structures to reduce them to the provided values for fields.
-/
partial def reduce (structNames : Array Name) (e : Expr) : MetaM Expr := do
match e with
| .forallE .. =>
@@ -880,7 +1015,10 @@ where
else
k
partial def tryToSynthesizeDefault (structs : Array Struct) (allStructNames : Array Name) (maxDistance : Nat) (fieldName : Name) (mvarId : MVarId) : TermElabM Bool :=
/--
Attempts to synthesize a default value for a missing field `fieldName` using default values from each structure in `structs`.
-/
def tryToSynthesizeDefault (structs : Array StructInstView) (allStructNames : Array Name) (maxDistance : Nat) (fieldName : Name) (mvarId : MVarId) : TermElabM Bool :=
let rec loop (i : Nat) (dist : Nat) := do
if dist > maxDistance then
return false
@@ -900,14 +1038,25 @@ partial def tryToSynthesizeDefault (structs : Array Struct) (allStructNames : Ar
| none =>
let mvarDecl getMVarDecl mvarId
let val ensureHasType mvarDecl.type val
mvarId.assign val
return true
/-
We must use `checkedAssign` here to ensure we do not create a cyclic
assignment. See #3150.
This can happen when there are holes in the the fields the default value
depends on.
Possible improvement: create a new `_` instead of returning `false` when
`checkedAssign` fails. Reason: the field will not be needed after the
other `_` are resolved by the user.
-/
mvarId.checkedAssign val
| _ => loop (i+1) dist
else
return false
loop 0 0
partial def step (struct : Struct) : M Unit :=
/--
Performs one step of default value synthesis.
-/
partial def step (struct : StructInstView) : M Unit :=
unless ( isRoundDone) do
withReader (fun ctx => { ctx with structs := ctx.structs.push struct }) do
for field in struct.fields do
@@ -924,7 +1073,10 @@ partial def step (struct : Struct) : M Unit :=
modify fun _ => { progress := true }
| _ => pure ()
partial def propagateLoop (hierarchyDepth : Nat) (d : Nat) (struct : Struct) : M Unit := do
/--
Main entry point to default value synthesis in the `M` monad.
-/
partial def propagateLoop (hierarchyDepth : Nat) (d : Nat) (struct : StructInstView) : M Unit := do
match ( findDefaultMissing? struct) with
| none => return () -- Done
| some field =>
@@ -947,16 +1099,22 @@ partial def propagateLoop (hierarchyDepth : Nat) (d : Nat) (struct : Struct) : M
else
propagateLoop hierarchyDepth (d+1) struct
def propagate (struct : Struct) : TermElabM Unit :=
/--
Synthesizes default values for all missing fields, if possible.
-/
def propagate (struct : StructInstView) : TermElabM Unit :=
let hierarchyDepth := getHierarchyDepth struct
let structNames := collectStructNames struct #[]
propagateLoop hierarchyDepth 0 struct { allStructNames := structNames } |>.run' {}
end DefaultFields
private def elabStructInstAux (stx : Syntax) (expectedType? : Option Expr) (source : Source) : TermElabM Expr := do
let structName getStructName expectedType? source
let struct liftMacroM <| mkStructView stx structName source
/--
Main entry point to elaborator for structure instance notation, unless the structure instance is a modifyOp.
-/
private def elabStructInstAux (stx : Syntax) (expectedType? : Option Expr) (sources : SourcesView) : TermElabM Expr := do
let structName getStructName expectedType? sources
let struct liftMacroM <| mkStructView stx structName sources
let struct expandStruct struct
trace[Elab.struct] "{struct}"
/- We try to synthesize pending problems with `withSynthesize` combinator before trying to use default values.
@@ -974,7 +1132,7 @@ private def elabStructInstAux (stx : Syntax) (expectedType? : Option Expr) (sour
TODO: investigate whether this design decision may have unintended side effects or produce confusing behavior.
-/
let { val := r, struct, instMVars } withSynthesize (postpone := .yes) <| elabStruct struct expectedType?
let { val := r, struct, instMVars } withSynthesize (postpone := .yes) <| elabStructInstView struct expectedType?
trace[Elab.struct] "before propagate {r}"
DefaultFields.propagate struct
synthesizeAppInstMVars instMVars r
@@ -984,13 +1142,13 @@ private def elabStructInstAux (stx : Syntax) (expectedType? : Option Expr) (sour
match ( expandNonAtomicExplicitSources stx) with
| some stxNew => withMacroExpansion stx stxNew <| elabTerm stxNew expectedType?
| none =>
let sourceView getStructSource stx
let sourcesView getStructSources stx
if let some modifyOp isModifyOp? stx then
if sourceView.explicit.isEmpty then
if sourcesView.explicit.isEmpty then
throwError "invalid \{...} notation, explicit source is required when using '[<index>] := <value>'"
elabModifyOp stx modifyOp sourceView.explicit expectedType?
elabModifyOp stx modifyOp sourcesView.explicit expectedType?
else
elabStructInstAux stx expectedType? sourceView
elabStructInstAux stx expectedType? sourcesView
builtin_initialize
registerTraceClass `Elab.struct

View File

@@ -236,8 +236,9 @@ where
-- Pretty-printing instructions shouldn't affect validity
let s := s.trim
!s.isEmpty &&
(s.front != '\'' || s == "''") &&
(s.front != '\'' || "''".isPrefixOf s) &&
s.front != '\"' &&
!(isIdBeginEscape s.front) &&
!(s.front == '`' && (s.endPos == ⟨1⟩ || isIdFirst (s.get ⟨1⟩) || isIdBeginEscape (s.get ⟨1⟩))) &&
!s.front.isDigit &&
!(s.any Char.isWhitespace)

View File

@@ -13,6 +13,31 @@ open Meta
# Implementation of the `change` tactic
-/
/--
Elaborates the pattern `p` and ensures that it is defeq to `e`.
Emulates `(show p from ?m : e)`, returning the type of `?m`, but `e` and `p` do not need to be types.
Unlike `(show p from ?m : e)`, this can assign synthetic opaque metavariables appearing in `p`.
-/
def elabChange (e : Expr) (p : Term) : TacticM Expr := do
let p runTermElab do
let p Term.elabTermEnsuringType p ( inferType e)
unless isDefEq p e do
/-
Sometimes isDefEq can fail due to postponed elaboration problems.
We synthesize pending synthetic mvars while allowing typeclass instances to be postponed,
which might enable solving for them with an additional `isDefEq`.
-/
Term.synthesizeSyntheticMVars (postpone := .partial)
discard <| isDefEq p e
pure p
withAssignableSyntheticOpaque do
unless isDefEq p e do
let (p, tgt) addPPExplicitToExposeDiff p e
throwError "\
'change' tactic failed, pattern{indentExpr p}\n\
is not definitionally equal to target{indentExpr tgt}"
instantiateMVars p
/-- `change` can be used to replace the main goal or its hypotheses with
different, yet definitionally equal, goal or hypotheses.
@@ -38,15 +63,13 @@ the main goal. -/
| `(tactic| change $newType:term $[$loc:location]?) => do
withLocation (expandOptLocation (Lean.mkOptionalNode loc))
(atLocal := fun h => do
let hTy h.getType
-- This is a hack to get the new type to elaborate in the same sort of way that
-- it would for a `show` expression for the goal.
let mvar mkFreshExprMVar none
let (_, mvars) elabTermWithHoles
( `(term | show $newType from $( Term.exprToSyntax mvar))) hTy `change
let (hTy', mvars) withCollectingNewGoalsFrom (elabChange ( h.getType) newType) ( getMainTag) `change
liftMetaTactic fun mvarId => do
return ( mvarId.changeLocalDecl h ( inferType mvar)) :: mvars)
(atTarget := evalTactic <| `(tactic| refine_lift show $newType from ?_))
(failed := fun _ => throwError "change tactic failed")
return ( mvarId.changeLocalDecl h hTy') :: mvars)
(atTarget := do
let (tgt', mvars) withCollectingNewGoalsFrom (elabChange ( getMainTarget) newType) ( getMainTag) `change
liftMetaTactic fun mvarId => do
return ( mvarId.replaceTargetDefEq tgt') :: mvars)
(failed := fun _ => throwError "'change' tactic failed")
end Lean.Elab.Tactic

View File

@@ -114,6 +114,13 @@ private def elabConfig (recover : Bool) (structName : Name) (items : Array Confi
let e Term.withSynthesize <| Term.elabTermEnsuringType stx (mkConst structName)
instantiateMVars e
section
-- We automatically disable the following option for `macro`s but the subsequent `def` both contains
-- a quotation and is called only by `macro`s, so we disable the option for it manually. Note that
-- we can't use `in` as it is parsed as a single command and so the option would not influence the
-- parser.
set_option internal.parseQuotWithCurrentStage false
private def mkConfigElaborator
(doc? : Option (TSyntax ``Parser.Command.docComment)) (elabName type monadName : Ident)
(adapt recover : Term) : MacroM (TSyntax `command) := do
@@ -148,6 +155,8 @@ private def mkConfigElaborator
throwError msg
go)
end
/-!
`declare_config_elab elabName TypeName` declares a function `elabName : Syntax → TacticM TypeName`
that elaborates a tactic configuration.

View File

@@ -5,6 +5,7 @@ Authors: Leonardo de Moura
-/
prelude
import Lean.Elab.Tactic.ElabTerm
import Lean.Elab.Tactic.Change
import Lean.Elab.Tactic.Conv.Basic
namespace Lean.Elab.Tactic.Conv
@@ -15,11 +16,9 @@ open Meta
| `(conv| change $e) => withMainContext do
let lhs getLhs
let mvarCounterSaved := ( getMCtx).mvarCounter
let r elabTermEnsuringType e ( inferType lhs)
logUnassignedAndAbort ( filterOldMVars ( getMVars r) mvarCounterSaved)
unless ( isDefEqGuarded r lhs) do
throwError "invalid 'change' conv tactic, term{indentExpr r}\nis not definitionally equal to current left-hand-side{indentExpr lhs}"
changeLhs r
let lhs' elabChange lhs e
logUnassignedAndAbort ( filterOldMVars ( getMVars lhs') mvarCounterSaved)
changeLhs lhs'
| _ => throwUnsupportedSyntax
end Lean.Elab.Tactic.Conv

View File

@@ -18,21 +18,22 @@ private def mkKey (e : Expr) (simp : Bool) : MetaM (Array Key) := do
let (_, _, type) withReducible <| forallMetaTelescopeReducing e
let type whnfR type
if simp then
if let some (_, lhs, _) := type.eq? then
mkPath lhs simpDtConfig
else if let some (lhs, _) := type.iff? then
mkPath lhs simpDtConfig
else if let some (_, lhs, _) := type.ne? then
mkPath lhs simpDtConfig
else if let some p := type.not? then
match p.eq? with
| some (_, lhs, _) =>
mkPath lhs simpDtConfig
| _ => mkPath p simpDtConfig
else
mkPath type simpDtConfig
withSimpGlobalConfig do
if let some (_, lhs, _) := type.eq? then
mkPath lhs
else if let some (lhs, _) := type.iff? then
mkPath lhs
else if let some (_, lhs, _) := type.ne? then
mkPath lhs
else if let some p := type.not? then
match p.eq? with
| some (_, lhs, _) =>
mkPath lhs
| _ => mkPath p
else
mkPath type
else
mkPath type {}
mkPath type
private def getType (t : TSyntax `term) : TermElabM Expr := do
if let `($id:ident) := t then

View File

@@ -542,11 +542,6 @@ declare_config_elab elabDecideConfig Parser.Tactic.DecideConfig
let cfg elabDecideConfig stx[1]
evalDecideCore `decide cfg
@[builtin_tactic Lean.Parser.Tactic.decideBang] def evalDecideBang : Tactic := fun stx => do
let cfg elabDecideConfig stx[1]
let cfg := { cfg with kernel := true }
evalDecideCore `decide! cfg
@[builtin_tactic Lean.Parser.Tactic.nativeDecide] def evalNativeDecide : Tactic := fun stx => do
let cfg elabDecideConfig stx[1]
let cfg := { cfg with native := true }

View File

@@ -195,9 +195,6 @@ structure ExtTheorems where
erased : PHashSet Name := {}
deriving Inhabited
/-- Discrimation tree settings for the `ext` extension. -/
def extExt.config : WhnfCoreConfig := {}
/-- The environment extension to track `@[ext]` theorems. -/
builtin_initialize extExtension :
SimpleScopedEnvExtension ExtTheorem ExtTheorems
@@ -211,7 +208,7 @@ builtin_initialize extExtension :
ordered from high priority to low. -/
@[inline] def getExtTheorems (ty : Expr) : MetaM (Array ExtTheorem) := do
let extTheorems := extExtension.getState ( getEnv)
let arr extTheorems.tree.getMatch ty extExt.config
let arr extTheorems.tree.getMatch ty
let erasedArr := arr.filter fun thm => !extTheorems.erased.contains thm.declName
-- Using insertion sort because it is stable and the list of matches should be mostly sorted.
-- Most ext theorems have default priority.
@@ -258,7 +255,7 @@ builtin_initialize registerBuiltinAttribute {
but this theorem proves{indentD declTy}"
let some (ty, lhs, rhs) := declTy.eq? | failNotEq
unless lhs.isMVar && rhs.isMVar do failNotEq
let keys withReducible <| DiscrTree.mkPath ty extExt.config
let keys withReducible <| DiscrTree.mkPath ty
let priority liftCommandElabM <| Elab.liftMacroM do evalPrio (prio.getD ( `(prio| default)))
extExtension.add {declName, keys, priority} kind
-- Realize iff theorem

View File

@@ -40,7 +40,7 @@ def exact? (ref : Syntax) (required : Option (Array (TSyntax `term))) (requireCl
| some suggestions =>
if requireClose then throwError
"`exact?` could not close the goal. Try `apply?` to see partial suggestions."
reportOutOfHeartbeats `library_search ref
reportOutOfHeartbeats `apply? ref
for (_, suggestionMCtx) in suggestions do
withMCtx suggestionMCtx do
addExactSuggestion ref ( instantiateMVars (mkMVar mvar)).headBeta (addSubgoalsMsg := true)

View File

@@ -91,7 +91,7 @@ def elabSimpConfig (optConfig : Syntax) (kind : SimpKind) : TacticM Meta.Simp.Co
| .simpAll => return ( elabSimpConfigCtxCore optConfig).toConfig
| .dsimp => return { ( elabDSimpConfigCore optConfig) with }
private def addDeclToUnfoldOrTheorem (thms : SimpTheorems) (id : Origin) (e : Expr) (post : Bool) (inv : Bool) (kind : SimpKind) : MetaM SimpTheorems := do
private def addDeclToUnfoldOrTheorem (config : Meta.ConfigWithKey) (thms : SimpTheorems) (id : Origin) (e : Expr) (post : Bool) (inv : Bool) (kind : SimpKind) : MetaM SimpTheorems := do
if e.isConst then
let declName := e.constName!
let info getConstInfo declName
@@ -108,7 +108,7 @@ private def addDeclToUnfoldOrTheorem (thms : SimpTheorems) (id : Origin) (e : Ex
let fvarId := e.fvarId!
let decl fvarId.getDecl
if ( isProp decl.type) then
thms.add id #[] e (post := post) (inv := inv)
thms.add id #[] e (post := post) (inv := inv) (config := config)
else if !decl.isLet then
throwError "invalid argument, variable is not a proposition or let-declaration"
else if inv then
@@ -116,9 +116,9 @@ private def addDeclToUnfoldOrTheorem (thms : SimpTheorems) (id : Origin) (e : Ex
else
return thms.addLetDeclToUnfold fvarId
else
thms.add id #[] e (post := post) (inv := inv)
thms.add id #[] e (post := post) (inv := inv) (config := config)
private def addSimpTheorem (thms : SimpTheorems) (id : Origin) (stx : Syntax) (post : Bool) (inv : Bool) : TermElabM SimpTheorems := do
private def addSimpTheorem (config : Meta.ConfigWithKey) (thms : SimpTheorems) (id : Origin) (stx : Syntax) (post : Bool) (inv : Bool) : TermElabM SimpTheorems := do
let thm? Term.withoutModifyingElabMetaStateWithInfo <| withRef stx do
let e Term.elabTerm stx none
Term.synthesizeSyntheticMVars (postpone := .no) (ignoreStuckTC := true)
@@ -132,7 +132,7 @@ private def addSimpTheorem (thms : SimpTheorems) (id : Origin) (stx : Syntax) (p
else
return some (#[], e)
if let some (levelParams, proof) := thm? then
thms.add id levelParams proof (post := post) (inv := inv)
thms.add id levelParams proof (post := post) (inv := inv) (config := config)
else
return thms
@@ -212,7 +212,7 @@ def elabSimpArgs (stx : Syntax) (ctx : Simp.Context) (simprocs : Simp.SimprocsAr
match ( resolveSimpIdTheorem? term) with
| .expr e =>
let name mkFreshId
thms addDeclToUnfoldOrTheorem thms (.stx name arg) e post inv kind
thms addDeclToUnfoldOrTheorem ctx.indexConfig thms (.stx name arg) e post inv kind
| .simproc declName =>
simprocs simprocs.add declName post
| .ext (some ext₁) (some ext₂) _ =>
@@ -224,7 +224,7 @@ def elabSimpArgs (stx : Syntax) (ctx : Simp.Context) (simprocs : Simp.SimprocsAr
simprocs := simprocs.push ( ext₂.getSimprocs)
| .none =>
let name mkFreshId
thms addSimpTheorem thms (.stx name arg) term post inv
thms addSimpTheorem ctx.indexConfig thms (.stx name arg) term post inv
else if arg.getKind == ``Lean.Parser.Tactic.simpStar then
starArg := true
else
@@ -329,7 +329,7 @@ def mkSimpContext (stx : Syntax) (eraseLocal : Bool) (kind := SimpKind.simp)
let hs getPropHyps
for h in hs do
unless simpTheorems.isErased (.fvar h) do
simpTheorems simpTheorems.addTheorem (.fvar h) ( h.getDecl).toExpr
simpTheorems simpTheorems.addTheorem (.fvar h) ( h.getDecl).toExpr (config := ctx.indexConfig)
let ctx := ctx.setSimpTheorems simpTheorems
return { ctx, simprocs, dischargeWrapper }

View File

@@ -25,7 +25,7 @@ def elabSimprocPattern (stx : Syntax) : MetaM Expr := do
def elabSimprocKeys (stx : Syntax) : MetaM (Array Meta.SimpTheoremKey) := do
let pattern elabSimprocPattern stx
DiscrTree.mkPath pattern simpDtConfig
withSimpGlobalConfig <| DiscrTree.mkPath pattern
def checkSimprocType (declName : Name) : CoreM Bool := do
let decl getConstInfo declName

View File

@@ -1298,13 +1298,20 @@ def isTacticOrPostponedHole? (e : Expr) : TermElabM (Option MVarId) := do
| _ => return none
| _ => pure none
def mkTermInfo (elaborator : Name) (stx : Syntax) (e : Expr) (expectedType? : Option Expr := none) (lctx? : Option LocalContext := none) (isBinder := false) : TermElabM (Sum Info MVarId) := do
def mkTermInfo (elaborator : Name) (stx : Syntax) (e : Expr) (expectedType? : Option Expr := none)
(lctx? : Option LocalContext := none) (isBinder := false) :
TermElabM (Sum Info MVarId) := do
match ( isTacticOrPostponedHole? e) with
| some mvarId => return Sum.inr mvarId
| none =>
let e := removeSaveInfoAnnotation e
return Sum.inl <| Info.ofTermInfo { elaborator, lctx := lctx?.getD ( getLCtx), expr := e, stx, expectedType?, isBinder }
def mkPartialTermInfo (elaborator : Name) (stx : Syntax) (expectedType? : Option Expr := none)
(lctx? : Option LocalContext := none) :
TermElabM Info := do
return Info.ofPartialTermInfo { elaborator, lctx := lctx?.getD ( getLCtx), stx, expectedType? }
/--
Pushes a new leaf node to the info tree associating the expression `e` to the syntax `stx`.
As a result, when the user hovers over `stx` they will see the type of `e`, and if `e`
@@ -1326,41 +1333,54 @@ def addTermInfo (stx : Syntax) (e : Expr) (expectedType? : Option Expr := none)
if ( read).inPattern && !force then
return mkPatternWithRef e stx
else
withInfoContext' (pure ()) (fun _ => mkTermInfo elaborator stx e expectedType? lctx? isBinder) |> discard
discard <| withInfoContext'
(pure ())
(fun _ => mkTermInfo elaborator stx e expectedType? lctx? isBinder)
(mkPartialTermInfo elaborator stx expectedType? lctx?)
return e
def addTermInfo' (stx : Syntax) (e : Expr) (expectedType? : Option Expr := none) (lctx? : Option LocalContext := none) (elaborator := Name.anonymous) (isBinder := false) : TermElabM Unit :=
discard <| addTermInfo stx e expectedType? lctx? elaborator isBinder
def withInfoContext' (stx : Syntax) (x : TermElabM Expr) (mkInfo : Expr TermElabM (Sum Info MVarId)) : TermElabM Expr := do
def withInfoContext' (stx : Syntax) (x : TermElabM Expr)
(mkInfo : Expr TermElabM (Sum Info MVarId)) (mkInfoOnError : TermElabM Info) :
TermElabM Expr := do
if ( read).inPattern then
let e x
return mkPatternWithRef e stx
else
Elab.withInfoContext' x mkInfo
Elab.withInfoContext' x mkInfo mkInfoOnError
/-- Info node capturing `def/let rec` bodies, used by the unused variables linter. -/
structure BodyInfo where
/-- The body as a fully elaborated term. -/
value : Expr
/-- The body as a fully elaborated term. `none` if the body failed to elaborate. -/
value? : Option Expr
deriving TypeName
/-- Creates an `Info.ofCustomInfo` node backed by a `BodyInfo`. -/
def mkBodyInfo (stx : Syntax) (value : Expr) : Info :=
.ofCustomInfo { stx, value := .mk { value : BodyInfo } }
def mkBodyInfo (stx : Syntax) (value? : Option Expr) : Info :=
.ofCustomInfo { stx, value := .mk { value? : BodyInfo } }
/-- Extracts a `BodyInfo` custom info. -/
def getBodyInfo? : Info Option BodyInfo
| .ofCustomInfo { value, .. } => value.get? BodyInfo
| _ => none
def withTermInfoContext' (elaborator : Name) (stx : Syntax) (x : TermElabM Expr)
(expectedType? : Option Expr := none) (lctx? : Option LocalContext := none)
(isBinder : Bool := false) :
TermElabM Expr :=
withInfoContext' stx x
(mkTermInfo elaborator stx (expectedType? := expectedType?) (lctx? := lctx?) (isBinder := isBinder))
(mkPartialTermInfo elaborator stx (expectedType? := expectedType?) (lctx? := lctx?))
/--
Postpone the elaboration of `stx`, return a metavariable that acts as a placeholder, and
ensures the info tree is updated and a hole id is introduced.
When `stx` is elaborated, new info nodes are created and attached to the new hole id in the info tree.
-/
def postponeElabTerm (stx : Syntax) (expectedType? : Option Expr) : TermElabM Expr := do
withInfoContext' stx (mkInfo := mkTermInfo .anonymous (expectedType? := expectedType?) stx) do
withTermInfoContext' .anonymous stx (expectedType? := expectedType?) do
postponeElabTermCore stx expectedType?
/--
@@ -1372,7 +1392,7 @@ private def elabUsingElabFnsAux (s : SavedState) (stx : Syntax) (expectedType? :
| (elabFn::elabFns) =>
try
-- record elaborator in info tree, but only when not backtracking to other elaborators (outer `try`)
withInfoContext' stx (mkInfo := mkTermInfo elabFn.declName (expectedType? := expectedType?) stx)
withTermInfoContext' elabFn.declName stx (expectedType? := expectedType?)
(try
elabFn.value stx expectedType?
catch ex => match ex with
@@ -1755,7 +1775,7 @@ private partial def elabTermAux (expectedType? : Option Expr) (catchExPostpone :
let result match ( liftMacroM (expandMacroImpl? env stx)) with
| some (decl, stxNew?) =>
let stxNew liftMacroM <| liftExcept stxNew?
withInfoContext' stx (mkInfo := mkTermInfo decl (expectedType? := expectedType?) stx) <|
withTermInfoContext' decl stx (expectedType? := expectedType?) <|
withMacroExpansion stx stxNew <|
withRef stxNew <|
elabTermAux expectedType? catchExPostpone implicitLambda stxNew

View File

@@ -599,17 +599,20 @@ def geq (u v : Level) : Bool :=
where
go (u v : Level) : Bool :=
u == v ||
let k := fun () =>
match v with
| imax v₁ v₂ => go u v₁ && go u v₂
| _ =>
let v' := v.getLevelOffset
(u.getLevelOffset == v' || v'.isZero)
&& u.getOffset v.getOffset
match u, v with
| _, zero => true
| u, max v₁ v₂ => go u v₁ && go u v₂
| max u₁ u₂, v => go u₁ v || go u₂ v
| u, imax v₁ v₂ => go u v && go u v
| imax _ u₂, v => go u v
| succ u, succ v => go u v
| _, _ =>
let v' := v.getLevelOffset
(u.getLevelOffset == v' || v'.isZero)
&& u.getOffset v.getOffset
| _, zero => true
| u, max v₁ v₂ => go u v₁ && go u v₂
| max u₁ u₂, v => go u₁ v || go u₂ v || k ()
| imax _ u₂, v => go u v
| succ u, succ v => go u v
| _, _ => k ()
termination_by (u, v)
end Level

View File

@@ -393,16 +393,17 @@ where
| .ofCustomInfo ti =>
if !linter.unusedVariables.analyzeTactics.get ci.options then
if let some bodyInfo := ti.value.get? Elab.Term.BodyInfo then
-- the body is the only `Expr` we will analyze in this case
-- NOTE: we include it even if no tactics are present as at least for parameters we want
-- to lint only truly unused binders
let (e, _) := instantiateMVarsCore ci.mctx bodyInfo.value
modify fun s => { s with
assignments := s.assignments.push (.insert {} .anonymous e) }
let tacticsPresent := children.any (·.findInfo? (· matches .ofTacticInfo ..) |>.isSome)
withReader (· || tacticsPresent) do
go children.toArray ci
return false
if let some value := bodyInfo.value? then
-- the body is the only `Expr` we will analyze in this case
-- NOTE: we include it even if no tactics are present as at least for parameters we want
-- to lint only truly unused binders
let (e, _) := instantiateMVarsCore ci.mctx value
modify fun s => { s with
assignments := s.assignments.push (.insert {} .anonymous e) }
let tacticsPresent := children.any (·.findInfo? (· matches .ofTacticInfo ..) |>.isSome)
withReader (· || tacticsPresent) do
go children.toArray ci
return false
| .ofTermInfo ti =>
if ignored then return true
match ti.expr with

View File

@@ -32,6 +32,9 @@ inductive ReduceMode where
| reduceSimpleOnly
| none
private def config : ConfigWithKey :=
{ transparency := .reducible, iota := false, proj := .no : Config }.toConfigWithKey
mutual
/--
@@ -61,8 +64,8 @@ where
-- Drawback: cost.
return e
else match mode with
| .reduce => DiscrTree.reduce e {}
| .reduceSimpleOnly => DiscrTree.reduce e { iota := false, proj := .no }
| .reduce => DiscrTree.reduce e
| .reduceSimpleOnly => withConfigWithKey config <| DiscrTree.reduce e
| .none => return e
lt (a b : Expr) : MetaM Bool := do

View File

@@ -196,13 +196,13 @@ where
let packedArg := Unary.pack packedDomain args
return e.beta #[packedArg]
| [n] => do
withLocalDecl n .default domain fun x => do
withLocalDeclD n domain fun x => do
let dummy := Expr.const ``Unit []
mkLambdaFVars #[x] ( go packedDomain dummy (args.push x) [])
| n :: ns =>
match_expr domain with
| PSigma a b =>
withLocalDecl n .default a fun x => do
withLocalDeclD n a fun x => do
mkLambdaFVars #[x] ( go packedDomain (b.beta #[x]) (args.push x) ns)
| _ => throwError "curryPSigma: Expected PSigma type, got {domain}"
@@ -319,7 +319,7 @@ def uncurryType (types : Array Expr) : MetaM Expr := do
unless type.isForall do
throwError "Mutual.uncurryType: Expected forall type, got {type}"
let domain packType (types.map (·.bindingDomain!))
withLocalDeclD `x domain fun x => do
withLocalDeclD ( mkFreshUserName `x) domain fun x => do
let codomain Mutual.mkCodomain types x
mkForallFVars #[x] codomain
@@ -485,13 +485,14 @@ projects to the `i`th function of type,
-/
def curryProj (argsPacker : ArgsPacker) (e : Expr) (i : Nat) : MetaM Expr := do
let n := argsPacker.numFuncs
let packedDomain := ( inferType e).bindingDomain!
let t inferType e
let packedDomain := t.bindingDomain!
let unaryTypes Mutual.unpackType n packedDomain
unless i < unaryTypes.length do
throwError "curryProj: index out of range"
let unaryType := unaryTypes[i]!
-- unary : (x : a ⊗ b) → e[inl x]
let unary withLocalDecl `x .default unaryType fun x => do
let unary withLocalDeclD t.bindingName! unaryType fun x => do
let packedArg Mutual.pack unaryTypes.length packedDomain i x
mkLambdaFVars #[x] (e.beta #[packedArg])
-- nary : (x : a) → (y : b) → e[inl (x,y)]

View File

@@ -27,6 +27,51 @@ namespace Lean.Meta
builtin_initialize isDefEqStuckExceptionId : InternalExceptionId registerInternalExceptionId `isDefEqStuck
def TransparencyMode.toUInt64 : TransparencyMode UInt64
| .all => 0
| .default => 1
| .reducible => 2
| .instances => 3
def EtaStructMode.toUInt64 : EtaStructMode UInt64
| .all => 0
| .notClasses => 1
| .none => 2
/--
Configuration for projection reduction. See `whnfCore`.
-/
inductive ProjReductionKind where
/-- Projections `s.i` are not reduced at `whnfCore`. -/
| no
/--
Projections `s.i` are reduced at `whnfCore`, and `whnfCore` is used at `s` during the process.
Recall that `whnfCore` does not perform `delta` reduction (i.e., it will not unfold constant declarations).
-/
| yes
/--
Projections `s.i` are reduced at `whnfCore`, and `whnf` is used at `s` during the process.
Recall that `whnfCore` does not perform `delta` reduction (i.e., it will not unfold constant declarations), but `whnf` does.
-/
| yesWithDelta
/--
Projections `s.i` are reduced at `whnfCore`, and `whnfAtMostI` is used at `s` during the process.
Recall that `whnfAtMostI` is like `whnf` but uses transparency at most `instances`.
This option is stronger than `yes`, but weaker than `yesWithDelta`.
We use this option to ensure we reduce projections to prevent expensive defeq checks when unifying TC operations.
When unifying e.g. `(@Field.toNeg α inst1).1 =?= (@Field.toNeg α inst2).1`,
we only want to unify negation (and not all other field operations as well).
Unifying the field instances slowed down unification: https://github.com/leanprover/lean4/issues/1986
-/
| yesWithDeltaI
deriving DecidableEq, Inhabited, Repr
def ProjReductionKind.toUInt64 : ProjReductionKind UInt64
| .no => 0
| .yes => 1
| .yesWithDelta => 2
| .yesWithDeltaI => 3
/--
Configuration flags for the `MetaM` monad.
Many of them are used to control the `isDefEq` function that checks whether two terms are definitionally equal or not.
@@ -118,9 +163,64 @@ structure Config where
- `max u w =?= mav u ?v` is solved with `?v := w` ignoring the solution `?v := max u w`
-/
univApprox : Bool := true
/-- If `true`, reduce recursor/matcher applications, e.g., `Nat.rec true (fun _ _ => false) Nat.zero` reduces to `true` -/
iota : Bool := true
/-- If `true`, reduce terms such as `(fun x => t[x]) a` into `t[a]` -/
beta : Bool := true
/-- Control projection reduction at `whnfCore`. -/
proj : ProjReductionKind := .yesWithDelta
/--
Zeta reduction: `let x := v; e[x]` reduces to `e[v]`.
We say a let-declaration `let x := v; e` is non dependent if it is equivalent to `(fun x => e) v`.
Recall that
```
fun x : BitVec 5 => let n := 5; fun y : BitVec n => x = y
```
is type correct, but
```
fun x : BitVec 5 => (fun n => fun y : BitVec n => x = y) 5
```
is not.
-/
zeta : Bool := true
/--
Zeta-delta reduction: given a local context containing entry `x : t := e`, free variable `x` reduces to `e`.
-/
zetaDelta : Bool := true
deriving Inhabited
/-- Convert `isDefEq` and `WHNF` relevant parts into a key for caching results -/
private def Config.toKey (c : Config) : UInt64 :=
c.transparency.toUInt64 |||
(c.foApprox.toUInt64 <<< 2) |||
(c.ctxApprox.toUInt64 <<< 3) |||
(c.quasiPatternApprox.toUInt64 <<< 4) |||
(c.constApprox.toUInt64 <<< 5) |||
(c.isDefEqStuckEx.toUInt64 <<< 6) |||
(c.unificationHints.toUInt64 <<< 7) |||
(c.proofIrrelevance.toUInt64 <<< 8) |||
(c.assignSyntheticOpaque.toUInt64 <<< 9) |||
(c.offsetCnstrs.toUInt64 <<< 10) |||
(c.iota.toUInt64 <<< 11) |||
(c.beta.toUInt64 <<< 12) |||
(c.zeta.toUInt64 <<< 13) |||
(c.zetaDelta.toUInt64 <<< 14) |||
(c.univApprox.toUInt64 <<< 15) |||
(c.etaStruct.toUInt64 <<< 16) |||
(c.proj.toUInt64 <<< 18)
/-- Configuration with key produced by `Config.toKey`. -/
structure ConfigWithKey where
private mk ::
config : Config
key : UInt64
deriving Inhabited
def Config.toConfigWithKey (c : Config) : ConfigWithKey :=
{ config := c, key := c.toKey }
/--
Function parameter information cache.
Function parameter information cache.
-/
structure ParamInfo where
/-- The binder annotation for the parameter. -/
@@ -178,7 +278,6 @@ def ParamInfo.isStrictImplicit (p : ParamInfo) : Bool :=
def ParamInfo.isExplicit (p : ParamInfo) : Bool :=
p.binderInfo == BinderInfo.default
/--
Function information cache. See `ParamInfo`.
-/
@@ -192,11 +291,12 @@ structure FunInfo where
resultDeps : Array Nat := #[]
/--
Key for the function information cache.
Key for the function information cache.
-/
structure InfoCacheKey where
/-- The transparency mode used to compute the `FunInfo`. -/
transparency : TransparencyMode
private mk ::
/-- key produced using `Config.toKey`. -/
configKey : UInt64
/-- The function being cached information about. It is quite often an `Expr.const`. -/
expr : Expr
/--
@@ -207,11 +307,10 @@ structure InfoCacheKey where
nargs? : Option Nat
deriving Inhabited, BEq
namespace InfoCacheKey
instance : Hashable InfoCacheKey :=
fun transparency, expr, nargs => mixHash (hash transparency) <| mixHash (hash expr) (hash nargs)
end InfoCacheKey
instance : Hashable InfoCacheKey where
hash := fun { configKey, expr, nargs? } => mixHash (hash configKey) <| mixHash (hash expr) (hash nargs?)
-- Remark: we don't need to store `Config.toKey` because typeclass resolution uses a fixed configuration.
structure SynthInstanceCacheKey where
localInsts : LocalInstances
type : Expr
@@ -231,38 +330,50 @@ structure AbstractMVarsResult where
abbrev SynthInstanceCache := PersistentHashMap SynthInstanceCacheKey (Option AbstractMVarsResult)
abbrev InferTypeCache := PersistentExprStructMap Expr
-- Key for `InferType` and `WHNF` caches
structure ExprConfigCacheKey where
private mk ::
expr : Expr
configKey : UInt64
deriving Inhabited
instance : BEq ExprConfigCacheKey where
beq a b :=
Expr.equal a.expr b.expr &&
a.configKey == b.configKey
instance : Hashable ExprConfigCacheKey where
hash := fun { expr, configKey } => mixHash (hash expr) (hash configKey)
abbrev InferTypeCache := PersistentHashMap ExprConfigCacheKey Expr
abbrev FunInfoCache := PersistentHashMap InfoCacheKey FunInfo
abbrev WhnfCache := PersistentExprStructMap Expr
abbrev WhnfCache := PersistentHashMap ExprConfigCacheKey Expr
structure DefEqCacheKey where
private mk ::
lhs : Expr
rhs : Expr
configKey : UInt64
deriving Inhabited, BEq
instance : Hashable DefEqCacheKey where
hash := fun { lhs, rhs, configKey } => mixHash (hash lhs) <| mixHash (hash rhs) (hash configKey)
/--
A mapping `(s, t) ↦ isDefEq s t` per transparency level.
TODO: consider more efficient representations (e.g., a proper set) and caching policies (e.g., imperfect cache).
We should also investigate the impact on memory consumption. -/
structure DefEqCache where
reducible : PersistentHashMap (Expr × Expr) Bool := {}
instances : PersistentHashMap (Expr × Expr) Bool := {}
default : PersistentHashMap (Expr × Expr) Bool := {}
all : PersistentHashMap (Expr × Expr) Bool := {}
deriving Inhabited
/--
A cache for `inferType` at transparency levels `.default` an `.all`.
A mapping `(s, t) ↦ isDefEq s t`.
TODO: consider more efficient representations (e.g., a proper set) and caching policies (e.g., imperfect cache).
We should also investigate the impact on memory consumption.
-/
structure InferTypeCaches where
default : InferTypeCache
all : InferTypeCache
deriving Inhabited
abbrev DefEqCache := PersistentHashMap DefEqCacheKey Bool
/--
Cache datastructures for type inference, type class resolution, whnf, and definitional equality.
Cache datastructures for type inference, type class resolution, whnf, and definitional equality.
-/
structure Cache where
inferType : InferTypeCaches := {}, {}
inferType : InferTypeCache := {}
funInfo : FunInfoCache := {}
synthInstance : SynthInstanceCache := {}
whnfDefault : WhnfCache := {} -- cache for closed terms and `TransparencyMode.default`
whnfAll : WhnfCache := {} -- cache for closed terms and `TransparencyMode.all`
whnf : WhnfCache := {}
defEqTrans : DefEqCache := {} -- transient cache for terms containing mvars or using nonstandard configuration options, it is frequently reset.
defEqPerm : DefEqCache := {} -- permanent cache for terms not containing mvars and using standard configuration options
deriving Inhabited
@@ -333,6 +444,7 @@ register_builtin_option maxSynthPendingDepth : Nat := {
-/
structure Context where
private config : Config := {}
private configKey : UInt64 := config.toKey
/-- Local context -/
lctx : LocalContext := {}
/-- Local instances in `lctx`. -/
@@ -483,17 +595,27 @@ variable [MonadControlT MetaM n] [Monad n]
@[inline] def modifyCache (f : Cache Cache) : MetaM Unit :=
modify fun { mctx, cache, zetaDeltaFVarIds, postponed, diag } => { mctx, cache := f cache, zetaDeltaFVarIds, postponed, diag }
@[inline] def modifyInferTypeCacheDefault (f : InferTypeCache InferTypeCache) : MetaM Unit :=
modifyCache fun icd, ica, c1, c2, c3, c4, c5, c6 => f icd, ica, c1, c2, c3, c4, c5, c6
@[inline] def modifyInferTypeCacheAll (f : InferTypeCache InferTypeCache) : MetaM Unit :=
modifyCache fun icd, ica, c1, c2, c3, c4, c5, c6 => icd, f ica, c1, c2, c3, c4, c5, c6
@[inline] def modifyInferTypeCache (f : InferTypeCache InferTypeCache) : MetaM Unit :=
modifyCache fun ic, c1, c2, c3, c4, c5 => f ic, c1, c2, c3, c4, c5
@[inline] def modifyDefEqTransientCache (f : DefEqCache DefEqCache) : MetaM Unit :=
modifyCache fun c1, c2, c3, c4, c5, defeqTrans, c6 => c1, c2, c3, c4, c5, f defeqTrans, c6
modifyCache fun c1, c2, c3, c4, defeqTrans, c5 => c1, c2, c3, c4, f defeqTrans, c5
@[inline] def modifyDefEqPermCache (f : DefEqCache DefEqCache) : MetaM Unit :=
modifyCache fun c1, c2, c3, c4, c5, c6, defeqPerm => c1, c2, c3, c4, c5, c6, f defeqPerm
modifyCache fun c1, c2, c3, c4, c5, defeqPerm => c1, c2, c3, c4, c5, f defeqPerm
def mkExprConfigCacheKey (expr : Expr) : MetaM ExprConfigCacheKey :=
return { expr, configKey := ( read).configKey }
def mkDefEqCacheKey (lhs rhs : Expr) : MetaM DefEqCacheKey := do
let configKey := ( read).configKey
if Expr.quickLt lhs rhs then
return { lhs, rhs, configKey }
else
return { lhs := rhs, rhs := lhs, configKey }
def mkInfoCacheKey (expr : Expr) (nargs? : Option Nat) : MetaM InfoCacheKey :=
return { expr, nargs?, configKey := ( read).configKey }
@[inline] def resetDefEqPermCaches : MetaM Unit :=
modifyDefEqPermCache fun _ => {}
@@ -538,6 +660,9 @@ def getLocalInstances : MetaM LocalInstances :=
def getConfig : MetaM Config :=
return ( read).config
def getConfigWithKey : MetaM ConfigWithKey :=
return ( getConfig).toConfigWithKey
def resetZetaDeltaFVarIds : MetaM Unit :=
modify fun s => { s with zetaDeltaFVarIds := {} }
@@ -941,7 +1066,16 @@ def elimMVarDeps (xs : Array Expr) (e : Expr) (preserveOrder : Bool := false) :
/-- `withConfig f x` executes `x` using the updated configuration object obtained by applying `f`. -/
@[inline] def withConfig (f : Config Config) : n α n α :=
mapMetaM <| withReader (fun ctx => { ctx with config := f ctx.config })
mapMetaM <| withReader fun ctx =>
let config := f ctx.config
let configKey := config.toKey
{ ctx with config, configKey }
@[inline] def withConfigWithKey (c : ConfigWithKey) : n α n α :=
mapMetaM <| withReader fun ctx =>
let config := c.config
let configKey := c.key
{ ctx with config, configKey }
@[inline] def withCanUnfoldPred (p : Config ConstantInfo CoreM Bool) : n α n α :=
mapMetaM <| withReader (fun ctx => { ctx with canUnfold? := p })
@@ -961,8 +1095,15 @@ Executes `x` tracking zetaDelta reductions `Config.trackZetaDelta := true`
@[inline] def withoutProofIrrelevance (x : n α) : n α :=
withConfig (fun cfg => { cfg with proofIrrelevance := false }) x
@[inline] private def Context.setTransparency (ctx : Context) (transparency : TransparencyMode) : Context :=
let config := { ctx.config with transparency }
-- Recall that `transparency` is stored in the first 2 bits
let configKey : UInt64 := ((ctx.configKey >>> (2 : UInt64)) <<< 2) ||| transparency.toUInt64
{ ctx with config, configKey }
@[inline] def withTransparency (mode : TransparencyMode) : n α n α :=
withConfig (fun config => { config with transparency := mode })
-- We avoid `withConfig` for performance reasons.
mapMetaM <| withReader (·.setTransparency mode)
/-- `withDefault x` executes `x` using the default transparency setting. -/
@[inline] def withDefault (x : n α) : n α :=
@@ -983,13 +1124,10 @@ or type class instances are unfolded.
Execute `x` ensuring the transparency setting is at least `mode`.
Recall that `.all > .default > .instances > .reducible`.
-/
@[inline] def withAtLeastTransparency (mode : TransparencyMode) (x : n α) : n α :=
withConfig
(fun config =>
let oldMode := config.transparency
let mode := if oldMode.lt mode then mode else oldMode
{ config with transparency := mode })
x
@[inline] def withAtLeastTransparency (mode : TransparencyMode) : n α n α :=
mapMetaM <| withReader fun ctx =>
let modeOld := ctx.config.transparency
ctx.setTransparency <| if modeOld.lt mode then mode else modeOld
/-- Execute `x` allowing `isDefEq` to assign synthetic opaque metavariables. -/
@[inline] def withAssignableSyntheticOpaque (x : n α) : n α :=
@@ -1011,8 +1149,8 @@ def getTheoremInfo (info : ConstantInfo) : MetaM (Option ConstantInfo) := do
private def getDefInfoTemp (info : ConstantInfo) : MetaM (Option ConstantInfo) := do
match ( getTransparency) with
| TransparencyMode.all => return some info
| TransparencyMode.default => return some info
| .all => return some info
| .default => return some info
| _ =>
if ( isReducible info.name) then
return some info

View File

@@ -91,7 +91,15 @@ private partial def mkKey (e : Expr) : CanonM UInt64 := do
let eNew instantiateMVars e
unless eNew == e do
return ( mkKey eNew)
let info getFunInfo f
let info if f.hasLooseBVars then
-- If `f` has loose bound variables, `getFunInfo` will fail.
-- This can only happen if `f` contains local variables.
-- Instead we use an empty `FunInfo`, which results in the
-- `i < info.paramInfo.size` check below failing for all indices,
-- and hence mixing in the hash for all arguments.
pure {}
else
getFunInfo f
let mut k mkKey f
for i in [:e.getAppNumArgs] do
if h : i < info.paramInfo.size then
@@ -101,10 +109,13 @@ private partial def mkKey (e : Expr) : CanonM UInt64 := do
else
k := mixHash k ( mkKey (e.getArg! i))
return k
| .lam _ t b _
| .forallE _ t b _ =>
| .lam n t b bi
| .forallE n t b bi =>
-- Note that we do not use `withLocalDecl` here, for performance reasons.
-- Instead we have a guard for loose bound variables in the `.app` case above.
return mixHash ( mkKey t) ( mkKey b)
| .letE _ _ v b _ =>
| .letE n t v b _ =>
-- Similarly, we do not use `withLetDecl` here.
return mixHash ( mkKey v) ( mkKey b)
| .proj _ i s =>
return mixHash i.toUInt64 ( mkKey s)
@@ -124,11 +135,11 @@ def canon (e : Expr) : CanonM Expr := do
if ( isDefEq e e') then
return e'
-- `e` is not definitionally equal to any expression in `es'`. We claim this should be rare.
unsafe modify fun { cache, keyToExprs } => { cache, keyToExprs := keyToExprs.insert k (e :: es') }
modify fun { cache, keyToExprs } => { cache, keyToExprs := keyToExprs.insert k (e :: es') }
return e
else
-- `e` is the first expression we found with key `k`.
unsafe modify fun { cache, keyToExprs } => { cache, keyToExprs := keyToExprs.insert k [e] }
modify fun { cache, keyToExprs } => { cache, keyToExprs := keyToExprs.insert k [e] }
return e
end Canonicalizer

View File

@@ -305,16 +305,13 @@ def hasNoindexAnnotation (e : Expr) : Bool :=
/--
Reduction procedure for the discrimination tree indexing.
The parameter `config` controls how aggressively the term is reduced.
The parameter at type `DiscrTree` controls this value.
See comment at `DiscrTree`.
-/
partial def reduce (e : Expr) (config : WhnfCoreConfig) : MetaM Expr := do
let e whnfCore e config
partial def reduce (e : Expr) : MetaM Expr := do
let e whnfCore e
match ( unfoldDefinition? e) with
| some e => reduce e config
| some e => reduce e
| none => match e.etaExpandedStrict? with
| some e => reduce e config
| some e => reduce e
| none => return e
/--
@@ -333,24 +330,24 @@ private def isBadKey (fn : Expr) : Bool :=
| _ => true
/--
Reduce `e` until we get an irreducible term (modulo current reducibility setting) or the resulting term
is a bad key (see comment at `isBadKey`).
We use this method instead of `reduce` for root terms at `pushArgs`. -/
private partial def reduceUntilBadKey (e : Expr) (config : WhnfCoreConfig) : MetaM Expr := do
Reduce `e` until we get an irreducible term (modulo current reducibility setting) or the resulting term
is a bad key (see comment at `isBadKey`).
We use this method instead of `reduce` for root terms at `pushArgs`. -/
private partial def reduceUntilBadKey (e : Expr) : MetaM Expr := do
let e step e
match e.etaExpandedStrict? with
| some e => reduceUntilBadKey e config
| some e => reduceUntilBadKey e
| none => return e
where
step (e : Expr) := do
let e whnfCore e config
let e whnfCore e
match ( unfoldDefinition? e) with
| some e' => if isBadKey e'.getAppFn then return e else step e'
| none => return e
/-- whnf for the discrimination tree module -/
def reduceDT (e : Expr) (root : Bool) (config : WhnfCoreConfig) : MetaM Expr :=
if root then reduceUntilBadKey e config else reduce e config
def reduceDT (e : Expr) (root : Bool) : MetaM Expr :=
if root then reduceUntilBadKey e else reduce e
/- Remark: we use `shouldAddAsStar` only for nested terms, and `root == false` for nested terms -/
@@ -372,11 +369,11 @@ In this issue, we have a local hypotheses `(h : ∀ p : α × β, f p p.2 = p.2)
For example, it was introduced by another tactic. Thus, when populating the discrimination tree explicit arguments provided to `simp` (e.g., `simp [h]`),
we use `noIndexAtArgs := true`. See comment: https://github.com/leanprover/lean4/issues/2670#issuecomment-1758889365
-/
private def pushArgs (root : Bool) (todo : Array Expr) (e : Expr) (config : WhnfCoreConfig) (noIndexAtArgs : Bool) : MetaM (Key × Array Expr) := do
private def pushArgs (root : Bool) (todo : Array Expr) (e : Expr) (noIndexAtArgs : Bool) : MetaM (Key × Array Expr) := do
if hasNoindexAnnotation e then
return (.star, todo)
else
let e reduceDT e root config
let e reduceDT e root
let fn := e.getAppFn
let push (k : Key) (nargs : Nat) (todo : Array Expr): MetaM (Key × Array Expr) := do
let info getFunInfoNArgs fn nargs
@@ -422,23 +419,23 @@ private def pushArgs (root : Bool) (todo : Array Expr) (e : Expr) (config : Whnf
| _ => return (.other, todo)
@[inherit_doc pushArgs]
partial def mkPathAux (root : Bool) (todo : Array Expr) (keys : Array Key) (config : WhnfCoreConfig) (noIndexAtArgs : Bool) : MetaM (Array Key) := do
partial def mkPathAux (root : Bool) (todo : Array Expr) (keys : Array Key) (noIndexAtArgs : Bool) : MetaM (Array Key) := do
if todo.isEmpty then
return keys
else
let e := todo.back!
let todo := todo.pop
let (k, todo) pushArgs root todo e config noIndexAtArgs
mkPathAux false todo (keys.push k) config noIndexAtArgs
let (k, todo) pushArgs root todo e noIndexAtArgs
mkPathAux false todo (keys.push k) noIndexAtArgs
private def initCapacity := 8
@[inherit_doc pushArgs]
def mkPath (e : Expr) (config : WhnfCoreConfig) (noIndexAtArgs := false) : MetaM (Array Key) := do
def mkPath (e : Expr) (noIndexAtArgs := false) : MetaM (Array Key) := do
withReducible do
let todo : Array Expr := .mkEmpty initCapacity
let keys : Array Key := .mkEmpty initCapacity
mkPathAux (root := true) (todo.push e) keys config noIndexAtArgs
mkPathAux (root := true) (todo.push e) keys noIndexAtArgs
private partial def createNodes (keys : Array Key) (v : α) (i : Nat) : Trie α :=
if h : i < keys.size then
@@ -492,23 +489,23 @@ def insertCore [BEq α] (d : DiscrTree α) (keys : Array Key) (v : α) : DiscrTr
let c := insertAux keys v 1 c
{ root := d.root.insert k c }
def insert [BEq α] (d : DiscrTree α) (e : Expr) (v : α) (config : WhnfCoreConfig) (noIndexAtArgs := false) : MetaM (DiscrTree α) := do
let keys mkPath e config noIndexAtArgs
def insert [BEq α] (d : DiscrTree α) (e : Expr) (v : α) (noIndexAtArgs := false) : MetaM (DiscrTree α) := do
let keys mkPath e noIndexAtArgs
return d.insertCore keys v
/--
Inserts a value into a discrimination tree,
but only if its key is not of the form `#[*]` or `#[=, *, *, *]`.
-/
def insertIfSpecific [BEq α] (d : DiscrTree α) (e : Expr) (v : α) (config : WhnfCoreConfig) (noIndexAtArgs := false) : MetaM (DiscrTree α) := do
let keys mkPath e config noIndexAtArgs
def insertIfSpecific [BEq α] (d : DiscrTree α) (e : Expr) (v : α) (noIndexAtArgs := false) : MetaM (DiscrTree α) := do
let keys mkPath e noIndexAtArgs
return if keys == #[Key.star] || keys == #[Key.const `Eq 3, Key.star, Key.star, Key.star] then
d
else
d.insertCore keys v
private def getKeyArgs (e : Expr) (isMatch root : Bool) (config : WhnfCoreConfig) : MetaM (Key × Array Expr) := do
let e reduceDT e root config
private def getKeyArgs (e : Expr) (isMatch root : Bool) : MetaM (Key × Array Expr) := do
let e reduceDT e root
unless root do
-- See pushArgs
if let some v := toNatLit? e then
@@ -580,11 +577,11 @@ private def getKeyArgs (e : Expr) (isMatch root : Bool) (config : WhnfCoreConfig
| .forallE _ d _ _ => return (.arrow, #[d])
| _ => return (.other, #[])
private abbrev getMatchKeyArgs (e : Expr) (root : Bool) (config : WhnfCoreConfig) : MetaM (Key × Array Expr) :=
getKeyArgs e (isMatch := true) (root := root) (config := config)
private abbrev getMatchKeyArgs (e : Expr) (root : Bool) : MetaM (Key × Array Expr) :=
getKeyArgs e (isMatch := true) (root := root)
private abbrev getUnifyKeyArgs (e : Expr) (root : Bool) (config : WhnfCoreConfig) : MetaM (Key × Array Expr) :=
getKeyArgs e (isMatch := false) (root := root) (config := config)
private abbrev getUnifyKeyArgs (e : Expr) (root : Bool) : MetaM (Key × Array Expr) :=
getKeyArgs e (isMatch := false) (root := root)
private def getStarResult (d : DiscrTree α) : Array α :=
let result : Array α := .mkEmpty initCapacity
@@ -595,7 +592,7 @@ private def getStarResult (d : DiscrTree α) : Array α :=
private abbrev findKey (cs : Array (Key × Trie α)) (k : Key) : Option (Key × Trie α) :=
cs.binSearch (k, default) (fun a b => a.1 < b.1)
private partial def getMatchLoop (todo : Array Expr) (c : Trie α) (result : Array α) (config : WhnfCoreConfig) : MetaM (Array α) := do
private partial def getMatchLoop (todo : Array Expr) (c : Trie α) (result : Array α) : MetaM (Array α) := do
match c with
| .node vs cs =>
if todo.isEmpty then
@@ -606,48 +603,48 @@ private partial def getMatchLoop (todo : Array Expr) (c : Trie α) (result : Arr
let e := todo.back!
let todo := todo.pop
let first := cs[0]! /- Recall that `Key.star` is the minimal key -/
let (k, args) getMatchKeyArgs e (root := false) config
let (k, args) getMatchKeyArgs e (root := false)
/- We must always visit `Key.star` edges since they are wildcards.
Thus, `todo` is not used linearly when there is `Key.star` edge
and there is an edge for `k` and `k != Key.star`. -/
let visitStar (result : Array α) : MetaM (Array α) :=
if first.1 == .star then
getMatchLoop todo first.2 result config
getMatchLoop todo first.2 result
else
return result
let visitNonStar (k : Key) (args : Array Expr) (result : Array α) : MetaM (Array α) :=
match findKey cs k with
| none => return result
| some c => getMatchLoop (todo ++ args) c.2 result config
| some c => getMatchLoop (todo ++ args) c.2 result
let result visitStar result
match k with
| .star => return result
| _ => visitNonStar k args result
private def getMatchRoot (d : DiscrTree α) (k : Key) (args : Array Expr) (result : Array α) (config : WhnfCoreConfig) : MetaM (Array α) :=
private def getMatchRoot (d : DiscrTree α) (k : Key) (args : Array Expr) (result : Array α) : MetaM (Array α) :=
match d.root.find? k with
| none => return result
| some c => getMatchLoop args c result config
| some c => getMatchLoop args c result
private def getMatchCore (d : DiscrTree α) (e : Expr) (config : WhnfCoreConfig) : MetaM (Key × Array α) :=
private def getMatchCore (d : DiscrTree α) (e : Expr) : MetaM (Key × Array α) :=
withReducible do
let result := getStarResult d
let (k, args) getMatchKeyArgs e (root := true) config
let (k, args) getMatchKeyArgs e (root := true)
match k with
| .star => return (k, result)
| _ => return (k, ( getMatchRoot d k args result config))
| _ => return (k, ( getMatchRoot d k args result))
/--
Find values that match `e` in `d`.
-/
def getMatch (d : DiscrTree α) (e : Expr) (config : WhnfCoreConfig) : MetaM (Array α) :=
return ( getMatchCore d e config).2
def getMatch (d : DiscrTree α) (e : Expr) : MetaM (Array α) :=
return ( getMatchCore d e).2
/--
Similar to `getMatch`, but returns solutions that are prefixes of `e`.
We store the number of ignored arguments in the result.-/
partial def getMatchWithExtra (d : DiscrTree α) (e : Expr) (config : WhnfCoreConfig) : MetaM (Array (α × Nat)) := do
let (k, result) getMatchCore d e config
partial def getMatchWithExtra (d : DiscrTree α) (e : Expr) : MetaM (Array (α × Nat)) := do
let (k, result) getMatchCore d e
let result := result.map (·, 0)
if !e.isApp then
return result
@@ -669,7 +666,7 @@ where
| _ => return false
go (e : Expr) (numExtra : Nat) (result : Array (α × Nat)) : MetaM (Array (α × Nat)) := do
let result := result ++ ( getMatchCore d e config).2.map (., numExtra)
let result := result ++ ( getMatchCore d e).2.map (., numExtra)
if e.isApp then
go e.appFn! (numExtra + 1) result
else
@@ -678,8 +675,8 @@ where
/--
Return the root symbol for `e`, and the number of arguments after `reduceDT`.
-/
def getMatchKeyRootFor (e : Expr) (config : WhnfCoreConfig) : MetaM (Key × Nat) := do
let e reduceDT e (root := true) config
def getMatchKeyRootFor (e : Expr) : MetaM (Key × Nat) := do
let e reduceDT e (root := true)
let numArgs := e.getAppNumArgs
let key := match e.getAppFn with
| .lit v => .lit v
@@ -716,17 +713,17 @@ We use this method to simulate Lean 3's indexing.
The natural number in the result is the number of arguments in `e` after `reduceDT`.
-/
def getMatchLiberal (d : DiscrTree α) (e : Expr) (config : WhnfCoreConfig) : MetaM (Array α × Nat) := do
def getMatchLiberal (d : DiscrTree α) (e : Expr) : MetaM (Array α × Nat) := do
withReducible do
let result := getStarResult d
let (k, numArgs) getMatchKeyRootFor e config
let (k, numArgs) getMatchKeyRootFor e
match k with
| .star => return (result, numArgs)
| _ => return (getAllValuesForKey d k result, numArgs)
partial def getUnify (d : DiscrTree α) (e : Expr) (config : WhnfCoreConfig) : MetaM (Array α) :=
partial def getUnify (d : DiscrTree α) (e : Expr) : MetaM (Array α) :=
withReducible do
let (k, args) getUnifyKeyArgs e (root := true) config
let (k, args) getUnifyKeyArgs e (root := true)
match k with
| .star => d.root.foldlM (init := #[]) fun result k c => process k.arity #[] c result
| _ =>
@@ -750,7 +747,7 @@ where
else
let e := todo.back!
let todo := todo.pop
let (k, args) getUnifyKeyArgs e (root := false) config
let (k, args) getUnifyKeyArgs e (root := false)
let visitStar (result : Array α) : MetaM (Array α) :=
let first := cs[0]!
if first.1 == .star then

View File

@@ -1387,15 +1387,21 @@ private abbrev unfold (e : Expr) (failK : MetaM α) (successK : Expr → MetaM
/-- Auxiliary method for isDefEqDelta -/
private def unfoldBothDefEq (fn : Name) (t s : Expr) : MetaM LBool := do
match t, s with
| Expr.const _ ls₁, Expr.const _ ls₂ => isListLevelDefEq ls₁ ls₂
| Expr.app _ _, Expr.app _ _ =>
| .const _ ls₁, .const _ ls₂ =>
match ( isListLevelDefEq ls₁ ls₂) with
| .true => return .true
| _ =>
unfold t (pure .undef) fun t =>
unfold s (pure .undef) fun s =>
isDefEqLeftRight fn t s
| .app _ _, .app _ _ =>
if ( tryHeuristic t s) then
pure LBool.true
return .true
else
unfold t
(unfold s (pure LBool.undef) (fun s => isDefEqRight fn t s))
(unfold s (pure .undef) fun s => isDefEqRight fn t s)
(fun t => unfold s (isDefEqLeft fn t s) (fun s => isDefEqLeftRight fn t s))
| _, _ => pure LBool.false
| _, _ => return .false
private def sameHeadSymbol (t s : Expr) : Bool :=
match t.getAppFn, s.getAppFn with
@@ -1674,11 +1680,12 @@ private partial def isDefEqQuick (t s : Expr) : MetaM LBool :=
-- | Expr.mdata _ t _, s => isDefEqQuick t s
-- | t, Expr.mdata _ s _ => isDefEqQuick t s
| .fvar fvarId₁, .fvar fvarId₂ => do
if ( fvarId₁.isLetVar <||> fvarId₂.isLetVar) then
return LBool.undef
else if fvarId₁ == fvarId₂ then
return LBool.true
if fvarId₁ == fvarId₂ then
return .true
else if ( fvarId₁.isLetVar <||> fvarId₂.isLetVar) then
return .undef
else
-- If `t` and `s` are not proofs or let-variables, we still return `.undef` and let other rules (e.g., unit-like) kick in.
isDefEqProofIrrel t s
| t, s =>
isDefEqQuickOther t s
@@ -2079,50 +2086,37 @@ Structure for storing defeq cache key information.
-/
structure DefEqCacheKeyInfo where
kind : DefEqCacheKind
key : Expr × Expr
key : DefEqCacheKey
private def mkCacheKey (t s : Expr) : MetaM DefEqCacheKeyInfo := do
let kind getDefEqCacheKind t s
let key := if Expr.quickLt t s then (t, s) else (s, t)
let key mkDefEqCacheKey t s
return { key, kind }
private def getCachedResult (keyInfo : DefEqCacheKeyInfo) : MetaM LBool := do
let cache match keyInfo.kind with
| .transient => pure ( get).cache.defEqTrans
| .permanent => pure ( get).cache.defEqPerm
let cache := match ( getTransparency) with
| .reducible => cache.reducible
| .instances => cache.instances
| .default => cache.default
| .all => cache.all
match cache.find? keyInfo.key with
| some val => return val.toLBool
| none => return .undef
def DefEqCache.update (cache : DefEqCache) (mode : TransparencyMode) (key : Expr × Expr) (result : Bool) : DefEqCache :=
match mode with
| .reducible => { cache with reducible := cache.reducible.insert key result }
| .instances => { cache with instances := cache.instances.insert key result }
| .default => { cache with default := cache.default.insert key result }
| .all => { cache with all := cache.all.insert key result }
private def cacheResult (keyInfo : DefEqCacheKeyInfo) (result : Bool) : MetaM Unit := do
let mode getTransparency
let key := keyInfo.key
match keyInfo.kind with
| .permanent => modifyDefEqPermCache fun c => c.update mode key result
| .permanent => modifyDefEqPermCache fun c => c.insert key result
| .transient =>
/-
We must ensure that all assigned metavariables in the key are replaced by their current assignments.
Otherwise, the key is invalid after the assignment is "backtracked".
See issue #1870 for an example.
-/
let key := ( instantiateMVars key.1, instantiateMVars key.2)
modifyDefEqTransientCache fun c => c.update mode key result
let key mkDefEqCacheKey ( instantiateMVars key.lhs) ( instantiateMVars key.rhs)
modifyDefEqTransientCache fun c => c.insert key result
private def whnfCoreAtDefEq (e : Expr) : MetaM Expr := do
if backward.isDefEq.lazyWhnfCore.get ( getOptions) then
whnfCore e (config := { proj := .yesWithDeltaI })
withConfig (fun ctx => { ctx with proj := .yesWithDeltaI }) <| whnfCore e
else
whnfCore e

View File

@@ -10,13 +10,13 @@ import Lean.Meta.InferType
namespace Lean.Meta
@[inline] private def checkFunInfoCache (fn : Expr) (maxArgs? : Option Nat) (k : MetaM FunInfo) : MetaM FunInfo := do
let t getTransparency
match ( get).cache.funInfo.find? t, fn, maxArgs? with
| some finfo => pure finfo
let key mkInfoCacheKey fn maxArgs?
match ( get).cache.funInfo.find? key with
| some finfo => return finfo
| none => do
let finfo k
modify fun s => { s with cache := { s.cache with funInfo := s.cache.funInfo.insert t, fn, maxArgs? finfo } }
pure finfo
modify fun s => { s with cache := { s.cache with funInfo := s.cache.funInfo.insert key finfo } }
return finfo
@[inline] private def whenHasVar {α} (e : Expr) (deps : α) (k : α α) : α :=
if e.hasFVar then k deps else deps

View File

@@ -97,8 +97,8 @@ private def inferConstType (c : Name) (us : List Level) : MetaM Expr := do
private def inferProjType (structName : Name) (idx : Nat) (e : Expr) : MetaM Expr := do
let structType inferType e
let structType whnf structType
let failed {α} : Unit MetaM α := fun _ =>
throwError "invalid projection{indentExpr (mkProj structName idx e)} from type {structType}"
let failed {α} : Unit MetaM α := fun _ => do
throwError "invalid projection{indentExpr (mkProj structName idx e)}\nfrom type{indentExpr structType}"
matchConstStructure structType.getAppFn failed fun structVal structLvls ctorVal =>
let structTypeArgs := structType.getAppArgs
if structVal.numParams + structVal.numIndices != structTypeArgs.size then
@@ -165,24 +165,27 @@ private def inferFVarType (fvarId : FVarId) : MetaM Expr := do
| none => fvarId.throwUnknown
@[inline] private def checkInferTypeCache (e : Expr) (inferType : MetaM Expr) : MetaM Expr := do
match ( getTransparency) with
| .default =>
match ( get).cache.inferType.default.find? e with
if e.hasMVar then
inferType
else
let key mkExprConfigCacheKey e
match ( get).cache.inferType.find? key with
| some type => return type
| none =>
let type inferType
unless e.hasMVar || type.hasMVar do
modifyInferTypeCacheDefault fun c => c.insert e type
unless type.hasMVar do
modifyInferTypeCache fun c => c.insert key type
return type
| .all =>
match ( get).cache.inferType.all.find? e with
| some type => return type
| none =>
let type inferType
unless e.hasMVar || type.hasMVar do
modifyInferTypeCacheAll fun c => c.insert e type
return type
| _ => panic! "checkInferTypeCache: transparency mode not default or all"
private def defaultConfig : ConfigWithKey :=
{ : Config }.toConfigWithKey
private def allConfig : ConfigWithKey :=
{ transparency := .all : Config }.toConfigWithKey
@[inline] def withInferTypeConfig (x : MetaM α) : MetaM α := do
let cfg := if ( getTransparency) == .all then allConfig else defaultConfig
withConfigWithKey cfg x
@[export lean_infer_type]
def inferTypeImp (e : Expr) : MetaM Expr :=
@@ -201,7 +204,7 @@ def inferTypeImp (e : Expr) : MetaM Expr :=
| .forallE .. => checkInferTypeCache e (inferForallType e)
| .lam .. => checkInferTypeCache e (inferLambdaType e)
| .letE .. => checkInferTypeCache e (inferLambdaType e)
withIncRecDepth <| withAtLeastTransparency TransparencyMode.default (infer e)
withIncRecDepth <| withInferTypeConfig (infer e)
/--
Return `LBool.true` if given level is always equivalent to universe level zero.

View File

@@ -72,9 +72,6 @@ structure Instances where
erased : PHashSet Name := {}
deriving Inhabited
/-- Configuration for the discrimination tree module -/
def tcDtConfig : WhnfCoreConfig := {}
def addInstanceEntry (d : Instances) (e : InstanceEntry) : Instances :=
match e.globalName? with
| some n => { d with discrTree := d.discrTree.insertCore e.keys e, instanceNames := d.instanceNames.insert n e, erased := d.erased.erase n }
@@ -98,7 +95,7 @@ private def mkInstanceKey (e : Expr) : MetaM (Array InstanceKey) := do
let type inferType e
withNewMCtxDepth do
let (_, _, type) forallMetaTelescopeReducing type
DiscrTree.mkPath type tcDtConfig
DiscrTree.mkPath type
/--
Compute the order the arguments of `inst` should be synthesized.

View File

@@ -184,9 +184,9 @@ private def elimLooseBVarsByBeta (e : Expr) : CoreM Expr :=
else
return .continue)
private def getKeyArgs (e : Expr) (isMatch root : Bool) (config : WhnfCoreConfig) :
private def getKeyArgs (e : Expr) (isMatch root : Bool) :
MetaM (Key × Array Expr) := do
let e DiscrTree.reduceDT e root config
let e DiscrTree.reduceDT e root
unless root do
-- See pushArgs
if let some v := toNatLit? e then
@@ -259,9 +259,9 @@ private def getKeyArgs (e : Expr) (isMatch root : Bool) (config : WhnfCoreConfig
/-
Given an expression we are looking for patterns that match, return the key and sub-expressions.
-/
private abbrev getMatchKeyArgs (e : Expr) (root : Bool) (config : WhnfCoreConfig) :
private abbrev getMatchKeyArgs (e : Expr) (root : Bool) :
MetaM (Key × Array Expr) :=
getKeyArgs e (isMatch := true) (root := root) (config := config)
getKeyArgs e (isMatch := true) (root := root)
end MatchClone
@@ -313,8 +313,6 @@ discriminator key is computed and processing the remaining
terms is deferred until demanded by a match.
-/
structure LazyDiscrTree (α : Type) where
/-- Configuration for normalization. -/
config : Lean.Meta.WhnfCoreConfig := {}
/-- Backing array of trie entries. Should be owned by this trie. -/
tries : Array (LazyDiscrTree.Trie α) := #[default]
/-- Map from discriminator trie roots to the index. -/
@@ -332,12 +330,12 @@ open Lean.Meta.DiscrTree (mkNoindexAnnotation hasNoindexAnnotation reduceDT)
/--
Specialization of Lean.Meta.DiscrTree.pushArgs
-/
private def pushArgs (root : Bool) (todo : Array Expr) (e : Expr) (config : WhnfCoreConfig) :
private def pushArgs (root : Bool) (todo : Array Expr) (e : Expr) :
MetaM (Key × Array Expr) := do
if hasNoindexAnnotation e then
return (.star, todo)
else
let e reduceDT e root config
let e reduceDT e root
let fn := e.getAppFn
let push (k : Key) (nargs : Nat) (todo : Array Expr) : MetaM (Key × Array Expr) := do
let info getFunInfoNArgs fn nargs
@@ -389,8 +387,8 @@ private def initCapacity := 8
/--
Get the root key and rest of terms of an expression using the specified config.
-/
private def rootKey (cfg: WhnfCoreConfig) (e : Expr) : MetaM (Key × Array Expr) :=
pushArgs true (Array.mkEmpty initCapacity) e cfg
private def rootKey (e : Expr) : MetaM (Key × Array Expr) :=
pushArgs true (Array.mkEmpty initCapacity) e
private partial def buildPath (op : Bool Array Expr Expr MetaM (Key × Array Expr)) (root : Bool) (todo : Array Expr) (keys : Array Key) : MetaM (Array Key) := do
if todo.isEmpty then
@@ -407,9 +405,9 @@ Create a key path from an expression using the function used for patterns.
This differs from Lean.Meta.DiscrTree.mkPath and targetPath in that the expression
should uses free variables rather than meta-variables for holes.
-/
def patternPath (e : Expr) (config : WhnfCoreConfig) : MetaM (Array Key) := do
def patternPath (e : Expr) : MetaM (Array Key) := do
let todo : Array Expr := .mkEmpty initCapacity
let op root todo e := pushArgs root todo e config
let op root todo e := pushArgs root todo e
buildPath op (root := true) (todo.push e) (.mkEmpty initCapacity)
/--
@@ -417,21 +415,21 @@ Create a key path from an expression we are matching against.
This should have mvars instantiated where feasible.
-/
def targetPath (e : Expr) (config : WhnfCoreConfig) : MetaM (Array Key) := do
def targetPath (e : Expr) : MetaM (Array Key) := do
let todo : Array Expr := .mkEmpty initCapacity
let op root todo e := do
let (k, args) MatchClone.getMatchKeyArgs e root config
let (k, args) MatchClone.getMatchKeyArgs e root
pure (k, todo ++ args)
buildPath op (root := true) (todo.push e) (.mkEmpty initCapacity)
/- Monad for finding matches while resolving deferred patterns. -/
@[reducible]
private def MatchM α := ReaderT WhnfCoreConfig (StateRefT (Array (Trie α)) MetaM)
private def MatchM α := StateRefT (Array (Trie α)) MetaM
private def runMatch (d : LazyDiscrTree α) (m : MatchM α β) : MetaM (β × LazyDiscrTree α) := do
let { config := c, tries := a, roots := r } := d
let (result, a) withReducible $ (m.run c).run a
pure (result, { config := c, tries := a, roots := r})
let { tries := a, roots := r } := d
let (result, a) withReducible <| m.run a
return (result, { tries := a, roots := r})
private def setTrie (i : TrieIndex) (v : Trie α) : MatchM α Unit :=
modify (·.set! i v)
@@ -444,7 +442,7 @@ private def newTrie [Monad m] [MonadState (Array (Trie α)) m] (e : LazyEntry α
private def addLazyEntryToTrie (i:TrieIndex) (e : LazyEntry α) : MatchM α Unit :=
modify (·.modify i (·.pushPending e))
private def evalLazyEntry (config : WhnfCoreConfig)
private def evalLazyEntry
(p : Array α × TrieIndex × Std.HashMap Key TrieIndex)
(entry : LazyEntry α)
: MatchM α (Array α × TrieIndex × Std.HashMap Key TrieIndex) := do
@@ -456,7 +454,7 @@ private def evalLazyEntry (config : WhnfCoreConfig)
else
let e := todo.back!
let todo := todo.pop
let (k, todo) withLCtx lctx.1 lctx.2 $ pushArgs false todo e config
let (k, todo) withLCtx lctx.1 lctx.2 <| pushArgs false todo e
if k == .star then
if starIdx = 0 then
let starIdx newTrie (todo, lctx, v)
@@ -477,26 +475,25 @@ private def evalLazyEntry (config : WhnfCoreConfig)
This evaluates all lazy entries in a trie and updates `values`, `starIdx`, and `children`
accordingly.
-/
private partial def evalLazyEntries (config : WhnfCoreConfig)
private partial def evalLazyEntries
(values : Array α) (starIdx : TrieIndex) (children : Std.HashMap Key TrieIndex)
(entries : Array (LazyEntry α)) :
MatchM α (Array α × TrieIndex × Std.HashMap Key TrieIndex) := do
let mut values := values
let mut starIdx := starIdx
let mut children := children
entries.foldlM (init := (values, starIdx, children)) (evalLazyEntry config)
entries.foldlM (init := (values, starIdx, children)) evalLazyEntry
private def evalNode (c : TrieIndex) :
MatchM α (Array α × TrieIndex × Std.HashMap Key TrieIndex) := do
let .node vs star cs pending := (get).get! c
if pending.size = 0 then
pure (vs, star, cs)
return (vs, star, cs)
else
let config read
setTrie c default
let (vs, star, cs) evalLazyEntries config vs star cs pending
let (vs, star, cs) evalLazyEntries vs star cs pending
setTrie c <| .node vs star cs #[]
pure (vs, star, cs)
return (vs, star, cs)
def dropKeyAux (next : TrieIndex) (rest : List Key) :
MatchM α Unit :=
@@ -723,11 +720,11 @@ private def push (d : PreDiscrTree α) (k : Key) (e : LazyEntry α) : PreDiscrTr
d.modifyAt k (·.push e)
/-- Convert a pre-discrimination tree to a lazy discrimination tree. -/
private def toLazy (d : PreDiscrTree α) (config : WhnfCoreConfig := {}) : LazyDiscrTree α :=
private def toLazy (d : PreDiscrTree α) : LazyDiscrTree α :=
let { roots, tries } := d
-- Adjust trie indices so the first value is reserved (so 0 is never a valid trie index)
let roots := roots.fold (init := roots) (fun m k n => m.insert k (n+1))
{ config, roots, tries := #[default] ++ tries.map (.node {} 0 {}) }
{ roots, tries := #[default] ++ tries.map (.node {} 0 {}) }
/-- Merge two discrimination trees. -/
protected def append (x y : PreDiscrTree α) : PreDiscrTree α :=
@@ -756,12 +753,12 @@ namespace InitEntry
/--
Constructs an initial entry from an expression and value.
-/
def fromExpr (expr : Expr) (value : α) (config : WhnfCoreConfig := {}) : MetaM (InitEntry α) := do
def fromExpr (expr : Expr) (value : α) : MetaM (InitEntry α) := do
let lctx getLCtx
let linst getLocalInstances
let lctx := (lctx, linst)
let (key, todo) LazyDiscrTree.rootKey config expr
pure <| { key, entry := (todo, lctx, value) }
let (key, todo) LazyDiscrTree.rootKey expr
return { key, entry := (todo, lctx, value) }
/--
Creates an entry for a subterm of an initial entry.
@@ -769,11 +766,11 @@ Creates an entry for a subterm of an initial entry.
This is slightly more efficient than using `fromExpr` on subterms since it avoids a redundant call
to `whnf`.
-/
def mkSubEntry (e : InitEntry α) (idx : Nat) (value : α) (config : WhnfCoreConfig := {}) :
def mkSubEntry (e : InitEntry α) (idx : Nat) (value : α) :
MetaM (InitEntry α) := do
let (todo, lctx, _) := e.entry
let (key, todo) LazyDiscrTree.rootKey config todo[idx]!
pure <| { key, entry := (todo, lctx, value) }
let (key, todo) LazyDiscrTree.rootKey todo[idx]!
return { key, entry := (todo, lctx, value) }
end InitEntry

View File

@@ -207,7 +207,7 @@ def getInstances (type : Expr) : MetaM (Array Instance) := do
| none => throwError "type class instance expected{indentExpr type}"
| some className =>
let globalInstances getGlobalInstancesIndex
let result globalInstances.getUnify type tcDtConfig
let result globalInstances.getUnify type
-- Using insertion sort because it is stable and the array `result` should be mostly sorted.
-- Most instances have default priority.
let result := result.insertionSort fun e₁ e₂ => e₁.priority < e₂.priority

View File

@@ -776,7 +776,8 @@ In the type of `value`, reduces
and then wraps `value` in an appropriate type hint.
-/
def cleanPackedArgs (eqnInfo : WF.EqnInfo) (value : Expr) : MetaM Expr := do
let t Meta.transform ( inferType value) (skipConstInApp := true) (pre := fun e => do
let type inferType value
let cleanType Meta.transform type (skipConstInApp := true) (pre := fun e => do
-- Need to beta-reduce first
let e' := e.headBeta
if e' != e then
@@ -819,7 +820,7 @@ def cleanPackedArgs (eqnInfo : WF.EqnInfo) (value : Expr) : MetaM Expr := do
return .continue e'
return .continue e)
mkExpectedTypeHint value t
mkExpectedTypeHint value cleanType
/--
Takes `foo._unary.induct`, where the motive is a `PSigma`/`PSum` type and

View File

@@ -114,10 +114,11 @@ where
if lhs.isRawNatLit && rhs.isRawNatLit then cont
else
try
match ( injection mvarId fvarId newNames) with
| .solved => return .solved
| .subgoal mvarId newEqs remainingNames =>
mvarId.withContext <| go d (newEqs.toList ++ fvarIds) mvarId remainingNames
commitIfNoEx do
match ( injection mvarId fvarId newNames) with
| .solved => return .solved
| .subgoal mvarId newEqs remainingNames =>
mvarId.withContext <| go d (newEqs.toList ++ fvarIds) mvarId remainingNames
catch _ => cont
else cont

View File

@@ -33,17 +33,21 @@ def _root_.Lean.MVarId.replaceTargetEq (mvarId : MVarId) (targetNew : Expr) (eqP
return mvarNew.mvarId!
/--
Convert the given goal `Ctx |- target` into `Ctx |- targetNew`. It assumes the goals are definitionally equal.
We use the proof term
```
@id target mvarNew
```
to create a checkpoint. -/
Converts the given goal `Ctx |- target` into `Ctx |- targetNew`. It assumes the goals are definitionally equal.
We use the proof term
```
@id target mvarNew
```
to create a checkpoint.
If `targetNew` is equal to `target`, then returns `mvarId` unchanged.
Uses `Expr.equal` for the comparison so that it is possible to update binder names, etc., which are user-visible.
-/
def _root_.Lean.MVarId.replaceTargetDefEq (mvarId : MVarId) (targetNew : Expr) : MetaM MVarId :=
mvarId.withContext do
mvarId.checkNotAssigned `change
let target mvarId.getType
if target == targetNew then
if Expr.equal target targetNew then
return mvarId
else
let tag mvarId.getTag
@@ -95,12 +99,15 @@ abbrev _root_.Lean.MVarId.replaceLocalDecl (mvarId : MVarId) (fvarId : FVarId) (
replaceLocalDeclCore mvarId fvarId typeNew eqProof
/--
Replace the type of `fvarId` at `mvarId` with `typeNew`.
Replaces the type of `fvarId` at `mvarId` with `typeNew`.
Remark: this method assumes that `typeNew` is definitionally equal to the current type of `fvarId`.
If `typeNew` is equal to current type of `fvarId`, then returns `mvarId` unchanged.
Uses `Expr.equal` for the comparison so that it is possible to update binder names, etc., which are user-visible.
-/
def _root_.Lean.MVarId.replaceLocalDeclDefEq (mvarId : MVarId) (fvarId : FVarId) (typeNew : Expr) : MetaM MVarId := do
mvarId.withContext do
if typeNew == ( fvarId.getType) then
if Expr.equal typeNew ( fvarId.getType) then
return mvarId
else
let mvarDecl mvarId.getDecl

View File

@@ -19,9 +19,6 @@ namespace Lean.Meta.Rfl
open Lean Meta
/-- Discrimation tree settings for the `refl` extension. -/
def reflExt.config : WhnfCoreConfig := {}
/-- Environment extensions for `refl` lemmas -/
initialize reflExt :
SimpleScopedEnvExtension (Name × Array DiscrTree.Key) (DiscrTree Name)
@@ -42,7 +39,7 @@ initialize registerBuiltinAttribute {
if let .app (.const ``Eq [_]) _ := rel then
throwError "@[refl] attribute may not be used on `Eq.refl`."
unless withNewMCtxDepth <| isDefEq lhs rhs do fail
let key DiscrTree.mkPath rel reflExt.config
let key DiscrTree.mkPath rel
reflExt.add (decl, key) kind
}
@@ -91,7 +88,7 @@ def _root_.Lean.MVarId.applyRfl (goal : MVarId) : MetaM Unit := goal.withContext
goal.setType (.app t.appFn! lhs)
let s saveState
let mut ex? := none
for lem in (reflExt.getState ( getEnv)).getMatch rel reflExt.config do
for lem in (reflExt.getState ( getEnv)).getMatch rel do
try
let gs goal.apply ( mkConstWithFreshMVarLevels lem)
if gs.isEmpty then return () else
@@ -123,7 +120,7 @@ def _root_.Lean.MVarId.liftReflToEq (mvarId : MVarId) : MetaM MVarId := do
if rel.isAppOf `Eq then
-- No need to lift Eq to Eq
return mvarId
for lem in (reflExt.getState ( getEnv)).getMatch rel reflExt.config do
for lem in (reflExt.getState ( getEnv)).getMatch rel do
let res observing? do
-- First create an equality relating the LHS and RHS
-- and reduce the goal to proving that LHS is related to LHS.

View File

@@ -239,9 +239,10 @@ def withNewLemmas {α} (xs : Array Expr) (f : SimpM α) : SimpM α := do
withFreshCache do
let mut s getSimpTheorems
let mut updated := false
let ctx getContext
for x in xs do
if ( isProof x) then
s s.addTheorem (.fvar x.fvarId!) x
s s.addTheorem (.fvar x.fvarId!) x (config := ctx.indexConfig)
updated := true
if updated then
withSimpTheorems s f
@@ -832,7 +833,7 @@ def simpTargetStar (mvarId : MVarId) (ctx : Simp.Context) (simprocs : SimprocsAr
for h in ( getPropHyps) do
let localDecl h.getDecl
let proof := localDecl.toExpr
let simpTheorems ctx.simpTheorems.addTheorem (.fvar h) proof
let simpTheorems ctx.simpTheorems.addTheorem (.fvar h) proof (config := ctx.indexConfig)
ctx := ctx.setSimpTheorems simpTheorems
match ( simpTarget mvarId ctx simprocs discharge? (stats := stats)) with
| (none, stats) => return (TacticResultCNM.closed, stats)

View File

@@ -203,7 +203,7 @@ def rewrite? (e : Expr) (s : SimpTheoremTree) (erased : PHashSet Origin) (tag :
where
/-- For `(← getConfig).index := true`, use discrimination tree structure when collecting `simp` theorem candidates. -/
rewriteUsingIndex? : SimpM (Option Result) := do
let candidates s.getMatchWithExtra e (getDtConfig ( getConfig))
let candidates withSimpIndexConfig <| s.getMatchWithExtra e
if candidates.isEmpty then
trace[Debug.Meta.Tactic.simp] "no theorems found for {tag}-rewriting {e}"
return none
@@ -221,7 +221,7 @@ where
Only the root symbol is taken into account. Most of the structure of the discrimination tree is ignored.
-/
rewriteNoIndex? : SimpM (Option Result) := do
let (candidates, numArgs) s.getMatchLiberal e (getDtConfig ( getConfig))
let (candidates, numArgs) withSimpIndexConfig <| s.getMatchLiberal e
if candidates.isEmpty then
trace[Debug.Meta.Tactic.simp] "no theorems found for {tag}-rewriting {e}"
return none
@@ -245,7 +245,7 @@ where
diagnoseWhenNoIndex (thm : SimpTheorem) : SimpM Unit := do
if ( isDiagnosticsEnabled) then
let candidates s.getMatchWithExtra e (getDtConfig ( getConfig))
let candidates withSimpIndexConfig <| s.getMatchWithExtra e
for (candidate, _) in candidates do
if unsafe ptrEq thm candidate then
return ()

View File

@@ -42,7 +42,8 @@ private def initEntries : M Unit := do
unless simpThms.isErased (.fvar h) do
let localDecl h.getDecl
let proof := localDecl.toExpr
simpThms simpThms.addTheorem (.fvar h) proof
let ctx := ( get).ctx
simpThms simpThms.addTheorem (.fvar h) proof (config := ctx.indexConfig)
modify fun s => { s with ctx := s.ctx.setSimpTheorems simpThms }
if hsNonDeps.contains h then
-- We only simplify nondependent hypotheses
@@ -95,7 +96,7 @@ private partial def loop : M Bool := do
trace[Meta.Tactic.simp.all] "entry.id: {← ppOrigin entry.id}, {entry.type} => {typeNew}"
let mut simpThmsNew := ( getSimpTheorems).eraseTheorem (.fvar entry.fvarId)
let idNew mkFreshId
simpThmsNew simpThmsNew.addTheorem (.other idNew) ( mkExpectedTypeHint proofNew typeNew)
simpThmsNew simpThmsNew.addTheorem (.other idNew) ( mkExpectedTypeHint proofNew typeNew) (config := ctx.indexConfig)
modify fun s => { s with
modified := true
ctx := ctx.setSimpTheorems simpThmsNew

View File

@@ -204,8 +204,18 @@ structure SimpTheorems where
toUnfoldThms : PHashMap Name (Array Name) := {}
deriving Inhabited
/-- Configuration for the discrimination tree. -/
def simpDtConfig : WhnfCoreConfig := { iota := false, proj := .no, zetaDelta := false }
/--
Configuration for `MetaM` used to process global simp theorems
-/
def simpGlobalConfig : ConfigWithKey :=
{ iota := false
proj := .no
zetaDelta := false
transparency := .reducible
: Config }.toConfigWithKey
@[inline] def withSimpGlobalConfig : MetaM α MetaM α :=
withConfigWithKey simpGlobalConfig
partial def SimpTheorems.eraseCore (d : SimpTheorems) (thmId : Origin) : SimpTheorems :=
let d := { d with erased := d.erased.insert thmId, lemmaNames := d.lemmaNames.erase thmId }
@@ -298,7 +308,7 @@ private partial def isPerm : Expr → Expr → MetaM Bool
| s, t => return s == t
private def checkBadRewrite (lhs rhs : Expr) : MetaM Unit := do
let lhs DiscrTree.reduceDT lhs (root := true) simpDtConfig
let lhs withSimpGlobalConfig <| DiscrTree.reduceDT lhs (root := true)
if lhs == rhs && lhs.isFVar then
throwError "invalid `simp` theorem, equation is equivalent to{indentExpr (← mkEq lhs rhs)}"
@@ -381,11 +391,11 @@ private def mkSimpTheoremCore (origin : Origin) (e : Expr) (levelParams : Array
assert! origin != .fvar .anonymous
let type instantiateMVars ( inferType e)
withNewMCtxDepth do
let (_, _, type) withReducible <| forallMetaTelescopeReducing type
let (_, _, type) forallMetaTelescopeReducing type
let type whnfR type
let (keys, perm)
match type.eq? with
| some (_, lhs, rhs) => pure ( DiscrTree.mkPath lhs simpDtConfig noIndexAtArgs, isPerm lhs rhs)
| some (_, lhs, rhs) => pure ( DiscrTree.mkPath lhs noIndexAtArgs, isPerm lhs rhs)
| none => throwError "unexpected kind of 'simp' theorem{indentExpr type}"
return { origin, keys, perm, post, levelParams, proof, priority := prio, rfl := ( isRflProof proof) }
@@ -394,7 +404,7 @@ private def mkSimpTheoremsFromConst (declName : Name) (post : Bool) (inv : Bool)
let us := cinfo.levelParams.map mkLevelParam
let origin := .decl declName post inv
let val := mkConst declName us
withReducible do
withSimpGlobalConfig do
let type inferType val
checkTypeIsProp type
if inv || ( shouldPreprocess type) then
@@ -464,18 +474,10 @@ private def preprocessProof (val : Expr) (inv : Bool) : MetaM (Array Expr) := do
return ps.toArray.map fun (val, _) => val
/-- Auxiliary method for creating simp theorems from a proof term `val`. -/
def mkSimpTheorems (id : Origin) (levelParams : Array Name) (proof : Expr) (post := true) (inv := false) (prio : Nat := eval_prio default) : MetaM (Array SimpTheorem) :=
private def mkSimpTheorems (id : Origin) (levelParams : Array Name) (proof : Expr) (post := true) (inv := false) (prio : Nat := eval_prio default) : MetaM (Array SimpTheorem) :=
withReducible do
( preprocessProof proof inv).mapM fun val => mkSimpTheoremCore id val levelParams val post prio (noIndexAtArgs := true)
/-- Auxiliary method for adding a local simp theorem to a `SimpTheorems` datastructure. -/
def SimpTheorems.add (s : SimpTheorems) (id : Origin) (levelParams : Array Name) (proof : Expr) (inv := false) (post := true) (prio : Nat := eval_prio default) : MetaM SimpTheorems := do
if proof.isConst then
s.addConst proof.constName! post inv prio
else
let simpThms mkSimpTheorems id levelParams proof post inv prio
return simpThms.foldl addSimpTheoremEntry s
/--
Reducible functions and projection functions should always be put in `toUnfold`, instead
of trying to use equational theorems.
@@ -533,14 +535,25 @@ def SimpTheorems.addDeclToUnfold (d : SimpTheorems) (declName : Name) : MetaM Si
else
return d.addDeclToUnfoldCore declName
/-- Auxiliary method for adding a local simp theorem to a `SimpTheorems` datastructure. -/
def SimpTheorems.add (s : SimpTheorems) (id : Origin) (levelParams : Array Name) (proof : Expr)
(inv := false) (post := true) (prio : Nat := eval_prio default)
(config : ConfigWithKey := simpGlobalConfig) : MetaM SimpTheorems := do
if proof.isConst then
-- Recall that we use `simpGlobalConfig` for processing global declarations.
s.addConst proof.constName! post inv prio
else
let simpThms withConfigWithKey config <| mkSimpTheorems id levelParams proof post inv prio
return simpThms.foldl addSimpTheoremEntry s
abbrev SimpTheoremsArray := Array SimpTheorems
def SimpTheoremsArray.addTheorem (thmsArray : SimpTheoremsArray) (id : Origin) (h : Expr) : MetaM SimpTheoremsArray :=
def SimpTheoremsArray.addTheorem (thmsArray : SimpTheoremsArray) (id : Origin) (h : Expr) (config : ConfigWithKey := simpGlobalConfig) : MetaM SimpTheoremsArray :=
if thmsArray.isEmpty then
let thms : SimpTheorems := {}
return #[ ( thms.add id #[] h) ]
return #[ ( thms.add id #[] h (config := config)) ]
else
thmsArray.modifyM 0 fun thms => thms.add id #[] h
thmsArray.modifyM 0 fun thms => thms.add id #[] h (config := config)
def SimpTheoremsArray.eraseTheorem (thmsArray : SimpTheoremsArray) (thmId : Origin) : SimpTheoremsArray :=
thmsArray.map fun thms => thms.eraseCore thmId

View File

@@ -213,7 +213,7 @@ def SimprocEntry.tryD (s : SimprocEntry) (numExtraArgs : Nat) (e : Expr) : SimpM
| .inr proc => return ( proc e).addExtraArgs extraArgs
def simprocCore (post : Bool) (s : SimprocTree) (erased : PHashSet Name) (e : Expr) : SimpM Step := do
let candidates s.getMatchWithExtra e (getDtConfig ( getConfig))
let candidates withSimpIndexConfig <| s.getMatchWithExtra e
if candidates.isEmpty then
let tag := if post then "post" else "pre"
trace[Debug.Meta.Tactic.simp] "no {tag}-simprocs found for {e}"
@@ -250,7 +250,7 @@ def simprocCore (post : Bool) (s : SimprocTree) (erased : PHashSet Name) (e : Ex
return .continue
def dsimprocCore (post : Bool) (s : SimprocTree) (erased : PHashSet Name) (e : Expr) : SimpM DStep := do
let candidates s.getMatchWithExtra e (getDtConfig ( getConfig))
let candidates withSimpIndexConfig <| s.getMatchWithExtra e
if candidates.isEmpty then
let tag := if post then "post" else "pre"
trace[Debug.Meta.Tactic.simp] "no {tag}-simprocs found for {e}"

View File

@@ -53,7 +53,9 @@ abbrev CongrCache := ExprMap (Option CongrTheorem)
structure Context where
private mk ::
config : Config := {}
config : Config := {}
metaConfig : ConfigWithKey := default
indexConfig : ConfigWithKey := default
/-- `maxDischargeDepth` from `config` as an `UInt32`. -/
maxDischargeDepth : UInt32 := UInt32.ofNatTruncate config.maxDischargeDepth
simpTheorems : SimpTheoremsArray := {}
@@ -117,9 +119,32 @@ private def updateArith (c : Config) : CoreM Config := do
else
return c
/--
Converts `Simp.Config` into `Meta.ConfigWithKey` used for indexing.
-/
private def mkIndexConfig (c : Config) : ConfigWithKey :=
{ c with
proj := .no
transparency := .reducible
: Meta.Config }.toConfigWithKey
/--
Converts `Simp.Config` into `Meta.ConfigWithKey` used for `isDefEq`.
-/
-- TODO: use `metaConfig` at `isDefEq`. It is not being used yet because it will break Mathlib.
private def mkMetaConfig (c : Config) : ConfigWithKey :=
{ c with
proj := if c.proj then .yesWithDelta else .no
transparency := .reducible
: Meta.Config }.toConfigWithKey
def mkContext (config : Config := {}) (simpTheorems : SimpTheoremsArray := {}) (congrTheorems : SimpCongrTheorems := {}) : MetaM Context := do
let config updateArith config
return { config, simpTheorems, congrTheorems }
return {
config, simpTheorems, congrTheorems
metaConfig := mkMetaConfig config
indexConfig := mkIndexConfig config
}
def Context.setConfig (context : Context) (config : Config) : Context :=
{ context with config }
@@ -203,6 +228,15 @@ abbrev SimpM := ReaderT MethodsRef $ ReaderT Context $ StateRefT State MetaM
@[inline] def withInDSimp : SimpM α SimpM α :=
withTheReader Context (fun ctx => { ctx with inDSimp := true })
/--
Executes `x` using a `MetaM` configuration for indexing terms.
It is inferred from `Simp.Config`.
For example, if the user has set `simp (config := { zeta := false })`,
`isDefEq` and `whnf` in `MetaM` should not perform `zeta` reduction.
-/
@[inline] def withSimpIndexConfig (x : SimpM α) : SimpM α := do
withConfigWithKey ( readThe Simp.Context).indexConfig x
@[extern "lean_simp"]
opaque simp (e : Expr) : SimpM Result
@@ -679,16 +713,6 @@ def tryAutoCongrTheorem? (e : Expr) : SimpM (Option Result) := do
/- See comment above. This is reachable if `hasCast == true`. The `rhs` is not structurally equal to `mkAppN f argsNew` -/
return some { expr := rhs }
/--
Return a WHNF configuration for retrieving `[simp]` from the discrimination tree.
If user has disabled `zeta` and/or `beta` reduction in the simplifier, or enabled `zetaDelta`,
we must also disable/enable them when retrieving lemmas from discrimination tree. See issues: #2669 and #2281
-/
def getDtConfig (cfg : Config) : WhnfCoreConfig :=
match cfg.beta, cfg.zeta, cfg.zetaDelta with
| true, true, false => simpDtConfig
| _, _, _ => { simpDtConfig with zeta := cfg.zeta, beta := cfg.beta, zetaDelta := cfg.zetaDelta }
def Result.addExtraArgs (r : Result) (extraArgs : Array Expr) : MetaM Result := do
match r.proof? with
| none => return { expr := mkAppN r.expr extraArgs }

View File

@@ -18,9 +18,6 @@ open Lean Meta
namespace Lean.Meta.Symm
/-- Discrimation tree settings for the `symm` extension. -/
def symmExt.config : WhnfCoreConfig := {}
/-- Environment extensions for symm lemmas -/
builtin_initialize symmExt :
SimpleScopedEnvExtension (Name × Array DiscrTree.Key) (DiscrTree Name)
@@ -40,7 +37,7 @@ builtin_initialize registerBuiltinAttribute {
let some _ := xs.back? | fail
let targetTy reduce targetTy
let .app (.app rel _) _ := targetTy | fail
let key withReducible <| DiscrTree.mkPath rel symmExt.config
let key withReducible <| DiscrTree.mkPath rel
symmExt.add (decl, key) kind
}
@@ -54,7 +51,7 @@ namespace Lean.Expr
def getSymmLems (tgt : Expr) : MetaM (Array Name) := do
let .app (.app rel _) _ := tgt
| throwError "symmetry lemmas only apply to binary relations, not{indentExpr tgt}"
(symmExt.getState ( getEnv)).getMatch rel symmExt.config
(symmExt.getState ( getEnv)).getMatch rel
/-- Given a term `e : a ~ b`, construct a term in `b ~ a` using `@[symm]` lemmas. -/
def applySymm (e : Expr) : MetaM Expr := do

View File

@@ -430,11 +430,13 @@ def addSuggestions (ref : Syntax) (suggestions : Array Suggestion)
logInfoAt ref m!"{header}{msgs}"
addSuggestionCore ref suggestions header (isInline := false) origSpan? style? codeActionPrefix?
private def addExactSuggestionCore (addSubgoalsMsg : Bool) (e : Expr) : MetaM Suggestion := do
private def addExactSuggestionCore (addSubgoalsMsg : Bool) (e : Expr) : MetaM Suggestion :=
withOptions (pp.mvars.set · false) do
let stx delabToRefinableSyntax e
let mvars getMVars e
let suggestion if mvars.isEmpty then `(tactic| exact $stx) else `(tactic| refine $stx)
let messageData? := if mvars.isEmpty then m!"exact {e}" else m!"refine {e}"
let pp ppExpr e
let messageData? := if mvars.isEmpty then m!"exact {pp}" else m!"refine {pp}"
let postInfo? if !addSubgoalsMsg || mvars.isEmpty then pure none else
let mut str := "\nRemaining subgoals:"
for g in mvars do

View File

@@ -6,6 +6,7 @@ Authors: Leonardo de Moura
prelude
import Lean.ScopedEnvExtension
import Lean.Util.Recognizers
import Lean.Meta.Basic
import Lean.Meta.DiscrTree
import Lean.Meta.SynthInstance
@@ -27,7 +28,8 @@ structure UnificationHints where
instance : ToFormat UnificationHints where
format h := format h.discrTree
def UnificationHints.config : WhnfCoreConfig := { iota := false, proj := .no }
private def config : ConfigWithKey :=
{ iota := false, proj := .no : Config }.toConfigWithKey
def UnificationHints.add (hints : UnificationHints) (e : UnificationHintEntry) : UnificationHints :=
{ hints with discrTree := hints.discrTree.insertCore e.keys e.val }
@@ -81,7 +83,7 @@ def addUnificationHint (declName : Name) (kind : AttributeKind) : MetaM Unit :=
match decodeUnificationHint body with
| Except.error msg => throwError msg
| Except.ok hint =>
let keys DiscrTree.mkPath hint.pattern.lhs UnificationHints.config
let keys withConfigWithKey config <| DiscrTree.mkPath hint.pattern.lhs
validateHint hint
unificationHintExtension.add { keys := keys, val := declName } kind
@@ -101,7 +103,7 @@ def tryUnificationHints (t s : Expr) : MetaM Bool := do
if t.isMVar then
return false
let hints := unificationHintExtension.getState ( getEnv)
let candidates hints.discrTree.getMatch t UnificationHints.config
let candidates withConfigWithKey config <| hints.discrTree.getMatch t
for candidate in candidates do
if ( tryCandidate candidate) then
return true

View File

@@ -328,65 +328,8 @@ end
/-! # Weak Head Normal Form auxiliary combinators -/
-- ===========================
/--
Configuration for projection reduction. See `whnfCore`.
-/
inductive ProjReductionKind where
/-- Projections `s.i` are not reduced at `whnfCore`. -/
| no
/--
Projections `s.i` are reduced at `whnfCore`, and `whnfCore` is used at `s` during the process.
Recall that `whnfCore` does not perform `delta` reduction (i.e., it will not unfold constant declarations).
-/
| yes
/--
Projections `s.i` are reduced at `whnfCore`, and `whnf` is used at `s` during the process.
Recall that `whnfCore` does not perform `delta` reduction (i.e., it will not unfold constant declarations), but `whnf` does.
-/
| yesWithDelta
/--
Projections `s.i` are reduced at `whnfCore`, and `whnfAtMostI` is used at `s` during the process.
Recall that `whnfAtMostI` is like `whnf` but uses transparency at most `instances`.
This option is stronger than `yes`, but weaker than `yesWithDelta`.
We use this option to ensure we reduce projections to prevent expensive defeq checks when unifying TC operations.
When unifying e.g. `(@Field.toNeg α inst1).1 =?= (@Field.toNeg α inst2).1`,
we only want to unify negation (and not all other field operations as well).
Unifying the field instances slowed down unification: https://github.com/leanprover/lean4/issues/1986
-/
| yesWithDeltaI
deriving DecidableEq, Inhabited, Repr
/--
Configuration options for `whnfEasyCases` and `whnfCore`.
-/
structure WhnfCoreConfig where
/-- If `true`, reduce recursor/matcher applications, e.g., `Nat.rec true (fun _ _ => false) Nat.zero` reduces to `true` -/
iota : Bool := true
/-- If `true`, reduce terms such as `(fun x => t[x]) a` into `t[a]` -/
beta : Bool := true
/-- Control projection reduction at `whnfCore`. -/
proj : ProjReductionKind := .yesWithDelta
/--
Zeta reduction: `let x := v; e[x]` reduces to `e[v]`.
We say a let-declaration `let x := v; e` is non dependent if it is equivalent to `(fun x => e) v`.
Recall that
```
fun x : BitVec 5 => let n := 5; fun y : BitVec n => x = y
```
is type correct, but
```
fun x : BitVec 5 => (fun n => fun y : BitVec n => x = y) 5
```
is not.
-/
zeta : Bool := true
/--
Zeta-delta reduction: given a local context containing entry `x : t := e`, free variable `x` reduces to `e`.
-/
zetaDelta : Bool := true
/-- Auxiliary combinator for handling easy WHNF cases. It takes a function for handling the "hard" cases as an argument -/
@[specialize] partial def whnfEasyCases (e : Expr) (k : Expr MetaM Expr) (config : WhnfCoreConfig := {}) : MetaM Expr := do
@[specialize] partial def whnfEasyCases (e : Expr) (k : Expr MetaM Expr) : MetaM Expr := do
match e with
| .forallE .. => return e
| .lam .. => return e
@@ -397,7 +340,7 @@ structure WhnfCoreConfig where
| .const .. => k e
| .app .. => k e
| .proj .. => k e
| .mdata _ e => whnfEasyCases e k config
| .mdata _ e => whnfEasyCases e k
| .fvar fvarId =>
let decl fvarId.getDecl
match decl with
@@ -405,13 +348,14 @@ structure WhnfCoreConfig where
| .ldecl (value := v) .. =>
-- Let-declarations marked as implementation detail should always be unfolded
-- We initially added this feature for `simp`, and added it here for consistency.
unless config.zetaDelta || decl.isImplementationDetail do return e
if ( getConfig).trackZetaDelta then
let cfg getConfig
unless cfg.zetaDelta || decl.isImplementationDetail do return e
if cfg.trackZetaDelta then
modify fun s => { s with zetaDeltaFVarIds := s.zetaDeltaFVarIds.insert fvarId }
whnfEasyCases v k config
whnfEasyCases v k
| .mvar mvarId =>
match ( getExprMVarAssignment? mvarId) with
| some v => whnfEasyCases v k config
| some v => whnfEasyCases v k
| none => return e
@[specialize] private def deltaDefinition (c : ConstantInfo) (lvls : List Level)
@@ -611,30 +555,31 @@ private def whnfDelayedAssigned? (f' : Expr) (e : Expr) : MetaM (Option Expr) :=
Apply beta-reduction, zeta-reduction (i.e., unfold let local-decls), iota-reduction,
expand let-expressions, expand assigned meta-variables.
-/
partial def whnfCore (e : Expr) (config : WhnfCoreConfig := {}): MetaM Expr :=
partial def whnfCore (e : Expr) : MetaM Expr :=
go e
where
go (e : Expr) : MetaM Expr :=
whnfEasyCases e (config := config) fun e => do
whnfEasyCases e fun e => do
trace[Meta.whnf] e
match e with
| .const .. => pure e
| .letE _ _ v b _ => if config.zeta then go <| b.instantiate1 v else return e
| .letE _ _ v b _ => if ( getConfig).zeta then go <| b.instantiate1 v else return e
| .app f .. =>
if config.zeta then
let cfg getConfig
if cfg.zeta then
if let some (args, _, _, v, b) := e.letFunAppArgs? then
-- When zeta reducing enabled, always reduce `letFun` no matter the current reducibility level
return ( go <| mkAppN (b.instantiate1 v) args)
let f := f.getAppFn
let f' go f
if config.beta && f'.isLambda then
if cfg.beta && f'.isLambda then
let revArgs := e.getAppRevArgs
go <| f'.betaRev revArgs
else if let some eNew whnfDelayedAssigned? f' e then
go eNew
else
let e := if f == f' then e else e.updateFn f'
unless config.iota do return e
unless cfg.iota do return e
match ( reduceMatcher? e) with
| .reduced eNew => go eNew
| .partialApp => pure e
@@ -656,7 +601,7 @@ where
match ( projectCore? c i) with
| some e => go e
| none => return e
match config.proj with
match ( getConfig).proj with
| .no => return e
| .yes => k ( go c)
| .yesWithDelta => k ( whnf c)
@@ -967,26 +912,18 @@ def reduceNat? (e : Expr) : MetaM (Option Expr) :=
if e.hasFVar || e.hasExprMVar || ( read).canUnfold?.isSome then
return false
else
match ( getConfig).transparency with
| .default => return true
| .all => return true
| _ => return false
return true
@[inline] private def cached? (useCache : Bool) (e : Expr) : MetaM (Option Expr) := do
if useCache then
match ( getConfig).transparency with
| .default => return ( get).cache.whnfDefault.find? e
| .all => return ( get).cache.whnfAll.find? e
| _ => unreachable!
return ( get).cache.whnf.find? ( mkExprConfigCacheKey e)
else
return none
private def cache (useCache : Bool) (e r : Expr) : MetaM Expr := do
if useCache then
match ( getConfig).transparency with
| .default => modify fun s => { s with cache.whnfDefault := s.cache.whnfDefault.insert e r }
| .all => modify fun s => { s with cache.whnfAll := s.cache.whnfAll.insert e r }
| _ => unreachable!
let key mkExprConfigCacheKey e
modify fun s => { s with cache.whnf := s.cache.whnf.insert key r }
return r
@[export lean_whnf]

View File

@@ -1219,7 +1219,7 @@ private def mkLambda' (x : Name) (bi : BinderInfo) (t : Expr) (b : Expr) (etaRed
Similar to `LocalContext.mkBinding`, but handles metavariables correctly.
If `usedOnly == true` then `forall` and `lambda` expressions are created only for used variables.
If `usedLetOnly == true` then `let` expressions are created only for used (let-) variables. -/
@[specialize] def mkBinding (isLambda : Bool) (lctx : LocalContext) (xs : Array Expr) (e : Expr) (usedOnly : Bool) (usedLetOnly : Bool) (etaReduce : Bool) : M Expr := do
def mkBinding (isLambda : Bool) (lctx : LocalContext) (xs : Array Expr) (e : Expr) (usedOnly : Bool) (usedLetOnly : Bool) (etaReduce : Bool) : M Expr := do
let e abstractRange xs xs.size e
xs.size.foldRevM (init := e) fun i e => do
let x := xs[i]!

View File

@@ -137,7 +137,7 @@ def declValEqns := leading_parser
def whereStructField := leading_parser
Term.letDecl
def whereStructInst := leading_parser
ppIndent ppSpace >> "where" >> sepByIndent (ppGroup whereStructField) "; " (allowTrailingSep := true) >>
ppIndent ppSpace >> "where" >> Term.structInstFields (sepByIndent (ppGroup whereStructField) "; " (allowTrailingSep := true)) >>
optional Term.whereDecls
/-- `declVal` matches the right-hand side of a declaration, one of:
* `:= expr` (a "simple declaration")

View File

@@ -281,6 +281,12 @@ def structInstFieldAbbrev := leading_parser
atomic (ident >> notFollowedBy ("." <|> ":=" <|> symbol "[") "invalid field abbreviation")
def optEllipsis := leading_parser
optional " .."
/-
Tags the structure instance field syntax with a `Lean.Parser.Term.structInstFields` syntax node.
This node is used to enable structure instance field completion in the whitespace
of a structure instance notation.
-/
def structInstFields (p : Parser) : Parser := node `Lean.Parser.Term.structInstFields p
/--
Structure instance. `{ x := e, ... }` assigns `e` to field `x`, which may be
inherited. If `e` is itself a variable called `x`, it can be elided:
@@ -292,7 +298,7 @@ The structure type can be specified if not inferable:
-/
@[builtin_term_parser] def structInst := leading_parser
"{ " >> withoutPosition (optional (atomic (sepBy1 termParser ", " >> " with "))
>> sepByIndent (structInstFieldAbbrev <|> structInstField) ", " (allowTrailingSep := true)
>> structInstFields (sepByIndent (structInstFieldAbbrev <|> structInstField) ", " (allowTrailingSep := true))
>> optEllipsis
>> optional (" : " >> termParser)) >> " }"
def typeSpec := leading_parser " : " >> termParser
@@ -984,6 +990,7 @@ builtin_initialize
register_parser_alias bracketedBinder
register_parser_alias attrKind
register_parser_alias optSemicolon
register_parser_alias structInstFields
end Parser
end Lean

View File

@@ -91,21 +91,32 @@ Rather, it is called through the `app` delaborator.
-/
def delabConst : Delab := do
let Expr.const c₀ ls getExpr | unreachable!
let c₀ := if ( getPPOption getPPPrivateNames) then c₀ else (privateToUserName? c₀).getD c₀
let mut c unresolveNameGlobal c₀ (fullNames := getPPOption getPPFullNames)
let stx if ls.isEmpty || !( getPPOption getPPUniverses) then
if ( getLCtx).usesUserName c then
-- `c` is also a local declaration
if c == c₀ && !( read).inPattern then
-- `c` is the fully qualified named. So, we append the `_root_` prefix
c := `_root_ ++ c
let mut c₀ := c₀
let mut c := c₀
if let some n := privateToUserName? c₀ then
unless ( getPPOption getPPPrivateNames) do
if c₀ == mkPrivateName ( getEnv) n then
-- The name is defined in this module, so use `n` as the name and unresolve like any other name.
c₀ := n
c unresolveNameGlobal n (fullNames := getPPOption getPPFullNames)
else
c := c₀
pure <| mkIdent c
-- The name is not defined in this module, so make inaccessible. Unresolving does not make sense to do.
c withFreshMacroScope <| MonadQuotation.addMacroScope n
else
let mvars getPPOption getPPMVarsLevels
`($(mkIdent c).{$[$(ls.toArray.map (Level.quote · (prec := 0) (mvars := mvars)))],*})
c unresolveNameGlobal c (fullNames := getPPOption getPPFullNames)
let stx
if ls.isEmpty || !( getPPOption getPPUniverses) then
if ( getLCtx).usesUserName c then
-- `c` is also a local declaration
if c == c₀ && !( read).inPattern then
-- `c` is the fully qualified named. So, we append the `_root_` prefix
c := `_root_ ++ c
else
c := c₀
pure <| mkIdent c
else
let mvars getPPOption getPPMVarsLevels
`($(mkIdent c).{$[$(ls.toArray.map (Level.quote · (prec := 0) (mvars := mvars)))],*})
let stx maybeAddBlockImplicit stx
if ( getPPOption getPPTagAppFns) then
@@ -1092,19 +1103,29 @@ def coeDelaborator : Delab := whenPPOption getPPCoercions do
let e getExpr
let .const declName _ := e.getAppFn | failure
let some info Meta.getCoeFnInfo? declName | failure
let n := e.getAppNumArgs
guard <| n info.numArgs
if ( getPPOption getPPExplicit) && info.coercee != 0 then
-- Approximation: the only implicit arguments come before the coercee
failure
let n := e.getAppNumArgs
withOverApp info.numArgs do
match info.type with
| .coe => `($( withNaryArg info.coercee delab))
| .coeFun =>
if n = info.numArgs then
`($( withNaryArg info.coercee delab))
else
withNaryArg info.coercee delab
| .coeSort => `($( withNaryArg info.coercee delab))
if n == info.numArgs then
delabHead info 0 false
else
let nargs := n - info.numArgs
delabAppCore nargs (delabHead info nargs) (unexpand := false)
where
delabHead (info : CoeFnInfo) (nargs : Nat) (insertExplicit : Bool) : Delab := do
guard <| !insertExplicit
if info.type == .coeFun && nargs > 0 then
-- In the CoeFun case, annotate with the coercee itself.
-- We can still see the whole coercion expression by hovering over the whitespace between the arguments.
withNaryArg info.coercee <| withAnnotateTermInfo delab
else
withAnnotateTermInfo do
match info.type with
| .coe => `($( withNaryArg info.coercee delab))
| .coeFun => `($( withNaryArg info.coercee delab))
| .coeSort => `($( withNaryArg info.coercee delab))
@[builtin_delab app.dite]
def delabDIte : Delab := whenNotPPOption getPPExplicit <| whenPPOption getPPNotation <| withOverApp 5 do

View File

@@ -24,6 +24,11 @@ register_builtin_option pp.notation : Bool := {
group := "pp"
descr := "(pretty printer) disable/enable notation (infix, mixfix, postfix operators and unicode characters)"
}
register_builtin_option pp.parens : Bool := {
defValue := false
group := "pp"
descr := "(pretty printer) if set to true, notation is wrapped in parentheses regardless of precedence"
}
register_builtin_option pp.unicode.fun : Bool := {
defValue := false
group := "pp"
@@ -248,6 +253,7 @@ def getPPNatLit (o : Options) : Bool := o.get pp.natLit.name (getPPNumericTypes
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)
def getPPParens (o : Options) : Bool := o.get pp.parens.name pp.parens.defValue
def getPPUnicodeFun (o : Options) : Bool := o.get pp.unicode.fun.name false
def getPPMatch (o : Options) : Bool := o.get pp.match.name (!getPPAll o)
def getPPFieldNotation (o : Options) : Bool := o.get pp.fieldNotation.name (!getPPAll o)

View File

@@ -8,6 +8,7 @@ import Lean.Parser.Extension
import Lean.Parser.StrInterpolation
import Lean.ParserCompiler.Attribute
import Lean.PrettyPrinter.Basic
import Lean.PrettyPrinter.Delaborator.Options
/-!
@@ -82,8 +83,10 @@ namespace PrettyPrinter
namespace Parenthesizer
structure Context where
-- We need to store this `categoryParser` argument to deal with the implicit Pratt parser call in `trailingNode.parenthesizer`.
/-- We need to store this `categoryParser` argument to deal with the implicit Pratt parser call in `trailingNode.parenthesizer`. -/
cat : Name := Name.anonymous
/-- Whether to add parentheses regardless of any other conditions. This is cached from the `pp.parens` option. -/
forceParens : Bool := false
structure State where
stxTrav : Syntax.Traverser
@@ -217,8 +220,13 @@ def maybeParenthesize (cat : Name) (canJuxtapose : Bool) (mkParen : Syntax → S
let { minPrec := some minPrec, trailPrec := trailPrec, trailCat := trailCat, .. } get
| trace[PrettyPrinter.parenthesize] "visited a syntax tree without precedences?!{line ++ format stx}"
trace[PrettyPrinter.parenthesize] (m!"...precedences are {prec} >? {minPrec}" ++ if canJuxtapose then m!", {(trailPrec, trailCat)} <=? {(st.contPrec, st.contCat)}" else "")
-- Should we parenthesize?
if (prec > minPrec || canJuxtapose && match trailPrec, st.contPrec with | some trailPrec, some contPrec => trailCat == st.contCat && trailPrec <= contPrec | _, _ => false) then
/- Should we parenthesize?
* Note about forceParens mode: we don't insert outermost parentheses (we use the syntax traverser parents to detect this),
and we don't insert parentheses when we are at `maxPrec` (since this is effectively infinity).
-/
if ((( read).forceParens && !st.stxTrav.parents.isEmpty && minPrec < Parser.maxPrec)
|| prec > minPrec
|| canJuxtapose && match trailPrec, st.contPrec with | some trailPrec, some contPrec => trailCat == st.contCat && trailPrec <= contPrec | _, _ => false) then
-- The recursive `visit` call, by the invariant, has moved to the preceding node. In order to parenthesize
-- the original node, we must first move to the right, except if we already were at the left-most child in the first
-- place.
@@ -540,16 +548,23 @@ instance : Coe (Parenthesizer → Parenthesizer → Parenthesizer) Parenthesizer
end Parenthesizer
open Parenthesizer
/-- Add necessary parentheses in `stx` parsed by `parser`. -/
/--
Adds necessary parentheses in `stx` parsed by `parser`.
-/
def parenthesize (parenthesizer : Parenthesizer) (stx : Syntax) : CoreM Syntax := do
trace[PrettyPrinter.parenthesize.input] "{format stx}"
let opts getOptions
catchInternalId backtrackExceptionId
(do
let (_, st) (parenthesizer {}).run { stxTrav := Syntax.Traverser.fromSyntax stx }
let (_, st) (parenthesizer { forceParens := getPPParens opts }).run { stxTrav := Syntax.Traverser.fromSyntax stx }
pure st.stxTrav.cur)
(fun _ => throwError "parenthesize: uncaught backtrack exception")
def parenthesizeCategory (cat : Name) := parenthesize <| categoryParser.parenthesizer cat 0
/--
Adds necessary parentheses to the syntax in the given category (for example, `term`, `tactic`, or `command`).
-/
def parenthesizeCategory (cat : Name) (stx : Syntax) :=
parenthesize (categoryParser.parenthesizer cat 0) stx
def parenthesizeTerm := parenthesizeCategory `term
def parenthesizeTactic := parenthesizeCategory `tactic

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,610 @@
/-
Copyright (c) 2024 Lean FRO, LLC. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Leonardo de Moura, Marc Huisinga
-/
prelude
import Lean.Data.FuzzyMatching
import Lean.Elab.Tactic.Doc
import Lean.Server.Completion.CompletionResolution
import Lean.Server.Completion.EligibleHeaderDecls
namespace Lean.Server.Completion
open Elab
open Lean.Lsp
open Meta
open FuzzyMatching
section Infrastructure
structure ScoredCompletionItem where
item : CompletionItem
score : Float
deriving Inhabited
private structure Context where
params : CompletionParams
completionInfoPos : Nat
/-- Intermediate state while completions are being computed. -/
private structure State where
/-- All completion items and their fuzzy match scores so far. -/
items : Array ScoredCompletionItem := #[]
/--
Monad used for completion computation that allows modifying a completion `State` and reading
`CompletionParams`.
-/
private abbrev M := ReaderT Context $ StateRefT State MetaM
/-- Adds a new completion item to the state in `M`. -/
private def addItem
(item : CompletionItem)
(score : Float)
(id? : Option CompletionIdentifier := none)
: M Unit := do
let ctx read
let data := {
params := ctx.params,
cPos := ctx.completionInfoPos,
id?
: ResolvableCompletionItemData
}
let item := { item with data? := toJson data }
modify fun s => { s with items := s.items.push item, score }
/--
Adds a new completion item with the given `label`, `id`, `kind` and `score` to the state in `M`.
Computes the doc string from the environment if available.
-/
private def addUnresolvedCompletionItem
(label : Name)
(id : CompletionIdentifier)
(kind : CompletionItemKind)
(score : Float)
: M Unit := do
let env getEnv
let (docStringPrefix?, tags?) := Id.run do
let .const declName := id
| (none, none)
let some param := Linter.deprecatedAttr.getParam? env declName
| (none, none)
let docstringPrefix :=
if let some text := param.text? then
text
else if let some newName := param.newName? then
s!"`{declName}` has been deprecated, use `{newName}` instead."
else
s!"`{declName}` has been deprecated."
(some docstringPrefix, some #[CompletionItemTag.deprecated])
let docString? do
let .const declName := id
| pure none
findDocString? env declName
let doc? := do
let docValue
match docStringPrefix?, docString? with
| none, none => none
| some docStringPrefix, none => docStringPrefix
| none, docString => docString
| some docStringPrefix, some docString => s!"{docStringPrefix}\n\n{docString}"
pure { value := docValue , kind := MarkupKind.markdown : MarkupContent }
let item := { label := label.toString, kind? := kind, documentation? := doc?, tags?}
addItem item score id
private def getCompletionKindForDecl (constInfo : ConstantInfo) : M CompletionItemKind := do
let env getEnv
if constInfo.isCtor then
return CompletionItemKind.constructor
else if constInfo.isInductive then
if isClass env constInfo.name then
return CompletionItemKind.class
else if ( isEnumType constInfo.name) then
return CompletionItemKind.enum
else
return CompletionItemKind.struct
else if constInfo.isTheorem then
return CompletionItemKind.event
else if ( isProjectionFn constInfo.name) then
return CompletionItemKind.field
else
let isFunction : Bool withTheReader Core.Context ({ · with maxHeartbeats := 0 }) do
return ( whnf constInfo.type).isForall
if isFunction then
return CompletionItemKind.function
else
return CompletionItemKind.constant
private def addUnresolvedCompletionItemForDecl (label : Name) (declName : Name) (score : Float) : M Unit := do
if let some c := ( getEnv).find? declName then
addUnresolvedCompletionItem label (.const declName) ( getCompletionKindForDecl c) score
private def addKeywordCompletionItem (keyword : String) (score : Float) : M Unit := do
let item := { label := keyword, detail? := "keyword", documentation? := none, kind? := CompletionItemKind.keyword }
addItem item score
private def addNamespaceCompletionItem (ns : Name) (score : Float) : M Unit := do
let item := { label := ns.toString, detail? := "namespace", documentation? := none, kind? := CompletionItemKind.module }
addItem item score
private def runM
(params : CompletionParams)
(completionInfoPos : Nat)
(ctx : ContextInfo)
(lctx : LocalContext)
(x : M Unit)
: IO (Array ScoredCompletionItem) :=
ctx.runMetaM lctx do
let (_, s) x.run params, completionInfoPos |>.run {}
return s.items
end Infrastructure
section Utils
private def normPrivateName? (declName : Name) : MetaM (Option Name) := do
match privateToUserName? declName with
| none => return declName
| some userName =>
if mkPrivateName ( getEnv) userName == declName then
return userName
else
return none
/--
Return the auto-completion label if `id` can be auto completed using `declName` assuming namespace `ns` is open.
This function only succeeds with atomic labels. BTW, it seems most clients only use the last part.
Remark: `danglingDot == true` when the completion point is an identifier followed by `.`.
-/
private def matchDecl? (ns : Name) (id : Name) (danglingDot : Bool) (declName : Name) : MetaM (Option (Name × Float)) := do
let some declName normPrivateName? declName
| return none
if !ns.isPrefixOf declName then
return none
let declName := declName.replacePrefix ns Name.anonymous
if danglingDot then
-- If the input is `id.` and `declName` is of the form `id.atomic`, complete with `atomicName`
if id.isPrefixOf declName then
let declName := declName.replacePrefix id Name.anonymous
if declName.isAtomic && !declName.isAnonymous then
return some (declName, 1)
else if let (.str p₁ s₁, .str p₂ s₂) := (id, declName) then
if p₁ == p₂ then
-- If the namespaces agree, fuzzy-match on the trailing part
return fuzzyMatchScoreWithThreshold? s₁ s₂ |>.map (.mkSimple s₂, ·)
else if p₁.isAnonymous then
-- If `id` is namespace-less, also fuzzy-match declaration names in arbitrary namespaces
-- (but don't match the namespace itself).
-- Penalize score by component length of added namespace.
return fuzzyMatchScoreWithThreshold? s₁ s₂ |>.map (declName, · / (p₂.getNumParts + 1).toFloat)
return none
end Utils
section IdCompletionUtils
private def matchAtomic (id : Name) (declName : Name) (danglingDot : Bool) : Option Float := do
if danglingDot then
none
match id, declName with
| .str .anonymous s₁, .str .anonymous s₂ => fuzzyMatchScoreWithThreshold? s₁ s₂
| _, _ => none
/--
Truncate the given identifier and make sure it has length `≤ newLength`.
This function assumes `id` does not contain `Name.num` constructors.
-/
private partial def truncate (id : Name) (newLen : Nat) : Name :=
let rec go (id : Name) : Name × Nat :=
match id with
| Name.anonymous => (id, 0)
| Name.num .. => unreachable!
| .str p s =>
let (p', len) := go p
if len + 1 >= newLen then
(p', len)
else
let optDot := if p.isAnonymous then 0 else 1
let len' := len + optDot + s.length
if len' newLen then
(id, len')
else
(Name.mkStr p (s.extract 0 newLen - optDot - len), newLen)
(go id).1
def matchNamespace (ns : Name) (nsFragment : Name) (danglingDot : Bool) : Option Float :=
if danglingDot then
if nsFragment != ns && nsFragment.isPrefixOf ns then
some 1
else
none
else
match ns, nsFragment with
| .str p₁ s₁, .str p₂ s₂ =>
if p₁ == p₂ then fuzzyMatchScoreWithThreshold? s₂ s₁ else none
| _, _ => none
def completeNamespaces (ctx : ContextInfo) (id : Name) (danglingDot : Bool) : M Unit := do
let env getEnv
let add (ns : Name) (ns' : Name) (score : Float) : M Unit :=
if danglingDot then
addNamespaceCompletionItem (ns.replacePrefix (ns' ++ id) Name.anonymous) score
else
addNamespaceCompletionItem (ns.replacePrefix ns' Name.anonymous) score
env.getNamespaceSet |>.forM fun ns => do
unless ns.isInternal || env.contains ns do -- Ignore internal and namespaces that are also declaration names
for openDecl in ctx.openDecls do
match openDecl with
| OpenDecl.simple ns' _ =>
if let some score := matchNamespace ns (ns' ++ id) danglingDot then
add ns ns' score
return ()
| _ => pure ()
-- use current namespace
let rec visitNamespaces (ns' : Name) : M Unit := do
if let some score := matchNamespace ns (ns' ++ id) danglingDot then
add ns ns' score
else
match ns' with
| Name.str p .. => visitNamespaces p
| _ => return ()
visitNamespaces ctx.currNamespace
end IdCompletionUtils
section DotCompletionUtils
private def unfoldeDefinitionGuarded? (e : Expr) : MetaM (Option Expr) :=
try unfoldDefinition? e catch _ => pure none
/-- Return `true` if `e` is a `declName`-application, or can be unfolded (delta-reduced) to one. -/
private partial def isDefEqToAppOf (e : Expr) (declName : Name) : MetaM Bool := do
let isConstOf := match e.getAppFn with
| .const name .. => (privateToUserName? name).getD name == declName
| _ => false
if isConstOf then
return true
let some e unfoldeDefinitionGuarded? e | return false
isDefEqToAppOf e declName
private def isDotCompletionMethod (typeName : Name) (info : ConstantInfo) : MetaM Bool :=
forallTelescopeReducing info.type fun xs _ => do
for x in xs do
let localDecl x.fvarId!.getDecl
let type := localDecl.type.consumeMData
if ( isDefEqToAppOf type typeName) then
return true
return false
/--
Checks whether the expected type of `info.type` can be reduced to an application of `typeName`.
-/
private def isDotIdCompletionMethod (typeName : Name) (info : ConstantInfo) : MetaM Bool := do
forallTelescopeReducing info.type fun _ type =>
isDefEqToAppOf type.consumeMData typeName
/--
Converts `n` to `Name.anonymous` if `n` is a private prefix (see `Lean.isPrivatePrefix`).
-/
private def stripPrivatePrefix (n : Name) : Name :=
match n with
| .num _ 0 => if isPrivatePrefix n then .anonymous else n
| _ => n
/--
Compares `n₁` and `n₂` modulo private prefixes. Similar to `Name.cmp` but ignores all
private prefixes in both names.
Necessary because the namespaces of private names do not contain private prefixes.
-/
private partial def cmpModPrivate (n₁ n₂ : Name) : Ordering :=
let n₁ := stripPrivatePrefix n₁
let n₂ := stripPrivatePrefix n₂
match n₁, n₂ with
| .anonymous, .anonymous => Ordering.eq
| .anonymous, _ => Ordering.lt
| _, .anonymous => Ordering.gt
| .num p₁ i₁, .num p₂ i₂ =>
match compare i₁ i₂ with
| Ordering.eq => cmpModPrivate p₁ p₂
| ord => ord
| .num _ _, .str _ _ => Ordering.lt
| .str _ _, .num _ _ => Ordering.gt
| .str p₁ n₁, .str p₂ n₂ =>
match compare n₁ n₂ with
| Ordering.eq => cmpModPrivate p₁ p₂
| ord => ord
/--
`NameSet` where names are compared according to `cmpModPrivate`.
Helps speed up dot completion because it allows us to look up names without first having to
strip the private prefix from deep in the name, letting us reject most names without
having to scan the full name first.
-/
private def NameSetModPrivate := RBTree Name cmpModPrivate
/--
Given a type, try to extract relevant type names for dot notation field completion.
We extract the type name, parent struct names, and unfold the type.
The process mimics the dot notation elaboration procedure at `App.lean` -/
private partial def getDotCompletionTypeNames (type : Expr) : MetaM NameSetModPrivate :=
return ( visit type |>.run RBTree.empty).2
where
visit (type : Expr) : StateRefT NameSetModPrivate MetaM Unit := do
let .const typeName _ := type.getAppFn | return ()
modify fun s => s.insert typeName
if isStructure ( getEnv) typeName then
for parentName in ( getAllParentStructures typeName) do
modify fun s => s.insert parentName
let some type unfoldeDefinitionGuarded? type | return ()
visit type
end DotCompletionUtils
private def idCompletionCore
(ctx : ContextInfo)
(stx : Syntax)
(id : Name)
(hoverInfo : HoverInfo)
(danglingDot : Bool)
: M Unit := do
let mut id := id
if id.hasMacroScopes then
if stx.getHeadInfo matches .original .. then
id := id.eraseMacroScopes
else
-- Identifier is synthetic and has macro scopes => no completions
-- Erasing the macro scopes does not make sense in this case because the identifier name
-- is some random synthetic string.
return
let mut danglingDot := danglingDot
if let HoverInfo.inside delta := hoverInfo then
id := truncate id delta
danglingDot := false
if id.isAtomic then
-- search for matches in the local context
for localDecl in ( getLCtx) do
if let some score := matchAtomic id localDecl.userName danglingDot then
addUnresolvedCompletionItem localDecl.userName (.fvar localDecl.fvarId) (kind := CompletionItemKind.variable) score
-- search for matches in the environment
let env getEnv
forEligibleDeclsM fun declName c => do
let bestMatch? (·.2) <$> StateT.run (s := none) do
let matchUsingNamespace (ns : Name) : StateT (Option (Name × Float)) M Unit := do
let some (label, score) matchDecl? ns id danglingDot declName
| return
modify fun
| none =>
some (label, score)
| some (bestLabel, bestScore) =>
-- for open namespaces `A` and `A.B` and a decl `A.B.c`, pick the decl `c` over `B.c`
if label.isSuffixOf bestLabel then
some (label, score)
else
some (bestLabel, bestScore)
let rec visitNamespaces (ns : Name) : StateT (Option (Name × Float)) M Unit := do
let Name.str p .. := ns
| return ()
matchUsingNamespace ns
visitNamespaces p
-- use current namespace
visitNamespaces ctx.currNamespace
-- use open decls
for openDecl in ctx.openDecls do
let OpenDecl.simple ns exs := openDecl
| pure ()
if exs.contains declName then
continue
matchUsingNamespace ns
matchUsingNamespace Name.anonymous
if let some (bestLabel, bestScore) := bestMatch? then
addUnresolvedCompletionItem bestLabel (.const declName) ( getCompletionKindForDecl c) bestScore
let matchAlias (ns : Name) (alias : Name) : Option Float :=
-- Recall that aliases may not be atomic and include the namespace where they were created.
if ns.isPrefixOf alias then
let alias := alias.replacePrefix ns Name.anonymous
matchAtomic id alias danglingDot
else
none
let eligibleHeaderDecls getEligibleHeaderDecls env
-- Auxiliary function for `alias`
let addAlias (alias : Name) (declNames : List Name) (score : Float) : M Unit := do
declNames.forM fun declName => do
if allowCompletion eligibleHeaderDecls env declName then
addUnresolvedCompletionItemForDecl (.mkSimple alias.getString!) declName score
-- search explicitly open `ids`
for openDecl in ctx.openDecls do
match openDecl with
| OpenDecl.explicit openedId resolvedId =>
if allowCompletion eligibleHeaderDecls env resolvedId then
if let some score := matchAtomic id openedId danglingDot then
addUnresolvedCompletionItemForDecl (.mkSimple openedId.getString!) resolvedId score
| OpenDecl.simple ns _ =>
getAliasState env |>.forM fun alias declNames => do
if let some score := matchAlias ns alias then
addAlias alias declNames score
-- search for aliases
getAliasState env |>.forM fun alias declNames => do
-- use current namespace
let rec searchAlias (ns : Name) : M Unit := do
if let some score := matchAlias ns alias then
addAlias alias declNames score
else
match ns with
| Name.str p .. => searchAlias p
| _ => return ()
searchAlias ctx.currNamespace
-- Search keywords
if !danglingDot then
if let .str .anonymous s := id then
let keywords := Parser.getTokenTable env
for keyword in keywords.findPrefix s do
if let some score := fuzzyMatchScoreWithThreshold? s keyword then
addKeywordCompletionItem keyword score
-- Search namespaces
completeNamespaces ctx id danglingDot
def idCompletion
(params : CompletionParams)
(completionInfoPos : Nat)
(ctx : ContextInfo)
(lctx : LocalContext)
(stx : Syntax)
(id : Name)
(hoverInfo : HoverInfo)
(danglingDot : Bool)
: IO (Array ScoredCompletionItem) :=
runM params completionInfoPos ctx lctx do
idCompletionCore ctx stx id hoverInfo danglingDot
def dotCompletion
(params : CompletionParams)
(completionInfoPos : Nat)
(ctx : ContextInfo)
(info : TermInfo)
: IO (Array ScoredCompletionItem) :=
runM params completionInfoPos ctx info.lctx do
let nameSet try
getDotCompletionTypeNames ( instantiateMVars ( inferType info.expr))
catch _ =>
pure RBTree.empty
if nameSet.isEmpty then
return
forEligibleDeclsM fun declName c => do
let unnormedTypeName := declName.getPrefix
if ! nameSet.contains unnormedTypeName then
return
let some declName normPrivateName? declName
| return
let typeName := declName.getPrefix
if ! ( isDotCompletionMethod typeName c) then
return
let completionKind getCompletionKindForDecl c
addUnresolvedCompletionItem (.mkSimple c.name.getString!) (.const c.name) (kind := completionKind) 1
def dotIdCompletion
(params : CompletionParams)
(completionInfoPos : Nat)
(ctx : ContextInfo)
(lctx : LocalContext)
(id : Name)
(expectedType? : Option Expr)
: IO (Array ScoredCompletionItem) :=
runM params completionInfoPos ctx lctx do
let some expectedType := expectedType?
| return ()
let resultTypeFn := ( instantiateMVars expectedType).cleanupAnnotations.getAppFn.cleanupAnnotations
let .const .. := resultTypeFn
| return ()
let nameSet try
getDotCompletionTypeNames resultTypeFn
catch _ =>
pure RBTree.empty
forEligibleDeclsM fun declName c => do
let unnormedTypeName := declName.getPrefix
if ! nameSet.contains unnormedTypeName then
return
let some declName normPrivateName? declName
| return
let typeName := declName.getPrefix
if ! ( isDotIdCompletionMethod typeName c) then
return
let completionKind getCompletionKindForDecl c
if id.isAnonymous then
-- We're completing a lone dot => offer all decls of the type
addUnresolvedCompletionItem (.mkSimple c.name.getString!) (.const c.name) completionKind 1
return
let some (label, score) matchDecl? typeName id (danglingDot := false) declName | pure ()
addUnresolvedCompletionItem label (.const c.name) completionKind score
def fieldIdCompletion
(params : CompletionParams)
(completionInfoPos : Nat)
(ctx : ContextInfo)
(lctx : LocalContext)
(id : Option Name)
(structName : Name)
: IO (Array ScoredCompletionItem) :=
runM params completionInfoPos ctx lctx do
let idStr := id.map (·.toString) |>.getD ""
let fieldNames := getStructureFieldsFlattened ( getEnv) structName (includeSubobjectFields := false)
for fieldName in fieldNames do
let .str _ fieldName := fieldName | continue
let some score := fuzzyMatchScoreWithThreshold? idStr fieldName | continue
let item := { label := fieldName, detail? := "field", documentation? := none, kind? := CompletionItemKind.field }
addItem item score
def optionCompletion
(params : CompletionParams)
(completionInfoPos : Nat)
(ctx : ContextInfo)
(stx : Syntax)
(caps : ClientCapabilities)
: IO (Array ScoredCompletionItem) :=
ctx.runMetaM {} do
let (partialName, trailingDot) :=
-- `stx` is from `"set_option" >> ident`
match stx[1].getSubstring? (withLeading := false) (withTrailing := false) with
| none => ("", false) -- the `ident` is `missing`, list all options
| some ss =>
if !ss.str.atEnd ss.stopPos && ss.str.get ss.stopPos == '.' then
-- include trailing dot, which is not parsed by `ident`
(ss.toString ++ ".", true)
else
(ss.toString, false)
-- HACK(WN): unfold the type so ForIn works
let (decls : RBMap _ _ _) getOptionDecls
let opts getOptions
let mut items := #[]
for name, decl in decls do
if let some score := fuzzyMatchScoreWithThreshold? partialName name.toString then
let textEdit :=
if !caps.textDocument?.any (·.completion?.any (·.completionItem?.any (·.insertReplaceSupport?.any (·)))) then
none -- InsertReplaceEdit not supported by client
else if let some start, stop := stx[1].getRange? then
let stop := if trailingDot then stop + ' ' else stop
let range := ctx.fileMap.utf8PosToLspPos start, ctx.fileMap.utf8PosToLspPos stop
some { newText := name.toString, insert := range, replace := range : InsertReplaceEdit }
else
none
items := items.push {
label := name.toString
detail? := s!"({opts.get name decl.defValue}), {decl.descr}"
documentation? := none,
kind? := CompletionItemKind.property -- TODO: investigate whether this is the best kind for options.
textEdit? := textEdit
data? := toJson {
params,
cPos := completionInfoPos,
id? := none : ResolvableCompletionItemData
}
}, score
return items
def tacticCompletion
(params : CompletionParams)
(completionInfoPos : Nat)
(ctx : ContextInfo)
: IO (Array ScoredCompletionItem) := ctx.runMetaM .empty do
let allTacticDocs Tactic.Doc.allTacticDocs
let items : Array ScoredCompletionItem := allTacticDocs.map fun tacticDoc =>
{
label := tacticDoc.userName
detail? := none
documentation? := tacticDoc.docString.map fun docString =>
{ value := docString, kind := MarkupKind.markdown : MarkupContent }
kind? := CompletionItemKind.keyword
data? := toJson { params, cPos := completionInfoPos, id? := none : ResolvableCompletionItemData }
}, 1
return items
end Lean.Server.Completion

View File

@@ -0,0 +1,131 @@
/-
Copyright (c) 2024 Lean FRO, LLC. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Leonardo de Moura, Marc Huisinga
-/
prelude
import Lean.Server.Completion.SyntheticCompletion
namespace Lean.Server.Completion
open Elab
private def filterDuplicateCompletionInfos
(infos : Array ContextualizedCompletionInfo)
: Array ContextualizedCompletionInfo := Id.run do
-- We don't expect there to be too many duplicate completion infos,
-- so it's fine if this is quadratic (we don't need to implement `Hashable` / `LT` this way).
let mut deduplicatedInfos : Array ContextualizedCompletionInfo := #[]
for i in infos do
if deduplicatedInfos.any (fun di => eq di.info i.info) then
continue
deduplicatedInfos := deduplicatedInfos.push i
deduplicatedInfos
where
eq : CompletionInfo CompletionInfo Bool
| .dot ti₁ .., .dot ti₂ .. =>
ti₁.stx.eqWithInfo ti₂.stx && ti₁.expr == ti₂.expr
| .id stx₁ id₁ .., .id stx₂ id₂ .. =>
stx₁.eqWithInfo stx₂ && id₁ == id₂
| .dotId stx₁ id₁ .., .id stx₂ id₂ .. =>
stx₁.eqWithInfo stx₂ && id₁ == id₂
| .fieldId stx₁ id₁? _ structName₁, .fieldId stx₂ id₂? _ structName₂ =>
stx₁.eqWithInfo stx₂ && id₁? == id₂? && structName₁ == structName₂
| .namespaceId stx₁, .namespaceId stx₂ =>
stx₁.eqWithInfo stx₂
| .option stx₁, .option stx₂ =>
stx₁.eqWithInfo stx₂
| .endSection stx₁ scopeNames₁, .endSection stx₂ scopeNames₂ =>
stx₁.eqWithInfo stx₂ && scopeNames₁ == scopeNames₂
| .tactic stx₁, .tactic stx₂ =>
stx₁.eqWithInfo stx₂
| _, _ =>
false
def findCompletionInfosAt
(fileMap : FileMap)
(hoverPos : String.Pos)
(cmdStx : Syntax)
(infoTree : InfoTree)
: Array ContextualizedCompletionInfo := Id.run do
let hoverLine, _ := fileMap.toPosition hoverPos
let mut completionInfoCandidates := infoTree.foldInfo (init := #[]) (go hoverLine)
if completionInfoCandidates.isEmpty then
completionInfoCandidates := findSyntheticCompletions fileMap hoverPos cmdStx infoTree
return filterDuplicateCompletionInfos completionInfoCandidates
where
go
(hoverLine : Nat)
(ctx : ContextInfo)
(info : Info)
(best : Array ContextualizedCompletionInfo)
: Array ContextualizedCompletionInfo := Id.run do
let .ofCompletionInfo completionInfo := info
| return best
if ! info.occursInOrOnBoundary hoverPos then
return best
let headPos := info.pos?.get!
let tailPos := info.tailPos?.get!
let hoverInfo :=
if hoverPos < tailPos then
HoverInfo.inside (hoverPos - headPos).byteIdx
else
HoverInfo.after
let headPosLine, _ := fileMap.toPosition headPos
let tailPosLine, _ := fileMap.toPosition info.tailPos?.get!
if headPosLine != hoverLine || headPosLine != tailPosLine then
return best
return best.push { hoverInfo, ctx, info := completionInfo }
private def computePrioritizedCompletionPartitions
(items : Array (ContextualizedCompletionInfo × Nat))
: Array (Array (ContextualizedCompletionInfo × Nat)) :=
let partitions := items.groupByKey fun (i, _) =>
let isId := i.info matches .id ..
let size? := Info.ofCompletionInfo i.info |>.size?
(isId, size?)
-- Sort partitions so that non-id completions infos come before id completion infos and
-- within those two groups, smaller sizes come before larger sizes.
let partitionsByPriority := partitions.toArray.qsort
fun ((isId₁, size₁?), _) ((isId₂, size₂?), _) =>
match size₁?, size₂? with
| some _, none => true
| none, some _ => false
| _, _ =>
match isId₁, isId₂ with
| false, true => true
| true, false => false
| _, _ => Id.run do
let some size₁ := size₁?
| return false
let some size₂ := size₂?
| return false
return size₁ < size₂
partitionsByPriority.map (·.2)
/--
Finds all `CompletionInfo`s (both from the `InfoTree` and synthetic ones), prioritizes them,
arranges them in partitions of `CompletionInfo`s with the same priority and sorts these partitions
so that `CompletionInfo`s with the highest priority come first.
The returned `CompletionInfo`s are also tagged with their index in `findCompletionInfosAt` so that
when resolving a `CompletionItem`, we can reconstruct which `CompletionInfo` it was created from.
In general, the `InfoTree` may contain multiple different `CompletionInfo`s covering `hoverPos`,
and so we need to decide which of these `CompletionInfo`s we want to use to show completions to the
user. We choose priorities by the following rules:
- Synthetic completions have the lowest priority since they are only intended as a backup.
- Non-identifier completions have the highest priority since they tend to be much more helpful than
identifier completions when available since there are typically way too many of the latter.
- Within the three groups [non-id completions, id completions, synthetic completions],
`CompletionInfo`s with a smaller range are considered to be better.
-/
def findPrioritizedCompletionPartitionsAt
(fileMap : FileMap)
(hoverPos : String.Pos)
(cmdStx : Syntax)
(infoTree : InfoTree)
: Array (Array (ContextualizedCompletionInfo × Nat)) :=
findCompletionInfosAt fileMap hoverPos cmdStx infoTree
|>.zipWithIndex
|> computePrioritizedCompletionPartitions
end Lean.Server.Completion

View File

@@ -0,0 +1,91 @@
/-
Copyright (c) 2024 Lean FRO, LLC. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Leonardo de Moura, Marc Huisinga
-/
prelude
import Lean.Server.Completion.CompletionItemData
import Lean.Server.Completion.CompletionInfoSelection
namespace Lean.Lsp
/--
Identifier that is sent from the server to the client as part of the `CompletionItem.data?` field.
Needed to resolve the `CompletionItem` when the client sends a `completionItem/resolve` request
for that item, again containing the `data?` field provided by the server.
-/
inductive CompletionIdentifier where
| const (declName : Name)
| fvar (id : FVarId)
deriving FromJson, ToJson
/--
`CompletionItemData` that contains additional information to identify the item
in order to resolve it.
-/
structure ResolvableCompletionItemData extends CompletionItemData where
/-- Position of the completion info that this completion item was created from. -/
cPos : Nat
id? : Option CompletionIdentifier
deriving FromJson, ToJson
private partial def consumeImplicitPrefix (e : Expr) (k : Expr MetaM α) : MetaM α := do
match e with
| Expr.forallE n d b c =>
-- We do not consume instance implicit arguments because the user probably wants be aware of this dependency
if c == .implicit then
Meta.withLocalDecl n c d fun arg =>
consumeImplicitPrefix (b.instantiate1 arg) k
else
k e
| _ => k e
/--
Fills the `CompletionItem.detail?` field of `item` using the pretty-printed type identified by `id`.
-/
def CompletionItem.resolve
(item : CompletionItem)
(id : CompletionIdentifier)
: MetaM CompletionItem := do
let env getEnv
let lctx getLCtx
let mut item := item
if item.detail?.isNone then
let type? := match id with
| .const declName =>
env.find? declName |>.map ConstantInfo.type
| .fvar id =>
lctx.find? id |>.map LocalDecl.type
let detail? type?.mapM fun type =>
consumeImplicitPrefix type fun typeWithoutImplicits =>
return toString ( Meta.ppExpr typeWithoutImplicits)
item := { item with detail? := detail? }
return item
end Lean.Lsp
namespace Lean.Server.Completion
open Lean.Lsp
open Elab
/--
Fills the `CompletionItem.detail?` field of `item` using the pretty-printed type identified by `id`
in the context found at `hoverPos` in `infoTree`.
-/
def resolveCompletionItem?
(fileMap : FileMap)
(hoverPos : String.Pos)
(cmdStx : Syntax)
(infoTree : InfoTree)
(item : CompletionItem)
(id : CompletionIdentifier)
(completionInfoPos : Nat)
: IO CompletionItem := do
let completionInfos := findCompletionInfosAt fileMap hoverPos cmdStx infoTree
let some i := completionInfos.get? completionInfoPos
| return item
i.ctx.runMetaM i.info.lctx (item.resolve id)
end Lean.Server.Completion

View File

@@ -0,0 +1,22 @@
/-
Copyright (c) 2024 Lean FRO, LLC. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Leonardo de Moura, Marc Huisinga
-/
prelude
import Init.Prelude
import Lean.Elab.InfoTree.Types
namespace Lean.Server.Completion
open Elab
inductive HoverInfo : Type where
| after
| inside (delta : Nat)
structure ContextualizedCompletionInfo where
hoverInfo : HoverInfo
ctx : ContextInfo
info : CompletionInfo
end Lean.Server.Completion

View File

@@ -0,0 +1,53 @@
/-
Copyright (c) 2024 Lean FRO, LLC. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Marc Huisinga
-/
prelude
import Lean.Meta.CompletionName
namespace Lean.Server.Completion
open Meta
abbrev EligibleHeaderDecls := Std.HashMap Name ConstantInfo
/-- Cached header declarations for which `allowCompletion headerEnv decl` is true. -/
builtin_initialize eligibleHeaderDeclsRef : IO.Ref (Option EligibleHeaderDecls)
IO.mkRef none
/--
Returns the declarations in the header for which `allowCompletion env decl` is true, caching them
if not already cached.
-/
def getEligibleHeaderDecls (env : Environment) : IO EligibleHeaderDecls := do
eligibleHeaderDeclsRef.modifyGet fun
| some eligibleHeaderDecls => (eligibleHeaderDecls, some eligibleHeaderDecls)
| none =>
let (_, eligibleHeaderDecls) :=
StateT.run (m := Id) (s := {}) do
-- `map₁` are the header decls
env.constants.map₁.forM fun declName c => do
modify fun eligibleHeaderDecls =>
if allowCompletion env declName then
eligibleHeaderDecls.insert declName c
else
eligibleHeaderDecls
(eligibleHeaderDecls, some eligibleHeaderDecls)
/-- Iterate over all declarations that are allowed in completion results. -/
def forEligibleDeclsM [Monad m] [MonadEnv m] [MonadLiftT (ST IO.RealWorld) m]
[MonadLiftT IO m] (f : Name ConstantInfo m PUnit) : m PUnit := do
let env getEnv
( getEligibleHeaderDecls env).forM f
-- map₂ are exactly the local decls
env.constants.map₂.forM fun name c => do
if allowCompletion env name then
f name c
/-- Checks whether this declaration can appear in completion results. -/
def allowCompletion (eligibleHeaderDecls : EligibleHeaderDecls) (env : Environment)
(declName : Name) : Bool :=
eligibleHeaderDecls.contains declName ||
env.constants.map₂.contains declName && Lean.Meta.allowCompletion env declName
end Lean.Server.Completion

View File

@@ -4,13 +4,10 @@ Released under Apache 2.0 license as described in the file LICENSE.
Authors: Marc Huisinga
-/
prelude
import Lean.Data.Name
import Lean.Data.NameTrie
import Lean.Data.Lsp.Utf16
import Lean.Data.Lsp.LanguageFeatures
import Lean.Util.Paths
import Lean.Util.LakePath
import Lean.Server.CompletionItemData
import Lean.Server.Completion.CompletionItemData
namespace ImportCompletion

View File

@@ -0,0 +1,396 @@
/-
Copyright (c) 2024 Lean FRO, LLC. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Marc Huisinga
-/
prelude
import Lean.Server.InfoUtils
import Lean.Server.Completion.CompletionUtils
namespace Lean.Server.Completion
open Elab
private def findBest?
(infoTree : InfoTree)
(gt : α α Bool)
(f : ContextInfo Info PersistentArray InfoTree Option α)
: Option α :=
infoTree.visitM (m := Id) (postNode := choose) |>.join
where
choose
(ctx : ContextInfo)
(info : Info)
(cs : PersistentArray InfoTree)
(childValues : List (Option (Option α)))
: Option α :=
let bestChildValue := childValues.map (·.join) |>.foldl (init := none) fun v best =>
if isBetter v best then
v
else
best
if let some v := f ctx info cs then
if isBetter v bestChildValue then
v
else
bestChildValue
else
bestChildValue
isBetter (a b : Option α) : Bool :=
match a, b with
| none, none => false
| some _, none => true
| none, some _ => false
| some a, some b => gt a b
/--
If there are `Info`s that contain `hoverPos` and have a nonempty `LocalContext`,
yields the closest one of those `Info`s.
Otherwise, yields the closest `Info` that contains `hoverPos` and has an empty `LocalContext`.
-/
private def findClosestInfoWithLocalContextAt?
(hoverPos : String.Pos)
(infoTree : InfoTree)
: Option (ContextInfo × Info) :=
findBest? infoTree isBetter fun ctx info _ =>
if info.occursInOrOnBoundary hoverPos then
(ctx, info)
else
none
where
isBetter (a b : ContextInfo × Info) : Bool :=
let (_, ia) := a
let (_, ib) := b
if !ia.lctx.isEmpty && ib.lctx.isEmpty then
true
else if ia.lctx.isEmpty && !ib.lctx.isEmpty then
false
else if ia.isSmaller ib then
true
else if ib.isSmaller ia then
false
else
false
private def findSyntheticIdentifierCompletion?
(hoverPos : String.Pos)
(infoTree : InfoTree)
: Option ContextualizedCompletionInfo := do
let some (ctx, info) := findClosestInfoWithLocalContextAt? hoverPos infoTree
| none
let some stack := info.stx.findStack? (·.getRange?.any (·.contains hoverPos (includeStop := true)))
| none
let stack := stack.dropWhile fun (stx, _) => !(stx matches `($_:ident) || stx matches `($_:ident.))
let some (stx, _) := stack.head?
| none
let isDotIdCompletion := stack.any fun (stx, _) => stx matches `(.$_:ident)
if isDotIdCompletion then
-- An identifier completion is never useful in a dotId completion context.
none
let some (id, danglingDot) :=
match stx with
| `($id:ident) => some (id.getId, false)
| `($id:ident.) => some (id.getId, true)
| _ => none
| none
let tailPos := stx.getTailPos?.get!
let hoverInfo :=
if hoverPos < tailPos then
HoverInfo.inside (tailPos - hoverPos).byteIdx
else
HoverInfo.after
some { hoverInfo, ctx, info := .id stx id danglingDot info.lctx none }
private partial def getIndentationAmount (fileMap : FileMap) (line : Nat) : Nat := Id.run do
let lineStartPos := fileMap.lineStart line
let lineEndPos := fileMap.lineStart (line + 1)
let mut it : String.Iterator := fileMap.source, lineStartPos
let mut indentationAmount := 0
while it.pos < lineEndPos do
let c := it.curr
if c = ' ' || c = '\t' then
indentationAmount := indentationAmount + 1
else
break
it := it.next
return indentationAmount
private partial def isCursorOnWhitespace (fileMap : FileMap) (hoverPos : String.Pos) : Bool :=
fileMap.source.atEnd hoverPos || (fileMap.source.get hoverPos).isWhitespace
private partial def isCursorInProperWhitespace (fileMap : FileMap) (hoverPos : String.Pos) : Bool :=
(fileMap.source.atEnd hoverPos || (fileMap.source.get hoverPos).isWhitespace)
&& (fileMap.source.get (hoverPos - 1)).isWhitespace
private partial def isSyntheticTacticCompletion
(fileMap : FileMap)
(hoverPos : String.Pos)
(cmdStx : Syntax)
: Bool := Id.run do
let hoverFilePos := fileMap.toPosition hoverPos
let mut hoverLineIndentation := getIndentationAmount fileMap hoverFilePos.line
if hoverFilePos.column < hoverLineIndentation then
-- Ignore trailing whitespace after the cursor
hoverLineIndentation := hoverFilePos.column
go hoverFilePos hoverLineIndentation cmdStx 0
where
go
(hoverFilePos : Position)
(hoverLineIndentation : Nat)
(stx : Syntax)
(leadingWs : Nat)
: Bool := Id.run do
match stx.getPos?, stx.getTailPos? with
| some startPos, some endPos =>
let isCursorInCompletionRange :=
startPos.byteIdx - leadingWs <= hoverPos.byteIdx
&& hoverPos.byteIdx <= endPos.byteIdx + stx.getTrailingSize
if ! isCursorInCompletionRange then
return false
let mut wsBeforeArg := leadingWs
for arg in stx.getArgs do
if go hoverFilePos hoverLineIndentation arg wsBeforeArg then
return true
-- We must account for the whitespace before an argument because the syntax nodes we use
-- to identify tactic blocks only start *after* the whitespace following a `by`, and we
-- want to provide tactic completions in that whitespace as well.
-- This method of computing whitespace assumes that there are no syntax nodes without tokens
-- after `by` and before the first proper tactic syntax.
wsBeforeArg := arg.getTrailingSize
return isCompletionInEmptyTacticBlock stx
|| isCompletionAfterSemicolon stx
|| isCompletionOnTacticBlockIndentation hoverFilePos hoverLineIndentation stx
| _, _ =>
-- Empty tactic blocks typically lack ranges since they do not contain any tokens.
-- We do not perform more precise range checking in this case because we assume that empty
-- tactic blocks always occur within other syntax with ranges that let us narrow down the
-- search to the degree that we can be sure that the cursor is indeed in this empty tactic
-- block.
return isCompletionInEmptyTacticBlock stx
isCompletionOnTacticBlockIndentation
(hoverFilePos : Position)
(hoverLineIndentation : Nat)
(stx : Syntax)
: Bool := Id.run do
let isCursorInIndentation := hoverFilePos.column <= hoverLineIndentation
if ! isCursorInIndentation then
-- Do not trigger tactic completion at the end of a properly indented tactic block line since
-- that line might already have entered term mode by that point.
return false
let some tacticsNode := getTacticsNode? stx
| return false
let some firstTacticPos := tacticsNode.getPos?
| return false
let firstTacticLine := fileMap.toPosition firstTacticPos |>.line
let firstTacticIndentation := getIndentationAmount fileMap firstTacticLine
-- This ensures that we do not accidentally provide tactic completions in a term mode proof -
-- tactic completions are only provided at the same indentation level as the other tactics in
-- that tactic block.
let isCursorInTacticBlock := hoverLineIndentation == firstTacticIndentation
return isCursorInProperWhitespace fileMap hoverPos && isCursorInTacticBlock
isCompletionAfterSemicolon (stx : Syntax) : Bool := Id.run do
let some tacticsNode := getTacticsNode? stx
| return false
let tactics := tacticsNode.getArgs
-- We want to provide completions in the case of `skip;<CURSOR>`, so the cursor must only be on
-- whitespace, not in proper whitespace.
return isCursorOnWhitespace fileMap hoverPos && tactics.any fun tactic => Id.run do
let some tailPos := tactic.getTailPos?
| return false
let isCursorAfterSemicolon :=
tactic.isToken ";"
&& tailPos.byteIdx <= hoverPos.byteIdx
&& hoverPos.byteIdx <= tailPos.byteIdx + tactic.getTrailingSize
return isCursorAfterSemicolon
getTacticsNode? (stx : Syntax) : Option Syntax :=
if stx.getKind == ``Parser.Tactic.tacticSeq1Indented then
some stx[0]
else if stx.getKind == ``Parser.Tactic.tacticSeqBracketed then
some stx[1]
else
none
isCompletionInEmptyTacticBlock (stx : Syntax) : Bool :=
isCursorInProperWhitespace fileMap hoverPos && isEmptyTacticBlock stx
isEmptyTacticBlock (stx : Syntax) : Bool :=
stx.getKind == ``Parser.Tactic.tacticSeq && isEmpty stx
|| stx.getKind == ``Parser.Tactic.tacticSeq1Indented && isEmpty stx
|| stx.getKind == ``Parser.Tactic.tacticSeqBracketed && isEmpty stx[1]
isEmpty : Syntax Bool
| .missing => true
| .ident .. => false
| .atom .. => false
| .node _ _ args => args.all isEmpty
private partial def findOutermostContextInfo? (i : InfoTree) : Option ContextInfo :=
go i
where
go (i : InfoTree) : Option ContextInfo := do
match i with
| .context ctx i =>
match ctx with
| .commandCtx ctxInfo =>
some { ctxInfo with }
| _ =>
-- This shouldn't happen (see the `PartialContextInfo` docstring),
-- but let's continue searching regardless
go i
| .node _ cs =>
cs.findSome? go
| .hole .. =>
none
private def findSyntheticTacticCompletion?
(fileMap : FileMap)
(hoverPos : String.Pos)
(cmdStx : Syntax)
(infoTree : InfoTree)
: Option ContextualizedCompletionInfo := do
let ctx findOutermostContextInfo? infoTree
if ! isSyntheticTacticCompletion fileMap hoverPos cmdStx then
none
-- Neither `HoverInfo` nor the syntax in `.tactic` are important for tactic completion.
return { hoverInfo := HoverInfo.after, ctx, info := .tactic .missing }
private def findExpectedTypeAt (infoTree : InfoTree) (hoverPos : String.Pos) : Option (ContextInfo × Expr) := do
let (ctx, .ofTermInfo i) infoTree.smallestInfo? fun i => Id.run do
let some pos := i.pos?
| return false
let some tailPos := i.tailPos?
| return false
let .ofTermInfo ti := i
| return false
return ti.expectedType?.isSome && pos <= hoverPos && hoverPos <= tailPos
| none
(ctx, i.expectedType?.get!)
private partial def foldWithLeadingToken [Inhabited α]
(f : α Option Syntax Syntax α)
(init : α)
(stx : Syntax)
: α :=
let (_, r) := go none init stx
r
where
go [Inhabited α] (leadingToken? : Option Syntax) (acc : α) (stx : Syntax) : Option Syntax × α :=
let acc := f acc leadingToken? stx
match stx with
| .missing => (none, acc)
| .atom .. => (stx, acc)
| .ident .. => (stx, acc)
| .node _ _ args => Id.run do
let mut acc := acc
let mut lastToken? := none
for arg in args do
let (lastToken'?, acc') := go (lastToken? <|> leadingToken?) acc arg
lastToken? := lastToken'? <|> lastToken?
acc := acc'
return (lastToken?, acc)
private def findWithLeadingToken?
(p : Option Syntax Syntax Bool)
(stx : Syntax)
: Option Syntax :=
foldWithLeadingToken (stx := stx) (init := none) fun foundStx? leadingToken? stx =>
match foundStx? with
| some foundStx => foundStx
| none =>
if p leadingToken? stx then
some stx
else
none
private def isSyntheticStructFieldCompletion
(fileMap : FileMap)
(hoverPos : String.Pos)
(cmdStx : Syntax)
: Bool := Id.run do
let isCursorOnWhitespace := isCursorOnWhitespace fileMap hoverPos
let isCursorInProperWhitespace := isCursorInProperWhitespace fileMap hoverPos
if ! isCursorOnWhitespace then
return false
let hoverFilePos := fileMap.toPosition hoverPos
let mut hoverLineIndentation := getIndentationAmount fileMap hoverFilePos.line
if hoverFilePos.column < hoverLineIndentation then
-- Ignore trailing whitespace after the cursor
hoverLineIndentation := hoverFilePos.column
return Option.isSome <| findWithLeadingToken? (stx := cmdStx) fun leadingToken? stx => Id.run do
let some leadingToken := leadingToken?
| return false
if stx.getKind != ``Parser.Term.structInstFields then
return false
let fieldsAndSeps := stx[0].getArgs
let some outerBoundsStart := leadingToken.getTailPos? (canonicalOnly := true)
| return false
let some outerBoundsStop :=
stx.getTrailingTailPos? (canonicalOnly := true)
<|> leadingToken.getTrailingTailPos? (canonicalOnly := true)
| return false
let outerBounds : String.Range := outerBoundsStart, outerBoundsStop
let isCompletionInEmptyBlock :=
fieldsAndSeps.isEmpty && outerBounds.contains hoverPos (includeStop := true)
if isCompletionInEmptyBlock then
return true
let isCompletionAfterSep := fieldsAndSeps.zipWithIndex.any fun (fieldOrSep, i) => Id.run do
if i % 2 == 0 || !fieldOrSep.isAtom then
return false
let sep := fieldOrSep
let some sepTailPos := sep.getTailPos?
| return false
return sepTailPos <= hoverPos
&& hoverPos.byteIdx <= sepTailPos.byteIdx + sep.getTrailingSize
if isCompletionAfterSep then
return true
let isCompletionOnIndentation := Id.run do
if ! isCursorInProperWhitespace then
return false
let isCursorInIndentation := hoverFilePos.column <= hoverLineIndentation
if ! isCursorInIndentation then
return false
let some firstFieldPos := stx.getPos?
| return false
let firstFieldLine := fileMap.toPosition firstFieldPos |>.line
let firstFieldIndentation := getIndentationAmount fileMap firstFieldLine
let isCursorInBlock := hoverLineIndentation == firstFieldIndentation
return isCursorInBlock
return isCompletionOnIndentation
private def findSyntheticFieldCompletion?
(fileMap : FileMap)
(hoverPos : String.Pos)
(cmdStx : Syntax)
(infoTree : InfoTree)
: Option ContextualizedCompletionInfo := do
if ! isSyntheticStructFieldCompletion fileMap hoverPos cmdStx then
none
let (ctx, expectedType) findExpectedTypeAt infoTree hoverPos
let .const typeName _ := expectedType.getAppFn
| none
if ! isStructure ctx.env typeName then
none
return { hoverInfo := HoverInfo.after, ctx, info := .fieldId .missing none .empty typeName }
def findSyntheticCompletions
(fileMap : FileMap)
(hoverPos : String.Pos)
(cmdStx : Syntax)
(infoTree : InfoTree)
: Array ContextualizedCompletionInfo :=
let syntheticCompletionData? : Option ContextualizedCompletionInfo :=
findSyntheticTacticCompletion? fileMap hoverPos cmdStx infoTree <|>
findSyntheticFieldCompletion? fileMap hoverPos cmdStx infoTree <|>
findSyntheticIdentifierCompletion? hoverPos infoTree
syntheticCompletionData?.map (#[·]) |>.getD #[]
end Lean.Server.Completion

View File

@@ -28,7 +28,7 @@ import Lean.Server.FileWorker.WidgetRequests
import Lean.Server.FileWorker.SetupFile
import Lean.Server.Rpc.Basic
import Lean.Widget.InteractiveDiagnostic
import Lean.Server.ImportCompletion
import Lean.Server.Completion.ImportCompletion
/-!
For general server architecture, see `README.md`. For details of IPC communication, see `Watchdog.lean`.

View File

@@ -46,10 +46,11 @@ def handleCompletion (p : CompletionParams)
mapTask (findCompletionCmdDataAtPos doc pos) fun cmdData? => do
let some (cmdStx, infoTree) := cmdData?
-- work around https://github.com/microsoft/vscode/issues/155738
| return { items := #[{label := "-"}], isIncomplete := true }
if let some r Completion.find? p doc.meta.text pos cmdStx infoTree caps then
return r
return { items := #[ ], isIncomplete := true }
| return {
items := #[{label := "-", data? := toJson { params := p : Lean.Lsp.CompletionItemData }}],
isIncomplete := true
}
Completion.find? p doc.meta.text pos cmdStx infoTree caps
/--
Handles `completionItem/resolve` requests that are sent by the client after the user selects
@@ -62,7 +63,7 @@ def handleCompletionItemResolve (item : CompletionItem)
: RequestM (RequestTask CompletionItem) := do
let doc readDoc
let text := doc.meta.text
let some (data : CompletionItemDataWithId) := item.data?.bind fun data => (fromJson? data).toOption
let some (data : ResolvableCompletionItemData) := item.data?.bind fun data => (fromJson? data).toOption
| return .pure item
let some id := data.id?
| return .pure item
@@ -70,7 +71,7 @@ def handleCompletionItemResolve (item : CompletionItem)
mapTask (findCompletionCmdDataAtPos doc pos) fun cmdData? => do
let some (cmdStx, infoTree) := cmdData?
| return item
Completion.resolveCompletionItem? text pos cmdStx infoTree item id
Completion.resolveCompletionItem? text pos cmdStx infoTree item id data.cPos
open Elab in
def handleHover (p : HoverParams)

View File

@@ -136,6 +136,7 @@ def InfoTree.getCompletionInfos (infoTree : InfoTree) : Array (ContextInfo × Co
def Info.stx : Info Syntax
| ofTacticInfo i => i.stx
| ofTermInfo i => i.stx
| ofPartialTermInfo i => i.stx
| ofCommandInfo i => i.stx
| ofMacroExpansionInfo i => i.stx
| ofOptionInfo i => i.stx
@@ -146,6 +147,7 @@ def Info.stx : Info → Syntax
| ofFVarAliasInfo _ => .missing
| ofFieldRedeclInfo i => i.stx
| ofOmissionInfo i => i.stx
| ofChoiceInfo i => i.stx
def Info.lctx : Info LocalContext
| .ofTermInfo i => i.lctx

View File

@@ -815,7 +815,7 @@ theorem confirmRupHint_preserves_invariant_helper {n : Nat} (f : DefaultFormula
have k'_in_bounds : k' < acc.2.1.length := by
simp only [List.length_cons, Nat.succ_eq_add_one] at k'_succ_in_bounds
exact Nat.lt_of_succ_lt_succ k'_succ_in_bounds
exact h2 (acc.2.1.get k', k'_in_bounds) <| List.get_mem acc.snd.fst k' k'_in_bounds
exact h2 (acc.2.1.get k', k'_in_bounds) <| List.get_mem acc.snd.fst k', k'_in_bounds
· next l_ne_i =>
apply Or.inl
constructor

View File

@@ -99,17 +99,5 @@ attribute [bv_normalize] BitVec.mul_eq
attribute [bv_normalize] BitVec.udiv_eq
attribute [bv_normalize] BitVec.umod_eq
@[bv_normalize]
theorem Bool.and_eq_and (x y : Bool) : x.and y = (x && y) := by
rfl
@[bv_normalize]
theorem Bool.or_eq_or (x y : Bool) : x.or y = (x || y) := by
rfl
@[bv_normalize]
theorem Bool.no_eq_not (x : Bool) : x.not = !x := by
rfl
end Normalize
end Std.Tactic.BVDecide

View File

@@ -37,7 +37,15 @@ extern "C" {
#if defined(__GNUC__) || defined(__clang__)
#define LEAN_UNLIKELY(x) (__builtin_expect((x), 0))
#define LEAN_LIKELY(x) (__builtin_expect((x), 1))
#ifdef NDEBUG
#define LEAN_ALWAYS_INLINE __attribute__((always_inline))
#else
// We have observed stack frame increases from forced inlining overflowing the stack in debug builds,
// let's leave the decision to the compiler in that case
#define LEAN_ALWAYS_INLINE
#endif
#else
#define LEAN_UNLIKELY(x) (x)
#define LEAN_LIKELY(x) (x)
@@ -286,7 +294,7 @@ typedef struct {
void * m_data;
} lean_external_object;
static inline bool lean_is_scalar(lean_object * o) { return ((size_t)(o) & 1) == 1; }
static inline LEAN_ALWAYS_INLINE bool lean_is_scalar(lean_object * o) { return ((size_t)(o) & 1) == 1; }
static inline lean_object * lean_box(size_t n) { return (lean_object*)(((size_t)(n) << 1) | 1); }
static inline size_t lean_unbox(lean_object * o) { return (size_t)(o) >> 1; }
@@ -398,6 +406,13 @@ static inline unsigned lean_ptr_other(lean_object * o) {
small objects is a multiple of LEAN_OBJECT_SIZE_DELTA */
LEAN_EXPORT size_t lean_object_byte_size(lean_object * o);
/* Returns the size of the salient part of an object's storage,
i.e. the parts that contribute to the value representation;
padding or unused capacity is excluded. Operations that read
from an object's storage must only access these parts, since
the non-salient parts may not be initialized. */
LEAN_EXPORT size_t lean_object_data_byte_size(lean_object * o);
static inline bool lean_is_mt(lean_object * o) {
return o->m_rc < 0;
}
@@ -440,16 +455,16 @@ static inline void lean_inc_ref_n(lean_object * o, size_t n) {
LEAN_EXPORT void lean_dec_ref_cold(lean_object * o);
static inline void lean_dec_ref(lean_object * o) {
static inline LEAN_ALWAYS_INLINE void lean_dec_ref(lean_object * o) {
if (LEAN_LIKELY(o->m_rc > 1)) {
o->m_rc--;
} else if (o->m_rc != 0) {
lean_dec_ref_cold(o);
}
}
static inline void lean_inc(lean_object * o) { if (!lean_is_scalar(o)) lean_inc_ref(o); }
static inline void LEAN_ALWAYS_INLINE lean_inc(lean_object * o) { if (!lean_is_scalar(o)) lean_inc_ref(o); }
static inline void lean_inc_n(lean_object * o, size_t n) { if (!lean_is_scalar(o)) lean_inc_ref_n(o, n); }
static inline void lean_dec(lean_object * o) { if (!lean_is_scalar(o)) lean_dec_ref(o); }
static inline void LEAN_ALWAYS_INLINE lean_dec(lean_object * o) { if (!lean_is_scalar(o)) lean_dec_ref(o); }
static inline bool lean_is_ctor(lean_object * o) { return lean_ptr_tag(o) <= LeanMaxCtorTag; }
static inline bool lean_is_closure(lean_object * o) { return lean_ptr_tag(o) == LeanClosure; }
@@ -687,6 +702,9 @@ static inline size_t lean_array_capacity(b_lean_obj_arg o) { return lean_to_arra
static inline size_t lean_array_byte_size(lean_object * o) {
return sizeof(lean_array_object) + sizeof(void*)*lean_array_capacity(o);
}
static inline size_t lean_array_data_byte_size(lean_object * o) {
return sizeof(lean_array_object) + sizeof(void*)*lean_array_size(o);
}
static inline lean_object ** lean_array_cptr(lean_object * o) { return lean_to_array(o)->m_data; }
static inline void lean_array_set_size(u_lean_obj_arg o, size_t sz) {
assert(lean_is_array(o));
@@ -844,6 +862,9 @@ static inline size_t lean_sarray_byte_size(lean_object * o) {
return sizeof(lean_sarray_object) + lean_sarray_elem_size(o)*lean_sarray_capacity(o);
}
static inline size_t lean_sarray_size(b_lean_obj_arg o) { return lean_to_sarray(o)->m_size; }
static inline size_t lean_sarray_data_byte_size(lean_object * o) {
return sizeof(lean_sarray_object) + lean_sarray_elem_size(o)*lean_sarray_size(o);
}
static inline void lean_sarray_set_size(u_lean_obj_arg o, size_t sz) {
assert(lean_is_exclusive(o));
assert(sz <= lean_sarray_capacity(o));
@@ -1005,6 +1026,7 @@ static inline char const * lean_string_cstr(b_lean_obj_arg o) {
}
static inline size_t lean_string_size(b_lean_obj_arg o) { return lean_to_string(o)->m_size; }
static inline size_t lean_string_len(b_lean_obj_arg o) { return lean_to_string(o)->m_length; }
static inline size_t lean_string_data_byte_size(lean_object * o) { return sizeof(lean_string_object) + lean_string_size(o); }
LEAN_EXPORT lean_obj_res lean_string_push(lean_obj_arg s, uint32_t c);
LEAN_EXPORT lean_obj_res lean_string_append(lean_obj_arg s1, b_lean_obj_arg s2);
static inline lean_obj_res lean_string_length(b_lean_obj_arg s) { return lean_box(lean_string_len(s)); }
@@ -1195,14 +1217,14 @@ static inline lean_obj_res lean_nat_succ(b_lean_obj_arg a) {
return lean_nat_big_succ(a);
}
static inline lean_obj_res lean_nat_add(b_lean_obj_arg a1, b_lean_obj_arg a2) {
static inline LEAN_ALWAYS_INLINE lean_obj_res lean_nat_add(b_lean_obj_arg a1, b_lean_obj_arg a2) {
if (LEAN_LIKELY(lean_is_scalar(a1) && lean_is_scalar(a2)))
return lean_usize_to_nat(lean_unbox(a1) + lean_unbox(a2));
else
return lean_nat_big_add(a1, a2);
}
static inline lean_obj_res lean_nat_sub(b_lean_obj_arg a1, b_lean_obj_arg a2) {
static inline LEAN_ALWAYS_INLINE lean_obj_res lean_nat_sub(b_lean_obj_arg a1, b_lean_obj_arg a2) {
if (LEAN_LIKELY(lean_is_scalar(a1) && lean_is_scalar(a2))) {
size_t n1 = lean_unbox(a1);
size_t n2 = lean_unbox(a2);
@@ -1215,7 +1237,7 @@ static inline lean_obj_res lean_nat_sub(b_lean_obj_arg a1, b_lean_obj_arg a2) {
}
}
static inline lean_obj_res lean_nat_mul(b_lean_obj_arg a1, b_lean_obj_arg a2) {
static inline LEAN_ALWAYS_INLINE lean_obj_res lean_nat_mul(b_lean_obj_arg a1, b_lean_obj_arg a2) {
if (LEAN_LIKELY(lean_is_scalar(a1) && lean_is_scalar(a2))) {
size_t n1 = lean_unbox(a1);
if (n1 == 0)
@@ -1257,15 +1279,17 @@ static inline lean_obj_res lean_nat_mod(b_lean_obj_arg a1, b_lean_obj_arg a2) {
}
}
static inline bool lean_nat_eq(b_lean_obj_arg a1, b_lean_obj_arg a2) {
static inline LEAN_ALWAYS_INLINE bool lean_nat_eq(b_lean_obj_arg a1, b_lean_obj_arg a2) {
if (LEAN_LIKELY(lean_is_scalar(a1) && lean_is_scalar(a2))) {
// This comparison is UB according to the standard but allowed as per the
// GCC documentation and the address sanitizer does not complain about it.
return a1 == a2;
} else {
return lean_nat_big_eq(a1, a2);
}
}
static inline uint8_t lean_nat_dec_eq(b_lean_obj_arg a1, b_lean_obj_arg a2) {
static inline LEAN_ALWAYS_INLINE uint8_t lean_nat_dec_eq(b_lean_obj_arg a1, b_lean_obj_arg a2) {
return lean_nat_eq(a1, a2);
}
@@ -1273,27 +1297,31 @@ static inline bool lean_nat_ne(b_lean_obj_arg a1, b_lean_obj_arg a2) {
return !lean_nat_eq(a1, a2);
}
static inline bool lean_nat_le(b_lean_obj_arg a1, b_lean_obj_arg a2) {
static inline LEAN_ALWAYS_INLINE bool lean_nat_le(b_lean_obj_arg a1, b_lean_obj_arg a2) {
if (LEAN_LIKELY(lean_is_scalar(a1) && lean_is_scalar(a2))) {
// This comparison is UB according to the standard but allowed as per the
// GCC documentation and the address sanitizer does not complain about it.
return a1 <= a2;
} else {
return lean_nat_big_le(a1, a2);
}
}
static inline uint8_t lean_nat_dec_le(b_lean_obj_arg a1, b_lean_obj_arg a2) {
static inline LEAN_ALWAYS_INLINE uint8_t lean_nat_dec_le(b_lean_obj_arg a1, b_lean_obj_arg a2) {
return lean_nat_le(a1, a2);
}
static inline bool lean_nat_lt(b_lean_obj_arg a1, b_lean_obj_arg a2) {
static inline LEAN_ALWAYS_INLINE bool lean_nat_lt(b_lean_obj_arg a1, b_lean_obj_arg a2) {
if (LEAN_LIKELY(lean_is_scalar(a1) && lean_is_scalar(a2))) {
// This comparison is UB according to the standard but allowed as per the
// GCC documentation and the address sanitizer does not complain about it.
return a1 < a2;
} else {
return lean_nat_big_lt(a1, a2);
}
}
static inline uint8_t lean_nat_dec_lt(b_lean_obj_arg a1, b_lean_obj_arg a2) {
static inline LEAN_ALWAYS_INLINE uint8_t lean_nat_dec_lt(b_lean_obj_arg a1, b_lean_obj_arg a2) {
return lean_nat_lt(a1, a2);
}
@@ -2685,6 +2713,8 @@ static inline size_t lean_float_to_usize(double a) {
else
return (size_t) lean_float_to_uint32(a); // NOLINT
}
LEAN_EXPORT double lean_float_of_bits(uint64_t u);
LEAN_EXPORT uint64_t lean_float_to_bits(double d);
static inline double lean_float_add(double a, double b) { return a + b; }
static inline double lean_float_sub(double a, double b) { return a - b; }
static inline double lean_float_mul(double a, double b) { return a * b; }

View File

@@ -172,6 +172,28 @@ extern "C" LEAN_EXPORT size_t lean_object_byte_size(lean_object * o) {
}
}
extern "C" LEAN_EXPORT size_t lean_object_data_byte_size(lean_object * o) {
if (o->m_cs_sz == 0) {
/* Recall that multi-threaded, single-threaded and persistent objects are stored in the heap.
Persistent objects are multi-threaded and/or single-threaded that have been "promoted" to
a persistent status. */
switch (lean_ptr_tag(o)) {
case LeanArray: return lean_array_data_byte_size(o);
case LeanScalarArray: return lean_sarray_data_byte_size(o);
case LeanString: return lean_string_data_byte_size(o);
default: return lean_small_object_size(o);
}
} else {
/* See comment at `lean_set_non_heap_header`, for small objects we store the object size in the RC field. */
switch (lean_ptr_tag(o)) {
case LeanArray: return lean_array_data_byte_size(o);
case LeanScalarArray: return lean_sarray_data_byte_size(o);
case LeanString: return lean_string_data_byte_size(o);
default: return o->m_cs_sz;
}
}
}
static inline void lean_dealloc(lean_object * o, size_t sz) {
#ifdef LEAN_SMALL_ALLOCATOR
dealloc(o, sz);
@@ -1620,6 +1642,25 @@ extern "C" LEAN_EXPORT obj_res lean_float_frexp(double a) {
return r;
}
extern "C" LEAN_EXPORT double lean_float_of_bits(uint64_t u)
{
static_assert(sizeof(double) == sizeof(u), "`double` unexpected size.");
double ret;
std::memcpy(&ret, &u, sizeof(double));
if (isnan(ret))
ret = std::numeric_limits<double>::quiet_NaN();
return ret;
}
extern "C" LEAN_EXPORT uint64_t lean_float_to_bits(double d)
{
uint64_t ret;
if (isnan(d))
d = std::numeric_limits<double>::quiet_NaN();
std::memcpy(&ret, &d, sizeof(double));
return ret;
}
// =======================================
// Strings

View File

@@ -27,6 +27,10 @@ Author: Jared Roesch
#include <limits.h> // NOLINT
#endif
#ifdef __linux
#include <sys/syscall.h>
#endif
#include "runtime/object.h"
#include "runtime/io.h"
#include "runtime/array_ref.h"
@@ -80,6 +84,10 @@ extern "C" LEAN_EXPORT obj_res lean_io_process_get_pid(obj_arg) {
return lean_io_result_mk_ok(box_uint32(GetCurrentProcessId()));
}
extern "C" LEAN_EXPORT obj_res lean_io_get_tid(obj_arg) {
return lean_io_result_mk_ok(box_uint64(GetCurrentThreadId()));
}
extern "C" LEAN_EXPORT obj_res lean_io_process_child_wait(b_obj_arg, b_obj_arg child, obj_arg) {
HANDLE h = static_cast<HANDLE>(lean_get_external_data(cnstr_get(child, 3)));
DWORD exit_code;
@@ -316,6 +324,20 @@ extern "C" LEAN_EXPORT obj_res lean_io_process_get_pid(obj_arg) {
return lean_io_result_mk_ok(box_uint32(getpid()));
}
extern "C" LEAN_EXPORT obj_res lean_io_get_tid(obj_arg) {
uint64_t tid;
#ifdef __APPLE__
lean_always_assert(pthread_threadid_np(NULL, &tid) == 0);
#elif defined(LEAN_EMSCRIPTEN)
tid = 0;
#else
// since Linux 2.4.11, our glibc 2.27 requires at least 3.2
// glibc 2.30 would provide a wrapper
tid = (pid_t)syscall(SYS_gettid);
#endif
return lean_io_result_mk_ok(box_uint64(tid));
}
extern "C" LEAN_EXPORT obj_res lean_io_process_child_wait(b_obj_arg, b_obj_arg child, obj_arg) {
static_assert(sizeof(pid_t) == sizeof(uint32), "pid_t is expected to be a 32-bit type"); // NOLINT
pid_t pid = cnstr_get_uint32(child, 3 * sizeof(object *));

View File

@@ -13,8 +13,8 @@ namespace lean {
extern "C" LEAN_EXPORT uint8 lean_sharecommon_eq(b_obj_arg o1, b_obj_arg o2) {
lean_assert(!lean_is_scalar(o1));
lean_assert(!lean_is_scalar(o2));
size_t sz1 = lean_object_byte_size(o1);
size_t sz2 = lean_object_byte_size(o2);
size_t sz1 = lean_object_data_byte_size(o1);
size_t sz2 = lean_object_data_byte_size(o2);
if (sz1 != sz2) return false;
// compare relevant parts of the header
if (lean_ptr_tag(o1) != lean_ptr_tag(o2)) return false;
@@ -27,7 +27,7 @@ extern "C" LEAN_EXPORT uint8 lean_sharecommon_eq(b_obj_arg o1, b_obj_arg o2) {
extern "C" LEAN_EXPORT uint64_t lean_sharecommon_hash(b_obj_arg o) {
lean_assert(!lean_is_scalar(o));
size_t sz = lean_object_byte_size(o);
size_t sz = lean_object_data_byte_size(o);
size_t header_sz = sizeof(lean_object);
// hash relevant parts of the header
unsigned init = hash(lean_ptr_tag(o), lean_ptr_other(o));

Binary file not shown.

Binary file not shown.

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