@@ -77,6 +77,25 @@ public meta section
7777
7878open Lean Meta Elab Tactic
7979
80+ /-- Configuration for the `convert` family of tactics.
81+ This is `Congr!.Config` with different, less aggressive, defaults.
82+ -/
83+ structure Convert.Config extends Congr!.Config where
84+ postTransparency := .default -- Will be set to .reducible in a follow-up PR
85+
86+ /-- Elaborator for `Convert.Config` (which is equivalent to `Congr!.Config`
87+ but with different, less aggressive, defaults). -/
88+ declare_config_elab Convert.elabConfig Convert.Config
89+
90+ /--
91+ A configuration option that makes `convert` do the sorts of aggressive unfoldings that `congr`
92+ does while also similarly preventing `convert` from considering partial applications or congruences
93+ between different functions being applied.
94+ -/
95+ abbrev Convert.Config.unfoldSameFun : Convert.Config :=
96+ { Congr!.Config.unfoldSameFun with
97+ postTransparency := .default } -- Will be set to .reducible in a follow-up PR
98+
8099/--
81100Close the goal `g` using `Eq.mp v e`,
82101where `v` is a metavariable asserting that the type of `g` and `e` are equal.
@@ -132,8 +151,7 @@ pattern-matched, like `rintro` would, using the `with` keyword.
132151See also `convert_to t`, where `t` specifies the expected type, instead of a proof term of type `t`.
133152In other words, `convert_to t` works like `convert (?_ : t)`. Both tactics use the same options.
134153
135- * `convert! e` uses default transparency when solving side goals. This is currently the same
136- behaviour as `convert`, but `convert` will use reducible transparency in the future.
154+ * `convert! e` uses default transparency, rather than reducible, when solving side goals.
137155* `convert ← e` creates equality goals in the opposite direction (with the goal type on the right).
138156* `convert e using n`, where `n` is a positive numeral, controls the depth with which congruence is
139157 applied. For example, if the main goal is `⊢ Prime (n + n + 1)` and `e : Prime (2 * n + 1)`, then
@@ -156,6 +174,7 @@ example {n : ℕ} (e : Prime (2 * n + 1)) :
156174 ring
157175
158176-- `convert` can fail where `exact` succeeds.
177+ def p (n : ℕ) := True
159178example (h : p 0) : p 1 := by
160179 fail_if_success
161180 convert h -- fails, left-over goal 1 = 0
@@ -172,16 +191,16 @@ example (p q : Nat → Prop) (h : ∀ ε > 0, p ε) :
172191 sorry
173192```
174193-/
175- syntax (name := convert) "convert" Lean.Parser.Tactic.optConfig " ←" ? ppSpace term ( " using " num)?
176- (" with" (ppSpace colGt rintroPat)*)? : tactic
194+ syntax (name := convert) "convert" "!" ? Lean.Parser.Tactic.optConfig " ←" ? ppSpace term
195+ (" using " num)? ( " with" (ppSpace colGt rintroPat)*)? : tactic
177196
178197@ [tactic_alt convert]
179198syntax (name := convert!) "convert!" Lean.Parser.Tactic.optConfig " ←" ? ppSpace term
180199 (" using " num)? (" with" (ppSpace colGt rintroPat)*)? : tactic
181200
182201macro_rules
183202| `(tactic| convert! $cfg $[←%$l]? $t $[using $n]? $[with $[$w]*]?) =>
184- `(tactic| convert $cfg $[←%$l]? $t:term $[using $n]? $[with $[$w]*]?)
203+ `(tactic| convert ! $cfg $[←%$l]? $t:term $[using $n]? $[with $[$w]*]?)
185204
186205/--
187206Elaborates `term` ensuring the expected type, allowing stuck metavariables.
@@ -200,14 +219,39 @@ def elabTermForConvert (term : Syntax) (expectedType? : Option Expr) :
200219 return t
201220
202221elab_rules : tactic
203- | `(tactic| convert $cfg $[←%$sym]? $term $[using $n]? $[with $ps?*]?) =>
222+ | `(tactic| convert%$tk $[!%$semireducible]? $cfg $[←%$sym]? $term $[using $n]? $[with $ps?*]?) =>
204223 withMainContext do
205- let config ← Congr! .elabConfig (mkOptionalNode cfg)
224+ let config := { ← Convert .elabConfig cfg with }
206225 let patterns := (ps?.getD #[]).toList
207226 let expectedType ← mkFreshExprMVar (mkSort (← getLevel (← getMainTarget)))
208227 let (e, gs) ← elabTermForConvert term expectedType
209- liftMetaTactic fun g ↦
210- return (← g.convert e sym.isSome (n.map (·.getNat)) config patterns) ++ gs
228+ if semireducible.isNone then
229+ liftMetaTactic fun g ↦ do
230+ -- Suggest `convert!` instead of `convert` if we rely on default transparency.
231+ let redGoals ← g.convert e sym.isSome (n.map (·.getNat))
232+ { config with postTransparency := .reducible }
233+ patterns
234+ let defaultGoals ← g.convert e sym.isSome (n.map (·.getNat))
235+ { config with postTransparency := .default }
236+ patterns
237+ if redGoals.length == defaultGoals.length then
238+ let sameGoals ← try
239+ (redGoals.zip defaultGoals).allM fun (g₁, g₂) => do
240+ -- Check that they agree on the set of free variables, otherwise we get errors.
241+ -- We assume the context in the `convert` case is a subset of the `convert!` case
242+ -- since `convert!` can more agressively unfold and introduce more variables.
243+ if !(← g₁.getDecl).lctx.isSubPrefixOf (← g₂.getDecl).lctx then return false
244+ g₂.withContext <| withReducible <| isDefEq (← g₁.getType) (← g₂.getType)
245+ catch _ => pure false
246+ -- If the goals are not the same then we should preserve the original behaviour
247+ -- and use `convert!`.
248+ if !sameGoals then
249+ let tac ← `(tactic| convert!%$tk $cfg $[←%$sym]? $term $[using $n]? $[with $ps?*]?)
250+ TryThis.addSuggestion tk { suggestion := tac } (origSpan? := ← getRef)
251+ return defaultGoals ++ gs
252+ else
253+ liftMetaTactic fun g ↦
254+ return (← g.convert e sym.isSome (n.map (·.getNat)) config patterns) ++ gs
211255
212256/--
213257`convert_to t` on a goal `⊢ t'` changes the goal to `⊢ t` and adds new goals for proving the
@@ -221,8 +265,7 @@ pattern-matched, like `rintro` would, using the `with` keyword.
221265`convert e`, where `e` is a term of type `t`, uses `e` to close the new main goal. In other words,
222266`convert e` works like `convert_to t; refine e`. Both tactics use the same options.
223267
224- * `convert_to! t` uses default transparency when solving side goals. This is currently the same
225- behaviour as `convert_to`, but `convert_to` will use reducible transparency in the future.
268+ * `convert_to! t` uses default transparency, rather than reducible, when solving side goals.
226269* `convert_to ty at h` changes the type of the local hypothesis `h` to `ty`. If later local
227270 hypotheses or the goal depend on `h`, then `convert_to t at h` may leave a copy of `h`.
228271* `convert_to ← t` creates equality goals in the opposite direction (with the original goal type on
@@ -238,7 +281,7 @@ pattern-matched, like `rintro` would, using the `with` keyword.
238281* `convert_to (config := cfg) t` uses the configuration options in `cfg` to control the congruence
239282 rules (see `Congr!.Config`).
240283 -/
241- syntax (name := convertTo) "convert_to" Lean. Parser.Tactic.optConfig " ←" ? ppSpace term
284+ syntax (name := convertTo) "convert_to" ( "!" )? Parser.Tactic.optConfig " ←" ? ppSpace term
242285 (" using " num)? (" with" (ppSpace colGt rintroPat)*)? (Parser.Tactic.location)? : tactic
243286
244287@ [tactic_alt convertTo]
@@ -247,13 +290,20 @@ syntax (name := convert_to!) "convert_to!" Lean.Parser.Tactic.optConfig " ←"?
247290
248291macro_rules
249292| `(tactic| convert_to! $cfg $[←%$l]? $t $[using $n]? $[with $w]? $[$loc]?) =>
250- `(tactic| convert_to $cfg $[←%$l]? $t:term $[using $n]? $[with $w]? $[$loc]?)
293+ `(tactic| convert_to ! $cfg $[←%$l]? $t:term $[using $n]? $[with $w]? $[$loc]?)
251294
252295elab_rules : tactic
253- | `(tactic| convert_to $cfg $[←%$sym]? $newType $[using $n]?
296+ | `(tactic| convert_to $[!%$semireducible]? $ cfg $[←%$sym]? $newType $[using $n]?
254297 $[with $ps?*]? $[$loc?:location]?) => do
255298 let n : ℕ := n |>.map (·.getNat) |>.getD 1
256- let config ← Congr!.elabConfig cfg
299+ let mut config := { ← Convert.elabConfig cfg with }
300+ if semireducible.isSome then
301+ config := { config with
302+ -- TODO: also enable this in the future? (Not right now, for backwards compatibility).
303+ -- transparency := default,
304+ -- preTransparency := default,
305+ -- postTransparency := default -- Disabled while the port to `convert!` happens.
306+ }
257307 let patterns := (ps?.getD #[]).toList
258308 withLocation (expandOptLocation (mkOptionalNode loc?))
259309 (atLocal := fun fvarId ↦ do
@@ -279,8 +329,7 @@ into new goals, using the hole's name, if any, as the goal case name.
279329Like `congr!`, `convert_to` introduces variables while applying congruence rules. These can be
280330pattern-matched, like `rintro` would, using the `with` keyword.
281331
282- * `ac_change! t` uses default transparency when solving side goals. This is currently the same
283- behaviour as `ac_change`, but `ac_change` will use reducible transparency in the future.
332+ * `ac_change! t` uses default transparency, rather than reducible, when solving side goals.
284333* `ac_change t using n`, where `n` is a positive numeral, controls the depth with which congruence
285334 is applied. For example, if the main goal is `⊢ Prime ((a * b + 1) + c)`,
286335 then `ac_change Prime ((1 + a * b) + c) using 2` solves the side goals, and
@@ -305,5 +354,4 @@ macro_rules
305354| `(tactic| ac_change! $t $[using $n]?) =>
306355 `(tactic| convert_to! $t:term $[using $n]? <;> try ac_rfl)
307356
308-
309357end Mathlib.Tactic
0 commit comments