Skip to content

Commit c890823

Browse files
committed
new version based on Kyle's
1 parent f935700 commit c890823

2 files changed

Lines changed: 72 additions & 83 deletions

File tree

Mathlib/Tactic/Setm.lean

Lines changed: 57 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
/-
22
Copyright (c) 2026 Lua Viana Reis. All rights reserved.
33
Released 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
-/
66
module
77

88
public meta import Mathlib.Tactic.Set
99
public 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

1920
open 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}\nis 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]
5951
public 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}\nis not definitionally equal {
85+
""}to the target{indentExpr tgt}"

MathlibTest/Tactic/Setm.lean

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ example (h1 : 1 + 1 = 5) (h2 : 1 + 3 = 5) (h3 : 1 + 2 = 5) : True := by
1919
setm ?A + _ = ?B at h1 h2 h3
2020
guard_hyp A :=ₛ 1
2121
guard_hyp B :=ₛ 5
22-
guard_hyp h1 :ₛ A + A = B
22+
guard_hyp h1 :ₛ A + 1 = B
2323
guard_hyp h2 :ₛ A + 3 = B
2424
guard_hyp h3 :ₛ A + 2 = B
2525
trivial
@@ -40,6 +40,17 @@ example (h : b + a = c) : a + b = c := by
4040
guard_hyp B :=ₛ a
4141
exact h
4242

43+
/- Strange problem -/
44+
45+
example (n : Bool) : 1 + 2 = 3 := by
46+
cases n
47+
· setm ?A + ?B = _
48+
guard_hyp A :=ₛ 1
49+
guard_hyp B :=ₛ 2
50+
trivial
51+
setm ?A + ?B = _
52+
trivial
53+
4354
/- Test reducible + instances transparency -/
4455

4556
def NotQuiteNat : Type := Nat
@@ -54,9 +65,9 @@ example {a b c : NotQuiteNat} (h : a + b = c) : True := by
5465
trivial
5566

5667
/--
57-
error: setm: pattern
58-
@Eq Nat (?A✝ + ?B✝) ?m.12
59-
is not defeq to goal
68+
error: setm pattern
69+
@Eq Nat (A + B) ?m.12
70+
is not definitionally equal to the target
6071
@Eq NotQuiteNat (a + b) c
6172
-/
6273
#guard_msgs in

0 commit comments

Comments
 (0)