lean4-htt/src/Lean/Meta/Tactic/AuxLemma.lean
Joachim Breitner ac9a1cb415
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>
2026-04-27 10:07:59 +00:00

81 lines
3 KiB
Text
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/-
Copyright (c) 2021 Microsoft Corporation. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Leonardo de Moura
-/
module
prelude
public import Lean.AddDecl
public import Lean.DefEqAttrib
public section
namespace Lean.Meta
structure AuxLemmaKey where
type : Expr
-- 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
lemmas : PHashMap AuxLemmaKey (Name × List Name) := {}
deriving Inhabited
builtin_initialize auxLemmasExt : EnvExtension AuxLemmas ←
registerEnvExtension (pure {}) (asyncMode := .local) -- a mere cache, keep local
/--
Helper method for creating auxiliary lemmas in the environment.
It uses a cache that maps `type` to declaration name. The cache is not stored in `.olean` files.
It is useful to make sure the same auxiliary lemma is not created over and over again in the same
environment branch. For expensive auxiliary lemmas that should be deduplicated even across
different environment branches, consider using `realizeConst` instead.
This method is useful for tactics (e.g., `simp`) that may perform preprocessing steps to lemmas provided by
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) (defeq := false) : MetaM Name := do
let env ← getEnv
let s := auxLemmasExt.getState env
let key := { type, isPrivate := !env.isExporting, defeq }
let mkNewAuxLemma := do
let auxName ← mkAuxDeclName (kind := kind?.getD `_proof)
let decl :=
if env.hasUnsafe type || env.hasUnsafe value then
-- `result` contains unsafe code, thus we cannot use a theorem.
Declaration.defnDecl {
name := auxName
hints := ReducibilityHints.opaque
safety := DefinitionSafety.unsafe
levelParams, type, value
}
else
Declaration.thmDecl {
name := auxName
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)⟩
return auxName
if cache then
if let some (name, levelParams') := s.lemmas.find? key then
if levelParams == levelParams' then
return name
-- private contexts may reuse public matchers
if key.isPrivate then
if let some (name, levelParams') := s.lemmas.find? { key with isPrivate := false } then
if levelParams == levelParams' then
return name
mkNewAuxLemma
end Lean.Meta