feat: add @[backward_defeq] attribute and local useBackward simp option (#13492)

This PR introduces stricter inference for the `@[defeq]` attribute and a
companion `@[backward_defeq]` attribute that preserves the pre-PR
behavior
as an opt-in.

### What changed

* `@[defeq]` is now inferred only when the equation holds at
  `.instances` transparency (the transparency `dsimp` operates at).
* `@[backward_defeq]` is the old set: every theorem whose `rfl` proof
the legacy inference would have accepted is tagged `@[backward_defeq]`,
  so `defeq ⊆ backward_defeq` holds by construction.
* The option `backward.defeqAttrib.useBackward` (default `false`) makes
  `dsimp` also use `@[backward_defeq]` theorems, restoring the pre-PR
  behavior for a specific proof or file.
* The option is eqn-affecting: its value at the point of a function's
  definition is recorded so that the equation lemmas later generated for
  that function use the same value, regardless of the ambient option at
  the use site.

### Mathlib adaption

A companion adaption branch (`lean-pr-testing-backward-defeq-attrib` on
mathlib4) builds cleanly against this PR and passes `lake test` without
warnings. Most adaption changes are scoped
`set_option backward.defeqAttrib.useBackward true in` additions on the
failing declarations; a small number of files needed proof-level edits
where the stored form of a `dsimp%`/`@[reassoc]`/`@[elementwise]`
/`@[simps]`/`@[to_app]`-generated lemma had drifted under the stricter
regime.

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Joachim Breitner 2026-04-27 12:07:59 +02:00 committed by GitHub
parent b0d6a3c283
commit ac9a1cb415
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
29 changed files with 306 additions and 123 deletions

View file

@ -42,5 +42,5 @@ This gadget is supported by
It is ineffective in other positions (hypotheses of rewrite rules) or when used by other tactics
(e.g. `apply`).
-/
@[simp ↓, expose]
@[simp ↓, expose, implicit_reducible]
def binderNameHint {α : Sort u} {β : Sort v} {γ : Sort w} (v : α) (binder : β) (e : γ) : γ := e

View file

@ -16,6 +16,14 @@ public section
namespace Lean
open Meta
register_builtin_option backward.defeqAttrib.useBackward : Bool := {
defValue := false
descr := "When true, `dsimp` also uses theorems tagged `@[backward_defeq]`, i.e. \
theorems inferred to be rfl only at default (not instance) transparency. Set this \
locally (e.g. `set_option backward.defeqAttrib.useBackward true in ...`) to restore the \
pre-stricter-inference behavior for a specific proof."
}
/--
There are defeq theorems that only hold at transparency `.all`, but also others that hold
(from the kernel's point of view) but where the defeq checker here will run out of cycles.
@ -26,48 +34,88 @@ private def isDefEqCareful (e1 e2 : Expr) : MetaM Bool := do
withOptions (smartUnfolding.set · false) <| do
withDefault (isDefEq e1 e2) <||> withTransparency .all (isDefEq e1 e2)
def validateDefEqAttr (declName : Name) : AttrM Unit := do
let info ← getConstVal declName
MetaM.run' do
withTransparency .all do -- we want to look through defs in `info.type` all the way to `Eq`
forallTelescopeReducing info.type fun _ type => do
let type ← whnf type
-- NB: The warning wording should work both for explicit uses of `@[defeq]` as well as the implicit `:= rfl`.
let some (_, lhs, rhs) := type.eq? |
throwError m!"Not a definitional equality: the conclusion should be an equality, but is{inlineExpr type}"
let ok ← isDefEqCareful lhs rhs
unless ok do
let explanation := MessageData.ofLazyM (es := #[lhs, rhs]) do
let (lhs, rhs) ← addPPExplicitToExposeDiff lhs rhs
let mut msg := m!"Not a definitional equality: the left-hand side{indentExpr lhs}\nis \
not definitionally equal to the right-hand side{indentExpr rhs}"
if (← getEnv).isExporting then
let okPrivately ← withoutExporting <| isDefEqCareful lhs rhs
if okPrivately then
msg := msg ++ .note m!"This theorem is exported from the current module. \
This requires that all definitions that need to be unfolded to prove this \
theorem must be exposed."
pure msg
throwError explanation
private def withEqLhsRhs (type : Expr) (k : Expr → Expr → MetaM α) : MetaM α := do
withTransparency .all do -- we want to look through defs in `info.type` all the way to `Eq`
forallTelescopeReducing type fun _ type => do
let type ← whnf type
-- NB: The warning wording should work both for explicit uses of `@[defeq]` as well as the implicit `:= rfl`.
let some (_, lhs, rhs) := type.eq? |
throwError m!"Not a definitional equality: the conclusion should be an equality, but is{inlineExpr type}"
k lhs rhs
/--
Marks the theorem as a definitional equality.
Validates that `declName` is a definitional equality at `.default`/`.all` transparency
(the legacy permissive check). Throws a diagnostic error if not. This is used both as
the validator for the `@[defeq]` and `@[backward_defeq]` attributes and as a building
block for `inferDefEqAttr`.
-/
def validateDefEqAttr (declName : Name) : AttrM Unit := do
let info ← getConstVal declName
MetaM.run' do withEqLhsRhs info.type fun lhs rhs => do
let ok ← isDefEqCareful lhs rhs
unless ok do
let explanation := MessageData.ofLazyM (es := #[lhs, rhs]) do
let (lhs, rhs) ← addPPExplicitToExposeDiff lhs rhs
let mut msg := m!"Not a definitional equality: the left-hand side{indentExpr lhs}\nis \
not definitionally equal to the right-hand side{indentExpr rhs}"
if (← getEnv).isExporting then
let okPrivately ← withoutExporting <| isDefEqCareful lhs rhs
if okPrivately then
msg := msg ++ .note m!"This theorem is exported from the current module. \
This requires that all definitions that need to be unfolded to prove this \
theorem must be exposed."
pure msg
throwError explanation
The theorem must be an equality that holds by `rfl`. This allows `dsimp` to use this theorem
when rewriting.
/--
Marks a theorem as a definitional equality under the permissive transparency rules that
predated the stricter `@[defeq]` inference (i.e. an equality that holds at `.default` or
`.all` transparency, but possibly not at `.instances` transparency as required by `dsimp`).
A theorem with with a definition that is (syntactically) `:= rfl` is implicitly marked `@[defeq]`.
To avoid this behavior, write `:= (rfl)` instead.
Such theorems are inferred automatically by `inferDefEqAttr`: any theorem that the old
`:= rfl` inference would have accepted is tagged `@[backward_defeq]`, and additionally
tagged `@[defeq]` when it also passes the stricter check at instance transparency.
`dsimp` ignores `@[backward_defeq]` theorems by default. Setting
`set_option backward.defeqAttrib.useBackward true` (typically scoped to a single proof
with `set_option ... in`) makes `dsimp` treat them like `@[defeq]` theorems, which
provides a local backwards-compatibility escape hatch for proofs broken by the stricter
inference.
-/
@[builtin_doc]
builtin_initialize backwardDefeqAttr : TagAttribute ←
registerTagAttribute `backward_defeq
"mark theorem as a definitional equality under the permissive pre-stricter-inference \
rules, used by `dsimp` when `set_option backward.defeqAttrib.useBackward true`"
(validate := validateDefEqAttr) (applicationTime := .afterTypeChecking)
(asyncMode := .async .mainEnv)
/--
Marks the theorem as a definitional equality that can be used by `dsimp`.
The theorem must be an equality that holds at `.instances` transparency. A theorem
with a definition that is (syntactically) `:= rfl` is implicitly marked `@[defeq]`
(and also `@[backward_defeq]`, since the latter is a superset); write `:= (rfl)`
instead to suppress this.
The attribute should be given before a `@[simp]` attribute to have effect.
When using the module system, an exported theorem can only be `@[defeq]` if all definitions that
need to be unfolded to prove the theorem are exported and exposed.
When using the module system, an exported theorem can only be `@[defeq]` if all
definitions that need to be unfolded to prove the theorem are exported and exposed.
Tagging a theorem with `@[defeq]` automatically also tags it with `@[backward_defeq]`,
maintaining the invariant that `@[defeq]` theorems form a subset of `@[backward_defeq]`
theorems.
-/
@[builtin_doc]
builtin_initialize defeqAttr : TagAttribute ←
registerTagAttribute `defeq "mark theorem as a definitional equality, to be used by `dsimp`"
(validate := validateDefEqAttr) (applicationTime := .afterTypeChecking)
(validate := fun declName => do
-- Validate via the same check as `@[backward_defeq]`, then maintain the invariant
-- `defeq ⊆ backward_defeq` by also tagging `backward_defeq`.
validateDefEqAttr declName
backwardDefeqAttr.setTag declName)
(applicationTime := .afterTypeChecking)
(asyncMode := .async .mainEnv)
private partial def isRflProofCore (type : Expr) (proof : Expr) : CoreM Bool := do
@ -85,17 +133,28 @@ private partial def isRflProofCore (type : Expr) (proof : Expr) : CoreM Bool :=
-- `Eq.symm` of rfl proof is a rfl proof
isRflProofCore type proof.appArg! -- small hack: we don't need to set the exact type
else if proof.getAppFn.isConst then
-- The application of a `defeq` theorem is a `rfl` proof
return defeqAttr.hasTag (← getEnv) proof.getAppFn.constName!
-- The application of a `defeq` or `backward_defeq` theorem is a `rfl` proof
let env ← getEnv
let c := proof.getAppFn.constName!
return defeqAttr.hasTag env c || backwardDefeqAttr.hasTag env c
else
return false
else
return false
/--
For automatically generated theorems (equational theorems etc.), we want to set the `defeq` attribute
if the proof is `rfl`, essentially reproducing the behavior before the introduction of the `defeq`
attribute. This function infers the `defeq` attribute based on the declaration value.
For automatically generated theorems (equational theorems etc.), we tag the theorem with
the `defeq`/`backward_defeq` attributes based on its `rfl` proof:
* If the equation holds at `.instances` transparency (matching the transparency at which
`dsimp` operates), we tag it with `@[defeq]`.
* Independently, if the equation holds under the more permissive legacy check (equivalent
to `validateDefEqAttr`, i.e. at `.default` or `.all` transparency), we tag it with
`@[backward_defeq]`.
In particular, every theorem that would have been tagged `@[defeq]` before the stricter
inference rules were introduced is now tagged `@[backward_defeq]`. Local backwards
compatibility can be restored with `set_option backward.defeqAttrib.useBackward true`.
-/
def inferDefEqAttr (declName : Name) : MetaM Unit := do
withoutExporting do
@ -105,11 +164,25 @@ def inferDefEqAttr (declName : Name) : MetaM Unit := do
isRflProofCore info.type value
else
pure false
if isRfl then
try
withExporting (isExporting := !isPrivateName declName) do
validateDefEqAttr declName -- sanity-check: would we have accepted `@[defeq]` on this?
catch e =>
logError m!"Theorem {declName} has a `rfl`-proof and was thus inferred to be `@[defeq]`, \
but validating that attribute failed:{indentD e.toMessageData}"
unless isRfl do return
-- Strict check: defeq at instance transparency (as used by `dsimp`).
let strict ← withEqLhsRhs info.type fun lhs rhs =>
withReducibleAndInstances (isDefEq lhs rhs)
-- Sanity-check: is the theorem also defeq at the permissive (`.default`/`.all`)
-- transparency we use for `@[backward_defeq]`? If not, log the error that the
-- legacy inference would have emitted — but still proceed to tag
-- `@[backward_defeq]` unconditionally. The legacy (pre-PR) behavior tagged
-- `@[defeq]` unconditionally whenever `isRflProofCore` returned true,
-- regardless of whether the validation check passed, and we want to preserve
-- exactly that set under `backward_defeq` so that `useBackward=true` reliably
-- restores the old behavior.
try
withExporting (isExporting := !isPrivateName declName) do
validateDefEqAttr declName
catch e =>
unless strict do
logError m!"Theorem {declName} has a `rfl`-proof but could not be validated \
as a definitional equality:{indentD e.toMessageData}"
if strict then
defeqAttr.setTag declName
backwardDefeqAttr.setTag declName

View file

@ -129,7 +129,9 @@ structure DefView where
def DefView.isInstance (view : DefView) : Bool :=
view.modifiers.attrs.any fun attr => attr.name == `instance
/-- Prepends the `defeq` attribute, removing existing ones if there are any -/
/-- Prepends the `defeq` attribute, removing existing ones if there are any.
The `defeq` attribute's validator also tags `backward_defeq` on success, so the
superset invariant `defeq ⊆ backward_defeq` is preserved. -/
def DefView.markDefEq (view : DefView) : DefView :=
{ view with modifiers :=
view.modifiers.filterAttrs (·.name != `defeq) |>.addFirstAttr { name := `defeq } }

View file

@ -37,6 +37,8 @@ private def mkHeader (kind : String) (id : Name) (levelParams : List Name) (type
if defeqAttr.hasTag (← getEnv) id then
attrs := attrs.push m!"defeq"
else if backwardDefeqAttr.hasTag (← getEnv) id then
attrs := attrs.push m!"backward_defeq"
let mut m : MessageData := m!""
unless attrs.isEmpty do

View file

@ -40,7 +40,8 @@ value at definition time, not realization time, should matter.
This is implemented by storing their values at definition time (when non-default) in an environment
extension, and restoring them when the equations are lazily realized.
-/
def eqnAffectingOptions : Array (Lean.Option Bool) := #[backward.eqns.nonrecursive, backward.eqns.deepRecursiveSplit]
def eqnAffectingOptions : Array (Lean.Option Bool) :=
#[backward.eqns.nonrecursive, backward.eqns.deepRecursiveSplit, backward.defeqAttrib.useBackward]
/-- Environment extension that stores the values of `eqnAffectingOptions` at definition time,
keyed by declaration name. Only populated when at least one option has a non-default value.

View file

@ -18,6 +18,8 @@ structure AuxLemmaKey where
-- When an aux lemma is created in a private context and thus has a private name, we must not
-- reuse it in an exported context.
isPrivate : Bool
-- Whether the theorem should be tagged with `[defeq]` (needs to happen before caching)
defeq : Bool
deriving BEq, Hashable
structure AuxLemmas where
@ -39,10 +41,10 @@ builtin_initialize auxLemmasExt : EnvExtension AuxLemmas ←
users. For example, `simp` preprocessor may convert a lemma into multiple ones.
-/
def mkAuxLemma (levelParams : List Name) (type : Expr) (value : Expr) (kind? : Option Name := none)
(cache := true) (inferRfl := false) (forceExpose := false) : MetaM Name := do
(cache := true) (inferRfl := false) (forceExpose := false) (defeq := false) : MetaM Name := do
let env ← getEnv
let s := auxLemmasExt.getState env
let key := { type, isPrivate := !env.isExporting }
let key := { type, isPrivate := !env.isExporting, defeq }
let mkNewAuxLemma := do
let auxName ← mkAuxDeclName (kind := kind?.getD `_proof)
let decl :=
@ -60,6 +62,7 @@ def mkAuxLemma (levelParams : List Name) (type : Expr) (value : Expr) (kind? : O
levelParams, type, value
}
addDecl (forceExpose := forceExpose) decl
if defeq then defeqAttr.setTag auxName
if inferRfl then
inferDefEqAttr auxName
modifyEnv fun env => auxLemmasExt.modifyState env fun ⟨lemmas⟩ => ⟨lemmas.insert key (auxName, levelParams)⟩

View file

@ -107,7 +107,7 @@ where
return false
private def useImplicitDefEqProof (thm : SimpTheorem) : SimpM Bool := do
if thm.rfl then
if thm.rfl || (thm.backwardRfl && backward.defeqAttrib.useBackward.get (← getOptions)) then
return (← getConfig).implicitDefEqProofs
else
return false
@ -218,11 +218,12 @@ where
return none
else
let candidates := candidates.insertionSort fun e₁ e₂ => e₁.1.priority > e₂.1.priority
let useBackward := backward.defeqAttrib.useBackward.get (← getOptions)
for (thm, numExtraArgs) in candidates do
checkSystem "simp"
if inErasedSet thm then continue
if rflOnly then
unless thm.rfl do
unless thm.rfl || (useBackward && thm.backwardRfl) do
if debug.tactic.simp.checkDefEqAttr.get (← getOptions) &&
backward.dsimp.useDefEqAttr.get (← getOptions) then
let isRflOld ← withOptions (backward.dsimp.useDefEqAttr.set · false) do
@ -246,9 +247,10 @@ where
return none
else
let candidates := candidates.insertionSort fun e₁ e₂ => e₁.priority > e₂.priority
let useBackward := backward.defeqAttrib.useBackward.get (← getOptions)
for thm in candidates do
checkSystem "simp"
unless inErasedSet thm || (rflOnly && !thm.rfl) do
unless inErasedSet thm || (rflOnly && !(thm.rfl || (useBackward && thm.backwardRfl))) do
let result? ← withNewMCtxDepth do
let val ← thm.getValue
let type ← inferType val
@ -360,7 +362,7 @@ def simpMatchDiscrs? (info : MatcherInfo) (e : Expr) : SimpM (Option Result) :=
def simpMatchCore (matcherName : Name) (e : Expr) : SimpM Step := do
for matchEq in (← Match.getEquationsFor matcherName).eqnNames do
-- Try lemma
match (← withReducible <| Simp.tryTheorem? e { origin := .decl matchEq, proof := mkConst matchEq, rfl := (← isRflTheorem matchEq) }) with
match (← withReducible <| Simp.tryTheorem? e { origin := .decl matchEq, proof := mkConst matchEq, rfl := (← isRflTheorem matchEq), backwardRfl := (← isBackwardRflTheorem matchEq) }) with
| none => pure ()
| some r => return .visit r
return .continue
@ -441,7 +443,7 @@ def sevalGround : Simproc := fun e => do
-- `declName` has equation theorems associated with it.
for eqn in eqns do
-- TODO: cache SimpTheorem to avoid calls to `isRflTheorem`
if let some result ← Simp.tryTheorem? e { origin := .decl eqn, proof := mkConst eqn, rfl := (← isRflTheorem eqn) } then
if let some result ← Simp.tryTheorem? e { origin := .decl eqn, proof := mkConst eqn, rfl := (← isRflTheorem eqn), backwardRfl := (← isBackwardRflTheorem eqn) } then
trace[Meta.Tactic.simp.ground] "unfolded, {e} => {result.expr}"
return .visit result
return .continue

View file

@ -174,14 +174,20 @@ structure SimpTheorem where
`rfl` is true if `proof` is by `Eq.refl`, `rfl` or a `@[defeq]` theorem.
-/
rfl : Bool
/--
`backwardRfl` is true if `proof` is by a `@[backward_defeq]` theorem (i.e. rfl at default,
but not instance, transparency). Honored by `dsimp` only when
`backward.defeqAttrib.useBackward` is set.
-/
backwardRfl : Bool := false
deriving Inhabited
mutual
private partial def isRflProofCore (type : Expr) (proof : Expr) : CoreM Bool := do
private partial def isRflProofCore (includeBackward : Bool) (type : Expr) (proof : Expr) : CoreM Bool := do
match type with
| .forallE _ _ type _ =>
if let .lam _ _ proof _ := proof then
isRflProofCore type proof
isRflProofCore includeBackward type proof
else
return false
| _ =>
@ -190,23 +196,25 @@ mutual
return true
else if proof.isAppOfArity ``Eq.symm 4 then
-- `Eq.symm` of rfl theorem is a rfl theorem
isRflProofCore type proof.appArg! -- small hack: we don't need to set the exact type
isRflProofCore includeBackward type proof.appArg! -- small hack: we don't need to set the exact type
else if proof.getAppFn.isConst then
-- The application of a `rfl` theorem is a `rfl` theorem
-- A constant which is a `rfl` theorem is a `rfl` theorem
isRflTheoremCore proof.getAppFn.constName!
isRflTheoremCore includeBackward proof.getAppFn.constName!
else
return false
else
return false
private partial def isRflTheoremCore (declName : Name) : CoreM Bool := do
private partial def isRflTheoremCore (includeBackward : Bool) (declName : Name) : CoreM Bool := do
if backward.dsimp.useDefEqAttr.get (← getOptions) then
return defeqAttr.hasTag (← getEnv) declName
let env ← getEnv
return defeqAttr.hasTag env declName ||
(includeBackward && backwardDefeqAttr.hasTag env declName)
else
let { kind := .thm, constInfo, .. } ← getAsyncConstInfo declName | return false
let .thmInfo info ← traceBlock "isRflTheorem theorem body" constInfo | return false
isRflProofCore info.type info.value
isRflProofCore includeBackward info.type info.value
end
def isRflTheorem (declName : Name) : CoreM Bool :=
@ -214,7 +222,12 @@ def isRflTheorem (declName : Name) : CoreM Bool :=
-- for the ultimate application of a rfl theorem, only that the theorem type's LHS and RHS are
-- defeq.
withoutExporting do
isRflTheoremCore declName
isRflTheoremCore (includeBackward := false) declName
/-- Like `isRflTheorem`, but also returns `true` for `@[backward_defeq]` theorems. -/
def isBackwardRflTheorem (declName : Name) : CoreM Bool :=
withoutExporting do
isRflTheoremCore (includeBackward := true) declName
def isRflProof (proof : Expr) : MetaM Bool := do
-- Make theorem body available if `declName` is from the current module; the body does not matter
@ -222,9 +235,18 @@ def isRflProof (proof : Expr) : MetaM Bool := do
-- defeq.
withoutExporting do
if let .const declName .. := proof then
isRflTheoremCore declName
isRflTheoremCore (includeBackward := false) declName
else
isRflProofCore (← inferType proof) proof
isRflProofCore (includeBackward := false) (← inferType proof) proof
/-- Like `isRflProof`, but also returns `true` for proofs that are rfl only at default
transparency (via `@[backward_defeq]`). -/
def isBackwardRflProof (proof : Expr) : MetaM Bool := do
withoutExporting do
if let .const declName .. := proof then
isRflTheoremCore (includeBackward := true) declName
else
isRflProofCore (includeBackward := true) (← inferType proof) proof
instance : ToFormat SimpTheorem where
format s :=
@ -405,6 +427,7 @@ private def mkSimpTheoremCore (origin : Origin) (e : Expr) (levelParams : Array
let type ← instantiateMVars (← inferType e)
let (keys, perm) ← mkSimpTheoremKeys type noIndexAtArgs checkLhs
let rfl ← isRflProof proof
let backwardRfl ← if rfl then pure true else isBackwardRflProof proof
if rfl && simp.rfl.checkTransparency.get (← getOptions) then
forallTelescopeReducing type fun _ type => do
let checkDefEq (lhs rhs : Expr) := do
@ -419,7 +442,7 @@ private def mkSimpTheoremCore (origin : Origin) (e : Expr) (levelParams : Array
| Iff lhs rhs => checkDefEq lhs rhs
| _ =>
logWarning m!"'{origin.key}' is a 'rfl' simp theorem, unexpected resulting type{indentExpr type}"
return { origin, keys, perm, post, levelParams, proof, priority := prio, rfl }
return { origin, keys, perm, post, levelParams, proof, priority := prio, rfl, backwardRfl }
/--
Creates a `SimpTheorem` from a global theorem.
@ -440,6 +463,8 @@ def mkSimpTheoremFromConst (declName : Name) (post := true) (inv := false)
let auxName ← mkAuxLemma (kind? := `_simp) cinfo.levelParams type val (inferRfl := true)
(forceExpose := true) -- These kinds of theorems are small and `to_additive` may need to
-- unfold them.
-- The `[defeq]` attribute transfers (should only matter for `[← thm]`)
(defeq := inv && defeqAttr.hasTag (← getEnv) declName)
r := r.push <| (← do mkSimpTheoremCore origin (mkConst auxName us) #[] (mkConst auxName) post prio (noIndexAtArgs := false) (checkLhs := checkLhs))
return r
else

View file

@ -57,6 +57,7 @@ where
origin := .decl matchEqDeclName
proof := mkConst matchEqDeclName
rfl := (← isRflTheorem matchEqDeclName)
backwardRfl := (← isBackwardRflTheorem matchEqDeclName)
}
match (← withReducible <| Simp.tryTheorem? e simpTheorem) with
| none => return .continue

View file

@ -23,7 +23,7 @@ def unfold (e : Expr) (declName : Name) : MetaM Simp.Result := do
return { expr := (← deltaExpand e (· == declName)) }
where
pre (unfoldThm : Name) (e : Expr) : SimpM Simp.Step := do
match (← withReducible <| Simp.tryTheorem? e { origin := .decl unfoldThm, proof := mkConst unfoldThm, rfl := (← isRflTheorem unfoldThm) }) with
match (← withReducible <| Simp.tryTheorem? e { origin := .decl unfoldThm, proof := mkConst unfoldThm, rfl := (← isRflTheorem unfoldThm), backwardRfl := (← isBackwardRflTheorem unfoldThm) }) with
| none => pure ()
| some r => match (← reduceMatcher? r.expr) with
| .reduced e' => return .done { r with expr := e' }

View file

@ -538,10 +538,10 @@ theorem insert_eq_insertₘ [BEq α] [Hashable α] (m : Raw₀ α β) (a : α) (
theorem alter_eq_alterₘ [BEq α] [Hashable α] [LawfulBEq α] (m : Raw₀ α β) (a : α)
(f : Option (β a) → Option (β a)) : m.alter a f = m.alterₘ a f := by
dsimp only [alter, alterₘ, containsₘ, ← bucket_eq]
simp only [alter, alterₘ, containsₘ, ← bucket_eq]
split
· congr 2
· simp only [withComputedSize, bucket_updateBucket]
· simp only [withComputedSize, bucket_updateBucket, AssocList.contains_eq]
· simp only [Array.uset, bucket, Array.ugetElem_eq_getElem, Array.set_set, updateBucket]
· congr
@ -568,10 +568,10 @@ variable {β : Type v}
theorem alter_eq_alterₘ [BEq α] [Hashable α] [EquivBEq α] (m : Raw₀ α (fun _ => β)) (a : α)
(f : Option β → Option β) : Const.alter m a f = Const.alterₘ m a f := by
dsimp only [alter, alterₘ, containsₘ, ← bucket_eq]
simp only [alter, alterₘ, containsₘ, ← bucket_eq]
split
· congr 2
· simp only [withComputedSize, bucket_updateBucket]
· simp only [withComputedSize, bucket_updateBucket, AssocList.contains_eq]
· simp only [Array.uset, bucket, Array.ugetElem_eq_getElem, Array.set_set, updateBucket]
· congr

View file

@ -1,3 +1,4 @@
// please update stage0
#include "util/options.h"
namespace lean {

View file

@ -24,7 +24,7 @@ info: optimize.eq_def (x✝ : Expr) :
/--
info: equations:
@[defeq] theorem optimize.eq_1 : ∀ (i : BitVec 32), optimize (Expr.const i) = Expr.const i
@[backward_defeq] theorem optimize.eq_1 : ∀ (i : BitVec 32), optimize (Expr.const i) = Expr.const i
theorem optimize.eq_2 : ∀ (e1 : Expr) (bop : Unit) (i : BitVec 32),
optimize e1 = Expr.const i → optimize (Expr.op bop e1) = Expr.op bop (Expr.const 0)
theorem optimize.eq_3 : ∀ (e1 : Expr) (bop : Unit),
@ -44,8 +44,8 @@ def optimize2 : Expr → Expr
/--
info: equations:
@[defeq] theorem optimize2.eq_1 : ∀ (i : BitVec 32), optimize2 (Expr.const i) = Expr.const i
@[defeq] theorem optimize2.eq_2 : ∀ (bop : Unit) (e1 : Expr),
@[backward_defeq] theorem optimize2.eq_1 : ∀ (i : BitVec 32), optimize2 (Expr.const i) = Expr.const i
@[backward_defeq] theorem optimize2.eq_2 : ∀ (bop : Unit) (e1 : Expr),
optimize2 (Expr.op bop e1) =
match optimize2 e1 with
| Expr.const i => Expr.op bop (Expr.const 0)
@ -65,8 +65,8 @@ def optimize3 : Expr → Expr
/--
info: equations:
@[defeq] theorem optimize3.eq_1 : ∀ (i : BitVec 32), optimize3 (Expr.const i) = Expr.const i
@[defeq] theorem optimize3.eq_2 : ∀ (bop : Unit) (i : BitVec 32),
@[backward_defeq] theorem optimize3.eq_1 : ∀ (i : BitVec 32), optimize3 (Expr.const i) = Expr.const i
@[backward_defeq] theorem optimize3.eq_2 : ∀ (bop : Unit) (i : BitVec 32),
optimize3 (Expr.op bop (Expr.const i)) = Expr.op bop (optimize3 (Expr.const i))
theorem optimize3.eq_3 : ∀ (bop : Unit) (e1 : Expr),
(∀ (i : BitVec 32), e1 = Expr.const i → False) → optimize3 (Expr.op bop e1) = Expr.const 0

View file

@ -36,7 +36,7 @@ theorem Extension.recOn'.eq_1.{v} : ∀ {motive : (Γ Δ : Con) → Extension Γ
(zero : {Γ : Con} → motive Γ Γ Extension.zero)
(succ : {Γ Δ : Con} → (xt : Extension Γ Δ) → (A : Nat) → motive Γ Δ xt → motive Γ (Δ.ext A) (xt.succ A)) (x : Con),
Extension.recOn' zero succ Extension.zero = zero
@[defeq] theorem Extension.recOn'.eq_2.{v} : ∀ {motive : (Γ Δ : Con) → Extension Γ Δ → Sort v}
@[backward_defeq] theorem Extension.recOn'.eq_2.{v} : ∀ {motive : (Γ Δ : Con) → Extension Γ Δ → Sort v}
(zero : {Γ : Con} → motive Γ Γ Extension.zero)
(succ : {Γ Δ : Con} → (xt : Extension Γ Δ) → (A : Nat) → motive Γ Δ xt → motive Γ (Δ.ext A) (xt.succ A)) (x Δ : Con)
(A : Nat) (xt : Extension x Δ),
@ -53,10 +53,10 @@ def Extension.pullback_con
/--
info: equations:
@[defeq] theorem Extension.pullback_con.eq_1.{u} : ∀ {Op : Con → Con → Type u} {B B' : Con} (x : Op B' B),
@[backward_defeq] theorem Extension.pullback_con.eq_1.{u} : ∀ {Op : Con → Con → Type u} {B B' : Con} (x : Op B' B),
Extension.zero.pullback_con x = B'
@[defeq] theorem Extension.pullback_con.eq_2.{u} : ∀ {Op : Con → Con → Type u} {B B' : Con} (x : Op B' B) (Δ_2 : Con)
(xt : Extension B Δ_2) (A : Nat), (xt.succ A).pullback_con x = (xt.pullback_con x).ext A
@[backward_defeq] theorem Extension.pullback_con.eq_2.{u} : ∀ {Op : Con → Con → Type u} {B B' : Con} (x : Op B' B)
(Δ_2 : Con) (xt : Extension B Δ_2) (A : Nat), (xt.succ A).pullback_con x = (xt.pullback_con x).ext A
-/
#guard_msgs in
#print equations Extension.pullback_con

View file

@ -82,8 +82,13 @@ def e3 := a
-- are transported out
def f := a
#guard_msgs in example (h : P a) : P f := by dsimp [f]; exact h
-- `f.eq_1` etc. are only `[backward_defeq]` (not `[defeq]`) since `f` is semireducible;
-- so we need to opt into the backward behavior for `dsimp` to use them.
set_option backward.defeqAttrib.useBackward true in
#guard_msgs in example (h : P a) : P f := by dsimp [f.eq_1]; exact h
set_option backward.defeqAttrib.useBackward true in
#guard_msgs in example (h : P a) : P f := by dsimp [f.eq_def]; exact h
set_option backward.defeqAttrib.useBackward true in
#guard_msgs in example (h : P a) : P f := by dsimp [f.eq_unfold]; exact h

View file

@ -0,0 +1,65 @@
module
prelude
public import Lean.DefEqAttrib
import Init.Data.Nat.Basic
/-!
# Can `@[defeq]` inference have `strict=true` while `validateDefEqAttr` throws?
The algorithm in `inferDefEqAttr` swallows `validateDefEqAttr` errors when
`strict` passed:
```
try withExporting ... validateDefEqAttr declName
catch e =>
unless strict do logError m!"..."
```
This guards a theoretical case where the strict check (`.instances` transparency,
smart-unfolding ON, under `withoutExporting`) passes but the permissive check
(`.default`/`.all`, smart-unfolding OFF, under `withExporting` for public decls)
fails. The two differ along three axes: transparency, export-view, smart-unfolding.
Trying to construct a case:
1. Public `@[reducible] def hidden := 42` + public `theorem h = 42 := rfl`:
Both strict and permissive fail. The `@[reducible]` marker apparently attaches
to the exported view and does not make `hidden` instance-reducible under
`withoutExporting`. Not a counterexample.
2. The same pair as private: strict=true, permissive=true. Both pass.
3. Recursive `@[reducible]` def relying on smart unfolding: same pattern — public
fails both, private passes both.
Under the module system, it seems the `withoutExporting` in `inferDefEqAttr` does
not give the strict check a strictly-bigger view than the permissive check has.
Every case where strict succeeds, permissive succeeds too. The `unless strict do
logError` branch is therefore a defensive guard that (as far as this demonstrates)
is unreachable in practice.
-/
@[reducible] public def hidden : Nat := 42
-- Public version: error message from `validateDefEqAttr` surfaces via
-- `unless strict do logError` because strict=false (reducibility of the exported
-- view is gone under `withoutExporting`).
/--
error: Not a definitional equality: the left-hand side
hidden
is not definitionally equal to the right-hand side
42
Note: This theorem is exported from the current module. This requires that all definitions that need to be unfolded to prove this theorem must be exposed.
-/
#guard_msgs in
public theorem test_public : hidden = 42 := rfl
-- Private version: both strict and permissive checks pass, tagged `@[defeq]`.
theorem test_private : hidden = 42 := rfl
/-- info: @[defeq] private theorem test_private : hidden = 42 -/
#guard_msgs in
#print sig test_private

View file

@ -27,6 +27,7 @@ trace: x : Nat
⊢ P false
-/
#guard_msgs in
set_option backward.defeqAttrib.useBackward true in
example (x : Nat) : P (id x.succ.succ).isZero := by
dsimp [Nat.isZero.eq_2]
trace_state

View file

@ -8,8 +8,8 @@ def nonrecfun : Bool → Nat
/--
info: equations:
@[defeq] theorem Test1.nonrecfun.eq_1 : nonrecfun false = 0
@[defeq] theorem Test1.nonrecfun.eq_2 : nonrecfun true = 0
@[backward_defeq] theorem Test1.nonrecfun.eq_1 : nonrecfun false = 0
@[backward_defeq] theorem Test1.nonrecfun.eq_2 : nonrecfun true = 0
-/
#guard_msgs in
#print equations nonrecfun
@ -25,7 +25,7 @@ def nonrecfun : Bool → Nat
/--
info: equations:
@[defeq] theorem Test2.nonrecfun.eq_1 : ∀ (x : Bool),
@[backward_defeq] theorem Test2.nonrecfun.eq_1 : ∀ (x : Bool),
nonrecfun x =
match x with
| false => 0
@ -47,8 +47,8 @@ set_option backward.eqns.nonrecursive false
/--
info: equations:
@[defeq] theorem Test3.nonrecfun.eq_1 : nonrecfun false = 0
@[defeq] theorem Test3.nonrecfun.eq_2 : nonrecfun true = 0
@[backward_defeq] theorem Test3.nonrecfun.eq_1 : nonrecfun false = 0
@[backward_defeq] theorem Test3.nonrecfun.eq_2 : nonrecfun true = 0
-/
#guard_msgs in
#print equations nonrecfun

View file

@ -10,9 +10,9 @@ termination_by x
/--
info: equations:
@[defeq] theorem f.eq_1 : f 0 = 1
@[defeq] theorem f.eq_2 : f 100 = 2
@[defeq] theorem f.eq_3 : f 1000 = 3
@[backward_defeq] theorem f.eq_1 : f 0 = 1
@[backward_defeq] theorem f.eq_2 : f 100 = 2
@[backward_defeq] theorem f.eq_3 : f 1000 = 3
theorem f.eq_4 : ∀ (x_2 : Nat), (x_2 = 99 → False) → (x_2 = 999 → False) → f x_2.succ = f x_2
-/
#guard_msgs(pass trace, all) in
@ -27,9 +27,9 @@ def g (x : Nat) : Nat :=
/--
info: equations:
@[defeq] theorem g.eq_1 : g 0 = 1
@[defeq] theorem g.eq_2 : g 100 = 2
@[defeq] theorem g.eq_3 : g 1000 = 3
@[backward_defeq] theorem g.eq_1 : g 0 = 1
@[backward_defeq] theorem g.eq_2 : g 100 = 2
@[backward_defeq] theorem g.eq_3 : g 1000 = 3
theorem g.eq_4 : ∀ (x_2 : Nat), (x_2 = 99 → False) → (x_2 = 999 → False) → g x_2.succ = x_2
-/
#guard_msgs(pass trace, all) in

View file

@ -12,12 +12,12 @@ def replace (f : N → Option N) (t : N) : N :=
/--
info: equations:
@[defeq] theorem replace.eq_1 : ∀ (f : N → Option N),
@[backward_defeq] theorem replace.eq_1 : ∀ (f : N → Option N),
replace f N.zero =
match f N.zero with
| some u => u
| none => N.zero
@[defeq] theorem replace.eq_2 : ∀ (f : N → Option N) (t' : N),
@[backward_defeq] theorem replace.eq_2 : ∀ (f : N → Option N) (t' : N),
replace f t'.succ =
match f t'.succ with
| some u => u
@ -36,12 +36,12 @@ def replace2 (f : N → Option N) (t1 t2 : N) : N :=
/--
info: equations:
@[defeq] theorem replace2.eq_1 : ∀ (f : N → Option N) (t1 : N),
@[backward_defeq] theorem replace2.eq_1 : ∀ (f : N → Option N) (t1 : N),
replace2 f t1 N.zero =
match f t1 with
| some u => u
| none => N.zero
@[defeq] theorem replace2.eq_2 : ∀ (f : N → Option N) (t1 t' : N),
@[backward_defeq] theorem replace2.eq_2 : ∀ (f : N → Option N) (t1 t' : N),
replace2 f t1 t'.succ =
match f t1 with
| some u => u

View file

@ -99,9 +99,9 @@ def del [Ord α] (d : α) : Raw α → Raw α
/--
info: equations:
@[defeq] theorem Raw.del.eq_1.{u_1} : ∀ {α : Type u_1} [inst : Ord α] (d : α), del d nil = nil
@[defeq] theorem Raw.del.eq_2.{u_1} : ∀ {α : Type u_1} [inst : Ord α] (d d_1 : α) (color : Color) (left_1 : Raw α)
(data : α) (right left_3 : Raw α) (data_1 : α) (right_1 : Raw α),
@[backward_defeq] theorem Raw.del.eq_1.{u_1} : ∀ {α : Type u_1} [inst : Ord α] (d : α), del d nil = nil
@[backward_defeq] theorem Raw.del.eq_2.{u_1} : ∀ {α : Type u_1} [inst : Ord α] (d d_1 : α) (color : Color)
(left_1 : Raw α) (data : α) (right left_3 : Raw α) (data_1 : α) (right_1 : Raw α),
del d ((left_1.node data Color.black right).node d_1 color (left_3.node data_1 Color.black right_1)) =
match compare d d_1 with
| Ordering.lt => baldL d_1 (del d (left_1.node data Color.black right)) (left_3.node data_1 Color.black right_1)

View file

@ -1,7 +1,7 @@
/--
info: equations:
@[defeq] theorem List.append.eq_1.{u_1} : ∀ {α : Type u_1} (x : List α), [].append x = x
@[defeq] theorem List.append.eq_2.{u_1} : ∀ {α : Type u_1} (x : List α) (a : α) (as : List α),
@[backward_defeq] theorem List.append.eq_1.{u_1} : ∀ {α : Type u_1} (x : List α), [].append x = x
@[backward_defeq] theorem List.append.eq_2.{u_1} : ∀ {α : Type u_1} (x : List α) (a : α) (as : List α),
(a :: as).append x = a :: as.append x
-/
#guard_msgs in
@ -9,8 +9,8 @@ info: equations:
/--
info: equations:
@[defeq] theorem List.append.eq_1.{u_1} : ∀ {α : Type u_1} (x : List α), [].append x = x
@[defeq] theorem List.append.eq_2.{u_1} : ∀ {α : Type u_1} (x : List α) (a : α) (as : List α),
@[backward_defeq] theorem List.append.eq_1.{u_1} : ∀ {α : Type u_1} (x : List α), [].append x = x
@[backward_defeq] theorem List.append.eq_2.{u_1} : ∀ {α : Type u_1} (x : List α) (a : α) (as : List α),
(a :: as).append x = a :: as.append x
-/
#guard_msgs in

View file

@ -10,7 +10,7 @@ where
/--
info: equations:
@[defeq] theorem trailingZeros.aux.eq_1 : ∀ (i : Int) (hi : i ≠ 0) (acc k_2 : Nat) (x_1 : k_2 + 1 ≠ 0)
@[backward_defeq] theorem trailingZeros.aux.eq_1 : ∀ (i : Int) (hi : i ≠ 0) (acc k_2 : Nat) (x_1 : k_2 + 1 ≠ 0)
(hk_2 : i.natAbs ≤ k_2 + 1),
trailingZeros.aux k_2.succ i hi hk_2 acc = if h : i % 2 = 0 then trailingZeros.aux k_2 (i / 2) ⋯ ⋯ (acc + 1) else acc
-/
@ -55,10 +55,11 @@ where
/--
info: equations:
@[defeq] theorem trailingZeros2.aux.eq_1 : ∀ (i : Int) (hi : i ≠ 0) (acc k_2 : Nat) (hk_2 : i.natAbs ≤ k_2 + 1),
@[backward_defeq] theorem trailingZeros2.aux.eq_1 : ∀ (i : Int) (hi : i ≠ 0) (acc k_2 : Nat)
(hk_2 : i.natAbs ≤ k_2 + 1),
trailingZeros2.aux k_2.succ i hi hk_2 acc =
if h : i % 2 = 0 then trailingZeros2.aux k_2 (i / 2) ⋯ ⋯ (acc + 1) else acc
@[defeq] theorem trailingZeros2.aux.eq_2 : ∀ (i : Int) (hi : i ≠ 0) (acc : Nat) (hk_2 : i.natAbs ≤ 0),
@[backward_defeq] theorem trailingZeros2.aux.eq_2 : ∀ (i : Int) (hi : i ≠ 0) (acc : Nat) (hk_2 : i.natAbs ≤ 0),
trailingZeros2.aux 0 i hi hk_2 acc = acc
-/
#guard_msgs(pass trace, all) in

View file

@ -31,8 +31,8 @@ def RTree.simple_size : RTree α → Nat
| .node _x t _ts => 1 + (t true).simple_size + (t false).simple_size
/--
info: @[defeq] theorem RTree.simple_size.eq_1.{u_1} : ∀ {α : Type u_1} (_x : α) (t : Bool → RTree α) (_ts : List (RTree α)),
(RTree.node _x t _ts).simple_size = 1 + (t true).simple_size + (t false).simple_size :=
info: @[backward_defeq] theorem RTree.simple_size.eq_1.{u_1} : ∀ {α : Type u_1} (_x : α) (t : Bool → RTree α)
(_ts : List (RTree α)), (RTree.node _x t _ts).simple_size = 1 + (t true).simple_size + (t false).simple_size :=
fun {α} _x t _ts => Eq.refl (RTree.node _x t _ts).simple_size
-/
#guard_msgs in
@ -51,7 +51,7 @@ def RTree.aux_size : List (RTree α) → Nat
end
/--
info: @[defeq] theorem RTree.aux_size.eq_2.{u_1} : ∀ {α : Type u_1} (t : RTree α) (ts : List (RTree α)),
info: @[backward_defeq] theorem RTree.aux_size.eq_2.{u_1} : ∀ {α : Type u_1} (t : RTree α) (ts : List (RTree α)),
RTree.aux_size (t :: ts) = t.size + RTree.aux_size ts :=
fun {α} t ts => Eq.refl (RTree.aux_size (t :: ts))
-/
@ -67,7 +67,7 @@ def RTree.map_aux (f : α → β) : List (RTree α) → List (RTree β)
end
/--
info: @[defeq] theorem RTree.map_aux.eq_2.{u_1, u_2} : ∀ {α : Type u_1} {β : Type u_2} (f : α → β) (t : RTree α)
info: @[backward_defeq] theorem RTree.map_aux.eq_2.{u_1, u_2} : ∀ {α : Type u_1} {β : Type u_2} (f : α → β) (t : RTree α)
(ts : List (RTree α)), RTree.map_aux f (t :: ts) = RTree.map f t :: RTree.map_aux f ts :=
fun {α} {β} f t ts => Eq.refl (RTree.map_aux f (t :: ts))
-/
@ -95,7 +95,7 @@ def VTree.vec_size : Vec (VTree α true 5) n b → Nat
end
/--
info: @[defeq] theorem VTree.size.eq_1.{u_1} : ∀ {α : Type u_1} (b_2 : Bool) (n_2 : Nat) (a : α)
info: @[backward_defeq] theorem VTree.size.eq_1.{u_1} : ∀ {α : Type u_1} (b_2 : Bool) (n_2 : Nat) (a : α)
(f : List Bool → List Nat → Vec (VTree α true 5) n_2 b_2),
(VTree.node b_2 n_2 a f).size = 1 + VTree.vec_size (f [] []) :=
fun {α} b_2 n_2 a f => Eq.refl (VTree.node b_2 n_2 a f).size

View file

@ -4,8 +4,9 @@ def Option_map (f : α → β) : Option α → Option β
/--
info: equations:
@[defeq] theorem Option_map.eq_1.{u_1, u_2} : ∀ {α : Type u_1} {β : Type u_2} (f : α → β), Option_map f none = none
@[defeq] theorem Option_map.eq_2.{u_1, u_2} : ∀ {α : Type u_1} {β : Type u_2} (f : α → β) (x_1 : α),
@[backward_defeq] theorem Option_map.eq_1.{u_1, u_2} : ∀ {α : Type u_1} {β : Type u_2} (f : α → β),
Option_map f none = none
@[backward_defeq] theorem Option_map.eq_2.{u_1, u_2} : ∀ {α : Type u_1} {β : Type u_2} (f : α → β) (x_1 : α),
Option_map f (some x_1) = some (f x_1)
-/
#guard_msgs in

View file

@ -88,9 +88,9 @@ termination_by structural n => n
/--
info: equations:
@[defeq] theorem Structural.foo.eq_1 : foo 0 0 = 0
@[backward_defeq] theorem Structural.foo.eq_1 : foo 0 0 = 0
theorem Structural.foo.eq_2 : ∀ (x : Nat), (x = 0 → False) → foo 0 x = x
@[defeq] theorem Structural.foo.eq_3 : ∀ (x n : Nat), foo n.succ x = foo n x
@[backward_defeq] theorem Structural.foo.eq_3 : ∀ (x n : Nat), foo n.succ x = foo n x
-/
#guard_msgs in
#print equations foo

View file

@ -227,19 +227,19 @@ public def f.eq_def := 1
#guard_msgs in
public def fexp.eq_def := 1
/-- info: @[defeq] private theorem f.eq_def : f = 1 -/
/-- info: @[backward_defeq] private theorem f.eq_def : f = 1 -/
#guard_msgs in #print sig f.eq_def
/-- info: @[defeq] private theorem f.eq_unfold : f = 1 -/
/-- info: @[backward_defeq] private theorem f.eq_unfold : f = 1 -/
#guard_msgs in #print sig f.eq_unfold
/-- info: @[defeq] theorem fexp.eq_def : fexp = 1 -/
/-- info: @[backward_defeq] theorem fexp.eq_def : fexp = 1 -/
#guard_msgs in #print sig fexp.eq_def
/-- info: @[defeq] theorem fexp.eq_unfold : fexp = 1 -/
/-- info: @[backward_defeq] theorem fexp.eq_unfold : fexp = 1 -/
#guard_msgs in #print sig fexp.eq_unfold
/-- info: @[defeq] private theorem f_struct.eq_1 : f_struct 0 = 0 -/
/-- info: @[backward_defeq] private theorem f_struct.eq_1 : f_struct 0 = 0 -/
#guard_msgs in #print sig f_struct.eq_1
/--

View file

@ -51,13 +51,13 @@ example : P fexp := by dsimp only [fexp_trfl]; exact hP1
example : P fexp := by dsimp only [fexp_trfl']; exact hP1
/-- info: @[defeq] private theorem f.eq_def : f = 1 -/
/-- info: @[backward_defeq] private theorem f.eq_def : f = 1 -/
#guard_msgs in #print sig f.eq_def
/-- info: @[defeq] private theorem f.eq_unfold : f = 1 -/
/-- info: @[backward_defeq] private theorem f.eq_unfold : f = 1 -/
#guard_msgs in #print sig f.eq_unfold
/-- info: @[defeq] private theorem f_struct.eq_1 : f_struct 0 = 0 -/
/-- info: @[backward_defeq] private theorem f_struct.eq_1 : f_struct 0 = 0 -/
#guard_msgs in #print sig f_struct.eq_1
/--

View file

@ -1,13 +1,13 @@
import Module.Basic
import Lean
/-- info: @[defeq] theorem f.eq_def : f = 1 -/
/-- info: @[backward_defeq] theorem f.eq_def : f = 1 -/
#guard_msgs in #print sig f.eq_def
/-- info: @[defeq] theorem f.eq_unfold : f = 1 -/
/-- info: @[backward_defeq] theorem f.eq_unfold : f = 1 -/
#guard_msgs in #print sig f.eq_unfold
/-- info: @[defeq] theorem f_struct.eq_1 : f_struct 0 = 0 -/
/-- info: @[backward_defeq] theorem f_struct.eq_1 : f_struct 0 = 0 -/
#guard_msgs in #print sig f_struct.eq_1
/--
info: theorem f_struct.eq_def : ∀ (x : Nat),