@@ -359,8 +359,8 @@ def elabAsFVar (stx : Syntax) (userName? : Option Name := none) : TacticM FVarId
359
359
| _ => throwUnsupportedSyntax
360
360
361
361
/--
362
- Make sure `expectedType` does not contain free and metavariables.
363
- It applies zeta and zetaDelta-reduction to eliminate let-free-vars.
362
+ Make sure `expectedType` does not contain free and metavariables.
363
+ It applies zeta and zetaDelta-reduction to eliminate let-free-vars.
364
364
-/
365
365
private def preprocessPropToDecide (expectedType : Expr) : TermElabM Expr := do
366
366
let mut expectedType ← instantiateMVars expectedType
@@ -370,31 +370,95 @@ private def preprocessPropToDecide (expectedType : Expr) : TermElabM Expr := do
370
370
throwError "expected type must not contain free or meta variables{indentExpr expectedType}"
371
371
return expectedType
372
372
373
+ /--
374
+ Given the decidable instance `inst`, reduces it and returns a decidable instance expression
375
+ in whnf that can be regarded as the reason for the failure of `inst` to fully reduce.
376
+ -/
377
+ private partial def blameDecideReductionFailure (inst : Expr) : MetaM Expr := do
378
+ let inst ← whnf inst
379
+ -- If it's the Decidable recursor, then blame the major premise.
380
+ if inst.isAppOfArity ``Decidable.rec 5 then
381
+ return ← blameDecideReductionFailure inst.appArg!
382
+ -- If it is a matcher, look for a discriminant that's a Decidable instance to blame.
383
+ if let .const c _ := inst.getAppFn then
384
+ if let some info ← getMatcherInfo? c then
385
+ if inst.getAppNumArgs == info.arity then
386
+ let args := inst.getAppArgs
387
+ for i in [0 :info.numDiscrs] do
388
+ let inst' := args[info.numParams + 1 + i]!
389
+ if (← Meta.isClass? (← inferType inst')) == ``Decidable then
390
+ let inst'' ← whnf inst'
391
+ if !(inst''.isAppOf ``isTrue || inst''.isAppOf ``isFalse) then
392
+ return ← blameDecideReductionFailure inst''
393
+ return inst
394
+
373
395
@[builtin_tactic Lean.Parser.Tactic.decide] def evalDecide : Tactic := fun _ =>
374
396
closeMainGoalUsing fun expectedType => do
375
397
let expectedType ← preprocessPropToDecide expectedType
376
398
let d ← mkDecide expectedType
377
399
let d ← instantiateMVars d
378
400
-- Get instance from `d`
379
401
let s := d.appArg!
380
- -- Reduce the instance rather than `d` itself, since that gives a nicer error message on failure .
402
+ -- Reduce the instance rather than `d` itself for diagnostics purposes .
381
403
let r ← withAtLeastTransparency .default <| whnf s
382
- if r.isAppOf ``isFalse then
383
- throwError "\
384
- tactic 'decide' proved that the proposition\
385
- {indentExpr expectedType}\n \
386
- is false"
387
- unless r.isAppOf ``isTrue do
388
- throwError "\
389
- tactic 'decide' failed for proposition\
390
- {indentExpr expectedType}\n \
391
- since its 'Decidable' instance reduced to\
392
- {indentExpr r}\n \
393
- rather than to the 'isTrue' constructor."
394
- -- While we have a proof from reduction, we do not embed it in the proof term,
395
- -- but rather we let the kernel recompute it during type checking from a more efficient term.
396
- let rflPrf ← mkEqRefl (toExpr true )
397
- return mkApp3 (Lean.mkConst ``of_decide_eq_true) expectedType s rflPrf
404
+ if r.isAppOf ``isTrue then
405
+ -- Success!
406
+ -- While we have a proof from reduction, we do not embed it in the proof term,
407
+ -- and instead we let the kernel recompute it during type checking from the following more efficient term.
408
+ let rflPrf ← mkEqRefl (toExpr true )
409
+ return mkApp3 (Lean.mkConst ``of_decide_eq_true) expectedType s rflPrf
410
+ else
411
+ -- Diagnose the failure, lazily so that there is no performance impact if `decide` isn't being used interactively.
412
+ throwError MessageData.ofLazyM (es := #[expectedType]) do
413
+ if r.isAppOf ``isFalse then
414
+ return m!"\
415
+ tactic 'decide' proved that the proposition\
416
+ {indentExpr expectedType}\n \
417
+ is false"
418
+ -- Re-reduce the instance and collect diagnostics, to get all unfolded Decidable instances
419
+ let (reason, unfoldedInsts) ← withoutModifyingState <| withOptions (fun opt => diagnostics.set opt true ) do
420
+ modifyDiag (fun _ => {})
421
+ let reason ← withAtLeastTransparency .default <| blameDecideReductionFailure s
422
+ let unfolded := (← get).diag.unfoldCounter.foldl (init := #[]) fun cs n _ => cs.push n
423
+ let unfoldedInsts ← unfolded |>.qsort Name.lt |>.filterMapM fun n => do
424
+ let e ← mkConstWithLevelParams n
425
+ if (← Meta.isClass? (← inferType e)) == ``Decidable then
426
+ return m!"'{MessageData.ofConst e}'"
427
+ else
428
+ return none
429
+ return (reason, unfoldedInsts)
430
+ let stuckMsg :=
431
+ if unfoldedInsts.isEmpty then
432
+ m!"Reduction got stuck at the '{MessageData.ofConstName ``Decidable}' instance{indentExpr reason}"
433
+ else
434
+ let instances := if unfoldedInsts.size == 1 then "instance" else "instances"
435
+ m!"After unfolding the {instances} {MessageData.andList unfoldedInsts.toList}, \
436
+ reduction got stuck at the '{MessageData.ofConstName ``Decidable}' instance{indentExpr reason}"
437
+ let hint :=
438
+ if reason.isAppOf ``Eq.rec then
439
+ m!"\n\n \
440
+ Hint: Reduction got stuck on '▸' ({MessageData.ofConstName ``Eq.rec}), \
441
+ which suggests that one of the '{MessageData.ofConstName ``Decidable}' instances is defined using tactics such as 'rw' or 'simp'. \
442
+ To avoid tactics, make use of functions such as \
443
+ '{MessageData.ofConstName ``inferInstanceAs}' or '{MessageData.ofConstName ``decidable_of_decidable_of_iff}' \
444
+ to alter a proposition."
445
+ else if reason.isAppOf ``Classical.choice then
446
+ m!"\n\n \
447
+ Hint: Reduction got stuck on '{MessageData.ofConstName ``Classical.choice}', \
448
+ which indicates that a '{MessageData.ofConstName ``Decidable}' instance \
449
+ is defined using classical reasoning, proving an instance exists rather than giving a concrete construction. \
450
+ The 'decide' tactic works by evaluating a decision procedure via reduction, and it cannot make progress with such instances. \
451
+ This can occur due to the 'opened scoped Classical' command, which enables the instance \
452
+ '{MessageData.ofConstName ``Classical.propDecidable}'."
453
+ else
454
+ MessageData.nil
455
+ return m!"\
456
+ tactic 'decide' failed for proposition\
457
+ {indentExpr expectedType}\n \
458
+ since its '{MessageData.ofConstName ``Decidable}' instance\
459
+ {indentExpr s}\n \
460
+ did not reduce to '{MessageData.ofConstName ``isTrue}' or '{MessageData.ofConstName ``isFalse}'.\n\n \
461
+ {stuckMsg}{hint}"
398
462
399
463
private def mkNativeAuxDecl (baseName : Name) (type value : Expr) : TermElabM Name := do
400
464
let auxName ← Term.mkAuxName baseName
0 commit comments