refactor: remove binductionOn, use brecOn instead (#8820)

This PR removes the auto-generated `binductionOn` and `ibelow`
implementations for inductive types in favor of the improved `brecOn`
implementation from #7639.
This commit is contained in:
Parth Shastri 2025-06-17 03:07:24 -04:00 committed by GitHub
parent 259e2ec3e8
commit 17b133369d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 39 additions and 134 deletions

View file

@ -11,16 +11,12 @@ namespace Lean
def casesOnSuffix := "casesOn"
def recOnSuffix := "recOn"
def brecOnSuffix := "brecOn"
def binductionOnSuffix := "binductionOn"
def belowSuffix := "below"
def ibelowSuffix := "ibelow"
def mkCasesOnName (indDeclName : Name) : Name := Name.mkStr indDeclName casesOnSuffix
def mkRecOnName (indDeclName : Name) : Name := Name.mkStr indDeclName recOnSuffix
def mkBRecOnName (indDeclName : Name) : Name := Name.mkStr indDeclName brecOnSuffix
def mkBInductionOnName (indDeclName : Name) : Name := Name.mkStr indDeclName binductionOnSuffix
def mkBelowName (indDeclName : Name) : Name := Name.mkStr indDeclName belowSuffix
def mkIBelowName (indDeclName : Name) : Name := Name.mkStr indDeclName ibelowSuffix
builtin_initialize auxRecExt : TagDeclarationExtension ← mkTagDeclarationExtension

View file

@ -950,10 +950,8 @@ private def mkAuxConstructions (declNames : Array Name) : TermElabM Unit := do
if hasUnit then mkCasesOn n
if hasUnit && hasEq && hasHEq then mkNoConfusion n
if hasUnit && hasProd then mkBelow n
if hasUnit && hasProd then mkIBelow n
for n in declNames do
if hasUnit && hasProd then mkBRecOn n
if hasUnit && hasProd then mkBInductionOn n
private def elabInductiveViews (vars : Array Expr) (elabs : Array InductiveElabStep1) : TermElabM FinalizeContext := do
let view0 := elabs[0]!.view

View file

@ -231,8 +231,7 @@ def mkBRecOnF (recArgInfos : Array RecArgInfo) (positions : Positions)
mkLambdaFVars (indicesMajorArgs ++ #[below] ++ otherArgs) valueNew
/--
Given the `motives`, figures out whether to use `.brecOn` or `.binductionOn`, pass
the right universe levels, the parameters, and the motives.
Given the `motives`, pass the right universe levels, the parameters, and the motives.
It was already checked earlier in `checkCodomainsLevel` that the functions live in the same universe.
-/
def mkBRecOnConst (recArgInfos : Array RecArgInfo) (positions : Positions)
@ -240,7 +239,7 @@ def mkBRecOnConst (recArgInfos : Array RecArgInfo) (positions : Positions)
let indGroup := recArgInfos[0]!.indGroupInst
let motive := motives[0]!
let brecOnUniv ← lambdaTelescope motive fun _ type => getLevel type
let brecOnCons := fun idx => indGroup.brecOn false brecOnUniv idx
let brecOnCons := fun idx => indGroup.brecOn brecOnUniv idx
-- Pick one as a prototype
let brecOnAux := brecOnCons 0
-- Infer the type of the packed motive arguments

View file

@ -263,7 +263,7 @@ def tryAllArgs (fnNames : Array Name) (fixedParamPerms : FixedParamPerms) (xs :
-- Check that the group actually has a brecOn (we used to check this in getRecArgInfo,
-- but in the first phase we do not want to rule-out non-recursive types like `Array`, which
-- are ok in a nested group. This logic can maybe simplified)
unless (← hasConst (group.brecOnName false 0)) do
unless (← hasConst (group.brecOnName 0)) do
throwError "the type {group} does not have a `.brecOn` recursor"
let r ← k comb
trace[Elab.definition.structural] "tryAllArgs report:\n{report}"

View file

@ -36,15 +36,14 @@ def IndGroupInfo.ofInductiveVal (indInfo : InductiveVal) : IndGroupInfo where
def IndGroupInfo.numMotives (group : IndGroupInfo) : Nat :=
group.all.size + group.numNested
/-- Instantiates the right `.brecOn` or `.bInductionOn` for the given type former index,
/-- Instantiates the right `.brecOn` for the given type former index,
including universe parameters and fixed prefix. -/
partial def IndGroupInfo.brecOnName (info : IndGroupInfo) (ind : Bool) (idx : Nat) : Name :=
partial def IndGroupInfo.brecOnName (info : IndGroupInfo) (idx : Nat) : Name :=
if let .some n := info.all[idx]? then
if ind then mkBInductionOnName n
else mkBRecOnName n
mkBRecOnName n
else
let j := idx - info.all.size + 1
info.brecOnName ind 0 |>.appendIndexAfter j
info.brecOnName 0 |>.appendIndexAfter j
/--
An instance of an mutually inductive group of inductives, identified by the `all` array
@ -72,11 +71,11 @@ def IndGroupInst.isDefEq (igi1 igi2 : IndGroupInst) : MetaM Bool := do
unless (← (igi1.params.zip igi2.params).allM (fun (e₁, e₂) => Meta.isDefEqGuarded e₁ e₂)) do return false
return true
/-- Instantiates the right `.brecOn` or `.bInductionOn` for the given type former index,
/-- Instantiates the right `.brecOn` for the given type former index,
including universe parameters and fixed prefix. -/
def IndGroupInst.brecOn (group : IndGroupInst) (ind : Bool) (lvl : Level) (idx : Nat) : Expr :=
let n := group.brecOnName ind idx
let us := if ind then group.levels else lvl :: group.levels
def IndGroupInst.brecOn (group : IndGroupInst) (lvl : Level) (idx : Nat) : Expr :=
let n := group.brecOnName idx
let us := lvl :: group.levels
mkAppN (.const n us) group.params
/--

View file

@ -47,7 +47,7 @@ where
mkLambdaFVars #[arg] (← go prods args)
/--
Constructs the `.below` or `.ibelow` definition for a inductive predicate.
Constructs the `.below` definition for a inductive predicate.
For example for the `List` type, it constructs,
```
@ -56,30 +56,16 @@ For example for the `List` type, it constructs,
fun {α} {motive} t =>
List.rec PUnit (fun head tail tail_ih => PProd (PProd (motive tail) tail_ih) PUnit) t
```
and
```
@[reducible] protected def List.ibelow.{u} : {α : Type u} →
{motive : List α → Prop} → List α → Prop :=
fun {α} {motive} t =>
List.rec True (fun head tail tail_ih => (motive tail ∧ tail_ih) ∧ True) t
```
-/
private def mkBelowFromRec (recName : Name) (ibelow reflexive : Bool) (nParams : Nat)
private def mkBelowFromRec (recName : Name) (reflexive : Bool) (nParams : Nat)
(belowName : Name) : MetaM Unit := do
-- The construction follows the type of `ind.rec`
let .recInfo recVal ← getConstInfo recName
| throwError "{recName} not a .recInfo"
let lvl::lvls := recVal.levelParams.map (Level.param ·)
| throwError "recursor {recName} has no levelParams"
let lvlParam := recVal.levelParams.head!
let refType :=
if ibelow then
recVal.type.instantiateLevelParams [lvlParam] [0]
else
recVal.type
let decl ← forallTelescope refType fun refArgs _ => do
let decl ← forallTelescope recVal.type fun refArgs _ => do
assert! refArgs.size > nParams + recVal.numMotives + recVal.numMinors
let params : Array Expr := refArgs[:nParams]
let motives : Array Expr := refArgs[nParams:nParams + recVal.numMotives]
@ -87,11 +73,6 @@ private def mkBelowFromRec (recName : Name) (ibelow reflexive : Bool) (nParams :
let indices : Array Expr := refArgs[nParams + recVal.numMotives + recVal.numMinors:refArgs.size - 1]
let major : Expr := refArgs[refArgs.size - 1]!
-- universe parameter names of ibelow/below
let blvls :=
-- For ibelow we instantiate the first universe parameter of `.rec` to `.zero`
if ibelow then recVal.levelParams.tail!
else recVal.levelParams
-- universe parameter of the type fomer.
-- same as `typeFormerTypeLevel indVal.type`, but we want to infer it from the
-- type of the recursor, to be more robust when facing nested induction
@ -101,9 +82,7 @@ private def mkBelowFromRec (recName : Name) (ibelow reflexive : Bool) (nParams :
-- universe level of the resultant type
let rlvl : Level :=
if ibelow then
0
else if reflexive then
if reflexive then
mkLevelMax ilvl lvl
else
mkLevelMax 1 lvl
@ -129,21 +108,21 @@ private def mkBelowFromRec (recName : Name) (ibelow reflexive : Bool) (nParams :
let type ← mkForallFVars below_params (.sort rlvl)
val ← mkLambdaFVars below_params val
mkDefinitionValInferrringUnsafe belowName blvls type val .abbrev
mkDefinitionValInferrringUnsafe belowName recVal.levelParams type val .abbrev
addDecl (.defnDecl decl)
setReducibleAttribute decl.name
modifyEnv fun env => markAuxRecursor env decl.name
modifyEnv fun env => addProtected env decl.name
private def mkBelowOrIBelow (indName : Name) (ibelow : Bool) : MetaM Unit := do
def mkBelow (indName : Name) : MetaM Unit := do
let .inductInfo indVal ← getConstInfo indName | return
unless indVal.isRec do return
if ← isPropFormerType indVal.type then return
let recName := mkRecName indName
let belowName := if ibelow then mkIBelowName indName else mkBelowName indName
mkBelowFromRec recName ibelow indVal.isReflexive indVal.numParams belowName
let belowName := mkBelowName indName
mkBelowFromRec recName indVal.isReflexive indVal.numParams belowName
-- If this is the first inductive in a mutual group with nested inductives,
-- generate the constructions for the nested inductives now
@ -151,10 +130,7 @@ private def mkBelowOrIBelow (indName : Name) (ibelow : Bool) : MetaM Unit := do
for i in [:indVal.numNested] do
let recName := recName.appendIndexAfter (i + 1)
let belowName := belowName.appendIndexAfter (i + 1)
mkBelowFromRec recName ibelow indVal.isReflexive indVal.numParams belowName
def mkBelow (declName : Name) : MetaM Unit := mkBelowOrIBelow declName false
def mkIBelow (declName : Name) : MetaM Unit := mkBelowOrIBelow declName true
mkBelowFromRec recName indVal.isReflexive indVal.numParams belowName
/--
If `minorType` is the type of a minor premies of a recursor, such as
@ -198,7 +174,7 @@ private def buildBRecOnMinorPremise (rlvl : Level) (motives : Array Expr)
go #[] minor_args.toList
/--
Constructs the `.brecon` or `.binductionon` definition for a inductive predicate.
Constructs the `.brecOn` definition for a inductive predicate.
For example for the `List` type, it constructs,
```
@ -211,34 +187,16 @@ fun {α} {motive} t (F_1 : (t : List α) → List.below t → motive t) => (
t
).1
```
and
```
@[reducible] protected def List.binductionOn.{u} : ∀ {α : Type u} {motive : List α → Prop}
(t : List α), (∀ (t : List α), List.ibelow t → motive t) → motive t :=
fun {α} {motive} t F_1 => (
@List.rec α (fun t => And (motive t) (@List.ibelow α motive t))
⟨F_1 [] True.intro, True.intro⟩
(fun head tail tail_ih => ⟨F_1 (head :: tail) ⟨tail_ih, True.intro⟩, ⟨tail_ih, True.intro⟩⟩)
t
).1
```
-/
private def mkBRecOnFromRec (recName : Name) (ind reflexive : Bool) (nParams : Nat)
private def mkBRecOnFromRec (recName : Name) (reflexive : Bool) (nParams : Nat)
(all : Array Name) (brecOnName : Name) : MetaM Unit := do
let .recInfo recVal ← getConstInfo recName | return
let lvl::lvls := recVal.levelParams.map (Level.param ·)
| throwError "recursor {recName} has no levelParams"
let lvlParam := recVal.levelParams.head!
-- universe parameter names of brecOn/binductionOn
let blps := if ind then recVal.levelParams.tail! else recVal.levelParams
-- universe parameter names of brecOn
let blps := recVal.levelParams
let refType :=
if ind then
recVal.type.instantiateLevelParams [lvlParam] [0]
else
recVal.type
let decl ← forallTelescope refType fun refArgs refBody => do
let decl ← forallTelescope recVal.type fun refArgs refBody => do
assert! refArgs.size > nParams + recVal.numMotives + recVal.numMinors
let params : Array Expr := refArgs[:nParams]
let motives : Array Expr := refArgs[nParams:nParams + recVal.numMotives]
@ -247,7 +205,7 @@ private def mkBRecOnFromRec (recName : Name) (ind reflexive : Bool) (nParams : N
let major : Expr := refArgs[refArgs.size - 1]!
let some idx := motives.idxOf? refBody.getAppFn
| throwError "result type of {refType} is not one of {motives}"
| throwError "result type of {recVal.type} is not one of {motives}"
-- universe parameter of the type fomer.
-- same as `typeFormerTypeLevel indVal.type`, but we want to infer it from the
@ -258,22 +216,19 @@ private def mkBRecOnFromRec (recName : Name) (ind reflexive : Bool) (nParams : N
-- universe level of the resultant type
let rlvl : Level :=
if ind then
0
else if reflexive then
if reflexive then
mkLevelMax ilvl lvl
else
mkLevelMax 1 lvl
-- One `below` for each motive, with the same motive parameters
let blvls := if ind then lvls else lvl::lvls
let blvls := lvl::lvls
let belows := Array.ofFn (n := motives.size) fun ⟨i,_⟩ =>
let belowName :=
if let some n := all[i]? then
if ind then mkIBelowName n else mkBelowName n
mkBelowName n
else
if ind then .str all[0]! s!"ibelow_{i-all.size + 1}"
else .str all[0]! s!"below_{i-all.size + 1}"
.str all[0]! s!"below_{i-all.size + 1}"
mkAppN (.const belowName blvls) (params ++ motives)
-- create types of functionals (one for each motive)
@ -321,14 +276,14 @@ private def mkBRecOnFromRec (recName : Name) (ind reflexive : Bool) (nParams : N
modifyEnv fun env => markAuxRecursor env decl.name
modifyEnv fun env => addProtected env decl.name
def mkBRecOnOrBInductionOn (indName : Name) (ind : Bool) : MetaM Unit := do
def mkBRecOn (indName : Name) : MetaM Unit := do
let .inductInfo indVal ← getConstInfo indName | return
unless indVal.isRec do return
if ← isPropFormerType indVal.type then return
let recName := mkRecName indName
let brecOnName := if ind then mkBInductionOnName indName else mkBRecOnName indName
mkBRecOnFromRec recName ind indVal.isReflexive indVal.numParams indVal.all.toArray brecOnName
let brecOnName := mkBRecOnName indName
mkBRecOnFromRec recName indVal.isReflexive indVal.numParams indVal.all.toArray brecOnName
-- If this is the first inductive in a mutual group with nested inductives,
-- generate the constructions for the nested inductives now.
@ -336,8 +291,4 @@ def mkBRecOnOrBInductionOn (indName : Name) (ind : Bool) : MetaM Unit := do
for i in [:indVal.numNested] do
let recName := recName.appendIndexAfter (i + 1)
let brecOnName := brecOnName.appendIndexAfter (i + 1)
mkBRecOnFromRec recName ind indVal.isReflexive indVal.numParams indVal.all.toArray brecOnName
def mkBRecOn (declName : Name) : MetaM Unit := mkBRecOnOrBInductionOn declName false
def mkBInductionOn (declName : Name) : MetaM Unit := mkBRecOnOrBInductionOn declName true
mkBRecOnFromRec recName indVal.isReflexive indVal.numParams indVal.all.toArray brecOnName

View file

@ -171,8 +171,6 @@ differences:
Despite its name, this function does *not* recognize the `.brecOn` of inductive *predicates*,
which we also do not support at this point.
Since (for now) we only support `Prop` in the induction principle, we rewrite to `.binductionOn`.
* The elaboration of structurally recursive function can handle extra arguments. We keep the
`motive` parameters in the original order.
@ -1320,7 +1318,7 @@ where doRealize inductName := do
throwError "the indices and major argument of the brecOn application are not variables:{indentExpr body}"
unless brecOnExtras.all (·.isFVar) do
throwError "the extra arguments to the brecOn application are not variables:{indentExpr body}"
let lvl :: indLevels := us |throwError "Too few universe parameters in .brecOn application:{indentExpr body}"
let _ :: indLevels := us | throwError "Too few universe parameters in .brecOn application:{indentExpr body}"
let group : Structural.IndGroupInst := { Structural.IndGroupInfo.ofInductiveVal indInfo with
levels := indLevels, params := brecOnArgs }
@ -1347,7 +1345,7 @@ where doRealize inductName := do
let positions : Structural.Positions := .groupAndSort (·.indIdx) recArgInfos (Array.range indInfo.numTypeFormers)
-- Below we'll need the types of the motive arguments (brecOn argument order)
let brecMotiveTypes ← inferArgumentTypesN recInfo.numMotives (group.brecOn true lvl 0)
let brecMotiveTypes ← inferArgumentTypesN recInfo.numMotives (group.brecOn 0 0)
trace[Meta.FunInd] m!"brecMotiveTypes: {brecMotiveTypes}"
assert! brecMotiveTypes.size = positions.size
@ -1406,7 +1404,7 @@ where doRealize inductName := do
-- Now we can calculate the expected types of the minor arguments
let minorTypes ← inferArgumentTypesN recInfo.numMotives <|
mkAppN (group.brecOn true lvl 0) (packedMotives ++ brecOnTargets)
mkAppN (group.brecOn 0 0) (packedMotives ++ brecOnTargets)
trace[Meta.FunInd] m!"minorTypes: {minorTypes}"
-- So that we can transform them
let (minors', mvars) ← M2.run do
@ -1448,7 +1446,7 @@ where doRealize inductName := do
let some indIdx := positions.findIdx? (·.contains idx) | panic! "invalid positions"
let some pos := positions.find? (·.contains idx) | panic! "invalid positions"
let some packIdx := pos.findIdx? (· == idx) | panic! "invalid positions"
let e := group.brecOn true lvl indIdx -- unconditionally using binduction here
let e := group.brecOn 0 indIdx
let e := mkAppN e packedMotives
let e := mkAppN e indicesMajor
let e := mkAppN e minors'

View file

@ -43,7 +43,6 @@ inductive V (α : Type _) : Nat → Type _
#check @V.rec
#check @V.noConfusion
#check @V.brecOn
#check @V.binductionOn
#check @V.casesOn
#check @V.recOn
#check @V.below

View file

@ -5,7 +5,7 @@ inductive Tree
| node : Tree → Tree → Tree
abbrev notSubtree (x : Tree) (t : Tree) : Prop :=
Tree.ibelow (motive := fun z => x ≠ z) t
t.rec True fun l r l_ih r_ih => (x ≠ l ∧ l_ih) ∧ (x ≠ r ∧ r_ih)
infix:50 "≮" => notSubtree

View file

@ -34,16 +34,6 @@ fun {motive_1} {motive_2} t =>
#guard_msgs in
#print Tree.below_1
/--
info: @[reducible] protected def Ex1.Tree.ibelow_1 : {motive_1 : Tree → Prop} →
{motive_2 : List.{0} Tree → Prop} → List.{0} Tree → Prop :=
fun {motive_1} {motive_2} t =>
Tree.rec_1.{1} (fun a a_ih => And (motive_2 a) a_ih) True
(fun head tail head_ih tail_ih => And (And (motive_1 head) head_ih) (And (motive_2 tail) tail_ih)) t
-/
#guard_msgs in
#print Tree.ibelow_1
/--
info: Ex1.Tree.brecOn.{u} {motive_1 : Tree → Sort u} {motive_2 : List.{0} Tree → Sort u} (t : Tree)
(F_1 : (t : Tree) → Tree.below.{u} t → motive_1 t) (F_2 : (t : List.{0} Tree) → Tree.below_1.{u} t → motive_2 t) :
@ -60,14 +50,6 @@ info: Ex1.Tree.brecOn_1.{u} {motive_1 : Tree → Sort u} {motive_2 : List.{0} Tr
#guard_msgs in
#check Tree.brecOn_1
/--
info: Ex1.Tree.binductionOn_1 {motive_1 : Tree → Prop} {motive_2 : List.{0} Tree → Prop} (t : List.{0} Tree)
(F_1 : ∀ (t : Tree), Tree.ibelow t → motive_1 t) (F_2 : ∀ (t : List.{0} Tree), Tree.ibelow_1 t → motive_2 t) :
motive_2 t
-/
#guard_msgs in
#check Tree.binductionOn_1
end Ex1
namespace Ex2
@ -145,15 +127,6 @@ info: Ex2.Tree.brecOn_2.{u} {motive_1 : Tree → Sort u} {motive_2 : List.{0} (L
#guard_msgs in
#check Tree.brecOn_2
/--
info: Ex2.Tree.binductionOn_2 {motive_1 : Tree → Prop} {motive_2 : List.{0} (List.{0} Tree) → Prop}
{motive_3 : List.{0} Tree → Prop} (t : List.{0} Tree) (F_1 : ∀ (t : Tree), Tree.ibelow t → motive_1 t)
(F_2 : ∀ (t : List.{0} (List.{0} Tree)), Tree.ibelow_1 t → motive_2 t)
(F_3 : ∀ (t : List.{0} Tree), Tree.ibelow_2 t → motive_3 t) : motive_3 t
-/
#guard_msgs in
#check Tree.binductionOn_2
end Ex2
namespace Ex3
@ -194,12 +167,4 @@ info: Ex3.Tree.brecOn_1.{u_1, u} {motive_1 : Tree.{u} → Sort u_1} {motive_2 :
#guard_msgs in
#check Tree.brecOn_1
/--
info: Ex3.Tree.binductionOn_1.{u} {motive_1 : Tree.{u} → Prop} {motive_2 : List.{u} Tree.{u} → Prop} (t : List.{u} Tree.{u})
(F_1 : ∀ (t : Tree.{u}), Tree.ibelow.{u} t → motive_1 t)
(F_2 : ∀ (t : List.{u} Tree.{u}), Tree.ibelow_1.{u} t → motive_2 t) : motive_2 t
-/
#guard_msgs in
#check Tree.binductionOn_1
end Ex3