lean4-htt/tests/lean/run/letToHave.lean
Kyle Miller 71cf266cd7
feat: add Meta.letToHave and the let_to_have tactic (#8954)
This PR adds a procedure that efficiently transforms `let` expressions
into `have` expressions (`Meta.letToHave`). This is exposed as the
`let_to_have` tactic.

It uses the `withTrackingZetaDelta` technique: the expression is
typechecked, and any `let` variables that don't enter the zeta delta set
are nondependent. The procedure uses a number of heuristics to limit the
amount of typechecking performed. For example, it is ok to skip
subexpressions that do not contain fvars, mvars, or `let`s.
2025-06-24 01:33:53 +00:00

325 lines
7 KiB
Text
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import Lean
/-!
# Tests of the `letToHave` transformation
-/
set_option pp.letVarTypes true
set_option pp.mvars.anonymous false
open Lean Elab Command in
/--
`#let_to_have t` elaborates `t` then applies let-to-have. It typechecks `t` before and after.
-/
elab "#let_to_have " t:term : command => runTermElabM fun _ => do
let e ← Term.elabTermAndSynthesize t none
Meta.check e
let e' ← Meta.letToHave e
Meta.check e'
unless ← Meta.isDefEq e e' do
throwError "result is not definitionally equal"
if e == e' then
logInfo m!"no change"
else
logInfo m!"{e'}"
set_option linter.unusedVariables false
/-!
Very basic tests where there are no lets.
-/
/-- info: no change -/
#guard_msgs in #let_to_have true
/-- info: no change -/
#guard_msgs in #let_to_have fun (x : Nat) => x + 1
/-- info: no change -/
#guard_msgs in #let_to_have ∀ (x : Nat), x = x
/-- info: no change -/
#guard_msgs in #let_to_have have x := 1; x + 1
/-!
Basic tests of nondependent `let`s.
-/
/--
info: have x : Nat := 1;
true
-/
#guard_msgs in #let_to_have let x := 1; true
/--
info: have x : Nat := 1;
x + 1
-/
#guard_msgs in #let_to_have let x := 1; x + 1
/--
info: have x : Nat := 1;
have x' : Nat := x;
have x'' : Nat := x + x';
have x''' : Nat := x + x' + x'';
x + x' + x'' + x'''
-/
#guard_msgs in #let_to_have
let x : Nat := 1; let x' := x; let x'' := x + x'; let x''' := x + x' + x''; x + x' + x'' + x'''
/--
info: fun x =>
have x' : Nat := x;
have x'' : Nat := x + x';
have x''' : Nat := x + x' + x'';
x + x' + x'' + x'''
-/
#guard_msgs in #let_to_have
fun x : Nat => let x' := x; let x'' := x + x'; let x''' := x + x' + x''; x + x' + x'' + x'''
/--
info: (x : Nat) →
have x' : Nat := x;
have x'' : Nat := x + x';
have x''' : Nat := x + x' + x'';
Fin (x + x' + x'' + x''')
-/
#guard_msgs in #let_to_have
∀ x : Nat, let x' := x; let x'' := x + x'; let x''' := x + x' + x''; Fin (x + x' + x'' + x''')
/-!
Testing the cache. Reports one expression transformed.
-/
/--
info: (have x : Nat := 1;
x + 1) +
have x : Nat := 1;
x + 1
---
trace: [Meta.letToHave] ✅️ transformed 1 `let` expressions into `have` expressions
[Meta.letToHave] result:
(have x : Nat := 1;
x + 1) +
have x : Nat := 1;
x + 1
-/
#guard_msgs in
set_option trace.Meta.letToHave true in
#let_to_have (let x := 1; x + 1) + (let x := 1; x + 1)
/-!
Cache uses strict equality. Preserves binder names.
-/
/--
info: (have x : Nat := 1;
x + 1) +
have y : Nat := 1;
y + 1
-/
#guard_msgs in
#let_to_have (let x := 1; x + 1) + (let y := 1; y + 1)
/-!
The expression `(1 + 2 + 3 + 4 + 5 + 6)` enters the cache first without a type,
but then a type needs to be computed under the `let`.
-/
/--
info: 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 +
have y : Nat := 1;
1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + y
-/
#guard_msgs in
#let_to_have (1 + 2 + 3 + 4 + 5 + 6 + 7 + 8) + (let y := 1; (1 + 2 + 3 + 4 + 5 + 6 + 7 + 8) + y)
/-!
Dependence in a let value.
-/
/--
info: let x : Nat := 1;
have h : x = 1 := ⋯;
x
-/
#guard_msgs in #let_to_have let x := 1; let h : x = 1 := rfl; x
/-!
Dependence in a let type.
-/
/--
info: let x : Nat := 1;
have h : 0 = 0 := ⋯;
x
-/
#guard_msgs in #let_to_have let x := 1; let h : (0 : Fin x) = (0 : Fin x) := rfl; x
/-!
No dependence from the let type.
-/
/--
info: have x : Nat := 1;
have h : 0 = 0 := ⋯;
x
-/
#guard_msgs in #let_to_have let x := 1; let h : (0 : Fin (x + 1)) = (0 : Fin (x + 1)) := rfl; x
/-!
Another dependence in the let type.
-/
/--
info: let x : Nat := 1;
have h : 0 = 0 := ⋯;
x
-/
#guard_msgs in #let_to_have let x := 1; let h : (0 : Fin (x + 1)) = (0 : Fin (1 + 1)) := rfl; x
/-!
Dependence in a forall type
-/
/--
info: let U : Type 1 := Type;
have α : U := Nat;
∀ (n : α), n = n
-/
#guard_msgs in #let_to_have let U := Type; let α : U := Nat; ∀ (n : α), n = n
/-!
Dependence in a forall body
-/
/--
info: let U : Type 1 := Type;
have α : U := Nat;
Bool → α
-/
#guard_msgs in #let_to_have let U := Type; let α : U := Nat; Bool → α
/-!
Dependence in a lambda type
-/
/--
info: let U : Type 1 := Type;
have α : U := Nat;
fun n => n
-/
#guard_msgs in #let_to_have let U := Type; let α : U := Nat; fun (n : α) => n
/-!
Metavariable under binder, might be dependent, doesn't change.
-/
/-- info: no change -/
#guard_msgs in #let_to_have let x := 1; ?m
/-!
Metavariable has a context that doesn't include `x`, so it doesn't depend on it.
-/
/--
info: have x : ?_ := ?m;
?m
-/
#guard_msgs in #let_to_have let x := ?m; ?m
/-!
Similar, but we see that the outer `let` remains dependent.
-/
/--
info: let x : Nat := 1;
have y : ?_ := ?m;
?m
-/
#guard_msgs in #let_to_have let x := 1; let y := ?m; ?m
/-!
Test with a deep let expression.
-/
syntax "deepLets% " num term:arg term:arg : term
macro_rules
| `(deepLets% 0 $b $e) => `(if $b then $e else 0)
| `(deepLets% $n $b $e) =>
let n' : Lean.Syntax.NumLit := Lean.quote (n.getNat - 1)
`(let b' : Bool := !$b; let x : Nat := 1*$e; deepLets% $n' b' x)
/--
info: fun a =>
let b' : Bool := !true;
let x : Nat := 1 * (0 + a);
let b' : Bool := !b';
let x : Nat := 1 * x;
let b' : Bool := !b';
let x : Nat := 1 * x;
let b' : Bool := !b';
let x : Nat := 1 * x;
let b' : Bool := !b';
let x : Nat := 1 * x;
if b' = true then x else 0 : Nat → Nat
-/
#guard_msgs in #check fun a => deepLets% 5 true (0 + a)
/--
info: fun a =>
have b' : Bool := !true;
have x : Nat := 1 * (0 + a);
have b' : Bool := !b';
have x : Nat := 1 * x;
have b' : Bool := !b';
have x : Nat := 1 * x;
have b' : Bool := !b';
have x : Nat := 1 * x;
have b' : Bool := !b';
have x : Nat := 1 * x;
if b' = true then x else 0
-/
#guard_msgs in #let_to_have fun a => deepLets% 5 true (0 + a)
/--
info: fun a =>
have b' : Bool := !true;
have x : Nat := 1 * (0 + a);
have b' : Bool := !b';
have x : Nat := 1 * x;
have b' : Bool := !b';
have x : Nat := 1 * x;
have b' : Bool := !b';
have x : Nat := 1 * x;
have b' : Bool := !b';
have x : Nat := ⋯;
-/
#guard_msgs in set_option pp.deepTerms.threshold 10 in #let_to_have fun a => deepLets% 33 true (0 + a)
/--
info: fun a =>
have b' : Bool := !true;
have x : Nat := 1 * (0 + a);
have b' : Bool := !b';
have x : Nat := 1 * x;
have b' : Bool := !b';
have x : Nat := 1 * x;
have b' : Bool := !b';
have x : Nat := 1 * x;
have b' : Bool := !b';
have x : Nat := ⋯;
-/
#guard_msgs in set_option pp.deepTerms.threshold 10 in #let_to_have fun a => deepLets% 150 true (0 + a)
/-!
Tests of the `let_to_have` tactic.
-/
/--
trace: h :
have x : Nat := 1;
x = x
⊢ have y : Nat := 1;
y = y
-/
#guard_msgs in
example (h : let x := 1; x = x) : let y := 1; y = y := by
let_to_have at *
trace_state
rfl
/--
trace: h :
let x : Nat := 1;
x = x
⊢ have y : Nat := 1;
y = y
-/
#guard_msgs in
example (h : let x := 1; x = x) : let y := 1; y = y := by
let_to_have
trace_state
rfl
/--
trace: h :
have x : Nat := 1;
x = x
⊢ let y : Nat := 1;
y = y
-/
#guard_msgs in
example (h : let x := 1; x = x) : let y := 1; y = y := by
let_to_have at h
trace_state
rfl