Zulip Chat Archive
Stream: lean4
Topic: Collect mvars during a tactic
Leni Aniva (Sep 07 2024 at 00:04):
Sometimes when you invoke MVarId.revert
(the low-level MetaM
version), additional mvars can be generated.
Is there a function that can collect the generated mvars from this tactic? The simplest way I can think of is to record the mvar counter and used filterOldMVars
to filter all mvars in the current MetaM
context.
This function would take place of the ??
in
def invokeRevertAndCollect (mvarId: MVarId) (fvarId: FVarId): MetaM (List MVarId) := do
let (nextGoal, moreMVars) := ?? do
let (_, nextGoal) := mvarId.revert #[fvarId]
pure nextGoal
return [nextGoal] ++ moreMVars
Kyle Miller (Sep 07 2024 at 00:13):
Do you have an example where you need to collect these mvars? If it's generating mvars, they should be delayed assigned, so they are not your responsibility.
Leni Aniva (Sep 07 2024 at 00:34):
Kyle Miller said:
Do you have an example where you need to collect these mvars? If it's generating mvars, they should be delayed assigned, so they are not your responsibility.
There are some cases when the generated mvars are not delayed assigned. Consider this example:
| n : Nat
?snd : Eq ?alpha ?lhs ?rhs
| n : Nat
?alpha : Sort u
| n : Nat
?lhs : ?alpha
| n : Nat
?rhs : ?alpha
Now suppose you execute .revert #[fvarId]
where fvarId
is the id of this n
, on ?snd
, you get
?snd : forall (n: Nat), Eq ?alpha' ?lhs' ?rhs'
(the actual generated mvar names are not as clean as this. I annotated this for clarity)
In this case instead of having ?lhs'
being delayed assigned with some expr dependent on ?lhs
, we get
| n : Nat
?lhs := ?lhs' n
This is a pretty peculiar case, but if it occurs I don't even have a way of detecting that it happened, hence the question
Kyle Miller (Sep 07 2024 at 02:54):
I see. I was noticing this issue recently, and it can pop up whenever you have natural metavariables, and there are lots of tactics that seem to accidentally create natural metavariables for new goals.
Can you make sure these metavariables are synthetic opaque instead?
Leni Aniva (Sep 07 2024 at 03:20):
Kyle Miller said:
I see. I was noticing this issue recently, and it can pop up whenever you have natural metavariables, and there are lots of tactics that seem to accidentally create natural metavariables for new goals.
Can you make sure these metavariables are synthetic opaque instead?
do you mean the automatically generated ones?
I have no control over the mvars ?alpha, ?lhs, ?rhs
because they could be generated from other tactics.
As long as I can detect when this problem occurs, I can deal with it. My only question is about detecting this problem and collecting the resulting mvars.
Kyle Miller (Sep 07 2024 at 03:37):
If revert
causes goals to disappear, this is a bug. Please report these bugs. Can you give mwe's of this phenomenon?
A workaround you could try is to change all the goal metavariables to be synthetic opaque yourself with docs#Lean.MetavarContext.setMVarKind before doing revert. I think this should work.
Leni Aniva (Sep 07 2024 at 03:37):
Kyle Miller said:
If
revert
causes goals to disappear, this is a bug. Please report these bugs. Can you give mwe's of this phenomenon?A workaround you could try is to change all the goal metavariables to be synthetic opaque yourself with docs#Lean.MetavarContext.setMVarKind before doing revert. I think this should work.
it doesn't cause goals to disappear. I'm operating on the MetaM
level where there's no concept of goals like TacticM
. I just need to capture the generated mvars from a revert
Kyle Miller (Sep 07 2024 at 04:09):
I am pretty sure that revert should not be assigning metavariables like this. You shouldn't need to capture generated mvars.
Leni Aniva (Sep 07 2024 at 04:10):
Kyle Miller said:
I am pretty sure that revert should not be assigning metavariables like this. You shouldn't need to capture generated mvars.
so it should just fail in this case? even if the assigned mvars are .natural
?
Kyle Miller (Sep 07 2024 at 04:10):
Another workaround you could try is to use withNewMCtxDepth
to prevent pre-existing mvars to be assigned.
Kyle Miller (Sep 07 2024 at 04:12):
so it should just fail in this case?
No. My understanding of this issue is that the assignment is happening in the wrong direction. It would be very helpful having some mwes to properly report it.
Leni Aniva (Sep 07 2024 at 04:34):
Kyle Miller said:
so it should just fail in this case?
No. My understanding of this issue is that the assignment is happening in the wrong direction. It would be very helpful having some mwes to properly report it.
I can try producing one.
Leni Aniva (Sep 07 2024 at 06:16):
Its done:
import Lean
open Lean
def showMVar (name: String) (mvarId: MVarId): MetaM Unit := do
if let .some expr ← getExprMVarAssignment? mvarId then
IO.println s!"{name} := {← Meta.ppExpr expr}"
else if let .some a ← getDelayedMVarAssignment? mvarId then
IO.println s!"{name} ::= {a.mvarIdPending.name}"
else
IO.println s!"{name} is not assigned"
def metaM: MetaM Unit := do
let α: Expr := .sort 0
let nat: Expr := .const `Nat []
Meta.withLocalDecl `x .default nat $ λ fvar => do
let lhs: Expr ← Meta.mkFreshExprMVar (type? := .some α)
let rhs ← Meta.mkFreshExprSyntheticOpaqueMVar α
let goalType ← Meta.mkEq lhs rhs
let goal ← Meta.mkFreshExprSyntheticOpaqueMVar goalType
IO.println s!"goal: {← Meta.ppExpr $ ← goal.mvarId!.getType}"
let (_, goal2) ← goal.mvarId!.revert #[fvar.fvarId!]
IO.println s!"goal2 : {← Meta.ppExpr $ ← goal2.getType}"
let .forallE _ _ body _ ← goal2.getType | panic! "This must be a ∀"
let .some (_, .app lhs' (.bvar _), .app rhs' (.bvar _)) := body.eq? | panic! "Wrong type"
showMVar "lhs" lhs.mvarId!
showMVar "rhs" rhs.mvarId!
showMVar "lhs'" lhs'.mvarId!
showMVar "rhs'" rhs'.mvarId!
def main : IO Unit := do
let env: Environment ← importModules
(imports := #[`Init])
(opts := {})
(trustLevel := 1)
let options: Options := {}
let options := options.setBool `pp.all true
let coreM := Meta.MetaM.run' metaM
let coreContext: Lean.Core.Context := {
options,
currNamespace := `Example
openDecls := [], -- No 'open' directives needed
fileName := "<stdin>",
fileMap := { source := "", positions := #[0] }
}
match ← (coreM.run' coreContext { env }).toBaseIO with
| .error exception =>
IO.println s!"{← exception.toMessageData.toString}"
| .ok _ => return ()
RHS is set to .syntheticOpaque
so it wasn't modified by revert
(edit: However the generated mvar in goal2
corresponding to rhs
was modified). Should I file a bug in Lean4 repo?
In summary:
lhs
: Assigned tolhs? x
rhs
: Not assignedlhs'
: Not assignedrhs'
: Delayed assigned torhs
with fvar. (IMO correct behaviour)
Leni Aniva (Sep 07 2024 at 06:40):
Moreover, if you run the tactic with withNewMCtxDepth
, you get
goal: @Eq.{1} Prop ?m.2 ?m.3
unknown metavariable '?_uniq.7'
The unknown mvar occurs at ← goal2.getType
.
Leni Aniva (Sep 07 2024 at 08:07):
https://github.com/leanprover/lean4/issues/5279
Leni Aniva (Sep 07 2024 at 08:14):
I came up with a way to fix it. If we can have a function
def withOpaqueOldMVars (mvarCount: Nat) (m: MetaM a): MetaM a := ...
which overrides the kind of mvars older than mvarCount
to be .syntheticOpaque
. Then we can call it before MetavarContext.revert
to prevent the elimination function from assigning to these mvars.
If people are fine with this solution, I'll open a PR. It requires adding a new field to Meta.Context
and modifications to elim
Kevin Buzzard (Sep 07 2024 at 09:11):
I love it when people in this community persevere to understand a problem. Thanks a lot for minimising and thinking about a fix! I'm well aware that it's time-consuming.
Leni Aniva (Sep 07 2024 at 17:53):
Kevin Buzzard said:
I love it when people in this community persevere to understand a problem. Thanks a lot for minimising and thinking about a fix! I'm well aware that it's time-consuming.
My main research project is blocked on this bug I may as well fix it asap
Yann Herklotz (Sep 09 2024 at 10:06):
I just wanted to note that this looks similar to a previous discussion on MVar assignments, we've been working around this with an MVar collection tactic and being careful about where MVars are assingned.
Kyle Miller (Sep 09 2024 at 17:31):
Thanks @Yann Herklotz, I was trying to remember that thread. I've linked to it on the issue (lean4#5279).
Leni Aniva (Sep 09 2024 at 18:35):
Yann Herklotz said:
I just wanted to note that this looks similar to a previous discussion on MVar assignments, we've been working around this with an MVar collection tactic and being careful about where MVars are assingned.
I came up with a workaround which is to intercept all MVars during Tactic.setGoals
and force them to be .syntheticOpaque
. It seems to be working for now, but if a tactic runs things with withAssignableSyntheticOpaque
we could potentially lose track of the goals
Leni Aniva (Sep 10 2024 at 02:59):
There's a problem with this workaround. I sometimes rely on the fact that apply
can unify metavariables. For example, I can apply le_of_eq
to 2 <= ?m
and this will induce ?m := 2
. With all mvars set to .syntheticOpaque
, this is no longer possible. I think having this sort of unification is fine as long as no goals are lost
Kyle Miller (Sep 10 2024 at 05:21):
Yann suggested in the other thread that apply
might need to be doing its unifications using withSyntheticOpaque
— this might be justified.
Leni Aniva (Sep 10 2024 at 06:07):
Kyle Miller said:
Yann suggested in the other thread that
apply
might need to be doing its unifications usingwithSyntheticOpaque
— this might be justified.
do you mean withAssignableSyntheticOpaque
?
Yann Herklotz (Sep 10 2024 at 07:43):
Yeah I think so, as far as I understand, one should use synthetic MVars when creating MVars that one wants to keep around, and that tactics that want to unify those MVars should use withAssignableSyntheticOpaque
to explicitly assign them.
Thomas Murrills (Sep 13 2024 at 03:20):
Leni Aniva said:
Moreover, if you run the tactic with
withNewMCtxDepth
, you getgoal: @Eq.{1} Prop ?m.2 ?m.3 unknown metavariable '?_uniq.7'
The unknown mvar occurs at
← goal2.getType
.
Relatedly, I’d love if there were a version of withNewMCtxDepth
that didn’t revert the entire mctx
at the end, but instead raised the depth of the newly-created mvars inside back up to the depth outside. That would prevent unknown mvar errors like these and imo provide more intuitive behavior (intuitively, I wouldn’t expect that increasing the depth would affect anything besides assignability).
I’ve run into this situation a few times, and collecting/returning the necessary information from within withNewMCtxDepth
in a stateless fashion can be frustrating. (I would be in favor of withNewMCtxDepth
itself changing if it didn’t break anything!)
It’s not hard to write a combinator which manages the depths like this yourself, but it does feel “risky”! :) (And a little inefficient.)
(Note: an alternative, cheaper implementation that I think achieves the same effect (but might require a core change) is to not actually change the depth of any mvars upon closing out withNewMCtxDepth'
, but to simply say that any mvars with the current mctx depth or greater are assignable. However, to ensure that you can always create a fresh depth easily when entering subsequent (non-nested) withNewMCtxDepth'
s (i.e. to avoid re-entering a previously-used depth), we could maintain the maximum encountered mctx depth in the MetaM
state, and increase the depth based on that.)
Last updated: May 02 2025 at 03:31 UTC