Zulip Chat Archive

Stream: lean4

Topic: Delayed-assigned mvars footgun


Jannis Limperg (May 23 2024 at 19:20):

At some point in Aesop I need to determine whether a partially completed proof e : Expr is sorry-free:

e.hasSorry

Of course I recognise Lean's biggest footgun, so I write instead:

(<- instantiateMVars e).hasSorry

But in fact this is incorrect as well: if e contains a delayed-assigned mvar ?m, then ?m is not instantiated by instantiateMVars but its assignment may still contain sorry. So the (probably) correct version is something like this:

partial def hasSorry (e : Expr) : MetaM Bool :=
  (·.isSome) <$> e.findM? λ
    | .mvar mvarId => do
      if let some ass  getDelayedMVarAssignment? mvarId then
        hasSorry $ .mvar ass.mvarIdPending
      else if let some ass  getExprMVarAssignment? mvarId then
        hasSorry ass
      else
        return false
    | .const ``sorryAx _ => return true
    | _ => return false

I figured this out when debugging an issue, so this is a real scenario (albeit a rare one because delayed-assigned mvars are rare). I suspect that a lot of core and Mathlib code contains this bug since (<- instantiateMVars e).foldM or (<- instantiateMVars e).findM is a common pattern.

David Renshaw (May 25 2024 at 23:13):

why does instantiateMVars not take into account delayed mvar assignments?

Kyle Miller (May 25 2024 at 23:59):

@David Renshaw It does, but it only substitutes them in once the mvar it's been delayed assigned to itself instantiates to something without any mvars.


Last updated: May 02 2025 at 03:31 UTC