Zulip Chat Archive

Stream: lean4

Topic: help with a metavariable issue


Scott Morrison (Aug 29 2023 at 09:42):

There is some code in Mathlib.Lean.Meta that tries to determine if a goal g is "independent" of a list of other goals, in the sense that assigning a value to g cannot change the solvability of those other goals.

However it is unfortunately buggy, and I'd like some help sorting it out.

Here's src#Lean.MVarId.independent?:

/--
Check if a goal is "independent" of a list of other goals.
We say a goal is independent of other goals if assigning a value to it
can not change the solvability of the other goals.

This function only calculates a conservative approximation of this condition.
-/
def independent? (L : List MVarId) (g : MVarId) : MetaM Bool := do
  let t  instantiateMVars ( g.getType)
  if t.hasExprMVar then
    -- If the goal's type contains other meta-variables,
    -- we conservatively say that `g` is not independent.
    return false
  if t.isProp then
    -- If the goal is propositional,
    -- proof-irrelevance should ensure it is independent of any other goals.
    return true
  if  g.subsingleton? then
    -- If the goal is a subsingleton, it should be independent of any other goals.
    return true
  -- Finally, we check if the goal `g` appears in the type of any of the goals `L`.
  let r  L.allM fun g' => do
    let t'  instantiateMVars ( g'.getType)
    pure <| !( exprDependsOn' t' (.mvar g))
  return r

Scott Morrison (Aug 29 2023 at 09:42):

Here's an example of it working fine:

import Mathlib.Lean.Meta
import Mathlib.Tactic.RunCmd

open Lean Elab Tactic

example : Σ α, Option α := by
  constructor
  -- Goals are `?fst : Type` and `?snd : Option ?fst`.
  -- Correctly, `independent?` claims neither is independent.
  -- (`?snd` because its type contains a metavariable, and `?fst` because `?snd`'s type depends on it)
  run_tac do
    let gs  getGoals
    for g in gs do
      guard ! ( g.independent? gs)
  sorry

But it's easy to fool:

import Mathlib.Lean.Meta
import Mathlib.Tactic.RunCmd

open Lean Elab Tactic

example : Σ α, Option α := by
  constructor
  -- Now we introduce a hypothesis in the second goal
  rotate_left
  have := 0
  -- Now `independent?` incorrectly thinks `?fst` is independent.
  run_tac do
    let gs  getGoals
    for g in gs do
      guard ! ( g.independent? gs) <|> throwError m!"{← g.getType} was incorrectly considered independent!"
  sorry

What's going on here? After the have, the goal state says:

case fst
this: Nat
 Type ?u.8
case snd
 Option
  (let_fun this := 0;
  ?m.39 this)

where ?m.39 : Nat → Type ?u.8 has "lost contact" with ?fst, and so independent? can see that actually ?fst does appear here.

Scott Morrison (Aug 29 2023 at 09:46):

(This problem causes the exact? bug reported earlier.)

Damiano Testa (Aug 29 2023 at 10:46):

Maybe I am misunderstanding, but when the target is a Type, I would have thought that "dependence" would be (also) that you are looking for a term of that type. So, maybe, replacing the last

    pure <| !( exprDependsOn' t' (.mvar g))

with

    let defeq :=  isDefEq ( inferType t') ( g.getType'')
    pure <| !(( exprDependsOn' t' (.mvar g))  defeq)

is closer to what you want?

Sorry if this is just noise!

Scott Morrison (Aug 29 2023 at 10:59):

@Damiano Testa , I'm not sure what you mean. I think that situation is already being handled correctly:

import Mathlib.Lean.Meta
import Mathlib.Tactic.RunCmd

open Lean Elab Tactic

example : Σ α, α := by
  constructor
  -- Goals are `?fst : Type` and `?snd : ?fst`.
  -- Correctly, `independent?` claims neither is independent.
  -- (`?snd` because its type contains a metavariable, and `?fst` because `?snd`'s type depends on it)
  run_tac do
    let gs  getGoals
    for g in gs do
      guard ! ( g.independent? gs)
  rotate_left
  sorry
  sorry

Damiano Testa (Aug 29 2023 at 11:05):

Hmm, this is strange: in the other case, the one where you expected something different, exprDependsOn' seems to give the opposite answer, though, or am I completely confused?

Scott Morrison (Aug 29 2023 at 11:06):

Yes, in the confusing case, that fact that I added an extra hypothesis means that exprDependsOn' can not see the dependence.

Damiano Testa (Aug 29 2023 at 11:07):

But the defeq check does say that the type of t' and the actual target of g are defeq.

Damiano Testa (Aug 29 2023 at 11:08):

So, I agree that in the latest working example exprDependsOn' is doing the right thing, but it seems to be confused by something else in the failing example from before.

Scott Morrison (Aug 29 2023 at 11:10):

The problem in the confusing situation is that the second goal depends on ?m.39 : Nat → Type ?u.8, rather than ?fst : Type ?u.8.

Damiano Testa (Aug 29 2023 at 11:15):

Ok, so you are saying that the defeq test does the right thing in this situation, because it is just telling you that the type of g' is "one-up" from the type of t' and this is because of the Nat → Type ?u.8, right?

(Btw, I have way more mvars than you do: fun (this : Nat) => ?_uniq.16536 this!)

Scott Morrison (Aug 29 2023 at 11:16):

I'm sorry, I don't understand.

Damiano Testa (Aug 29 2023 at 11:19):

While trying to answer your question, I realized that I need to think more about what I meant...

Damiano Testa (Aug 29 2023 at 11:20):

The fact that the question is about independence and the checks are for dependence and then the !s all conspire to confuse me.

Damiano Testa (Aug 29 2023 at 11:28):

In the confusing situation, exprDependsOn' t' (.mvar g) returns false, whereas we both agree that it should be true. Am I right about this?

Scott Morrison (Aug 29 2023 at 11:43):

Yes!

Scott Morrison (Aug 29 2023 at 11:43):

But the solution has to come from understanding the relationship between the ?m.X : Nat → Type ?u and ?fst : Type ?u.

Damiano Testa (Aug 29 2023 at 11:44):

Ok, I think that my confused message was trying to say: the defeq solution simply checks that some universes somewhere agree -- oh, this is a sign that they could be dependent on each other! I agree that it is too weak.

Kyle Miller (Aug 29 2023 at 14:20):

I've got one hint: ?m.39 is a delayed assignment, which is a metavariable with an entry in another table saying how it links up to another metavariable. The code for docs#Lean.Meta.getMVars will give you all metavariables, even accounting for delayed assignments.

I'm not sure if this is a bug in exprDependsOn' that it doesn't account for delayed assignments, or if it's working as intended.

Kyle Miller (Aug 29 2023 at 14:28):

Here's a version that uses docs#Lean.Meta.getMVars, and it seems to do the right thing.

import Mathlib.Lean.Meta
import Mathlib.Tactic.RunCmd

open Lean Elab Tactic

def Lean.MVarId.independent?' (L : List MVarId) (g : MVarId) : MetaM Bool := do
  let t  instantiateMVars ( g.getType)
  if t.hasExprMVar then
    -- If the goal's type contains other meta-variables,
    -- we conservatively say that `g` is not independent.
    return false
  if t.isProp then
    -- If the goal is propositional,
    -- proof-irrelevance should ensure it is independent of any other goals.
    return true
  if  g.subsingleton? then
    -- If the goal is a subsingleton, it should be independent of any other goals.
    return true
  -- Finally, we check if the goal `g` appears in the type of any of the goals `L`.
  L.allM fun g' => do
    let mvars  Meta.getMVars ( g'.getType)
    pure <| !(mvars.contains g)

example : Σ α, Option α := by
  constructor
  -- Now we introduce a hypothesis in the second goal
  rotate_left
  have := 0
  run_tac do
    let gs  getUnsolvedGoals
    for g in gs do
      let ty  instantiateMVars ( g.getType)
      logInfo m!"{Expr.mvar g} : {ty} is independent = {(← g.independent?' gs)}"
  sorry
/-
?fst : Type ?u.3087 is independent = false

?snd : Option
  (let_fun this := 0;
  ?m.3118 this) is independent = false
-/

Scott Morrison (Aug 30 2023 at 04:37):

Thanks Kyle, this was super helpful! I think this is incorrect behaviour in exprDependsOn', I've made an issue at lean#2483 and a proposed fix at lean4#2484, and in the meantime your solution works in exact?.


Last updated: Dec 20 2023 at 11:08 UTC