fix: pending metavariable issue

It fixes issue reported at https://leanprover.zulipchat.com/#narrow/stream/270676-lean4/topic/let.20overload
This commit is contained in:
Leonardo de Moura 2021-08-10 14:52:53 -07:00
parent 7cec20582b
commit 972f00b0ff
2 changed files with 69 additions and 0 deletions

View file

@ -590,6 +590,42 @@ def throwTypeMismatchError (header? : Option String) (expectedType : Expr) (eTyp
def withoutMacroStackAtErr (x : TermElabM α) : TermElabM α :=
withTheReader Core.Context (fun (ctx : Core.Context) => { ctx with options := pp.macroStack.set ctx.options false }) x
namespace ContainsPendingMVar
abbrev M := MonadCacheT Expr Unit (OptionT TermElabM)
/-- See `containsPostponedTerm` -/
partial def visit (e : Expr) : M Unit := do
checkCache e fun _ => do
match e with
| Expr.forallE _ d b _ => visit d; visit b
| Expr.lam _ d b _ => visit d; visit b
| Expr.letE _ t v b _ => visit t; visit v; visit b
| Expr.app f a _ => visit f; visit a
| Expr.mdata _ b _ => visit b
| Expr.proj _ _ b _ => visit b
| Expr.fvar fvarId .. =>
match (← getLocalDecl fvarId) with
| LocalDecl.cdecl .. => return ()
| LocalDecl.ldecl (value := v) .. => visit v
| Expr.mvar mvarId .. =>
let e' ← instantiateMVars e
if e' != e then
visit e'
else
match (← getDelayedAssignment? mvarId) with
| some d => visit d.val
| none => failure
| _ => return ()
end ContainsPendingMVar
/-- Return `true` if `e` contains a pending metavariable. Remark: it also visits let-declarations. -/
def containsPendingMVar (e : Expr) : TermElabM Bool := do
match (← ContainsPendingMVar.visit e |>.run.run) with
| some _ => return false
| none => return true
/- Try to synthesize metavariable using type class resolution.
This method assumes the local context and local instances of `instMVar` coincide
with the current local context and local instances.
@ -606,6 +642,26 @@ def synthesizeInstMVarCore (instMVar : MVarId) (maxResultSize? : Option Nat := n
if (← isExprMVarAssigned instMVar) then
let oldVal ← instantiateMVars (mkMVar instMVar)
unless (← isDefEq oldVal val) do
if (← containsPendingMVar oldVal <||> containsPendingMVar val) then
/- If `val` or `oldVal` contains metavariables directly or indirectly (e.g., in a let-declaration),
we return `false` to indicate we should try again later. This is very course grain since
the metavariable may not be responsible for the failure. We should refine the test in the future if needed.
This check has been added to address dependencies between postponed metavariables. The following
example demonstrates the issue fixed by this test.
```
structure Point where
x : Nat
y : Nat
def Point.compute (p : Point) : Point :=
let p := { p with x := 1 }
let p := { p with y := 0 }
if (p.x - p.y) > p.x then p else p
```
The `isDefEq` test above fails for `Decidable (p.x - p.y ≤ p.x)` when the structure instance assigned to
`p` has not been elaborated yet.
-/
return false -- we will try again later
let oldValType ← inferType oldVal
let valType ← inferType val
unless (← isDefEq oldValType valType) do

View file

@ -0,0 +1,13 @@
structure Point where
x : Nat
y : Nat
def Point.compute1 (p : Point) : Point :=
let p := { p with x := 1 }
let p := { p with y := 0 }
if (p.x - p.y) > p.x then p else p
def Point.compute2 (p : Point) : Point :=
let q := { p with x := 1 }
let r := { q with y := 0 }
if (r.x - r.y) > r.x then r else r