Skip to content

Commit 2215f28

Browse files
committed
fix(translate): don't unfold aux lemmas, but translate them (leanprover-community#33603)
`to_additive` has some troublesome interactions with the module system, because it sometimes relies on unfolding theorems whose value is not exported. This PR fixes that by never unfolding these theorems, and instead creating their translation the first time they appear. This will help remove some instances of `import all` and instances of `proofsInPublic`. The only theorems that we still unfold are the auxiliary theorems created by `simp`. These have very small proofs, and these proofs are always exported, so it is fine to unfold them.
1 parent e55b59b commit 2215f28

5 files changed

Lines changed: 80 additions & 80 deletions

File tree

Mathlib/Lean/Name.lean

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -58,14 +58,3 @@ def Lean.Name.decapitalize (n : Name) : Name :=
5858
n.modifyBase fun
5959
| .str p s => .str p s.decapitalize
6060
| n => n
61-
62-
/-- Whether the lemma has a name of the form produced by `Lean.Meta.mkAuxLemma`. -/
63-
def Lean.Name.isAuxLemma (n : Name) : Bool :=
64-
match n with
65-
-- `mkAuxLemma` generally allows for arbitrary prefixes but these are the ones produced by core.
66-
| .str _ s => "_proof_".isPrefixOf s || "_simp_".isPrefixOf s
67-
| _ => false
68-
69-
/-- Unfold all lemmas created by `Lean.Meta.mkAuxLemma`. -/
70-
def Lean.Meta.unfoldAuxLemmas (e : Expr) : MetaM Expr := do
71-
deltaExpand e Lean.Name.isAuxLemma

Mathlib/MeasureTheory/Function/LpSpace/DomAct/Continuous.lean

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ module
88
public import Mathlib.MeasureTheory.Function.LpSpace.DomAct.Basic
99
public import Mathlib.MeasureTheory.Function.LpSpace.ContinuousCompMeasurePreserving
1010
public import Mathlib.Topology.Algebra.Constructions.DomMulAct
11-
import all Mathlib.MeasureTheory.Function.LpSpace.DomAct.Basic -- for `to_additive` to unfold proof
1211

1312
/-!
1413
# Continuity of the action of `Mᵈᵐᵃ` on `MeasureSpace.Lp E p μ`

Mathlib/Tactic/Translate/Core.lean

Lines changed: 51 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -531,16 +531,6 @@ def applyReplacementLambda (t : TranslateData) (dontTranslate : List Nat) (e : E
531531
else
532532
applyReplacementFun t e #[]
533533

534-
/-- Unfold auxlemmas in the type and value. -/
535-
def declUnfoldAuxLemmas (decl : ConstantInfo) : MetaM ConstantInfo := do
536-
let mut decl := decl
537-
decl := decl.updateType <| ← unfoldAuxLemmas decl.type
538-
if let some v := decl.value? (allowOpaque := true) then
539-
trace[translate] "value before unfold:{indentExpr v}"
540-
decl := decl.updateValue <| ← unfoldAuxLemmas v
541-
trace[translate] "value after unfold:{indentExpr decl.value!}"
542-
return decl
543-
544534
/-- Run applyReplacementFun on the given `srcDecl` to make a new declaration with name `tgt` -/
545535
def updateDecl (t : TranslateData) (tgt : Name) (srcDecl : ConstantInfo)
546536
(reorder : List (List Nat)) (dont : List Nat) : MetaM ConstantInfo := do
@@ -578,48 +568,49 @@ def findRelevantArg (t : TranslateData) (nm : Name) : CoreM Nat := MetaM.run' do
578568
trace[translate_detail] "findRelevantArg: {arg}"
579569
return arg.getD 0
580570

581-
/-- Abstracts the nested proofs in the value of `decl` if it is a def.
582-
This follows the behaviour of `Elab.abstractNestedProofs`. -/
583-
def declAbstractNestedProofs (decl : ConstantInfo) : MetaM ConstantInfo := do
584-
let .defnInfo info := decl | return decl
585-
let value ← withDeclNameForAuxNaming decl.name do Meta.abstractNestedProofs info.value
586-
return .defnInfo { info with value }
571+
/-- Unfold `simp` auxlemmas in the type and value.
572+
The reason why we can't just translate them is that they are generated by the `@[simp]` attribute,
573+
so it would require a change in the implementation of `@[simp]` to add these translateions.
574+
Additionally, these lemmas have very short proofs, so unfolding them is not costly. -/
575+
def declUnfoldSimpAuxLemmas (decl : ConstantInfo) : MetaM ConstantInfo := do
576+
let unfold (e : Expr) := deltaExpand e fun
577+
| .str _ s => "_simp_".isPrefixOf s
578+
| _ => false
579+
let mut decl := decl
580+
decl := decl.updateType <| ← unfold decl.type
581+
if let some v := decl.value? (allowOpaque := true) then
582+
trace[translate] "value before unfold:{indentExpr v}"
583+
decl := decl.updateValue <| ← unfold v
584+
trace[translate] "value after unfold:{indentExpr decl.value!}"
585+
return decl
587586

588-
/-- Find the target name of `pre` and all created auxiliary declarations. -/
589-
def findTargetName (env : Environment) (t : TranslateData) (src pre tgt_pre : Name) : CoreM Name :=
587+
/-- Find the target name of `src`, which is assumed to have been selected by `findAuxDecls`. -/
588+
def findTargetName (env : Environment) (t : TranslateData) (src pre tgt_pre : Name) :
589+
CoreM Name := do
590590
/- This covers auxiliary declarations like `match_i` and `proof_i`. -/
591-
if let some post := pre.isPrefixOf? src then
592-
return tgt_pre ++ post
593-
else if src.hasMacroScopes then
594-
-- This branch should come before the next one because an aux def may be both private and macro
595-
-- scoped - but really the next branch shouldn't just assume all private defs are eqns??
591+
if let some post := (privateToUserName pre).isPrefixOf? (privateToUserName src) then
592+
let tgt := tgt_pre ++ post
593+
return if isPrivateName src then mkPrivateName env tgt else tgt
594+
if src.hasMacroScopes then
596595
mkFreshUserName src.eraseMacroScopes
597-
/- This covers equation lemmas (for other declarations). -/
598-
else if let some post := privateToUserName? src then
599-
match findTranslation? env t post.getPrefix with
600-
-- this is an equation lemma for a declaration without a translation. We will skip this.
601-
| none => return src
602-
-- this is an equation lemma for a declaration with a translation. We will translate this.
603-
-- Note: if this errors we could do this instead by calling `getEqnsFor?`
604-
| some addName => return src.updatePrefix <| mkPrivateName env addName
605596
else
606-
throwError "internal @[{t.attrName}] error."
607-
608-
/-- Returns a `NameSet` of all auxiliary constants in `e` that might have been generated
609-
when adding `pre` to the environment.
610-
Examples include `pre.match_5` and
611-
`_private.Mathlib.MyFile.someOtherNamespace.someOtherDeclaration._eq_2`.
612-
The last two examples may or may not have been generated by this declaration.
613-
The last example may or may not be the equation lemma of a declaration with a translation attribute.
614-
We will only translate it if it has a translation attribute.
615-
616-
Note that this function would return `proof_nnn` aux lemmas if
617-
we hadn't unfolded them in `declUnfoldAuxLemmas`.
597+
withDeclNameForAuxNaming src do mkAuxDeclName (Name.mkSimple s!"_{t.attrName.toString}")
598+
599+
/-- Returns a `NameSet` of auxiliary constants in `decl` that might have been generated
600+
when adding `pre` to the environment, and which hence might need to be translated.
601+
Examples include `pre.match_5`, `pre._proof_2`, `someOtherDeclaration._proof_2` and `wrapped✝`.
602+
The reason why we have to include `_proof_i` lemmas from other declarations is that there is a
603+
cache of such proofs, and previous such auxiliary proofs are reused when possible.
604+
These auxiliary declarations may be private or not, independent of whether `pre` is private.
605+
`wrapped✝` is generated by `irreducible_def`, and it has macro scopes.
618606
-/
619-
def findAuxDecls (e : Expr) (pre : Name) : NameSet :=
620-
e.foldConsts ∅ fun n l ↦
621-
if (privateToUserName n).getPrefix == privateToUserName pre || n.hasMacroScopes then
622-
l.insert n
607+
def findAuxDecls (decl : ConstantInfo) (pre : Name) : CoreM (Array Name) := do
608+
let env ← withoutExporting getEnv
609+
return (Expr.app decl.type (decl.value! (allowOpaque := true))).foldConsts #[] fun n l ↦
610+
if (env.find? n).any (·.hasValue (allowOpaque := true)) &&
611+
((match n with | .str _ s => "_proof_".isPrefixOf s | _ => false) ||
612+
(privateToUserName n).getPrefix == privateToUserName pre || n.hasMacroScopes) then
613+
l.push n
623614
else
624615
l
625616

@@ -631,7 +622,7 @@ occurring in `src` using the `translations` dictionary.
631622
`pre` is the declaration that got the translation attribute and `tgt_pre` is the target of this
632623
declaration. -/
633624
partial def transformDeclRec (t : TranslateData) (ref : Syntax) (pre tgt_pre src : Name)
634-
(reorder : List (List Nat) := []) (dontTranslate : List Nat := []) : CoreM Unit := do
625+
(dontTranslate : List Nat) (reorder : List (List Nat) := []) : CoreM Unit := do
635626
let env ← getEnv
636627
trace[translate_detail] "visiting {src}"
637628
-- if we have already translated this declaration, we do nothing.
@@ -655,27 +646,28 @@ partial def transformDeclRec (t : TranslateData) (ref : Syntax) (pre tgt_pre src
655646
return
656647
let srcDecl ← withoutExporting do getConstInfo src
657648
-- we first unfold all auxlemmas, since they are not always able to be translated on their own
658-
let srcDecl ← withoutExporting do MetaM.run' do declUnfoldAuxLemmas srcDecl
649+
let srcDecl ← withoutExporting do MetaM.run' do declUnfoldSimpAuxLemmas srcDecl
659650
-- we then transform all auxiliary declarations generated when elaborating `pre`
660-
for n in findAuxDecls srcDecl.type pre do
661-
transformDeclRec t ref pre tgt_pre n
662-
if let some value := srcDecl.value? (allowOpaque := true) then
663-
for n in findAuxDecls value pre do
664-
transformDeclRec t ref pre tgt_pre n
651+
for n in ← findAuxDecls srcDecl pre do
652+
transformDeclRec t ref pre tgt_pre n dontTranslate
665653
-- expose target body when source body is exposed
666654
withExporting (isExporting := (← getEnv).setExporting true |>.find? src |>.any (·.hasValue)) do
667655
-- if the auxiliary declaration doesn't have prefix `pre`, then we have to add this declaration
668656
-- to the translation dictionary, since otherwise we cannot translate the name.
669657
let relevantArg ← findRelevantArg t src
670658
if !pre.isPrefixOf src || src != pre && relevantArg != 0 then
671659
insertTranslation t src tgt { relevantArg }
660+
-- We still lack a heuristic that automatically infers the `dontTranslate`,
661+
-- so for now we do a best guess based on argument names.
662+
let dontTranslate ← if dontTranslate.isEmpty then pure [] else
663+
if src == pre then pure dontTranslate else
664+
let namesPre := (← getConstInfo pre).type.getForallBinderNames
665+
let namesSrc := (← getConstInfo src).type.getForallBinderNames
666+
pure <| dontTranslate.filterMap (namesPre[·]? >>= namesSrc.idxOf?)
672667
-- now transform the source declaration
673668
let trgDecl ← MetaM.run' <| updateDecl t tgt srcDecl reorder dontTranslate
674-
let some value := trgDecl.value? (allowOpaque := true)
675-
| throwError "Expected {tgt} to have a value."
669+
let value := trgDecl.value! (allowOpaque := true)
676670
trace[translate] "generating\n{tgt} : {trgDecl.type} :=\n {value}"
677-
-- "Refold" all the aux lemmas that we unfolded.
678-
let trgDecl ← MetaM.run' <| declAbstractNestedProofs trgDecl
679671
/- If `src` is explicitly marked as `noncomputable`, then add the new decl as a declaration but
680672
do not compile it, and mark is as noncomputable. Otherwise, only log errors in compiling if `src`
681673
has executable code.
@@ -1194,7 +1186,7 @@ partial def addTranslationAttr (t : TranslateData) (src : Name) (cfg : Config)
11941186
proceedFields t src tgt argInfo
11951187
else
11961188
-- tgt doesn't exist, so let's make it
1197-
transformDeclRec t cfg.ref src tgt src argInfo.reorder cfg.dontTranslate
1189+
transformDeclRec t cfg.ref src tgt src cfg.dontTranslate argInfo.reorder
11981190
let nestedNames ← copyMetaData t cfg src tgt argInfo
11991191
-- add pop-up information when mousing over the given translated name
12001192
-- (the information will be over the attribute if no translated name is given)

MathlibTest/ToDual.lean

Lines changed: 28 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ import Mathlib.Order.Defs.PartialOrder
22
import Mathlib.Order.Notation
33
import Mathlib.Tactic.ToAdditive
44

5+
variable {α : Type} [PartialOrder α] (a b c : α)
6+
57
-- test that we can translate between structures, reordering the arguments of the fields
68
class SemilatticeInf (α : Type) extends PartialOrder α, Min α where
79
le_inf : ∀ a b c : α, a ≤ b → a ≤ c → a ≤ b ⊓ c
@@ -39,8 +41,7 @@ in the application
3941
-/
4042
#guard_msgs in
4143
@[to_dual]
42-
instance {α : Type} [PartialOrder α] [Min α]
43-
(le_inf : ∀ a b c : α, a ≤ b → a ≤ c → a ≤ b ⊓ c) : SemilatticeInf α where
44+
instance [Min α] (le_inf : ∀ a b c : α, a ≤ b → a ≤ c → a ≤ b ⊓ c) : SemilatticeInf α where
4445
le_inf
4546

4647

@@ -53,7 +54,7 @@ Note: This linter can be disabled with `set_option linter.translateReorder false
5354
-/
5455
#guard_msgs in
5556
@[to_dual self (reorder := a b)]
56-
theorem le_imp_le {α : Type} [PartialOrder α] (a b : α) : a ≤ b → a ≤ b := id
57+
theorem le_imp_le : a ≤ b → a ≤ b := id
5758

5859
-- The comparison on `reorder`s can see that `a b` is the same as `b a`:
5960
/--
@@ -64,7 +65,7 @@ Note: This linter can be disabled with `set_option linter.translateReorder false
6465
-/
6566
#guard_msgs in
6667
@[to_dual self (reorder := b a)]
67-
theorem le_imp_le' {α : Type} [PartialOrder α] (a b : α) : a ≤ b → a ≤ b := id
68+
theorem le_imp_le' : a ≤ b → a ≤ b := id
6869

6970
-- It is possible to overwrite the autogenerated `reorder`:
7071
/--
@@ -75,7 +76,7 @@ but 'le_imp_le''' has type
7576
-/
7677
#guard_msgs in
7778
@[to_dual self (reorder := 2 3)]
78-
theorem le_imp_le'' {α : Type} [PartialOrder α] (a b : α) : a ≤ b → a ≤ b := id
79+
theorem le_imp_le'' : a ≤ b → a ≤ b := id
7980

8081
-- We can even overwrite it with the empty `reorder`:
8182
/--
@@ -86,7 +87,7 @@ but 'le_imp_le'''' has type
8687
-/
8788
#guard_msgs in
8889
@[to_dual self (reorder := )]
89-
theorem le_imp_le''' {α : Type} [PartialOrder α] (a b : α) : a ≤ b → a ≤ b := id
90+
theorem le_imp_le''' : a ≤ b → a ≤ b := id
9091

9192
-- Test a larger permutation:
9293

@@ -104,11 +105,11 @@ Maybe we should have a new command `@[to_dual_translate]`, analogous to `@[to_du
104105
instead of using `@[to_dual self]` for those cases.
105106
-/
106107
@[to_dual self]
107-
theorem not_lt_self {α : Type} [PartialOrder α] (a : α) : ¬ a < a := lt_irrefl a
108+
theorem not_lt_self : ¬ a < a := lt_irrefl a
108109

109110
-- Test that we do not translate numerals like we do in `@[to_additive]`
110111
@[to_dual self]
111-
theorem one_le_one {α : Type} [One α] [Preorder α] : (1 : α) ≤ 1 := le_rfl
112+
theorem one_le_one [One α] : (1 : α) ≤ 1 := le_rfl
112113

113114
-- Test the name generated by `to_dual none`
114115
@[to_dual none]
@@ -117,3 +118,22 @@ theorem bot_eq_top {α : Type} [Bot α] [Top α] : (⊤ : α) = ⊥ → (⊤ :
117118
/-- info: bot_eq_top._to_dual_1 {α : Type} [Top α] [Bot α] : ⊥ = ⊤ → ⊥ = ⊤ -/
118119
#guard_msgs in
119120
#check bot_eq_top._to_dual_1
121+
122+
/- Test the translation of auxLemmas.
123+
`_proof_i` lemmas are translated, but `_simp_i` lemmas are unfolded. -/
124+
@[to_dual lt_le_trans]
125+
theorem le_lt_trans (h₁ : a ≤ b) (h₂ : b < c) : a < c := by
126+
grind
127+
128+
theorem le_refl': ∀ a : α, a ≤ a := by
129+
simp
130+
131+
/--
132+
info: fun {α} [PartialOrder α] a b c h₁ h₂ => lt_le_trans._proof_1_1 a b c h₁ h₂
133+
---
134+
info: fun {α} [PartialOrder α] => of_eq_true (Eq.trans (forall_congr fun a => le_refl._simp_1 a) (implies_true α))
135+
-/
136+
#guard_msgs in
137+
run_meta
138+
Lean.logInfo (← Lean.getConstInfo ``lt_le_trans).value!
139+
Lean.logInfo (← Lean.getConstInfo ``le_refl').value!

MathlibTest/toAdditive.lean

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -231,7 +231,7 @@ def mul_foo {α} [Monoid α] (a : α) : ℕ → α
231231

232232
-- cannot apply `@[to_additive]` to `some_def` if `some_def.in_namespace` doesn't have the attribute
233233
run_cmd liftCoreM <| successIfFail <|
234-
transformDeclRec ToAdditive.data (← getRef) `Test.some_def `Test.add_some_def `Test.some_def
234+
transformDeclRec ToAdditive.data (← getRef) `Test.some_def `Test.add_some_def `Test.some_def []
235235

236236

237237
attribute [to_additive some_other_name] some_def.in_namespace

0 commit comments

Comments
 (0)