lean4-htt/tests/lean/run/mvcgenInvariantsWith.lean
Sebastian Graf 5f4d724c2d
feat: abstract metavariables when generalizing match motives (#8099) (#11696)
This PR improves `match` generalization such that it abstracts
metavariables in types of local variables and in the result type of the
match over the match discriminants. Previously, a metavariable in the
result type would silently default to the behavior of `generalizing :=
false`, and a metavariable in the type of a free variable would lead to
an error (#8099). Example of a `match` that elaborates now but
previously wouldn't:
```lean
example (a : Nat) (ha : a = 37) :=
    (match a with | 42 => by contradiction | n => n) = 37
```
This is because the result type of the `match` is a metavariable that
was not abstracted over `a` and hence generalization failed; the result
is that `contradiction` cannot pick up the proof `ha : 42 = 37`.
The old behavior can be recovered by passing `(generalizing := false)`
to the `match`.

Furthermore, programs such as the following can now be elaborated:
```lean
example (n : Nat) : Id (Fin (n + 1)) :=
  have jp : ?m := ?rhs
  match n with
  | 0 => ?jmp1
  | n + 1 => ?jmp2
  where finally
  case m => exact Fin (n + 1) → Id (Fin (n + 1))
  case jmp1 => exact jp ⟨0, by decide⟩
  case jmp2 => exact jp ⟨n, by omega⟩
  case rhs => exact pure
```
This is useful for the `do` elaborator.

Fixes #8099.
2025-12-16 14:34:29 +00:00

231 lines
7.8 KiB
Text

import Std.Tactic.Do
import Std
open Std Do
set_option grind.warning false
set_option mvcgen.warning false
def nodup (l : List Int) : Bool := Id.run do
let mut seen : HashSet Int := ∅
for x in l do
if x ∈ seen then
return false
seen := seen.insert x
return true
theorem nodup_correct_vanilla (l : List Int) : nodup l ↔ l.Nodup := by
generalize h : nodup l = r
apply Id.of_wp_run_eq h
mvcgen
case inv1 =>
exact Invariant.withEarlyReturn
(onReturn := fun ret seen => ⌜ret = false ∧ ¬l.Nodup⌝)
(onContinue := fun traversalState seen =>
⌜(∀ x, x ∈ seen ↔ x ∈ traversalState.prefix) ∧ traversalState.prefix.Nodup⌝)
all_goals mleave; grind
theorem nodup_correct_invariants (l : List Int) : nodup l ↔ l.Nodup := by
generalize h : nodup l = r
apply Id.of_wp_run_eq h
mvcgen invariants
· Invariant.withEarlyReturn
(onReturn := fun ret seen => ⌜ret = false ∧ ¬l.Nodup⌝) -- minimal indentation here is part of the test
(onContinue := fun traversalState seen =>
⌜(∀ x, x ∈ seen ↔ x ∈ traversalState.prefix) ∧ traversalState.prefix.Nodup⌝)
all_goals grind
theorem nodup_correct_invariants_with_pretac (l : List Int) : nodup l ↔ l.Nodup := by
generalize h : nodup l = r
apply Id.of_wp_run_eq h
mvcgen invariants
· Invariant.withEarlyReturn
(onReturn := fun ret seen => ⌜ret = false ∧ ¬l.Nodup⌝)
(onContinue := fun traversalState seen =>
⌜(∀ x, x ∈ seen ↔ x ∈ traversalState.prefix) ∧ traversalState.prefix.Nodup⌝)
with grind
theorem nodup_correct_invariants_with_cases (l : List Int) : nodup l ↔ l.Nodup := by
generalize h : nodup l = r
apply Id.of_wp_run_eq h
mvcgen
invariants
· Invariant.withEarlyReturn
(onReturn := fun ret seen => ⌜ret = false ∧ ¬l.Nodup⌝)
(onContinue := fun traversalState seen =>
⌜(∀ x, x ∈ seen ↔ x ∈ traversalState.prefix) ∧ traversalState.prefix.Nodup⌝)
with
| vc1 => grind
| vc2 => grind
| vc3 => grind
| vc4 => grind
| vc5 => grind
theorem nodup_correct_invariants_with_pretac_cases (l : List Int) : nodup l ↔ l.Nodup := by
generalize h : nodup l = r
apply Id.of_wp_run_eq h
mvcgen
invariants
· Invariant.withEarlyReturn
(onReturn := fun ret seen => ⌜ret = false ∧ ¬l.Nodup⌝)
(onContinue := fun traversalState seen =>
⌜(∀ x, x ∈ seen ↔ x ∈ traversalState.prefix) ∧ traversalState.prefix.Nodup⌝)
with mleave -- mleave is a no-op here, but we are just testing the grammar
| vc1 => grind
| vc2 | vc3 | vc4 => grind
| vc5 => grind
theorem nodup_correct_invariants_with_cases_error (l : List Int) : nodup l ↔ l.Nodup := by
generalize h : nodup l = r
apply Id.of_wp_run_eq h
mvcgen
invariants
· Invariant.withEarlyReturn
(onReturn := fun ret seen => ⌜ret = false ∧ ¬l.Nodup⌝)
(onContinue := fun traversalState seen =>
⌜(∀ x, x ∈ seen ↔ x ∈ traversalState.prefix) ∧ traversalState.prefix.Nodup⌝)
with mleave -- mleave is a no-op here, but we are just testing the grammar
| vc1 => grind
| vc2 | vc3 | vc4 => grind
| vc5 => grind
theorem test_with_pretac {m : Option Nat} (h : m = some 4) :
⦃⌜True⌝⦄
(match (generalizing := false) m with
| some n => (set n : StateM Nat PUnit)
| none => set 0)
⦃⇓ _ s => ⌜s = 4⌝⦄ := by
mvcgen with simp_all
theorem test_with_cases {m : Option Nat} (h : m = some 4) :
⦃⌜True⌝⦄
(match (generalizing := false) m with
| some n => (set n : StateM Nat PUnit)
| none => set 0)
⦃⇓ _ s => ⌜s = 4⌝⦄ := by
mvcgen
with
| vc1 => grind
| vc2 => grind
theorem test_with_cases_generalizing {m : Option Nat} (h : m = some 4) :
⦃⌜True⌝⦄
(match m with
| some n => (set n : StateM Nat PUnit)
| none => set 0)
⦃⇓ _ s => ⌜s = 4⌝⦄ := by
mvcgen
with
| vc1 => grind
theorem test_with_pretac_cases {m : Option Nat} (h : m = some 4) :
⦃⌜True⌝⦄
(match (generalizing := false) m with
| some n => (set n : StateM Nat PUnit)
| none => set 0)
⦃⇓ _ s => ⌜s = 4⌝⦄ := by
mvcgen
with simp -- `simp` is a no-op on some goals, but it should not fail
| vc1 => grind
| vc2 => grind
def nodup_twice (l : List Int) : Bool := Id.run do
let mut seen : HashSet Int := ∅
for x in l do
if x ∈ seen then
return false
seen := seen.insert x
let mut seen2 : HashSet Int := ∅
for x in l do
if x ∈ seen2 then
return false
seen2 := seen2.insert x
return true
theorem nodup_twice_correct_invariants_with (l : List Int) : nodup_twice l ↔ l.Nodup := by
generalize h : nodup_twice l = r
apply Id.of_wp_run_eq h
mvcgen
invariants
· Invariant.withEarlyReturn
(onReturn := fun ret seen => ⌜ret = false ∧ ¬l.Nodup⌝)
(onContinue := fun traversalState seen =>
⌜(∀ x, x ∈ seen ↔ x ∈ traversalState.prefix) ∧ traversalState.prefix.Nodup⌝)
· Invariant.withEarlyReturn
(onReturn := fun ret seen => ⌜ret = false ∧ ¬l.Nodup⌝)
(onContinue := fun traversalState seen =>
⌜(∀ x, x ∈ seen ↔ x ∈ traversalState.prefix) ∧ traversalState.prefix.Nodup⌝)
with grind
theorem nodup_twice_correct_invariants_multiple_with (l : List Int) : nodup_twice l ↔ l.Nodup := by
generalize h : nodup_twice l = r
apply Id.of_wp_run_eq h
mvcgen
invariants
· Invariant.withEarlyReturn
(onReturn := fun ret seen => ⌜ret = false ∧ ¬l.Nodup⌝)
(onContinue := fun traversalState seen =>
⌜(∀ x, x ∈ seen ↔ x ∈ traversalState.prefix) ∧ traversalState.prefix.Nodup⌝)
· Invariant.withEarlyReturn
(onReturn := fun ret seen => ⌜ret = false ∧ ¬l.Nodup⌝)
(onContinue := fun traversalState seen =>
⌜(∀ x, x ∈ seen ↔ x ∈ traversalState.prefix) ∧ traversalState.prefix.Nodup⌝)
with grind
/--
error: Lacking definitions for the following invariants.
Invariant l (MProd (Option Bool) (HashSet Int)) PostShape.pure
-/
#guard_msgs in
theorem nodup_twice_missing_one_invariant (l : List Int) : nodup_twice l ↔ l.Nodup := by
generalize h : nodup_twice l = r
apply Id.of_wp_run_eq h
mvcgen
invariants
· Invariant.withEarlyReturn
(onReturn := fun ret seen => ⌜ret = false ∧ ¬l.Nodup⌝)
(onContinue := fun traversalState seen =>
⌜(∀ x, x ∈ seen ↔ x ∈ traversalState.prefix) ∧ traversalState.prefix.Nodup⌝)
with grind
/--
error: Lacking definitions for the following invariants.
Invariant l (MProd (Option Bool) (HashSet Int)) PostShape.pure
Invariant l (MProd (Option Bool) (HashSet Int)) PostShape.pure
-/
#guard_msgs in
theorem nodup_twice_missing_two_invariants (l : List Int) : nodup_twice l ↔ l.Nodup := by
generalize h : nodup_twice l = r
apply Id.of_wp_run_eq h
mvcgen
invariants
with grind
def copy (l : List Nat) : Id (Array Nat) := do
let mut acc := #[]
for x in l do
acc := acc.push x
return acc
set_option warn.sorry false in
theorem copy_labelled_invariants (l : List Nat) : ⦃⌜True⌝⦄ copy l ⦃⇓ r => ⌜r = l.toArray⌝⦄ := by
mvcgen [copy] invariants
| inv1 acc => ⇓ ⟨xs, letMuts⟩ => ⌜acc = l.toArray⌝
with admit
set_option warn.sorry false in
theorem copy_labelled_invariants_noname (l : List Nat) : ⦃⌜True⌝⦄ copy l ⦃⇓ r => ⌜r = l.toArray⌝⦄ := by
mvcgen [copy] invariants
| _ acc => ⇓ ⟨xs, letMuts⟩ => ⌜acc = l.toArray⌝
with admit
/-- error: Alternation between labelled and bulleted invariants is not supported. -/
#guard_msgs in
theorem copy_labelled_invariants_dontmix (l : List Nat) : ⦃⌜True⌝⦄ copy l ⦃⇓ r => ⌜r = l.toArray⌝⦄ := by
mvcgen [copy] invariants
· ⇓ ⟨xs, letMuts⟩ => ⌜True⌝
| _ acc => ⇓ ⟨xs, letMuts⟩ => ⌜acc = l.toArray⌝
with admit