We are considering removing `.` as an alternative for `·` in the lambda dot notation (e.g., `(·+·)`). Reasons: - `.` is not a perfect replacement for `·` (e.g., `(·.insert ·)`) - `.` is too overloaded: `(f.x)` and `(f .x)` and `(f . x)`. We want to keep the first two.
140 lines
6.4 KiB
Text
140 lines
6.4 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
|
||
-/
|
||
import Lean.Elab.PreDefinition.Structural.Basic
|
||
|
||
namespace Lean.Elab.Structural
|
||
open Meta
|
||
|
||
private def getIndexMinPos (xs : Array Expr) (indices : Array Expr) : Nat := Id.run <| do
|
||
let mut minPos := xs.size
|
||
for index in indices do
|
||
match xs.indexOf? index with
|
||
| some pos => if pos.val < minPos then minPos := pos.val
|
||
| _ => pure ()
|
||
return minPos
|
||
|
||
-- Indices can only depend on other indices
|
||
private def hasBadIndexDep? (ys : Array Expr) (indices : Array Expr) : MetaM (Option (Expr × Expr)) := do
|
||
for index in indices do
|
||
let indexType ← inferType index
|
||
for y in ys do
|
||
if !indices.contains y && (← dependsOn indexType y.fvarId!) then
|
||
return some (index, y)
|
||
return none
|
||
|
||
-- Inductive datatype parameters cannot depend on ys
|
||
private def hasBadParamDep? (ys : Array Expr) (indParams : Array Expr) : MetaM (Option (Expr × Expr)) := do
|
||
for p in indParams do
|
||
let pType ← inferType p
|
||
for y in ys do
|
||
if ← dependsOn pType y.fvarId! then
|
||
return some (p, y)
|
||
return none
|
||
|
||
private def throwStructuralFailed : MetaM α :=
|
||
throwError "structural recursion cannot be used"
|
||
|
||
private def orelse' (x y : M α) : M α := do
|
||
let saveState ← get
|
||
orelseMergeErrors x (do set saveState; y)
|
||
|
||
/--
|
||
Try to find an argument that is structurally smaller in every recursive application.
|
||
We use this argument to justify termination using the auxiliary `brecOn` construction.
|
||
|
||
We give preference for arguments that are *not* indices of inductive types of other arguments.
|
||
See issue #837 for an example where we can show termination using the index of an inductive family, but
|
||
we don't get the desired definitional equalities.
|
||
|
||
We perform two passes. In the first-pass, we only consider arguments that are not indices.
|
||
In the second pass, we consider them.
|
||
|
||
TODO: explore whether there are better solutions, and whether there are other ways to break the heuristic used
|
||
for creating the smart unfolding auxiliary definition.
|
||
-/
|
||
partial def findRecArg (numFixed : Nat) (xs : Array Expr) (k : RecArgInfo → M α) : M α := do
|
||
/- Collect arguments that are indices. See comment above. -/
|
||
let indicesRef : IO.Ref FVarIdSet ← IO.mkRef {}
|
||
for x in xs do
|
||
let xType ← inferType x
|
||
/- Traverse all sub-expressions in the type of `x` -/
|
||
forEachExpr xType fun e =>
|
||
/- If `e` is an inductive family, we store in `indicesRef` all variables in `xs` that occur in "index positions". -/
|
||
matchConstInduct e.getAppFn (fun _ => pure ()) fun info _ => do
|
||
if info.numIndices > 0 && info.numParams + info.numIndices == e.getAppNumArgs then
|
||
for arg in e.getAppArgs[:info.numIndices] do
|
||
forEachExpr arg fun e => do
|
||
if e.isFVar && xs.any (· == e) then
|
||
indicesRef.modify fun indices => indices.insert e.fvarId!
|
||
let indices ← indicesRef.get
|
||
/- We perform two passes. See comment above. -/
|
||
let rec go (i : Nat) (firstPass : Bool) : M α := do
|
||
if h : i < xs.size then
|
||
let x := xs.get ⟨i, h⟩
|
||
let localDecl ← getFVarLocalDecl x
|
||
if localDecl.isLet then
|
||
throwStructuralFailed
|
||
else if firstPass == indices.contains localDecl.fvarId then
|
||
go (i+1) firstPass
|
||
else
|
||
let xType ← whnfD localDecl.type
|
||
matchConstInduct xType.getAppFn (fun _ => go (i+1) firstPass) fun indInfo us => do
|
||
if !(← hasConst (mkBRecOnName indInfo.name)) then
|
||
go (i+1) firstPass
|
||
else if indInfo.isReflexive && !(← hasConst (mkBInductionOnName indInfo.name)) then
|
||
go (i+1) firstPass
|
||
else
|
||
let indArgs := xType.getAppArgs
|
||
let indParams := indArgs.extract 0 indInfo.numParams
|
||
let indIndices := indArgs.extract indInfo.numParams indArgs.size
|
||
if !indIndices.all Expr.isFVar then
|
||
orelse'
|
||
(throwError "argument #{i+1} was not used because its type is an inductive family and indices are not variables{indentExpr xType}")
|
||
(go (i+1) firstPass)
|
||
else if !indIndices.allDiff then
|
||
orelse'
|
||
(throwError "argument #{i+1} was not used because its type is an inductive family and indices are not pairwise distinct{indentExpr xType}")
|
||
(go (i+1) firstPass)
|
||
else
|
||
let indexMinPos := getIndexMinPos xs indIndices
|
||
let numFixed := if indexMinPos < numFixed then indexMinPos else numFixed
|
||
let fixedParams := xs.extract 0 numFixed
|
||
let ys := xs.extract numFixed xs.size
|
||
match (← hasBadIndexDep? ys indIndices) with
|
||
| some (index, y) =>
|
||
orelse'
|
||
(throwError "argument #{i+1} was not used because its type is an inductive family{indentExpr xType}\nand index{indentExpr index}\ndepends on the non index{indentExpr y}")
|
||
(go (i+1) firstPass)
|
||
| none =>
|
||
match (← hasBadParamDep? ys indParams) with
|
||
| some (indParam, y) =>
|
||
orelse'
|
||
(throwError "argument #{i+1} was not used because its type is an inductive datatype{indentExpr xType}\nand parameter{indentExpr indParam}\ndepends on{indentExpr y}")
|
||
(go (i+1) firstPass)
|
||
| none =>
|
||
let indicesPos := indIndices.map fun index => match ys.indexOf? index with | some i => i.val | none => unreachable!
|
||
orelse'
|
||
(mapError
|
||
(k { fixedParams := fixedParams
|
||
ys := ys
|
||
pos := i - fixedParams.size
|
||
indicesPos := indicesPos
|
||
indName := indInfo.name
|
||
indLevels := us
|
||
indParams := indParams
|
||
indIndices := indIndices
|
||
reflexive := indInfo.isReflexive
|
||
indPred := ←isInductivePredicate indInfo.name })
|
||
(fun msg => m!"argument #{i+1} was not used for structural recursion{indentD msg}"))
|
||
(go (i+1) firstPass)
|
||
else if firstPass then
|
||
go (i := numFixed) (firstPass := false)
|
||
else
|
||
throwStructuralFailed
|
||
|
||
go (i := numFixed) (firstPass := true)
|
||
|
||
end Lean.Elab.Structural
|