Co-authored-by: euprunin <euprunin@users.noreply.github.com> Co-authored-by: Kim Morrison <scott.morrison@gmail.com>
299 lines
15 KiB
Text
299 lines
15 KiB
Text
/-
|
||
Copyright (c) 2021 Microsoft Corporation. All rights reserved.
|
||
Released under Apache 2.0 license as described in the file LICENSE.
|
||
Authors: Leonardo de Moura, Joachim Breitner
|
||
-/
|
||
prelude
|
||
import Lean.Util.HasConstCache
|
||
import Lean.Meta.PProdN
|
||
import Lean.Meta.Match.MatcherApp.Transform
|
||
import Lean.Elab.RecAppSyntax
|
||
import Lean.Elab.PreDefinition.Basic
|
||
import Lean.Elab.PreDefinition.Structural.Basic
|
||
import Lean.Elab.PreDefinition.Structural.RecArgInfo
|
||
|
||
namespace Lean.Elab.Structural
|
||
open Meta
|
||
|
||
private def throwToBelowFailed : MetaM α :=
|
||
throwError "toBelow failed"
|
||
|
||
partial def searchPProd (e : Expr) (F : Expr) (k : Expr → Expr → MetaM α) : MetaM α := do
|
||
match (← whnf e) with
|
||
| .app (.app (.const `PProd _) d1) d2 =>
|
||
(do searchPProd d1 (.proj ``PProd 0 F) k)
|
||
<|> (do searchPProd d2 (.proj ``PProd 1 F) k)
|
||
| .app (.app (.const `And _) d1) d2 =>
|
||
(do searchPProd d1 (.proj `And 0 F) k)
|
||
<|> (do searchPProd d2 (.proj `And 1 F) k)
|
||
| .const `PUnit _
|
||
| .const `True _ => throwToBelowFailed
|
||
| _ => k e F
|
||
|
||
/-- See `toBelow` -/
|
||
private partial def toBelowAux (C : Expr) (belowDict : Expr) (arg : Expr) (F : Expr) : MetaM Expr := do
|
||
trace[Elab.definition.structural] "belowDict start:{indentExpr belowDict}\narg:{indentExpr arg}"
|
||
-- First search through the PProd packing of the different `brecOn` motives
|
||
searchPProd belowDict F fun belowDict F => do
|
||
trace[Elab.definition.structural] "belowDict step 1:{indentExpr belowDict}"
|
||
-- Then instantiate parameters of a reflexive type, if needed
|
||
forallTelescopeReducing belowDict fun xs belowDict => do
|
||
let arg ← zetaReduce arg
|
||
let argArgs := arg.getAppArgs
|
||
unless argArgs.size >= xs.size do throwToBelowFailed
|
||
let n := argArgs.size
|
||
let argTailArgs := argArgs.extract (n - xs.size) n
|
||
let belowDict := belowDict.replaceFVars xs argTailArgs
|
||
-- And again search through the PProd packing due to multiple functions recursing on the
|
||
-- same inductive data type
|
||
-- (We could use the funIdx and the `positions` array to replace this search with more
|
||
-- targeted indexing.)
|
||
searchPProd belowDict (mkAppN F argTailArgs) fun belowDict F => do
|
||
trace[Elab.definition.structural] "belowDict step 2:{indentExpr belowDict}"
|
||
match belowDict with
|
||
| .app belowDictFun belowDictArg =>
|
||
unless belowDictFun.getAppFn == C do throwToBelowFailed
|
||
unless ← isDefEq belowDictArg arg do throwToBelowFailed
|
||
pure F
|
||
| _ =>
|
||
trace[Elab.definition.structural] "belowDict not an app:{indentExpr belowDict}"
|
||
throwToBelowFailed
|
||
|
||
/-- See `toBelow` -/
|
||
private def withBelowDict [Inhabited α] (below : Expr) (numIndParams : Nat)
|
||
(positions : Positions) (k : Array Expr → Expr → MetaM α) : MetaM α := do
|
||
let numTypeFormers := positions.size
|
||
let belowType ← inferType below
|
||
trace[Elab.definition.structural] "belowType: {belowType}"
|
||
unless (← isTypeCorrect below) do
|
||
trace[Elab.definition.structural] "not type correct!"
|
||
belowType.withApp fun f args => do
|
||
unless numIndParams + numTypeFormers < args.size do
|
||
trace[Elab.definition.structural] "unexpected 'below' type{indentExpr belowType}"
|
||
throwToBelowFailed
|
||
let params := args[:numIndParams]
|
||
let finalArgs := args[numIndParams+numTypeFormers:]
|
||
let pre := mkAppN f params
|
||
let motiveTypes ← inferArgumentTypesN numTypeFormers pre
|
||
let numMotives : Nat := positions.numIndices
|
||
trace[Elab.definition.structural] "numMotives: {numMotives}"
|
||
let mut CTypes := Array.mkArray numMotives (.sort 37) -- dummy value
|
||
for poss in positions, motiveType in motiveTypes do
|
||
for pos in poss do
|
||
CTypes := CTypes.set! pos motiveType
|
||
let CDecls : Array (Name × (Array Expr → MetaM Expr)) ← CTypes.mapM fun t => do
|
||
return ((← mkFreshUserName `C), fun _ => pure t)
|
||
withLocalDeclsD CDecls fun Cs => do
|
||
-- We have to pack these canary motives like we packed the real motives
|
||
let packedCs ← positions.mapMwith PProdN.packLambdas motiveTypes Cs
|
||
let belowDict := mkAppN pre packedCs
|
||
let belowDict := mkAppN belowDict finalArgs
|
||
trace[Elab.definition.structural] "initial belowDict for {Cs}:{indentExpr belowDict}"
|
||
unless (← isTypeCorrect belowDict) do
|
||
trace[Elab.definition.structural] "not type correct!"
|
||
k Cs belowDict
|
||
|
||
/--
|
||
`below` is a free variable with type of the form `I.below indParams motive indices major`,
|
||
where `I` is the name of an inductive datatype.
|
||
|
||
For example, when trying to show that the following function terminates using structural recursion
|
||
```lean
|
||
def addAdjacent : List Nat → List Nat
|
||
| [] => []
|
||
| [a] => [a]
|
||
| a::b::as => (a+b) :: addAdjacent as
|
||
```
|
||
when we are visiting `addAdjacent as` at `replaceRecApps`, `below` has type
|
||
`@List.below Nat (fun (x : List Nat) => List Nat) (a::b::as)`
|
||
The motive `fun (x : List Nat) => List Nat` depends on the actual function we are trying to compute.
|
||
So, we first replace it with a fresh variable `C` at `withBelowDict`.
|
||
Recall that `brecOn` implements course-of-values recursion, and `below` can be viewed as a dictionary
|
||
of the "previous values".
|
||
We search this dictionary using the auxiliary function `toBelowAux`.
|
||
The dictionary is built using the `PProd` (`And` for inductive predicates).
|
||
We keep searching it until we find `C recArg`, where `C` is the auxiliary fresh variable created at `withBelowDict`. -/
|
||
private partial def toBelow (below : Expr) (numIndParams : Nat) (positions : Positions) (fnIndex : Nat) (recArg : Expr) : MetaM Expr := do
|
||
withBelowDict below numIndParams positions fun Cs belowDict =>
|
||
toBelowAux Cs[fnIndex]! belowDict recArg below
|
||
|
||
private partial def replaceRecApps (recArgInfos : Array RecArgInfo) (positions : Positions)
|
||
(below : Expr) (e : Expr) : M Expr :=
|
||
let recFnNames := recArgInfos.map (·.fnName)
|
||
let containsRecFn (e : Expr) : StateRefT (HasConstCache recFnNames) M Bool :=
|
||
modifyGet (·.contains e)
|
||
let rec loop (below : Expr) (e : Expr) : StateRefT (HasConstCache recFnNames) M Expr := do
|
||
if !(← containsRecFn e) then
|
||
return e
|
||
match e with
|
||
| Expr.lam n d b c =>
|
||
withLocalDecl n c (← loop below d) fun x => do
|
||
mkLambdaFVars #[x] (← loop below (b.instantiate1 x))
|
||
| Expr.forallE n d b c =>
|
||
withLocalDecl n c (← loop below d) fun x => do
|
||
mkForallFVars #[x] (← loop below (b.instantiate1 x))
|
||
| Expr.letE n type val body _ =>
|
||
withLetDecl n (← loop below type) (← loop below val) fun x => do
|
||
mkLetFVars #[x] (← loop below (body.instantiate1 x)) (usedLetOnly := false)
|
||
| Expr.mdata d b =>
|
||
if let some stx := getRecAppSyntax? e then
|
||
withRef stx <| loop below b
|
||
else
|
||
return mkMData d (← loop below b)
|
||
| Expr.proj n i e => return mkProj n i (← loop below e)
|
||
| Expr.app _ _ =>
|
||
let processApp (e : Expr) : StateRefT (HasConstCache recFnNames) M Expr :=
|
||
e.withApp fun f args => do
|
||
if let .some fnIdx := recArgInfos.findIdx? (f.isConstOf ·.fnName) then
|
||
let recArgInfo := recArgInfos[fnIdx]!
|
||
let some recArg := args[recArgInfo.recArgPos]?
|
||
| throwError "insufficient number of parameters at recursive application {indentExpr e}"
|
||
-- For reflexive type, we may have nested recursive applications in recArg
|
||
let recArg ← loop below recArg
|
||
let f ←
|
||
try toBelow below recArgInfo.indGroupInst.params.size positions fnIdx recArg
|
||
catch _ => throwError "failed to eliminate recursive application{indentExpr e}"
|
||
-- We don't pass the fixed parameters, the indices and the major arg to `f`, only the rest
|
||
let (_, fArgs) := recArgInfo.pickIndicesMajor args[recArgInfo.numFixed:]
|
||
let fArgs ← fArgs.mapM (replaceRecApps recArgInfos positions below ·)
|
||
return mkAppN f fArgs
|
||
else
|
||
return mkAppN (← loop below f) (← args.mapM (loop below))
|
||
match (← matchMatcherApp? (alsoCasesOn := true) e) with
|
||
| some matcherApp =>
|
||
if recArgInfos.all (fun recArgInfo => !recArgHasLooseBVarsAt recArgInfo.fnName recArgInfo.recArgPos e) then
|
||
processApp e
|
||
else
|
||
/- Here is an example we currently do not handle
|
||
```
|
||
def g (xs : List Nat) : Nat :=
|
||
match xs with
|
||
| [] => 0
|
||
| y::ys =>
|
||
match ys with
|
||
| [] => 1
|
||
| _::_::zs => g zs + 1
|
||
| zs => g ys + 2
|
||
```
|
||
We are matching on `ys`, but still using `ys` in the third alternative.
|
||
If we push the `below` argument over the dependent match it will be able to eliminate recursive call using `zs`.
|
||
To make it work, users have to write the third alternative as `| zs => g zs + 2`
|
||
If this is too annoying in practice, we may replace `ys` with the matching term, but
|
||
this may generate weird error messages, when it doesn't work. -/
|
||
trace[Elab.definition.structural] "below before matcherApp.addArg: {below} : {← inferType below}"
|
||
if let some matcherApp ← matcherApp.addArg? below then
|
||
let altsNew ← (Array.zip matcherApp.alts matcherApp.altNumParams).mapM fun (alt, numParams) =>
|
||
lambdaBoundedTelescope alt numParams fun xs altBody => do
|
||
trace[Elab.definition.structural] "altNumParams: {numParams}, xs: {xs}"
|
||
unless xs.size = numParams do
|
||
throwError "unexpected matcher application alternative{indentExpr alt}\nat application{indentExpr e}"
|
||
let belowForAlt := xs[numParams - 1]!
|
||
mkLambdaFVars xs (← loop belowForAlt altBody)
|
||
pure { matcherApp with alts := altsNew }.toExpr
|
||
else
|
||
processApp e
|
||
| none => processApp e
|
||
| e =>
|
||
ensureNoRecFn recFnNames e
|
||
pure e
|
||
loop below e |>.run' {}
|
||
|
||
/--
|
||
Calculates the `.brecOn` motive corresponding to one structural recursive function.
|
||
The `value` is the function with (only) the fixed parameters moved into the context.
|
||
-/
|
||
def mkBRecOnMotive (recArgInfo : RecArgInfo) (value : Expr) : M Expr := do
|
||
lambdaTelescope value fun xs value => do
|
||
let type := (← inferType value).headBeta
|
||
let (indexMajorArgs, otherArgs) := recArgInfo.pickIndicesMajor xs
|
||
let motive ← mkForallFVars otherArgs type
|
||
mkLambdaFVars indexMajorArgs motive
|
||
|
||
/--
|
||
Calculates the `.brecOn` functional argument corresponding to one structural recursive function.
|
||
The `value` is the function with (only) the fixed parameters moved into the context,
|
||
The `type` is the expected type of the argument.
|
||
The `recArgInfos` is used to transform the body of the function to replace recursive calls with
|
||
uses of the `below` induction hypothesis.
|
||
-/
|
||
def mkBRecOnF (recArgInfos : Array RecArgInfo) (positions : Positions)
|
||
(recArgInfo : RecArgInfo) (value : Expr) (FType : Expr) : M Expr := do
|
||
lambdaTelescope value fun xs value => do
|
||
let (indexMajorArgs, otherArgs) := recArgInfo.pickIndicesMajor xs
|
||
let FType ← instantiateForall FType indexMajorArgs
|
||
forallBoundedTelescope FType (some 1) fun below _ => do
|
||
-- TODO: `below` user name is `f`, and it will make a global `f` to be pretty printed as `_root_.f` in error messages.
|
||
-- We should add an option to `forallBoundedTelescope` to ensure fresh names are used.
|
||
let below := below[0]!
|
||
let valueNew ← replaceRecApps recArgInfos positions below value
|
||
mkLambdaFVars (indexMajorArgs ++ #[below] ++ otherArgs) valueNew
|
||
|
||
/--
|
||
Given the `motives`, figures out whether to use `.brecOn` or `.binductionOn`, 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)
|
||
(motives : Array Expr) : MetaM (Nat → Expr) := do
|
||
let indGroup := recArgInfos[0]!.indGroupInst
|
||
let motive := motives[0]!
|
||
let brecOnUniv ← lambdaTelescope motive fun _ type => getLevel type
|
||
let indInfo ← getConstInfoInduct indGroup.all[0]!
|
||
let useBInductionOn := indInfo.isReflexive && brecOnUniv == levelZero
|
||
let brecOnUniv ←
|
||
if indInfo.isReflexive && brecOnUniv != levelZero then
|
||
decLevel brecOnUniv
|
||
else
|
||
pure brecOnUniv
|
||
let brecOnCons := fun idx => indGroup.brecOn useBInductionOn brecOnUniv idx
|
||
-- Pick one as a prototype
|
||
let brecOnAux := brecOnCons 0
|
||
-- Infer the type of the packed motive arguments
|
||
let packedMotiveTypes ← inferArgumentTypesN indGroup.numMotives brecOnAux
|
||
let packedMotives ← positions.mapMwith PProdN.packLambdas packedMotiveTypes motives
|
||
|
||
return fun n => mkAppN (brecOnCons n) packedMotives
|
||
|
||
/--
|
||
Given the `recArgInfos` and the `motives`, infer the types of the `F` arguments to the `.brecOn`
|
||
combinators. This assumes that all `.brecOn` functions of a mutual inductive have the same structure.
|
||
|
||
It also undoes the permutation and packing done by `packMotives`
|
||
-/
|
||
def inferBRecOnFTypes (recArgInfos : Array RecArgInfo) (positions : Positions)
|
||
(brecOnConst : Nat → Expr) : MetaM (Array Expr) := do
|
||
let numTypeFormers := positions.size
|
||
let recArgInfo := recArgInfos[0]! -- pick an arbitrary one
|
||
let brecOn := brecOnConst 0
|
||
check brecOn
|
||
let brecOnType ← inferType brecOn
|
||
-- Skip the indices and major argument
|
||
let packedFTypes ← forallBoundedTelescope brecOnType (some (recArgInfo.indicesPos.size + 1)) fun _ brecOnType =>
|
||
-- And return the types of the next arguments
|
||
arrowDomainsN numTypeFormers brecOnType
|
||
|
||
let mut FTypes := Array.mkArray positions.numIndices (Expr.sort 0)
|
||
for packedFType in packedFTypes, poss in positions do
|
||
for pos in poss do
|
||
FTypes := FTypes.set! pos packedFType
|
||
return FTypes
|
||
|
||
/--
|
||
Completes the `.brecOn` for the given function.
|
||
The `value` is the function with (only) the fixed parameters moved into the context.
|
||
-/
|
||
def mkBrecOnApp (positions : Positions) (fnIdx : Nat) (brecOnConst : Nat → Expr)
|
||
(FArgs : Array Expr) (recArgInfo : RecArgInfo) (value : Expr) : MetaM Expr := do
|
||
lambdaTelescope value fun ys _value => do
|
||
let (indexMajorArgs, otherArgs) := recArgInfo.pickIndicesMajor ys
|
||
let brecOn := brecOnConst recArgInfo.indIdx
|
||
let brecOn := mkAppN brecOn indexMajorArgs
|
||
let packedFTypes ← inferArgumentTypesN positions.size brecOn
|
||
let packedFArgs ← positions.mapMwith PProdN.mkLambdas packedFTypes FArgs
|
||
let brecOn := mkAppN brecOn packedFArgs
|
||
let some poss := positions.find? (·.contains fnIdx)
|
||
| throwError "mkBrecOnApp: Could not find {fnIdx} in {positions}"
|
||
let brecOn ← PProdN.proj poss.size (poss.getIdx? fnIdx).get! brecOn
|
||
mkLambdaFVars ys (mkAppN brecOn otherArgs)
|
||
|
||
end Lean.Elab.Structural
|