forked from leanprover/lean4
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathRecursorInfo.lean
278 lines (249 loc) · 13.2 KB
/
RecursorInfo.lean
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
/-
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.Basic
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
| .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) (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 ← x.fvarId!.getDecl
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 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"
/-
@[builtin_attr_parser] 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