@@ -18,6 +18,7 @@ public import Lean.Meta.Sym.Simp.Rewrite
1818public import Lean.Meta.Sym.Simp.Simproc
1919public import Lean.Elab.Tactic.Grind.Main
2020public import Lean.Elab.Tactic.Grind.Basic
21+ import Lean.Meta.Sym.ProofInstInfo
2122
2223open Lean Parser Meta Elab Tactic Sym
2324open Lean.Elab.Tactic.Do Lean.Elab.Tactic.Do.SpecAttr
@@ -277,6 +278,38 @@ private structure ParsedArgs where
277278 scope : VCGen.Scope
278279 invariantAlts? : Option (Std.HashMap Nat Syntax)
279280
281+ /-- Build a `Sym.Pattern` from `e` by abstracting the metavariables `xs` into pattern variables.
282+ `checkTypeMask?` is `none` because `until` holes appear as function arguments, whose types the
283+ enclosing application already constrains. -/
284+ private def mkUntilPattern (xs : Array Expr) (e : Expr) : MetaM Sym.Pattern := do
285+ let pattern := e.abstract xs
286+ let mut varTypes := #[]
287+ for h : i in [0 :xs.size] do
288+ varTypes := varTypes.push ((← inferType xs[i]).abstractRange i xs)
289+ let mut fnInfos : AssocList Name Sym.ProofInstInfo := {}
290+ for declName in pattern.getUsedConstants do
291+ if let some info ← Sym.mkProofInstInfo? declName then
292+ fnInfos := fnInfos.insertNew declName info
293+ let varInfos? ← Sym.mkProofInstArgInfo? xs
294+ return { levelParams := [], varTypes, pattern, fnInfos, varInfos?, checkTypeMask? := none }
295+
296+ /-- Build a deferred `until` pattern (holes `_` allowed, as in `conv in $t`). The pattern term is
297+ elaborated lazily when the first program is seen in `solve`, using that program's monad `m` as the
298+ expected type (`m _`) so overloaded heads resolve; the result is cached. The holes become pattern
299+ variables. -/
300+ private def elabUntilPattern (p : Term) : TermElabM (IO.Ref UntilPatternThunk) := do
301+ let lctx ← getLCtx
302+ let localInsts ← getLocalInstances
303+ IO.mkRef <| UntilPatternThunk.deferred fun m =>
304+ withLCtx lctx localInsts <| Term.TermElabM.run' <|
305+ -- Restore the metavariable state but keep info trees, so hovers work on the pattern.
306+ Term.withoutModifyingElabMetaStateWithInfo <| withRef p <|
307+ withTheReader Term.Context ({ · with ignoreTCFailures := true }) <|
308+ Term.withoutErrToSorry do
309+ let e ← instantiateMVars (← Term.elabTerm p (some (mkApp m (← mkFreshTypeMVar))))
310+ let xs := (e.collectMVars {}).result.map Expr.mvar
311+ mkUntilPattern xs e
312+
280313/-- Parse `mvcgen'` arguments. -/
281314private def parseArgs (stx : Syntax) (goal : MVarId) : TermElabM ParsedArgs := goal.withContext do
282315 if mvcgen.warning.get (← getOptions) then
@@ -290,16 +323,18 @@ private def parseArgs (stx : Syntax) (goal : MVarId) : TermElabM ParsedArgs := g
290323 -- explicit `(elimLets := true)` at the syntax level (upstream `Config` can't
291324 -- distinguish "default true" from "user-set true"); not yet wired.
292325 let (ctx, scope) ← VCGen.mkContext stx[2 ] goal
293- let hypSimpMethods ← elabSimplifyingAssumptions stx[4 ]
294- let invariantAlts? ← parseInvariantMap stx[3 ]
326+ let untilPat? ← if stx[3 ].isNone then pure none else some <$> elabUntilPattern ⟨stx[3 ][1 ]⟩
327+ let hypSimpMethods ← elabSimplifyingAssumptions stx[5 ]
328+ let invariantAlts? ← parseInvariantMap stx[4 ]
295329 let ctx := { ctx with
296330 hypSimpMethods,
297331 trivial := config.trivial,
298332 useJP := config.jp,
299333 errorOnMissingSpec := config.errorOnMissingSpec,
300334 debug := config.debug,
301335 internalize := config.internalize,
302- invariantAlts := invariantAlts?.getD {} }
336+ invariantAlts := invariantAlts?.getD {},
337+ untilPat? }
303338 return { config, ctx, scope, invariantAlts? }
304339
305340/-- `mvcgen'` step inside `sym => …` blocks. -/
@@ -314,7 +349,7 @@ def evalSymMVCGen' : Lean.Elab.Tactic.Grind.GrindTactic := fun stx => do
314349 return result
315350 if args.invariantAlts?.isNone then
316351 runTacticM (goals := result.invariants.toList) <|
317- elabInvariants stx[3 ] result.invariants (suggestInvariant (result.vcs.map (·.mvarId)))
352+ elabInvariants stx[4 ] result.invariants (suggestInvariant (result.vcs.map (·.mvarId)))
318353 let invariants ← result.invariants.filterM (not <$> ·.isAssigned)
319354 let newGoals ← Lean.Elab.Tactic.Grind.liftGrindM do
320355 let invGoals ← invariants.toList.mapM Grind.mkGoalCore
@@ -328,7 +363,7 @@ goals. The optional `with $g:grind` clause runs as `<;> $g` and lets the user-su
328363grind step share an internalised E-graph with `mvcgen'`. -/
329364@ [builtin_tactic Lean.Parser.Tactic.mvcgen']
330365public def elabMVCGen' : Tactic := fun stx => withMainContext do
331- let `(tactic| mvcgen'%$tk $cfg:optConfig $[[$lems,*]]? $(invs)?
366+ let `(tactic| mvcgen'%$tk $cfg:optConfig $[[$lems,*]]? $[ until $u:term]? $ (invs)?
332367 $[simplifying_assumptions $(sa)? $[[$thms,*]]?]? $[with $g:grind]?) := stx
333368 | throwUnsupportedSyntax
334369 -- Without `with`, no downstream grind step will read the E-graph, so opt out of
@@ -338,7 +373,7 @@ public def elabMVCGen' : Tactic := fun stx => withMainContext do
338373 | none => do
339374 let off ← `(optConfig| -internalize)
340375 pure (Lean.Parser.Tactic.appendConfig off cfg)
341- let core ← `(grind| mvcgen'%$tk $cfg:optConfig $[[$lems,*]]? $(invs)?
376+ let core ← `(grind| mvcgen'%$tk $cfg:optConfig $[[$lems,*]]? $[ until $u:term]? $ (invs)?
342377 $[simplifying_assumptions $(sa)? $[[$thms,*]]?]?)
343378 let step ← match g with
344379 | some g => `(grind| $core <;> $g)
0 commit comments