mirror of
https://github.com/leanprover/lean4.git
synced 2026-03-18 19:04:07 +00:00
Compare commits
55 Commits
upstream_a
...
replace
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
54de6271fd | ||
|
|
fdc64def1b | ||
|
|
644d4263f1 | ||
|
|
eb7951e872 | ||
|
|
56d703db8e | ||
|
|
50d661610d | ||
|
|
0554ab39aa | ||
|
|
3a6ebd88bb | ||
|
|
e601cdb193 | ||
|
|
06f73d621b | ||
|
|
c27474341e | ||
|
|
27b962f14d | ||
|
|
2032ffa3fc | ||
|
|
c424d99cc9 | ||
|
|
fbedb79b46 | ||
|
|
1965a022eb | ||
|
|
90b08ef22e | ||
|
|
66e8cb7966 | ||
|
|
4718af5474 | ||
|
|
c138801c3a | ||
|
|
5b4c24ff97 | ||
|
|
1cb7450f40 | ||
|
|
02d1ebb564 | ||
|
|
488bfe2128 | ||
|
|
55402a5899 | ||
|
|
659218cf17 | ||
|
|
904239ae61 | ||
|
|
b548b4faae | ||
|
|
a7364499d2 | ||
|
|
003835111d | ||
|
|
61a8695ab1 | ||
|
|
127214bd18 | ||
|
|
b1944b662c | ||
|
|
a17832ba14 | ||
|
|
561ac09d61 | ||
|
|
f68429d3a7 | ||
|
|
a58232b820 | ||
|
|
696b08dca2 | ||
|
|
3a63b72eea | ||
|
|
9c160b8030 | ||
|
|
4bd75825b4 | ||
|
|
709e9909e7 | ||
|
|
83dd720337 | ||
|
|
ac631f4736 | ||
|
|
1f547225d1 | ||
|
|
09a43990aa | ||
|
|
819848a0db | ||
|
|
8f8b0a8322 | ||
|
|
9f633dcba2 | ||
|
|
cd4c7e4c35 | ||
|
|
9908823764 | ||
|
|
3e313d38f4 | ||
|
|
1b101a3d43 | ||
|
|
adcec8e67a | ||
|
|
86d032ebf9 |
@@ -7,6 +7,8 @@ prelude
|
||||
import Init.Prelude
|
||||
import Init.Notation
|
||||
import Init.Tactics
|
||||
import Init.TacticsExtra
|
||||
import Init.RCases
|
||||
import Init.Core
|
||||
import Init.Control
|
||||
import Init.Data.Basic
|
||||
@@ -23,5 +25,6 @@ import Init.NotationExtra
|
||||
import Init.SimpLemmas
|
||||
import Init.Hints
|
||||
import Init.Conv
|
||||
import Init.Guard
|
||||
import Init.Simproc
|
||||
import Init.SizeOfLemmas
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/-
|
||||
Copyright (c) 2020 Microsoft Corporation. All rights reserved.
|
||||
Released under Apache 2.0 license as described in the file LICENSE.
|
||||
Authors: Leonardo de Moura
|
||||
Authors: Leonardo de Moura, Mario Carneiro
|
||||
-/
|
||||
prelude
|
||||
import Init.Core
|
||||
@@ -123,21 +123,15 @@ theorem byCases {p q : Prop} (hpq : p → q) (hnpq : ¬p → q) : q :=
|
||||
theorem byContradiction {p : Prop} (h : ¬p → False) : p :=
|
||||
Decidable.byContradiction (dec := propDecidable _) h
|
||||
|
||||
end Classical
|
||||
|
||||
/--
|
||||
`by_cases (h :)? p` splits the main goal into two cases, assuming `h : p` in the first branch, and `h : ¬ p` in the second branch.
|
||||
-/
|
||||
syntax "by_cases " (atomic(ident " : "))? term : tactic
|
||||
|
||||
macro_rules
|
||||
| `(tactic| by_cases $e) => `(tactic| by_cases h : $e)
|
||||
macro_rules
|
||||
| `(tactic| by_cases $h : $e) =>
|
||||
`(tactic|
|
||||
cases em $e with
|
||||
| inl $h => _
|
||||
| inr $h => _)
|
||||
| `(tactic| by_cases $e) =>
|
||||
`(tactic|
|
||||
cases em $e with
|
||||
| inl h => _
|
||||
| inr h => _)
|
||||
|
||||
end Classical
|
||||
`(tactic| open Classical in refine if $h:ident : $e then ?pos else ?neg)
|
||||
|
||||
@@ -290,6 +290,12 @@ between e.g. `↑x + ↑y` and `↑(x + y)`.
|
||||
-/
|
||||
syntax:1024 (name := coeNotation) "↑" term:1024 : term
|
||||
|
||||
/-- `⇑ t` coerces `t` to a function. -/
|
||||
syntax:1024 (name := coeFunNotation) "⇑" term:1024 : term
|
||||
|
||||
/-- `↥ t` coerces `t` to a type. -/
|
||||
syntax:1024 (name := coeSortNotation) "↥" term:1024 : term
|
||||
|
||||
/-! # Basic instances -/
|
||||
|
||||
instance boolToProp : Coe Bool Prop where
|
||||
|
||||
@@ -666,6 +666,8 @@ theorem Iff.refl (a : Prop) : a ↔ a :=
|
||||
protected theorem Iff.rfl {a : Prop} : a ↔ a :=
|
||||
Iff.refl a
|
||||
|
||||
macro_rules | `(tactic| rfl) => `(tactic| exact Iff.rfl)
|
||||
|
||||
theorem Iff.trans (h₁ : a ↔ b) (h₂ : b ↔ c) : a ↔ c :=
|
||||
Iff.intro
|
||||
(fun ha => Iff.mp h₂ (Iff.mp h₁ ha))
|
||||
|
||||
@@ -150,18 +150,18 @@ theorem Context.evalList_mergeIdem (ctx : Context α) (h : ContextInformation.is
|
||||
rfl
|
||||
| cons z zs =>
|
||||
by_cases h₂ : x = y
|
||||
case inl =>
|
||||
case pos =>
|
||||
rw [h₂, mergeIdem_head, ih]
|
||||
simp [evalList, ←ctx.assoc.1, h.1, EvalInformation.evalOp]
|
||||
case inr =>
|
||||
case neg =>
|
||||
rw [mergeIdem_head2]
|
||||
by_cases h₃ : y = z
|
||||
case inl =>
|
||||
case pos =>
|
||||
simp [mergeIdem_head, h₃, evalList]
|
||||
cases h₄ : mergeIdem (z :: zs) with
|
||||
| nil => apply absurd h₄; apply mergeIdem_nonEmpty; simp
|
||||
| cons u us => simp_all [mergeIdem, mergeIdem.loop, evalList]
|
||||
case inr =>
|
||||
case neg =>
|
||||
simp [mergeIdem_head2, h₃, evalList] at *
|
||||
rw [ih]
|
||||
assumption
|
||||
|
||||
@@ -515,6 +515,12 @@ def replace (s pattern replacement : String) : String :=
|
||||
termination_by s.endPos.1 - pos.1
|
||||
loop "" 0 0
|
||||
|
||||
/-- Return the beginning of the line that contains character `pos`. -/
|
||||
def findLineStart (s : String) (pos : String.Pos) : String.Pos :=
|
||||
match s.revFindAux (· = '\n') pos with
|
||||
| none => 0
|
||||
| some n => ⟨n.byteIdx + 1⟩
|
||||
|
||||
end String
|
||||
|
||||
namespace Substring
|
||||
|
||||
129
src/Init/Guard.lean
Normal file
129
src/Init/Guard.lean
Normal file
@@ -0,0 +1,129 @@
|
||||
/-
|
||||
Copyright (c) 2021 Mario Carneiro. All rights reserved.
|
||||
Released under Apache 2.0 license as described in the file LICENSE.
|
||||
Authors: Mario Carneiro
|
||||
-/
|
||||
prelude
|
||||
import Init.Tactics
|
||||
import Init.Conv
|
||||
import Init.NotationExtra
|
||||
|
||||
namespace Lean.Parser
|
||||
|
||||
/-- Reducible defeq matching for `guard_hyp` types -/
|
||||
syntax colonR := " : "
|
||||
/-- Default-reducibility defeq matching for `guard_hyp` types -/
|
||||
syntax colonD := " :~ "
|
||||
/-- Syntactic matching for `guard_hyp` types -/
|
||||
syntax colonS := " :ₛ "
|
||||
/-- Alpha-eq matching for `guard_hyp` types -/
|
||||
syntax colonA := " :ₐ "
|
||||
/-- The `guard_hyp` type specifier, one of `:`, `:~`, `:ₛ`, `:ₐ` -/
|
||||
syntax colon := colonR <|> colonD <|> colonS <|> colonA
|
||||
|
||||
/-- Reducible defeq matching for `guard_hyp` values -/
|
||||
syntax colonEqR := " := "
|
||||
/-- Default-reducibility defeq matching for `guard_hyp` values -/
|
||||
syntax colonEqD := " :=~ "
|
||||
/-- Syntactic matching for `guard_hyp` values -/
|
||||
syntax colonEqS := " :=ₛ "
|
||||
/-- Alpha-eq matching for `guard_hyp` values -/
|
||||
syntax colonEqA := " :=ₐ "
|
||||
/-- The `guard_hyp` value specifier, one of `:=`, `:=~`, `:=ₛ`, `:=ₐ` -/
|
||||
syntax colonEq := colonEqR <|> colonEqD <|> colonEqS <|> colonEqA
|
||||
|
||||
/-- Reducible defeq matching for `guard_expr` -/
|
||||
syntax equalR := " = "
|
||||
/-- Default-reducibility defeq matching for `guard_expr` -/
|
||||
syntax equalD := " =~ "
|
||||
/-- Syntactic matching for `guard_expr` -/
|
||||
syntax equalS := " =ₛ "
|
||||
/-- Alpha-eq matching for `guard_expr` -/
|
||||
syntax equalA := " =ₐ "
|
||||
/-- The `guard_expr` matching specifier, one of `=`, `=~`, `=ₛ`, `=ₐ` -/
|
||||
syntax equal := equalR <|> equalD <|> equalS <|> equalA
|
||||
|
||||
namespace Tactic
|
||||
|
||||
/--
|
||||
Tactic to check equality of two expressions.
|
||||
* `guard_expr e = e'` checks that `e` and `e'` are defeq at reducible transparency.
|
||||
* `guard_expr e =~ e'` checks that `e` and `e'` are defeq at default transparency.
|
||||
* `guard_expr e =ₛ e'` checks that `e` and `e'` are syntactically equal.
|
||||
* `guard_expr e =ₐ e'` checks that `e` and `e'` are alpha-equivalent.
|
||||
|
||||
Both `e` and `e'` are elaborated then have their metavariables instantiated before the equality
|
||||
check. Their types are unified (using `isDefEqGuarded`) before synthetic metavariables are
|
||||
processed, which helps with default instance handling.
|
||||
-/
|
||||
syntax (name := guardExpr) "guard_expr " term:51 equal term : tactic
|
||||
@[inherit_doc guardExpr]
|
||||
syntax (name := guardExprConv) "guard_expr " term:51 equal term : conv
|
||||
|
||||
/--
|
||||
Tactic to check that the target agrees with a given expression.
|
||||
* `guard_target = e` checks that the target is defeq at reducible transparency to `e`.
|
||||
* `guard_target =~ e` checks that the target is defeq at default transparency to `e`.
|
||||
* `guard_target =ₛ e` checks that the target is syntactically equal to `e`.
|
||||
* `guard_target =ₐ e` checks that the target is alpha-equivalent to `e`.
|
||||
|
||||
The term `e` is elaborated with the type of the goal as the expected type, which is mostly
|
||||
useful within `conv` mode.
|
||||
-/
|
||||
syntax (name := guardTarget) "guard_target " equal term : tactic
|
||||
@[inherit_doc guardTarget]
|
||||
syntax (name := guardTargetConv) "guard_target " equal term : conv
|
||||
|
||||
/--
|
||||
Tactic to check that a named hypothesis has a given type and/or value.
|
||||
|
||||
* `guard_hyp h : t` checks the type up to reducible defeq,
|
||||
* `guard_hyp h :~ t` checks the type up to default defeq,
|
||||
* `guard_hyp h :ₛ t` checks the type up to syntactic equality,
|
||||
* `guard_hyp h :ₐ t` checks the type up to alpha equality.
|
||||
* `guard_hyp h := v` checks value up to reducible defeq,
|
||||
* `guard_hyp h :=~ v` checks value up to default defeq,
|
||||
* `guard_hyp h :=ₛ v` checks value up to syntactic equality,
|
||||
* `guard_hyp h :=ₐ v` checks the value up to alpha equality.
|
||||
|
||||
The value `v` is elaborated using the type of `h` as the expected type.
|
||||
-/
|
||||
syntax (name := guardHyp)
|
||||
"guard_hyp " term:max (colon term)? (colonEq term)? : tactic
|
||||
@[inherit_doc guardHyp] syntax (name := guardHypConv)
|
||||
"guard_hyp " term:max (colon term)? (colonEq term)? : conv
|
||||
|
||||
end Tactic
|
||||
|
||||
namespace Command
|
||||
|
||||
/--
|
||||
Command to check equality of two expressions.
|
||||
* `#guard_expr e = e'` checks that `e` and `e'` are defeq at reducible transparency.
|
||||
* `#guard_expr e =~ e'` checks that `e` and `e'` are defeq at default transparency.
|
||||
* `#guard_expr e =ₛ e'` checks that `e` and `e'` are syntactically equal.
|
||||
* `#guard_expr e =ₐ e'` checks that `e` and `e'` are alpha-equivalent.
|
||||
|
||||
This is a command version of the `guard_expr` tactic. -/
|
||||
syntax (name := guardExprCmd) "#guard_expr " term:51 equal term : command
|
||||
|
||||
/--
|
||||
Command to check that an expression evaluates to `true`.
|
||||
|
||||
`#guard e` elaborates `e` ensuring its type is `Bool` then evaluates `e` and checks that
|
||||
the result is `true`. The term is elaborated *without* variables declared using `variable`, since
|
||||
these cannot be evaluated.
|
||||
|
||||
Since this makes use of coercions, so long as a proposition `p` is decidable, one can write
|
||||
`#guard p` rather than `#guard decide p`. A consequence to this is that if there is decidable
|
||||
equality one can write `#guard a = b`. Note that this is not exactly the same as checking
|
||||
if `a` and `b` evaluate to the same thing since it uses the `DecidableEq` instance to do
|
||||
the evaluation.
|
||||
|
||||
Note: this uses the untrusted evaluator, so `#guard` passing is *not* a proof that the
|
||||
expression equals `true`. -/
|
||||
syntax (name := guardCmd) "#guard " term : command
|
||||
|
||||
end Command
|
||||
|
||||
end Lean.Parser
|
||||
@@ -268,6 +268,7 @@ syntax (name := rawNatLit) "nat_lit " num : term
|
||||
@[inherit_doc] infixr:90 " ∘ " => Function.comp
|
||||
@[inherit_doc] infixr:35 " × " => Prod
|
||||
|
||||
@[inherit_doc] infix:50 " ∣ " => Dvd.dvd
|
||||
@[inherit_doc] infixl:55 " ||| " => HOr.hOr
|
||||
@[inherit_doc] infixl:58 " ^^^ " => HXor.hXor
|
||||
@[inherit_doc] infixl:60 " &&& " => HAnd.hAnd
|
||||
@@ -484,6 +485,13 @@ existing code. It may be removed in a future version of the library.
|
||||
-/
|
||||
syntax (name := deprecated) "deprecated" (ppSpace ident)? : attr
|
||||
|
||||
/--
|
||||
The `@[coe]` attribute on a function (which should also appear in a
|
||||
`instance : Coe A B := ⟨myFn⟩` declaration) allows the delaborator to show
|
||||
applications of this function as `↑` when printing expressions.
|
||||
-/
|
||||
syntax (name := Attr.coe) "coe" : attr
|
||||
|
||||
/--
|
||||
When `parent_dir` contains the current Lean file, `include_str "path" / "to" / "file"` becomes
|
||||
a string literal with the contents of the file at `"parent_dir" / "path" / "to" / "file"`. If this
|
||||
|
||||
@@ -1314,6 +1314,11 @@ class Mod (α : Type u) where
|
||||
/-- `a % b` computes the remainder upon dividing `a` by `b`. See `HMod`. -/
|
||||
mod : α → α → α
|
||||
|
||||
/-- Notation typeclass for the `∣` operation (typed as `\|`), which represents divisibility. -/
|
||||
class Dvd (α : Type _) where
|
||||
/-- Divisibility. `a ∣ b` (typed as `\|`) means that there is some `c` such that `b = a * c`. -/
|
||||
dvd : α → α → Prop
|
||||
|
||||
/--
|
||||
The homogeneous version of `HPow`: `a ^ b : α` where `a : α`, `b : β`.
|
||||
(The right argument is not the same as the left since we often want this even
|
||||
|
||||
192
src/Init/RCases.lean
Normal file
192
src/Init/RCases.lean
Normal file
@@ -0,0 +1,192 @@
|
||||
/-
|
||||
Copyright (c) 2017 Mario Carneiro. All rights reserved.
|
||||
Released under Apache 2.0 license as described in the file LICENSE.
|
||||
Authors: Mario Carneiro, Jacob von Raumer
|
||||
-/
|
||||
prelude
|
||||
import Init.Tactics
|
||||
import Init.NotationExtra
|
||||
|
||||
/-!
|
||||
# Recursive cases (`rcases`) tactic and related tactics
|
||||
|
||||
`rcases` is a tactic that will perform `cases` recursively, according to a pattern. It is used to
|
||||
destructure hypotheses or expressions composed of inductive types like `h1 : a ∧ b ∧ c ∨ d` or
|
||||
`h2 : ∃ x y, trans_rel R x y`. Usual usage might be `rcases h1 with ⟨ha, hb, hc⟩ | hd` or
|
||||
`rcases h2 with ⟨x, y, _ | ⟨z, hxz, hzy⟩⟩` for these examples.
|
||||
|
||||
Each element of an `rcases` pattern is matched against a particular local hypothesis (most of which
|
||||
are generated during the execution of `rcases` and represent individual elements destructured from
|
||||
the input expression). An `rcases` pattern has the following grammar:
|
||||
|
||||
* A name like `x`, which names the active hypothesis as `x`.
|
||||
* A blank `_`, which does nothing (letting the automatic naming system used by `cases` name the
|
||||
hypothesis).
|
||||
* A hyphen `-`, which clears the active hypothesis and any dependents.
|
||||
* The keyword `rfl`, which expects the hypothesis to be `h : a = b`, and calls `subst` on the
|
||||
hypothesis (which has the effect of replacing `b` with `a` everywhere or vice versa).
|
||||
* A type ascription `p : ty`, which sets the type of the hypothesis to `ty` and then matches it
|
||||
against `p`. (Of course, `ty` must unify with the actual type of `h` for this to work.)
|
||||
* A tuple pattern `⟨p1, p2, p3⟩`, which matches a constructor with many arguments, or a series
|
||||
of nested conjunctions or existentials. For example if the active hypothesis is `a ∧ b ∧ c`,
|
||||
then the conjunction will be destructured, and `p1` will be matched against `a`, `p2` against `b`
|
||||
and so on.
|
||||
* A `@` before a tuple pattern as in `@⟨p1, p2, p3⟩` will bind all arguments in the constructor,
|
||||
while leaving the `@` off will only use the patterns on the explicit arguments.
|
||||
* An alternation pattern `p1 | p2 | p3`, which matches an inductive type with multiple constructors,
|
||||
or a nested disjunction like `a ∨ b ∨ c`.
|
||||
|
||||
The patterns are fairly liberal about the exact shape of the constructors, and will insert
|
||||
additional alternation branches and tuple arguments if there are not enough arguments provided, and
|
||||
reuse the tail for further matches if there are too many arguments provided to alternation and
|
||||
tuple patterns.
|
||||
|
||||
This file also contains the `obtain` and `rintro` tactics, which use the same syntax of `rcases`
|
||||
patterns but with a slightly different use case:
|
||||
|
||||
* `rintro` (or `rintros`) is used like `rintro x ⟨y, z⟩` and is the same as `intros` followed by
|
||||
`rcases` on the newly introduced arguments.
|
||||
* `obtain` is the same as `rcases` but with a syntax styled after `have` rather than `cases`.
|
||||
`obtain ⟨hx, hy⟩ | hz := foo` is equivalent to `rcases foo with ⟨hx, hy⟩ | hz`. Unlike `rcases`,
|
||||
`obtain` also allows one to omit `:= foo`, although a type must be provided in this case,
|
||||
as in `obtain ⟨hx, hy⟩ | hz : a ∧ b ∨ c`, in which case it produces a subgoal for proving
|
||||
`a ∧ b ∨ c` in addition to the subgoals `hx : a, hy : b |- goal` and `hz : c |- goal`.
|
||||
|
||||
## Tags
|
||||
|
||||
rcases, rintro, obtain, destructuring, cases, pattern matching, match
|
||||
-/
|
||||
namespace Lean.Parser.Tactic
|
||||
|
||||
/-- The syntax category of `rcases` patterns. -/
|
||||
declare_syntax_cat rcasesPat
|
||||
/-- A medium precedence `rcases` pattern is a list of `rcasesPat` separated by `|` -/
|
||||
syntax rcasesPatMed := sepBy1(rcasesPat, " | ")
|
||||
/-- A low precedence `rcases` pattern is a `rcasesPatMed` optionally followed by `: ty` -/
|
||||
syntax rcasesPatLo := rcasesPatMed (" : " term)?
|
||||
/-- `x` is a pattern which binds `x` -/
|
||||
syntax (name := rcasesPat.one) ident : rcasesPat
|
||||
/-- `_` is a pattern which ignores the value and gives it an inaccessible name -/
|
||||
syntax (name := rcasesPat.ignore) "_" : rcasesPat
|
||||
/-- `-` is a pattern which removes the value from the context -/
|
||||
syntax (name := rcasesPat.clear) "-" : rcasesPat
|
||||
/--
|
||||
A `@` before a tuple pattern as in `@⟨p1, p2, p3⟩` will bind all arguments in the constructor,
|
||||
while leaving the `@` off will only use the patterns on the explicit arguments.
|
||||
-/
|
||||
syntax (name := rcasesPat.explicit) "@" noWs rcasesPat : rcasesPat
|
||||
/--
|
||||
`⟨pat, ...⟩` is a pattern which matches on a tuple-like constructor
|
||||
or multi-argument inductive constructor
|
||||
-/
|
||||
syntax (name := rcasesPat.tuple) "⟨" rcasesPatLo,* "⟩" : rcasesPat
|
||||
/-- `(pat)` is a pattern which resets the precedence to low -/
|
||||
syntax (name := rcasesPat.paren) "(" rcasesPatLo ")" : rcasesPat
|
||||
|
||||
/-- The syntax category of `rintro` patterns. -/
|
||||
declare_syntax_cat rintroPat
|
||||
/-- An `rcases` pattern is an `rintro` pattern -/
|
||||
syntax (name := rintroPat.one) rcasesPat : rintroPat
|
||||
/--
|
||||
A multi argument binder `(pat1 pat2 : ty)` binds a list of patterns and gives them all type `ty`.
|
||||
-/
|
||||
syntax (name := rintroPat.binder) (priority := default+1) -- to override rcasesPat.paren
|
||||
"(" rintroPat+ (" : " term)? ")" : rintroPat
|
||||
|
||||
/- TODO
|
||||
/--
|
||||
`rcases? e` will perform case splits on `e` in the same way as `rcases e`,
|
||||
but rather than accepting a pattern, it does a maximal cases and prints the
|
||||
pattern that would produce this case splitting. The default maximum depth is 5,
|
||||
but this can be modified with `rcases? e : n`.
|
||||
-/
|
||||
syntax (name := rcases?) "rcases?" casesTarget,* (" : " num)? : tactic
|
||||
-/
|
||||
|
||||
/--
|
||||
`rcases` is a tactic that will perform `cases` recursively, according to a pattern. It is used to
|
||||
destructure hypotheses or expressions composed of inductive types like `h1 : a ∧ b ∧ c ∨ d` or
|
||||
`h2 : ∃ x y, trans_rel R x y`. Usual usage might be `rcases h1 with ⟨ha, hb, hc⟩ | hd` or
|
||||
`rcases h2 with ⟨x, y, _ | ⟨z, hxz, hzy⟩⟩` for these examples.
|
||||
|
||||
Each element of an `rcases` pattern is matched against a particular local hypothesis (most of which
|
||||
are generated during the execution of `rcases` and represent individual elements destructured from
|
||||
the input expression). An `rcases` pattern has the following grammar:
|
||||
|
||||
* A name like `x`, which names the active hypothesis as `x`.
|
||||
* A blank `_`, which does nothing (letting the automatic naming system used by `cases` name the
|
||||
hypothesis).
|
||||
* A hyphen `-`, which clears the active hypothesis and any dependents.
|
||||
* The keyword `rfl`, which expects the hypothesis to be `h : a = b`, and calls `subst` on the
|
||||
hypothesis (which has the effect of replacing `b` with `a` everywhere or vice versa).
|
||||
* A type ascription `p : ty`, which sets the type of the hypothesis to `ty` and then matches it
|
||||
against `p`. (Of course, `ty` must unify with the actual type of `h` for this to work.)
|
||||
* A tuple pattern `⟨p1, p2, p3⟩`, which matches a constructor with many arguments, or a series
|
||||
of nested conjunctions or existentials. For example if the active hypothesis is `a ∧ b ∧ c`,
|
||||
then the conjunction will be destructured, and `p1` will be matched against `a`, `p2` against `b`
|
||||
and so on.
|
||||
* A `@` before a tuple pattern as in `@⟨p1, p2, p3⟩` will bind all arguments in the constructor,
|
||||
while leaving the `@` off will only use the patterns on the explicit arguments.
|
||||
* An alteration pattern `p1 | p2 | p3`, which matches an inductive type with multiple constructors,
|
||||
or a nested disjunction like `a ∨ b ∨ c`.
|
||||
|
||||
A pattern like `⟨a, b, c⟩ | ⟨d, e⟩` will do a split over the inductive datatype,
|
||||
naming the first three parameters of the first constructor as `a,b,c` and the
|
||||
first two of the second constructor `d,e`. If the list is not as long as the
|
||||
number of arguments to the constructor or the number of constructors, the
|
||||
remaining variables will be automatically named. If there are nested brackets
|
||||
such as `⟨⟨a⟩, b | c⟩ | d` then these will cause more case splits as necessary.
|
||||
If there are too many arguments, such as `⟨a, b, c⟩` for splitting on
|
||||
`∃ x, ∃ y, p x`, then it will be treated as `⟨a, ⟨b, c⟩⟩`, splitting the last
|
||||
parameter as necessary.
|
||||
|
||||
`rcases` also has special support for quotient types: quotient induction into Prop works like
|
||||
matching on the constructor `quot.mk`.
|
||||
|
||||
`rcases h : e with PAT` will do the same as `rcases e with PAT` with the exception that an
|
||||
assumption `h : e = PAT` will be added to the context.
|
||||
-/
|
||||
syntax (name := rcases) "rcases" casesTarget,* (" with " rcasesPatLo)? : tactic
|
||||
|
||||
/--
|
||||
The `obtain` tactic is a combination of `have` and `rcases`. See `rcases` for
|
||||
a description of supported patterns.
|
||||
|
||||
```lean
|
||||
obtain ⟨patt⟩ : type := proof
|
||||
```
|
||||
is equivalent to
|
||||
```lean
|
||||
have h : type := proof
|
||||
rcases h with ⟨patt⟩
|
||||
```
|
||||
|
||||
If `⟨patt⟩` is omitted, `rcases` will try to infer the pattern.
|
||||
|
||||
If `type` is omitted, `:= proof` is required.
|
||||
-/
|
||||
syntax (name := obtain) "obtain" (ppSpace rcasesPatMed)? (" : " term)? (" := " term,+)? : tactic
|
||||
|
||||
/- TODO
|
||||
/--
|
||||
`rintro?` will introduce and case split on variables in the same way as
|
||||
`rintro`, but will also print the `rintro` invocation that would have the same
|
||||
result. Like `rcases?`, `rintro? : n` allows for modifying the
|
||||
depth of splitting; the default is 5.
|
||||
-/
|
||||
syntax (name := rintro?) "rintro?" (" : " num)? : tactic
|
||||
-/
|
||||
|
||||
/--
|
||||
The `rintro` tactic is a combination of the `intros` tactic with `rcases` to
|
||||
allow for destructuring patterns while introducing variables. See `rcases` for
|
||||
a description of supported patterns. For example, `rintro (a | ⟨b, c⟩) ⟨d, e⟩`
|
||||
will introduce two variables, and then do case splits on both of them producing
|
||||
two subgoals, one with variables `a d e` and the other with `b c d e`.
|
||||
|
||||
`rintro`, unlike `rcases`, also supports the form `(x y : ty)` for introducing
|
||||
and type-ascripting multiple variables at once, similar to binders.
|
||||
-/
|
||||
syntax (name := rintro) "rintro" (ppSpace colGt rintroPat)+ (" : " term)? : tactic
|
||||
|
||||
end Lean.Parser.Tactic
|
||||
@@ -6,11 +6,15 @@ Authors: Gabriel Ebner
|
||||
prelude
|
||||
import Init.System.IO
|
||||
|
||||
set_option linter.missingDocs true
|
||||
|
||||
namespace IO
|
||||
|
||||
/-- Internally, a `Promise` is just a `Task` that is in the "Promised" or "Finished" state. -/
|
||||
private opaque PromiseImpl (α : Type) : { P : Type // Nonempty α ↔ Nonempty P } :=
|
||||
⟨Task α, fun ⟨_⟩ => ⟨⟨‹_›⟩⟩, fun ⟨⟨_⟩⟩ => ⟨‹_›⟩⟩
|
||||
private opaque PromisePointed : NonemptyType.{0}
|
||||
|
||||
private structure PromiseImpl (α : Type) : Type where
|
||||
prom : PromisePointed.type
|
||||
h : Nonempty α
|
||||
|
||||
/--
|
||||
`Promise α` allows you to create a `Task α` whose value is provided later by calling `resolve`.
|
||||
@@ -26,10 +30,10 @@ Every promise must eventually be resolved.
|
||||
Otherwise the memory used for the promise will be leaked,
|
||||
and any tasks depending on the promise's result will wait forever.
|
||||
-/
|
||||
def Promise (α : Type) : Type := (PromiseImpl α).1
|
||||
def Promise (α : Type) : Type := PromiseImpl α
|
||||
|
||||
instance [Nonempty α] : Nonempty (Promise α) :=
|
||||
(PromiseImpl α).2.1 inferInstance
|
||||
instance [s : Nonempty α] : Nonempty (Promise α) :=
|
||||
Nonempty.intro { prom := Classical.choice PromisePointed.property, h := s }
|
||||
|
||||
/-- Creates a new `Promise`. -/
|
||||
@[extern "lean_io_promise_new"]
|
||||
@@ -43,15 +47,12 @@ Only the first call to this function has an effect.
|
||||
@[extern "lean_io_promise_resolve"]
|
||||
opaque Promise.resolve (value : α) (promise : @& Promise α) : BaseIO Unit
|
||||
|
||||
private unsafe def Promise.resultImpl (promise : Promise α) : Task α :=
|
||||
unsafeCast promise
|
||||
|
||||
/--
|
||||
The result task of a `Promise`.
|
||||
|
||||
The task blocks until `Promise.resolve` is called.
|
||||
-/
|
||||
@[implemented_by Promise.resultImpl]
|
||||
@[extern "lean_io_promise_result"]
|
||||
opaque Promise.result (promise : Promise α) : Task α :=
|
||||
have : Nonempty α := (PromiseImpl α).2.2 ⟨promise⟩
|
||||
have : Nonempty α := promise.h
|
||||
Classical.choice inferInstance
|
||||
|
||||
@@ -207,6 +207,28 @@ the first matching constructor, or else fails.
|
||||
-/
|
||||
syntax (name := constructor) "constructor" : tactic
|
||||
|
||||
/--
|
||||
Applies the second constructor when
|
||||
the goal is an inductive type with exactly two constructors, or fails otherwise.
|
||||
```
|
||||
example : True ∨ False := by
|
||||
left
|
||||
trivial
|
||||
```
|
||||
-/
|
||||
syntax (name := left) "left" : tactic
|
||||
|
||||
/--
|
||||
Applies the second constructor when
|
||||
the goal is an inductive type with exactly two constructors, or fails otherwise.
|
||||
```
|
||||
example {p q : Prop} (h : q) : p ∨ q := by
|
||||
right
|
||||
exact h
|
||||
```
|
||||
-/
|
||||
syntax (name := right) "right" : tactic
|
||||
|
||||
/--
|
||||
* `case tag => tac` focuses on the goal with case name `tag` and solves it using `tac`,
|
||||
or else fails.
|
||||
@@ -323,9 +345,14 @@ syntax (name := eqRefl) "eq_refl" : tactic
|
||||
`rfl` tries to close the current goal using reflexivity.
|
||||
This is supposed to be an extensible tactic and users can add their own support
|
||||
for new reflexive relations.
|
||||
|
||||
Remark: `rfl` is an extensible tactic. We later add `macro_rules` to try different
|
||||
reflexivity theorems (e.g., `Iff.rfl`).
|
||||
-/
|
||||
macro "rfl" : tactic => `(tactic| eq_refl)
|
||||
|
||||
macro_rules | `(tactic| rfl) => `(tactic| exact HEq.rfl)
|
||||
|
||||
/--
|
||||
`rfl'` is similar to `rfl`, but disables smart unfolding and unfolds all kinds of definitions,
|
||||
theorems included (relevant for declarations defined by well-founded recursion).
|
||||
@@ -371,7 +398,7 @@ syntax locationWildcard := " *"
|
||||
A hypothesis location specification consists of 1 or more hypothesis references
|
||||
and optionally `⊢` denoting the goal.
|
||||
-/
|
||||
syntax locationHyp := (ppSpace colGt term:max)+ ppSpace patternIgnore( atomic("|" noWs "-") <|> "⊢")?
|
||||
syntax locationHyp := (ppSpace colGt term:max)+ patternIgnore(ppSpace (atomic("|" noWs "-") <|> "⊢"))?
|
||||
|
||||
/--
|
||||
Location specifications are used by many tactics that can operate on either the
|
||||
@@ -432,13 +459,17 @@ syntax (name := rewriteSeq) "rewrite" (config)? rwRuleSeq (location)? : tactic
|
||||
/--
|
||||
`rw` is like `rewrite`, but also tries to close the goal by "cheap" (reducible) `rfl` afterwards.
|
||||
-/
|
||||
macro (name := rwSeq) "rw" c:(config)? s:rwRuleSeq l:(location)? : tactic =>
|
||||
macro (name := rwSeq) "rw " c:(config)? s:rwRuleSeq l:(location)? : tactic =>
|
||||
match s with
|
||||
| `(rwRuleSeq| [$rs,*]%$rbrak) =>
|
||||
-- We show the `rfl` state on `]`
|
||||
`(tactic| (rewrite $(c)? [$rs,*] $(l)?; with_annotate_state $rbrak (try (with_reducible rfl))))
|
||||
| _ => Macro.throwUnsupported
|
||||
|
||||
/-- `rwa` calls `rw`, then closes any remaining goals using `assumption`. -/
|
||||
macro "rwa " rws:rwRuleSeq loc:(location)? : tactic =>
|
||||
`(tactic| (rw $rws:rwRuleSeq $[$loc:location]?; assumption))
|
||||
|
||||
/--
|
||||
The `injection` tactic is based on the fact that constructors of inductive data
|
||||
types are injections.
|
||||
@@ -816,6 +847,100 @@ while `congr 2` produces the intended `⊢ x + y = y + x`.
|
||||
-/
|
||||
syntax (name := congr) "congr" (ppSpace num)? : tactic
|
||||
|
||||
|
||||
/--
|
||||
In tactic mode, `if h : t then tac1 else tac2` can be used as alternative syntax for:
|
||||
```
|
||||
by_cases h : t
|
||||
· tac1
|
||||
· tac2
|
||||
```
|
||||
It performs case distinction on `h : t` or `h : ¬t` and `tac1` and `tac2` are the subproofs.
|
||||
|
||||
You can use `?_` or `_` for either subproof to delay the goal to after the tactic, but
|
||||
if a tactic sequence is provided for `tac1` or `tac2` then it will require the goal to be closed
|
||||
by the end of the block.
|
||||
-/
|
||||
syntax (name := tacDepIfThenElse)
|
||||
ppRealGroup(ppRealFill(ppIndent("if " binderIdent " : " term " then") ppSpace matchRhsTacticSeq)
|
||||
ppDedent(ppSpace) ppRealFill("else " matchRhsTacticSeq)) : tactic
|
||||
|
||||
/--
|
||||
In tactic mode, `if t then tac1 else tac2` is alternative syntax for:
|
||||
```
|
||||
by_cases t
|
||||
· tac1
|
||||
· tac2
|
||||
```
|
||||
It performs case distinction on `h† : t` or `h† : ¬t`, where `h†` is an anonymous
|
||||
hypothesis, and `tac1` and `tac2` are the subproofs. (It doesn't actually use
|
||||
nondependent `if`, since this wouldn't add anything to the context and hence would be
|
||||
useless for proving theorems. To actually insert an `ite` application use
|
||||
`refine if t then ?_ else ?_`.)
|
||||
-/
|
||||
syntax (name := tacIfThenElse)
|
||||
ppRealGroup(ppRealFill(ppIndent("if " term " then") ppSpace matchRhsTacticSeq)
|
||||
ppDedent(ppSpace) ppRealFill("else " matchRhsTacticSeq)) : tactic
|
||||
|
||||
/--
|
||||
The tactic `nofun` is shorthand for `exact nofun`: it introduces the assumptions, then performs an
|
||||
empty pattern match, closing the goal if the introduced pattern is impossible.
|
||||
-/
|
||||
macro "nofun" : tactic => `(tactic| exact nofun)
|
||||
|
||||
/--
|
||||
The tactic `nomatch h` is shorthand for `exact nomatch h`.
|
||||
-/
|
||||
macro "nomatch " es:term,+ : tactic =>
|
||||
`(tactic| exact nomatch $es:term,*)
|
||||
|
||||
/--
|
||||
Acts like `have`, but removes a hypothesis with the same name as
|
||||
this one if possible. For example, if the state is:
|
||||
|
||||
```lean
|
||||
f : α → β
|
||||
h : α
|
||||
⊢ goal
|
||||
```
|
||||
|
||||
Then after `replace h := f h` the state will be:
|
||||
|
||||
```lean
|
||||
f : α → β
|
||||
h : β
|
||||
⊢ goal
|
||||
```
|
||||
|
||||
whereas `have h := f h` would result in:
|
||||
|
||||
```lean
|
||||
f : α → β
|
||||
h† : α
|
||||
h : β
|
||||
⊢ goal
|
||||
```
|
||||
|
||||
This can be used to simulate the `specialize` and `apply at` tactics of Coq.
|
||||
-/
|
||||
syntax (name := replace) "replace" haveDecl : tactic
|
||||
|
||||
/--
|
||||
`repeat' tac` runs `tac` on all of the goals to produce a new list of goals,
|
||||
then runs `tac` again on all of those goals, and repeats until `tac` fails on all remaining goals.
|
||||
-/
|
||||
syntax (name := repeat') "repeat' " tacticSeq : tactic
|
||||
|
||||
/--
|
||||
`repeat1' tac` applies `tac` to main goal at least once. If the application succeeds,
|
||||
the tactic is applied recursively to the generated subgoals until it eventually fails.
|
||||
-/
|
||||
syntax (name := repeat1') "repeat1' " tacticSeq : tactic
|
||||
|
||||
/-- `and_intros` applies `And.intro` until it does not make progress. -/
|
||||
syntax "and_intros" : tactic
|
||||
macro_rules | `(tactic| and_intros) => `(tactic| repeat' refine And.intro ?_ ?_)
|
||||
|
||||
end Tactic
|
||||
|
||||
namespace Attr
|
||||
|
||||
66
src/Init/TacticsExtra.lean
Normal file
66
src/Init/TacticsExtra.lean
Normal file
@@ -0,0 +1,66 @@
|
||||
/-
|
||||
Copyright (c) 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
Released under Apache 2.0 license as described in the file LICENSE.
|
||||
Authors: Leonardo de Moura, Mario Carneiro
|
||||
-/
|
||||
prelude
|
||||
import Init.Tactics
|
||||
import Init.NotationExtra
|
||||
|
||||
/-!
|
||||
Extra tactics and implementation for some tactics defined at `Init/Tactic.lean`
|
||||
-/
|
||||
namespace Lean.Parser.Tactic
|
||||
|
||||
private def expandIfThenElse
|
||||
(ifTk thenTk elseTk pos neg : Syntax)
|
||||
(mkIf : Term → Term → MacroM Term) : MacroM (TSyntax `tactic) := do
|
||||
let mkCase tk holeOrTacticSeq mkName : MacroM (Term × Array (TSyntax `tactic)) := do
|
||||
if holeOrTacticSeq.isOfKind `Lean.Parser.Term.syntheticHole then
|
||||
pure (⟨holeOrTacticSeq⟩, #[])
|
||||
else if holeOrTacticSeq.isOfKind `Lean.Parser.Term.hole then
|
||||
pure (← mkName, #[])
|
||||
else
|
||||
let hole ← withFreshMacroScope mkName
|
||||
let holeId := hole.raw[1]
|
||||
let case ← (open TSyntax.Compat in `(tactic|
|
||||
case $holeId:ident =>%$tk
|
||||
-- annotate `then/else` with state after `case`
|
||||
with_annotate_state $tk skip
|
||||
$holeOrTacticSeq))
|
||||
pure (hole, #[case])
|
||||
let (posHole, posCase) ← mkCase thenTk pos `(?pos)
|
||||
let (negHole, negCase) ← mkCase elseTk neg `(?neg)
|
||||
`(tactic| (open Classical in refine%$ifTk $(← mkIf posHole negHole); $[$(posCase ++ negCase)]*))
|
||||
|
||||
macro_rules
|
||||
| `(tactic| if%$tk $h : $c then%$ttk $pos else%$etk $neg) =>
|
||||
expandIfThenElse tk ttk etk pos neg fun pos neg => `(if $h : $c then $pos else $neg)
|
||||
|
||||
macro_rules
|
||||
| `(tactic| if%$tk $c then%$ttk $pos else%$etk $neg) =>
|
||||
expandIfThenElse tk ttk etk pos neg fun pos neg => `(if h : $c then $pos else $neg)
|
||||
|
||||
/--
|
||||
`iterate n tac` runs `tac` exactly `n` times.
|
||||
`iterate tac` runs `tac` repeatedly until failure.
|
||||
|
||||
`iterate`'s argument is a tactic sequence,
|
||||
so multiple tactics can be run using `iterate n (tac₁; tac₂; ⋯)` or
|
||||
```lean
|
||||
iterate n
|
||||
tac₁
|
||||
tac₂
|
||||
⋯
|
||||
```
|
||||
-/
|
||||
syntax "iterate" (ppSpace num)? ppSpace tacticSeq : tactic
|
||||
macro_rules
|
||||
| `(tactic| iterate $seq:tacticSeq) =>
|
||||
`(tactic| try ($seq:tacticSeq); iterate $seq:tacticSeq)
|
||||
| `(tactic| iterate $n $seq:tacticSeq) =>
|
||||
match n.1.toNat with
|
||||
| 0 => `(tactic| skip)
|
||||
| n+1 => `(tactic| ($seq:tacticSeq); iterate $(quote n) $seq:tacticSeq)
|
||||
|
||||
end Lean.Parser.Tactic
|
||||
@@ -25,9 +25,13 @@ def leanMainFn := "_lean_main"
|
||||
|
||||
namespace LLVM
|
||||
-- TODO(bollu): instantiate target triple and find out what size_t is.
|
||||
def size_tType (llvmctx : LLVM.Context) : IO (LLVM.LLVMType llvmctx) :=
|
||||
def size_tType (llvmctx : LLVM.Context) : BaseIO (LLVM.LLVMType llvmctx) :=
|
||||
LLVM.i64Type llvmctx
|
||||
|
||||
-- TODO(bollu): instantiate target triple and find out what unsigned is.
|
||||
def unsignedType (llvmctx : LLVM.Context) : BaseIO (LLVM.LLVMType llvmctx) :=
|
||||
LLVM.i32Type llvmctx
|
||||
|
||||
-- Helper to add a function if it does not exist, and to return the function handle if it does.
|
||||
def getOrAddFunction (m : LLVM.Module ctx) (name : String) (type : LLVM.LLVMType ctx) : BaseIO (LLVM.Value ctx) := do
|
||||
match (← LLVM.getNamedFunction m name) with
|
||||
@@ -96,6 +100,15 @@ def getDecl (n : Name) : M llvmctx Decl := do
|
||||
| some d => pure d
|
||||
| none => throw s!"unknown declaration {n}"
|
||||
|
||||
def constInt8 (n : Nat) : M llvmctx (LLVM.Value llvmctx) := do
|
||||
LLVM.constInt8 llvmctx (UInt64.ofNat n)
|
||||
|
||||
def constInt64 (n : Nat) : M llvmctx (LLVM.Value llvmctx) := do
|
||||
LLVM.constInt64 llvmctx (UInt64.ofNat n)
|
||||
|
||||
def constIntSizeT (n : Nat) : M llvmctx (LLVM.Value llvmctx) := do
|
||||
LLVM.constIntSizeT llvmctx (UInt64.ofNat n)
|
||||
|
||||
def constIntUnsigned (n : Nat) : M llvmctx (LLVM.Value llvmctx) := do
|
||||
LLVM.constIntUnsigned llvmctx (UInt64.ofNat n)
|
||||
|
||||
@@ -162,14 +175,14 @@ def callLeanUnsignedToNatFn (builder : LLVM.Builder llvmctx)
|
||||
let retty ← LLVM.voidPtrType llvmctx
|
||||
let f ← getOrCreateFunctionPrototype mod retty "lean_unsigned_to_nat" argtys
|
||||
let fnty ← LLVM.functionType retty argtys
|
||||
let nv ← LLVM.constInt32 llvmctx (UInt64.ofNat n)
|
||||
let nv ← constIntUnsigned n
|
||||
LLVM.buildCall2 builder fnty f #[nv] name
|
||||
|
||||
def callLeanMkStringFromBytesFn (builder : LLVM.Builder llvmctx)
|
||||
(strPtr nBytes : LLVM.Value llvmctx) (name : String) : M llvmctx (LLVM.Value llvmctx) := do
|
||||
let fnName := "lean_mk_string_from_bytes"
|
||||
let retty ← LLVM.voidPtrType llvmctx
|
||||
let argtys := #[← LLVM.voidPtrType llvmctx, ← LLVM.i64Type llvmctx]
|
||||
let argtys := #[← LLVM.voidPtrType llvmctx, ← LLVM.size_tType llvmctx]
|
||||
let fn ← getOrCreateFunctionPrototype (← getLLVMModule) retty fnName argtys
|
||||
let fnty ← LLVM.functionType retty argtys
|
||||
LLVM.buildCall2 builder fnty fn #[strPtr, nBytes] name
|
||||
@@ -218,9 +231,9 @@ def callLeanAllocCtor (builder : LLVM.Builder llvmctx)
|
||||
let fn ← getOrCreateFunctionPrototype (← getLLVMModule) retty fnName argtys
|
||||
let fnty ← LLVM.functionType retty argtys
|
||||
|
||||
let tag ← LLVM.constInt32 llvmctx (UInt64.ofNat tag)
|
||||
let num_objs ← LLVM.constInt32 llvmctx (UInt64.ofNat num_objs)
|
||||
let scalar_sz ← LLVM.constInt32 llvmctx (UInt64.ofNat scalar_sz)
|
||||
let tag ← constIntUnsigned tag
|
||||
let num_objs ← constIntUnsigned num_objs
|
||||
let scalar_sz ← constIntUnsigned scalar_sz
|
||||
LLVM.buildCall2 builder fnty fn #[tag, num_objs, scalar_sz] name
|
||||
|
||||
def callLeanCtorSet (builder : LLVM.Builder llvmctx)
|
||||
@@ -228,7 +241,7 @@ def callLeanCtorSet (builder : LLVM.Builder llvmctx)
|
||||
let fnName := "lean_ctor_set"
|
||||
let retty ← LLVM.voidType llvmctx
|
||||
let voidptr ← LLVM.voidPtrType llvmctx
|
||||
let unsigned ← LLVM.size_tType llvmctx
|
||||
let unsigned ← LLVM.unsignedType llvmctx
|
||||
let argtys := #[voidptr, unsigned, voidptr]
|
||||
let fn ← getOrCreateFunctionPrototype (← getLLVMModule) retty fnName argtys
|
||||
let fnty ← LLVM.functionType retty argtys
|
||||
@@ -248,7 +261,7 @@ def callLeanAllocClosureFn (builder : LLVM.Builder llvmctx)
|
||||
(f arity nys : LLVM.Value llvmctx) (retName : String := "") : M llvmctx (LLVM.Value llvmctx) := do
|
||||
let fnName := "lean_alloc_closure"
|
||||
let retty ← LLVM.voidPtrType llvmctx
|
||||
let argtys := #[ ← LLVM.voidPtrType llvmctx, ← LLVM.size_tType llvmctx, ← LLVM.size_tType llvmctx]
|
||||
let argtys := #[ ← LLVM.voidPtrType llvmctx, ← LLVM.unsignedType llvmctx, ← LLVM.unsignedType llvmctx]
|
||||
let fn ← getOrCreateFunctionPrototype (← getLLVMModule) retty fnName argtys
|
||||
let fnty ← LLVM.functionType retty argtys
|
||||
LLVM.buildCall2 builder fnty fn #[f, arity, nys] retName
|
||||
@@ -257,7 +270,7 @@ def callLeanClosureSetFn (builder : LLVM.Builder llvmctx)
|
||||
(closure ix arg : LLVM.Value llvmctx) (retName : String := "") : M llvmctx Unit := do
|
||||
let fnName := "lean_closure_set"
|
||||
let retty ← LLVM.voidType llvmctx
|
||||
let argtys := #[ ← LLVM.voidPtrType llvmctx, ← LLVM.size_tType llvmctx, ← LLVM.voidPtrType llvmctx]
|
||||
let argtys := #[ ← LLVM.voidPtrType llvmctx, ← LLVM.unsignedType llvmctx, ← LLVM.voidPtrType llvmctx]
|
||||
let fn ← getOrCreateFunctionPrototype (← getLLVMModule) retty fnName argtys
|
||||
let fnty ← LLVM.functionType retty argtys
|
||||
let _ ← LLVM.buildCall2 builder fnty fn #[closure, ix, arg] retName
|
||||
@@ -285,7 +298,7 @@ def callLeanCtorRelease (builder : LLVM.Builder llvmctx)
|
||||
(closure i : LLVM.Value llvmctx) (retName : String := "") : M llvmctx Unit := do
|
||||
let fnName := "lean_ctor_release"
|
||||
let retty ← LLVM.voidType llvmctx
|
||||
let argtys := #[ ← LLVM.voidPtrType llvmctx, ← LLVM.size_tType llvmctx]
|
||||
let argtys := #[ ← LLVM.voidPtrType llvmctx, ← LLVM.unsignedType llvmctx]
|
||||
let fn ← getOrCreateFunctionPrototype (← getLLVMModule) retty fnName argtys
|
||||
let fnty ← LLVM.functionType retty argtys
|
||||
let _ ← LLVM.buildCall2 builder fnty fn #[closure, i] retName
|
||||
@@ -294,7 +307,7 @@ def callLeanCtorSetTag (builder : LLVM.Builder llvmctx)
|
||||
(closure i : LLVM.Value llvmctx) (retName : String := "") : M llvmctx Unit := do
|
||||
let fnName := "lean_ctor_set_tag"
|
||||
let retty ← LLVM.voidType llvmctx
|
||||
let argtys := #[ ← LLVM.voidPtrType llvmctx, ← LLVM.size_tType llvmctx]
|
||||
let argtys := #[ ← LLVM.voidPtrType llvmctx, ← LLVM.i8Type llvmctx]
|
||||
let fn ← getOrCreateFunctionPrototype (← getLLVMModule) retty fnName argtys
|
||||
let fnty ← LLVM.functionType retty argtys
|
||||
let _ ← LLVM.buildCall2 builder fnty fn #[closure, i] retName
|
||||
@@ -347,6 +360,31 @@ def builderAppendBasicBlock (builder : LLVM.Builder llvmctx) (name : String) : M
|
||||
let fn ← builderGetInsertionFn builder
|
||||
LLVM.appendBasicBlockInContext llvmctx fn name
|
||||
|
||||
/--
|
||||
Add an alloca to the first BB of the current function. The builders final position
|
||||
will be the end of the BB that we came from.
|
||||
|
||||
If it is possible to put an alloca in the first BB this approach is to be preferred
|
||||
over putting it in other BBs. This is because mem2reg only inspects allocas in the first BB,
|
||||
leading to missed optimizations for allocas in other BBs.
|
||||
-/
|
||||
def buildPrologueAlloca (builder : LLVM.Builder llvmctx) (ty : LLVM.LLVMType llvmctx) (name : @&String := "") : M llvmctx (LLVM.Value llvmctx) := do
|
||||
let origBB ← LLVM.getInsertBlock builder
|
||||
|
||||
let fn ← builderGetInsertionFn builder
|
||||
if (← LLVM.countBasicBlocks fn) == 0 then
|
||||
throw "Attempt to obtain first BB of function without BBs"
|
||||
|
||||
let entryBB ← LLVM.getEntryBasicBlock fn
|
||||
match ← LLVM.getFirstInstruction entryBB with
|
||||
| some instr => LLVM.positionBuilderBefore builder instr
|
||||
| none => LLVM.positionBuilderAtEnd builder entryBB
|
||||
|
||||
let alloca ← LLVM.buildAlloca builder ty name
|
||||
LLVM.positionBuilderAtEnd builder origBB
|
||||
return alloca
|
||||
|
||||
|
||||
def buildWhile_ (builder : LLVM.Builder llvmctx) (name : String)
|
||||
(condcodegen : LLVM.Builder llvmctx → M llvmctx (LLVM.Value llvmctx))
|
||||
(bodycodegen : LLVM.Builder llvmctx → M llvmctx Unit) : M llvmctx Unit := do
|
||||
@@ -428,7 +466,7 @@ def buildIfThenElse_ (builder : LLVM.Builder llvmctx) (name : String) (brval :
|
||||
-- Recall that lean uses `i8` for booleans, not `i1`, so we need to compare with `true`.
|
||||
def buildLeanBoolTrue? (builder : LLVM.Builder llvmctx)
|
||||
(b : LLVM.Value llvmctx) (name : String := "") : M llvmctx (LLVM.Value llvmctx) := do
|
||||
LLVM.buildICmp builder LLVM.IntPredicate.NE b (← LLVM.constInt8 llvmctx 0) name
|
||||
LLVM.buildICmp builder LLVM.IntPredicate.NE b (← constInt8 0) name
|
||||
|
||||
def emitFnDeclAux (mod : LLVM.Module llvmctx)
|
||||
(decl : Decl) (cppBaseName : String) (isExternal : Bool) : M llvmctx (LLVM.Value llvmctx) := do
|
||||
@@ -513,8 +551,8 @@ def emitArgSlot_ (builder : LLVM.Builder llvmctx)
|
||||
| Arg.var x => emitLhsSlot_ x
|
||||
| _ => do
|
||||
let slotty ← LLVM.voidPtrType llvmctx
|
||||
let slot ← LLVM.buildAlloca builder slotty "irrelevant_slot"
|
||||
let v ← callLeanBox builder (← LLVM.constIntUnsigned llvmctx 0) "irrelevant_val"
|
||||
let slot ← buildPrologueAlloca builder slotty "irrelevant_slot"
|
||||
let v ← callLeanBox builder (← constIntSizeT 0) "irrelevant_val"
|
||||
let _ ← LLVM.buildStore builder v slot
|
||||
return (slotty, slot)
|
||||
|
||||
@@ -536,7 +574,7 @@ def emitCtorSetArgs (builder : LLVM.Builder llvmctx)
|
||||
ys.size.forM fun i => do
|
||||
let zv ← emitLhsVal builder z
|
||||
let (_yty, yv) ← emitArgVal builder ys[i]!
|
||||
let iv ← LLVM.constIntUnsigned llvmctx (UInt64.ofNat i)
|
||||
let iv ← constIntUnsigned i
|
||||
callLeanCtorSet builder zv iv yv
|
||||
emitLhsSlotStore builder z zv
|
||||
pure ()
|
||||
@@ -545,7 +583,7 @@ def emitCtor (builder : LLVM.Builder llvmctx)
|
||||
(z : VarId) (c : CtorInfo) (ys : Array Arg) : M llvmctx Unit := do
|
||||
let (_llvmty, slot) ← emitLhsSlot_ z
|
||||
if c.size == 0 && c.usize == 0 && c.ssize == 0 then do
|
||||
let v ← callLeanBox builder (← constIntUnsigned c.cidx) "lean_box_outv"
|
||||
let v ← callLeanBox builder (← constIntSizeT c.cidx) "lean_box_outv"
|
||||
let _ ← LLVM.buildStore builder v slot
|
||||
else do
|
||||
let v ← emitAllocCtor builder c
|
||||
@@ -557,7 +595,7 @@ def emitInc (builder : LLVM.Builder llvmctx)
|
||||
let xv ← emitLhsVal builder x
|
||||
if n != 1
|
||||
then do
|
||||
let nv ← LLVM.constIntUnsigned llvmctx (UInt64.ofNat n)
|
||||
let nv ← constIntSizeT n
|
||||
callLeanRefcountFn builder (kind := RefcountKind.inc) (checkRef? := checkRef?) (delta := nv) xv
|
||||
else callLeanRefcountFn builder (kind := RefcountKind.inc) (checkRef? := checkRef?) xv
|
||||
|
||||
@@ -671,7 +709,7 @@ def emitPartialApp (builder : LLVM.Builder llvmctx) (z : VarId) (f : FunId) (ys
|
||||
|
||||
def emitApp (builder : LLVM.Builder llvmctx) (z : VarId) (f : VarId) (ys : Array Arg) : M llvmctx Unit := do
|
||||
if ys.size > closureMaxArgs then do
|
||||
let aargs ← LLVM.buildAlloca builder (← LLVM.arrayType (← LLVM.voidPtrType llvmctx) (UInt64.ofNat ys.size)) "aargs"
|
||||
let aargs ← buildPrologueAlloca builder (← LLVM.arrayType (← LLVM.voidPtrType llvmctx) (UInt64.ofNat ys.size)) "aargs"
|
||||
for i in List.range ys.size do
|
||||
let (yty, yv) ← emitArgVal builder ys[i]!
|
||||
let aslot ← LLVM.buildInBoundsGEP2 builder yty aargs #[← constIntUnsigned 0, ← constIntUnsigned i] s!"param_{i}_slot"
|
||||
@@ -680,7 +718,7 @@ def emitApp (builder : LLVM.Builder llvmctx) (z : VarId) (f : VarId) (ys : Array
|
||||
let retty ← LLVM.voidPtrType llvmctx
|
||||
let args := #[← emitLhsVal builder f, ← constIntUnsigned ys.size, aargs]
|
||||
-- '1 + ...'. '1' for the fn and 'args' for the arguments
|
||||
let argtys := #[← LLVM.voidPtrType llvmctx]
|
||||
let argtys := #[← LLVM.voidPtrType llvmctx, ← LLVM.unsignedType llvmctx, ← LLVM.voidPtrType llvmctx]
|
||||
let fn ← getOrCreateFunctionPrototype (← getLLVMModule) retty fnName argtys
|
||||
let fnty ← LLVM.functionType retty argtys
|
||||
let zv ← LLVM.buildCall2 builder fnty fn args
|
||||
@@ -722,18 +760,18 @@ def emitFullApp (builder : LLVM.Builder llvmctx)
|
||||
def emitLit (builder : LLVM.Builder llvmctx)
|
||||
(z : VarId) (t : IRType) (v : LitVal) : M llvmctx (LLVM.Value llvmctx) := do
|
||||
let llvmty ← toLLVMType t
|
||||
let zslot ← LLVM.buildAlloca builder llvmty
|
||||
let zslot ← buildPrologueAlloca builder llvmty
|
||||
addVartoState z zslot llvmty
|
||||
let zv ← match v with
|
||||
| LitVal.num v => emitNumLit builder t v
|
||||
| LitVal.str v =>
|
||||
let zero ← LLVM.constIntUnsigned llvmctx 0
|
||||
let zero ← constIntUnsigned 0
|
||||
let str_global ← LLVM.buildGlobalString builder v
|
||||
-- access through the global, into the 0th index of the array
|
||||
let strPtr ← LLVM.buildInBoundsGEP2 builder
|
||||
(← LLVM.opaquePointerTypeInContext llvmctx)
|
||||
str_global #[zero] ""
|
||||
let nbytes ← LLVM.constIntUnsigned llvmctx (UInt64.ofNat (v.utf8ByteSize))
|
||||
let nbytes ← constIntSizeT v.utf8ByteSize
|
||||
callLeanMkStringFromBytesFn builder strPtr nbytes ""
|
||||
LLVM.buildStore builder zv zslot
|
||||
return zslot
|
||||
@@ -757,7 +795,7 @@ def callLeanCtorGetUsize (builder : LLVM.Builder llvmctx)
|
||||
(x i : LLVM.Value llvmctx) (retName : String) : M llvmctx (LLVM.Value llvmctx) := do
|
||||
let fnName := "lean_ctor_get_usize"
|
||||
let retty ← LLVM.size_tType llvmctx
|
||||
let argtys := #[ ← LLVM.voidPtrType llvmctx, ← LLVM.size_tType llvmctx]
|
||||
let argtys := #[ ← LLVM.voidPtrType llvmctx, ← LLVM.unsignedType llvmctx]
|
||||
let fnty ← LLVM.functionType retty argtys
|
||||
let fn ← getOrCreateFunctionPrototype (← getLLVMModule) retty fnName argtys
|
||||
LLVM.buildCall2 builder fnty fn #[x, i] retName
|
||||
@@ -784,7 +822,7 @@ def emitSProj (builder : LLVM.Builder llvmctx)
|
||||
| IRType.uint32 => pure ("lean_ctor_get_uint32", ← LLVM.i32Type llvmctx)
|
||||
| IRType.uint64 => pure ("lean_ctor_get_uint64", ← LLVM.i64Type llvmctx)
|
||||
| _ => throw s!"Invalid type for lean_ctor_get: '{t}'"
|
||||
let argtys := #[ ← LLVM.voidPtrType llvmctx, ← LLVM.size_tType llvmctx]
|
||||
let argtys := #[ ← LLVM.voidPtrType llvmctx, ← LLVM.unsignedType llvmctx]
|
||||
let fn ← getOrCreateFunctionPrototype (← getLLVMModule) retty fnName argtys
|
||||
let xval ← emitLhsVal builder x
|
||||
let offset ← emitOffset builder n offset
|
||||
@@ -891,7 +929,7 @@ def emitReset (builder : LLVM.Builder llvmctx) (z : VarId) (n : Nat) (x : VarId)
|
||||
(fun builder => do
|
||||
let xv ← emitLhsVal builder x
|
||||
callLeanDecRef builder xv
|
||||
let box0 ← callLeanBox builder (← constIntUnsigned 0) "box0"
|
||||
let box0 ← callLeanBox builder (← constIntSizeT 0) "box0"
|
||||
emitLhsSlotStore builder z box0
|
||||
return ShouldForwardControlFlow.yes
|
||||
)
|
||||
@@ -912,7 +950,7 @@ def emitReuse (builder : LLVM.Builder llvmctx)
|
||||
emitLhsSlotStore builder z xv
|
||||
if updtHeader then
|
||||
let zv ← emitLhsVal builder z
|
||||
callLeanCtorSetTag builder zv (← constIntUnsigned c.cidx)
|
||||
callLeanCtorSetTag builder zv (← constInt8 c.cidx)
|
||||
return ShouldForwardControlFlow.yes
|
||||
)
|
||||
emitCtorSetArgs builder z ys
|
||||
@@ -935,7 +973,7 @@ def emitVDecl (builder : LLVM.Builder llvmctx) (z : VarId) (t : IRType) (v : Exp
|
||||
|
||||
def declareVar (builder : LLVM.Builder llvmctx) (x : VarId) (t : IRType) : M llvmctx Unit := do
|
||||
let llvmty ← toLLVMType t
|
||||
let alloca ← LLVM.buildAlloca builder llvmty "varx"
|
||||
let alloca ← buildPrologueAlloca builder llvmty "varx"
|
||||
addVartoState x alloca llvmty
|
||||
|
||||
partial def declareVars (builder : LLVM.Builder llvmctx) (f : FnBody) : M llvmctx Unit := do
|
||||
@@ -961,7 +999,7 @@ def emitTag (builder : LLVM.Builder llvmctx) (x : VarId) (xType : IRType) : M ll
|
||||
def emitSet (builder : LLVM.Builder llvmctx) (x : VarId) (i : Nat) (y : Arg) : M llvmctx Unit := do
|
||||
let fnName := "lean_ctor_set"
|
||||
let retty ← LLVM.voidType llvmctx
|
||||
let argtys := #[ ← LLVM.voidPtrType llvmctx, ← LLVM.size_tType llvmctx, ← LLVM.voidPtrType llvmctx]
|
||||
let argtys := #[ ← LLVM.voidPtrType llvmctx, ← LLVM.unsignedType llvmctx , ← LLVM.voidPtrType llvmctx]
|
||||
let fn ← getOrCreateFunctionPrototype (← getLLVMModule) retty fnName argtys
|
||||
let fnty ← LLVM.functionType retty argtys
|
||||
let _ ← LLVM.buildCall2 builder fnty fn #[← emitLhsVal builder x, ← constIntUnsigned i, (← emitArgVal builder y).2]
|
||||
@@ -969,7 +1007,7 @@ def emitSet (builder : LLVM.Builder llvmctx) (x : VarId) (i : Nat) (y : Arg) : M
|
||||
def emitUSet (builder : LLVM.Builder llvmctx) (x : VarId) (i : Nat) (y : VarId) : M llvmctx Unit := do
|
||||
let fnName := "lean_ctor_set_usize"
|
||||
let retty ← LLVM.voidType llvmctx
|
||||
let argtys := #[ ← LLVM.voidPtrType llvmctx, ← LLVM.size_tType llvmctx, ← LLVM.size_tType llvmctx]
|
||||
let argtys := #[ ← LLVM.voidPtrType llvmctx, ← LLVM.unsignedType llvmctx, ← LLVM.size_tType llvmctx]
|
||||
let fn ← getOrCreateFunctionPrototype (← getLLVMModule) retty fnName argtys
|
||||
let fnty ← LLVM.functionType retty argtys
|
||||
let _ ← LLVM.buildCall2 builder fnty fn #[← emitLhsVal builder x, ← constIntUnsigned i, (← emitLhsVal builder y)]
|
||||
@@ -1008,7 +1046,7 @@ def emitSSet (builder : LLVM.Builder llvmctx) (x : VarId) (n : Nat) (offset : Na
|
||||
| IRType.uint32 => pure ("lean_ctor_set_uint32", ← LLVM.i32Type llvmctx)
|
||||
| IRType.uint64 => pure ("lean_ctor_set_uint64", ← LLVM.i64Type llvmctx)
|
||||
| _ => throw s!"invalid type for 'lean_ctor_set': '{t}'"
|
||||
let argtys := #[ ← LLVM.voidPtrType llvmctx, ← LLVM.size_tType llvmctx, setty]
|
||||
let argtys := #[ ← LLVM.voidPtrType llvmctx, ← LLVM.unsignedType llvmctx, setty]
|
||||
let retty ← LLVM.voidType llvmctx
|
||||
let fn ← getOrCreateFunctionPrototype (← getLLVMModule) retty fnName argtys
|
||||
let xv ← emitLhsVal builder x
|
||||
@@ -1026,12 +1064,12 @@ def emitDel (builder : LLVM.Builder llvmctx) (x : VarId) : M llvmctx Unit := do
|
||||
let _ ← LLVM.buildCall2 builder fnty fn #[xv]
|
||||
|
||||
def emitSetTag (builder : LLVM.Builder llvmctx) (x : VarId) (i : Nat) : M llvmctx Unit := do
|
||||
let argtys := #[← LLVM.voidPtrType llvmctx, ← LLVM.size_tType llvmctx]
|
||||
let argtys := #[← LLVM.voidPtrType llvmctx, ← LLVM.i8Type llvmctx]
|
||||
let retty ← LLVM.voidType llvmctx
|
||||
let fn ← getOrCreateFunctionPrototype (← getLLVMModule) retty "lean_ctor_set_tag" argtys
|
||||
let xv ← emitLhsVal builder x
|
||||
let fnty ← LLVM.functionType retty argtys
|
||||
let _ ← LLVM.buildCall2 builder fnty fn #[xv, ← constIntUnsigned i]
|
||||
let _ ← LLVM.buildCall2 builder fnty fn #[xv, ← constInt8 i]
|
||||
|
||||
def ensureHasDefault' (alts : Array Alt) : Array Alt :=
|
||||
if alts.any Alt.isDefault then alts
|
||||
@@ -1057,7 +1095,7 @@ partial def emitCase (builder : LLVM.Builder llvmctx)
|
||||
match alt with
|
||||
| Alt.ctor c b =>
|
||||
let destbb ← builderAppendBasicBlock builder s!"case_{xType}_{c.name}_{c.cidx}"
|
||||
LLVM.addCase switch (← constIntUnsigned c.cidx) destbb
|
||||
LLVM.addCase switch (← constIntSizeT c.cidx) destbb
|
||||
LLVM.positionBuilderAtEnd builder destbb
|
||||
emitFnBody builder b
|
||||
| Alt.default b =>
|
||||
@@ -1141,14 +1179,14 @@ def emitFnArgs (builder : LLVM.Builder llvmctx)
|
||||
-- pv := *(argsi) = *(args + i)
|
||||
let pv ← LLVM.buildLoad2 builder llvmty argsi
|
||||
-- slot for arg[i] which is always void* ?
|
||||
let alloca ← LLVM.buildAlloca builder llvmty s!"arg_{i}"
|
||||
let alloca ← buildPrologueAlloca builder llvmty s!"arg_{i}"
|
||||
LLVM.buildStore builder pv alloca
|
||||
addVartoState params[i]!.x alloca llvmty
|
||||
else
|
||||
let n ← LLVM.countParams llvmfn
|
||||
for i in (List.range n.toNat) do
|
||||
let llvmty ← toLLVMType params[i]!.ty
|
||||
let alloca ← LLVM.buildAlloca builder llvmty s!"arg_{i}"
|
||||
let alloca ← buildPrologueAlloca builder llvmty s!"arg_{i}"
|
||||
let arg ← LLVM.getParam llvmfn (UInt64.ofNat i)
|
||||
let _ ← LLVM.buildStore builder arg alloca
|
||||
addVartoState params[i]!.x alloca llvmty
|
||||
@@ -1300,7 +1338,7 @@ def emitInitFn (mod : LLVM.Module llvmctx) (builder : LLVM.Builder llvmctx) : M
|
||||
let ginit?v ← LLVM.buildLoad2 builder ginit?ty ginit?slot "init_v"
|
||||
buildIfThen_ builder "isGInitialized" ginit?v
|
||||
(fun builder => do
|
||||
let box0 ← callLeanBox builder (← LLVM.constIntUnsigned llvmctx 0) "box0"
|
||||
let box0 ← callLeanBox builder (← constIntSizeT 0) "box0"
|
||||
let out ← callLeanIOResultMKOk builder box0 "retval"
|
||||
let _ ← LLVM.buildRet builder out
|
||||
pure ShouldForwardControlFlow.no)
|
||||
@@ -1318,7 +1356,7 @@ def emitInitFn (mod : LLVM.Module llvmctx) (builder : LLVM.Builder llvmctx) : M
|
||||
callLeanDecRef builder res
|
||||
let decls := getDecls env
|
||||
decls.reverse.forM (emitDeclInit builder initFn)
|
||||
let box0 ← callLeanBox builder (← LLVM.constIntUnsigned llvmctx 0) "box0"
|
||||
let box0 ← callLeanBox builder (← constIntSizeT 0) "box0"
|
||||
let out ← callLeanIOResultMKOk builder box0 "retval"
|
||||
let _ ← LLVM.buildRet builder out
|
||||
|
||||
@@ -1432,15 +1470,15 @@ def emitMainFn (mod : LLVM.Module llvmctx) (builder : LLVM.Builder llvmctx) : M
|
||||
#endif
|
||||
-/
|
||||
let inty ← LLVM.voidPtrType llvmctx
|
||||
let inslot ← LLVM.buildAlloca builder (← LLVM.pointerType inty) "in"
|
||||
let inslot ← buildPrologueAlloca builder (← LLVM.pointerType inty) "in"
|
||||
let resty ← LLVM.voidPtrType llvmctx
|
||||
let res ← LLVM.buildAlloca builder (← LLVM.pointerType resty) "res"
|
||||
let res ← buildPrologueAlloca builder (← LLVM.pointerType resty) "res"
|
||||
if usesLeanAPI then callLeanInitialize builder else callLeanInitializeRuntimeModule builder
|
||||
/- We disable panic messages because they do not mesh well with extracted closed terms.
|
||||
See issue #534. We can remove this workaround after we implement issue #467. -/
|
||||
callLeanSetPanicMessages builder (← LLVM.constFalse llvmctx)
|
||||
let world ← callLeanIOMkWorld builder
|
||||
let resv ← callModInitFn builder (← getModName) (← LLVM.constInt8 llvmctx 1) world ((← getModName).toString ++ "_init_out")
|
||||
let resv ← callModInitFn builder (← getModName) (← constInt8 1) world ((← getModName).toString ++ "_init_out")
|
||||
let _ ← LLVM.buildStore builder resv res
|
||||
|
||||
callLeanSetPanicMessages builder (← LLVM.constTrue llvmctx)
|
||||
@@ -1453,21 +1491,21 @@ def emitMainFn (mod : LLVM.Module llvmctx) (builder : LLVM.Builder llvmctx) : M
|
||||
callLeanDecRef builder resv
|
||||
callLeanInitTaskManager builder
|
||||
if xs.size == 2 then
|
||||
let inv ← callLeanBox builder (← LLVM.constInt (← LLVM.size_tType llvmctx) 0) "inv"
|
||||
let inv ← callLeanBox builder (← constIntSizeT 0) "inv"
|
||||
let _ ← LLVM.buildStore builder inv inslot
|
||||
let ity ← LLVM.size_tType llvmctx
|
||||
let islot ← LLVM.buildAlloca builder ity "islot"
|
||||
let islot ← buildPrologueAlloca builder ity "islot"
|
||||
let argcval ← LLVM.getParam main 0
|
||||
let argvval ← LLVM.getParam main 1
|
||||
LLVM.buildStore builder argcval islot
|
||||
buildWhile_ builder "argv"
|
||||
(condcodegen := fun builder => do
|
||||
let iv ← LLVM.buildLoad2 builder ity islot "iv"
|
||||
let i_gt_1 ← LLVM.buildICmp builder LLVM.IntPredicate.UGT iv (← constIntUnsigned 1) "i_gt_1"
|
||||
let i_gt_1 ← LLVM.buildICmp builder LLVM.IntPredicate.UGT iv (← constIntSizeT 1) "i_gt_1"
|
||||
return i_gt_1)
|
||||
(bodycodegen := fun builder => do
|
||||
let iv ← LLVM.buildLoad2 builder ity islot "iv"
|
||||
let iv_next ← LLVM.buildSub builder iv (← constIntUnsigned 1) "iv.next"
|
||||
let iv_next ← LLVM.buildSub builder iv (← constIntSizeT 1) "iv.next"
|
||||
LLVM.buildStore builder iv_next islot
|
||||
let nv ← callLeanAllocCtor builder 1 2 0 "nv"
|
||||
let argv_i_next_slot ← LLVM.buildGEP2 builder (← LLVM.voidPtrType llvmctx) argvval #[iv_next] "argv.i.next.slot"
|
||||
@@ -1509,7 +1547,7 @@ def emitMainFn (mod : LLVM.Module llvmctx) (builder : LLVM.Builder llvmctx) : M
|
||||
pure ShouldForwardControlFlow.no
|
||||
else do
|
||||
callLeanDecRef builder resv
|
||||
let _ ← LLVM.buildRet builder (← LLVM.constInt64 llvmctx 0)
|
||||
let _ ← LLVM.buildRet builder (← constInt64 0)
|
||||
pure ShouldForwardControlFlow.no
|
||||
|
||||
)
|
||||
@@ -1517,7 +1555,7 @@ def emitMainFn (mod : LLVM.Module llvmctx) (builder : LLVM.Builder llvmctx) : M
|
||||
let resv ← LLVM.buildLoad2 builder resty res "resv"
|
||||
callLeanIOResultShowError builder resv
|
||||
callLeanDecRef builder resv
|
||||
let _ ← LLVM.buildRet builder (← LLVM.constInt64 llvmctx 1)
|
||||
let _ ← LLVM.buildRet builder (← constInt64 1)
|
||||
pure ShouldForwardControlFlow.no)
|
||||
-- at the merge
|
||||
let _ ← LLVM.buildUnreachable builder
|
||||
@@ -1592,6 +1630,8 @@ def emitLLVM (env : Environment) (modName : Name) (filepath : String) : IO Unit
|
||||
let some fn ← LLVM.getNamedFunction emitLLVMCtx.llvmmodule name
|
||||
| throw <| IO.Error.userError s!"ERROR: linked module must have function from runtime module: '{name}'"
|
||||
LLVM.setLinkage fn LLVM.Linkage.internal
|
||||
if let some err ← LLVM.verifyModule emitLLVMCtx.llvmmodule then
|
||||
throw <| .userError err
|
||||
LLVM.writeBitcodeToFile emitLLVMCtx.llvmmodule filepath
|
||||
LLVM.disposeModule emitLLVMCtx.llvmmodule
|
||||
| .error err => throw (IO.Error.userError err)
|
||||
|
||||
@@ -182,6 +182,18 @@ opaque createBuilderInContext (ctx : Context) : BaseIO (Builder ctx)
|
||||
@[extern "lean_llvm_append_basic_block_in_context"]
|
||||
opaque appendBasicBlockInContext (ctx : Context) (fn : Value ctx) (name : @&String) : BaseIO (BasicBlock ctx)
|
||||
|
||||
@[extern "lean_llvm_count_basic_blocks"]
|
||||
opaque countBasicBlocks (fn : Value ctx) : BaseIO UInt64
|
||||
|
||||
@[extern "lean_llvm_get_entry_basic_block"]
|
||||
opaque getEntryBasicBlock (fn : Value ctx) : BaseIO (BasicBlock ctx)
|
||||
|
||||
@[extern "lean_llvm_get_first_instruction"]
|
||||
opaque getFirstInstruction (bb : BasicBlock ctx) : BaseIO (Option (Value ctx))
|
||||
|
||||
@[extern "lean_llvm_position_builder_before"]
|
||||
opaque positionBuilderBefore (builder : Builder ctx) (instr : Value ctx) : BaseIO Unit
|
||||
|
||||
@[extern "lean_llvm_position_builder_at_end"]
|
||||
opaque positionBuilderAtEnd (builder : Builder ctx) (bb : BasicBlock ctx) : BaseIO Unit
|
||||
|
||||
@@ -326,6 +338,9 @@ opaque disposeTargetMachine (tm : TargetMachine ctx) : BaseIO Unit
|
||||
@[extern "lean_llvm_dispose_module"]
|
||||
opaque disposeModule (m : Module ctx) : BaseIO Unit
|
||||
|
||||
@[extern "lean_llvm_verify_module"]
|
||||
opaque verifyModule (m : Module ctx) : BaseIO (Option String)
|
||||
|
||||
@[extern "lean_llvm_create_string_attribute"]
|
||||
opaque createStringAttribute (key : String) (value : String) : BaseIO (Attribute ctx)
|
||||
|
||||
@@ -439,6 +454,11 @@ def constInt32 (ctx : Context) (value : UInt64) (signExtend : Bool := false) : B
|
||||
def constInt64 (ctx : Context) (value : UInt64) (signExtend : Bool := false) : BaseIO (Value ctx) :=
|
||||
constInt' ctx 64 value signExtend
|
||||
|
||||
def constIntUnsigned (ctx : Context) (value : UInt64) (signExtend : Bool := false) : BaseIO (Value ctx) :=
|
||||
def constIntSizeT (ctx : Context) (value : UInt64) (signExtend : Bool := false) : BaseIO (Value ctx) :=
|
||||
-- TODO: make this stick to the actual size_t of the target machine
|
||||
constInt' ctx 64 value signExtend
|
||||
|
||||
def constIntUnsigned (ctx : Context) (value : UInt64) (signExtend : Bool := false) : BaseIO (Value ctx) :=
|
||||
-- TODO: make this stick to the actual unsigned of the target machine
|
||||
constInt' ctx 32 value signExtend
|
||||
end LLVM
|
||||
|
||||
@@ -84,6 +84,26 @@ partial def toPosition (fmap : FileMap) (pos : String.Pos) : Position :=
|
||||
-- Can also happen with EOF errors, which are not strictly inside the file.
|
||||
⟨lines.back, (pos - ps.back).byteIdx⟩
|
||||
|
||||
/-- Convert a `Lean.Position` to a `String.Pos`. -/
|
||||
def ofPosition (text : FileMap) (pos : Position) : String.Pos :=
|
||||
let colPos :=
|
||||
if h : pos.line - 1 < text.positions.size then
|
||||
text.positions.get ⟨pos.line - 1, h⟩
|
||||
else if text.positions.isEmpty then
|
||||
0
|
||||
else
|
||||
text.positions.back
|
||||
String.Iterator.nextn ⟨text.source, colPos⟩ pos.column |>.pos
|
||||
|
||||
/--
|
||||
Returns the position of the start of (1-based) line `line`.
|
||||
This gives the stame result as `map.ofPosition ⟨line, 0⟩`, but is more efficient.
|
||||
-/
|
||||
def lineStart (map : FileMap) (line : Nat) : String.Pos :=
|
||||
if h : line - 1 < map.positions.size then
|
||||
map.positions.get ⟨line - 1, h⟩
|
||||
else map.positions.back?.getD 0
|
||||
|
||||
end FileMap
|
||||
end Lean
|
||||
|
||||
|
||||
@@ -656,35 +656,40 @@ unsafe def elabEvalUnsafe : CommandElab
|
||||
return e
|
||||
-- Evaluate using term using `MetaEval` class.
|
||||
let elabMetaEval : CommandElabM Unit := do
|
||||
-- act? is `some act` if elaborated `term` has type `CommandElabM α`
|
||||
let act? ← runTermElabM fun _ => Term.withDeclName declName do
|
||||
let e ← elabEvalTerm
|
||||
let eType ← instantiateMVars (← inferType e)
|
||||
if eType.isAppOfArity ``CommandElabM 1 then
|
||||
let mut stx ← Term.exprToSyntax e
|
||||
unless (← isDefEq eType.appArg! (mkConst ``Unit)) do
|
||||
stx ← `($stx >>= fun v => IO.println (repr v))
|
||||
let act ← Lean.Elab.Term.evalTerm (CommandElabM Unit) (mkApp (mkConst ``CommandElabM) (mkConst ``Unit)) stx
|
||||
pure <| some act
|
||||
else
|
||||
let e ← mkRunMetaEval e
|
||||
let env ← getEnv
|
||||
let opts ← getOptions
|
||||
let act ← try addAndCompile e; evalConst (Environment → Options → IO (String × Except IO.Error Environment)) declName finally setEnv env
|
||||
let (out, res) ← act env opts -- we execute `act` using the environment
|
||||
logInfoAt tk out
|
||||
match res with
|
||||
| Except.error e => throwError e.toString
|
||||
| Except.ok env => do setEnv env; pure none
|
||||
let some act := act? | return ()
|
||||
act
|
||||
-- Generate an action without executing it. We use `withoutModifyingEnv` to ensure
|
||||
-- we don't polute the environment with auxliary declarations.
|
||||
-- We have special support for `CommandElabM` to ensure `#eval` can be used to execute commands
|
||||
-- that modify `CommandElabM` state not just the `Environment`.
|
||||
let act : Sum (CommandElabM Unit) (Environment → Options → IO (String × Except IO.Error Environment)) ←
|
||||
runTermElabM fun _ => Term.withDeclName declName do withoutModifyingEnv do
|
||||
let e ← elabEvalTerm
|
||||
let eType ← instantiateMVars (← inferType e)
|
||||
if eType.isAppOfArity ``CommandElabM 1 then
|
||||
let mut stx ← Term.exprToSyntax e
|
||||
unless (← isDefEq eType.appArg! (mkConst ``Unit)) do
|
||||
stx ← `($stx >>= fun v => IO.println (repr v))
|
||||
let act ← Lean.Elab.Term.evalTerm (CommandElabM Unit) (mkApp (mkConst ``CommandElabM) (mkConst ``Unit)) stx
|
||||
pure <| Sum.inl act
|
||||
else
|
||||
let e ← mkRunMetaEval e
|
||||
addAndCompile e
|
||||
let act ← evalConst (Environment → Options → IO (String × Except IO.Error Environment)) declName
|
||||
pure <| Sum.inr act
|
||||
match act with
|
||||
| .inl act => act
|
||||
| .inr act =>
|
||||
let (out, res) ← act (← getEnv) (← getOptions)
|
||||
logInfoAt tk out
|
||||
match res with
|
||||
| Except.error e => throwError e.toString
|
||||
| Except.ok env => setEnv env; pure ()
|
||||
-- Evaluate using term using `Eval` class.
|
||||
let elabEval : CommandElabM Unit := runTermElabM fun _ => Term.withDeclName declName do
|
||||
let elabEval : CommandElabM Unit := runTermElabM fun _ => Term.withDeclName declName do withoutModifyingEnv do
|
||||
-- fall back to non-meta eval if MetaEval hasn't been defined yet
|
||||
-- modify e to `runEval e`
|
||||
let e ← mkRunEval (← elabEvalTerm)
|
||||
let env ← getEnv
|
||||
let act ← try addAndCompile e; evalConst (IO (String × Except IO.Error Unit)) declName finally setEnv env
|
||||
addAndCompile e
|
||||
let act ← evalConst (IO (String × Except IO.Error Unit)) declName
|
||||
let (out, res) ← liftM (m := IO) act
|
||||
logInfoAt tk out
|
||||
match res with
|
||||
@@ -722,6 +727,8 @@ opaque elabEval : CommandElab
|
||||
match stx with
|
||||
| `($doc:docComment add_decl_doc $id) =>
|
||||
let declName ← resolveGlobalConstNoOverloadWithInfo id
|
||||
unless ((← getEnv).getModuleIdxFor? declName).isNone do
|
||||
throwError "invalid 'add_decl_doc', declaration is in an imported module"
|
||||
if let .none ← findDeclarationRangesCore? declName then
|
||||
-- this is only relevant for declarations added without a declaration range
|
||||
-- in particular `Quot.mk` et al which are added by `init_quot`
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
/-
|
||||
Copyright (c) 2019 Microsoft Corporation. All rights reserved.
|
||||
Released under Apache 2.0 license as described in the file LICENSE.
|
||||
Authors: Leonardo de Moura
|
||||
Authors: Leonardo de Moura, Gabriel Ebner
|
||||
-/
|
||||
import Lean.Compiler.BorrowedAnnotation
|
||||
import Lean.Meta.KAbstract
|
||||
import Lean.Meta.Closure
|
||||
import Lean.Meta.MatchUtil
|
||||
import Lean.Elab.SyntheticMVars
|
||||
import Lean.Compiler.ImplementedByAttr
|
||||
|
||||
namespace Lean.Elab.Term
|
||||
open Meta
|
||||
@@ -19,6 +21,20 @@ open Meta
|
||||
throwError "invalid coercion notation, expected type is not known"
|
||||
ensureHasType expectedType? e
|
||||
|
||||
@[builtin_term_elab coeFunNotation] def elabCoeFunNotation : TermElab := fun stx _ => do
|
||||
let x ← elabTerm stx[1] none
|
||||
if let some ty ← coerceToFunction? x then
|
||||
return ty
|
||||
else
|
||||
throwError "cannot coerce to function{indentExpr x}"
|
||||
|
||||
@[builtin_term_elab coeSortNotation] def elabCoeSortNotation : TermElab := fun stx _ => do
|
||||
let x ← elabTerm stx[1] none
|
||||
if let some ty ← coerceToSort? x then
|
||||
return ty
|
||||
else
|
||||
throwError "cannot coerce to sort{indentExpr x}"
|
||||
|
||||
@[builtin_term_elab anonymousCtor] def elabAnonymousCtor : TermElab := fun stx expectedType? =>
|
||||
match stx with
|
||||
| `(⟨$args,*⟩) => do
|
||||
@@ -411,4 +427,33 @@ private def withLocalIdentFor (stx : Term) (e : Expr) (k : Term → TermElabM Ex
|
||||
let e ← elabTerm stx[1] expectedType?
|
||||
return DiscrTree.mkNoindexAnnotation e
|
||||
|
||||
-- TODO: investigate whether we need this function
|
||||
private def mkAuxNameForElabUnsafe (hint : Name) : TermElabM Name :=
|
||||
withFreshMacroScope do
|
||||
let name := (← getDeclName?).getD Name.anonymous ++ hint
|
||||
return addMacroScope (← getMainModule) name (← getCurrMacroScope)
|
||||
|
||||
@[builtin_term_elab «unsafe»]
|
||||
def elabUnsafe : TermElab := fun stx expectedType? =>
|
||||
match stx with
|
||||
| `(unsafe $t) => do
|
||||
let t ← elabTermAndSynthesize t expectedType?
|
||||
if (← logUnassignedUsingErrorInfos (← getMVars t)) then
|
||||
throwAbortTerm
|
||||
let t ← mkAuxDefinitionFor (← mkAuxName `unsafe) t
|
||||
let .const unsafeFn unsafeLvls .. := t.getAppFn | unreachable!
|
||||
let .defnInfo unsafeDefn ← getConstInfo unsafeFn | unreachable!
|
||||
let implName ← mkAuxNameForElabUnsafe `impl
|
||||
addDecl <| Declaration.defnDecl {
|
||||
name := implName
|
||||
type := unsafeDefn.type
|
||||
levelParams := unsafeDefn.levelParams
|
||||
value := (← mkOfNonempty unsafeDefn.type)
|
||||
hints := .opaque
|
||||
safety := .safe
|
||||
}
|
||||
setImplementedBy implName unsafeFn
|
||||
return mkAppN (Lean.mkConst implName unsafeLvls) t.getAppArgs
|
||||
| _ => throwUnsupportedSyntax
|
||||
|
||||
end Lean.Elab.Term
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
/-
|
||||
Copyright (c) 2019 Microsoft Corporation. All rights reserved.
|
||||
Released under Apache 2.0 license as described in the file LICENSE.
|
||||
Authors: Leonardo de Moura
|
||||
Authors: Leonardo de Moura, Gabriel Ebner
|
||||
-/
|
||||
import Lean.Elab.Binders
|
||||
import Lean.Elab.SyntheticMVars
|
||||
import Lean.Elab.SetOption
|
||||
|
||||
namespace Lean.Elab.Command
|
||||
|
||||
@@ -503,6 +504,49 @@ def expandDeclId (declId : Syntax) (modifiers : Modifiers) : CommandElabM Expand
|
||||
|
||||
end Elab.Command
|
||||
|
||||
open Elab Command MonadRecDepth
|
||||
|
||||
/--
|
||||
Lifts an action in `CommandElabM` into `CoreM`, updating the traces and the environment.
|
||||
|
||||
Commands that modify the processing of subsequent commands,
|
||||
such as `open` and `namespace` commands,
|
||||
only have an effect for the remainder of the `CommandElabM` computation passed here,
|
||||
and do not affect subsequent commands.
|
||||
-/
|
||||
def liftCommandElabM (cmd : CommandElabM α) : CoreM α := do
|
||||
let (a, commandState) ←
|
||||
cmd.run {
|
||||
fileName := ← getFileName
|
||||
fileMap := ← getFileMap
|
||||
ref := ← getRef
|
||||
tacticCache? := none
|
||||
} |>.run {
|
||||
env := ← getEnv
|
||||
maxRecDepth := ← getMaxRecDepth
|
||||
scopes := [{ header := "", opts := ← getOptions }]
|
||||
}
|
||||
modify fun coreState => { coreState with
|
||||
traceState.traces := coreState.traceState.traces ++ commandState.traceState.traces
|
||||
env := commandState.env
|
||||
}
|
||||
if let some err := commandState.messages.msgs.toArray.find? (·.severity matches .error) then
|
||||
throwError err.data
|
||||
pure a
|
||||
|
||||
/--
|
||||
Given a command elaborator `cmd`, returns a new command elaborator that
|
||||
first evaluates any local `set_option ... in ...` clauses and then invokes `cmd` on what remains.
|
||||
-/
|
||||
partial def withSetOptionIn (cmd : CommandElab) : CommandElab := fun stx => do
|
||||
if stx.getKind == ``Lean.Parser.Command.in &&
|
||||
stx[0].getKind == ``Lean.Parser.Command.set_option then
|
||||
let opts ← Elab.elabSetOption stx[0][1] stx[0][2]
|
||||
Command.withScope (fun scope => { scope with opts }) do
|
||||
withSetOptionIn cmd stx[1]
|
||||
else
|
||||
cmd stx
|
||||
|
||||
export Elab.Command (Linter addLinter)
|
||||
|
||||
end Lean
|
||||
|
||||
@@ -524,14 +524,14 @@ private def updateResultingUniverse (views : Array InductiveView) (numParams : N
|
||||
register_builtin_option bootstrap.inductiveCheckResultingUniverse : Bool := {
|
||||
defValue := true,
|
||||
group := "bootstrap",
|
||||
descr := "by default the `inductive/structure commands report an error if the resulting universe is not zero, but may be zero for some universe parameters. Reason: unless this type is a subsingleton, it is hardly what the user wants since it can only eliminate into `Prop`. In the `Init` package, we define subsingletons, and we use this option to disable the check. This option may be deleted in the future after we improve the validator"
|
||||
descr := "by default the `inductive`/`structure` commands report an error if the resulting universe is not zero, but may be zero for some universe parameters. Reason: unless this type is a subsingleton, it is hardly what the user wants since it can only eliminate into `Prop`. In the `Init` package, we define subsingletons, and we use this option to disable the check. This option may be deleted in the future after we improve the validator"
|
||||
}
|
||||
|
||||
def checkResultingUniverse (u : Level) : TermElabM Unit := do
|
||||
if bootstrap.inductiveCheckResultingUniverse.get (← getOptions) then
|
||||
let u ← instantiateLevelMVars u
|
||||
if !u.isZero && !u.isNeverZero then
|
||||
throwError "invalid universe polymorphic type, the resultant universe is not Prop (i.e., 0), but it may be Prop for some parameter values (solution: use 'u+1' or 'max 1 u'{indentD u}"
|
||||
throwError "invalid universe polymorphic type, the resultant universe is not Prop (i.e., 0), but it may be Prop for some parameter values (solution: use 'u+1' or 'max 1 u'){indentD u}"
|
||||
|
||||
private def checkResultingUniverses (views : Array InductiveView) (numParams : Nat) (indTypes : List InductiveType) : TermElabM Unit := do
|
||||
let u := (← instantiateLevelMVars (← getResultingUniverse indTypes)).normalize
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/-
|
||||
Copyright (c) 2020 Microsoft Corporation. All rights reserved.
|
||||
Released under Apache 2.0 license as described in the file LICENSE.
|
||||
Authors: Leonardo de Moura
|
||||
Authors: Leonardo de Moura, Mario Carneiro
|
||||
-/
|
||||
import Lean.Util.ForEachExprWhere
|
||||
import Lean.Meta.Match.Match
|
||||
@@ -1236,17 +1236,46 @@ where
|
||||
builtin_initialize
|
||||
registerTraceClass `Elab.match
|
||||
|
||||
-- leading_parser:leadPrec "nomatch " >> termParser
|
||||
-- leading_parser:leadPrec "nomatch " >> sepBy1 termParser ", "
|
||||
@[builtin_term_elab «nomatch»] def elabNoMatch : TermElab := fun stx expectedType? => do
|
||||
match stx with
|
||||
| `(nomatch $discrExpr) =>
|
||||
if (← isAtomicDiscr discrExpr) then
|
||||
| `(nomatch $discrs,*) =>
|
||||
let discrs := discrs.getElems
|
||||
if (← discrs.allM fun discr => isAtomicDiscr discr.raw) then
|
||||
let expectedType ← waitExpectedType expectedType?
|
||||
let discr := mkNode ``Lean.Parser.Term.matchDiscr #[mkNullNode, discrExpr]
|
||||
elabMatchAux none #[discr] #[] mkNullNode expectedType
|
||||
/- Wait for discriminant types. -/
|
||||
for discr in discrs do
|
||||
let d ← elabTerm discr none
|
||||
let dType ← inferType d
|
||||
trace[Elab.match] "discr {d} : {← instantiateMVars dType}"
|
||||
tryPostponeIfMVar dType
|
||||
let discrs := discrs.map fun discr => mkNode ``Lean.Parser.Term.matchDiscr #[mkNullNode, discr.raw]
|
||||
elabMatchAux none discrs #[] mkNullNode expectedType
|
||||
else
|
||||
let stxNew ← `(let_mvar% ?x := $discrExpr; nomatch ?x)
|
||||
let rec loop (discrs : List Term) (discrsNew : Array Syntax) : TermElabM Term := do
|
||||
match discrs with
|
||||
| [] =>
|
||||
return ⟨stx.setArg 1 (Syntax.mkSep discrsNew (mkAtomFrom stx ", "))⟩
|
||||
| discr :: discrs =>
|
||||
if (← isAtomicDiscr discr) then
|
||||
loop discrs (discrsNew.push discr)
|
||||
else
|
||||
withFreshMacroScope do
|
||||
let discrNew ← `(?x)
|
||||
let r ← loop discrs (discrsNew.push discrNew)
|
||||
`(let_mvar% ?x := $discr; $r)
|
||||
let stxNew ← loop discrs.toList #[]
|
||||
withMacroExpansion stx stxNew <| elabTerm stxNew expectedType?
|
||||
| _ => throwUnsupportedSyntax
|
||||
|
||||
@[builtin_term_elab «nofun»] def elabNoFun : TermElab := fun stx expectedType? => do
|
||||
match stx with
|
||||
| `($tk:nofun) =>
|
||||
let expectedType ← waitExpectedType expectedType?
|
||||
let binders ← forallTelescopeReducing expectedType fun args _ =>
|
||||
args.mapM fun _ => withFreshMacroScope do `(a)
|
||||
let stxNew ← `(fun%$tk $binders* => nomatch%$tk $binders,*)
|
||||
withMacroExpansion stx stxNew <| elabTerm stxNew expectedType?
|
||||
| _ => throwUnsupportedSyntax
|
||||
|
||||
end Lean.Elab.Term
|
||||
|
||||
@@ -23,3 +23,7 @@ import Lean.Elab.Tactic.Unfold
|
||||
import Lean.Elab.Tactic.Cache
|
||||
import Lean.Elab.Tactic.Calc
|
||||
import Lean.Elab.Tactic.Congr
|
||||
import Lean.Elab.Tactic.Guard
|
||||
import Lean.Elab.Tactic.RCases
|
||||
import Lean.Elab.Tactic.Repeat
|
||||
import Lean.Elab.Tactic.Change
|
||||
|
||||
@@ -335,6 +335,15 @@ def evalTacticAt (tac : Syntax) (mvarId : MVarId) : TacticM (List MVarId) := do
|
||||
finally
|
||||
setGoals gs
|
||||
|
||||
/--
|
||||
Like `evalTacticAt`, but without restoring the goal list or pruning solved goals.
|
||||
Useful when these tasks are already being done in an outer loop.
|
||||
-/
|
||||
def evalTacticAtRaw (tac : Syntax) (mvarId : MVarId) : TacticM (List MVarId) := do
|
||||
setGoals [mvarId]
|
||||
evalTactic tac
|
||||
getGoals
|
||||
|
||||
def ensureHasNoMVars (e : Expr) : TacticM Unit := do
|
||||
let e ← instantiateMVars e
|
||||
let pendingMVars ← getMVars e
|
||||
|
||||
@@ -3,6 +3,7 @@ Copyright (c) 2021 Microsoft Corporation. All rights reserved.
|
||||
Released under Apache 2.0 license as described in the file LICENSE.
|
||||
Authors: Leonardo de Moura
|
||||
-/
|
||||
import Lean.Meta.Tactic.Apply
|
||||
import Lean.Meta.Tactic.Assumption
|
||||
import Lean.Meta.Tactic.Contradiction
|
||||
import Lean.Meta.Tactic.Refl
|
||||
@@ -11,6 +12,7 @@ import Lean.Elab.Open
|
||||
import Lean.Elab.SetOption
|
||||
import Lean.Elab.Tactic.Basic
|
||||
import Lean.Elab.Tactic.ElabTerm
|
||||
import Lean.Elab.Do
|
||||
|
||||
namespace Lean.Elab.Tactic
|
||||
open Meta
|
||||
@@ -323,6 +325,12 @@ def forEachVar (hs : Array Syntax) (tac : MVarId → FVarId → MetaM MVarId) :
|
||||
@[builtin_tactic Lean.Parser.Tactic.substVars] def evalSubstVars : Tactic := fun _ =>
|
||||
liftMetaTactic fun mvarId => return [← substVars mvarId]
|
||||
|
||||
/--
|
||||
`subst_eq` repeatedly substitutes according to the equality proof hypotheses in the context,
|
||||
replacing the left side of the equality with the right, until no more progress can be made.
|
||||
-/
|
||||
elab "subst_eqs" : tactic => Elab.Tactic.liftMetaTactic1 (·.substEqs)
|
||||
|
||||
/--
|
||||
Searches for a metavariable `g` s.t. `tag` is its exact name.
|
||||
If none then searches for a metavariable `g` s.t. `tag` is a suffix of its name.
|
||||
@@ -468,4 +476,24 @@ where
|
||||
| none => throwIllFormedSyntax
|
||||
| some ms => IO.sleep ms.toUInt32
|
||||
|
||||
@[builtin_tactic left] def evalLeft : Tactic := fun _stx => do
|
||||
liftMetaTactic (fun g => g.nthConstructor `left 0 (some 2))
|
||||
|
||||
@[builtin_tactic right] def evalRight : Tactic := fun _stx => do
|
||||
liftMetaTactic (fun g => g.nthConstructor `right 1 (some 2))
|
||||
|
||||
@[builtin_tactic replace] def evalReplace : Tactic := fun stx => do
|
||||
match stx with
|
||||
| `(tactic| replace $decl:haveDecl) =>
|
||||
withMainContext do
|
||||
let vars ← Elab.Term.Do.getDoHaveVars <| mkNullNode #[.missing, decl]
|
||||
let origLCtx ← getLCtx
|
||||
evalTactic $ ← `(tactic| have $decl:haveDecl)
|
||||
let mut toClear := #[]
|
||||
for fv in vars do
|
||||
if let some ldecl := origLCtx.findFromUserName? fv.getId then
|
||||
toClear := toClear.push ldecl.fvarId
|
||||
liftMetaTactic1 (·.tryClearMany toClear)
|
||||
| _ => throwUnsupportedSyntax
|
||||
|
||||
end Lean.Elab.Tactic
|
||||
|
||||
51
src/Lean/Elab/Tactic/Change.lean
Normal file
51
src/Lean/Elab/Tactic/Change.lean
Normal file
@@ -0,0 +1,51 @@
|
||||
/-
|
||||
Copyright (c) 2023 Kyle Miller. All rights reserved.
|
||||
Released under Apache 2.0 license as described in the file LICENSE.
|
||||
Authors: Kyle Miller
|
||||
-/
|
||||
import Lean.Meta.Tactic.Replace
|
||||
import Lean.Elab.Tactic.Location
|
||||
|
||||
namespace Lean.Elab.Tactic
|
||||
open Meta
|
||||
/-!
|
||||
# Implementation of the `change` tactic
|
||||
-/
|
||||
|
||||
/-- `change` can be used to replace the main goal or its hypotheses with
|
||||
different, yet definitionally equal, goal or hypotheses.
|
||||
|
||||
For example, if `n : Nat` and the current goal is `⊢ n + 2 = 2`, then
|
||||
```lean
|
||||
change _ + 1 = _
|
||||
```
|
||||
changes the goal to `⊢ n + 1 + 1 = 2`.
|
||||
|
||||
The tactic also applies to hypotheses. If `h : n + 2 = 2` and `h' : n + 3 = 4`
|
||||
are hypotheses, then
|
||||
```lean
|
||||
change _ + 1 = _ at h h'
|
||||
```
|
||||
changes their types to be `h : n + 1 + 1 = 2` and `h' : n + 2 + 1 = 4`.
|
||||
|
||||
Change is like `refine` in that every placeholder needs to be solved for by unification,
|
||||
but using named placeholders or `?_` results in `change` to creating new goals.
|
||||
|
||||
The tactic `show e` is interchangeable with `change e`, where the pattern `e` is applied to
|
||||
the main goal. -/
|
||||
@[builtin_tactic change] elab_rules : tactic
|
||||
| `(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
|
||||
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")
|
||||
|
||||
end Lean.Elab.Tactic
|
||||
158
src/Lean/Elab/Tactic/Guard.lean
Normal file
158
src/Lean/Elab/Tactic/Guard.lean
Normal file
@@ -0,0 +1,158 @@
|
||||
/-
|
||||
Copyright (c) 2021 Mario Carneiro. All rights reserved.
|
||||
Released under Apache 2.0 license as described in the file LICENSE.
|
||||
Authors: Mario Carneiro, Leonardo de Moura
|
||||
-/
|
||||
import Lean.Elab.Command
|
||||
import Lean.Elab.Tactic.Conv.Basic
|
||||
import Lean.Meta.Basic
|
||||
import Lean.Meta.Eval
|
||||
|
||||
namespace Lean.Elab.Tactic.GuardExpr
|
||||
open Meta
|
||||
|
||||
/--
|
||||
The various `guard_*` tactics have similar matching specifiers for how equal expressions
|
||||
have to be to pass the tactic.
|
||||
This inductive gives the different specifiers that can be selected.
|
||||
-/
|
||||
inductive MatchKind
|
||||
/-- A syntactic match means that the `Expr`s are `==` after stripping `MData` -/
|
||||
| syntactic
|
||||
/-- A defeq match `isDefEqGuarded` returns true. (Note that unification is allowed here.) -/
|
||||
| defEq (red : TransparencyMode := .reducible)
|
||||
/-- An alpha-eq match means that `Expr.eqv` returns true. -/
|
||||
| alphaEq
|
||||
|
||||
open Lean.Parser Lean.Parser.Tactic Lean.Parser.Command
|
||||
|
||||
/-- Converts a `colon` syntax into a `MatchKind` -/
|
||||
def colon.toMatchKind : TSyntax ``colon → Option MatchKind
|
||||
| `(colon| :) => some .defEq
|
||||
| `(colon| :~) => some (.defEq .default)
|
||||
| `(colon| :ₛ) => some .syntactic
|
||||
| `(colon| :ₐ) => some .alphaEq
|
||||
| _ => none
|
||||
|
||||
/-- Converts a `colonEq` syntax into a `MatchKind` -/
|
||||
def colonEq.toMatchKind : TSyntax ``colonEq → Option MatchKind
|
||||
| `(colonEq| :=) => some .defEq
|
||||
| `(colonEq| :=~) => some (.defEq .default)
|
||||
| `(colonEq| :=ₛ) => some .syntactic
|
||||
| `(colonEq| :=ₐ) => some .alphaEq
|
||||
| _ => none
|
||||
|
||||
/-- Converts a `equal` syntax into a `MatchKind` -/
|
||||
def equal.toMatchKind : TSyntax ``equal → Option MatchKind
|
||||
| `(equal| =) => some .defEq
|
||||
| `(equal| =~) => some (.defEq .default)
|
||||
| `(equal| =ₛ) => some .syntactic
|
||||
| `(equal| =ₐ) => some .alphaEq
|
||||
| _ => none
|
||||
|
||||
/-- Applies the selected matching rule to two expressions. -/
|
||||
def MatchKind.isEq (a b : Expr) : MatchKind → MetaM Bool
|
||||
| .syntactic => return a.consumeMData == b.consumeMData
|
||||
| .alphaEq => return a.eqv b
|
||||
| .defEq red => withoutModifyingState <| withTransparency red <| Lean.Meta.isDefEqGuarded a b
|
||||
|
||||
|
||||
/-- Elaborate `a` and `b` and then do the given equality test `mk`. We make sure to unify
|
||||
the types of `a` and `b` after elaboration so that when synthesizing pending metavariables
|
||||
we don't get the wrong instances due to default instances (for example, for nat literals). -/
|
||||
def elabAndEvalMatchKind (mk : MatchKind) (a b : Term) : TermElabM Bool :=
|
||||
Term.withoutErrToSorry do
|
||||
let a ← Term.elabTerm a none
|
||||
let b ← Term.elabTerm b none
|
||||
-- Unify types before synthesizing pending metavariables:
|
||||
_ ← isDefEqGuarded (← inferType a) (← inferType b)
|
||||
Term.synthesizeSyntheticMVarsNoPostponing
|
||||
mk.isEq (← instantiateMVars a) (← instantiateMVars b)
|
||||
|
||||
@[builtin_tactic guardExpr]
|
||||
def evalGuardExpr : Tactic := fun
|
||||
| `(tactic| guard_expr $r $eq:equal $p)
|
||||
| `(conv| guard_expr $r $eq:equal $p) => withMainContext do
|
||||
let some mk := equal.toMatchKind eq | throwUnsupportedSyntax
|
||||
let res ← elabAndEvalMatchKind mk r p
|
||||
-- Note: `{eq}` itself prints a space before the relation.
|
||||
unless res do throwError "failed: {r}{eq} {p} is not true"
|
||||
| _ => throwUnsupportedSyntax
|
||||
|
||||
-- TODO: This is workaround. We currently allow two occurrences of `builtin_tactic`.
|
||||
@[builtin_tactic guardExprConv]
|
||||
def evalGuardExprConv : Tactic := evalGuardExpr
|
||||
|
||||
@[builtin_tactic guardTarget]
|
||||
def evalGuardTarget : Tactic :=
|
||||
let go eq r getTgt := withMainContext do
|
||||
let t ← getTgt >>= instantiateMVars
|
||||
let r ← elabTerm r (← inferType t)
|
||||
let some mk := equal.toMatchKind eq | throwUnsupportedSyntax
|
||||
unless ← mk.isEq r t do
|
||||
throwError "target of main goal is{indentExpr t}\nnot{indentExpr r}"
|
||||
fun
|
||||
| `(tactic| guard_target $eq $r) => go eq r getMainTarget
|
||||
| `(conv| guard_target $eq $r) => go eq r Conv.getLhs
|
||||
| _ => throwUnsupportedSyntax
|
||||
|
||||
-- See comment above
|
||||
@[builtin_tactic guardTargetConv]
|
||||
def evalGuardTargetConv : Tactic := evalGuardTarget
|
||||
|
||||
@[builtin_tactic guardHyp]
|
||||
def evalGuardHyp : Tactic := fun
|
||||
| `(tactic| guard_hyp $h $[$c $ty]? $[$eq $val]?)
|
||||
| `(conv| guard_hyp $h $[$c $ty]? $[$eq $val]?) => withMainContext do
|
||||
let fvarid ← getFVarId h
|
||||
let lDecl ←
|
||||
match (← getLCtx).find? fvarid with
|
||||
| none => throwError m!"hypothesis {h} not found"
|
||||
| some lDecl => pure lDecl
|
||||
if let (some c, some p) := (c, ty) then
|
||||
let some mk := colon.toMatchKind c | throwUnsupportedSyntax
|
||||
let e ← elabTerm p none
|
||||
let hty ← instantiateMVars lDecl.type
|
||||
unless ← mk.isEq e hty do
|
||||
throwError m!"hypothesis {h} has type{indentExpr hty}\nnot{indentExpr e}"
|
||||
match lDecl.value?, val with
|
||||
| none, some _ => throwError m!"{h} is not a let binding"
|
||||
| some _, none => throwError m!"{h} is a let binding"
|
||||
| some hval, some val =>
|
||||
let some mk := eq.bind colonEq.toMatchKind | throwUnsupportedSyntax
|
||||
let e ← elabTerm val lDecl.type
|
||||
let hval ← instantiateMVars hval
|
||||
unless ← mk.isEq e hval do
|
||||
throwError m!"hypothesis {h} has value{indentExpr hval}\nnot{indentExpr e}"
|
||||
| none, none => pure ()
|
||||
| _ => throwUnsupportedSyntax
|
||||
|
||||
@[builtin_tactic guardHypConv]
|
||||
def evalGuardHypConv : Tactic := evalGuardHyp
|
||||
|
||||
@[builtin_command_elab guardExprCmd]
|
||||
def evalGuardExprCmd : Lean.Elab.Command.CommandElab
|
||||
| `(command| #guard_expr $r $eq:equal $p) =>
|
||||
Lean.Elab.Command.runTermElabM fun _ => do
|
||||
let some mk := equal.toMatchKind eq | throwUnsupportedSyntax
|
||||
let res ← elabAndEvalMatchKind mk r p
|
||||
-- Note: `{eq}` itself prints a space before the relation.
|
||||
unless res do throwError "failed: {r}{eq} {p} is not true"
|
||||
| _ => throwUnsupportedSyntax
|
||||
|
||||
@[builtin_command_elab guardCmd]
|
||||
def evalGuardCmd : Lean.Elab.Command.CommandElab
|
||||
| `(command| #guard $e:term) => Lean.Elab.Command.liftTermElabM do
|
||||
let e ← Term.elabTermEnsuringType e (mkConst ``Bool)
|
||||
Term.synthesizeSyntheticMVarsNoPostponing
|
||||
let e ← instantiateMVars e
|
||||
let mvars ← getMVars e
|
||||
if mvars.isEmpty then
|
||||
let v ← unsafe evalExpr Bool (mkConst ``Bool) e
|
||||
unless v do
|
||||
throwError "expression{indentExpr e}\ndid not evaluate to `true`"
|
||||
else
|
||||
_ ← Term.logUnassignedUsingErrorInfos mvars
|
||||
| _ => throwUnsupportedSyntax
|
||||
|
||||
end Lean.Elab.Tactic.GuardExpr
|
||||
580
src/Lean/Elab/Tactic/RCases.lean
Normal file
580
src/Lean/Elab/Tactic/RCases.lean
Normal file
@@ -0,0 +1,580 @@
|
||||
/-
|
||||
Copyright (c) 2017 Mario Carneiro. All rights reserved.
|
||||
Released under Apache 2.0 license as described in the file LICENSE.
|
||||
Authors: Mario Carneiro, Jacob von Raumer
|
||||
-/
|
||||
import Lean.Elab.Tactic.Induction
|
||||
|
||||
namespace Lean.Elab.Tactic.RCases
|
||||
open Meta Parser Tactic
|
||||
|
||||
/--
|
||||
Enables the 'unused rcases pattern' linter. This will warn when a pattern is ignored by
|
||||
`rcases`, `rintro`, `ext` and similar tactics.
|
||||
-/
|
||||
register_option linter.unusedRCasesPattern : Bool := {
|
||||
defValue := true
|
||||
descr := "enable the 'unused rcases pattern' linter"
|
||||
}
|
||||
|
||||
instance : Coe Ident (TSyntax `rcasesPat) where
|
||||
coe stx := Unhygienic.run `(rcasesPat| $stx:ident)
|
||||
instance : Coe (TSyntax `rcasesPat) (TSyntax ``rcasesPatMed) where
|
||||
coe stx := Unhygienic.run `(rcasesPatMed| $stx:rcasesPat)
|
||||
instance : Coe (TSyntax ``rcasesPatMed) (TSyntax ``rcasesPatLo) where
|
||||
coe stx := Unhygienic.run `(rcasesPatLo| $stx:rcasesPatMed)
|
||||
instance : Coe (TSyntax `rcasesPat) (TSyntax `rintroPat) where
|
||||
coe stx := Unhygienic.run `(rintroPat| $stx:rcasesPat)
|
||||
|
||||
/-- A list, with a disjunctive meaning (like a list of inductive constructors, or subgoals) -/
|
||||
local notation "ListΣ" => List
|
||||
|
||||
/-- A list, with a conjunctive meaning (like a list of constructor arguments, or hypotheses) -/
|
||||
local notation "ListΠ" => List
|
||||
|
||||
/--
|
||||
An `rcases` pattern can be one of the following, in a nested combination:
|
||||
|
||||
* A name like `foo`
|
||||
* The special keyword `rfl` (for pattern matching on equality using `subst`)
|
||||
* A hyphen `-`, which clears the active hypothesis and any dependents.
|
||||
* A type ascription like `pat : ty` (parentheses are optional)
|
||||
* A tuple constructor like `⟨p1, p2, p3⟩`
|
||||
* An alternation / variant pattern `p1 | p2 | p3`
|
||||
|
||||
Parentheses can be used for grouping; alternation is higher precedence than type ascription, so
|
||||
`p1 | p2 | p3 : ty` means `(p1 | p2 | p3) : ty`.
|
||||
|
||||
N-ary alternations are treated as a group, so `p1 | p2 | p3` is not the same as `p1 | (p2 | p3)`,
|
||||
and similarly for tuples. However, note that an n-ary alternation or tuple can match an n-ary
|
||||
conjunction or disjunction, because if the number of patterns exceeds the number of constructors in
|
||||
the type being destructed, the extra patterns will match on the last element, meaning that
|
||||
`p1 | p2 | p3` will act like `p1 | (p2 | p3)` when matching `a1 ∨ a2 ∨ a3`. If matching against a
|
||||
type with 3 constructors, `p1 | (p2 | p3)` will act like `p1 | (p2 | p3) | _` instead.
|
||||
-/
|
||||
inductive RCasesPatt : Type
|
||||
/-- A parenthesized expression, used for hovers -/
|
||||
| paren (ref : Syntax) : RCasesPatt → RCasesPatt
|
||||
/-- A named pattern like `foo` -/
|
||||
| one (ref : Syntax) : Name → RCasesPatt
|
||||
/-- A hyphen `-`, which clears the active hypothesis and any dependents. -/
|
||||
| clear (ref : Syntax) : RCasesPatt
|
||||
/-- An explicit pattern `@pat`. -/
|
||||
| explicit (ref : Syntax) : RCasesPatt → RCasesPatt
|
||||
/-- A type ascription like `pat : ty` (parentheses are optional) -/
|
||||
| typed (ref : Syntax) : RCasesPatt → Term → RCasesPatt
|
||||
/-- A tuple constructor like `⟨p1, p2, p3⟩` -/
|
||||
| tuple (ref : Syntax) : ListΠ RCasesPatt → RCasesPatt
|
||||
/-- An alternation / variant pattern `p1 | p2 | p3` -/
|
||||
| alts (ref : Syntax) : ListΣ RCasesPatt → RCasesPatt
|
||||
deriving Repr
|
||||
|
||||
namespace RCasesPatt
|
||||
|
||||
instance : Inhabited RCasesPatt := ⟨RCasesPatt.one Syntax.missing `_⟩
|
||||
|
||||
/-- Get the name from a pattern, if provided -/
|
||||
partial def name? : RCasesPatt → Option Name
|
||||
| one _ `_ => none
|
||||
| one _ `rfl => none
|
||||
| one _ n => n
|
||||
| paren _ p
|
||||
| typed _ p _
|
||||
| alts _ [p] => p.name?
|
||||
| _ => none
|
||||
|
||||
/-- Get the syntax node from which this pattern was parsed. Used for error messages -/
|
||||
def ref : RCasesPatt → Syntax
|
||||
| paren ref _
|
||||
| one ref _
|
||||
| clear ref
|
||||
| explicit ref _
|
||||
| typed ref _ _
|
||||
| tuple ref _
|
||||
| alts ref _ => ref
|
||||
|
||||
/--
|
||||
Interpret an rcases pattern as a tuple, where `p` becomes `⟨p⟩` if `p` is not already a tuple.
|
||||
-/
|
||||
def asTuple : RCasesPatt → Bool × ListΠ RCasesPatt
|
||||
| paren _ p => p.asTuple
|
||||
| explicit _ p => (true, p.asTuple.2)
|
||||
| tuple _ ps => (false, ps)
|
||||
| p => (false, [p])
|
||||
|
||||
/--
|
||||
Interpret an rcases pattern as an alternation, where non-alternations are treated as one
|
||||
alternative.
|
||||
-/
|
||||
def asAlts : RCasesPatt → ListΣ RCasesPatt
|
||||
| paren _ p => p.asAlts
|
||||
| alts _ ps => ps
|
||||
| p => [p]
|
||||
|
||||
/-- Convert a list of patterns to a tuple pattern, but mapping `[p]` to `p` instead of `⟨p⟩`. -/
|
||||
def typed? (ref : Syntax) : RCasesPatt → Option Term → RCasesPatt
|
||||
| p, none => p
|
||||
| p, some ty => typed ref p ty
|
||||
|
||||
/-- Convert a list of patterns to a tuple pattern, but mapping `[p]` to `p` instead of `⟨p⟩`. -/
|
||||
def tuple' : ListΠ RCasesPatt → RCasesPatt
|
||||
| [p] => p
|
||||
| ps => tuple (ps.head?.map (·.ref) |>.getD .missing) ps
|
||||
|
||||
/--
|
||||
Convert a list of patterns to an alternation pattern, but mapping `[p]` to `p` instead of
|
||||
a unary alternation `|p`.
|
||||
-/
|
||||
def alts' (ref : Syntax) : ListΣ RCasesPatt → RCasesPatt
|
||||
| [p] => p
|
||||
| ps => alts ref ps
|
||||
|
||||
/--
|
||||
This function is used for producing rcases patterns based on a case tree. Suppose that we have
|
||||
a list of patterns `ps` that will match correctly against the branches of the case tree for one
|
||||
constructor. This function will merge tuples at the end of the list, so that `[a, b, ⟨c, d⟩]`
|
||||
becomes `⟨a, b, c, d⟩` instead of `⟨a, b, ⟨c, d⟩⟩`.
|
||||
|
||||
We must be careful to turn `[a, ⟨⟩]` into `⟨a, ⟨⟩⟩` instead of `⟨a⟩` (which will not perform the
|
||||
nested match).
|
||||
-/
|
||||
def tuple₁Core : ListΠ RCasesPatt → ListΠ RCasesPatt
|
||||
| [] => []
|
||||
| [tuple ref []] => [tuple ref []]
|
||||
| [tuple _ ps] => ps
|
||||
| p :: ps => p :: tuple₁Core ps
|
||||
|
||||
/--
|
||||
This function is used for producing rcases patterns based on a case tree. This is like
|
||||
`tuple₁Core` but it produces a pattern instead of a tuple pattern list, converting `[n]` to `n`
|
||||
instead of `⟨n⟩` and `[]` to `_`, and otherwise just converting `[a, b, c]` to `⟨a, b, c⟩`.
|
||||
-/
|
||||
def tuple₁ : ListΠ RCasesPatt → RCasesPatt
|
||||
| [] => default
|
||||
| [one ref n] => one ref n
|
||||
| ps => tuple ps.head!.ref $ tuple₁Core ps
|
||||
|
||||
/--
|
||||
This function is used for producing rcases patterns based on a case tree. Here we are given
|
||||
the list of patterns to apply to each argument of each constructor after the main case, and must
|
||||
produce a list of alternatives with the same effect. This function calls `tuple₁` to make the
|
||||
individual alternatives, and handles merging `[a, b, c | d]` to `a | b | c | d` instead of
|
||||
`a | b | (c | d)`.
|
||||
-/
|
||||
def alts₁Core : ListΣ (ListΠ RCasesPatt) → ListΣ RCasesPatt
|
||||
| [] => []
|
||||
| [[alts _ ps]] => ps
|
||||
| p :: ps => tuple₁ p :: alts₁Core ps
|
||||
|
||||
/--
|
||||
This function is used for producing rcases patterns based on a case tree. This is like
|
||||
`alts₁Core`, but it produces a cases pattern directly instead of a list of alternatives. We
|
||||
specially translate the empty alternation to `⟨⟩`, and translate `|(a | b)` to `⟨a | b⟩` (because we
|
||||
don't have any syntax for unary alternation). Otherwise we can use the regular merging of
|
||||
alternations at the last argument so that `a | b | (c | d)` becomes `a | b | c | d`.
|
||||
-/
|
||||
def alts₁ (ref : Syntax) : ListΣ (ListΠ RCasesPatt) → RCasesPatt
|
||||
| [[]] => tuple .missing []
|
||||
| [[alts ref ps]] => tuple ref ps
|
||||
| ps => alts' ref $ alts₁Core ps
|
||||
|
||||
open MessageData in
|
||||
partial instance : ToMessageData RCasesPatt := ⟨fmt 0⟩ where
|
||||
/-- parenthesize the message if the precedence is above `tgt` -/
|
||||
parenAbove (tgt p : Nat) (m : MessageData) : MessageData :=
|
||||
if tgt < p then m.paren else m
|
||||
/-- format an `RCasesPatt` with the given precedence: 0 = lo, 1 = med, 2 = hi -/
|
||||
fmt : Nat → RCasesPatt → MessageData
|
||||
| p, paren _ pat => fmt p pat
|
||||
| _, one _ n => n
|
||||
| _, clear _ => "-"
|
||||
| _, explicit _ pat => m!"@{fmt 2 pat}"
|
||||
| p, typed _ pat ty => parenAbove 0 p m!"{fmt 1 pat}: {ty}"
|
||||
| _, tuple _ pats => bracket "⟨" (joinSep (pats.map (fmt 0)) ("," ++ Format.line)) "⟩"
|
||||
| p, alts _ pats => parenAbove 1 p (joinSep (pats.map (fmt 2)) " | ")
|
||||
|
||||
end RCasesPatt
|
||||
|
||||
/--
|
||||
Takes the number of fields of a single constructor and patterns to match its fields against
|
||||
(not necessarily the same number). The returned lists each contain one element per field of the
|
||||
constructor. The `name` is the name which will be used in the top-level `cases` tactic, and the
|
||||
`rcases_patt` is the pattern which the field will be matched against by subsequent `cases`
|
||||
tactics.
|
||||
-/
|
||||
def processConstructor (ref : Syntax) (info : Array ParamInfo)
|
||||
(explicit : Bool) (idx : Nat) (ps : ListΠ RCasesPatt) : ListΠ Name × ListΠ RCasesPatt :=
|
||||
if _ : idx < info.size then
|
||||
if !explicit && info[idx].binderInfo != .default then
|
||||
let (ns, tl) := processConstructor ref info explicit (idx+1) ps
|
||||
(`_ :: ns, default :: tl)
|
||||
else if idx+1 < info.size then
|
||||
let p := ps.headD default
|
||||
let (ns, tl) := processConstructor ref info explicit (idx+1) (ps.tailD [])
|
||||
(p.name?.getD `_ :: ns, p :: tl)
|
||||
else match ps with
|
||||
| [] => ([`_], [default])
|
||||
| [p] => ([p.name?.getD `_], [p])
|
||||
| ps => ([`_], [(bif explicit then .explicit ref else id) (.tuple ref ps)])
|
||||
else ([], [])
|
||||
termination_by info.size - idx
|
||||
|
||||
/--
|
||||
Takes a list of constructor names, and an (alternation) list of patterns, and matches each
|
||||
pattern against its constructor. It returns the list of names that will be passed to `cases`,
|
||||
and the list of `(constructor name, patterns)` for each constructor, where `patterns` is the
|
||||
(conjunctive) list of patterns to apply to each constructor argument.
|
||||
-/
|
||||
def processConstructors (ref : Syntax) (params : Nat) (altVarNames : Array AltVarNames := #[]) :
|
||||
ListΣ Name → ListΣ RCasesPatt → MetaM (Array AltVarNames × ListΣ (Name × ListΠ RCasesPatt))
|
||||
| [], _ => pure (altVarNames, [])
|
||||
| c :: cs, ps => do
|
||||
let info := (← getFunInfo (← mkConstWithLevelParams c)).paramInfo
|
||||
let p := ps.headD default
|
||||
let t := ps.tailD []
|
||||
let ((explicit, h), t) := match cs, t with
|
||||
| [], _ :: _ => ((false, [RCasesPatt.alts ref ps]), [])
|
||||
| _, _ => (p.asTuple, t)
|
||||
let (ns, ps) := processConstructor p.ref info explicit params h
|
||||
let (altVarNames, r) ← processConstructors ref params (altVarNames.push ⟨true, ns⟩) cs t
|
||||
pure (altVarNames, (c, ps) :: r)
|
||||
|
||||
open Elab Tactic
|
||||
|
||||
-- TODO(Mario): this belongs in core
|
||||
/-- Like `Lean.Meta.subst`, but preserves the `FVarSubst`. -/
|
||||
def subst' (goal : MVarId) (hFVarId : FVarId)
|
||||
(fvarSubst : FVarSubst := {}) : MetaM (FVarSubst × MVarId) := do
|
||||
let hLocalDecl ← hFVarId.getDecl
|
||||
let error {α} _ : MetaM α := throwTacticEx `subst goal
|
||||
m!"invalid equality proof, it is not of the form (x = t) or (t = x){indentExpr hLocalDecl.type}"
|
||||
let some (_, lhs, rhs) ← matchEq? hLocalDecl.type | error ()
|
||||
let substReduced (newType : Expr) (symm : Bool) : MetaM (FVarSubst × MVarId) := do
|
||||
let goal ← goal.assert hLocalDecl.userName newType (mkFVar hFVarId)
|
||||
let (hFVarId', goal) ← goal.intro1P
|
||||
let goal ← goal.clear hFVarId
|
||||
substCore goal hFVarId' (symm := symm) (tryToSkip := true) (fvarSubst := fvarSubst)
|
||||
let rhs' ← whnf rhs
|
||||
if rhs'.isFVar then
|
||||
if rhs != rhs' then
|
||||
substReduced (← mkEq lhs rhs') true
|
||||
else
|
||||
substCore goal hFVarId (symm := true) (tryToSkip := true) (fvarSubst := fvarSubst)
|
||||
else
|
||||
let lhs' ← whnf lhs
|
||||
if lhs'.isFVar then
|
||||
if lhs != lhs' then
|
||||
substReduced (← mkEq lhs' rhs) false
|
||||
else
|
||||
substCore goal hFVarId (symm := false) (tryToSkip := true) (fvarSubst := fvarSubst)
|
||||
else error ()
|
||||
|
||||
mutual
|
||||
|
||||
/--
|
||||
This will match a pattern `pat` against a local hypothesis `e`.
|
||||
* `g`: The initial subgoal
|
||||
* `fs`: A running variable substitution, the result of `cases` operations upstream.
|
||||
The variable `e` must be run through this map before locating it in the context of `g`,
|
||||
and the output variable substitutions will be end extensions of this one.
|
||||
* `clears`: The list of variables to clear in all subgoals generated from this point on.
|
||||
We defer clear operations because clearing too early can cause `cases` to fail.
|
||||
The actual clearing happens in `RCases.finish`.
|
||||
* `e`: a local hypothesis, the scrutinee to match against.
|
||||
* `a`: opaque "user data" which is passed through all the goal calls at the end.
|
||||
* `pat`: the pattern to match against
|
||||
* `cont`: A continuation. This is called on every goal generated by the result of the pattern
|
||||
match, with updated values for `g` , `fs`, `clears`, and `a`.
|
||||
-/
|
||||
partial def rcasesCore (g : MVarId) (fs : FVarSubst) (clears : Array FVarId) (e : Expr) (a : α)
|
||||
(pat : RCasesPatt) (cont : MVarId → FVarSubst → Array FVarId → α → TermElabM α) :
|
||||
TermElabM α := do
|
||||
let asFVar : Expr → MetaM _
|
||||
| .fvar e => pure e
|
||||
| e => throwError "rcases tactic failed: {e} is not a fvar"
|
||||
withRef pat.ref <| g.withContext do match pat with
|
||||
| .one ref `rfl =>
|
||||
Term.synthesizeSyntheticMVarsNoPostponing
|
||||
-- Note: the mdata prevents the span from getting highlighted like a variable
|
||||
Term.addTermInfo' ref (.mdata {} e)
|
||||
let (fs, g) ← subst' g (← asFVar (fs.apply e)) fs
|
||||
cont g fs clears a
|
||||
| .one ref _ =>
|
||||
if e.isFVar then
|
||||
Term.addLocalVarInfo ref e
|
||||
cont g fs clears a
|
||||
| .clear ref =>
|
||||
Term.addTermInfo' ref (.mdata {} e)
|
||||
cont g fs (if let .fvar e := e then clears.push e else clears) a
|
||||
| .typed ref pat ty =>
|
||||
Term.addTermInfo' ref (.mdata {} e)
|
||||
let expected ← Term.elabType ty
|
||||
let e := fs.apply e
|
||||
let etype ← inferType e
|
||||
unless ← isDefEq etype expected do
|
||||
Term.throwTypeMismatchError "rcases: scrutinee" expected etype e
|
||||
let g ← if let .fvar e := e then g.replaceLocalDeclDefEq e expected else pure g
|
||||
rcasesCore g fs clears e a pat cont
|
||||
| .paren ref p
|
||||
| .alts ref [p] =>
|
||||
Term.addTermInfo' ref (.mdata {} e)
|
||||
rcasesCore g fs clears e a p cont
|
||||
| _ =>
|
||||
Term.addTermInfo' pat.ref (.mdata {} e)
|
||||
let e := fs.apply e
|
||||
let _ ← asFVar e
|
||||
Term.synthesizeSyntheticMVarsNoPostponing
|
||||
let type ← whnfD (← inferType e)
|
||||
let failK {α} _ : TermElabM α :=
|
||||
throwError "rcases tactic failed: {e} : {type} is not an inductive datatype"
|
||||
let (r, subgoals) ← matchConst type.getAppFn failK fun
|
||||
| ConstantInfo.quotInfo info, _ => do
|
||||
unless info.kind matches QuotKind.type do failK ()
|
||||
let pat := pat.asAlts.headD default
|
||||
let (explicit, pat₁) := pat.asTuple
|
||||
let ([x], ps) := processConstructor pat.ref #[{}] explicit 0 pat₁ | unreachable!
|
||||
let (vars, g) ← g.revert (← getFVarsToGeneralize #[e])
|
||||
g.withContext do
|
||||
let elimInfo ← getElimInfo `Quot.ind
|
||||
let res ← ElimApp.mkElimApp elimInfo #[e] (← g.getTag)
|
||||
let elimArgs := res.elimApp.getAppArgs
|
||||
ElimApp.setMotiveArg g elimArgs[elimInfo.motivePos]!.mvarId! #[e.fvarId!]
|
||||
g.assign res.elimApp
|
||||
let #[{ name := n, mvarId := g, .. }] := res.alts | unreachable!
|
||||
let (v, g) ← g.intro x
|
||||
let (varsOut, g) ← g.introNP vars.size
|
||||
let fs' := (vars.zip varsOut).foldl (init := fs) fun fs (v, w) => fs.insert v (mkFVar w)
|
||||
pure ([(n, ps)], #[⟨⟨g, #[mkFVar v], fs'⟩, n⟩])
|
||||
| ConstantInfo.inductInfo info, _ => do
|
||||
let (altVarNames, r) ← processConstructors pat.ref info.numParams #[] info.ctors pat.asAlts
|
||||
(r, ·) <$> g.cases e.fvarId! altVarNames
|
||||
| _, _ => failK ()
|
||||
(·.2) <$> subgoals.foldlM (init := (r, a)) fun (r, a) ⟨goal, ctorName⟩ => do
|
||||
let rec
|
||||
/-- Runs `rcasesContinue` on the first pattern in `r` with a matching `ctorName`.
|
||||
The unprocessed patterns (subsequent to the matching pattern) are returned. -/
|
||||
align : ListΠ (Name × ListΠ RCasesPatt) → TermElabM (ListΠ (Name × ListΠ RCasesPatt) × α)
|
||||
| [] => pure ([], a)
|
||||
| (tgt, ps) :: as => do
|
||||
if tgt == ctorName then
|
||||
let fs := fs.append goal.subst
|
||||
(as, ·) <$> rcasesContinue goal.mvarId fs clears a (ps.zip goal.fields.toList) cont
|
||||
else
|
||||
align as
|
||||
align r
|
||||
|
||||
/--
|
||||
This will match a list of patterns against a list of hypotheses `e`. The arguments are similar
|
||||
to `rcasesCore`, but the patterns and local variables are in `pats`. Because the calls are all
|
||||
nested in continuations, later arguments can be matched many times, once per goal produced by
|
||||
earlier arguments. For example `⟨a | b, ⟨c, d⟩⟩` performs the `⟨c, d⟩` match twice, once on the
|
||||
`a` branch and once on `b`.
|
||||
-/
|
||||
partial def rcasesContinue (g : MVarId) (fs : FVarSubst) (clears : Array FVarId) (a : α)
|
||||
(pats : ListΠ (RCasesPatt × Expr)) (cont : MVarId → FVarSubst → Array FVarId → α → TermElabM α) :
|
||||
TermElabM α :=
|
||||
match pats with
|
||||
| [] => cont g fs clears a
|
||||
| ((pat, e) :: ps) =>
|
||||
rcasesCore g fs clears e a pat fun g fs clears a =>
|
||||
rcasesContinue g fs clears a ps cont
|
||||
|
||||
end
|
||||
|
||||
/-- Like `tryClearMany`, but also clears dependent hypotheses if possible -/
|
||||
def tryClearMany' (goal : MVarId) (fvarIds : Array FVarId) : MetaM MVarId := do
|
||||
let mut toErase := fvarIds
|
||||
for localDecl in (← goal.getDecl).lctx do
|
||||
if ← findLocalDeclDependsOn localDecl toErase.contains then
|
||||
toErase := toErase.push localDecl.fvarId
|
||||
goal.tryClearMany toErase
|
||||
|
||||
/--
|
||||
The terminating continuation used in `rcasesCore` and `rcasesContinue`. We specialize the type
|
||||
`α` to `Array MVarId` to collect the list of goals, and given the list of `clears`, it attempts to
|
||||
clear them from the goal and adds the goal to the list.
|
||||
-/
|
||||
def finish (toTag : Array (Ident × FVarId) := #[])
|
||||
(g : MVarId) (fs : FVarSubst) (clears : Array FVarId)
|
||||
(gs : Array MVarId) : TermElabM (Array MVarId) := do
|
||||
let cs : Array Expr := (clears.map fs.get).filter Expr.isFVar
|
||||
let g ← tryClearMany' g (cs.map Expr.fvarId!)
|
||||
g.withContext do
|
||||
for (stx, fvar) in toTag do
|
||||
Term.addLocalVarInfo stx (fs.get fvar)
|
||||
return gs.push g
|
||||
|
||||
open Elab
|
||||
|
||||
/-- Parses a `Syntax` into the `RCasesPatt` type used by the `RCases` tactic. -/
|
||||
partial def RCasesPatt.parse (stx : Syntax) : MetaM RCasesPatt :=
|
||||
match stx with
|
||||
| `(rcasesPatMed| $ps:rcasesPat|*) => return .alts' stx (← ps.getElems.toList.mapM (parse ·.raw))
|
||||
| `(rcasesPatLo| $pat:rcasesPatMed : $t:term) => return .typed stx (← parse pat) t
|
||||
| `(rcasesPatLo| $pat:rcasesPatMed) => parse pat
|
||||
| `(rcasesPat| _) => return .one stx `_
|
||||
| `(rcasesPat| $h:ident) => return .one h h.getId
|
||||
| `(rcasesPat| -) => return .clear stx
|
||||
| `(rcasesPat| @$pat) => return .explicit stx (← parse pat)
|
||||
| `(rcasesPat| ⟨$ps,*⟩) => return .tuple stx (← ps.getElems.toList.mapM (parse ·.raw))
|
||||
| `(rcasesPat| ($pat)) => return .paren stx (← parse pat)
|
||||
| _ => throwUnsupportedSyntax
|
||||
|
||||
-- extracted from elabCasesTargets
|
||||
/-- Generalize all the arguments as specified in `args` to fvars if they aren't already -/
|
||||
def generalizeExceptFVar (goal : MVarId) (args : Array GeneralizeArg) :
|
||||
MetaM (Array Expr × Array FVarId × MVarId) := do
|
||||
let argsToGeneralize := args.filter fun arg => !(arg.expr.isFVar && arg.hName?.isNone)
|
||||
let (fvarIdsNew, goal) ← goal.generalize argsToGeneralize
|
||||
let mut result := #[]
|
||||
let mut j := 0
|
||||
for arg in args do
|
||||
if arg.expr.isFVar && arg.hName?.isNone then
|
||||
result := result.push arg.expr
|
||||
else
|
||||
result := result.push (mkFVar fvarIdsNew[j]!)
|
||||
j := j+1
|
||||
pure (result, fvarIdsNew[j:], goal)
|
||||
|
||||
/--
|
||||
Given a list of targets of the form `e` or `h : e`, and a pattern, match all the targets
|
||||
against the pattern. Returns the list of produced subgoals.
|
||||
-/
|
||||
def rcases (tgts : Array (Option Ident × Syntax))
|
||||
(pat : RCasesPatt) (g : MVarId) : TermElabM (List MVarId) := Term.withSynthesize do
|
||||
let pats ← match tgts.size with
|
||||
| 0 => return [g]
|
||||
| 1 => pure [pat]
|
||||
| _ => pure (processConstructor pat.ref (tgts.map fun _ => {}) false 0 pat.asTuple.2).2
|
||||
let (pats, args) := Array.unzip <|← (tgts.zip pats.toArray).mapM fun ((hName?, tgt), pat) => do
|
||||
let (pat, ty) ← match pat with
|
||||
| .typed ref pat ty => withRef ref do
|
||||
let ty ← Term.elabType ty
|
||||
pure (.typed ref pat (← Term.exprToSyntax ty), some ty)
|
||||
| _ => pure (pat, none)
|
||||
let expr ← Term.ensureHasType ty (← Term.elabTerm tgt ty)
|
||||
pure (pat, { expr, xName? := pat.name?, hName? := hName?.map (·.getId) : GeneralizeArg })
|
||||
let (vs, hs, g) ← generalizeExceptFVar g args
|
||||
let toTag := tgts.filterMap (·.1) |>.zip hs
|
||||
let gs ← rcasesContinue g {} #[] #[] (pats.zip vs).toList (finish (toTag := toTag))
|
||||
pure gs.toList
|
||||
|
||||
/--
|
||||
The `obtain` tactic in the no-target case. Given a type `T`, create a goal `|- T` and
|
||||
and pattern match `T` against the given pattern. Returns the list of goals, with the assumed goal
|
||||
first followed by the goals produced by the pattern match.
|
||||
-/
|
||||
def obtainNone (pat : RCasesPatt) (ty : Syntax) (g : MVarId) : TermElabM (List MVarId) :=
|
||||
Term.withSynthesize do
|
||||
let ty ← Term.elabType ty
|
||||
let g₁ ← mkFreshExprMVar (some ty)
|
||||
let (v, g₂) ← (← g.assert (pat.name?.getD default) ty g₁).intro1
|
||||
let gs ← rcasesCore g₂ {} #[] (.fvar v) #[] pat finish
|
||||
pure (g₁.mvarId! :: gs.toList)
|
||||
|
||||
mutual
|
||||
variable [Monad m] [MonadQuotation m]
|
||||
|
||||
/-- Expand a `rintroPat` into an equivalent list of `rcasesPat` patterns. -/
|
||||
partial def expandRIntroPat (pat : TSyntax `rintroPat)
|
||||
(acc : Array (TSyntax `rcasesPat) := #[]) (ty? : Option Term := none) :
|
||||
Array (TSyntax `rcasesPat) :=
|
||||
match pat with
|
||||
| `(rintroPat| $p:rcasesPat) => match ty? with
|
||||
| some ty => acc.push <| Unhygienic.run <| withRef p `(rcasesPat| ($p:rcasesPat : $ty))
|
||||
| none => acc.push p
|
||||
| `(rintroPat| ($(pats)* $[: $ty?']?)) => expandRIntroPats pats acc (ty?' <|> ty?)
|
||||
| _ => acc
|
||||
|
||||
/-- Expand a list of `rintroPat` into an equivalent list of `rcasesPat` patterns. -/
|
||||
partial def expandRIntroPats (pats : Array (TSyntax `rintroPat))
|
||||
(acc : Array (TSyntax `rcasesPat) := #[]) (ty? : Option Term := none) :
|
||||
Array (TSyntax `rcasesPat) :=
|
||||
pats.foldl (fun acc p => expandRIntroPat p acc ty?) acc
|
||||
|
||||
end
|
||||
|
||||
mutual
|
||||
|
||||
/--
|
||||
This introduces the pattern `pat`. It has the same arguments as `rcasesCore`, plus:
|
||||
* `ty?`: the nearest enclosing type ascription on the current pattern
|
||||
-/
|
||||
partial def rintroCore (g : MVarId) (fs : FVarSubst) (clears : Array FVarId) (a : α)
|
||||
(ref : Syntax) (pat : TSyntax `rintroPat) (ty? : Option Term)
|
||||
(cont : MVarId → FVarSubst → Array FVarId → α → TermElabM α) : TermElabM α := do
|
||||
match pat with
|
||||
| `(rintroPat| $pat:rcasesPat) =>
|
||||
let pat := (← RCasesPatt.parse pat).typed? ref ty?
|
||||
let (v, g) ← g.intro (pat.name?.getD `_)
|
||||
rcasesCore g fs clears (.fvar v) a pat cont
|
||||
| `(rintroPat| ($(pats)* $[: $ty?']?)) =>
|
||||
let ref := if pats.size == 1 then pat.raw else .missing
|
||||
rintroContinue g fs clears ref pats (ty?' <|> ty?) a cont
|
||||
| _ => throwUnsupportedSyntax
|
||||
|
||||
/--
|
||||
This introduces the list of patterns `pats`. It has the same arguments as `rcasesCore`, plus:
|
||||
* `ty?`: the nearest enclosing type ascription on the current pattern
|
||||
-/
|
||||
partial def rintroContinue (g : MVarId) (fs : FVarSubst) (clears : Array FVarId)
|
||||
(ref : Syntax) (pats : TSyntaxArray `rintroPat) (ty? : Option Term) (a : α)
|
||||
(cont : MVarId → FVarSubst → Array FVarId → α → TermElabM α) : TermElabM α := do
|
||||
g.withContext (loop 0 g fs clears a)
|
||||
where
|
||||
/-- Runs `rintroContinue` on `pats[i:]` -/
|
||||
loop i g fs clears a := do
|
||||
if h : i < pats.size then
|
||||
rintroCore g fs clears a ref (pats.get ⟨i, h⟩) ty? (loop (i+1))
|
||||
else cont g fs clears a
|
||||
|
||||
end
|
||||
|
||||
/--
|
||||
The implementation of the `rintro` tactic. It takes a list of patterns `pats` and
|
||||
an optional type ascription `ty?` and introduces the patterns, resulting in zero or more goals.
|
||||
-/
|
||||
def rintro (pats : TSyntaxArray `rintroPat) (ty? : Option Term)
|
||||
(g : MVarId) : TermElabM (List MVarId) := Term.withSynthesize do
|
||||
(·.toList) <$> rintroContinue g {} #[] .missing pats ty? #[] finish
|
||||
|
||||
@[builtin_tactic Lean.Parser.Tactic.rcases] def evalRCases : Tactic := fun stx => do
|
||||
match stx with
|
||||
| `(tactic| rcases%$tk $tgts,* $[with $pat?]?) =>
|
||||
let pat ← match pat? with
|
||||
| some pat => RCasesPatt.parse pat
|
||||
| none => pure $ RCasesPatt.tuple tk []
|
||||
let tgts := tgts.getElems.map fun tgt =>
|
||||
(if tgt.raw[0].isNone then none else some ⟨tgt.raw[0][0]⟩, tgt.raw[1])
|
||||
let g ← getMainGoal
|
||||
g.withContext do replaceMainGoal (← RCases.rcases tgts pat g)
|
||||
| _ => throwUnsupportedSyntax
|
||||
|
||||
@[builtin_tactic Lean.Parser.Tactic.obtain] def evalObtain : Tactic := fun stx => do
|
||||
match stx with
|
||||
| `(tactic| obtain%$tk $[$pat?:rcasesPatMed]? $[: $ty?]? $[:= $val?,*]?) =>
|
||||
let pat? ← liftM <| pat?.mapM RCasesPatt.parse
|
||||
if let some val := val? then
|
||||
let pat := pat?.getD (RCasesPatt.one tk `_)
|
||||
let pat := pat.typed? tk ty?
|
||||
let tgts := val.getElems.map fun val => (none, val.raw)
|
||||
let g ← getMainGoal
|
||||
g.withContext do replaceMainGoal (← RCases.rcases tgts pat g)
|
||||
else if let some ty := ty? then
|
||||
let pat := pat?.getD (RCasesPatt.one tk `this)
|
||||
let g ← getMainGoal
|
||||
g.withContext do replaceMainGoal (← RCases.obtainNone pat ty g)
|
||||
else
|
||||
throwError "\
|
||||
`obtain` requires either an expected type or a value.\n\
|
||||
usage: `obtain ⟨patt⟩? : type (:= val)?` or `obtain ⟨patt⟩? (: type)? := val`"
|
||||
| _ => throwUnsupportedSyntax
|
||||
|
||||
@[builtin_tactic Lean.Parser.Tactic.rintro] def evalRIntro : Tactic := fun stx => do
|
||||
match stx with
|
||||
| `(tactic| rintro $pats* $[: $ty?]?) =>
|
||||
let g ← getMainGoal
|
||||
g.withContext do replaceMainGoal (← RCases.rintro pats ty? g)
|
||||
| _ => throwUnsupportedSyntax
|
||||
|
||||
end RCases
|
||||
25
src/Lean/Elab/Tactic/Repeat.lean
Normal file
25
src/Lean/Elab/Tactic/Repeat.lean
Normal file
@@ -0,0 +1,25 @@
|
||||
/-
|
||||
Copyright (c) 2022 Mario Carneiro. All rights reserved.
|
||||
Released under Apache 2.0 license as described in the file LICENSE.
|
||||
Authors: Mario Carneiro, Scott Morrison
|
||||
-/
|
||||
import Lean.Meta.Tactic.Repeat
|
||||
import Lean.Elab.Tactic.Basic
|
||||
|
||||
namespace Lean.Elab.Tactic
|
||||
|
||||
@[builtin_tactic repeat']
|
||||
def evalRepeat' : Tactic := fun stx => do
|
||||
match stx with
|
||||
| `(tactic| repeat' $tac:tacticSeq) =>
|
||||
setGoals (← Meta.repeat' (evalTacticAtRaw tac) (← getGoals))
|
||||
| _ => throwUnsupportedSyntax
|
||||
|
||||
@[builtin_tactic repeat1']
|
||||
def evalRepeat1' : Tactic := fun stx => do
|
||||
match stx with
|
||||
| `(tactic| repeat1' $tac:tacticSeq) =>
|
||||
setGoals (← Meta.repeat1' (evalTacticAtRaw tac) (← getGoals))
|
||||
| _ => throwUnsupportedSyntax
|
||||
|
||||
end Lean.Elab.Tactic
|
||||
@@ -72,6 +72,7 @@ def Simp.DischargeWrapper.with (w : Simp.DischargeWrapper) (x : Option Simp.Disc
|
||||
finally
|
||||
set (← ref.get)
|
||||
|
||||
/-- Construct a `Simp.DischargeWrapper` from the `Syntax` for a `simp` discharger. -/
|
||||
private def mkDischargeWrapper (optDischargeSyntax : Syntax) : TacticM Simp.DischargeWrapper := do
|
||||
if optDischargeSyntax.isNone then
|
||||
return Simp.DischargeWrapper.default
|
||||
@@ -258,8 +259,15 @@ structure MkSimpContextResult where
|
||||
If `kind != SimpKind.simp`, the `discharge` option must be `none`
|
||||
|
||||
TODO: generate error message if non `rfl` theorems are provided as arguments to `dsimp`.
|
||||
|
||||
The argument `simpTheorems` defaults to `getSimpTheorems`,
|
||||
but allows overriding with an arbitrary mechanism to choose
|
||||
the simp theorems besides those specified in the syntax.
|
||||
Note that if the syntax includes `simp only`, the `simpTheorems` argument is ignored.
|
||||
-/
|
||||
def mkSimpContext (stx : Syntax) (eraseLocal : Bool) (kind := SimpKind.simp) (ignoreStarArg : Bool := false) : TacticM MkSimpContextResult := do
|
||||
def mkSimpContext (stx : Syntax) (eraseLocal : Bool) (kind := SimpKind.simp)
|
||||
(ignoreStarArg : Bool := false) (simpTheorems : CoreM SimpTheorems := getSimpTheorems) :
|
||||
TacticM MkSimpContextResult := do
|
||||
if !stx[2].isNone then
|
||||
if kind == SimpKind.simpAll then
|
||||
throwError "'simp_all' tactic does not support 'discharger' option"
|
||||
@@ -270,7 +278,7 @@ def mkSimpContext (stx : Syntax) (eraseLocal : Bool) (kind := SimpKind.simp) (ig
|
||||
let simpTheorems ← if simpOnly then
|
||||
simpOnlyBuiltins.foldlM (·.addConst ·) ({} : SimpTheorems)
|
||||
else
|
||||
getSimpTheorems
|
||||
simpTheorems
|
||||
let simprocs ← if simpOnly then pure {} else Simp.getSimprocs
|
||||
let congrTheorems ← getSimpCongrTheorems
|
||||
let r ← elabSimpArgs stx[4] (eraseLocal := eraseLocal) (kind := kind) (simprocs := #[simprocs]) {
|
||||
|
||||
@@ -141,6 +141,15 @@ def hasExprMVar : LocalDecl → Bool
|
||||
| cdecl (type := t) .. => t.hasExprMVar
|
||||
| ldecl (type := t) (value := v) .. => t.hasExprMVar || v.hasExprMVar
|
||||
|
||||
/--
|
||||
Set the kind of a `LocalDecl`.
|
||||
-/
|
||||
def setKind : LocalDecl → LocalDeclKind → LocalDecl
|
||||
| cdecl index fvarId userName type bi _, kind =>
|
||||
cdecl index fvarId userName type bi kind
|
||||
| ldecl index fvarId userName type value nonDep _, kind =>
|
||||
ldecl index fvarId userName type value nonDep kind
|
||||
|
||||
end LocalDecl
|
||||
|
||||
/-- A LocalContext is an ordered set of local variable declarations.
|
||||
@@ -311,6 +320,13 @@ def renameUserName (lctx : LocalContext) (fromName : Name) (toName : Name) : Loc
|
||||
{ fvarIdToDecl := map.insert decl.fvarId decl
|
||||
decls := decls.set decl.index decl }
|
||||
|
||||
/--
|
||||
Set the kind of the given fvar.
|
||||
-/
|
||||
def setKind (lctx : LocalContext) (fvarId : FVarId)
|
||||
(kind : LocalDeclKind) : LocalContext :=
|
||||
lctx.modifyLocalDecl fvarId (·.setKind kind)
|
||||
|
||||
def setBinderInfo (lctx : LocalContext) (fvarId : FVarId) (bi : BinderInfo) : LocalContext :=
|
||||
modifyLocalDecl lctx fvarId fun decl => decl.setBinderInfo bi
|
||||
|
||||
@@ -451,6 +467,27 @@ def sanitizeNames (lctx : LocalContext) : StateM NameSanitizerState LocalContext
|
||||
modify fun s => s.insert decl.userName
|
||||
pure lctx
|
||||
|
||||
/--
|
||||
Given an `FVarId`, this function returns the corresponding user name,
|
||||
but only if the name can be used to recover the original FVarId.
|
||||
-/
|
||||
def getRoundtrippingUserName? (lctx : LocalContext) (fvarId : FVarId) : Option Name := do
|
||||
let ldecl₁ ← lctx.find? fvarId
|
||||
let ldecl₂ ← lctx.findFromUserName? ldecl₁.userName
|
||||
guard <| ldecl₁.fvarId == ldecl₂.fvarId
|
||||
some ldecl₁.userName
|
||||
|
||||
/--
|
||||
Sort the given `FVarId`s by the order in which they appear in `lctx`. If any of
|
||||
the `FVarId`s do not appear in `lctx`, the result is unspecified.
|
||||
-/
|
||||
def sortFVarsByContextOrder (lctx : LocalContext) (hyps : Array FVarId) : Array FVarId :=
|
||||
let hyps := hyps.map fun fvarId =>
|
||||
match lctx.fvarIdToDecl.find? fvarId with
|
||||
| none => (0, fvarId)
|
||||
| some ldecl => (ldecl.index, fvarId)
|
||||
hyps.qsort (fun h i => h.fst < i.fst) |>.map (·.snd)
|
||||
|
||||
end LocalContext
|
||||
|
||||
/-- Class used to denote that `m` has a local context. -/
|
||||
|
||||
@@ -43,3 +43,4 @@ import Lean.Meta.CasesOn
|
||||
import Lean.Meta.ExprLens
|
||||
import Lean.Meta.ExprTraverse
|
||||
import Lean.Meta.Eval
|
||||
import Lean.Meta.CoeAttr
|
||||
|
||||
86
src/Lean/Meta/CoeAttr.lean
Normal file
86
src/Lean/Meta/CoeAttr.lean
Normal file
@@ -0,0 +1,86 @@
|
||||
/-
|
||||
Copyright (c) 2022 Gabriel Ebner. All rights reserved.
|
||||
Released under Apache 2.0 license as described in the file LICENSE.
|
||||
Authors: Gabriel Ebner, Mario Carneiro, Leonardo de Moura
|
||||
-/
|
||||
import Lean.Attributes
|
||||
import Lean.ScopedEnvExtension
|
||||
import Lean.Meta.FunInfo
|
||||
|
||||
/-!
|
||||
# The `@[coe]` attribute, used to delaborate coercion functions as `↑`
|
||||
|
||||
When writing a coercion, if the pattern
|
||||
```
|
||||
@[coe]
|
||||
def A.toB (a : A) : B := sorry
|
||||
|
||||
instance : Coe A B where coe := A.toB
|
||||
```
|
||||
is used, then `A.toB a` will be pretty-printed as `↑a`.
|
||||
-/
|
||||
|
||||
namespace Lean.Meta
|
||||
|
||||
/-- The different types of coercions that are supported by the `coe` attribute. -/
|
||||
inductive CoeFnType
|
||||
/-- The basic coercion `↑x`, see `CoeT.coe` -/
|
||||
| coe
|
||||
/-- The coercion to a function type, see `CoeFun.coe` -/
|
||||
| coeFun
|
||||
/-- The coercion to a type, see `CoeSort.coe` -/
|
||||
| coeSort
|
||||
deriving Inhabited, Repr, DecidableEq
|
||||
|
||||
instance : ToExpr CoeFnType where
|
||||
toTypeExpr := mkConst ``CoeFnType
|
||||
toExpr := open CoeFnType in fun
|
||||
| coe => mkConst ``coe
|
||||
| coeFun => mkConst ``coeFun
|
||||
| coeSort => mkConst ``coeSort
|
||||
|
||||
/-- Information associated to a coercion function to enable sensible delaboration. -/
|
||||
structure CoeFnInfo where
|
||||
/-- The number of arguments to the coercion function -/
|
||||
numArgs : Nat
|
||||
/-- The argument index that represents the value being coerced -/
|
||||
coercee : Nat
|
||||
/-- The type of coercion -/
|
||||
type : CoeFnType
|
||||
deriving Inhabited, Repr
|
||||
|
||||
instance : ToExpr CoeFnInfo where
|
||||
toTypeExpr := mkConst ``CoeFnInfo
|
||||
toExpr | ⟨a, b, c⟩ => mkApp3 (mkConst ``CoeFnInfo.mk) (toExpr a) (toExpr b) (toExpr c)
|
||||
|
||||
/-- The environment extension for tracking coercion functions for delaboration -/
|
||||
-- TODO: does it need to be a scoped extension
|
||||
initialize coeExt : SimpleScopedEnvExtension (Name × CoeFnInfo) (NameMap CoeFnInfo) ←
|
||||
registerSimpleScopedEnvExtension {
|
||||
addEntry := fun st (n, i) => st.insert n i
|
||||
initial := {}
|
||||
}
|
||||
|
||||
/-- Lookup the coercion information for a given function -/
|
||||
def getCoeFnInfo? (fn : Name) : CoreM (Option CoeFnInfo) :=
|
||||
return (coeExt.getState (← getEnv)).find? fn
|
||||
|
||||
/-- Add `name` to the coercion extension and add a coercion delaborator for the function. -/
|
||||
def registerCoercion (name : Name) (info : Option CoeFnInfo := none) : MetaM Unit := do
|
||||
let info ← match info with | some info => pure info | none => do
|
||||
let fnInfo ← getFunInfo (← mkConstWithLevelParams name)
|
||||
let some coercee := fnInfo.paramInfo.findIdx? (·.binderInfo.isExplicit)
|
||||
| throwError "{name} has no explicit arguments"
|
||||
pure { numArgs := coercee + 1, coercee, type := .coe }
|
||||
modifyEnv fun env => coeExt.addEntry env (name, info)
|
||||
|
||||
builtin_initialize registerBuiltinAttribute {
|
||||
name := `coe
|
||||
descr := "Adds a definition as a coercion"
|
||||
add := fun decl _stx kind => MetaM.run' do
|
||||
unless kind == .global do
|
||||
throwError "cannot add local or scoped coe attribute"
|
||||
registerCoercion decl
|
||||
}
|
||||
|
||||
end Lean.Meta
|
||||
@@ -1,7 +1,7 @@
|
||||
/-
|
||||
Copyright (c) 2019 Microsoft Corporation. All rights reserved.
|
||||
Released under Apache 2.0 license as described in the file LICENSE.
|
||||
Authors: Leonardo de Moura
|
||||
Authors: Leonardo de Moura, Jannis Limperg, Scott Morrison
|
||||
-/
|
||||
import Lean.Meta.WHNF
|
||||
import Lean.Meta.Transform
|
||||
@@ -32,8 +32,8 @@ namespace Lean.Meta.DiscrTree
|
||||
and `Add.add Nat Nat.hasAdd a b` generates paths with the following keys
|
||||
respectively
|
||||
```
|
||||
⟨Add.add, 4⟩, *, *, *, *
|
||||
⟨Add.add, 4⟩, *, *, ⟨a,0⟩, ⟨b,0⟩
|
||||
⟨Add.add, 4⟩, α, *, *, *
|
||||
⟨Add.add, 4⟩, Nat, *, ⟨a,0⟩, ⟨b,0⟩
|
||||
```
|
||||
|
||||
That is, we don't reduce `Add.add Nat inst a b` into `Nat.add a b`.
|
||||
@@ -450,6 +450,18 @@ def insert [BEq α] (d : DiscrTree α) (e : Expr) (v : α) (config : WhnfCoreCon
|
||||
let keys ← mkPath e config
|
||||
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) : MetaM (DiscrTree α) := do
|
||||
let keys ← mkPath e config
|
||||
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
|
||||
unless root do
|
||||
@@ -676,4 +688,124 @@ where
|
||||
| .arrow => visitNonStar .other #[] (← visitNonStar k args (← visitStar result))
|
||||
| _ => visitNonStar k args (← visitStar result)
|
||||
|
||||
end Lean.Meta.DiscrTree
|
||||
namespace Trie
|
||||
|
||||
-- `Inhabited` instance to allow `partial` definitions below.
|
||||
private local instance [Monad m] : Inhabited (σ → β → m σ) := ⟨fun s _ => pure s⟩
|
||||
|
||||
/--
|
||||
Monadically fold the keys and values stored in a `Trie`.
|
||||
-/
|
||||
partial def foldM [Monad m] (initialKeys : Array Key)
|
||||
(f : σ → Array Key → α → m σ) : (init : σ) → Trie α → m σ
|
||||
| init, Trie.node vs children => do
|
||||
let s ← vs.foldlM (init := init) fun s v => f s initialKeys v
|
||||
children.foldlM (init := s) fun s (k, t) =>
|
||||
t.foldM (initialKeys.push k) f s
|
||||
|
||||
/--
|
||||
Fold the keys and values stored in a `Trie`.
|
||||
-/
|
||||
@[inline]
|
||||
def fold (initialKeys : Array Key) (f : σ → Array Key → α → σ) (init : σ) (t : Trie α) : σ :=
|
||||
Id.run <| t.foldM initialKeys (init := init) fun s k a => return f s k a
|
||||
|
||||
/--
|
||||
Monadically fold the values stored in a `Trie`.
|
||||
-/
|
||||
partial def foldValuesM [Monad m] (f : σ → α → m σ) : (init : σ) → Trie α → m σ
|
||||
| init, node vs children => do
|
||||
let s ← vs.foldlM (init := init) f
|
||||
children.foldlM (init := s) fun s (_, c) => c.foldValuesM (init := s) f
|
||||
|
||||
/--
|
||||
Fold the values stored in a `Trie`.
|
||||
-/
|
||||
@[inline]
|
||||
def foldValues (f : σ → α → σ) (init : σ) (t : Trie α) : σ :=
|
||||
Id.run <| t.foldValuesM (init := init) f
|
||||
|
||||
/--
|
||||
The number of values stored in a `Trie`.
|
||||
-/
|
||||
partial def size : Trie α → Nat
|
||||
| Trie.node vs children =>
|
||||
children.foldl (init := vs.size) fun n (_, c) => n + size c
|
||||
|
||||
end Trie
|
||||
|
||||
|
||||
/--
|
||||
Monadically fold over the keys and values stored in a `DiscrTree`.
|
||||
-/
|
||||
@[inline]
|
||||
def foldM [Monad m] (f : σ → Array Key → α → m σ) (init : σ)
|
||||
(t : DiscrTree α) : m σ :=
|
||||
t.root.foldlM (init := init) fun s k t => t.foldM #[k] (init := s) f
|
||||
|
||||
/--
|
||||
Fold over the keys and values stored in a `DiscrTree`
|
||||
-/
|
||||
@[inline]
|
||||
def fold (f : σ → Array Key → α → σ) (init : σ) (t : DiscrTree α) : σ :=
|
||||
Id.run <| t.foldM (init := init) fun s keys a => return f s keys a
|
||||
|
||||
/--
|
||||
Monadically fold over the values stored in a `DiscrTree`.
|
||||
-/
|
||||
@[inline]
|
||||
def foldValuesM [Monad m] (f : σ → α → m σ) (init : σ) (t : DiscrTree α) :
|
||||
m σ :=
|
||||
t.root.foldlM (init := init) fun s _ t => t.foldValuesM (init := s) f
|
||||
|
||||
/--
|
||||
Fold over the values stored in a `DiscrTree`.
|
||||
-/
|
||||
@[inline]
|
||||
def foldValues (f : σ → α → σ) (init : σ) (t : DiscrTree α) : σ :=
|
||||
Id.run <| t.foldValuesM (init := init) f
|
||||
|
||||
/--
|
||||
Check for the presence of a value satisfying a predicate.
|
||||
-/
|
||||
@[inline]
|
||||
def containsValueP [BEq α] (t : DiscrTree α) (f : α → Bool) : Bool :=
|
||||
t.foldValues (init := false) fun r a => r || f a
|
||||
|
||||
/--
|
||||
Extract the values stored in a `DiscrTree`.
|
||||
-/
|
||||
@[inline]
|
||||
def values (t : DiscrTree α) : Array α :=
|
||||
t.foldValues (init := #[]) fun as a => as.push a
|
||||
|
||||
/--
|
||||
Extract the keys and values stored in a `DiscrTree`.
|
||||
-/
|
||||
@[inline]
|
||||
def toArray (t : DiscrTree α) : Array (Array Key × α) :=
|
||||
t.fold (init := #[]) fun as keys a => as.push (keys, a)
|
||||
|
||||
/--
|
||||
Get the number of values stored in a `DiscrTree`. O(n) in the size of the tree.
|
||||
-/
|
||||
@[inline]
|
||||
def size (t : DiscrTree α) : Nat :=
|
||||
t.root.foldl (init := 0) fun n _ t => n + t.size
|
||||
|
||||
variable {m : Type → Type} [Monad m]
|
||||
|
||||
/-- Apply a monadic function to the array of values at each node in a `DiscrTree`. -/
|
||||
partial def Trie.mapArraysM (t : DiscrTree.Trie α) (f : Array α → m (Array β)) :
|
||||
m (DiscrTree.Trie β) :=
|
||||
match t with
|
||||
| .node vs children =>
|
||||
return .node (← f vs) (← children.mapM fun (k, t') => do pure (k, ← t'.mapArraysM f))
|
||||
|
||||
/-- Apply a monadic function to the array of values at each node in a `DiscrTree`. -/
|
||||
def mapArraysM (d : DiscrTree α) (f : Array α → m (Array β)) : m (DiscrTree β) := do
|
||||
pure { root := ← d.root.mapM (fun t => t.mapArraysM f) }
|
||||
|
||||
/-- Apply a function to the array of values at each node in a `DiscrTree`. -/
|
||||
def mapArrays (d : DiscrTree α) (f : Array α → Array β) : DiscrTree β :=
|
||||
Id.run <| d.mapArraysM fun A => pure (f A)
|
||||
|
||||
@@ -22,10 +22,12 @@ import Lean.Meta.Tactic.Simp
|
||||
import Lean.Meta.Tactic.AuxLemma
|
||||
import Lean.Meta.Tactic.SplitIf
|
||||
import Lean.Meta.Tactic.Split
|
||||
import Lean.Meta.Tactic.TryThis
|
||||
import Lean.Meta.Tactic.Cleanup
|
||||
import Lean.Meta.Tactic.Unfold
|
||||
import Lean.Meta.Tactic.Rename
|
||||
import Lean.Meta.Tactic.LinearArith
|
||||
import Lean.Meta.Tactic.AC
|
||||
import Lean.Meta.Tactic.Refl
|
||||
import Lean.Meta.Tactic.Congr
|
||||
import Lean.Meta.Tactic.Congr
|
||||
import Lean.Meta.Tactic.Repeat
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/-
|
||||
Copyright (c) 2020 Microsoft Corporation. All rights reserved.
|
||||
Released under Apache 2.0 license as described in the file LICENSE.
|
||||
Authors: Leonardo de Moura
|
||||
Authors: Leonardo de Moura, Siddhartha Gadgil
|
||||
-/
|
||||
import Lean.Util.FindMVar
|
||||
import Lean.Meta.SynthInstance
|
||||
@@ -230,4 +230,25 @@ def _root_.Lean.MVarId.exfalso (mvarId : MVarId) : MetaM MVarId :=
|
||||
mvarId.assign (mkApp2 (mkConst ``False.elim [u]) target mvarIdNew)
|
||||
return mvarIdNew.mvarId!
|
||||
|
||||
/--
|
||||
Apply the `n`-th constructor of the target type,
|
||||
checking that it is an inductive type,
|
||||
and that there are the expected number of constructors.
|
||||
-/
|
||||
def _root_.Lean.MVarId.nthConstructor
|
||||
(name : Name) (idx : Nat) (expected? : Option Nat := none) (goal : MVarId) :
|
||||
MetaM (List MVarId) := do
|
||||
goal.withContext do
|
||||
goal.checkNotAssigned name
|
||||
matchConstInduct (← goal.getType').getAppFn
|
||||
(fun _ => throwTacticEx name goal "target is not an inductive datatype")
|
||||
fun ival us => do
|
||||
if let some e := expected? then unless ival.ctors.length == e do
|
||||
throwTacticEx name goal
|
||||
s!"{name} tactic works for inductive types with exactly {e} constructors"
|
||||
if h : idx < ival.ctors.length then
|
||||
goal.apply <| mkConst ival.ctors[idx] us
|
||||
else
|
||||
throwTacticEx name goal s!"index {idx} out of bounds, only {ival.ctors.length} constructors"
|
||||
|
||||
end Lean.Meta
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/-
|
||||
Copyright (c) 2020 Microsoft Corporation. All rights reserved.
|
||||
Released under Apache 2.0 license as described in the file LICENSE.
|
||||
Authors: Leonardo de Moura
|
||||
Authors: Leonardo de Moura, Mario Carneiro
|
||||
-/
|
||||
import Lean.Data.AssocList
|
||||
import Lean.Expr
|
||||
@@ -63,6 +63,13 @@ def domain (s : FVarSubst) : List FVarId :=
|
||||
def any (p : FVarId → Expr → Bool) (s : FVarSubst) : Bool :=
|
||||
s.map.any p
|
||||
|
||||
/--
|
||||
Constructs a substitution consisting of `s` followed by `t`.
|
||||
This satisfies `(s.append t).apply e = t.apply (s.apply e)`
|
||||
-/
|
||||
def append (s t : FVarSubst) : FVarSubst :=
|
||||
s.1.foldl (fun s' k v => s'.insert k (t.apply v)) t
|
||||
|
||||
end FVarSubst
|
||||
end Meta
|
||||
|
||||
|
||||
64
src/Lean/Meta/Tactic/Repeat.lean
Normal file
64
src/Lean/Meta/Tactic/Repeat.lean
Normal file
@@ -0,0 +1,64 @@
|
||||
/-
|
||||
Copyright (c) 2022 Mario Carneiro. All rights reserved.
|
||||
Released under Apache 2.0 license as described in the file LICENSE.
|
||||
Authors: Mario Carneiro, Scott Morrison
|
||||
-/
|
||||
import Lean.Meta.Basic
|
||||
|
||||
namespace Lean.Meta
|
||||
/--
|
||||
Implementation of `repeat'` and `repeat1'`.
|
||||
|
||||
`repeat'Core f` runs `f` on all of the goals to produce a new list of goals,
|
||||
then runs `f` again on all of those goals, and repeats until `f` fails on all remaining goals,
|
||||
or until `maxIters` total calls to `f` have occurred.
|
||||
|
||||
Returns a boolean indicating whether `f` succeeded at least once, and
|
||||
all the remaining goals (i.e. those on which `f` failed).
|
||||
-/
|
||||
def repeat'Core [Monad m] [MonadExcept ε m] [MonadBacktrack s m] [MonadMCtx m]
|
||||
(f : MVarId → m (List MVarId)) (goals : List MVarId) (maxIters := 100000) :
|
||||
m (Bool × List MVarId) := do
|
||||
let (progress, acc) ← go maxIters false goals [] #[]
|
||||
pure (progress, (← acc.filterM fun g => not <$> g.isAssigned).toList)
|
||||
where
|
||||
/-- Auxiliary for `repeat'Core`. `repeat'Core.go f maxIters progress goals stk acc` evaluates to
|
||||
essentially `acc.toList ++ repeat' f (goals::stk).join maxIters`: that is, `acc` are goals we will
|
||||
not revisit, and `(goals::stk).join` is the accumulated todo list of subgoals. -/
|
||||
go : Nat → Bool → List MVarId → List (List MVarId) → Array MVarId → m (Bool × Array MVarId)
|
||||
| _, p, [], [], acc => pure (p, acc)
|
||||
| n, p, [], goals::stk, acc => go n p goals stk acc
|
||||
| n, p, g::goals, stk, acc => do
|
||||
if ← g.isAssigned then
|
||||
go n p goals stk acc
|
||||
else
|
||||
match n with
|
||||
| 0 => pure <| (p, acc.push g ++ goals |> stk.foldl .appendList)
|
||||
| n+1 =>
|
||||
match ← observing? (f g) with
|
||||
| some goals' => go n true goals' (goals::stk) acc
|
||||
| none => go n p goals stk (acc.push g)
|
||||
termination_by n p goals stk _ => (n, stk, goals)
|
||||
|
||||
/--
|
||||
`repeat' f` runs `f` on all of the goals to produce a new list of goals,
|
||||
then runs `f` again on all of those goals, and repeats until `f` fails on all remaining goals,
|
||||
or until `maxIters` total calls to `f` have occurred.
|
||||
Always succeeds (returning the original goals if `f` fails on all of them).
|
||||
-/
|
||||
def repeat' [Monad m] [MonadExcept ε m] [MonadBacktrack s m] [MonadMCtx m]
|
||||
(f : MVarId → m (List MVarId)) (goals : List MVarId) (maxIters := 100000) : m (List MVarId) :=
|
||||
repeat'Core f goals maxIters <&> (·.2)
|
||||
|
||||
/--
|
||||
`repeat1' f` runs `f` on all of the goals to produce a new list of goals,
|
||||
then runs `f` again on all of those goals, and repeats until `f` fails on all remaining goals,
|
||||
or until `maxIters` total calls to `f` have occurred.
|
||||
Fails if `f` does not succeed at least once.
|
||||
-/
|
||||
def repeat1' [Monad m] [MonadError m] [MonadExcept ε m] [MonadBacktrack s m] [MonadMCtx m]
|
||||
(f : MVarId → m (List MVarId)) (goals : List MVarId) (maxIters := 100000) : m (List MVarId) := do
|
||||
let (.true, goals) ← repeat'Core f goals maxIters | throwError "repeat1' made no progress"
|
||||
pure goals
|
||||
|
||||
end Lean.Meta
|
||||
@@ -4,6 +4,7 @@ Released under Apache 2.0 license as described in the file LICENSE.
|
||||
Authors: Leonardo de Moura
|
||||
-/
|
||||
import Lean.Util.ForEachExpr
|
||||
import Lean.Elab.InfoTree.Main
|
||||
import Lean.Meta.AppBuilder
|
||||
import Lean.Meta.MatchUtil
|
||||
import Lean.Meta.Tactic.Util
|
||||
@@ -139,29 +140,54 @@ def _root_.Lean.MVarId.change (mvarId : MVarId) (targetNew : Expr) (checkDefEq :
|
||||
def change (mvarId : MVarId) (targetNew : Expr) (checkDefEq := true) : MetaM MVarId := mvarId.withContext do
|
||||
mvarId.change targetNew checkDefEq
|
||||
|
||||
/--
|
||||
Replace the type of the free variable `fvarId` with `typeNew`.
|
||||
If `checkDefEq = false`, this method assumes that `typeNew` is definitionally equal to `fvarId` type.
|
||||
If `checkDefEq = true`, throw an error if `typeNew` is not definitionally equal to `fvarId` type.
|
||||
-/
|
||||
def _root_.Lean.MVarId.changeLocalDecl (mvarId : MVarId) (fvarId : FVarId) (typeNew : Expr) (checkDefEq := true) : MetaM MVarId := do
|
||||
mvarId.checkNotAssigned `changeLocalDecl
|
||||
let (xs, mvarId) ← mvarId.revert #[fvarId] true
|
||||
/-- Runs the continuation `k` after temporarily reverting some variables from the local context of a metavariable (identified by `mvarId`), then reintroduces local variables as specified by `k`.
|
||||
|
||||
The argument `fvarIds` is an array of `fvarIds` to revert in the order specified. An error is thrown if they cannot be reverted in order.
|
||||
|
||||
Once the local variables have been reverted, `k` is passed `mvarId` along with an array of local variables that were reverted. This array always has `fvarIds` as a prefix, but it may contain additional variables that were reverted due to dependencies. `k` returns a value, a goal, an array of _link variables_.
|
||||
|
||||
Once `k` has completed, one variable is introduced for each link variable returned by `k`. If the returned variable is `none`, the variable is just introduced. If it is `some fv`, the variable is introduced and then linked as an alias of `fv` in the info tree. For example, having `k` return `fvars.map .some` as the link variables causes all reverted variables to be introduced and linked.
|
||||
|
||||
Returns the value returned by `k` along with the resulting goal.
|
||||
-/
|
||||
def _root_.Lean.MVarId.withReverted (mvarId : MVarId) (fvarIds : Array FVarId)
|
||||
(k : MVarId → Array FVarId → MetaM (α × Array (Option FVarId) × MVarId))
|
||||
(clearAuxDeclsInsteadOfRevert := false) : MetaM (α × MVarId) := do
|
||||
let (xs, mvarId) ← mvarId.revert fvarIds true clearAuxDeclsInsteadOfRevert
|
||||
let (r, xs', mvarId) ← k mvarId xs
|
||||
let (ys, mvarId) ← mvarId.introNP xs'.size
|
||||
mvarId.withContext do
|
||||
let numReverted := xs.size
|
||||
let target ← mvarId.getType
|
||||
for x? in xs', y in ys do
|
||||
if let some x := x? then
|
||||
Elab.pushInfoLeaf (.ofFVarAliasInfo { id := y, baseId := x, userName := ← y.getUserName })
|
||||
return (r, mvarId)
|
||||
|
||||
/--
|
||||
Replaces the type of the free variable `fvarId` with `typeNew`.
|
||||
|
||||
If `checkDefEq` is `true` then an error is thrown if `typeNew` is not definitionally
|
||||
equal to the type of `fvarId`. Otherwise this function assumes `typeNew` and the type
|
||||
of `fvarId` are definitionally equal.
|
||||
|
||||
This function is the same as `Lean.MVarId.changeLocalDecl` but makes sure to push substitution
|
||||
information into the info tree.
|
||||
-/
|
||||
def _root_.Lean.MVarId.changeLocalDecl (mvarId : MVarId) (fvarId : FVarId) (typeNew : Expr)
|
||||
(checkDefEq := true) : MetaM MVarId := do
|
||||
mvarId.checkNotAssigned `changeLocalDecl
|
||||
let (_, mvarId) ← mvarId.withReverted #[fvarId] fun mvarId fvars => mvarId.withContext do
|
||||
let check (typeOld : Expr) : MetaM Unit := do
|
||||
if checkDefEq then
|
||||
unless (← isDefEq typeNew typeOld) do
|
||||
throwTacticEx `changeHypothesis mvarId m!"given type{indentExpr typeNew}\nis not definitionally equal to{indentExpr typeOld}"
|
||||
let finalize (targetNew : Expr) : MetaM MVarId := do
|
||||
let mvarId ← mvarId.replaceTargetDefEq targetNew
|
||||
let (_, mvarId) ← mvarId.introNP numReverted
|
||||
pure mvarId
|
||||
match target with
|
||||
| .forallE n d b c => do check d; finalize (mkForall n c typeNew b)
|
||||
| .letE n t v b _ => do check t; finalize (mkLet n typeNew v b)
|
||||
| _ => throwTacticEx `changeHypothesis mvarId "unexpected auxiliary target"
|
||||
unless ← isDefEq typeNew typeOld do
|
||||
throwTacticEx `changeLocalDecl mvarId
|
||||
m!"given type{indentExpr typeNew}\nis not definitionally equal to{indentExpr typeOld}"
|
||||
let finalize (targetNew : Expr) := do
|
||||
return ((), fvars.map .some, ← mvarId.replaceTargetDefEq targetNew)
|
||||
match ← mvarId.getType with
|
||||
| .forallE n d b bi => do check d; finalize (.forallE n typeNew b bi)
|
||||
| .letE n t v b ndep => do check t; finalize (.letE n typeNew v b ndep)
|
||||
| _ => throwTacticEx `changeLocalDecl mvarId "unexpected auxiliary target"
|
||||
return mvarId
|
||||
|
||||
@[deprecated MVarId.changeLocalDecl]
|
||||
def changeLocalDecl (mvarId : MVarId) (fvarId : FVarId) (typeNew : Expr) (checkDefEq := true) : MetaM MVarId := do
|
||||
|
||||
@@ -12,9 +12,13 @@ import Lean.Meta.Tactic.Simp.SimpCongrTheorems
|
||||
namespace Lean.Meta
|
||||
namespace Simp
|
||||
|
||||
/-- The result of simplifying some expression `e`. -/
|
||||
structure Result where
|
||||
/-- The simplified version of `e` -/
|
||||
expr : Expr
|
||||
proof? : Option Expr := none -- If none, proof is assumed to be `refl`
|
||||
/-- A proof that `$e = $expr`, where the simplified expression is on the RHS.
|
||||
If `none`, the proof is assumed to be `refl`. -/
|
||||
proof? : Option Expr := none
|
||||
/-- Save the field `dischargeDepth` at `Simp.Context` because it impacts the simplifier result. -/
|
||||
dischargeDepth : UInt32 := 0
|
||||
/-- If `cache := true` the result is cached. -/
|
||||
|
||||
585
src/Lean/Meta/Tactic/TryThis.lean
Normal file
585
src/Lean/Meta/Tactic/TryThis.lean
Normal file
@@ -0,0 +1,585 @@
|
||||
/-
|
||||
Copyright (c) 2021 Gabriel Ebner. All rights reserved.
|
||||
Released under Apache 2.0 license as described in the file LICENSE.
|
||||
Authors: Gabriel Ebner, Mario Carneiro, Thomas Murrills
|
||||
-/
|
||||
import Lean.Server.CodeActions
|
||||
import Lean.Widget.UserWidget
|
||||
import Lean.Data.Json.Elab
|
||||
|
||||
/-- Gets the LSP range from a `String.Range`. -/
|
||||
def Lean.FileMap.utf8RangeToLspRange (text : FileMap) (range : String.Range) : Lsp.Range :=
|
||||
{ start := text.utf8PosToLspPos range.start, «end» := text.utf8PosToLspPos range.stop }
|
||||
|
||||
|
||||
/-!
|
||||
# "Try this" support
|
||||
|
||||
This implements a mechanism for tactics to print a message saying `Try this: <suggestion>`,
|
||||
where `<suggestion>` is a link to a replacement tactic. Users can either click on the link
|
||||
in the suggestion (provided by a widget), or use a code action which applies the suggestion.
|
||||
-/
|
||||
namespace Lean.Meta.Tactic.TryThis
|
||||
|
||||
open Lean Elab PrettyPrinter Meta Server RequestM
|
||||
|
||||
/-! # Raw widget -/
|
||||
|
||||
/--
|
||||
This is a widget which is placed by `TryThis.addSuggestion` and `TryThis.addSuggestions`.
|
||||
|
||||
When placed by `addSuggestion`, it says `Try this: <replacement>`
|
||||
where `<replacement>` is a link which will perform the replacement.
|
||||
|
||||
When placed by `addSuggestions`, it says:
|
||||
```
|
||||
Try these:
|
||||
```
|
||||
* `<replacement1>`
|
||||
* `<replacement2>`
|
||||
* `<replacement3>`
|
||||
* ...
|
||||
|
||||
where `<replacement*>` is a link which will perform the replacement.
|
||||
-/
|
||||
@[widget_module] def tryThisWidget : Widget.Module where
|
||||
javascript := "
|
||||
import * as React from 'react';
|
||||
import { EditorContext } from '@leanprover/infoview';
|
||||
const e = React.createElement;
|
||||
export default function ({ pos, suggestions, range, header, isInline, style }) {
|
||||
const editorConnection = React.useContext(EditorContext)
|
||||
const defStyle = style || {
|
||||
className: 'link pointer dim',
|
||||
style: { color: 'var(--vscode-textLink-foreground)' }
|
||||
}
|
||||
|
||||
// Construct the children of the HTML element for a given suggestion.
|
||||
function makeSuggestion({ suggestion, preInfo, postInfo, style }) {
|
||||
function onClick() {
|
||||
editorConnection.api.applyEdit({
|
||||
changes: { [pos.uri]: [{ range, newText: suggestion }] }
|
||||
})
|
||||
}
|
||||
return [
|
||||
preInfo,
|
||||
e('span', { onClick, title: 'Apply suggestion', ...style || defStyle }, suggestion),
|
||||
postInfo
|
||||
]
|
||||
}
|
||||
|
||||
// Choose between an inline 'Try this'-like display and a list-based 'Try these'-like display.
|
||||
let inner = null
|
||||
if (isInline) {
|
||||
inner = e('div', { className: 'ml1' },
|
||||
e('pre', { className: 'font-code pre-wrap' }, header, makeSuggestion(suggestions[0])))
|
||||
} else {
|
||||
inner = e('div', { className: 'ml1' },
|
||||
e('pre', { className: 'font-code pre-wrap' }, header),
|
||||
e('ul', { style: { paddingInlineStart: '20px' } }, suggestions.map(s =>
|
||||
e('li', { className: 'font-code pre-wrap' }, makeSuggestion(s)))))
|
||||
}
|
||||
return e('details', { open: true },
|
||||
e('summary', { className: 'mv2 pointer' }, 'Suggestions'),
|
||||
inner)
|
||||
}"
|
||||
|
||||
/-! # Code action -/
|
||||
|
||||
/-- A packet of information about a "Try this" suggestion
|
||||
that we store in the infotree for the associated code action to retrieve. -/
|
||||
structure TryThisInfo : Type where
|
||||
/-- The textual range to be replaced by one of the suggestions. -/
|
||||
range : Lsp.Range
|
||||
/--
|
||||
A list of suggestions for the user to choose from.
|
||||
Each suggestion may optionally come with an override for the code action title.
|
||||
-/
|
||||
suggestionTexts : Array (String × Option String)
|
||||
/-- The prefix to display before the code action for a "Try this" suggestion if no custom code
|
||||
action title is provided. If not provided, `"Try this: "` is used. -/
|
||||
codeActionPrefix? : Option String
|
||||
deriving TypeName
|
||||
|
||||
/--
|
||||
This is a code action provider that looks for `TryThisInfo` nodes and supplies a code action to
|
||||
apply the replacement.
|
||||
-/
|
||||
@[code_action_provider] def tryThisProvider : CodeActionProvider := fun params snap => do
|
||||
let doc ← readDoc
|
||||
pure <| snap.infoTree.foldInfo (init := #[]) fun _ctx info result => Id.run do
|
||||
let .ofCustomInfo { stx, value } := info | result
|
||||
let some { range, suggestionTexts, codeActionPrefix? } :=
|
||||
value.get? TryThisInfo | result
|
||||
let some stxRange := stx.getRange? | result
|
||||
let stxRange := doc.meta.text.utf8RangeToLspRange stxRange
|
||||
unless stxRange.start.line ≤ params.range.end.line do return result
|
||||
unless params.range.start.line ≤ stxRange.end.line do return result
|
||||
let mut result := result
|
||||
for h : i in [:suggestionTexts.size] do
|
||||
let (newText, title?) := suggestionTexts[i]'h.2
|
||||
let title := title?.getD <| (codeActionPrefix?.getD "Try this: ") ++ newText
|
||||
result := result.push {
|
||||
eager.title := title
|
||||
eager.kind? := "quickfix"
|
||||
-- Only make the first option preferred
|
||||
eager.isPreferred? := if i = 0 then true else none
|
||||
eager.edit? := some <| .ofTextEdit doc.versionedIdentifier { range, newText }
|
||||
}
|
||||
result
|
||||
|
||||
/-! # Formatting -/
|
||||
|
||||
/-- Yields `(indent, column)` given a `FileMap` and a `String.Range`, where `indent` is the number
|
||||
of spaces by which the line that first includes `range` is initially indented, and `column` is the
|
||||
column `range` starts at in that line. -/
|
||||
def getIndentAndColumn (map : FileMap) (range : String.Range) : Nat × Nat :=
|
||||
let start := map.source.findLineStart range.start
|
||||
let body := map.source.findAux (· ≠ ' ') range.start start
|
||||
((body - start).1, (range.start - start).1)
|
||||
|
||||
/-- Replace subexpressions like `?m.1234` with `?_` so it can be copy-pasted. -/
|
||||
partial def replaceMVarsByUnderscores [Monad m] [MonadQuotation m]
|
||||
(s : Syntax) : m Syntax :=
|
||||
s.replaceM fun s => do
|
||||
let `(?$id:ident) := s | pure none
|
||||
if id.getId.hasNum || id.getId.isInternal then `(?_) else pure none
|
||||
|
||||
/-- Delaborate `e` into syntax suitable for use by `refine`. -/
|
||||
def delabToRefinableSyntax (e : Expr) : MetaM Term :=
|
||||
return ⟨← replaceMVarsByUnderscores (← delab e)⟩
|
||||
|
||||
/--
|
||||
An option allowing the user to customize the ideal input width. Defaults to 100.
|
||||
This option controls output format when
|
||||
the output is intended to be copied back into a lean file -/
|
||||
register_option format.inputWidth : Nat := {
|
||||
/- The default maximum width of an ideal line in source code. -/
|
||||
defValue := 100
|
||||
descr := "ideal input width"
|
||||
}
|
||||
|
||||
/-- Get the input width specified in the options -/
|
||||
def getInputWidth (o : Options) : Nat := format.inputWidth.get o
|
||||
|
||||
/-! # `Suggestion` data -/
|
||||
|
||||
-- TODO: we could also support `Syntax` and `Format`
|
||||
/-- Text to be used as a suggested replacement in the infoview. This can be either a `TSyntax kind`
|
||||
for a single `kind : SyntaxNodeKind` or a raw `String`.
|
||||
|
||||
Instead of using constructors directly, there are coercions available from these types to
|
||||
`SuggestionText`. -/
|
||||
inductive SuggestionText where
|
||||
/-- `TSyntax kind` used as suggested replacement text in the infoview. Note that while `TSyntax`
|
||||
is in general parameterized by a list of `SyntaxNodeKind`s, we only allow one here; this
|
||||
unambiguously guides pretty-printing. -/
|
||||
| tsyntax {kind : SyntaxNodeKind} : TSyntax kind → SuggestionText
|
||||
/-- A raw string to be used as suggested replacement text in the infoview. -/
|
||||
| string : String → SuggestionText
|
||||
deriving Inhabited
|
||||
|
||||
instance : ToMessageData SuggestionText where
|
||||
toMessageData
|
||||
| .tsyntax stx => stx
|
||||
| .string s => s
|
||||
|
||||
instance {kind : SyntaxNodeKind} : CoeHead (TSyntax kind) SuggestionText where
|
||||
coe := .tsyntax
|
||||
|
||||
instance : Coe String SuggestionText where
|
||||
coe := .string
|
||||
|
||||
namespace SuggestionText
|
||||
|
||||
/-- Pretty-prints a `SuggestionText` as a `Format`. If the `SuggestionText` is some `TSyntax kind`,
|
||||
we use the appropriate pretty-printer; strings are coerced to `Format`s as-is. -/
|
||||
def pretty : SuggestionText → CoreM Format
|
||||
| .tsyntax (kind := kind) stx => ppCategory kind stx
|
||||
| .string text => return text
|
||||
|
||||
/- Note that this is essentially `return (← s.pretty).prettyExtra w indent column`, but we
|
||||
special-case strings to avoid converting them to `Format`s and back, which adds indentation after each newline. -/
|
||||
/-- Pretty-prints a `SuggestionText` as a `String` and wraps with respect to the pane width,
|
||||
indentation, and column, via `Format.prettyExtra`. If `w := none`, then
|
||||
`w := getInputWidth (← getOptions)` is used. Raw `String`s are returned as-is. -/
|
||||
def prettyExtra (s : SuggestionText) (w : Option Nat := none)
|
||||
(indent column : Nat := 0) : CoreM String :=
|
||||
match s with
|
||||
| .tsyntax (kind := kind) stx => do
|
||||
let w ← match w with | none => do pure <| getInputWidth (← getOptions) | some n => pure n
|
||||
return (← ppCategory kind stx).pretty w indent column
|
||||
| .string text => return text
|
||||
|
||||
end SuggestionText
|
||||
|
||||
/--
|
||||
Style hooks for `Suggestion`s. See `SuggestionStyle.error`, `.warning`, `.success`, `.value`,
|
||||
and other definitions here for style presets. This is an arbitrary `Json` object, with the following
|
||||
interesting fields:
|
||||
* `title`: the hover text in the suggestion link
|
||||
* `className`: the CSS classes applied to the link
|
||||
* `style`: A `Json` object with additional inline CSS styles such as `color` or `textDecoration`.
|
||||
-/
|
||||
def SuggestionStyle := Json deriving Inhabited, ToJson
|
||||
|
||||
/-- Style as an error. By default, decorates the text with an undersquiggle; providing the argument
|
||||
`decorated := false` turns this off. -/
|
||||
def SuggestionStyle.error (decorated := true) : SuggestionStyle :=
|
||||
let style := if decorated then
|
||||
json% {
|
||||
-- The VS code error foreground theme color (`--vscode-errorForeground`).
|
||||
color: "var(--vscode-errorForeground)",
|
||||
textDecoration: "underline wavy var(--vscode-editorError-foreground) 1pt"
|
||||
}
|
||||
else json% { color: "var(--vscode-errorForeground)" }
|
||||
json% { className: "pointer dim", style: $style }
|
||||
|
||||
/-- Style as a warning. By default, decorates the text with an undersquiggle; providing the
|
||||
argument `decorated := false` turns this off. -/
|
||||
def SuggestionStyle.warning (decorated := true) : SuggestionStyle :=
|
||||
if decorated then
|
||||
json% {
|
||||
-- The `.gold` CSS class, which the infoview uses when e.g. building a file.
|
||||
className: "gold pointer dim",
|
||||
style: { textDecoration: "underline wavy var(--vscode-editorWarning-foreground) 1pt" }
|
||||
}
|
||||
else json% { className: "gold pointer dim" }
|
||||
|
||||
/-- Style as a success. -/
|
||||
def SuggestionStyle.success : SuggestionStyle :=
|
||||
-- The `.information` CSS class, which the infoview uses on successes.
|
||||
json% { className: "information pointer dim" }
|
||||
|
||||
/-- Style the same way as a hypothesis appearing in the infoview. -/
|
||||
def SuggestionStyle.asHypothesis : SuggestionStyle :=
|
||||
json% { className: "goal-hyp pointer dim" }
|
||||
|
||||
/-- Style the same way as an inaccessible hypothesis appearing in the infoview. -/
|
||||
def SuggestionStyle.asInaccessible : SuggestionStyle :=
|
||||
json% { className: "goal-inaccessible pointer dim" }
|
||||
|
||||
/-- Draws the color from a red-yellow-green color gradient with red at `0.0`, yellow at `0.5`, and
|
||||
green at `1.0`. Values outside the range `[0.0, 1.0]` are clipped to lie within this range.
|
||||
|
||||
With `showValueInHoverText := true` (the default), the value `t` will be included in the `title` of
|
||||
the HTML element (which appears on hover). -/
|
||||
def SuggestionStyle.value (t : Float) (showValueInHoverText := true) : SuggestionStyle :=
|
||||
let t := min (max t 0) 1
|
||||
json% {
|
||||
className: "pointer dim",
|
||||
-- interpolates linearly from 0º to 120º with 95% saturation and lightness
|
||||
-- varying around 50% in HSL space
|
||||
style: { color: $(s!"hsl({(t * 120).round} 95% {60 * ((t - 0.5)^2 + 0.75)}%)") },
|
||||
title: $(if showValueInHoverText then s!"Apply suggestion ({t})" else "Apply suggestion")
|
||||
}
|
||||
|
||||
/-- Holds a `suggestion` for replacement, along with `preInfo` and `postInfo` strings to be printed
|
||||
immediately before and after that suggestion, respectively. It also includes an optional
|
||||
`MessageData` to represent the suggestion in logs; by default, this is `none`, and `suggestion` is
|
||||
used. -/
|
||||
structure Suggestion where
|
||||
/-- Text to be used as a replacement via a code action. -/
|
||||
suggestion : SuggestionText
|
||||
/-- Optional info to be printed immediately before replacement text in a widget. -/
|
||||
preInfo? : Option String := none
|
||||
/-- Optional info to be printed immediately after replacement text in a widget. -/
|
||||
postInfo? : Option String := none
|
||||
/-- Optional style specification for the suggestion. If `none` (the default), the suggestion is
|
||||
styled as a text link. Otherwise, the suggestion can be styled as:
|
||||
* a status: `.error`, `.warning`, `.success`
|
||||
* a hypothesis name: `.asHypothesis`, `.asInaccessible`
|
||||
* a variable color: `.value (t : Float)`, which draws from a red-yellow-green gradient, with red
|
||||
at `0.0` and green at `1.0`.
|
||||
|
||||
See `SuggestionStyle` for details. -/
|
||||
style? : Option SuggestionStyle := none
|
||||
/-- How to represent the suggestion as `MessageData`. This is used only in the info diagnostic.
|
||||
If `none`, we use `suggestion`. Use `toMessageData` to render a `Suggestion` in this manner. -/
|
||||
messageData? : Option MessageData := none
|
||||
/-- How to construct the text that appears in the lightbulb menu from the suggestion text. If
|
||||
`none`, we use `fun ppSuggestionText => "Try this: " ++ ppSuggestionText`. Only the pretty-printed
|
||||
`suggestion : SuggestionText` is used here. -/
|
||||
toCodeActionTitle? : Option (String → String) := none
|
||||
deriving Inhabited
|
||||
|
||||
/-- Converts a `Suggestion` to `Json` in `CoreM`. We need `CoreM` in order to pretty-print syntax.
|
||||
|
||||
This also returns a `String × Option String` consisting of the pretty-printed text and any custom
|
||||
code action title if `toCodeActionTitle?` is provided.
|
||||
|
||||
If `w := none`, then `w := getInputWidth (← getOptions)` is used.
|
||||
-/
|
||||
def Suggestion.toJsonAndInfoM (s : Suggestion) (w : Option Nat := none) (indent column : Nat := 0) :
|
||||
CoreM (Json × String × Option String) := do
|
||||
let text ← s.suggestion.prettyExtra w indent column
|
||||
let mut json := [("suggestion", (text : Json))]
|
||||
if let some preInfo := s.preInfo? then json := ("preInfo", preInfo) :: json
|
||||
if let some postInfo := s.postInfo? then json := ("postInfo", postInfo) :: json
|
||||
if let some style := s.style? then json := ("style", toJson style) :: json
|
||||
return (Json.mkObj json, text, s.toCodeActionTitle?.map (· text))
|
||||
|
||||
/- If `messageData?` is specified, we use that; otherwise (by default), we use `toMessageData` of
|
||||
the suggestion text. -/
|
||||
instance : ToMessageData Suggestion where
|
||||
toMessageData s := s.messageData?.getD (toMessageData s.suggestion)
|
||||
|
||||
instance : Coe SuggestionText Suggestion where
|
||||
coe t := { suggestion := t }
|
||||
|
||||
/-- Delaborate `e` into a suggestion suitable for use by `refine`. -/
|
||||
def delabToRefinableSuggestion (e : Expr) : MetaM Suggestion :=
|
||||
return { suggestion := ← delabToRefinableSyntax e, messageData? := e }
|
||||
|
||||
/-! # Widget hooks -/
|
||||
|
||||
/-- Core of `addSuggestion` and `addSuggestions`. Whether we use an inline display for a single
|
||||
element or a list display is controlled by `isInline`. -/
|
||||
private def addSuggestionCore (ref : Syntax) (suggestions : Array Suggestion)
|
||||
(header : String) (isInline : Bool) (origSpan? : Option Syntax := none)
|
||||
(style? : Option SuggestionStyle := none)
|
||||
(codeActionPrefix? : Option String := none) : CoreM Unit := do
|
||||
if let some range := (origSpan?.getD ref).getRange? then
|
||||
let map ← getFileMap
|
||||
-- FIXME: this produces incorrect results when `by` is at the beginning of the line, i.e.
|
||||
-- replacing `tac` in `by tac`, because the next line will only be 2 space indented
|
||||
-- (less than `tac` which starts at column 3)
|
||||
let (indent, column) := getIndentAndColumn map range
|
||||
let suggestions ← suggestions.mapM (·.toJsonAndInfoM (indent := indent) (column := column))
|
||||
let suggestionTexts := suggestions.map (·.2)
|
||||
let suggestions := suggestions.map (·.1)
|
||||
let ref := Syntax.ofRange <| ref.getRange?.getD range
|
||||
let range := map.utf8RangeToLspRange range
|
||||
pushInfoLeaf <| .ofCustomInfo {
|
||||
stx := ref
|
||||
value := Dynamic.mk
|
||||
{ range, suggestionTexts, codeActionPrefix? : TryThisInfo }
|
||||
}
|
||||
Widget.savePanelWidgetInfo (hash tryThisWidget.javascript) ref
|
||||
(props := return json% {
|
||||
suggestions: $suggestions,
|
||||
range: $range,
|
||||
header: $header,
|
||||
isInline: $isInline,
|
||||
style: $style?
|
||||
})
|
||||
|
||||
/-- Add a "try this" suggestion. This has three effects:
|
||||
|
||||
* An info diagnostic is displayed saying `Try this: <suggestion>`
|
||||
* A widget is registered, saying `Try this: <suggestion>` with a link on `<suggestion>` to apply
|
||||
the suggestion
|
||||
* A code action is added, which will apply the suggestion.
|
||||
|
||||
The parameters are:
|
||||
* `ref`: the span of the info diagnostic
|
||||
* `s`: a `Suggestion`, which contains
|
||||
* `suggestion`: the replacement text;
|
||||
* `preInfo?`: an optional string shown immediately after the replacement text in the widget
|
||||
message (only)
|
||||
* `postInfo?`: an optional string shown immediately after the replacement text in the widget
|
||||
message (only)
|
||||
* `style?`: an optional `Json` object used as the value of the `style` attribute of the
|
||||
suggestion text's element (not the whole suggestion element).
|
||||
* `messageData?`: an optional message to display in place of `suggestion` in the info diagnostic
|
||||
(only). The widget message uses only `suggestion`. If `messageData?` is `none`, we simply use
|
||||
`suggestion` instead.
|
||||
* `toCodeActionTitle?`: an optional function `String → String` describing how to transform the
|
||||
pretty-printed suggestion text into the code action text which appears in the lightbulb menu.
|
||||
If `none`, we simply prepend `"Try This: "` to the suggestion text.
|
||||
* `origSpan?`: a syntax object whose span is the actual text to be replaced by `suggestion`.
|
||||
If not provided it defaults to `ref`.
|
||||
* `header`: a string that begins the display. By default, it is `"Try this: "`.
|
||||
* `codeActionPrefix?`: an optional string to be used as the prefix of the replacement text if the
|
||||
suggestion does not have a custom `toCodeActionTitle?`. If not provided, `"Try this: "` is used.
|
||||
-/
|
||||
def addSuggestion (ref : Syntax) (s : Suggestion) (origSpan? : Option Syntax := none)
|
||||
(header : String := "Try this: ") (codeActionPrefix? : Option String := none) : MetaM Unit := do
|
||||
logInfoAt ref m!"{header}{s}"
|
||||
addSuggestionCore ref #[s] header (isInline := true) origSpan?
|
||||
(codeActionPrefix? := codeActionPrefix?)
|
||||
|
||||
/-- Add a list of "try this" suggestions as a single "try these" suggestion. This has three effects:
|
||||
|
||||
* An info diagnostic is displayed saying `Try these: <list of suggestions>`
|
||||
* A widget is registered, saying `Try these: <list of suggestions>` with a link on each
|
||||
`<suggestion>` to apply the suggestion
|
||||
* A code action for each suggestion is added, which will apply the suggestion.
|
||||
|
||||
The parameters are:
|
||||
* `ref`: the span of the info diagnostic
|
||||
* `suggestions`: an array of `Suggestion`s, which each contain
|
||||
* `suggestion`: the replacement text;
|
||||
* `preInfo?`: an optional string shown immediately after the replacement text in the widget
|
||||
message (only)
|
||||
* `postInfo?`: an optional string shown immediately after the replacement text in the widget
|
||||
message (only)
|
||||
* `style?`: an optional `Json` object used as the value of the `style` attribute of the
|
||||
suggestion text's element (not the whole suggestion element).
|
||||
* `messageData?`: an optional message to display in place of `suggestion` in the info diagnostic
|
||||
(only). The widget message uses only `suggestion`. If `messageData?` is `none`, we simply use
|
||||
`suggestion` instead.
|
||||
* `toCodeActionTitle?`: an optional function `String → String` describing how to transform the
|
||||
pretty-printed suggestion text into the code action text which appears in the lightbulb menu.
|
||||
If `none`, we simply prepend `"Try This: "` to the suggestion text.
|
||||
* `origSpan?`: a syntax object whose span is the actual text to be replaced by `suggestion`.
|
||||
If not provided it defaults to `ref`.
|
||||
* `header`: a string that precedes the list. By default, it is `"Try these:"`.
|
||||
* `style?`: a default style for all suggestions which do not have a custom `style?` set.
|
||||
* `codeActionPrefix?`: an optional string to be used as the prefix of the replacement text for all
|
||||
suggestions which do not have a custom `toCodeActionTitle?`. If not provided, `"Try this: "` is
|
||||
used.
|
||||
-/
|
||||
def addSuggestions (ref : Syntax) (suggestions : Array Suggestion)
|
||||
(origSpan? : Option Syntax := none) (header : String := "Try these:")
|
||||
(style? : Option SuggestionStyle := none)
|
||||
(codeActionPrefix? : Option String := none) : MetaM Unit := do
|
||||
if suggestions.isEmpty then throwErrorAt ref "no suggestions available"
|
||||
let msgs := suggestions.map toMessageData
|
||||
let msgs := msgs.foldl (init := MessageData.nil) (fun msg m => msg ++ m!"\n• " ++ m)
|
||||
logInfoAt ref m!"{header}{msgs}"
|
||||
addSuggestionCore ref suggestions header (isInline := false) origSpan? style? codeActionPrefix?
|
||||
|
||||
private def addExactSuggestionCore (addSubgoalsMsg : Bool) (e : Expr) : MetaM Suggestion := 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 postInfo? ← if !addSubgoalsMsg || mvars.isEmpty then pure none else
|
||||
let mut str := "\nRemaining subgoals:"
|
||||
for g in mvars do
|
||||
-- TODO: use a MessageData.ofExpr instead of rendering to string
|
||||
let e ← PrettyPrinter.ppExpr (← instantiateMVars (← g.getType))
|
||||
str := str ++ Format.pretty ("\n⊢ " ++ e)
|
||||
pure str
|
||||
pure { suggestion, postInfo?, messageData? }
|
||||
|
||||
/-- Add an `exact e` or `refine e` suggestion.
|
||||
|
||||
The parameters are:
|
||||
* `ref`: the span of the info diagnostic
|
||||
* `e`: the replacement expression
|
||||
* `origSpan?`: a syntax object whose span is the actual text to be replaced by `suggestion`.
|
||||
If not provided it defaults to `ref`.
|
||||
* `addSubgoalsMsg`: if true (default false), any remaining subgoals will be shown after
|
||||
`Remaining subgoals:`
|
||||
* `codeActionPrefix?`: an optional string to be used as the prefix of the replacement text if the
|
||||
suggestion does not have a custom `toCodeActionTitle?`. If not provided, `"Try this: "` is used.
|
||||
-/
|
||||
def addExactSuggestion (ref : Syntax) (e : Expr)
|
||||
(origSpan? : Option Syntax := none) (addSubgoalsMsg := false)
|
||||
(codeActionPrefix? : Option String := none): MetaM Unit := do
|
||||
addSuggestion ref (← addExactSuggestionCore addSubgoalsMsg e)
|
||||
(origSpan? := origSpan?) (codeActionPrefix? := codeActionPrefix?)
|
||||
|
||||
/-- Add `exact e` or `refine e` suggestions.
|
||||
|
||||
The parameters are:
|
||||
* `ref`: the span of the info diagnostic
|
||||
* `es`: the array of replacement expressions
|
||||
* `origSpan?`: a syntax object whose span is the actual text to be replaced by `suggestion`.
|
||||
If not provided it defaults to `ref`.
|
||||
* `addSubgoalsMsg`: if true (default false), any remaining subgoals will be shown after
|
||||
`Remaining subgoals:`
|
||||
* `codeActionPrefix?`: an optional string to be used as the prefix of the replacement text for all
|
||||
suggestions which do not have a custom `toCodeActionTitle?`. If not provided, `"Try this: "` is
|
||||
used.
|
||||
-/
|
||||
def addExactSuggestions (ref : Syntax) (es : Array Expr)
|
||||
(origSpan? : Option Syntax := none) (addSubgoalsMsg := false)
|
||||
(codeActionPrefix? : Option String := none) : MetaM Unit := do
|
||||
let suggestions ← es.mapM <| addExactSuggestionCore addSubgoalsMsg
|
||||
addSuggestions ref suggestions (origSpan? := origSpan?) (codeActionPrefix? := codeActionPrefix?)
|
||||
|
||||
/-- Add a term suggestion.
|
||||
|
||||
The parameters are:
|
||||
* `ref`: the span of the info diagnostic
|
||||
* `e`: the replacement expression
|
||||
* `origSpan?`: a syntax object whose span is the actual text to be replaced by `suggestion`.
|
||||
If not provided it defaults to `ref`.
|
||||
* `header`: a string which precedes the suggestion. By default, it's `"Try this: "`.
|
||||
* `codeActionPrefix?`: an optional string to be used as the prefix of the replacement text if the
|
||||
suggestion does not have a custom `toCodeActionTitle?`. If not provided, `"Try this: "` is used.
|
||||
-/
|
||||
def addTermSuggestion (ref : Syntax) (e : Expr)
|
||||
(origSpan? : Option Syntax := none) (header : String := "Try this: ")
|
||||
(codeActionPrefix? : Option String := none) : MetaM Unit := do
|
||||
addSuggestion ref (← delabToRefinableSuggestion e) (origSpan? := origSpan?) (header := header)
|
||||
(codeActionPrefix? := codeActionPrefix?)
|
||||
|
||||
/-- Add term suggestions.
|
||||
|
||||
The parameters are:
|
||||
* `ref`: the span of the info diagnostic
|
||||
* `es`: an array of the replacement expressions
|
||||
* `origSpan?`: a syntax object whose span is the actual text to be replaced by `suggestion`.
|
||||
If not provided it defaults to `ref`.
|
||||
* `header`: a string which precedes the list of suggestions. By default, it's `"Try these:"`.
|
||||
* `codeActionPrefix?`: an optional string to be used as the prefix of the replacement text for all
|
||||
suggestions which do not have a custom `toCodeActionTitle?`. If not provided, `"Try this: "` is
|
||||
used.
|
||||
-/
|
||||
def addTermSuggestions (ref : Syntax) (es : Array Expr)
|
||||
(origSpan? : Option Syntax := none) (header : String := "Try these:")
|
||||
(codeActionPrefix? : Option String := none) : MetaM Unit := do
|
||||
addSuggestions ref (← es.mapM delabToRefinableSuggestion)
|
||||
(origSpan? := origSpan?) (header := header) (codeActionPrefix? := codeActionPrefix?)
|
||||
|
||||
open Lean Elab Elab.Tactic PrettyPrinter Meta
|
||||
|
||||
/-- Add a suggestion for `have h : t := e`. -/
|
||||
def addHaveSuggestion (ref : Syntax) (h? : Option Name) (t? : Option Expr) (e : Expr)
|
||||
(origSpan? : Option Syntax := none) : TermElabM Unit := do
|
||||
let estx ← delabToRefinableSyntax e
|
||||
let prop ← isProp (← inferType e)
|
||||
let tac ← if let some t := t? then
|
||||
let tstx ← delabToRefinableSyntax t
|
||||
if prop then
|
||||
match h? with
|
||||
| some h => `(tactic| have $(mkIdent h) : $tstx := $estx)
|
||||
| none => `(tactic| have : $tstx := $estx)
|
||||
else
|
||||
`(tactic| let $(mkIdent (h?.getD `_)) : $tstx := $estx)
|
||||
else
|
||||
if prop then
|
||||
match h? with
|
||||
| some h => `(tactic| have $(mkIdent h) := $estx)
|
||||
| none => `(tactic| have := $estx)
|
||||
else
|
||||
`(tactic| let $(mkIdent (h?.getD `_)) := $estx)
|
||||
addSuggestion ref tac origSpan?
|
||||
|
||||
open Lean.Parser.Tactic
|
||||
open Lean.Syntax
|
||||
|
||||
/-- Add a suggestion for `rw [h₁, ← h₂] at loc`. -/
|
||||
def addRewriteSuggestion (ref : Syntax) (rules : List (Expr × Bool))
|
||||
(type? : Option Expr := none) (loc? : Option Expr := none)
|
||||
(origSpan? : Option Syntax := none) :
|
||||
TermElabM Unit := do
|
||||
let rules_stx := TSepArray.ofElems <| ← rules.toArray.mapM fun ⟨e, symm⟩ => do
|
||||
let t ← delabToRefinableSyntax e
|
||||
if symm then `(rwRule| ← $t:term) else `(rwRule| $t:term)
|
||||
let tac ← do
|
||||
let loc ← loc?.mapM fun loc => do `(location| at $(← delab loc):term)
|
||||
`(tactic| rw [$rules_stx,*] $(loc)?)
|
||||
|
||||
-- We don't simply write `let mut tacMsg := m!"{tac}"` here
|
||||
-- but instead rebuild it, so that there are embedded `Expr`s in the message,
|
||||
-- thus giving more information in the hovers.
|
||||
-- Perhaps in future we will have a better way to attach elaboration information to
|
||||
-- `Syntax` embedded in a `MessageData`.
|
||||
let mut tacMsg :=
|
||||
let rulesMsg := MessageData.sbracket <| MessageData.joinSep
|
||||
(rules.map fun ⟨e, symm⟩ => (if symm then "← " else "") ++ m!"{e}") ", "
|
||||
if let some loc := loc? then
|
||||
m!"rw {rulesMsg} at {loc}"
|
||||
else
|
||||
m!"rw {rulesMsg}"
|
||||
let mut extraMsg := ""
|
||||
if let some type := type? then
|
||||
tacMsg := tacMsg ++ m!"\n-- {type}"
|
||||
extraMsg := extraMsg ++ s!"\n-- {← PrettyPrinter.ppExpr type}"
|
||||
addSuggestion ref (s := { suggestion := tac, postInfo? := extraMsg, messageData? := tacMsg })
|
||||
origSpan?
|
||||
@@ -393,6 +393,18 @@ def _root_.Lean.MVarId.isDelayedAssigned [Monad m] [MonadMCtx m] (mvarId : MVarI
|
||||
def isMVarDelayedAssigned [Monad m] [MonadMCtx m] (mvarId : MVarId) : m Bool := do
|
||||
mvarId.isDelayedAssigned
|
||||
|
||||
/--
|
||||
Check whether a metavariable is assigned or delayed-assigned. A
|
||||
delayed-assigned metavariable is already 'solved' but the solution cannot be
|
||||
substituted yet because we have to wait for some other metavariables to be
|
||||
assigned first. So in many situations you want to treat a delayed-assigned
|
||||
metavariable as assigned.
|
||||
-/
|
||||
def _root_.Lean.MVarId.isAssignedOrDelayedAssigned [Monad m] [MonadMCtx m] (mvarId : MVarId) :
|
||||
m Bool := do
|
||||
let mctx ← getMCtx
|
||||
return mctx.eAssignment.contains mvarId || mctx.dAssignment.contains mvarId
|
||||
|
||||
def isLevelMVarAssignable [Monad m] [MonadMCtx m] (mvarId : LMVarId) : m Bool := do
|
||||
let mctx ← getMCtx
|
||||
match mctx.lDepth.find? mvarId with
|
||||
@@ -483,6 +495,10 @@ def _root_.Lean.MVarId.assign [MonadMCtx m] (mvarId : MVarId) (val : Expr) : m U
|
||||
def assignExprMVar [MonadMCtx m] (mvarId : MVarId) (val : Expr) : m Unit :=
|
||||
mvarId.assign val
|
||||
|
||||
/--
|
||||
Add a delayed assignment for the given metavariable. You must make sure that
|
||||
the metavariable is not already assigned or delayed-assigned.
|
||||
-/
|
||||
def assignDelayedMVar [MonadMCtx m] (mvarId : MVarId) (fvars : Array Expr) (mvarIdPending : MVarId) : m Unit :=
|
||||
modifyMCtx fun m => { m with dAssignment := m.dAssignment.insert mvarId { fvars, mvarIdPending } }
|
||||
|
||||
@@ -809,6 +825,20 @@ def findDecl? (mctx : MetavarContext) (mvarId : MVarId) : Option MetavarDecl :=
|
||||
def findUserName? (mctx : MetavarContext) (userName : Name) : Option MVarId :=
|
||||
mctx.userNames.find? userName
|
||||
|
||||
/--
|
||||
Modify the declaration of a metavariable. If the metavariable is not declared,
|
||||
the `MetavarContext` is returned unchanged.
|
||||
|
||||
You must ensure that the modification is legal. In particular, expressions may
|
||||
only be replaced with defeq expressions.
|
||||
-/
|
||||
def modifyExprMVarDecl (mctx : MetavarContext) (mvarId : MVarId)
|
||||
(f : MetavarDecl → MetavarDecl) : MetavarContext :=
|
||||
if let some mdecl := mctx.decls.find? mvarId then
|
||||
{ mctx with decls := mctx.decls.insert mvarId (f mdecl) }
|
||||
else
|
||||
mctx
|
||||
|
||||
def setMVarKind (mctx : MetavarContext) (mvarId : MVarId) (kind : MetavarKind) : MetavarContext :=
|
||||
let decl := mctx.getDecl mvarId
|
||||
{ mctx with decls := mctx.decls.insert mvarId { decl with kind := kind } }
|
||||
@@ -840,6 +870,35 @@ def setMVarType (mctx : MetavarContext) (mvarId : MVarId) (type : Expr) : Metava
|
||||
let decl := mctx.getDecl mvarId
|
||||
{ mctx with decls := mctx.decls.insert mvarId { decl with type := type } }
|
||||
|
||||
/--
|
||||
Modify the local context of a metavariable. If the metavariable is not declared,
|
||||
the `MetavarContext` is returned unchanged.
|
||||
|
||||
You must ensure that the modification is legal. In particular, expressions may
|
||||
only be replaced with defeq expressions.
|
||||
-/
|
||||
def modifyExprMVarLCtx (mctx : MetavarContext) (mvarId : MVarId)
|
||||
(f : LocalContext → LocalContext) : MetavarContext :=
|
||||
mctx.modifyExprMVarDecl mvarId fun mdecl => { mdecl with lctx := f mdecl.lctx }
|
||||
|
||||
/--
|
||||
Set the kind of an fvar. If the given metavariable is not declared or the
|
||||
given fvar doesn't exist in its context, the `MetavarContext` is returned
|
||||
unchanged.
|
||||
-/
|
||||
def setFVarKind (mctx : MetavarContext) (mvarId : MVarId) (fvarId : FVarId)
|
||||
(kind : LocalDeclKind) : MetavarContext :=
|
||||
mctx.modifyExprMVarLCtx mvarId (·.setKind fvarId kind)
|
||||
|
||||
/--
|
||||
Set the `BinderInfo` of an fvar. If the given metavariable is not declared or
|
||||
the given fvar doesn't exist in its context, the `MetavarContext` is returned
|
||||
unchanged.
|
||||
-/
|
||||
def setFVarBinderInfo (mctx : MetavarContext) (mvarId : MVarId)
|
||||
(fvarId : FVarId) (bi : BinderInfo) : MetavarContext :=
|
||||
mctx.modifyExprMVarLCtx mvarId (·.setBinderInfo fvarId bi)
|
||||
|
||||
def findLevelDepth? (mctx : MetavarContext) (mvarId : LMVarId) : Option Nat :=
|
||||
mctx.lDepth.find? mvarId
|
||||
|
||||
@@ -1377,4 +1436,46 @@ def getExprAssignmentDomain (mctx : MetavarContext) : Array MVarId :=
|
||||
|
||||
end MetavarContext
|
||||
|
||||
namespace MVarId
|
||||
|
||||
/--
|
||||
Modify the declaration of a metavariable. If the metavariable is not declared,
|
||||
nothing happens.
|
||||
|
||||
You must ensure that the modification is legal. In particular, expressions may
|
||||
only be replaced with defeq expressions.
|
||||
-/
|
||||
def modifyDecl [MonadMCtx m] (mvarId : MVarId)
|
||||
(f : MetavarDecl → MetavarDecl) : m Unit :=
|
||||
modifyMCtx (·.modifyExprMVarDecl mvarId f)
|
||||
|
||||
/--
|
||||
Modify the local context of a metavariable. If the metavariable is not declared,
|
||||
nothing happens.
|
||||
|
||||
You must ensure that the modification is legal. In particular, expressions may
|
||||
only be replaced with defeq expressions.
|
||||
-/
|
||||
def modifyLCtx [MonadMCtx m] (mvarId : MVarId)
|
||||
(f : LocalContext → LocalContext) : m Unit :=
|
||||
modifyMCtx (·.modifyExprMVarLCtx mvarId f)
|
||||
|
||||
/--
|
||||
Set the kind of an fvar. If the given metavariable is not declared or the
|
||||
given fvar doesn't exist in its context, nothing happens.
|
||||
-/
|
||||
def setFVarKind [MonadMCtx m] (mvarId : MVarId) (fvarId : FVarId)
|
||||
(kind : LocalDeclKind) : m Unit :=
|
||||
modifyMCtx (·.setFVarKind mvarId fvarId kind)
|
||||
|
||||
/--
|
||||
Set the `BinderInfo` of an fvar. If the given metavariable is not declared or
|
||||
the given fvar doesn't exist in its context, nothing happens.
|
||||
-/
|
||||
def setFVarBinderInfo [MonadMCtx m] (mvarId : MVarId) (fvarId : FVarId)
|
||||
(bi : BinderInfo) : m Unit :=
|
||||
modifyMCtx (·.setFVarBinderInfo mvarId fvarId bi)
|
||||
|
||||
end MVarId
|
||||
|
||||
end Lean
|
||||
|
||||
@@ -92,6 +92,9 @@ example : (List.range 1000).length = 1000 := by native_decide
|
||||
@[builtin_tactic_parser] def nativeDecide := leading_parser
|
||||
nonReservedSymbol "native_decide"
|
||||
|
||||
builtin_initialize
|
||||
register_parser_alias "matchRhsTacticSeq" matchRhs
|
||||
|
||||
end Tactic
|
||||
end Parser
|
||||
end Lean
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/-
|
||||
Copyright (c) 2019 Microsoft Corporation. All rights reserved.
|
||||
Released under Apache 2.0 license as described in the file LICENSE.
|
||||
Authors: Leonardo de Moura, Sebastian Ullrich
|
||||
Authors: Leonardo de Moura, Sebastian Ullrich, Mario Carneiro
|
||||
-/
|
||||
import Lean.Parser.Attr
|
||||
import Lean.Parser.Level
|
||||
@@ -432,7 +432,9 @@ Empty match/ex falso. `nomatch e` is of arbitrary type `α : Sort u` if
|
||||
Lean can show that an empty set of patterns is exhaustive given `e`'s type,
|
||||
e.g. because it has no constructors.
|
||||
-/
|
||||
@[builtin_term_parser] def «nomatch» := leading_parser:leadPrec "nomatch " >> termParser
|
||||
@[builtin_term_parser] def «nomatch» := leading_parser:leadPrec "nomatch " >> sepBy1 termParser ", "
|
||||
|
||||
@[builtin_term_parser] def «nofun» := leading_parser "nofun"
|
||||
|
||||
def funImplicitBinder := withAntiquot (mkAntiquot "implicitBinder" ``implicitBinder) <|
|
||||
atomic (lookahead ("{" >> many1 binderIdent >> (symbol " : " <|> "}"))) >> implicitBinder
|
||||
@@ -599,6 +601,19 @@ def matchAltsWhereDecls := leading_parser
|
||||
@[builtin_term_parser] def noindex := leading_parser
|
||||
"no_index " >> termParser maxPrec
|
||||
|
||||
/--
|
||||
`unsafe t : α` is an expression constructor which allows using unsafe declarations inside the
|
||||
body of `t : α`, by creating an auxiliary definition containing `t` and using `implementedBy` to
|
||||
wrap it in a safe interface. It is required that `α` is nonempty for this to be sound,
|
||||
but even beyond that, an `unsafe` block should be carefully inspected for memory safety because
|
||||
the compiler is unable to guarantee the safety of the operation.
|
||||
|
||||
For example, the `evalExpr` function is unsafe, because the compiler cannot guarantee that when
|
||||
you call ```evalExpr Foo ``Foo e``` that the type `Foo` corresponds to the name `Foo`, but in a
|
||||
particular use case, we can ensure this, so `unsafe (evalExpr Foo ``Foo e)` is a correct usage.
|
||||
-/
|
||||
@[builtin_term_parser] def «unsafe» := leading_parser:leadPrec "unsafe " >> termParser
|
||||
|
||||
/-- `binrel% r a b` elaborates `r a b` as a binary relation using the type propogation protocol in `Lean.Elab.Extra`. -/
|
||||
@[builtin_term_parser] def binrel := leading_parser
|
||||
"binrel% " >> ident >> ppSpace >> termParser maxPrec >> ppSpace >> termParser maxPrec
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
/-
|
||||
Copyright (c) 2020 Sebastian Ullrich. All rights reserved.
|
||||
Released under Apache 2.0 license as described in the file LICENSE.
|
||||
Authors: Sebastian Ullrich
|
||||
Authors: Sebastian Ullrich, Leonardo de Moura, Gabriel Ebner, Mario Carneiro
|
||||
-/
|
||||
|
||||
import Lean.Parser
|
||||
import Lean.PrettyPrinter.Delaborator.Basic
|
||||
import Lean.PrettyPrinter.Delaborator.SubExpr
|
||||
import Lean.PrettyPrinter.Delaborator.TopDownAnalyze
|
||||
import Lean.Parser
|
||||
import Lean.Meta.CoeAttr
|
||||
|
||||
namespace Lean.PrettyPrinter.Delaborator
|
||||
open Lean.Meta
|
||||
@@ -315,7 +315,8 @@ Default delaborator for applications.
|
||||
-/
|
||||
@[builtin_delab app]
|
||||
def delabApp : Delab := do
|
||||
delabAppCore (← getExpr).getAppNumArgs delabAppFn
|
||||
let e ← getExpr
|
||||
delabAppCore e.getAppNumArgs delabAppFn
|
||||
|
||||
/--
|
||||
The `withOverApp` combinator allows delaborators to handle "over-application" by using the core
|
||||
@@ -810,6 +811,27 @@ def delabProjectionApp : Delab := whenPPOption getPPStructureProjections $ do
|
||||
let appStx ← withAppArg delab
|
||||
`($(appStx).$(mkIdent f):ident)
|
||||
|
||||
/--
|
||||
This delaborator tries to elide functions which are known coercions.
|
||||
For example, `Int.ofNat` is a coercion, so instead of printing `ofNat n` we just print `↑n`,
|
||||
and when re-parsing this we can (usually) recover the specific coercion being used.
|
||||
-/
|
||||
@[builtin_delab app]
|
||||
def coeDelaborator : Delab := whenPPOption getPPCoercions do
|
||||
let e ← getExpr
|
||||
let .const declName _ := e.getAppFn | failure
|
||||
let some info ← Meta.getCoeFnInfo? declName | failure
|
||||
let n := e.getAppNumArgs
|
||||
withOverApp info.numArgs do
|
||||
match info.type with
|
||||
| .coe => `(↑$(← withNaryArg info.coercee delab))
|
||||
| .coeFun =>
|
||||
if n = info.numArgs then
|
||||
`(⇑$(← withNaryArg info.coercee delab))
|
||||
else
|
||||
withNaryArg info.coercee delab
|
||||
| .coeSort => `(↥$(← withNaryArg info.coercee delab))
|
||||
|
||||
@[builtin_delab app.dite]
|
||||
def delabDIte : Delab := whenPPOption getPPNotation <| withOverApp 5 do
|
||||
-- Note: we keep this as a delaborator for now because it actually accesses the expression.
|
||||
|
||||
@@ -68,18 +68,38 @@ to perform the computation after the user has clicked on the code action in thei
|
||||
def CodeActionProvider := CodeActionParams → Snapshot → RequestM (Array LazyCodeAction)
|
||||
deriving instance Inhabited for CodeActionProvider
|
||||
|
||||
private builtin_initialize builtinCodeActionProviders : IO.Ref (NameMap CodeActionProvider) ←
|
||||
IO.mkRef ∅
|
||||
|
||||
def addBuiltinCodeActionProvider (decl : Name) (provider : CodeActionProvider) : IO Unit :=
|
||||
builtinCodeActionProviders.modify (·.insert decl provider)
|
||||
|
||||
builtin_initialize codeActionProviderExt : SimplePersistentEnvExtension Name NameSet ← registerSimplePersistentEnvExtension {
|
||||
addImportedFn := fun nss => nss.foldl (fun acc ns => ns.foldl NameSet.insert acc) ∅
|
||||
addEntryFn := fun s n => s.insert n
|
||||
toArrayFn := fun es => es.toArray.qsort Name.quickLt
|
||||
}
|
||||
|
||||
builtin_initialize registerBuiltinAttribute {
|
||||
name := `code_action_provider
|
||||
descr := "Use to decorate methods for suggesting code actions. This is a low-level interface for making code actions."
|
||||
add := fun src _stx _kind => do
|
||||
modifyEnv (codeActionProviderExt.addEntry · src)
|
||||
}
|
||||
builtin_initialize
|
||||
let mkAttr (builtin : Bool) (name : Name) := registerBuiltinAttribute {
|
||||
name
|
||||
descr := (if builtin then "(builtin) " else "") ++
|
||||
"Use to decorate methods for suggesting code actions. This is a low-level interface for making code actions."
|
||||
applicationTime := .afterCompilation
|
||||
add := fun decl stx kind => do
|
||||
Attribute.Builtin.ensureNoArgs stx
|
||||
unless kind == AttributeKind.global do throwError "invalid attribute '{name}', must be global"
|
||||
unless (← getConstInfo decl).type.isConstOf ``CodeActionProvider do
|
||||
throwError "invalid attribute '{name}', must be of type `Lean.Server.CodeActionProvider`"
|
||||
let env ← getEnv
|
||||
if builtin then
|
||||
let h := mkConst decl
|
||||
declareBuiltin decl <| mkApp (mkConst ``addBuiltinCodeActionProvider) h
|
||||
else
|
||||
setEnv <| codeActionProviderExt.addEntry env decl
|
||||
}
|
||||
mkAttr true `builtin_code_action_provider
|
||||
mkAttr false `code_action_provider
|
||||
|
||||
private unsafe def evalCodeActionProviderUnsafe [MonadEnv M] [MonadOptions M] [MonadError M] [Monad M] (declName : Name) : M CodeActionProvider := do
|
||||
evalConstCheck CodeActionProvider ``CodeActionProvider declName
|
||||
@@ -103,7 +123,7 @@ def handleCodeAction (params : CodeActionParams) : RequestM (RequestTask (Array
|
||||
let env ← getEnv
|
||||
let names := codeActionProviderExt.getState env |>.toArray
|
||||
let caps ← names.mapM evalCodeActionProvider
|
||||
return Array.zip names caps
|
||||
return (← builtinCodeActionProviders.get).toList.toArray ++ Array.zip names caps
|
||||
caps.concatMapM fun (providerName, cap) => do
|
||||
let cas ← cap params snap
|
||||
cas.mapIdxM fun i lca => do
|
||||
@@ -131,7 +151,9 @@ def handleCodeActionResolve (param : CodeAction) : RequestM (RequestTask CodeAct
|
||||
withWaitFindSnap doc (fun s => s.endPos ≥ pos)
|
||||
(notFoundX := throw <| RequestError.internalError "snapshot not found")
|
||||
fun snap => do
|
||||
let cap ← RequestM.runCoreM snap <| evalCodeActionProvider data.providerName
|
||||
let cap ← match (← builtinCodeActionProviders.get).find? data.providerName with
|
||||
| some cap => pure cap
|
||||
| none => RequestM.runCoreM snap <| evalCodeActionProvider data.providerName
|
||||
let cas ← cap data.params snap
|
||||
let some ca := cas[data.providerResultIndex]?
|
||||
| throw <| RequestError.internalError s!"Failed to resolve code action index {data.providerResultIndex}."
|
||||
|
||||
@@ -305,6 +305,10 @@ def getRange? (stx : Syntax) (canonicalOnly := false) : Option String.Range :=
|
||||
| some start, some stop => some { start, stop }
|
||||
| _, _ => none
|
||||
|
||||
/-- Returns a synthetic Syntax which has the specified `String.Range`. -/
|
||||
def ofRange (range : String.Range) (canonical := true) : Lean.Syntax :=
|
||||
.atom (.synthetic range.start range.stop canonical) ""
|
||||
|
||||
/--
|
||||
Represents a cursor into a syntax tree that can be read, written, and advanced down/up/left/right.
|
||||
Indices are allowed to be out-of-bound, in which case `cur` is `Syntax.missing`.
|
||||
|
||||
@@ -56,6 +56,11 @@ class ToModule (α : Type u) where
|
||||
|
||||
instance : ToModule Module := ⟨id⟩
|
||||
|
||||
private builtin_initialize builtinModulesRef : IO.Ref (RBMap UInt64 Module compare) ← IO.mkRef ∅
|
||||
|
||||
def addBuiltinModule (m : Module) : IO Unit :=
|
||||
builtinModulesRef.modify (·.insert m.javascriptHash m)
|
||||
|
||||
/-- Every constant `c : α` marked with `@[widget_module]` is registered here.
|
||||
The registry maps `hash (toModule c).javascript` to ``(`c, `(@toModule α inst c))``
|
||||
where `inst : ToModule α` is synthesized during registration time
|
||||
@@ -71,19 +76,38 @@ builtin_initialize moduleRegistry : ModuleRegistry ←
|
||||
toArrayFn := fun es => es.toArray
|
||||
}
|
||||
|
||||
private def widgetModuleAttrImpl : AttributeImpl where
|
||||
name := `widget_module
|
||||
descr := "Registers a widget module. Its type must implement Lean.Widget.ToModule."
|
||||
applicationTime := AttributeApplicationTime.afterCompilation
|
||||
add decl _stx _kind := Prod.fst <$> MetaM.run do
|
||||
let e ← mkAppM ``ToModule.toModule #[.const decl []]
|
||||
let mod ← evalModule e
|
||||
let env ← getEnv
|
||||
if let some (n, _) := moduleRegistry.getState env |>.find? mod.javascriptHash then
|
||||
logWarning m!"A widget module with the same hash(JS source code) was already registered at {Expr.const n []}."
|
||||
setEnv <| moduleRegistry.addEntry env (mod.javascriptHash, decl, e)
|
||||
|
||||
builtin_initialize registerBuiltinAttribute widgetModuleAttrImpl
|
||||
/--
|
||||
Registers `[builtin_widget_module]` and `[widget_module]` and binds the latter's implementation
|
||||
(used for creating the obsolete `[widget]` alias below).
|
||||
-/
|
||||
builtin_initialize widgetModuleAttrImpl : AttributeImpl ←
|
||||
let mkAttr (builtin : Bool) (name : Name) := do
|
||||
let impl := {
|
||||
name
|
||||
descr := (if builtin then "(builtin) " else "") ++
|
||||
"Registers a widget module. Its type must implement Lean.Widget.ToModule."
|
||||
applicationTime := .afterCompilation
|
||||
add := fun decl stx kind => Prod.fst <$> MetaM.run do
|
||||
Attribute.Builtin.ensureNoArgs stx
|
||||
unless kind == AttributeKind.global do throwError "invalid attribute '{name}', must be global"
|
||||
let e ← mkAppM ``ToModule.toModule #[.const decl []]
|
||||
let mod ← evalModule e
|
||||
let env ← getEnv
|
||||
if let some _ := (← builtinModulesRef.get).find? mod.javascriptHash then
|
||||
logWarning m!"A builtin widget module with the same hash(JS source code) was already registered."
|
||||
if let some (n, _) := moduleRegistry.getState env |>.find? mod.javascriptHash then
|
||||
logWarning m!"A widget module with the same hash(JS source code) was already registered at {Expr.const n []}."
|
||||
let env ← getEnv
|
||||
if builtin then
|
||||
let h := mkConst decl
|
||||
declareBuiltin decl <| mkApp (mkConst ``addBuiltinModule) h
|
||||
else
|
||||
setEnv <| moduleRegistry.addEntry env (mod.javascriptHash, decl, e)
|
||||
}
|
||||
registerBuiltinAttribute impl
|
||||
return impl
|
||||
let _ ← mkAttr true `builtin_widget_module
|
||||
mkAttr false `widget_module
|
||||
|
||||
/-! ## Retrieval of widget modules -/
|
||||
|
||||
@@ -101,6 +125,9 @@ structure WidgetSource where
|
||||
open Server RequestM in
|
||||
@[server_rpc_method]
|
||||
def getWidgetSource (args : GetWidgetSourceParams) : RequestM (RequestTask WidgetSource) := do
|
||||
if let some m := (← builtinModulesRef.get).find? args.hash then
|
||||
return .pure { sourcetext := m.javascript }
|
||||
|
||||
let doc ← readDoc
|
||||
let pos := doc.meta.text.lspPosToUtf8Pos args.pos
|
||||
let notFound := throwThe RequestError ⟨.invalidParams, s!"No widget module with hash {args.hash} registered"⟩
|
||||
|
||||
@@ -18,6 +18,7 @@ Lean's IR.
|
||||
#include "runtime/string_ref.h"
|
||||
|
||||
#ifdef LEAN_LLVM
|
||||
#include "llvm-c/Analysis.h"
|
||||
#include "llvm-c/BitReader.h"
|
||||
#include "llvm-c/BitWriter.h"
|
||||
#include "llvm-c/Core.h"
|
||||
@@ -1424,3 +1425,74 @@ extern "C" LEAN_EXPORT lean_object *llvm_is_declaration(size_t ctx, size_t globa
|
||||
return lean_io_result_mk_ok(lean_box(is_bool));
|
||||
#endif // LEAN_LLVM
|
||||
}
|
||||
|
||||
extern "C" LEAN_EXPORT lean_object *lean_llvm_verify_module(size_t ctx, size_t mod,
|
||||
lean_object * /* w */) {
|
||||
#ifndef LEAN_LLVM
|
||||
lean_always_assert(
|
||||
false && ("Please build a version of Lean4 with -DLLVM=ON to invoke "
|
||||
"the LLVM backend function."));
|
||||
#else
|
||||
char* msg = NULL;
|
||||
LLVMBool broken = LLVMVerifyModule(lean_to_Module(mod), LLVMReturnStatusAction, &msg);
|
||||
if (broken) {
|
||||
return lean_io_result_mk_ok(lean::mk_option_some(lean_mk_string(msg)));
|
||||
} else {
|
||||
return lean_io_result_mk_ok(lean::mk_option_none());
|
||||
}
|
||||
#endif // LEAN_LLVM
|
||||
}
|
||||
|
||||
extern "C" LEAN_EXPORT lean_object *lean_llvm_count_basic_blocks(size_t ctx, size_t fn_val,
|
||||
lean_object * /* w */) {
|
||||
#ifndef LEAN_LLVM
|
||||
lean_always_assert(
|
||||
false && ("Please build a version of Lean4 with -DLLVM=ON to invoke "
|
||||
"the LLVM backend function."));
|
||||
#else
|
||||
LLVMValueRef fn_ref = lean_to_Value(fn_val);
|
||||
return lean_io_result_mk_ok(lean_box_uint64((uint64_t)LLVMCountBasicBlocks(fn_ref)));
|
||||
#endif // LEAN_LLVM
|
||||
}
|
||||
|
||||
extern "C" LEAN_EXPORT lean_object *lean_llvm_get_entry_basic_block(size_t ctx, size_t fn_val,
|
||||
lean_object * /* w */) {
|
||||
#ifndef LEAN_LLVM
|
||||
lean_always_assert(
|
||||
false && ("Please build a version of Lean4 with -DLLVM=ON to invoke "
|
||||
"the LLVM backend function."));
|
||||
#else
|
||||
LLVMValueRef fn_ref = lean_to_Value(fn_val);
|
||||
LLVMBasicBlockRef bb_ref = LLVMGetEntryBasicBlock(fn_ref);
|
||||
return lean_io_result_mk_ok(lean_box_usize(BasicBlock_to_lean(bb_ref)));
|
||||
#endif // LEAN_LLVM
|
||||
}
|
||||
|
||||
extern "C" LEAN_EXPORT lean_object *lean_llvm_get_first_instruction(size_t ctx, size_t bb,
|
||||
lean_object * /* w */) {
|
||||
#ifndef LEAN_LLVM
|
||||
lean_always_assert(
|
||||
false && ("Please build a version of Lean4 with -DLLVM=ON to invoke "
|
||||
"the LLVM backend function."));
|
||||
#else
|
||||
LLVMBasicBlockRef bb_ref = lean_to_BasicBlock(bb);
|
||||
LLVMValueRef instr_ref = LLVMGetFirstInstruction(bb_ref);
|
||||
if (instr_ref == NULL) {
|
||||
return lean_io_result_mk_ok(lean::mk_option_none());
|
||||
} else {
|
||||
return lean_io_result_mk_ok(lean::mk_option_some(lean_box_usize(Value_to_lean(instr_ref))));
|
||||
}
|
||||
#endif // LEAN_LLVM
|
||||
}
|
||||
|
||||
extern "C" LEAN_EXPORT lean_object *lean_llvm_position_builder_before(
|
||||
size_t ctx, size_t builder, size_t instr, lean_object * /* w */) {
|
||||
#ifndef LEAN_LLVM
|
||||
lean_always_assert(
|
||||
false && ("Please build a version of Lean4 with -DLLVM=ON to invoke "
|
||||
"the LLVM backend function."));
|
||||
#else
|
||||
LLVMPositionBuilderBefore(lean_to_Builder(builder), lean_to_Value(instr));
|
||||
return lean_io_result_mk_ok(lean_box(0));
|
||||
#endif // LEAN_LLVM
|
||||
}
|
||||
|
||||
@@ -1033,6 +1033,8 @@ extern "C" LEAN_EXPORT b_obj_res lean_io_wait_any_core(b_obj_arg task_list) {
|
||||
return g_task_manager->wait_any(task_list);
|
||||
}
|
||||
|
||||
// Internally, a `Promise` is just a `Task` that is in the "Promised" or "Finished" state
|
||||
|
||||
extern "C" LEAN_EXPORT obj_res lean_io_promise_new(obj_arg) {
|
||||
lean_always_assert(g_task_manager);
|
||||
bool keep_alive = false;
|
||||
@@ -1050,6 +1052,11 @@ extern "C" LEAN_EXPORT obj_res lean_io_promise_resolve(obj_arg value, b_obj_arg
|
||||
return io_result_mk_ok(box(0));
|
||||
}
|
||||
|
||||
extern "C" LEAN_EXPORT obj_res lean_io_promise_result(obj_arg promise) {
|
||||
// the task is the promise itself
|
||||
return promise;
|
||||
}
|
||||
|
||||
// =======================================
|
||||
// Natural numbers
|
||||
|
||||
|
||||
BIN
stage0/src/CMakeLists.txt
generated
BIN
stage0/src/CMakeLists.txt
generated
Binary file not shown.
BIN
stage0/src/library/trace.cpp
generated
BIN
stage0/src/library/trace.cpp
generated
Binary file not shown.
BIN
stage0/src/runtime/object.cpp
generated
BIN
stage0/src/runtime/object.cpp
generated
Binary file not shown.
BIN
stage0/stdlib/Init.c
generated
BIN
stage0/stdlib/Init.c
generated
Binary file not shown.
BIN
stage0/stdlib/Init/Classical.c
generated
BIN
stage0/stdlib/Init/Classical.c
generated
Binary file not shown.
BIN
stage0/stdlib/Init/Coe.c
generated
BIN
stage0/stdlib/Init/Coe.c
generated
Binary file not shown.
BIN
stage0/stdlib/Init/Conv.c
generated
BIN
stage0/stdlib/Init/Conv.c
generated
Binary file not shown.
BIN
stage0/stdlib/Init/Data/Array/Basic.c
generated
BIN
stage0/stdlib/Init/Data/Array/Basic.c
generated
Binary file not shown.
BIN
stage0/stdlib/Init/Data/Channel.c
generated
BIN
stage0/stdlib/Init/Data/Channel.c
generated
Binary file not shown.
BIN
stage0/stdlib/Init/Data/Format/Basic.c
generated
BIN
stage0/stdlib/Init/Data/Format/Basic.c
generated
Binary file not shown.
BIN
stage0/stdlib/Init/Data/Format/Syntax.c
generated
BIN
stage0/stdlib/Init/Data/Format/Syntax.c
generated
Binary file not shown.
BIN
stage0/stdlib/Init/Data/Repr.c
generated
BIN
stage0/stdlib/Init/Data/Repr.c
generated
Binary file not shown.
BIN
stage0/stdlib/Init/Data/String/Basic.c
generated
BIN
stage0/stdlib/Init/Data/String/Basic.c
generated
Binary file not shown.
BIN
stage0/stdlib/Init/Data/ToString/Basic.c
generated
BIN
stage0/stdlib/Init/Data/ToString/Basic.c
generated
Binary file not shown.
BIN
stage0/stdlib/Init/Meta.c
generated
BIN
stage0/stdlib/Init/Meta.c
generated
Binary file not shown.
BIN
stage0/stdlib/Init/Notation.c
generated
BIN
stage0/stdlib/Init/Notation.c
generated
Binary file not shown.
BIN
stage0/stdlib/Init/NotationExtra.c
generated
BIN
stage0/stdlib/Init/NotationExtra.c
generated
Binary file not shown.
BIN
stage0/stdlib/Init/Simproc.c
generated
BIN
stage0/stdlib/Init/Simproc.c
generated
Binary file not shown.
BIN
stage0/stdlib/Init/System/IO.c
generated
BIN
stage0/stdlib/Init/System/IO.c
generated
Binary file not shown.
BIN
stage0/stdlib/Init/System/Promise.c
generated
BIN
stage0/stdlib/Init/System/Promise.c
generated
Binary file not shown.
BIN
stage0/stdlib/Init/Tactics.c
generated
BIN
stage0/stdlib/Init/Tactics.c
generated
Binary file not shown.
BIN
stage0/stdlib/Init/TacticsExtra.c
generated
Normal file
BIN
stage0/stdlib/Init/TacticsExtra.c
generated
Normal file
Binary file not shown.
BIN
stage0/stdlib/Lean/Attributes.c
generated
BIN
stage0/stdlib/Lean/Attributes.c
generated
Binary file not shown.
BIN
stage0/stdlib/Lean/Compiler/IR/Basic.c
generated
BIN
stage0/stdlib/Lean/Compiler/IR/Basic.c
generated
Binary file not shown.
BIN
stage0/stdlib/Lean/Compiler/IR/Borrow.c
generated
BIN
stage0/stdlib/Lean/Compiler/IR/Borrow.c
generated
Binary file not shown.
BIN
stage0/stdlib/Lean/Compiler/IR/Checker.c
generated
BIN
stage0/stdlib/Lean/Compiler/IR/Checker.c
generated
Binary file not shown.
BIN
stage0/stdlib/Lean/Compiler/IR/CompilerM.c
generated
BIN
stage0/stdlib/Lean/Compiler/IR/CompilerM.c
generated
Binary file not shown.
BIN
stage0/stdlib/Lean/Compiler/IR/ElimDeadBranches.c
generated
BIN
stage0/stdlib/Lean/Compiler/IR/ElimDeadBranches.c
generated
Binary file not shown.
BIN
stage0/stdlib/Lean/Compiler/IR/EmitLLVM.c
generated
BIN
stage0/stdlib/Lean/Compiler/IR/EmitLLVM.c
generated
Binary file not shown.
BIN
stage0/stdlib/Lean/Compiler/IR/Format.c
generated
BIN
stage0/stdlib/Lean/Compiler/IR/Format.c
generated
Binary file not shown.
BIN
stage0/stdlib/Lean/Compiler/ImplementedByAttr.c
generated
BIN
stage0/stdlib/Lean/Compiler/ImplementedByAttr.c
generated
Binary file not shown.
BIN
stage0/stdlib/Lean/Compiler/InitAttr.c
generated
BIN
stage0/stdlib/Lean/Compiler/InitAttr.c
generated
Binary file not shown.
BIN
stage0/stdlib/Lean/Compiler/LCNF/ElimDeadBranches.c
generated
BIN
stage0/stdlib/Lean/Compiler/LCNF/ElimDeadBranches.c
generated
Binary file not shown.
BIN
stage0/stdlib/Lean/Compiler/LCNF/Testing.c
generated
BIN
stage0/stdlib/Lean/Compiler/LCNF/Testing.c
generated
Binary file not shown.
BIN
stage0/stdlib/Lean/Data/Format.c
generated
BIN
stage0/stdlib/Lean/Data/Format.c
generated
Binary file not shown.
BIN
stage0/stdlib/Lean/Data/Json.c
generated
BIN
stage0/stdlib/Lean/Data/Json.c
generated
Binary file not shown.
BIN
stage0/stdlib/Lean/Data/Json/Elab.c
generated
Normal file
BIN
stage0/stdlib/Lean/Data/Json/Elab.c
generated
Normal file
Binary file not shown.
BIN
stage0/stdlib/Lean/Data/Json/Parser.c
generated
BIN
stage0/stdlib/Lean/Data/Json/Parser.c
generated
Binary file not shown.
BIN
stage0/stdlib/Lean/Data/Json/Printer.c
generated
BIN
stage0/stdlib/Lean/Data/Json/Printer.c
generated
Binary file not shown.
BIN
stage0/stdlib/Lean/Data/KVMap.c
generated
BIN
stage0/stdlib/Lean/Data/KVMap.c
generated
Binary file not shown.
BIN
stage0/stdlib/Lean/Data/Lsp/Communication.c
generated
BIN
stage0/stdlib/Lean/Data/Lsp/Communication.c
generated
Binary file not shown.
BIN
stage0/stdlib/Lean/Data/Name.c
generated
BIN
stage0/stdlib/Lean/Data/Name.c
generated
Binary file not shown.
BIN
stage0/stdlib/Lean/Data/Parsec.c
generated
BIN
stage0/stdlib/Lean/Data/Parsec.c
generated
Binary file not shown.
BIN
stage0/stdlib/Lean/Data/Position.c
generated
BIN
stage0/stdlib/Lean/Data/Position.c
generated
Binary file not shown.
BIN
stage0/stdlib/Lean/Data/Trie.c
generated
BIN
stage0/stdlib/Lean/Data/Trie.c
generated
Binary file not shown.
BIN
stage0/stdlib/Lean/Elab/App.c
generated
BIN
stage0/stdlib/Lean/Elab/App.c
generated
Binary file not shown.
BIN
stage0/stdlib/Lean/Elab/Attributes.c
generated
BIN
stage0/stdlib/Lean/Elab/Attributes.c
generated
Binary file not shown.
BIN
stage0/stdlib/Lean/Elab/Binders.c
generated
BIN
stage0/stdlib/Lean/Elab/Binders.c
generated
Binary file not shown.
BIN
stage0/stdlib/Lean/Elab/BuiltinCommand.c
generated
BIN
stage0/stdlib/Lean/Elab/BuiltinCommand.c
generated
Binary file not shown.
BIN
stage0/stdlib/Lean/Elab/BuiltinNotation.c
generated
BIN
stage0/stdlib/Lean/Elab/BuiltinNotation.c
generated
Binary file not shown.
BIN
stage0/stdlib/Lean/Elab/BuiltinTerm.c
generated
BIN
stage0/stdlib/Lean/Elab/BuiltinTerm.c
generated
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user