Skip to content

Commit edf804c

Browse files
authored
feat: heterogeneous noConfusion (#11474)
This PR generalizes the `noConfusion` constructions to heterogeneous equalities (assuming propositional equalities between the indices). This lays ground work for better support for applying injection to heterogeneous equalities in grind. The `Meta.mkNoConfusion` app builder shields most of the code from these changes. Since the per-constructor noConfusion principles are now more expressive, `Meta.mkNoConfusion` no longer uses the general one. In `Init.Prelude` some proofs are more pedestrian because `injection` now needs a bit more machinery. This is a breaking change for whoever uses the `noConfusion` principle manually and explicitly for a type with indices. Fixes #11450.
1 parent 8b7cbe7 commit edf804c

File tree

15 files changed

+437
-224
lines changed

15 files changed

+437
-224
lines changed

src/Init/Data/Int/Basic.lean

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -278,7 +278,11 @@ set_option bootstrap.genMatcherCode false in
278278
def decNonneg (m : @& Int) : Decidable (NonNeg m) :=
279279
match m with
280280
| ofNat m => isTrue <| NonNeg.mk m
281-
| -[_ +1] => isFalse <| fun h => nomatch h
281+
| -[i +1] => isFalse <| fun h =>
282+
have : ∀ j, (j = -[i +1]) → NonNeg j → False := fun _ hj hnn =>
283+
Int.NonNeg.casesOn (motive := fun j _ => j = -[i +1] → False) hnn
284+
(fun _ h => Int.noConfusion h) hj
285+
this -[i +1] rfl h
282286

283287
/-- Decides whether `a ≤ b`.
284288

src/Init/Prelude.lean

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1837,6 +1837,8 @@ def Nat.ble : @& Nat → @& Nat → Bool
18371837
| succ _, zero => false
18381838
| succ n, succ m => ble n m
18391839

1840+
attribute [gen_constructor_elims] Bool
1841+
18401842
/--
18411843
Non-strict, or weak, inequality of natural numbers, usually accessed via the `≤` operator.
18421844
-/
@@ -1860,9 +1862,14 @@ protected def Nat.lt (n m : Nat) : Prop :=
18601862
instance instLTNat : LT Nat where
18611863
lt := Nat.lt
18621864

1863-
theorem Nat.not_succ_le_zero : ∀ (n : Nat), LE.le (succ n) 0 → False
1864-
| 0 => nofun
1865-
| succ _ => nofun
1865+
theorem Nat.not_succ_le_zero (n : Nat) : LE.le (succ n) 0 → False :=
1866+
-- No injectivity tactic until `attribute [gen_constructor_elims] Nat`
1867+
have : ∀ m, Eq m 0 → LE.le (succ n) m → False := fun _ hm hle =>
1868+
Nat.le.casesOn (motive := fun m _ => Eq m 0 → False) hle
1869+
(fun h => Nat.noConfusion h)
1870+
(fun _ h => Nat.noConfusion h)
1871+
hm
1872+
this 0 rfl
18661873

18671874
theorem Nat.not_lt_zero (n : Nat) : Not (LT.lt n 0) :=
18681875
not_succ_le_zero n
@@ -1999,10 +2006,12 @@ protected theorem Nat.lt_of_not_le {a b : Nat} (h : Not (LE.le a b)) : LT.lt b a
19992006

20002007
protected theorem Nat.add_pos_right :
20012008
{b : Nat} → (a : Nat) → (hb : LT.lt 0 b) → LT.lt 0 (HAdd.hAdd a b)
2009+
| zero, _, h => (Nat.not_succ_le_zero _ h).elim
20022010
| succ _, _, _ => Nat.zero_lt_succ _
20032011

20042012
protected theorem Nat.mul_pos :
20052013
{n m : Nat} → (hn : LT.lt 0 n) → (hm : LT.lt 0 m) → LT.lt 0 (HMul.hMul n m)
2014+
| _, zero, _, hb => (Nat.not_succ_le_zero _ hb).elim
20062015
| _, succ _, ha, _ => Nat.add_pos_right _ ha
20072016

20082017
protected theorem Nat.pow_pos {a : Nat} : {n : Nat} → (h : LT.lt 0 a) → LT.lt 0 (HPow.hPow a n)
@@ -2059,6 +2068,8 @@ protected def Nat.sub : (@& Nat) → (@& Nat) → Nat
20592068
| a, 0 => a
20602069
| a, succ b => pred (Nat.sub a b)
20612070

2071+
attribute [gen_constructor_elims] Nat
2072+
20622073
instance instSubNat : Sub Nat where
20632074
sub := Nat.sub
20642075

@@ -2216,9 +2227,6 @@ theorem Nat.mod_lt : (x : Nat) → {y : Nat} → (hy : LT.lt 0 y) → LT.lt (HM
22162227
| .isTrue _ => Nat.modCore_lt hm
22172228
| .isFalse h => Nat.lt_of_not_le h
22182229

2219-
attribute [gen_constructor_elims] Nat
2220-
attribute [gen_constructor_elims] Bool
2221-
22222230
/--
22232231
Gets the word size of the current platform. The word size may be 64 or 32 bits.
22242232

src/Lean/Compiler/LCNF/ToLCNF.lean

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -666,11 +666,11 @@ where
666666
let .const declName _ := e.getAppFn | unreachable!
667667
let typeName := declName.getPrefix
668668
let .inductInfo inductVal ← getConstInfo typeName | unreachable!
669-
let arity := inductVal.numParams + inductVal.numIndices + 1 /- motive -/ + 2 /- lhs/rhs-/ + 1 /- equality -/
669+
let arity := inductVal.numParams + 1 /- motive -/ + 3*(inductVal.numIndices + 1) /- lhs/rhs and equalities -/
670670
etaIfUnderApplied e arity do
671671
let args := e.getAppArgs
672-
let lhs ← liftMetaM do Meta.whnf args[inductVal.numParams + inductVal.numIndices + 1]!
673-
let rhs ← liftMetaM do Meta.whnf args[inductVal.numParams + inductVal.numIndices + 2]!
672+
let lhs ← liftMetaM do Meta.whnf args[inductVal.numParams + 1 + inductVal.numIndices]!
673+
let rhs ← liftMetaM do Meta.whnf args[inductVal.numParams + 1 + inductVal.numIndices + 1 + inductVal.numIndices]!
674674
let lhs ← liftMetaM lhs.toCtorIfLit
675675
let rhs ← liftMetaM rhs.toCtorIfLit
676676
match (← liftMetaM <| Meta.isConstructorApp? lhs), (← liftMetaM <| Meta.isConstructorApp? rhs) with

src/Lean/Meta/AppBuilder.lean

Lines changed: 36 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -478,46 +478,54 @@ def mkNoConfusion (target : Expr) (h : Expr) : MetaM Expr := do
478478
let α ← whnfD α
479479
matchConstInduct α.getAppFn (fun _ => throwAppBuilderException `noConfusion ("inductive type expected" ++ indentExpr α)) fun indVal us => do
480480
let u ← getLevel target
481-
if let some (ctorA, ys1) ← constructorApp? a then
482-
if let some (ctorB, ys2) ← constructorApp? b then
483-
-- Special case for different manifest constructors, where we can use `ctorIdx`
481+
if let some (ctorA, ys1) ← constructorApp'? a then
482+
if let some (ctorB, ys2) ← constructorApp'? b then
483+
-- Different constructors: Use use `ctorIdx`
484484
if ctorA.cidx ≠ ctorB.cidx then
485485
let ctorIdxName := Name.mkStr indVal.name "ctorIdx"
486486
if (← hasConst ctorIdxName) && (← hasConst `noConfusion_of_Nat) then
487487
let ctorIdx := mkAppN (mkConst ctorIdxName us) α.getAppArgs
488488
let v ← getLevel α
489489
return mkApp2 (mkConst ``False.elim [u]) target <|
490490
mkAppN (mkConst `noConfusion_of_Nat [v]) #[α, ctorIdx, a, b, h]
491-
492-
-- Special case for same constructors, where we can maybe use the per-constructor
493-
-- noConfusion definition with its type already manifest
494-
if ctorA.cidx = ctorB.cidx then
491+
else
492+
throwError "mkNoConfusion: Missing {ctorIdxName} or {`noConfusion_of_Nat}"
493+
else
494+
-- Same constructors: use per-constructor noConfusion
495495
-- Nullary constructors, the construction is trivial
496496
if ctorA.numFields = 0 then
497497
return ← withLocalDeclD `P target fun P => mkLambdaFVars #[P] P
498498

499499
let noConfusionName := ctorA.name.str "noConfusion"
500-
if (← hasConst noConfusionName) then
501-
let xs := α.getAppArgs[:ctorA.numParams]
502-
let noConfusion := mkAppN (mkConst noConfusionName (u :: us)) xs
503-
let fields1 : Array Expr := ys1[ctorA.numParams:]
504-
let fields2 : Array Expr := ys2[ctorA.numParams:]
505-
let mask ← occursInCtorTypeMask ctorA.name
506-
assert! mask.size = ctorA.numFields
507-
let mut ok := true
508-
let mut fields2' := #[]
509-
for m in mask, f1 in fields1, f2 in fields2 do
510-
if m then
511-
unless (← isDefEq f1 f2) do
512-
ok := false
513-
break
514-
else
515-
fields2' := fields2'.push f2
516-
if ok then
517-
return mkAppN noConfusion (#[target] ++ fields1 ++ fields2' ++ #[h])
518-
519-
-- Fall back: Use generic theorem
520-
return mkAppN (mkConst (Name.mkStr indVal.name "noConfusion") (u :: us)) (α.getAppArgs ++ #[target, a, b, h])
500+
unless (← hasConst noConfusionName) do
501+
throwError "mkNoConfusion: Missing {noConfusionName}"
502+
let noConfusionNameInfo ← getConstVal noConfusionName
503+
504+
let xs := α.getAppArgs[:ctorA.numParams]
505+
let noConfusion := mkAppN (mkConst noConfusionName (u :: us)) xs
506+
let fields1 : Array Expr := ys1[ctorA.numParams:]
507+
let fields2 : Array Expr := ys2[ctorA.numParams:]
508+
let mut e := mkAppN noConfusion (#[target] ++ fields1 ++ fields2)
509+
let arity := noConfusionNameInfo.type.getNumHeadForalls
510+
-- Index equalities expected. Can be less than `indVal.numIndices` when this constructor
511+
-- has fixed indices.
512+
assert! arity ≥ xs.size + fields1.size + fields2.size + 3
513+
let numIndEqs := arity - (xs.size + fields1.size + fields2.size + 3) -- 3 for `target`, `h` and the continuation
514+
for _ in [:numIndEqs] do
515+
let eq ← whnf (← whnfForall (← inferType e)).bindingDomain!
516+
if let some (_,i,_,_) := eq.heq? then
517+
e := mkApp e (← mkHEqRefl i)
518+
else if let some (_,i,_) := eq.eq? then
519+
e := mkApp e (← mkEqRefl i)
520+
else
521+
throwError "mkNoConfusion: unexpected equality `{eq}` as next argument to{inlineExpr (← inferType e)}"
522+
let eq := (← whnfForall (← inferType e)).bindingDomain!
523+
if eq.isHEq then
524+
e := mkApp e (← mkHEqOfEq h)
525+
else
526+
e := mkApp e h
527+
return e
528+
throwError "mkNoConfusion: No manifest constructors in {a} = {b}"
521529

522530
/-- Given a `monad` and `e : α`, makes `pure e`.-/
523531
def mkPure (monad : Expr) (e : Expr) : MetaM Expr :=

src/Lean/Meta/Basic.lean

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1834,6 +1834,9 @@ private def withNewBinderInfosImp (bs : Array (FVarId × BinderInfo)) (k : MetaM
18341834
def withNewBinderInfos (bs : Array (FVarId × BinderInfo)) (k : n α) : n α :=
18351835
mapMetaM (fun k => withNewBinderInfosImp bs k) k
18361836

1837+
def withImplicitBinderInfos (bs : Array Expr) (k : n α) : n α :=
1838+
withNewBinderInfos (bs.map (·.fvarId!, BinderInfo.implicit)) k
1839+
18371840
/--
18381841
Execute `k` using a local context where any `x` in `xs` that is tagged as
18391842
instance implicit is treated as a regular implicit. -/

0 commit comments

Comments
 (0)