mirror of
https://github.com/leanprover/lean4.git
synced 2026-03-18 02:44:12 +00:00
Compare commits
42 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ddcee2de91 | ||
|
|
02b69ff33c | ||
|
|
29415e8822 | ||
|
|
3c7555168d | ||
|
|
5eef3d27fb | ||
|
|
75d1504af2 | ||
|
|
a00cf6330f | ||
|
|
1f32477385 | ||
|
|
91c14c7ee9 | ||
|
|
69530afdf9 | ||
|
|
b7667c1604 | ||
|
|
d6f898001b | ||
|
|
a38566693b | ||
|
|
4bef3588b5 | ||
|
|
64538cf6e8 | ||
|
|
aadf3f1d2c | ||
|
|
95bf45ff8b | ||
|
|
2a02c121cf | ||
|
|
4600bb16fc | ||
|
|
7ccdfc30ff | ||
|
|
7f0bdefb6e | ||
|
|
799b2b6628 | ||
|
|
b8d6e44c4f | ||
|
|
5a99cb326c | ||
|
|
e10fac93a6 | ||
|
|
62ae320e1c | ||
|
|
98b1edfc1f | ||
|
|
ab162b3f52 | ||
|
|
b8a13ab755 | ||
|
|
405593ea28 | ||
|
|
24f305c0e3 | ||
|
|
5d553d6369 | ||
|
|
a449e3fdd6 | ||
|
|
764386734c | ||
|
|
7f1d7a595b | ||
|
|
f13e5ca852 | ||
|
|
ecbaeff24b | ||
|
|
691acde696 | ||
|
|
b1e0c1b594 | ||
|
|
93b4ec0351 | ||
|
|
f06fc30c0b | ||
|
|
64b35a8c19 |
@@ -613,8 +613,15 @@ def findIdx? {α : Type u} (p : α → Bool) (as : Array α) : Option Nat :=
|
||||
decreasing_by simp_wf; decreasing_trivial_pre_omega
|
||||
loop 0
|
||||
|
||||
def getIdx? [BEq α] (a : Array α) (v : α) : Option Nat :=
|
||||
a.findIdx? fun a => a == v
|
||||
@[inline]
|
||||
def findFinIdx? {α : Type u} (p : α → Bool) (as : Array α) : Option (Fin as.size) :=
|
||||
let rec @[semireducible] -- This is otherwise irreducible because it uses well-founded recursion.
|
||||
loop (j : Nat) :=
|
||||
if h : j < as.size then
|
||||
if p as[j] then some ⟨j, h⟩ else loop (j + 1)
|
||||
else none
|
||||
decreasing_by simp_wf; decreasing_trivial_pre_omega
|
||||
loop 0
|
||||
|
||||
@[semireducible] -- This is otherwise irreducible because it uses well-founded recursion.
|
||||
def indexOfAux [BEq α] (a : Array α) (v : α) (i : Nat) : Option (Fin a.size) :=
|
||||
@@ -627,6 +634,10 @@ decreasing_by simp_wf; decreasing_trivial_pre_omega
|
||||
def indexOf? [BEq α] (a : Array α) (v : α) : Option (Fin a.size) :=
|
||||
indexOfAux a v 0
|
||||
|
||||
@[deprecated indexOf? (since := "2024-11-20")]
|
||||
def getIdx? [BEq α] (a : Array α) (v : α) : Option Nat :=
|
||||
a.findIdx? fun a => a == v
|
||||
|
||||
@[inline]
|
||||
def any (as : Array α) (p : α → Bool) (start := 0) (stop := as.size) : Bool :=
|
||||
Id.run <| as.anyM p start stop
|
||||
@@ -766,48 +777,62 @@ def takeWhile (p : α → Bool) (as : Array α) : Array α :=
|
||||
decreasing_by simp_wf; decreasing_trivial_pre_omega
|
||||
go 0 #[]
|
||||
|
||||
/-- Remove the element at a given index from an array without bounds checks, using a `Fin` index.
|
||||
/--
|
||||
Remove the element at a given index from an array without a runtime bounds checks,
|
||||
using a `Nat` index and a tactic-provided bound.
|
||||
|
||||
This function takes worst case O(n) time because
|
||||
it has to backshift all elements at positions greater than `i`.-/
|
||||
This function takes worst case O(n) time because
|
||||
it has to backshift all elements at positions greater than `i`.-/
|
||||
@[semireducible] -- This is otherwise irreducible because it uses well-founded recursion.
|
||||
def feraseIdx (a : Array α) (i : Fin a.size) : Array α :=
|
||||
if h : i.val + 1 < a.size then
|
||||
let a' := a.swap ⟨i.val + 1, h⟩ i
|
||||
let i' : Fin a'.size := ⟨i.val + 1, by simp [a', h]⟩
|
||||
a'.feraseIdx i'
|
||||
def eraseIdx (a : Array α) (i : Nat) (h : i < a.size := by get_elem_tactic) : Array α :=
|
||||
if h' : i + 1 < a.size then
|
||||
let a' := a.swap ⟨i + 1, h'⟩ ⟨i, h⟩
|
||||
a'.eraseIdx (i + 1) (by simp [a', h'])
|
||||
else
|
||||
a.pop
|
||||
termination_by a.size - i.val
|
||||
decreasing_by simp_wf; exact Nat.sub_succ_lt_self _ _ i.isLt
|
||||
termination_by a.size - i
|
||||
decreasing_by simp_wf; exact Nat.sub_succ_lt_self _ _ h
|
||||
|
||||
-- This is required in `Lean.Data.PersistentHashMap`.
|
||||
@[simp] theorem size_feraseIdx (a : Array α) (i : Fin a.size) : (a.feraseIdx i).size = a.size - 1 := by
|
||||
induction a, i using Array.feraseIdx.induct with
|
||||
| @case1 a i h a' _ ih =>
|
||||
unfold feraseIdx
|
||||
simp [h, a', ih]
|
||||
| case2 a i h =>
|
||||
unfold feraseIdx
|
||||
simp [h]
|
||||
@[simp] theorem size_eraseIdx (a : Array α) (i : Nat) (h) : (a.eraseIdx i h).size = a.size - 1 := by
|
||||
induction a, i, h using Array.eraseIdx.induct with
|
||||
| @case1 a i h h' a' ih =>
|
||||
unfold eraseIdx
|
||||
simp [h', a', ih]
|
||||
| case2 a i h h' =>
|
||||
unfold eraseIdx
|
||||
simp [h']
|
||||
|
||||
/-- Remove the element at a given index from an array, or do nothing if the index is out of bounds.
|
||||
|
||||
This function takes worst case O(n) time because
|
||||
it has to backshift all elements at positions greater than `i`.-/
|
||||
def eraseIdx (a : Array α) (i : Nat) : Array α :=
|
||||
if h : i < a.size then a.feraseIdx ⟨i, h⟩ else a
|
||||
def eraseIdxIfInBounds (a : Array α) (i : Nat) : Array α :=
|
||||
if h : i < a.size then a.eraseIdx i h else a
|
||||
|
||||
/-- Remove the element at a given index from an array, or panic if the index is out of bounds.
|
||||
|
||||
This function takes worst case O(n) time because
|
||||
it has to backshift all elements at positions greater than `i`. -/
|
||||
def eraseIdx! (a : Array α) (i : Nat) : Array α :=
|
||||
if h : i < a.size then a.eraseIdx i h else panic! "invalid index"
|
||||
|
||||
def erase [BEq α] (as : Array α) (a : α) : Array α :=
|
||||
match as.indexOf? a with
|
||||
| none => as
|
||||
| some i => as.feraseIdx i
|
||||
| some i => as.eraseIdx i
|
||||
|
||||
/-- Erase the first element that satisfies the predicate `p`. -/
|
||||
def eraseP (as : Array α) (p : α → Bool) : Array α :=
|
||||
match as.findIdx? p with
|
||||
| none => as
|
||||
| some i => as.eraseIdxIfInBounds i
|
||||
|
||||
/-- Insert element `a` at position `i`. -/
|
||||
@[inline] def insertAt (as : Array α) (i : Fin (as.size + 1)) (a : α) : Array α :=
|
||||
@[inline] def insertIdx (as : Array α) (i : Nat) (a : α) (_ : i ≤ as.size := by get_elem_tactic) : Array α :=
|
||||
let rec @[semireducible] -- This is otherwise irreducible because it uses well-founded recursion.
|
||||
loop (as : Array α) (j : Fin as.size) :=
|
||||
if i.1 < j then
|
||||
if i < j then
|
||||
let j' := ⟨j-1, Nat.lt_of_le_of_lt (Nat.pred_le _) j.2⟩
|
||||
let as := as.swap j' j
|
||||
loop as ⟨j', by rw [size_swap]; exact j'.2⟩
|
||||
@@ -818,12 +843,23 @@ def erase [BEq α] (as : Array α) (a : α) : Array α :=
|
||||
let as := as.push a
|
||||
loop as ⟨j, size_push .. ▸ j.lt_succ_self⟩
|
||||
|
||||
@[deprecated insertIdx (since := "2024-11-20")] abbrev insertAt := @insertIdx
|
||||
|
||||
/-- Insert element `a` at position `i`. Panics if `i` is not `i ≤ as.size`. -/
|
||||
def insertAt! (as : Array α) (i : Nat) (a : α) : Array α :=
|
||||
def insertIdx! (as : Array α) (i : Nat) (a : α) : Array α :=
|
||||
if h : i ≤ as.size then
|
||||
insertAt as ⟨i, Nat.lt_succ_of_le h⟩ a
|
||||
insertIdx as i a
|
||||
else panic! "invalid index"
|
||||
|
||||
@[deprecated insertIdx! (since := "2024-11-20")] abbrev insertAt! := @insertIdx!
|
||||
|
||||
/-- Insert element `a` at position `i`, or do nothing if `as.size < i`. -/
|
||||
def insertIdxIfInBounds (as : Array α) (i : Nat) (a : α) : Array α :=
|
||||
if h : i ≤ as.size then
|
||||
insertIdx as i a
|
||||
else
|
||||
as
|
||||
|
||||
@[semireducible] -- This is otherwise irreducible because it uses well-founded recursion.
|
||||
def isPrefixOfAux [BEq α] (as bs : Array α) (hle : as.size ≤ bs.size) (i : Nat) : Bool :=
|
||||
if h : i < as.size then
|
||||
|
||||
@@ -52,7 +52,7 @@ namespace Array
|
||||
let mid := (lo + hi)/2
|
||||
let midVal := as.get! mid
|
||||
if lt midVal k then
|
||||
if mid == lo then do let v ← add (); pure <| as.insertAt! (lo+1) v
|
||||
if mid == lo then do let v ← add (); pure <| as.insertIdx! (lo+1) v
|
||||
else binInsertAux lt merge add as k mid hi
|
||||
else if lt k midVal then
|
||||
binInsertAux lt merge add as k lo mid
|
||||
@@ -67,7 +67,7 @@ namespace Array
|
||||
(k : α) : m (Array α) :=
|
||||
let _ := Inhabited.mk k
|
||||
if as.isEmpty then do let v ← add (); pure <| as.push v
|
||||
else if lt k (as.get! 0) then do let v ← add (); pure <| as.insertAt! 0 v
|
||||
else if lt k (as.get! 0) then do let v ← add (); pure <| as.insertIdx! 0 v
|
||||
else if !lt (as.get! 0) k then as.modifyM 0 <| merge
|
||||
else if lt as.back! k then do let v ← add (); pure <| as.push v
|
||||
else if !lt k as.back! then as.modifyM (as.size - 1) <| merge
|
||||
|
||||
275
src/Init/Data/Array/Find.lean
Normal file
275
src/Init/Data/Array/Find.lean
Normal file
@@ -0,0 +1,275 @@
|
||||
/-
|
||||
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]
|
||||
|
||||
end Array
|
||||
@@ -601,7 +601,7 @@ 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
|
||||
|
||||
@@ -620,19 +620,19 @@ theorem getElem?_of_mem {a : α} {as : Array α} :
|
||||
|
||||
@[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 -/
|
||||
|
||||
@@ -1218,6 +1218,14 @@ theorem push_eq_append_singleton (as : Array α) (x) : as.push x = as ++ #[x] :=
|
||||
@[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
|
||||
@@ -1594,9 +1602,9 @@ theorem swap_comm (a : Array α) {i j : Fin a.size} : a.swap i j = a.swap j i :=
|
||||
|
||||
/-! ### eraseIdx -/
|
||||
|
||||
theorem feraseIdx_eq_eraseIdx {a : Array α} {i : Fin a.size} :
|
||||
a.feraseIdx i = a.eraseIdx i.1 := by
|
||||
simp [eraseIdx]
|
||||
theorem eraseIdx_eq_eraseIdxIfInBounds {a : Array α} {i : Nat} (h : i < a.size) :
|
||||
a.eraseIdx i h = a.eraseIdxIfInBounds i := by
|
||||
simp [eraseIdxIfInBounds, h]
|
||||
|
||||
/-! ### isPrefixOf -/
|
||||
|
||||
@@ -1826,16 +1834,15 @@ theorem takeWhile_go_toArray (p : α → Bool) (l : List α) (i : Nat) :
|
||||
l.toArray.takeWhile p = (l.takeWhile p).toArray := by
|
||||
simp [Array.takeWhile, takeWhile_go_toArray]
|
||||
|
||||
@[simp] theorem feraseIdx_toArray (l : List α) (i : Fin l.toArray.size) :
|
||||
l.toArray.feraseIdx i = (l.eraseIdx i).toArray := by
|
||||
rw [feraseIdx]
|
||||
split <;> rename_i h
|
||||
· rw [feraseIdx_toArray]
|
||||
@[simp] theorem eraseIdx_toArray (l : List α) (i : Nat) (h : i < l.toArray.size) :
|
||||
l.toArray.eraseIdx i h = (l.eraseIdx i).toArray := by
|
||||
rw [Array.eraseIdx]
|
||||
split <;> rename_i h'
|
||||
· rw [eraseIdx_toArray]
|
||||
simp only [swap_toArray, Fin.getElem_fin, toList_toArray, mk.injEq]
|
||||
rw [eraseIdx_set_gt (by simp), eraseIdx_set_eq]
|
||||
simp
|
||||
· rcases i with ⟨i, w⟩
|
||||
simp at h w
|
||||
· simp at h h'
|
||||
have t : i = l.length - 1 := by omega
|
||||
simp [t]
|
||||
termination_by l.length - i
|
||||
@@ -1845,9 +1852,9 @@ decreasing_by
|
||||
simp
|
||||
omega
|
||||
|
||||
@[simp] theorem eraseIdx_toArray (l : List α) (i : Nat) :
|
||||
l.toArray.eraseIdx i = (l.eraseIdx i).toArray := by
|
||||
rw [Array.eraseIdx]
|
||||
@[simp] theorem eraseIdxIfInBounds_toArray (l : List α) (i : Nat) :
|
||||
l.toArray.eraseIdxIfInBounds i = (l.eraseIdx i).toArray := by
|
||||
rw [Array.eraseIdxIfInBounds]
|
||||
split
|
||||
· simp
|
||||
· simp_all [eraseIdx_eq_self.2]
|
||||
@@ -1866,16 +1873,60 @@ namespace Array
|
||||
(as.takeWhile p).toList = as.toList.takeWhile p := by
|
||||
induction as; simp
|
||||
|
||||
@[simp] theorem toList_feraseIdx (as : Array α) (i : Fin as.size) :
|
||||
(as.feraseIdx i).toList = as.toList.eraseIdx i.1 := by
|
||||
@[simp] theorem toList_eraseIdx (as : Array α) (i : Nat) (h : i < as.size) :
|
||||
(as.eraseIdx i h).toList = as.toList.eraseIdx i := by
|
||||
induction as
|
||||
simp
|
||||
|
||||
@[simp] theorem toList_eraseIdx (as : Array α) (i : Nat) :
|
||||
(as.eraseIdx i).toList = as.toList.eraseIdx i := by
|
||||
@[simp] theorem toList_eraseIdxIfInBounds (as : Array α) (i : Nat) :
|
||||
(as.eraseIdxIfInBounds i).toList = as.toList.eraseIdx i := by
|
||||
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
|
||||
|
||||
/-! ### 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]
|
||||
|
||||
/-! ### findSomeRevM?, findRevM?, findSomeRev?, findRev? -/
|
||||
|
||||
@[simp] theorem findSomeRevM?_eq_findSomeM?_reverse
|
||||
@@ -1940,6 +1991,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 -/
|
||||
|
||||
@@ -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⟩
|
||||
|
||||
@@ -2611,7 +2611,7 @@ theorem getLsbD_rotateLeftAux_of_geq {x : BitVec w} {r : Nat} {i : Nat} (hi : i
|
||||
apply getLsbD_ge
|
||||
omega
|
||||
|
||||
/-- When `r < w`, we give a formula for `(x.rotateRight r).getLsbD i`. -/
|
||||
/-- When `r < w`, we give a formula for `(x.rotateLeft r).getLsbD i`. -/
|
||||
theorem getLsbD_rotateLeft_of_le {x : BitVec w} {r i : Nat} (hr: r < w) :
|
||||
(x.rotateLeft r).getLsbD i =
|
||||
cond (i < r)
|
||||
@@ -2638,6 +2638,56 @@ theorem getElem_rotateLeft {x : BitVec w} {r i : Nat} (h : i < w) :
|
||||
if h' : i < r % w then x[(w - (r % w) + i)] else x[i - (r % w)] := by
|
||||
simp [← BitVec.getLsbD_eq_getElem, h]
|
||||
|
||||
/-- If `w ≤ x < 2 * w`, then `x % w = x - w` -/
|
||||
theorem mod_eq_sub_of_le_of_lt {x w : Nat} (x_le : w ≤ x) (x_lt : x < 2 * w) :
|
||||
x % w = x - w := by
|
||||
rw [Nat.mod_eq_sub_mod, Nat.mod_eq_of_lt (by omega)]
|
||||
omega
|
||||
|
||||
theorem getMsbD_rotateLeftAux_of_lt {x : BitVec w} {r : Nat} {i : Nat} (hi : i < w - r) :
|
||||
(x.rotateLeftAux r).getMsbD i = x.getMsbD (r + i) := by
|
||||
rw [rotateLeftAux, getMsbD_or]
|
||||
simp [show i < w - r by omega, Nat.add_comm]
|
||||
|
||||
theorem getMsbD_rotateLeftAux_of_ge {x : BitVec w} {r : Nat} {i : Nat} (hi : i ≥ w - r) :
|
||||
(x.rotateLeftAux r).getMsbD i = (decide (i < w) && x.getMsbD (i - (w - r))) := by
|
||||
simp [rotateLeftAux, getMsbD_or, show i + r ≥ w by omega, show ¬i < w - r by omega]
|
||||
|
||||
/-- When `r < w`, we give a formula for `(x.rotateLeft r).getMsbD i`. -/
|
||||
theorem getMsbD_rotateLeft_of_lt {n w : Nat} {x : BitVec w} (hi : r < w):
|
||||
(x.rotateLeft r).getMsbD n = (decide (n < w) && x.getMsbD ((r + n) % w)) := by
|
||||
rcases w with rfl | w
|
||||
· simp
|
||||
· rw [BitVec.rotateLeft_eq_rotateLeftAux_of_lt (by omega)]
|
||||
by_cases h : n < (w + 1) - r
|
||||
· simp [getMsbD_rotateLeftAux_of_lt h, Nat.mod_eq_of_lt, show r + n < (w + 1) by omega, show n < w + 1 by omega]
|
||||
· simp [getMsbD_rotateLeftAux_of_ge <| Nat.ge_of_not_lt h]
|
||||
by_cases h₁ : n < w + 1
|
||||
· simp only [h₁, decide_true, Bool.true_and]
|
||||
have h₂ : (r + n) < 2 * (w + 1) := by omega
|
||||
rw [mod_eq_sub_of_le_of_lt (by omega) (by omega)]
|
||||
congr 1
|
||||
omega
|
||||
· simp [h₁]
|
||||
|
||||
theorem getMsbD_rotateLeft {r n w : Nat} {x : BitVec w} :
|
||||
(x.rotateLeft r).getMsbD n = (decide (n < w) && x.getMsbD ((r + n) % w)) := by
|
||||
rcases w with rfl | w
|
||||
· simp
|
||||
· by_cases h : r < w
|
||||
· rw [getMsbD_rotateLeft_of_lt (by omega)]
|
||||
· rw [← rotateLeft_mod_eq_rotateLeft, getMsbD_rotateLeft_of_lt (by apply Nat.mod_lt; simp)]
|
||||
simp
|
||||
|
||||
@[simp]
|
||||
theorem msb_rotateLeft {m w : Nat} {x : BitVec w} :
|
||||
(x.rotateLeft m).msb = x.getMsbD (m % w) := by
|
||||
simp only [BitVec.msb, getMsbD_rotateLeft]
|
||||
by_cases h : w = 0
|
||||
· simp [h]
|
||||
· simp
|
||||
omega
|
||||
|
||||
/-! ## Rotate Right -/
|
||||
|
||||
/--
|
||||
@@ -2699,7 +2749,7 @@ theorem rotateRight_mod_eq_rotateRight {x : BitVec w} {r : Nat} :
|
||||
simp only [rotateRight, Nat.mod_mod]
|
||||
|
||||
/-- When `r < w`, we give a formula for `(x.rotateRight r).getLsb i`. -/
|
||||
theorem getLsbD_rotateRight_of_le {x : BitVec w} {r i : Nat} (hr: r < w) :
|
||||
theorem getLsbD_rotateRight_of_lt {x : BitVec w} {r i : Nat} (hr: r < w) :
|
||||
(x.rotateRight r).getLsbD i =
|
||||
cond (i < w - r)
|
||||
(x.getLsbD (r + i))
|
||||
@@ -2717,7 +2767,7 @@ theorem getLsbD_rotateRight {x : BitVec w} {r i : Nat} :
|
||||
(decide (i < w) && x.getLsbD (i - (w - (r % w)))) := by
|
||||
rcases w with ⟨rfl, w⟩
|
||||
· simp
|
||||
· rw [← rotateRight_mod_eq_rotateRight, getLsbD_rotateRight_of_le (Nat.mod_lt _ (by omega))]
|
||||
· rw [← rotateRight_mod_eq_rotateRight, getLsbD_rotateRight_of_lt (Nat.mod_lt _ (by omega))]
|
||||
|
||||
@[simp]
|
||||
theorem getElem_rotateRight {x : BitVec w} {r i : Nat} (h : i < w) :
|
||||
@@ -2725,6 +2775,56 @@ theorem getElem_rotateRight {x : BitVec w} {r i : Nat} (h : i < w) :
|
||||
simp only [← BitVec.getLsbD_eq_getElem]
|
||||
simp [getLsbD_rotateRight, h]
|
||||
|
||||
theorem getMsbD_rotateRightAux_of_lt {x : BitVec w} {r : Nat} {i : Nat} (hi : i < r) :
|
||||
(x.rotateRightAux r).getMsbD i = x.getMsbD (i + (w - r)) := by
|
||||
rw [rotateRightAux, getMsbD_or, getMsbD_ushiftRight]
|
||||
simp [show i < r by omega]
|
||||
|
||||
theorem getMsbD_rotateRightAux_of_ge {x : BitVec w} {r : Nat} {i : Nat} (hi : i ≥ r) :
|
||||
(x.rotateRightAux r).getMsbD i = (decide (i < w) && x.getMsbD (i - r)) := by
|
||||
simp [rotateRightAux, show ¬ i < r by omega, show i + (w - r) ≥ w by omega]
|
||||
|
||||
/-- When `m < w`, we give a formula for `(x.rotateLeft m).getMsbD i`. -/
|
||||
@[simp]
|
||||
theorem getMsbD_rotateRight_of_lt {w n m : Nat} {x : BitVec w} (hr : m < w):
|
||||
(x.rotateRight m).getMsbD n = (decide (n < w) && (if (n < m % w)
|
||||
then x.getMsbD ((w + n - m % w) % w) else x.getMsbD (n - m % w))):= by
|
||||
rcases w with rfl | w
|
||||
· simp
|
||||
· rw [rotateRight_eq_rotateRightAux_of_lt (by omega)]
|
||||
by_cases h : n < m
|
||||
· simp only [getMsbD_rotateRightAux_of_lt h, show n < w + 1 by omega, decide_true,
|
||||
show m % (w + 1) = m by rw [Nat.mod_eq_of_lt hr], h, ↓reduceIte,
|
||||
show (w + 1 + n - m) < (w + 1) by omega, Nat.mod_eq_of_lt, Bool.true_and]
|
||||
congr 1
|
||||
omega
|
||||
· simp [h, getMsbD_rotateRightAux_of_ge <| Nat.ge_of_not_lt h]
|
||||
by_cases h₁ : n < w + 1
|
||||
· simp [h, h₁, decide_true, Bool.true_and, Nat.mod_eq_of_lt hr]
|
||||
· simp [h₁]
|
||||
|
||||
@[simp]
|
||||
theorem getMsbD_rotateRight {w n m : Nat} {x : BitVec w} :
|
||||
(x.rotateRight m).getMsbD n = (decide (n < w) && (if (n < m % w)
|
||||
then x.getMsbD ((w + n - m % w) % w) else x.getMsbD (n - m % w))):= by
|
||||
rcases w with rfl | w
|
||||
· simp
|
||||
· by_cases h₀ : m < w
|
||||
· rw [getMsbD_rotateRight_of_lt (by omega)]
|
||||
· rw [← rotateRight_mod_eq_rotateRight, getMsbD_rotateRight_of_lt (by apply Nat.mod_lt; simp)]
|
||||
simp
|
||||
|
||||
@[simp]
|
||||
theorem msb_rotateRight {r w : Nat} {x : BitVec w} :
|
||||
(x.rotateRight r).msb = x.getMsbD ((w - r % w) % w) := by
|
||||
simp only [BitVec.msb, getMsbD_rotateRight]
|
||||
by_cases h₀ : 0 < w
|
||||
· simp only [h₀, decide_true, Nat.add_zero, Nat.zero_le, Nat.sub_eq_zero_of_le, Bool.true_and,
|
||||
ite_eq_left_iff, Nat.not_lt, Nat.le_zero_eq]
|
||||
intro h₁
|
||||
simp [h₁]
|
||||
· simp [show w = 0 by omega]
|
||||
|
||||
/- ## twoPow -/
|
||||
|
||||
theorem twoPow_eq (w : Nat) (i : Nat) : twoPow w i = 1#w <<< i := by
|
||||
|
||||
@@ -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 -/
|
||||
|
||||
|
||||
@@ -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⟩
|
||||
|
||||
@@ -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 -/
|
||||
|
||||
@@ -238,7 +238,7 @@ 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 _ (get_mem l ⟨n, @length_pmap _ _ p f l h ▸ hn⟩)) := by
|
||||
simp only [get_eq_getElem]
|
||||
simp [getElem_pmap]
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -492,9 +503,9 @@ 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 :=
|
||||
let ⟨_, e⟩ := getElem?_eq_some_iff.1 e; e ▸ getElem_mem ..
|
||||
@@ -1025,6 +1036,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 +1164,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
|
||||
@@ -2395,7 +2415,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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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). -/
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -366,10 +366,10 @@ to be updated.
|
||||
@[implemented_by updateFunDeclCoreImp] opaque FunDeclCore.updateCore (decl: FunDecl) (type : Expr) (params : Array Param) (value : Code) : FunDecl
|
||||
|
||||
def CasesCore.extractAlt! (cases : Cases) (ctorName : Name) : Alt × Cases :=
|
||||
let found (i : Nat) := (cases.alts[i]!, { cases with alts := cases.alts.eraseIdx i })
|
||||
if let some i := cases.alts.findIdx? fun | .alt ctorName' .. => ctorName == ctorName' | _ => false then
|
||||
let found i := (cases.alts[i], { cases with alts := cases.alts.eraseIdx i })
|
||||
if let some i := cases.alts.findFinIdx? fun | .alt ctorName' .. => ctorName == ctorName' | _ => false then
|
||||
found i
|
||||
else if let some i := cases.alts.findIdx? fun | .default _ => true | _ => false then
|
||||
else if let some i := cases.alts.findFinIdx? fun | .default _ => true | _ => false then
|
||||
found i
|
||||
else
|
||||
unreachable!
|
||||
|
||||
@@ -134,9 +134,9 @@ def withEachOccurrence (targetName : Name) (f : Nat → PassInstaller) : PassIns
|
||||
|
||||
def installAfter (targetName : Name) (p : Pass → Pass) (occurrence : Nat := 0) : PassInstaller where
|
||||
install passes :=
|
||||
if let some idx := passes.findIdx? (fun p => p.name == targetName && p.occurrence == occurrence) then
|
||||
let passUnderTest := passes[idx]!
|
||||
return passes.insertAt! (idx + 1) (p passUnderTest)
|
||||
if let some idx := passes.findFinIdx? (fun p => p.name == targetName && p.occurrence == occurrence) then
|
||||
let passUnderTest := passes[idx]
|
||||
return passes.insertIdx (idx + 1) (p passUnderTest)
|
||||
else
|
||||
throwError s!"Tried to insert pass after {targetName}, occurrence {occurrence} but {targetName} is not in the pass list"
|
||||
|
||||
@@ -145,9 +145,9 @@ def installAfterEach (targetName : Name) (p : Pass → Pass) : PassInstaller :=
|
||||
|
||||
def installBefore (targetName : Name) (p : Pass → Pass) (occurrence : Nat := 0): PassInstaller where
|
||||
install passes :=
|
||||
if let some idx := passes.findIdx? (fun p => p.name == targetName && p.occurrence == occurrence) then
|
||||
let passUnderTest := passes[idx]!
|
||||
return passes.insertAt! idx (p passUnderTest)
|
||||
if let some idx := passes.findFinIdx? (fun p => p.name == targetName && p.occurrence == occurrence) then
|
||||
let passUnderTest := passes[idx]
|
||||
return passes.insertIdx idx (p passUnderTest)
|
||||
else
|
||||
throwError s!"Tried to insert pass after {targetName}, occurrence {occurrence} but {targetName} is not in the pass list"
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@ private def elabSpecArgs (declName : Name) (args : Array Syntax) : MetaM (Array
|
||||
result := result.push idx
|
||||
else
|
||||
let argName := arg.getId
|
||||
if let some idx := argNames.getIdx? argName then
|
||||
if let some idx := argNames.indexOf? argName then
|
||||
if result.contains idx then throwErrorAt arg "invalid specialization argument name `{argName}`, it has already been specified as a specialization candidate"
|
||||
result := result.push idx
|
||||
else
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -233,10 +233,10 @@ partial def eraseAux [BEq α] : Node α β → USize → α → Node α β
|
||||
| n@(Node.collision keys vals heq), _, k =>
|
||||
match keys.indexOf? k with
|
||||
| some idx =>
|
||||
let keys' := keys.feraseIdx idx
|
||||
have keq := keys.size_feraseIdx idx
|
||||
let vals' := vals.feraseIdx (Eq.ndrec idx heq)
|
||||
have veq := vals.size_feraseIdx (Eq.ndrec idx heq)
|
||||
let keys' := keys.eraseIdx idx
|
||||
have keq := keys.size_eraseIdx idx _
|
||||
let vals' := vals.eraseIdx (Eq.ndrec idx heq)
|
||||
have veq := vals.size_eraseIdx (Eq.ndrec idx heq) _
|
||||
have : keys.size - 1 = vals.size - 1 := by rw [heq]
|
||||
Node.collision keys' vals' (keq.trans (this.trans veq.symm))
|
||||
| none => n
|
||||
|
||||
@@ -1347,7 +1347,7 @@ where
|
||||
let mut unusableNamedArgs := unusableNamedArgs
|
||||
for x in xs, bInfo in bInfos do
|
||||
let xDecl ← x.mvarId!.getDecl
|
||||
if let some idx := remainingNamedArgs.findIdx? (·.name == xDecl.userName) then
|
||||
if let some idx := remainingNamedArgs.findFinIdx? (·.name == xDecl.userName) then
|
||||
/- If there is named argument with name `xDecl.userName`, then it is accounted for and we can't make use of it. -/
|
||||
remainingNamedArgs := remainingNamedArgs.eraseIdx idx
|
||||
else
|
||||
@@ -1355,9 +1355,9 @@ where
|
||||
/- We found a type of the form (baseName ...).
|
||||
First, we check if the current argument is an explicit one,
|
||||
and if the current explicit position "fits" at `args` (i.e., it must be ≤ arg.size) -/
|
||||
if argIdx ≤ args.size && bInfo.isExplicit then
|
||||
if h : argIdx ≤ args.size ∧ bInfo.isExplicit then
|
||||
/- We can insert `e` as an explicit argument -/
|
||||
return (args.insertAt! argIdx (Arg.expr e), namedArgs)
|
||||
return (args.insertIdx argIdx (Arg.expr e), namedArgs)
|
||||
else
|
||||
/- If we can't add `e` to `args`, we try to add it using a named argument, but this is only possible
|
||||
if there isn't an argument with the same name occurring before it. -/
|
||||
@@ -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
|
||||
|
||||
@@ -211,7 +211,7 @@ private def replaceBinderAnnotation (binder : TSyntax ``Parser.Term.bracketedBin
|
||||
else
|
||||
`(bracketedBinderF| {$id $[: $ty?]?})
|
||||
for id in ids.reverse do
|
||||
if let some idx := binderIds.findIdx? fun binderId => binderId.raw.isIdent && binderId.raw.getId == id.raw.getId then
|
||||
if let some idx := binderIds.findFinIdx? fun binderId => binderId.raw.isIdent && binderId.raw.getId == id.raw.getId then
|
||||
binderIds := binderIds.eraseIdx idx
|
||||
modifiedVarDecls := true
|
||||
varDeclsNew := varDeclsNew.push (← mkBinder id explicit)
|
||||
|
||||
@@ -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 :=
|
||||
|
||||
@@ -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 }
|
||||
}
|
||||
|
||||
|
||||
@@ -1518,7 +1518,7 @@ mutual
|
||||
-/
|
||||
partial def doForToCode (doFor : Syntax) (doElems : List Syntax) : M CodeBlock := do
|
||||
let doForDecls := doFor[1].getSepArgs
|
||||
if doForDecls.size > 1 then
|
||||
if h : doForDecls.size > 1 then
|
||||
/-
|
||||
Expand
|
||||
```
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -332,9 +332,9 @@ where
|
||||
else
|
||||
let accessible := isNextArgAccessible ctx
|
||||
let (d, ctx) := getNextParam ctx
|
||||
match ctx.namedArgs.findIdx? fun namedArg => namedArg.name == d.1 with
|
||||
match ctx.namedArgs.findFinIdx? fun namedArg => namedArg.name == d.1 with
|
||||
| some idx =>
|
||||
let arg := ctx.namedArgs[idx]!
|
||||
let arg := ctx.namedArgs[idx]
|
||||
let ctx := { ctx with namedArgs := ctx.namedArgs.eraseIdx idx }
|
||||
let ctx ← pushNewArg accessible ctx arg.val
|
||||
processCtorAppContext ctx
|
||||
|
||||
@@ -292,9 +292,9 @@ def mkBrecOnApp (positions : Positions) (fnIdx : Nat) (brecOnConst : Nat → Exp
|
||||
let packedFTypes ← inferArgumentTypesN positions.size brecOn
|
||||
let packedFArgs ← positions.mapMwith PProdN.mkLambdas packedFTypes FArgs
|
||||
let brecOn := mkAppN brecOn packedFArgs
|
||||
let some poss := positions.find? (·.contains fnIdx)
|
||||
let some (size, idx) := positions.findSome? fun pos => (pos.size, ·) <$> pos.indexOf? fnIdx
|
||||
| throwError "mkBrecOnApp: Could not find {fnIdx} in {positions}"
|
||||
let brecOn ← PProdN.proj poss.size (poss.getIdx? fnIdx).get! brecOn
|
||||
let brecOn ← PProdN.proj size idx brecOn
|
||||
mkLambdaFVars ys (mkAppN brecOn otherArgs)
|
||||
|
||||
end Lean.Elab.Structural
|
||||
|
||||
@@ -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}"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -40,7 +40,7 @@ private partial def post (fixedPrefix : Nat) (argsPacker : ArgsPacker) (funNames
|
||||
return TransformStep.done e
|
||||
let declName := f.constName!
|
||||
let us := f.constLevels!
|
||||
if let some fidx := funNames.getIdx? declName then
|
||||
if let some fidx := funNames.indexOf? declName then
|
||||
let arity := fixedPrefix + argsPacker.varNamess[fidx]!.size
|
||||
let e' ← withAppN arity e fun args => do
|
||||
let fixedArgs := args[:fixedPrefix]
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -58,7 +58,7 @@ partial def mkTuple : Array Syntax → TermElabM Syntax
|
||||
| #[] => `(Unit.unit)
|
||||
| #[e] => return e
|
||||
| es => do
|
||||
let stx ← mkTuple (es.eraseIdx 0)
|
||||
let stx ← mkTuple (es.eraseIdxIfInBounds 0)
|
||||
`(Prod.mk $(es[0]!) $stx)
|
||||
|
||||
def resolveSectionVariable (sectionVars : NameMap Name) (id : Name) : List (Name × List String) :=
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -142,7 +142,7 @@ where
|
||||
let args := stx.getArgs
|
||||
if (← checkLeftRec stx[0]) then
|
||||
if args.size == 1 then throwErrorAt stx "invalid atomic left recursive syntax"
|
||||
let args := args.eraseIdx 0
|
||||
let args := args.eraseIdxIfInBounds 0
|
||||
let args ← args.mapM fun arg => withNestedParser do process arg
|
||||
mkParserSeq args
|
||||
else
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 }
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -340,9 +340,9 @@ where
|
||||
for h : altStxIdx in [0:altStxs.size] do
|
||||
let altStx := altStxs[altStxIdx]
|
||||
let altName := getAltName altStx
|
||||
if let some i := alts.findIdx? (·.1 == altName) then
|
||||
if let some i := alts.findFinIdx? (·.1 == altName) then
|
||||
-- cover named alternative
|
||||
applyAltStx tacSnaps altStxIdx altStx alts[i]!
|
||||
applyAltStx tacSnaps altStxIdx altStx alts[i]
|
||||
alts := alts.eraseIdx i
|
||||
else if !alts.isEmpty && isWildcard altStx then
|
||||
-- cover all alternatives
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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 }
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)]
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -83,7 +83,7 @@ where
|
||||
forallTelescopeReducing t fun xs s => do
|
||||
let motiveType ← instantiateForall motive xs[:numParams]
|
||||
withLocalDecl motiveName BinderInfo.implicit motiveType fun motive => do
|
||||
mkForallFVars (xs.insertAt! numParams motive) s)
|
||||
mkForallFVars (xs.insertIdxIfInBounds numParams motive) s)
|
||||
|
||||
motiveType (indVal : InductiveVal) : MetaM Expr :=
|
||||
forallTelescopeReducing indVal.type fun xs _ => do
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -129,9 +129,9 @@ where
|
||||
let typeNew := b.instantiate1 y
|
||||
if let some (_, lhs, rhs) ← matchEq? d then
|
||||
if lhs.isFVar && ys.contains lhs && args.contains lhs && isNamedPatternProof typeNew y then
|
||||
let some j := ys.getIdx? lhs | unreachable!
|
||||
let some j := ys.indexOf? lhs | unreachable!
|
||||
let ys := ys.eraseIdx j
|
||||
let some k := args.getIdx? lhs | unreachable!
|
||||
let some k := args.indexOf? lhs | unreachable!
|
||||
let mask := mask.set! k false
|
||||
let args := args.map fun arg => if arg == lhs then rhs else arg
|
||||
let arg ← mkEqRefl rhs
|
||||
|
||||
@@ -107,7 +107,7 @@ private def getMajorPosDepElim (declName : Name) (majorPos? : Option Nat) (xs :
|
||||
if motiveArgs.isEmpty then
|
||||
throwError "invalid user defined recursor, '{declName}' does not support dependent elimination, and position of the major premise was not specified (solution: set attribute '[recursor <pos>]', where <pos> is the position of the major premise)"
|
||||
let major := motiveArgs.back!
|
||||
match xs.getIdx? major with
|
||||
match xs.indexOf? major with
|
||||
| some majorPos => pure (major, majorPos, true)
|
||||
| none => throwError "ill-formed recursor '{declName}'"
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -27,7 +27,7 @@ def _root_.Lean.MVarId.clear (mvarId : MVarId) (fvarId : FVarId) : MetaM MVarId
|
||||
throwTacticEx `clear mvarId m!"target depends on '{mkFVar fvarId}'"
|
||||
let lctx := lctx.erase fvarId
|
||||
let localInsts ← getLocalInstances
|
||||
let localInsts := match localInsts.findIdx? fun localInst => localInst.fvar.fvarId! == fvarId with
|
||||
let localInsts := match localInsts.findFinIdx? fun localInst => localInst.fvar.fvarId! == fvarId with
|
||||
| none => localInsts
|
||||
| some idx => localInsts.eraseIdx idx
|
||||
let newMVar ← mkFreshExprMVarAt lctx localInsts mvarDecl.type MetavarKind.syntheticOpaque tag
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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 ()
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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}"
|
||||
|
||||
@@ -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 }
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -248,9 +248,7 @@ instance : Hashable LocalInstance where
|
||||
|
||||
/-- Remove local instance with the given `fvarId`. Do nothing if `localInsts` does not contain any free variable with id `fvarId`. -/
|
||||
def LocalInstances.erase (localInsts : LocalInstances) (fvarId : FVarId) : LocalInstances :=
|
||||
match localInsts.findIdx? (fun inst => inst.fvar.fvarId! == fvarId) with
|
||||
| some idx => localInsts.eraseIdx idx
|
||||
| _ => localInsts
|
||||
localInsts.eraseP (fun inst => inst.fvar.fvarId! == fvarId)
|
||||
|
||||
/-- A kind for the metavariable that determines its unification behaviour.
|
||||
For more information see the large comment at the beginning of this file. -/
|
||||
@@ -1219,7 +1217,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]!
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -338,7 +349,7 @@ def delabAppExplicitCore (fieldNotation : Bool) (numArgs : Nat) (delabHead : (in
|
||||
if idx == 0 then
|
||||
-- If it's the first argument, then we can tag `obj.field` with the first app.
|
||||
head ← withBoundedAppFn (numArgs - 1) <| annotateTermInfo head
|
||||
return Syntax.mkApp head (argStxs.eraseIdx idx)
|
||||
return Syntax.mkApp head (argStxs.eraseIdxIfInBounds idx)
|
||||
else
|
||||
return Syntax.mkApp fnStx argStxs
|
||||
|
||||
@@ -865,7 +876,7 @@ def delabLam : Delab :=
|
||||
-- "default" binder group is the only one that expects binder names
|
||||
-- as a term, i.e. a single `Syntax.ident` or an application thereof
|
||||
let stxCurNames ←
|
||||
if curNames.size > 1 then
|
||||
if h : curNames.size > 1 then
|
||||
`($(curNames.get! 0) $(curNames.eraseIdx 0)*)
|
||||
else
|
||||
pure $ curNames.get! 0;
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
610
src/Lean/Server/Completion/CompletionCollectors.lean
Normal file
610
src/Lean/Server/Completion/CompletionCollectors.lean
Normal 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
|
||||
131
src/Lean/Server/Completion/CompletionInfoSelection.lean
Normal file
131
src/Lean/Server/Completion/CompletionInfoSelection.lean
Normal 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
|
||||
91
src/Lean/Server/Completion/CompletionResolution.lean
Normal file
91
src/Lean/Server/Completion/CompletionResolution.lean
Normal 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
|
||||
22
src/Lean/Server/Completion/CompletionUtils.lean
Normal file
22
src/Lean/Server/Completion/CompletionUtils.lean
Normal 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
|
||||
53
src/Lean/Server/Completion/EligibleHeaderDecls.lean
Normal file
53
src/Lean/Server/Completion/EligibleHeaderDecls.lean
Normal 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
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user