fix: FunInd with nested well-founded recurison and late fixed parameters (#8094)

This PR fixes the generation of functional induction principles for
functions with nested nested well-founded recursion and late fixed
parameters. This is a follow-up for #7166. Fixes #8093.
This commit is contained in:
Joachim Breitner 2025-04-25 11:20:27 +02:00 committed by GitHub
parent 416e07a68e
commit 3fe195a4a9
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 27 additions and 14 deletions

View file

@ -20,17 +20,14 @@ open Eqns
structure EqnInfo extends EqnInfoCore where
declNames : Array Name
declNameNonRec : Name
fixedPrefixSize : Nat
argsPacker : ArgsPacker
fixedParamPerms : FixedParamPerms
deriving Inhabited
builtin_initialize eqnInfoExt : MapDeclarationExtension EqnInfo ← mkMapDeclarationExtension
def registerEqnsInfo (preDefs : Array PreDefinition) (declNameNonRec : Name) (fixedParamPerms : FixedParamPerms)
(argsPacker : ArgsPacker) : MetaM Unit := do
let fixedPrefixSize := fixedParamPerms.numFixed
preDefs.forM fun preDef => ensureEqnReservedNamesAvailable preDef.declName
/-
See issue #2327.
@ -43,7 +40,7 @@ def registerEqnsInfo (preDefs : Array PreDefinition) (declNameNonRec : Name) (fi
modifyEnv fun env =>
preDefs.foldl (init := env) fun env preDef =>
eqnInfoExt.insert env preDef.declName { preDef with
declNames, declNameNonRec, fixedPrefixSize, argsPacker, fixedParamPerms }
declNames, declNameNonRec, argsPacker, fixedParamPerms }
def getEqnsFor? (declName : Name) : MetaM (Option (Array Name)) := do
if let some info := eqnInfoExt.find? (← getEnv) declName then

View file

@ -875,15 +875,17 @@ def cleanPackedArgs (eqnInfo : WF.EqnInfo) (value : Expr) : MetaM Expr := do
-- Look for _unary redexes
if e.isAppOf eqnInfo.declNameNonRec then
let args := e.getAppArgs
if eqnInfo.fixedPrefixSize + 1 ≤ args.size then
let packedArg := args.back!
let some (i, unpackedArgs) := eqnInfo.argsPacker.unpack packedArg
| throwError "Unexpected packedArg:{indentExpr packedArg}"
let e' := .const eqnInfo.declNames[i]! e.getAppFn.constLevels!
let e' := mkAppN e' args.pop
let e' := mkAppN e' unpackedArgs
let e' := mkAppN e' args[eqnInfo.fixedPrefixSize+1:]
return .continue e'
if h : args.size ≥ eqnInfo.fixedParamPerms.numFixed + 1 then
let xs := args[:eqnInfo.fixedParamPerms.numFixed]
let packedArg := args[eqnInfo.fixedParamPerms.numFixed]
let extraArgs := args[eqnInfo.fixedParamPerms.numFixed+1:]
let some (funIdx, ys) := eqnInfo.argsPacker.unpack packedArg
| throwError "Unexpected packedArg:{indentExpr packedArg}"
let args' := eqnInfo.fixedParamPerms.perms[funIdx]!.buildArgs xs ys
let e' := .const eqnInfo.declNames[funIdx]! e.getAppFn.constLevels!
let e' := mkAppN e' args'
let e' := mkAppN e' extraArgs
return .continue e'
return .continue e)
mkExpectedTypeHint value cleanType
@ -902,6 +904,7 @@ def unpackMutualInduction (eqnInfo : WF.EqnInfo) : MetaM Name := do
return inductName
where doRealize inductName := do
let unaryInductName ← deriveUnaryInduction eqnInfo.declNameNonRec
mapError (f := (m!"Cannot unpack functional cases principle {.ofConstName unaryInductName} (please report this issue)\n{indentD ·}")) do
let ci ← getConstInfo unaryInductName
let us := ci.levelParams
let value := .const ci.name (us.map mkLevelParam)
@ -930,7 +933,7 @@ where doRealize inductName := do
return value
unless ← isTypeCorrect value do
logError m!"failed to unpack induction principle:{indentExpr value}"
logError m!"final term is type incorrect:{indentExpr value}"
check value
let type ← inferType value
let type ← elimOptParam type

View file

@ -0,0 +1,13 @@
axiom testSorry : α
def foo (n : Nat) (p : Nat) : Nat :=
foo (foo (n - 1) p) p
termination_by n
decreasing_by all_goals exact testSorry
/--
info: foo.induct (p : Nat) (motive : Nat → Prop) (case1 : ∀ (x : Nat), motive (x - 1) → motive (foo (x - 1) p) → motive x)
(n : Nat) : motive n
-/
#guard_msgs in
#check foo.induct