blob: 917ae1074bab8a45392c9049e063ec5059a9e97d [file]
/-
Copyright (c) 2020 Microsoft Corporation. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Leonardo de Moura
-/
import Lean.AuxRecursor
import Lean.Util.FindExpr
import Lean.Meta.ExprDefEq
namespace Lean.Meta
inductive RecursorUnivLevelPos where
| motive -- marks where the universe of the motive should go
| majorType (idx : Nat) -- marks where the #idx universe of the major premise type goes
instance : ToString RecursorUnivLevelPos := ⟨fun
| RecursorUnivLevelPos.motive => "<motive-univ>"
| RecursorUnivLevelPos.majorType idx => toString idx⟩
structure RecursorInfo where
recursorName : Name
typeName : Name
univLevelPos : List RecursorUnivLevelPos
depElim : Bool
recursive : Bool
numArgs : Nat -- Total number of arguments
majorPos : Nat
paramsPos : List (Option Nat) -- Position of the recursor parameters in the major premise, instance implicit arguments are `none`
indicesPos : List Nat -- Position of the recursor indices in the major premise
produceMotive : List Bool -- If the i-th element is true then i-th minor premise produces the motive
namespace RecursorInfo
def numParams (info : RecursorInfo) : Nat := info.paramsPos.length
def numIndices (info : RecursorInfo) : Nat := info.indicesPos.length
def motivePos (info : RecursorInfo) : Nat := info.numParams
def firstIndexPos (info : RecursorInfo) : Nat := info.majorPos - info.numIndices
def isMinor (info : RecursorInfo) (pos : Nat) : Bool :=
if pos ≤ info.motivePos then false
else if info.firstIndexPos ≤ pos && pos ≤ info.majorPos then false
else true
def numMinors (info : RecursorInfo) : Nat :=
let r := info.numArgs
let r := r - info.motivePos - 1
r - (info.majorPos + 1 - info.firstIndexPos)
instance : ToString RecursorInfo := ⟨fun info =>
"{\n" ++
" name := " ++ toString info.recursorName ++ "\n" ++
" type := " ++ toString info.typeName ++ "\n" ++
" univs := " ++ toString info.univLevelPos ++ "\n" ++
" depElim := " ++ toString info.depElim ++ "\n" ++
" recursive := " ++ toString info.recursive ++ "\n" ++
" numArgs := " ++ toString info.numArgs ++ "\n" ++
" numParams := " ++ toString info.numParams ++ "\n" ++
" numIndices := " ++ toString info.numIndices ++ "\n" ++
" numMinors := " ++ toString info.numMinors ++ "\n" ++
" major := " ++ toString info.majorPos ++ "\n" ++
" motive := " ++ toString info.motivePos ++ "\n" ++
" paramsAtMajor := " ++ toString info.paramsPos ++ "\n" ++
" indicesAtMajor := " ++ toString info.indicesPos ++ "\n" ++
" produceMotive := " ++ toString info.produceMotive ++ "\n" ++
"}"⟩
end RecursorInfo
private def mkRecursorInfoForKernelRec (declName : Name) (val : RecursorVal) : MetaM RecursorInfo := do
let ival ← getConstInfoInduct val.getInduct
let numLParams := ival.levelParams.length
let univLevelPos := (List.range numLParams).map RecursorUnivLevelPos.majorType
let univLevelPos := if val.levelParams.length == numLParams then univLevelPos else RecursorUnivLevelPos.motive :: univLevelPos
let produceMotive := List.replicate val.numMinors true
let paramsPos := (List.range val.numParams).map some
let indicesPos := (List.range val.numIndices).map fun pos => val.numParams + pos
let numArgs := val.numIndices + val.numParams + val.numMinors + val.numMotives + 1
pure {
recursorName := declName,
typeName := val.getInduct,
univLevelPos := univLevelPos,
majorPos := val.getMajorIdx,
depElim := true,
recursive := ival.isRec,
produceMotive := produceMotive,
paramsPos := paramsPos,
indicesPos := indicesPos,
numArgs := numArgs
}
private def getMajorPosIfAuxRecursor? (declName : Name) (majorPos? : Option Nat) : MetaM (Option Nat) :=
if majorPos?.isSome then pure majorPos?
else do
let env ← getEnv
if !isAuxRecursor env declName then pure none
else match declName with
| Name.str p s _ =>
if s != recOnSuffix && s != casesOnSuffix && s != brecOnSuffix then
pure none
else do
let val ← getConstInfoRec (mkRecName p)
pure $ some (val.numParams + val.numIndices + (if s == casesOnSuffix then 1 else val.numMotives))
| _ => pure none
private def checkMotive (declName : Name) (motive : Expr) (motiveArgs : Array Expr) : MetaM Unit :=
unless motive.isFVar && motiveArgs.all Expr.isFVar do
throwError "invalid user defined recursor '{declName}', result type must be of the form (C t), where C is a bound variable, and t is a (possibly empty) sequence of bound variables"
/- Compute number of parameters for (user-defined) recursor.
We assume a parameter is anything that occurs before the motive -/
private partial def getNumParams (xs : Array Expr) (motive : Expr) (i : Nat) : Nat :=
if h : i < xs.size then
let x := xs.get ⟨i, h⟩
if motive == x then i
else getNumParams xs motive (i+1)
else
i
private def getMajorPosDepElim (declName : Name) (majorPos? : Option Nat) (xs : Array Expr) (motive : Expr) (motiveArgs : Array Expr)
: MetaM (Expr × Nat × Bool) := do
match majorPos? with
| some majorPos =>
if h : majorPos < xs.size then
let major := xs.get ⟨majorPos, h⟩
let depElim := motiveArgs.contains major
pure (major, majorPos, depElim)
else throwError "invalid major premise position for user defined recursor, recursor has only {xs.size} arguments"
| none => do
if motiveArgs.isEmpty then
throwError "invalid user defined recursor, '{declName}' does not support dependent elimination, and position of the major premise was not specified (solution: set attribute '[recursor <pos>]', where <pos> is the position of the major premise)"
let major := motiveArgs.back
match xs.getIdx? major with
| some majorPos => pure (major, majorPos, true)
| none => throwError "ill-formed recursor '{declName}'"
private def getParamsPos (declName : Name) (xs : Array Expr) (numParams : Nat) (Iargs : Array Expr) : MetaM (List (Option Nat)) := do
let mut paramsPos := #[]
for i in [:numParams] do
let x := xs[i]
match (← Iargs.findIdxM? fun Iarg => isDefEq Iarg x) with
| some j => paramsPos := paramsPos.push (some j)
| none => do
let localDecl ← getLocalDecl x.fvarId!
if localDecl.binderInfo.isInstImplicit then
paramsPos := paramsPos.push none
else
throwError"invalid user defined recursor '{declName}', type of the major premise does not contain the recursor parameter"
pure paramsPos.toList
private def getIndicesPos (declName : Name) (xs : Array Expr) (majorPos numIndices : Nat) (Iargs : Array Expr) : MetaM (List Nat) := do
let mut indicesPos := #[]
for i in [:numIndices] do
let i := majorPos - numIndices + i
let x := xs[i]
match (← Iargs.findIdxM? fun Iarg => isDefEq Iarg x) with
| some j => indicesPos := indicesPos.push j
| none => throwError "invalid user defined recursor '{declName}', type of the major premise does not contain the recursor index"
pure indicesPos.toList
private def getMotiveLevel (declName : Name) (motiveResultType : Expr) : MetaM Level :=
match motiveResultType with
| Expr.sort u@(Level.zero _) _ => pure u
| Expr.sort u@(Level.param _ _) _ => pure u
| _ =>
throwError "invalid user defined recursor '{declName}', motive result sort must be Prop or (Sort u) where u is a universe level parameter"
private def getUnivLevelPos (declName : Name) (lparams : List Name) (motiveLvl : Level) (Ilevels : List Level) : MetaM (List RecursorUnivLevelPos) := do
let Ilevels := Ilevels.toArray
let mut univLevelPos := #[]
for p in lparams do
if motiveLvl == mkLevelParam p then
univLevelPos := univLevelPos.push RecursorUnivLevelPos.motive
else
match Ilevels.findIdx? fun u => u == mkLevelParam p with
| some i => univLevelPos := univLevelPos.push (RecursorUnivLevelPos.majorType i)
| none =>
throwError "invalid user defined recursor '{declName}', major premise type does not contain universe level parameter '{p}'"
pure univLevelPos.toList
private def getProduceMotiveAndRecursive (xs : Array Expr) (numParams numIndices majorPos : Nat) (motive : Expr) : MetaM (List Bool × Bool) := do
let mut produceMotive := #[]
let mut recursor := false
for i in [:xs.size] do
if i < numParams + 1 then
continue --skip parameters and motive
if majorPos - numIndices ≤ i && i ≤ majorPos then
continue -- skip indices and major premise
-- process minor premise
let x := xs[i]
let xType ← inferType x
(produceMotive, recursor) ← forallTelescopeReducing xType fun minorArgs minorResultType => minorResultType.withApp fun res _ => do
let produceMotive := produceMotive.push (res == motive)
let recursor ← if recursor then pure recursor else minorArgs.anyM fun minorArg => do
let minorArgType ← inferType minorArg
pure (minorArgType.find? fun e => e == motive).isSome
pure (produceMotive, recursor)
pure (produceMotive.toList, recursor)
private def checkMotiveResultType (declName : Name) (motiveArgs : Array Expr) (motiveResultType : Expr) (motiveTypeParams : Array Expr) : MetaM Unit := do
if !motiveResultType.isSort || motiveArgs.size != motiveTypeParams.size then
throwError "invalid user defined recursor '{declName}', motive must have a type of the form (C : Pi (i : B A), I A i -> Type), where A is (possibly empty) sequence of variables (aka parameters), (i : B A) is a (possibly empty) telescope (aka indices), and I is a constant"
private def mkRecursorInfoAux (cinfo : ConstantInfo) (majorPos? : Option Nat) : MetaM RecursorInfo := do
let declName := cinfo.name
let majorPos? ← getMajorPosIfAuxRecursor? declName majorPos?
forallTelescopeReducing cinfo.type fun xs type => type.withApp fun motive motiveArgs => do
checkMotive declName motive motiveArgs
let numParams := getNumParams xs motive 0
let (major, majorPos, depElim) ← getMajorPosDepElim declName majorPos? xs motive motiveArgs
let numIndices := if depElim then motiveArgs.size - 1 else motiveArgs.size
if majorPos < numIndices then
throwError "invalid user defined recursor '{declName}', indices must occur before major premise"
let majorType ← inferType major
majorType.withApp fun I Iargs =>
match I with
| Expr.const Iname Ilevels _ => do
let paramsPos ← getParamsPos declName xs numParams Iargs
let indicesPos ← getIndicesPos declName xs majorPos numIndices Iargs
let motiveType ← inferType motive
forallTelescopeReducing motiveType fun motiveTypeParams motiveResultType => do
checkMotiveResultType declName motiveArgs motiveResultType motiveTypeParams
let motiveLvl ← getMotiveLevel declName motiveResultType
let univLevelPos ← getUnivLevelPos declName cinfo.levelParams motiveLvl Ilevels
let (produceMotive, recursive) ← getProduceMotiveAndRecursive xs numParams numIndices majorPos motive
pure {
recursorName := declName,
typeName := Iname,
univLevelPos := univLevelPos,
majorPos := majorPos,
depElim := depElim,
recursive := recursive,
produceMotive := produceMotive,
paramsPos := paramsPos,
indicesPos := indicesPos,
numArgs := xs.size
}
| _ => throwError "invalid user defined recursor '{declName}', type of the major premise must be of the form (I ...), where I is a constant"
/-
@[builtinAttrParser] def «recursor» := leading_parser "recursor " >> numLit
-/
def Attribute.Recursor.getMajorPos (stx : Syntax) : AttrM Nat := do
if stx.getKind == `Lean.Parser.Attr.recursor then
let pos := stx[1].isNatLit?.getD 0
if pos == 0 then
throwErrorAt stx "major premise position must be greater than zero"
return pos - 1
else
throwErrorAt stx "unexpected attribute argument, numeral expected"
private def mkRecursorInfoCore (declName : Name) (majorPos? : Option Nat := none) : MetaM RecursorInfo := do
let cinfo ← getConstInfo declName
match cinfo with
| ConstantInfo.recInfo val => mkRecursorInfoForKernelRec declName val
| _ => mkRecursorInfoAux cinfo majorPos?
builtin_initialize recursorAttribute : ParametricAttribute Nat ←
registerParametricAttribute {
name := `recursor,
descr := "user defined recursor, numerical parameter specifies position of the major premise",
getParam := fun _ stx => Attribute.Recursor.getMajorPos stx
afterSet := fun declName majorPos => do
discard <| mkRecursorInfoCore declName (some majorPos) |>.run'
}
def getMajorPos? (env : Environment) (declName : Name) : Option Nat :=
recursorAttribute.getParam env declName
def mkRecursorInfo (declName : Name) (majorPos? : Option Nat := none) : MetaM RecursorInfo := do
let cinfo ← getConstInfo declName
match cinfo with
| ConstantInfo.recInfo val => mkRecursorInfoForKernelRec declName val
| _ => match majorPos? with
| none => do mkRecursorInfoAux cinfo (getMajorPos? (← getEnv) declName)
| _ => mkRecursorInfoAux cinfo majorPos?
end Lean.Meta