-
Notifications
You must be signed in to change notification settings - Fork 822
Expand file tree
/
Copy pathCheck.lean
More file actions
364 lines (340 loc) · 14.8 KB
/
Check.lean
File metadata and controls
364 lines (340 loc) · 14.8 KB
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
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
/-
Copyright (c) 2019 Microsoft Corporation. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Leonardo de Moura
-/
module
prelude
public import Lean.Meta.Sorry
import Init.Data.Range.Polymorphic.Iterators
import Lean.OriginalConstKind
public section
/-!
This is not the Kernel type checker, but an auxiliary method for checking
whether terms produced by tactics and `isDefEq` are type correct.
-/
namespace Lean.Meta
private def ensureType (e : Expr) : MetaM Unit := do
discard <| getLevel e
private def checkConstant (constName : Name) (us : List Level) : MetaM Unit := do
let cinfo ← getConstVal constName
unless us.length == cinfo.levelParams.length do
throwIncorrectNumberOfLevels constName us
private def getFunctionDomain (f : Expr) : MetaM (Expr × BinderInfo) := do
let fType ← inferType f
let fType ← whnfD fType
match fType with
| Expr.forallE _ d _ c => return (d, c)
| _ => throwFunctionExpected f
/--
Given two expressions `a` and `b`, this method tries to annotate terms with `pp.explicit := true`
and other `pp` options to expose "implicit" differences.
For example, suppose `a` and `b` are of the form
```lean
@HashMap Nat Nat eqInst hasInst1
@HashMap Nat Nat eqInst hasInst2
```
By default, the pretty printer formats both of them as `HashMap Nat Nat`.
So, counterintuitive error messages such as
```lean
error: application type mismatch
HashMap.insert m
argument
m
has type
HashMap Nat Nat
but is expected to have type
HashMap Nat Nat
```
would be produced.
By adding `pp.explicit := true`, we can generate the more informative error
```lean
error: application type mismatch
HashMap.insert m
argument
m
has type
@HashMap Nat Nat eqInst hasInst1
but is expected to have type
@HashMap Nat Nat eqInst hasInst2
```
Remark: this method implements simple heuristics; we should extend it as we find other counterintuitive
error messages.
-/
partial def addPPExplicitToExposeDiff (a b : Expr) : MetaM (Expr × Expr) := do
if (← getOptions).getBool `pp.all false || (← getOptions).getBool `pp.explicit false then
return (a, b)
else
-- We want to be able to assign metavariables to work out what why `isDefEq` failed,
-- but we don't want these assignments to leak out of the function.
-- Note: we shouldn't instantiate mvars in `visit` to prevent leakage.
withoutModifyingState do
visit (← instantiateMVars a) (← instantiateMVars b)
where
visit (a b : Expr) : MetaM (Expr × Expr) := do
try
match a, b with
| .mdata _ a', _ =>
let (a', b) ← visit a' b
return (a.updateMData! a', b)
| _, .mdata _ b' =>
let (a, b') ← visit a b'
return (a, b.updateMData! b')
| .const nm _, .const nm' _ =>
if nm != nm' then
return (a, b)
else
return (a.setPPUniverses true, b.setPPUniverses true)
| .proj _ i a', .proj _ j b' =>
if i != j then
return (a, b)
else
let (a', b') ← visit a' b'
return (a.updateProj! a', b.updateProj! b')
| .app .., .app .. =>
if a.getAppNumArgs != b.getAppNumArgs then
return (a, b)
else if a.getAppFn'.isMVar || b.getAppFn'.isMVar then
-- This is a failed higher-order unification. Do not proceed to `isDefEq`.
return (a, b)
else if !(← isDefEq a.getAppFn b.getAppFn) then
let (fa, fb) ← visit a.getAppFn b.getAppFn
return (mkAppN fa a.getAppArgs, mkAppN fb b.getAppArgs)
else
-- The function might be "overapplied", so we can't use `forallBoundedTelescope`.
-- That is to say, the arity might depend on the values of the arguments.
-- We look for the first explicit argument that is different.
-- Otherwise we look for the first implicit argument.
-- We try `isDefEq` on all arguments to get discretionary mvar assignments.
let mut as := a.getAppArgs
let mut bs := b.getAppArgs
let mut aFnType ← inferType a.getAppFn
let mut bFnType ← inferType b.getAppFn
let mut firstExplicitDiff? := none
let mut firstImplicitDiff? := none
for i in *...as.size do
unless aFnType.isForall do aFnType ← withTransparency .all <| whnf aFnType
unless bFnType.isForall do bFnType ← withTransparency .all <| whnf bFnType
-- These pattern matches are expected to succeed:
let .forallE _ _ abody abi := aFnType | return (a, b)
let .forallE _ _ bbody bbi := bFnType | return (a, b)
aFnType := abody.instantiate1 as[i]!
bFnType := bbody.instantiate1 bs[i]!
unless (← isDefEq as[i]! bs[i]!) do
if abi.isExplicit && bbi.isExplicit then
firstExplicitDiff? := firstExplicitDiff? <|> some i
else
firstImplicitDiff? := firstImplicitDiff? <|> some i
-- Some special cases
let fn? : Option Name :=
match a.getAppFn, b.getAppFn with
| .const ca .., .const cb .. => if ca == cb then ca else none
| _, _ => none
if fn? == ``OfNat.ofNat && as.size ≥ 3 && firstImplicitDiff? == some 0 then
-- Even if there is an explicit diff, it is better to see that the type is different.
return (a.setPPNumericTypes true, b.setPPNumericTypes true)
if fn? == ``sorryAx then
-- If these are `sorry`s with differing source positions, make sure the delaborator shows the positions.
if let some { module? := moda? } := isLabeledSorry? a then
if let some { module? := modb? } := isLabeledSorry? b then
if moda? != modb? then
return (a.setOption `pp.sorrySource true, b.setOption `pp.sorrySource true)
-- General case
if let some i := firstExplicitDiff? <|> firstImplicitDiff? then
let (ai, bi) ← visit as[i]! bs[i]!
as := as.set! i ai
bs := bs.set! i bi
let a := mkAppN a.getAppFn as
let b := mkAppN b.getAppFn bs
if firstExplicitDiff?.isSome then
return (a, b)
else
return (a.setPPExplicit true, b.setPPExplicit true)
| .forallE na ta ba bia, .forallE nb tb bb bib =>
if !(← isDefEq ta tb) then
let (ta, tb) ← visit ta tb
let a := Expr.forallE na ta ba bia
let b := Expr.forallE nb tb bb bib
return (a.setPPPiBinderTypes true, b.setPPPiBinderTypes true)
else
-- Then bodies must not be defeq.
withLocalDeclD na ta fun arg => do
let (ba', bb') ← visit (ba.instantiate1 arg) (bb.instantiate1 arg)
return (Expr.forallE na ta (ba'.abstract #[arg]) bia, Expr.forallE nb tb (bb'.abstract #[arg]) bib)
| .lam na ta ba bia, .lam nb tb bb bib =>
if !(← isDefEq ta tb) then
let (ta, tb) ← visit ta tb
let a := Expr.lam na ta ba bia
let b := Expr.lam nb tb bb bib
return (a.setPPFunBinderTypes true, b.setPPFunBinderTypes true)
else
-- Then bodies must not be defeq.
withLocalDeclD na ta fun arg => do
let (ba', bb') ← visit (ba.instantiate1 arg) (bb.instantiate1 arg)
return (Expr.lam na ta (ba'.abstract #[arg]) bia, Expr.lam nb tb (bb'.abstract #[arg]) bib)
| _, _ => return (a, b)
catch _ =>
return (a, b)
def throwLetTypeMismatchMessage {α} (fvarId : FVarId) : MetaM α := do
let lctx ← getLCtx
match lctx.find? fvarId with
| some (LocalDecl.ldecl _ _ _ t v nondep _) => do
let vType ← inferType v
let (vType, t) ← addPPExplicitToExposeDiff vType t
let declKind := if nondep then "have" else "let"
throwError "invalid {declKind} declaration, term{indentExpr v}\nhas type{indentExpr vType}\nbut is expected to have type{indentExpr t}"
| _ => unreachable!
/-- Adds note about definitions not unfolded because of the module system, if any. -/
def mkUnfoldAxiomsNote (givenType expectedType : Expr) : MetaM MessageData := do
let env ← getEnv
if env.header.isModule then
let origDiag := (← get).diag
try
let _ ← observing <| withOptions (diagnostics.set · true) <| isDefEq givenType expectedType
let blocked := (← get).diag.unfoldAxiomCounter.toList.filterMap fun (n, count) => do
let count := count - origDiag.unfoldAxiomCounter.findD n 0
guard <| count > 0 && getOriginalConstKind? env n matches some .defn
return m!"{.ofConstName n} ↦ {count}"
if !blocked.isEmpty then
return MessageData.note m!"The following definitions were not unfolded because \
their definition is not exposed:{indentD <| .joinSep blocked Format.line}"
finally
modify ({ · with diag := origDiag })
return .nil
/--
Return error message "has type{givenType}\nbut is expected to have type{expectedType}"
Adds the type’s types unless they are defeq.
If `trailing?` is non-`none`, it is appended to the end of the message. This requires modifying the
produced message, so prefer specifying `trailing?` over appending a message to the result of this
function. Any expressions appearing in the trailing message should be included in `trailingExprs`.
-/
def mkHasTypeButIsExpectedMsg (givenType expectedType : Expr)
(trailing? : Option MessageData := none) (trailingExprs : Array Expr := #[])
: MetaM MessageData := do
return MessageData.ofLazyM (es := #[givenType, expectedType] ++ trailingExprs) do
let mut msg ← (try
let givenTypeType ← inferType givenType
let expectedTypeType ← inferType expectedType
if (← isDefEqGuarded givenTypeType expectedTypeType) then
let (givenType, expectedType) ← addPPExplicitToExposeDiff givenType expectedType
let trailing := trailing?.map (m!"\n" ++ ·) |>.getD .nil
pure m!"has type{indentExpr givenType}\n\
but is expected to have type{indentExpr expectedType}{trailing}"
else
let (givenType, expectedType) ← addPPExplicitToExposeDiff givenType expectedType
let (givenTypeType, expectedTypeType) ← addPPExplicitToExposeDiff givenTypeType expectedTypeType
let trailing := match trailing? with
| none => inlineExprTrailing expectedTypeType
| some trailing => inlineExpr expectedTypeType ++ trailing
pure m!"has type{indentExpr givenType}\nof sort{inlineExpr givenTypeType}\
but is expected to have type{indentExpr expectedType}\nof sort{trailing}"
catch _ =>
let (givenType, expectedType) ← addPPExplicitToExposeDiff givenType expectedType
let trailing := trailing?.map (m!"\n" ++ ·) |>.getD .nil
pure m!"has type{indentExpr givenType}\nbut is expected to have type{indentExpr expectedType}{trailing}")
return msg ++ (← mkUnfoldAxiomsNote givenType expectedType)
def throwAppTypeMismatch (f a : Expr) : MetaM α := do
-- Clarify that `a` is "last" only if it may be confused with some preceding argument; otherwise,
-- avoid this wording because it may be misleading if more arguments follow `a`, e.g., if `f a` is
-- a subexpression of `f a b`
let argDescStr := if f.getAppArgs.any (· == a) then
m!"last{indentExpr a}\nargument "
else
m!"argument{indentExpr a}\n"
let mut e := mkApp f a
let msg ← try
let (expectedType, binfo) ← getFunctionDomain f
unless binfo.isExplicit do
e := e.setAppPPExplicit
let aType ← inferType a
let hasTypeButIsExpected ← mkHasTypeButIsExpectedMsg aType expectedType
(trailing? := m!"in the application{indentExpr e}") (trailingExprs := #[e])
pure m!"Application type mismatch: The {argDescStr}{hasTypeButIsExpected}"
catch _ =>
e := e.setAppPPExplicit
pure m!"Application type mismatch: The {argDescStr}is not of the expected type in the application{indentExpr e}"
throwError msg
def checkApp (f a : Expr) : MetaM Unit := do
let fType ← inferType f
let fType ← whnf fType
match fType with
| Expr.forallE _ d _ _ =>
let aType ← inferType a
unless (← isDefEq d aType) do
throwAppTypeMismatch f a
| _ => throwFunctionExpected (mkApp f a)
def checkProj (structName : Name) (idx : Nat) (e : Expr) : MetaM Unit := do
let structType ← whnf (← inferType e)
let projType ← inferType (mkProj structName idx e)
if (← isProp structType) && !(← isProp projType) then
throwError "invalid projection{indentExpr (mkProj structName idx e)}\nfrom type{indentExpr structType}"
private partial def checkAux (e : Expr) : MetaM Unit := do
check e |>.run
where
check (e : Expr) : MonadCacheT ExprStructEq Unit MetaM Unit :=
checkCache { val := e : ExprStructEq } fun _ => do
match e with
| .forallE .. => checkForall e
| .lam .. => checkLambdaLet e
| .letE .. => checkLambdaLet e
| .const c lvls => checkConstant c lvls
| .app f a => check f; check a; checkApp f a
| .mdata _ e => check e
| .proj s i e => check e; checkProj s i e
| _ => return ()
checkLambdaLet (e : Expr) : MonadCacheT ExprStructEq Unit MetaM Unit :=
lambdaLetTelescope e fun xs b => do
xs.forM fun x => do
let xDecl ← getFVarLocalDecl x;
match xDecl with
| .cdecl (type := t) .. =>
ensureType t
check t
| .ldecl (type := t) (value := v) .. =>
ensureType t
check t
let vType ← inferType v
unless (← isDefEq t vType) do throwLetTypeMismatchMessage x.fvarId!
check v
check b
checkForall (e : Expr) : MonadCacheT ExprStructEq Unit MetaM Unit :=
forallTelescope e fun xs b => do
xs.forM fun x => do
let xDecl ← getFVarLocalDecl x
ensureType xDecl.type
check xDecl.type
ensureType b
check b
/--
Throw an exception if `e` is not type correct.
-/
def check (e : Expr) (transparency : TransparencyMode := .all) : MetaM Unit :=
withTraceNode `Meta.check (fun _ =>
return m!"{e}") do
try
withTransparency transparency $ checkAux e
catch ex =>
trace[Meta.check] ex.toMessageData
throw ex
/--
Return true if `e` is type correct.
-/
def isTypeCorrect (e : Expr) : MetaM Bool := do
try
check e
pure true
catch _ =>
pure false
/--
Throw an exception if `e` cannot be type checked using the kernel.
This function is used for debugging purposes only.
Be sure to share common expressions in `e` before calling this function for good performance.
-/
def checkWithKernel (e : Expr) : MetaM Unit := do
let e ← instantiateExprMVars e
match Kernel.check (← getEnv) (← getLCtx) e with
| .ok .. => return ()
| .error ex => throwError "kernel type checker failed at{indentExpr e}\nwith error message\n{ex.toMessageData (← getOptions)}"
builtin_initialize
registerTraceClass `Meta.check
end Lean.Meta