11/-
22Copyright (c) 2026 Lua Viana Reis. All rights reserved.
33Released under Apache 2.0 license as described in the file LICENSE.
4- Authors: Lua Viana Reis
4+ Authors: Lua Viana Reis, Kyle Miller, Gareth Ma
55-/
66module
77
88public meta import Mathlib.Tactic.Set
99public meta import Lean.Elab.BuiltinTerm
10+ public meta import Batteries.Tactic.Exact
1011
1112/-!
1213# The `setm` tactic
@@ -18,90 +19,67 @@ meta section
1819
1920open Lean Elab Tactic Meta Term Syntax
2021
21- namespace Mathlib.Tactic.SetM
22+ abbrev SetMReplaceM := StateT (AssocList Name MVarId) TermElabM
2223
23- partial def wrapSyntheticHoles : Syntax → StateT (NameMap MVarId) TacticM Syntax :=
24- fun stx => do
25- if stx.isOfKind ``Lean.Parser.Term.syntheticHole && stx[1 ].isIdent then
26- let mvar ←
27- if let .some old := (← get).get? stx[1 ].getId then
28- pure old
29- else
30- let name ← mkFreshUserName stx[1 ].getId
31- let mvar := (← mkFreshExprMVar .none (userName := name)).mvarId!
32- modify (.insert · stx[1 ].getId mvar)
33- pure mvar
34- let ident : Ident := mkIdent (← mvar.getTag)
35- return ← withRef stx <| `(? $ident)
36- match stx with
37- | .node info kind args => return .node info kind (← args.mapM wrapSyntheticHoles)
38- | _ => return stx
24+ /-- Collect all synthetic holes and replace them with fresh metavariables. -/
25+ partial def replaceWithMVars (stx : Term) : SetMReplaceM Term := do
26+ let stx ← stx.raw.replaceM fun stx ↦ do
27+ if let `(?$n:ident) := stx then
28+ let mvar ← ((← get).find? n.getId).getDM do
29+ let name ← mkFreshUserName n.getId
30+ let mvar := (← mkFreshExprMVar none (userName := name)).mvarId!
31+ modify (.cons n.getId mvar)
32+ pure mvar
33+ return ← withRef stx <| `(? $(mkIdent (← mvar.getTag)))
34+ else if let `(?_) := stx then
35+ let mvar ← mkFreshExprMVar none
36+ let name ← mkFreshUserName .anonymous
37+ modify (.cons name mvar.mvarId!)
38+ return ← withRef stx <| `(? $(mkIdent name))
39+ else pure none
40+ return ⟨stx⟩
3941
40- syntax (name := setMStx) "setm " term (Parser.Tactic.location)? : tactic
41-
42- -- This code was copied from the `change` tactic in core. I am not sure if in our context,
43- -- all of it is necessary (could there be other synthetic opaque MVars that were not holes?).
44- def elabSetM (e : Expr) (p : Syntax) : TacticM Expr := do
45- let p ← runTermElab do
46- let p ← Term.elabTermEnsuringType p (← inferType e)
47- unless ← isDefEq p e do
48- Term.synthesizeSyntheticMVars (postpone := .partial)
49- discard <| isDefEq p e
50- pure p
51- withAssignableSyntheticOpaque do
52- unless ← isDefEq p e do
53- throwError MessageData.ofLazyM (es := #[p, e]) do
54- let (p, tgt) ← addPPExplicitToExposeDiff p e
55- return m! "setm: pattern{ indentExpr p} \n is not defeq to goal{ indentExpr tgt} "
56- return ← instantiateMVars p
42+ /--
43+ The `setm` tactic ("`set` with matching") matches a pattern containing named holes the type of a
44+ local declaration (using the `at h` syntax) or the main goal, and introduces `let` bound variables
45+ representing subexpressions whose location corresponds to the given named hole. These variables are
46+ also substituted into the type of declaration (or main goal).
47+ -/
48+ syntax (name := setM) "setm " term (Parser.Tactic.location)? : tactic
5749
58- @ [tactic setMStx ]
50+ @ [tactic setM ]
5951public def evalSetM : Tactic
6052 | `(tactic| setm $pat:term $[$loc:location]?) => withReducibleAndInstances do
61- let (pat, mvars) ← StateT.run (wrapSyntheticHoles pat) .empty
6253 let locations := expandOptLocation (Lean.mkOptionalNode loc)
63- -- First, unify the pattern with the target of each location.
64- withLocation locations
65- (atLocal := unify pat ∘ .some)
66- (atTarget := unify pat .none)
67- (failed := fun _ ↦ throwError "'setm' tactic failed" )
68- -- The, for each metavariable...
69- for (n, m) in mvars do
70- -- ... instantiate it and introduce a let into the context...
71- let val ← instantiateMVars (.mvar m)
72- let ty ← m.getType'
73- let fvar ← liftMetaTacticAux fun goal ↦ do
74- let (fvar, goal) ← (← goal.define n ty val).intro1P
75- return (fvar, [goal])
76- -- ... and rewrite the introduced decl in each one of the locations.
54+ withMainContext do
55+ let (pat, mvars) ← (replaceWithMVars pat).run {}
56+ let pat ← Term.elabTerm pat none
57+ let mut g ← getMainGoal
58+ for (name, mvar) in mvars.toList.reverse do
59+ let mvar' ← mkFreshExprMVar none
60+ g ← g.define name (← mvar'.mvarId!.getType) mvar'
61+ let (fvar, g') ← g.intro1P
62+ mvar.assign (.fvar fvar)
63+ g := g'
64+ replaceMainGoal [g]
7765 withLocation locations
78- (atLocal := rewrite fvar val ∘ .some)
79- (atTarget := rewrite fvar val .none)
80- (failed := fun _ ↦ throwError "'setm' tactic failed" )
66+ (atLocal := fun loc ↦ do
67+ if ← isDefEq pat (← loc.getType) then
68+ liftMetaTactic fun g ↦ do
69+ return [← g.replaceLocalDeclDefEq loc pat]
70+ else
71+ defeqError pat (← loc.getType))
72+ (atTarget := do
73+ liftMetaTactic fun g ↦ do
74+ if ← isDefEq pat (← g.getType) then
75+ return [← g.replaceTargetDefEq pat]
76+ else
77+ defeqError pat (← g.getType))
78+ (failed := fun _ ↦ throwError "tactic `setm` failed" )
8179 | _ => throwUnsupportedSyntax
8280 where
83- unify (pat : Syntax) (lvar : Option FVarId) : TacticM Unit := do
84- let e ← if let .some l := lvar
85- then l.getType
86- else getMainTarget
87- let tgt ← elabSetM e pat
88- liftMetaTactic fun goal => do
89- return [← changeDecl goal tgt lvar]
90- rewrite (fvar : FVarId) (val : Expr) (lvar : Option FVarId) : TacticM Unit := do
91- liftMetaTactic fun goal ↦ do
92- let tgt ← instantiateMVars (← lvar.elim goal.getType (·.getType))
93- let absTgt ← kabstract tgt val
94- let goal ← if absTgt.hasLooseBVars
95- then changeDecl goal (absTgt.instantiate1 (.fvar fvar)) lvar
96- else pure goal
97- return [goal]
98- changeDecl (goal : MVarId) (tgt : Expr) (l? : Option FVarId := .none) : MetaM MVarId :=
99- match l? with
100- | .some fvar => do
101- let lctx := (← goal.getDecl).lctx.modifyLocalDecl fvar
102- fun d ↦ d.setType tgt
103- goal.modifyLCtx (fun _ ↦ lctx)
104- return goal
105- | .none => goal.replaceTargetDefEq tgt
106-
107- end Mathlib.Tactic.SetM
81+ defeqError {α} (p e : Expr) : MetaM α :=
82+ throwError MessageData.ofLazyM (es := #[p, e]) do
83+ let (p, tgt) ← addPPExplicitToExposeDiff p e
84+ return m! "setm pattern{ indentExpr p} \n is not definitionally equal {
85+ "" } to the target{ indentExpr tgt} "
0 commit comments