feat: congruence equations for matchers (#8284)

This PR adds a new variant of equations for matchers, namely “congruence
equations” that generalize the normal matcher equations. They have
unrestricted left-hand-sides, extra equality assumptions relating the
discriminiants with the patterns and thus prove heterogenous equalities.
In that sense they combine congruence with rewriting. They can be used
to rewrite matcher applications where, due to dependencies, `simp` would
fail to rewrite the discriminants, and will be used when producing the
unfolding induction theorems.
This commit is contained in:
Joachim Breitner 2025-05-11 15:04:59 +02:00 committed by GitHub
parent ca73223d4c
commit dc1a70fa43
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 415 additions and 64 deletions

View file

@ -98,9 +98,68 @@ def unfoldNamedPattern (e : Expr) : MetaM Expr := do
1. Eliminates arguments for named parameters and the associated equation proofs.
2. Equality parameters associated with the `h : discr` notation are replaced with `rfl` proofs.
Recall that this kind of parameter always occurs after the parameters correspoting to pattern variables.
`numNonEqParams` is the size of the prefix.
2. Instantiate the `Unit` parameter of an otherwise argumentless alternative.
It does not handle the equality parameters associated with the `h : discr` notation.
The continuation `k` takes four arguments `ys args mask type`.
- `ys` are variables for the hypotheses that have not been eliminated.
- `args` are the arguments for the alternative `alt` that has type `altType`. `ys.size <= args.size`
- `mask[i]` is true if the hypotheses has not been eliminated. `mask.size == args.size`.
- `type` is the resulting type for `altType`.
We use the `mask` to build the splitter proof. See `mkSplitterProof`.
This can be used to use the alternative of a match expression in its splitter.
-/
partial def forallAltVarsTelescope (altType : Expr) (altNumParams numDiscrEqs : Nat)
(k : (patVars : Array Expr) → (args : Array Expr) → (mask : Array Bool) → (type : Expr) → MetaM α) : MetaM α := do
go #[] #[] #[] 0 altType
where
go (ys : Array Expr) (args : Array Expr) (mask : Array Bool) (i : Nat) (type : Expr) : MetaM α := do
let type ← whnfForall type
if i < altNumParams - numDiscrEqs then
let Expr.forallE n d b .. := type
| throwError "expecting {altNumParams} parameters, excluding {numDiscrEqs} equalities, but found type{indentExpr altType}"
-- Handle the special case of `Unit` parameters.
if i = 0 && altNumParams - numDiscrEqs = 1 && d.isConstOf ``Unit && !b.hasLooseBVars then
return ← k #[] #[mkConst ``Unit.unit] #[false] b
let d ← Match.unfoldNamedPattern d
withLocalDeclD n d fun y => do
let typeNew := b.instantiate1 y
if let some (_, lhs, rhs) ← matchEq? d then
if lhs.isFVar && ys.contains lhs && args.contains lhs && isNamedPatternProof typeNew y then
let some j := ys.finIdxOf? lhs | unreachable!
let ys := ys.eraseIdx j
let some k := args.idxOf? lhs | unreachable!
let mask := mask.set! k false
let args := args.map fun arg => if arg == lhs then rhs else arg
let arg ← mkEqRefl rhs
let typeNew := typeNew.replaceFVar lhs rhs
return ← withReplaceFVarId lhs.fvarId! rhs do
withReplaceFVarId y.fvarId! arg do
go ys (args.push arg) (mask.push false) (i+1) typeNew
go (ys.push y) (args.push y) (mask.push true) (i+1) typeNew
else
let type ← Match.unfoldNamedPattern type
k ys args mask type
isNamedPatternProof (type : Expr) (h : Expr) : Bool :=
Option.isSome <| type.find? fun e =>
if let some e := Match.isNamedPattern? e then
e.appArg! == h
else
false
/--
Extension of `forallAltTelescope` that continues further:
Equality parameters associated with the `h : discr` notation are replaced with `rfl` proofs.
Recall that this kind of parameter always occurs after the parameters corresponding to pattern
variables.
The continuation `k` takes four arguments `ys args mask type`.
- `ys` are variables for the hypotheses that have not been eliminated.
@ -116,57 +175,45 @@ def unfoldNamedPattern (e : Expr) : MetaM Expr := do
partial def forallAltTelescope (altType : Expr) (altNumParams numDiscrEqs : Nat)
(k : (ys : Array Expr) → (eqs : Array Expr) → (args : Array Expr) → (mask : Array Bool) → (type : Expr) → MetaM α)
: MetaM α := do
go #[] #[] #[] #[] 0 altType
forallAltVarsTelescope altType altNumParams numDiscrEqs fun ys args mask altType => do
go ys #[] args mask 0 altType
where
go (ys : Array Expr) (eqs : Array Expr) (args : Array Expr) (mask : Array Bool) (i : Nat) (type : Expr) : MetaM α := do
let type ← whnfForall type
if i < altNumParams then
if i < numDiscrEqs then
let Expr.forallE n d b .. := type
| throwError "expecting {altNumParams} parameters, including {numDiscrEqs} equalities, but found type{indentExpr altType}"
if i < altNumParams - numDiscrEqs then
let d ← unfoldNamedPattern d
withLocalDeclD n d fun y => do
let typeNew := b.instantiate1 y
if let some (_, lhs, rhs) ← matchEq? d then
if lhs.isFVar && ys.contains lhs && args.contains lhs && isNamedPatternProof typeNew y then
let some j := ys.finIdxOf? lhs | unreachable!
let ys := ys.eraseIdx j
let some k := args.idxOf? lhs | unreachable!
let mask := mask.set! k false
let args := args.map fun arg => if arg == lhs then rhs else arg
let arg ← mkEqRefl rhs
let typeNew := typeNew.replaceFVar lhs rhs
return ← withReplaceFVarId lhs.fvarId! rhs do
withReplaceFVarId y.fvarId! arg do
go ys eqs (args.push arg) (mask.push false) (i+1) typeNew
go (ys.push y) eqs (args.push y) (mask.push true) (i+1) typeNew
let arg ← if let some (_, _, rhs) ← matchEq? d then
mkEqRefl rhs
else if let some (_, _, _, rhs) ← matchHEq? d then
mkHEqRefl rhs
else
let arg ← if let some (_, _, rhs) ← matchEq? d then
mkEqRefl rhs
else if let some (_, _, _, rhs) ← matchHEq? d then
mkHEqRefl rhs
else
throwError "unexpected match alternative type{indentExpr altType}"
withLocalDeclD n d fun eq => do
let typeNew := b.instantiate1 eq
go ys (eqs.push eq) (args.push arg) (mask.push false) (i+1) typeNew
throwError "unexpected match alternative type{indentExpr altType}"
withLocalDeclD n d fun eq => do
let typeNew := b.instantiate1 eq
go ys (eqs.push eq) (args.push arg) (mask.push false) (i+1) typeNew
else
let type ← unfoldNamedPattern type
/- Recall that alternatives that do not have variables have a `Unit` parameter to ensure
they are not eagerly evaluated. -/
if ys.size == 1 then
if (← inferType ys[0]!).isConstOf ``Unit && !(← dependsOn type ys[0]!.fvarId!) then
let rhs := mkConst ``Unit.unit
return ← withReplaceFVarId ys[0]!.fvarId! rhs do
return (← k #[] #[] #[rhs] #[false] type)
k ys eqs args mask type
isNamedPatternProof (type : Expr) (h : Expr) : Bool :=
Option.isSome <| type.find? fun e =>
if let some e := isNamedPattern? e then
e.appArg! == h
else
false
/--
Given an application of an matcher arm `alt` that is expecting the `numDiscrEqs`, and
an array of `discr = pattern` equalities (one for each discriminant), apply those that
are expected by the alternative.
-/
partial def mkAppDiscrEqs (alt : Expr) (heqs : Array Expr) (numDiscrEqs : Nat) : MetaM Expr := do
go alt (← inferType alt) 0
where
go e ty i := do
if i < numDiscrEqs then
let Expr.forallE n d b .. := ty
| throwError "expecting {numDiscrEqs} equalities, but found type{indentExpr alt}"
for heq in heqs do
if (← isDefEq (← inferType heq) d) then
return ← go (mkApp e heq) (b.instantiate1 heq) (i+1)
throwError "Could not find equation {n} : {d} among {heqs}"
else
return e
namespace SimpH
@ -328,21 +375,33 @@ private def unfoldElimOffset (mvarId : MVarId) : MetaM MVarId := do
mvarId.deltaTarget (· == ``Nat.elimOffset)
/--
Helper method for proving a conditional equational theorem associated with an alternative of
the `match`-eliminator `matchDeclName`. `type` contains the type of the theorem. -/
partial def proveCondEqThm (matchDeclName : Name) (type : Expr) : MetaM Expr := withLCtx {} {} do
Helper method for proving a conditional equational theorem associated with an alternative of
the `match`-eliminator `matchDeclName`. `type` contains the type of the theorem.
The `heqPos`/`heqNum` arguments indicate that these hypotheses are `Eq`/`HEq` hypotheses
to substitute first; this is used for the generalized match equations.
-/
partial def proveCondEqThm (matchDeclName : Name) (type : Expr)
(heqPos : Nat := 0) (heqNum : Nat := 0) : MetaM Expr := withLCtx {} {} do
let type ← instantiateMVars type
forallTelescope type fun ys target => do
let mvar0 ← mkFreshExprSyntheticOpaqueMVar target
trace[Meta.Match.matchEqs] "proveCondEqThm {mvar0.mvarId!}"
let mvarId ← mvar0.mvarId!.deltaTarget (· == matchDeclName)
withDefault <| go mvarId 0
mkLambdaFVars ys (← instantiateMVars mvar0)
let mvar0 ← mkFreshExprSyntheticOpaqueMVar type
trace[Meta.Match.matchEqs] "proveCondEqThm {mvar0.mvarId!}"
let mut mvarId := mvar0.mvarId!
if heqNum > 0 then
mvarId := (← mvarId.introN heqPos).2
for _ in [:heqNum] do
let (h, mvarId') ← mvarId.intro1
mvarId ← subst mvarId' h
trace[Meta.Match.matchEqs] "proveCondEqThm after subst{mvarId}"
mvarId := (← mvarId.intros).2
mvarId ← mvarId.deltaTarget (· == matchDeclName)
mvarId ← mvarId.heqOfEq
go mvarId 0
instantiateMVars mvar0
where
go (mvarId : MVarId) (depth : Nat) : MetaM Unit := withIncRecDepth do
trace[Meta.Match.matchEqs] "proveCondEqThm.go {mvarId}"
let mvarId' ← mvarId.modifyTargetEqLHS whnfCore
let mvarId := mvarId'
let mvarId ← mvarId.modifyTargetEqLHS whnfCore
let subgoals ←
(do mvarId.refl; return #[])
<|>
@ -768,21 +827,121 @@ where go baseName splitterName := withConfig (fun c => { c with etaStruct := .no
let result := { eqnNames, splitterName, splitterAltNumParams }
registerMatchEqns matchDeclName result
def congrEqnThmSuffixBase := "congr_eq"
def congrEqnThmSuffixBasePrefix := congrEqnThmSuffixBase ++ "_"
def congrEqn1ThmSuffix := congrEqnThmSuffixBasePrefix ++ "1"
example : congrEqn1ThmSuffix = "congr_eq_1" := rfl
/-- Returns `true` if `s` is of the form `congr_eq_<idx>` -/
def iscongrEqnReservedNameSuffix (s : String) : Bool :=
congrEqnThmSuffixBasePrefix.isPrefixOf s && (s.drop congrEqnThmSuffixBasePrefix.length).isNat
/- We generate the equations and splitter on demand, and do not save them on .olean files. -/
builtin_initialize matchCongrEqnsExt : EnvExtension (PHashMap Name (Array Name)) ←
-- Using `local` allows us to use the extension in `realizeConst` without specifying `replay?`.
-- The resulting state can still be accessed on the generated declarations using `findStateAsync`;
-- see below
registerEnvExtension (pure {}) (asyncMode := .local)
def registerMatchcongrEqns (matchDeclName : Name) (eqnNames : Array Name) : CoreM Unit := do
modifyEnv fun env => matchCongrEqnsExt.modifyState env fun map =>
map.insert matchDeclName eqnNames
/--
Generate the congruence equations for the given match auxiliary declaration.
The congruence equations have a completely unrestriced left-hand side (arbitrary discriminants),
and take propositional equations relating the discriminants to the patterns as arguments. In this
sense they combine a congruence lemma with the regular equation lemma.
Since the motive depends on the discriminants, they are `HEq` equations.
The code duplicates a fair bit of the logic above, and has to repeat the calculation of the
`notAlts`. One could avoid that and generate the generalized equations eagerly above, but they are
not always needed, so for now we live with the code duplication.
-/
def genMatchCongrEqns (matchDeclName : Name) : MetaM (Array Name) := do
let baseName := mkPrivateName (← getEnv) matchDeclName
let firstEqnName := .str baseName congrEqn1ThmSuffix
realizeConst matchDeclName firstEqnName (go baseName)
return matchCongrEqnsExt.findStateAsync (← getEnv) firstEqnName |>.find! matchDeclName
where go baseName := withConfig (fun c => { c with etaStruct := .none }) do
withConfig (fun c => { c with etaStruct := .none }) do
let constInfo ← getConstInfo matchDeclName
let us := constInfo.levelParams.map mkLevelParam
let some matchInfo ← getMatcherInfo? matchDeclName | throwError "'{matchDeclName}' is not a matcher function"
let numDiscrEqs := matchInfo.getNumDiscrEqs
forallTelescopeReducing constInfo.type fun xs _matchResultType => do
let mut eqnNames := #[]
let params := xs[:matchInfo.numParams]
let motive := xs[matchInfo.getMotivePos]!
let alts := xs[xs.size - matchInfo.numAlts:]
let firstDiscrIdx := matchInfo.numParams + 1
let discrs := xs[firstDiscrIdx : firstDiscrIdx + matchInfo.numDiscrs]
let mut notAlts := #[]
let mut idx := 1
for i in [:alts.size] do
let altNumParams := matchInfo.altNumParams[i]!
let thmName := (Name.str baseName congrEqnThmSuffixBase).appendIndexAfter idx
eqnNames := eqnNames.push thmName
let notAlt ← do
let alt := alts[i]!
Match.forallAltVarsTelescope (← inferType alt) altNumParams numDiscrEqs fun altVars args _mask altResultType => do
let patterns ← forallTelescope altResultType fun _ t => pure t.getAppArgs
let mut heqsTypes := #[]
assert! patterns.size == discrs.size
for discr in discrs, pattern in patterns do
let heqType ← mkEqHEq discr pattern
heqsTypes := heqsTypes.push ((`heq).appendIndexAfter (heqsTypes.size + 1), heqType)
withLocalDeclsDND heqsTypes fun heqs => do
let rhs ← Match.mkAppDiscrEqs (mkAppN alt args) heqs numDiscrEqs
let mut hs := #[]
for notAlt in notAlts do
let h ← instantiateForall notAlt patterns
if let some h ← Match.simpH? h patterns.size then
hs := hs.push h
trace[Meta.Match.matchEqs] "hs: {hs}"
let mut notAlt := mkConst ``False
for discr in discrs.toArray.reverse, pattern in patterns.reverse do
notAlt ← mkArrow (← mkEqHEq discr pattern) notAlt
notAlt ← mkForallFVars (discrs ++ altVars) notAlt
let lhs := mkAppN (mkConst constInfo.name us) (params ++ #[motive] ++ discrs ++ alts)
let thmType ← mkHEq lhs rhs
let thmType ← hs.foldrM (init := thmType) (mkArrow · ·)
let thmType ← mkForallFVars (params ++ #[motive] ++ discrs ++ alts ++ altVars ++ heqs) thmType
let thmType ← Match.unfoldNamedPattern thmType
-- Here we prove the theorem from scratch. One could likely also use the (non-generalized)
-- match equation theorem after subst'ing the `heqs`.
let thmVal ← Match.proveCondEqThm matchDeclName thmType
(heqPos := params.size + 1 + discrs.size + alts.size + altVars.size) (heqNum := heqs.size)
unless (← getEnv).contains thmName do
addDecl <| Declaration.thmDecl {
name := thmName
levelParams := constInfo.levelParams
type := thmType
value := thmVal
}
return notAlt
notAlts := notAlts.push notAlt
idx := idx + 1
registerMatchcongrEqns matchDeclName eqnNames
builtin_initialize registerTraceClass `Meta.Match.matchEqs
private def isMatchEqName? (env : Environment) (n : Name) : Option Name := do
private def isMatchEqName? (env : Environment) (n : Name) : Option (Name × Bool) := do
let .str p s := n | failure
guard <| isEqnReservedNameSuffix s || s == "splitter"
guard <| isEqnReservedNameSuffix s || s == "splitter" || iscongrEqnReservedNameSuffix s
let p ← privateToUserName? p
guard <| isMatcherCore env p
return p
return (p, iscongrEqnReservedNameSuffix s)
builtin_initialize registerReservedNamePredicate (isMatchEqName? · · |>.isSome)
builtin_initialize registerReservedNameAction fun name => do
let some p := isMatchEqName? (← getEnv) name |
let some (p, isGenEq) := isMatchEqName? (← getEnv) name |
return false
let _ ← MetaM.run' <| getEquationsFor p
if isGenEq then
let _ ← MetaM.run' <| genMatchCongrEqns p
else
let _ ← MetaM.run' <| getEquationsFor p
return true
end Lean.Meta.Match

View file

@ -190,8 +190,8 @@ private def forallAltTelescope'
{α} (origAltType : Expr) (numParams numDiscrEqs : Nat)
(k : Array Expr → Array Expr → n α) : n α := do
map2MetaM (fun k =>
Match.forallAltTelescope origAltType (numParams - numDiscrEqs) 0
fun ys _eqs args _mask _bodyType => k ys args
Match.forallAltVarsTelescope origAltType numParams numDiscrEqs
fun ys args _mask _bodyType => k ys args
) k
/--
@ -282,8 +282,8 @@ def transform
let aux1 := mkApp aux1 motive'
let aux1 := mkAppN aux1 discrs'
unless (← isTypeCorrect aux1) do
logError m!"failed to transform matcher, type error when constructing new pre-splitter motive:{indentExpr aux1}"
check aux1
mapError (f := (m!"failed to transform matcher, type error when constructing new pre-splitter motive:{indentExpr aux1}\n{indentD ·}")) do
check aux1
let origAltTypes ← inferArgumentTypesN matcherApp.alts.size aux1
-- We replace the matcher with the splitter

View file

@ -0,0 +1,192 @@
/-!
Tricky cases and regressions tests for generalized match equations.
-/
-- set_option trace.Meta.Match.matchEqs true
def myTest {α}
(mmotive : (x : List α) → Sort v)
(x : List α)
(h_1 : (a : α) → (dc : List α) → x = a :: dc → mmotive (a :: dc))
(h_2 : (x' : List α) → x = x' → mmotive x') : mmotive x :=
match (generalizing := false) h : x with
| (a :: dc) => h_1 a dc h
| x' => h_2 x' h
-- #check myTest.match_1
/--
info: myTest.match_1.splitter.{u_1, u_2} {α : Type u_1} (motive : List α → Sort u_2) (x✝ : List α)
(h_1 : (a : α) → (dc : List α) → x✝ = a :: dc → motive (a :: dc))
(h_2 : (x' : List α) → (∀ (a : α) (dc : List α), x' = a :: dc → False) → x✝ = x' → motive x') : motive x✝
-/
#guard_msgs(pass trace, all) in
#check myTest.match_1.splitter
/--
info: myTest.match_1.congr_eq_1.{u_1, u_2} {α : Type u_1} (motive : List α → Sort u_2) (x✝ : List α)
(h_1 : (a : α) → (dc : List α) → x✝ = a :: dc → motive (a :: dc)) (h_2 : (x' : List α) → x✝ = x' → motive x') (a : α)
(dc : List α) (heq_1 : x✝ = a :: dc) :
HEq
(match h : x✝ with
| a :: dc => h_1 a dc h
| x' => h_2 x' h)
(h_1 a dc heq_1)
-/
#guard_msgs(pass trace, all) in
#check myTest.match_1.congr_eq_1
/--
info: myTest.match_1.congr_eq_2.{u_1, u_2} {α : Type u_1} (motive : List α → Sort u_2) (x✝ : List α)
(h_1 : (a : α) → (dc : List α) → x✝ = a :: dc → motive (a :: dc)) (h_2 : (x' : List α) → x✝ = x' → motive x')
(x' : List α) (heq_1 : x✝ = x') :
(∀ (a : α) (dc : List α), x' = a :: dc → False) →
HEq
(match h : x✝ with
| a :: dc => h_1 a dc h
| x' => h_2 x' h)
(h_2 x' heq_1)
-/
#guard_msgs(pass trace, all) in
#check myTest.match_1.congr_eq_2
def take (n : Nat) (xs : List α) : List α := match n, xs with
| 0, _ => []
| _+1, [] => []
| n+1, a::as => a :: take n as
/--
info: take.match_1.{u_1, u_2} {α : Type u_1} (motive : Nat → List α → Sort u_2) (n✝ : Nat) (xs✝ : List α)
(h_1 : (x : List α) → motive 0 x) (h_2 : (n : Nat) → motive n.succ [])
(h_3 : (n : Nat) → (a : α) → (as : List α) → motive n.succ (a :: as)) : motive n✝ xs✝
-/
#guard_msgs(pass trace, all) in
#check take.match_1
/--
info: take.match_1.congr_eq_1.{u_1, u_2} {α : Type u_1} (motive : Nat → List α → Sort u_2) (n✝ : Nat) (xs✝ : List α)
(h_1 : (x : List α) → motive 0 x) (h_2 : (n : Nat) → motive n.succ [])
(h_3 : (n : Nat) → (a : α) → (as : List α) → motive n.succ (a :: as)) (x✝ : List α) (heq_1 : n✝ = 0)
(heq_2 : xs✝ = x✝) :
HEq
(match n✝, xs✝ with
| 0, x => h_1 x
| n.succ, [] => h_2 n
| n.succ, a :: as => h_3 n a as)
(h_1 x✝)
-/
#guard_msgs(pass trace, all) in #check take.match_1.congr_eq_1
def matchOptionUnit (o? : Option Unit) : Bool := Id.run do
if let some _ := o? then
true
else
false
/--
info: matchOptionUnit.match_1.congr_eq_1.{u_1} (motive : Option Unit → Sort u_1) (o?✝ : Option Unit)
(h_1 : (val : Unit) → motive (some val)) (h_2 : (x : Option Unit) → motive x) (val✝ : Unit)
(heq_1 : o?✝ = some val✝) :
HEq
(match o?✝ with
| some val => h_1 ()
| x => h_2 x)
(h_1 val✝)
-/
#guard_msgs(pass trace, all) in
#check matchOptionUnit.match_1.congr_eq_1
set_option linter.unusedVariables false in
partial def utf16PosToCodepointPosFromAux (s : String) : Nat → String.Pos → Nat → Bool
| 0, _, cp => true
| utf16pos, utf8pos, cp => false
/--
info: utf16PosToCodepointPosFromAux.match_1.congr_eq_1.{u_1} (motive : Nat → String.Pos → Nat → Sort u_1) (x✝ : Nat)
(x✝¹ : String.Pos) (x✝² : Nat) (h_1 : (x : String.Pos) → (cp : Nat) → motive 0 x cp)
(h_2 : (utf16pos : Nat) → (utf8pos : String.Pos) → (cp : Nat) → motive utf16pos utf8pos cp) (x✝³ : String.Pos)
(cp : Nat) (heq_1 : x✝ = 0) (heq_2 : x✝¹ = x✝³) (heq_3 : x✝² = cp) :
HEq
(match x✝, x✝¹, x✝² with
| 0, x, cp => h_1 x cp
| utf16pos, utf8pos, cp => h_2 utf16pos utf8pos cp)
(h_1 x✝³ cp)
-/
#guard_msgs(pass trace, all) in
#check utf16PosToCodepointPosFromAux.match_1.congr_eq_1
opaque some_expr : Option Nat
def wrongEq (m? : Option Nat) (h : some_expr = m?)
(w : 0 < m?.getD 0) : Bool := by
match m?, w with
| some m?, _ => exact true
/--
info: wrongEq.match_1.congr_eq_1.{u_1} (motive : (m? : Option Nat) → 0 < m?.getD 0 → some_expr = m? → Sort u_1)
(m?✝ : Option Nat) (w✝ : 0 < m?✝.getD 0) (h : some_expr = m?✝)
(h_1 : (m? : Nat) → (x : 0 < (some m?).getD 0) → (h : some_expr = some m?) → motive (some m?) x h) (m? : Nat)
(x✝ : 0 < (some m?).getD 0) (h✝ : some_expr = some m?) (heq_1 : m?✝ = some m?) (heq_2 : HEq w✝ x✝)
(heq_3 : HEq h h✝) :
HEq
(match m?✝, w✝, h with
| some m?, x, h => h_1 m? x h)
(h_1 m? x✝ h✝)
-/
#guard_msgs(pass trace, all) in #check wrongEq.match_1.congr_eq_1
set_option linter.unusedVariables false in
noncomputable def myNamedPatternTest (x : List Bool) : Bool :=
match hx : x with
| x'@hx':(x::xs) => false
| x' => true
/--
info: myNamedPatternTest.match_1.congr_eq_1.{u_1} (motive : List Bool → Sort u_1) (x✝ : List Bool)
(h_1 : (x' : List Bool) → (x : Bool) → (xs : List Bool) → x' = x :: xs → x✝ = x :: xs → motive (x :: xs))
(h_2 : (x' : List Bool) → x✝ = x' → motive x') (x : Bool) (xs : List Bool) (heq_1 : x✝ = x :: xs) :
HEq
(match hx : x✝ with
| x'@hx':(x :: xs) => h_1 x' x xs hx' hx
| x' => h_2 x' hx)
(h_1 (x :: xs) x xs ⋯ heq_1)
-/
#guard_msgs(pass trace, all) in
#check myNamedPatternTest.match_1.congr_eq_1
set_option linter.unusedVariables false in
def testMe (n : Nat) : Bool :=
match _ : n - 2 with
| 0 => true
| m => false
/--
info: testMe.match_1.congr_eq_2.{u_1} (motive : Nat → Sort u_1) (x✝ : Nat) (h_1 : x✝ = 0 → motive 0)
(h_2 : (m : Nat) → x✝ = m → motive m) (m : Nat) (heq_1 : x✝ = m) :
(m = 0 → False) →
HEq
(match h : x✝ with
| 0 => h_1 h
| m => h_2 m h)
(h_2 m heq_1)
-/
#guard_msgs(pass trace, all) in
#check testMe.match_1.congr_eq_2
-- JFR: Code to check if all matchers with equations also have congruence equations
/-
open Lean Meta in
run_meta do
-- if false do -- comment this line to run the test on all matchers in the environment
let s := Lean.Meta.Match.Extension.extension.getState (← getEnv) (asyncMode := .local)
for (k,_) in s.map do --, _ in [:600] do
unless (`Lean).isPrefixOf (privateToUserName k) do
let mut ok := false
try
let _ ← Match.getEquationsFor k
ok := true
catch _ =>
pure ()
if ok then
try
let _ ← Lean.Meta.Match.Match.gendMatchCongrEqns k
catch e =>
logError m!"failed to generate equations for {k} in {.ofConstName k.getPrefix}\n{indentD e.toMessageData}"
-/