This PR implements first-class support for nondependent let expressions in the elaborator; recall that a let expression `let x : t := v; b` is called *nondependent* if `fun x : t => b` typechecks, and the notation for a nondependent let expression is `have x := v; b`. Previously we encoded `have` using the `letFun` function, but now we make use of the `nondep` flag in the `Expr.letE` constructor for the encoding. This has been given full support throughout the metaprogramming interface and the elaborator. Key changes to the metaprogramming interface: - Local context `ldecl`s with `nondep := true` are generally treated as `cdecl`s. This is because in the body of a `have` expression the variable is opaque. Functions like `LocalDecl.isLet` by default return `false` for nondependent `ldecl`s. In the rare case where it is needed, they take an additional optional `allowNondep : Bool` flag (defaults to `false`) if the variable is being processed in a context where the value is relevant. - Functions such as `mkLetFVars` by default generalize nondependent let variables and create lambda expressions for them. The `generalizeNondepLet` flag (default true) can be set to false if `have` expressions should be produced instead. **Breaking change:** Uses of `letLambdaTelescope`/`mkLetFVars` need to use `generalizeNondepLet := false`. See the next item. - There are now some mapping functions to make telescoping operations more convenient. See `mapLetTelescope` and `mapLambdaLetTelescope`. There is also `mapLetDecl` as a counterpart to `withLetDecl` for creating `let`/`have` expressions. - Important note about the `generalizeNondepLet` flag: it should only be used for variables in a local context that the metaprogram "owns". Since nondependent let variables are treated as constants in most cases, the `value` field might refer to variables that do not exist, if for example those variables were cleared or reverted. Using `mapLetDecl` is always fine. - The simplifier will cache its let dependence calculations in the nondep field of let expressions. - The `intro` tactic still produces *dependent* local variables. Given that the simplifier will transform lets into haves, it would be surprising if that would prevent `intro` from creating a local variable whose value cannot be used. Note that nondependence of lets is not checked by the kernel. To external checker authors: If the elaborator gets the nondep flag wrong, we consider this to be an elaborator error. Feel free to typecheck `letE n t v b true` as if it were `app (lam n t b default) v` and please report issues. This PR follows up from #8751, which made sure the nondep flag was preserved in the C++ interface.
380 lines
6.7 KiB
Text
380 lines
6.7 KiB
Text
/-!
|
||
# Tests for `clear_value` tactic
|
||
-/
|
||
|
||
/-!
|
||
Check that `clear_value` clears the value.
|
||
-/
|
||
/--
|
||
trace: x : Nat
|
||
⊢ 0 ≤ x
|
||
-/
|
||
#guard_msgs in
|
||
example : let x := 22; 0 ≤ x := by
|
||
intro x
|
||
clear_value x
|
||
trace_state
|
||
exact Nat.zero_le _
|
||
|
||
/-!
|
||
Check that `clear_value` preserves the order of the local context.
|
||
-/
|
||
/--
|
||
trace: x : Nat
|
||
y : Nat := 23
|
||
⊢ True
|
||
-/
|
||
#guard_msgs in
|
||
example : let _x := 22; let _y := 23; True := by
|
||
intro x y
|
||
clear_value x
|
||
trace_state
|
||
trivial
|
||
|
||
/-!
|
||
Test `*` mode.
|
||
-/
|
||
/--
|
||
trace: x : Nat
|
||
⊢ 0 ≤ x
|
||
-/
|
||
#guard_msgs in
|
||
example : let x := 22; 0 ≤ x := by
|
||
intro x
|
||
clear_value *
|
||
trace_state
|
||
exact Nat.zero_le _
|
||
|
||
/-!
|
||
Double `*` is not allowed.
|
||
-/
|
||
/-- error: Multiple `*` arguments provided. -/
|
||
#guard_msgs in
|
||
example : let x := 22; 0 ≤ x := by
|
||
intro x
|
||
clear_value * *
|
||
|
||
/-!
|
||
Check that `clear_value` fails if there is no value to clear.
|
||
-/
|
||
/-- error: Hypothesis `x` is not a local definition. -/
|
||
#guard_msgs in
|
||
example (x : Nat) : 0 ≤ x := by
|
||
clear_value x
|
||
|
||
/-!
|
||
Check that `clear_value *` fails if it does nothing.
|
||
-/
|
||
/--
|
||
error: Tactic `clear_value` failed to clear any values.
|
||
x : Nat
|
||
⊢ 0 ≤ x
|
||
-/
|
||
#guard_msgs in
|
||
example (x : Nat) : 0 ≤ x := by
|
||
clear_value *
|
||
|
||
/-!
|
||
Check for validation that hypotheses aren't mentioned multiple times.
|
||
-/
|
||
/-- error: Hypothesis `x` appears multiple times. -/
|
||
#guard_msgs in
|
||
example : let x := 22; 0 ≤ x := by
|
||
intro x
|
||
clear_value x x
|
||
|
||
/-!
|
||
Check that `clear_value (h : x = _)` creates an equality hypothesis.
|
||
-/
|
||
/--
|
||
trace: x : Nat
|
||
h : x = 22
|
||
⊢ 0 ≤ x
|
||
-/
|
||
#guard_msgs in
|
||
example : let x := 22; 0 ≤ x := by
|
||
intro x
|
||
clear_value (h : x = _)
|
||
trace_state
|
||
rw [h]
|
||
exact Nat.zero_le _
|
||
|
||
/-!
|
||
Can use any term that is definitionally equal to the term.
|
||
-/
|
||
/--
|
||
trace: x : Nat
|
||
h : x = 12 + 10
|
||
⊢ 0 ≤ x
|
||
-/
|
||
#guard_msgs in
|
||
example : let x := 22; 0 ≤ x := by
|
||
intro x
|
||
clear_value (h : x = 12 + 10)
|
||
trace_state
|
||
rw [h]
|
||
exact Nat.zero_le _
|
||
|
||
/-!
|
||
There is no restriction on the value, except that it can't depend on the values being cleared.
|
||
-/
|
||
/--
|
||
trace: x : Nat
|
||
h : x = x
|
||
⊢ 0 ≤ x
|
||
-/
|
||
#guard_msgs in
|
||
example : let x := 22; 0 ≤ x := by
|
||
intro x
|
||
clear_value (h : x = x)
|
||
trace_state
|
||
rw [h]
|
||
exact Nat.zero_le _
|
||
|
||
/-!
|
||
Failure because it depends on the value.
|
||
-/
|
||
/--
|
||
error: Tactic `clear_value` failed, the value of `x` cannot be cleared.
|
||
x : Nat := 22
|
||
h : x = 22 + ↑0
|
||
⊢ 0 ≤ x
|
||
-/
|
||
#guard_msgs in
|
||
example : let x := 22; 0 ≤ x := by
|
||
intro x
|
||
clear_value (h : x = 22 + (0 : Fin x))
|
||
trace_state
|
||
rw [h]
|
||
exact Nat.zero_le _
|
||
|
||
/-!
|
||
Unifies the term.
|
||
-/
|
||
/--
|
||
trace: x : Nat
|
||
h : x = id 22
|
||
⊢ 0 ≤ x
|
||
-/
|
||
#guard_msgs in
|
||
example : let x := 22; 0 ≤ x := by
|
||
intro x
|
||
clear_value (h : x = id _)
|
||
trace_state
|
||
rw [h]
|
||
exact Nat.zero_le _
|
||
|
||
/-!
|
||
Error if provided term is not defeq.
|
||
-/
|
||
/--
|
||
error: Provided term
|
||
23
|
||
is not definitionally equal to
|
||
x := 22
|
||
-/
|
||
#guard_msgs in
|
||
example : let x := 22; 0 ≤ x := by
|
||
intro x
|
||
clear_value (h : x = 23)
|
||
|
||
/-!
|
||
Error if unification does not solve all metavariables.
|
||
-/
|
||
/--
|
||
error: don't know how to synthesize placeholder for argument 'e'
|
||
context:
|
||
x : Nat := ⋯
|
||
⊢ Nat
|
||
---
|
||
error: unsolved goals
|
||
x : Nat := 22
|
||
⊢ 0 ≤ x
|
||
-/
|
||
#guard_msgs in
|
||
example : let x := 22; 0 ≤ x := by
|
||
intro x
|
||
clear_value (h : x = if True then _ else _)
|
||
|
||
/-!
|
||
Check `clear_value` with `_` for binder for equality hypothesis.
|
||
-/
|
||
/--
|
||
trace: x : Nat
|
||
h✝ : x = 22
|
||
⊢ 0 ≤ 22
|
||
-/
|
||
#guard_msgs in
|
||
example : let x := 22; 0 ≤ x := by
|
||
intro x
|
||
clear_value (_ : x = _)
|
||
rw [‹x = 22›]
|
||
trace_state
|
||
exact Nat.zero_le _
|
||
|
||
/-!
|
||
Check that `clear_value` checks for type correctness.
|
||
-/
|
||
/--
|
||
error: Tactic `clear_value` failed, the value of `x` cannot be cleared.
|
||
x : Nat := 22
|
||
y : Fin x := 0
|
||
⊢ ↑y < x
|
||
-/
|
||
#guard_msgs in
|
||
example : let x := 22; let y : Fin x := 0; y.1 < x := by
|
||
intro x y
|
||
clear_value x
|
||
|
||
/-!
|
||
Even though we cannot clear `x` on its own, we can clear it if we clear `y` first.
|
||
-/
|
||
/--
|
||
trace: x : Nat
|
||
y : Fin x
|
||
⊢ ↑y < x
|
||
-/
|
||
#guard_msgs in
|
||
example : let x := 22; let y : Fin x := 0; y.1 < x := by
|
||
intro x y
|
||
clear_value y
|
||
clear_value x
|
||
trace_state
|
||
exact y.2
|
||
|
||
/-!
|
||
We can clear the values `x` and `y` in any order.
|
||
-/
|
||
/--
|
||
trace: x : Nat
|
||
y : Fin x
|
||
⊢ ↑y < x
|
||
-/
|
||
#guard_msgs in
|
||
example : let x := 22; let y : Fin x := 0; y.1 < x := by
|
||
intro x y
|
||
clear_value y x
|
||
trace_state
|
||
exact y.2
|
||
/--
|
||
trace: x : Nat
|
||
y : Fin x
|
||
⊢ ↑y < x
|
||
-/
|
||
#guard_msgs in
|
||
example : let x := 22; let y : Fin x := 0; y.1 < x := by
|
||
intro x y
|
||
clear_value x y
|
||
trace_state
|
||
exact y.2
|
||
|
||
/-!
|
||
Clearing `x` on its own and `y` on its own are OK because `y + 1` is nonzero.
|
||
-/
|
||
/--
|
||
trace: x y : Nat
|
||
z : Fin (y + 1) := 0
|
||
⊢ ↑z < y + 1
|
||
-/
|
||
#guard_msgs in
|
||
example : let x := 22; let y : Nat := x; let z : Fin (y + 1) := 0; z.1 < y + 1 := by
|
||
intro x y z
|
||
clear_value x
|
||
clear_value y
|
||
trace_state
|
||
exact z.2
|
||
|
||
/-!
|
||
Dependence, `clear_value` fails.
|
||
-/
|
||
/--
|
||
error: Tactic `clear_value` failed, the value of `α` cannot be cleared.
|
||
α : Type := Nat
|
||
x : α := 1
|
||
⊢ x = x
|
||
-/
|
||
#guard_msgs in
|
||
example : let α := Nat; let x : α := 1; @Eq α x x := by
|
||
intro α x
|
||
clear_value α
|
||
|
||
/-!
|
||
`clear_value *` manages to clear `x` but not `α`
|
||
-/
|
||
/--
|
||
trace: α : Type := Nat
|
||
x : α
|
||
⊢ x = 1
|
||
---
|
||
warning: declaration uses 'sorry'
|
||
-/
|
||
#guard_msgs in
|
||
example : let α := Nat; let x : α := 1; @Eq α x 1 := by
|
||
intro α x
|
||
clear_value *
|
||
trace_state
|
||
sorry
|
||
|
||
/-!
|
||
`clear_value *` fails if `x` is already cleared
|
||
-/
|
||
/--
|
||
error: Tactic `clear_value` failed to clear any values.
|
||
α : Type := Nat
|
||
x : α
|
||
⊢ x = 1
|
||
-/
|
||
#guard_msgs in
|
||
example : let α := Nat; let x : α := 1; @Eq α x 1 := by
|
||
intro α x
|
||
clear_value x
|
||
clear_value *
|
||
|
||
/-!
|
||
Can use `*` along with `(h : x = _)`.
|
||
-/
|
||
#guard_msgs in
|
||
example : let α := Nat; let x : α := 1; @Eq α x 1 := by
|
||
intro α x
|
||
clear_value (h : x = _) *
|
||
exact h
|
||
|
||
/-!
|
||
Minimized from https://github.com/leanprover-community/mathlib4/issues/25053
|
||
The issue is that `by exact k` under the binder creates a delayed assigned metavariable
|
||
```
|
||
?m : ℕ →
|
||
let val := 22;
|
||
val = 22 → ℕ → ℕ
|
||
```
|
||
that is applied to `val` and `val_def`. Note that this first `ℕ` corresponds to `val`,
|
||
so, despite the fact that the local context of the metavariable delayed assigned to `?m`
|
||
has `val` as a let binding, the value is still being passed in (the types are *not* ensuring
|
||
that `val` is defeq to the expected `val`!)
|
||
|
||
In this case, we can fix the issue by making sure to instantiate metavariables before running
|
||
`isTypeCorrect`.
|
||
-/
|
||
example : True := by
|
||
let val := 22
|
||
have val_def : val = 22 := rfl
|
||
have : Nat.zero = (fun k => by exact k) Nat.zero := by
|
||
clear_value val -- used to fail
|
||
rfl
|
||
trivial
|
||
|
||
/-!
|
||
Local context order preservation.
|
||
-/
|
||
/--
|
||
trace: x : Nat
|
||
y : Nat := 2
|
||
z : Nat := 3
|
||
⊢ x + y = x + z - 1
|
||
-/
|
||
#guard_msgs in
|
||
example : let x := 1; let y := 2; let z := 3; x + y = x + z - 1 := by
|
||
intro x y z
|
||
clear_value x
|
||
trace_state
|
||
rfl
|