lean4-htt/tests/lean/run/match2.lean
Joachim Breitner af6d2077a0
refactor: use match compilation to generate splitter (#11220)
This PR changes how match splitters are generated: Rather than rewriting
the match statement, the match compilation pipeline is used again.


The benefits are:

* Re-doing the match compilation means we can do more intelligent book
keeping, e.g. prove overlap assumptions only once and re-use the proof,
or prune the context of the MVar to speed up `contradiction`. This may
have allowed a different solution than #11200.
 
* It would unblock #11105, as the existing splitter implementation would
have trouble dealing with the matchers produced that way.
 
* It provides the necessary machinery also for source-exposed “none of
the above” bindings, a feature that we probably want at some point (and
we mostly need to find good syntax for, see #3136, although maybe I
should open a dedicated RFC).

* It allows us to skip costly things during matcher creation that would
only be useful for the splitter, and thus allows performance
improvements like #11508.
 
 * We can drop the existing implementation.
 
It’s not entirely free:

* We have to run `simpH` twice, once for the match equations and once
for the splitter.
2025-12-04 15:03:13 +00:00

174 lines
4.8 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
set_option linter.unusedVariables false
def checkWithMkMatcherInput (matcher : Lean.Name) : Lean.MetaM Unit :=
Lean.Meta.Match.withMkMatcherInput matcher (unfoldNamed := false) fun input => do
let res ← Lean.Meta.Match.mkMatcher input
let origMatcher ← Lean.getConstInfo matcher
if not <| input.matcherName == matcher then
throwError "matcher name not reconstructed correctly: {matcher} ≟ {input.matcherName}"
let lCtx ← Lean.getLCtx
let fvars := Lean.collectFVars {} res.matcher
let closure := Lean.Meta.Closure.mkLambda (fvars.fvarSet.toList.toArray.map lCtx.get!) res.matcher
let origTy := origMatcher.value!
let newTy := closure
if not <| ←Lean.Meta.isDefEq origTy newTy then
throwError "matcher {matcher} does not round-trip correctly:\n{origTy} ≟ {newTy}"
set_option smartUnfolding false
def f (xs : List Nat) : List Bool :=
xs.map fun
| 0 => true
| _ => false
#guard_msgs in
#eval checkWithMkMatcherInput ``f.match_1
#guard f [1, 2, 0, 2] == [false, false, true, false]
theorem ex1 : f [1, 0, 2] = [false, true, false] :=
rfl
#check f
set_option pp.raw true
set_option pp.raw.maxDepth 10
set_option trace.Elab.step true in
def g (xs : List Nat) : List Bool :=
xs.map <| by {
intro
| 0 => exact true
| _ => exact false
}
theorem ex2 : g [1, 0, 2] = [false, true, false] :=
rfl
theorem ex3 {p q r : Prop} : p q → r → (q ∧ r) (p ∧ r) :=
by intro
| Or.inl hp, h => { apply Or.inr; apply And.intro; assumption; assumption }
| Or.inr hq, h => { apply Or.inl; exact ⟨hq, h⟩ }
inductive C
| mk₁ : Nat → C
| mk₂ : Nat → Nat → C
def C.x : C → Nat
| C.mk₁ x => x
| C.mk₂ x _ => x
#eval checkWithMkMatcherInput ``C.x.match_1
def head : {α : Type} → List α → Option α
| _, a::as => some a
| _, _ => none
#eval checkWithMkMatcherInput ``head.match_1
theorem ex4 : head [1, 2] = some 1 :=
rfl
def head2 : {α : Type} → List α → Option α :=
@fun
| _, a::as => some a
| _, _ => none
theorem ex5 : head2 [1, 2] = some 1 :=
rfl
def head3 {α : Type} (xs : List α) : Option α :=
let rec aux {α : Type} : List α → Option α
| a::_ => some a
| _ => none;
aux xs
theorem ex6 : head3 [1, 2] = some 1 :=
rfl
inductive Vec.{u} (α : Type u) : Nat → Type u
| nil : Vec α 0
| cons {n} (head : α) (tail : Vec α n) : Vec α (n+1)
def Vec.mapHead1 {α β δ} : {n : Nat} → Vec α n → Vec β n → (α → β → δ) → Option δ
| _, nil, nil, f => none
| _, cons a as, cons b bs, f => some (f a b)
def Vec.mapHead2 {α β δ} : {n : Nat} → Vec α n → Vec β n → (α → β → δ) → Option δ
| _, nil, nil, f => none
| _, @cons _ n a as, cons b bs, f => some (f a b)
set_option pp.match false in
#print Vec.mapHead2 -- reused Vec.mapHead1.match_1
def Vec.mapHead3 {α β δ} : {n : Nat} → Vec α n → Vec β n → (α → β → δ) → Option δ
| _, nil, nil, f => none
| _, cons (tail := as) (head := a), cons b bs, f => some (f a b)
inductive Foo
| mk₁ (x y z w : Nat)
| mk₂ (x y z w : Nat)
def Foo.z : Foo → Nat
| mk₁ (z := z) .. => z
| mk₂ (z := z) .. => z
#eval checkWithMkMatcherInput ``Foo.z.match_1
#guard (Foo.mk₁ 10 20 30 40).z == 30
theorem ex7 : (Foo.mk₁ 10 20 30 40).z = 30 :=
rfl
def Foo.addY? : Foo × Foo → Option Nat
| (mk₁ (y := y₁) .., mk₁ (y := y₂) ..) => some (y₁ + y₂)
| _ => none
#eval checkWithMkMatcherInput ``Foo.addY?.match_1
#guard Foo.addY? (Foo.mk₁ 1 2 3 4, Foo.mk₁ 10 20 30 40) == some 22
theorem ex8 : Foo.addY? (Foo.mk₁ 1 2 3 4, Foo.mk₁ 10 20 30 40) = some 22 :=
rfl
instance {α} : Inhabited (Sigma fun m => Vec α m) :=
⟨⟨0, Vec.nil⟩⟩
partial def filter {α} (p : α → Bool) : {n : Nat} → Vec α n → Sigma fun m => Vec α m
| _, Vec.nil => ⟨0, Vec.nil⟩
| _, Vec.cons x xs => match p x, filter p xs with
| true, ⟨_, ys⟩ => ⟨_, Vec.cons x ys⟩
| false, ys => ys
#eval checkWithMkMatcherInput ``filter.match_1
inductive Bla
| ofNat (x : Nat)
| ofBool (x : Bool)
def Bla.optional? : Bla → Option Nat
| ofNat x => some x
| ofBool _ => none
#eval checkWithMkMatcherInput ``Bla.optional?.match_1
def Bla.isNat? (b : Bla) : Option { x : Nat // optional? b = some x } :=
match b.optional? with
| some y => some ⟨y, rfl⟩
| none => none
#eval checkWithMkMatcherInput ``Bla.isNat?.match_1
def foo (b : Bla) : Option Nat := b.optional?
theorem fooEq (b : Bla) : foo b = b.optional? :=
rfl
def Bla.isNat2? (b : Bla) : Option { x : Nat // optional? b = some x } :=
match h : foo b with
| some y => some ⟨y, Eq.trans (fooEq b).symm h⟩
| none => none
#eval checkWithMkMatcherInput ``Bla.isNat2?.match_1
def foo2 (x : Nat) : Nat :=
match (motive := (y : Nat) → x = y → Nat) x, rfl with
| 0, h => 0
| x+1, h => 1
#eval checkWithMkMatcherInput ``foo2.match_1