feat: add Sym.simp theorem set attributes (#13018)

This PR adds named theorem sets for `Sym.simp` with associated
attributes, following the same pattern as `Meta.simp`'s
`register_simp_attr`.

- `register_sym_simp_attr my_set` creates a named set with its own
`PersistentEnvExtension` and attribute
- `@[my_set] theorem ...` adds a rewrite theorem
- `@[my_set] def ...` adds equation theorems from the definition
- `builtin_initialize symSimpExtension` registers a default
`@[sym_simp]` set
- `getSymSimpTheorems` / `getSymSimpExtension?` retrieve theorem sets at
tactic time

New files:
- `Sym/Simp/Attr.lean`: attribute logic (`mkSymSimpAttr`,
`registerSymSimpAttr`)
- `Sym/Simp/RegisterCommand.lean`: `register_sym_simp_attr` macro

Tests:
- `tests/pkg/sym_simp_attr/`: package test with user-defined set
(`my_sym_simp`)
- `tests/elab/sym_simp_set.lean`: tests for the builtin `@[sym_simp]`
set
This commit is contained in:
Leonardo de Moura 2026-03-20 20:53:39 -07:00 committed by GitHub
parent 4a62d4a79b
commit 973062e4e1
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 225 additions and 0 deletions

View file

@ -23,3 +23,5 @@ public import Lean.Meta.Sym.Simp.Discharger
public import Lean.Meta.Sym.Simp.ControlFlow
public import Lean.Meta.Sym.Simp.Goal
public import Lean.Meta.Sym.Simp.Telescope
public import Lean.Meta.Sym.Simp.Attr
public import Lean.Meta.Sym.Simp.RegisterCommand

View file

@ -0,0 +1,74 @@
/-
Copyright (c) 2026 Amazon.com, Inc. or its affiliates. All Rights Reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Leonardo de Moura
-/
module
prelude
public import Lean.Meta.Sym.Simp.Theorems
import Lean.Meta.Tactic.Simp.SimpTheorems -- for ignoreEquations
import Lean.Meta.Eqns -- for getEqnsFor?
public section
namespace Lean.Meta.Sym.Simp
/--
Adds a `Sym.Simp` theorem (an equality) to the given extension.
-/
def addSymSimpTheorem (ext : SymSimpExtension) (declName : Name) (attrKind : AttributeKind) : MetaM Unit := do
let thm ← mkTheoremFromDecl declName
ext.add thm attrKind
/--
Creates a `Sym.Simp` attribute for a named theorem set.
When a proposition is tagged, it is added as a rewrite theorem.
When a definition is tagged, its equation theorems are added.
-/
def mkSymSimpAttr (attrName : Name) (attrDescr : String) (ext : SymSimpExtension)
(ref : Name := by exact decl_name%) : IO Unit :=
registerBuiltinAttribute {
ref := ref
name := attrName
descr := attrDescr
applicationTime := AttributeApplicationTime.afterCompilation
add := fun declName _ attrKind => do
let go : MetaM Unit := do
let info ← getAsyncConstInfo declName
if (← isProp info.sig.get.type) then
addSymSimpTheorem ext declName attrKind
else if info.kind matches .defn then
if (← Simp.ignoreEquations declName) then
throwError "Cannot add `{attrName}` attribute to `{.ofConstName declName}`: \
It is a reducible definition or projection. `Sym.simp` does not support unfolding."
else if let some eqns ← getEqnsFor? declName then
for eqn in eqns do
addSymSimpTheorem ext eqn attrKind
else
throwError "Cannot add `{attrName}` attribute to `{.ofConstName declName}`: \
No equation theorems found."
else
throwError "Cannot add `{attrName}` attribute to `{.ofConstName declName}`: \
It is not a proposition nor a definition with equation theorems."
discard <| go.run {} {}
erase := fun _declName => do
throwError "Erasing `Sym.simp` attributes is not supported yet."
}
/--
Registers a named `Sym.Simp` theorem set. Each set gets its own attribute
and its own `SymSimpExtension` (persistent environment extension).
Must be called during initialization.
-/
def registerSymSimpAttr (attrName : Name) (attrDescr : String)
(ref : Name := by exact decl_name%) : IO SymSimpExtension := do
let ext ← mkSymSimpExt ref
mkSymSimpAttr attrName attrDescr ext ref
symSimpExtensionMapRef.modify fun map => map.insert attrName ext
return ext
builtin_initialize symSimpExtension : SymSimpExtension ← registerSymSimpAttr `sym_simp "Sym.simp theorem"
def getSymSimpTheorems : CoreM Theorems :=
symSimpExtension.getTheorems
end Lean.Meta.Sym.Simp

View file

@ -0,0 +1,22 @@
/-
Copyright (c) 2026 Amazon.com, Inc. or its affiliates. All Rights Reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Leonardo de Moura
-/
module
prelude
public import Lean.Meta.Sym.Simp.Attr
public meta import Init.Data.ToString.Name
public meta import Init.Data.String.Extra
public section
namespace Lean.Meta.Sym.Simp
macro (name := _root_.Lean.Parser.Command.registerSymSimpAttr) doc:(docComment)?
"register_sym_simp_attr" id:ident : command => do
let str := id.getId.toString
let idParser := mkIdentFrom id (`Parser.Attr ++ id.getId)
let descr := quote ((doc.map (·.getDocString) |>.getD s!"Sym.simp set for {id.getId.toString}").removeLeadingSpaces)
`($[$doc:docComment]? public meta initialize ext : SymSimpExtension ← registerSymSimpAttr $(quote id.getId) $descr
$[$doc:docComment]? syntax (name := $idParser:ident) $(quote str):str : attr)
end Lean.Meta.Sym.Simp

View file

@ -8,6 +8,7 @@ prelude
public import Lean.Meta.Sym.Pattern
public import Lean.Meta.DiscrTree
import Lean.Meta.Sym.Simp.DiscrTree
import Lean.ExtraModUses
public section
namespace Lean.Meta.Sym.Simp
@ -32,6 +33,7 @@ instance : BEq Theorem where
/-- Collection of simplification theorems available to the simplifier. -/
structure Theorems where
thms : DiscrTree Theorem := {}
deriving Inhabited
def Theorems.insert (thms : Theorems) (thm : Theorem) : Theorems :=
{ thms with thms := insertPattern thms.thms thm.pattern thm }
@ -46,4 +48,34 @@ def mkTheoremFromDecl (declName : Name) : MetaM Theorem := do
let (pattern, rhs) ← mkEqPatternFromDecl declName
return { expr := mkConst declName, pattern, rhs }
/--
Environment extension storing a set of `Sym.Simp` theorems.
Each named set gets its own extension, created by `registerSymSimpAttr`.
-/
abbrev SymSimpExtension := SimpleScopedEnvExtension Theorem Theorems
def SymSimpExtension.getTheorems (ext : SymSimpExtension) : CoreM Theorems :=
return ext.getState (← getEnv)
def mkSymSimpExt (name : Name := by exact decl_name%) : IO SymSimpExtension :=
registerSimpleScopedEnvExtension {
name := name
initial := {}
addEntry := fun thms thm => thms.insert thm
exportEntry? := fun lvl thm => do
let .const declName _ := thm.expr | return thm
guard (lvl == .private || !isPrivateName declName)
return thm
}
abbrev SymSimpExtensionMap := Std.HashMap Name SymSimpExtension
builtin_initialize symSimpExtensionMapRef : IO.Ref SymSimpExtensionMap ← IO.mkRef {}
def getSymSimpExtension? (attrName : Name) : CoreM (Option SymSimpExtension) := do
let ext? := (← symSimpExtensionMapRef.get)[attrName]?
if let some ext := ext? then
recordExtraModUseFromDecl (isMeta := true) ext.ext.name
return ext?
end Lean.Meta.Sym.Simp

View file

@ -0,0 +1,38 @@
/-
Tests for the builtin `@[sym_simp]` attribute.
-/
import Lean
open Lean Elab Tactic Meta
-- Add rewrite theorems to the default set
@[sym_simp] theorem add_zero_nat (a : Nat) : a + 0 = a := Nat.add_zero a
@[sym_simp] theorem zero_add_nat (a : Nat) : 0 + a = a := Nat.zero_add a
-- Add a definition (equation theorems)
@[sym_simp] def myMul : Nat → Nat → Nat
| 0, _ => 0
| a + 1, b => b + myMul a b
-- Tactic that uses the default sym_simp set
elab "sym_simp_default" : tactic => do
let thms ← Sym.Simp.getSymSimpTheorems
let rewrite := thms.rewrite
let methods : Sym.Simp.Methods := {
post := Sym.Simp.evalGround.andThen rewrite
}
liftMetaTactic1 fun mvarId => Sym.SymM.run do
let mvarId ← Sym.preprocessMVar mvarId
(← Sym.simpGoal mvarId methods).toOption
-- Test: ground evaluation
example : 2 + 3 = 5 := by sym_simp_default
-- Test: rewrite theorem
example (n : Nat) : n + 0 = n := by sym_simp_default
-- Test: both rewrite theorems
example (n : Nat) : 0 + n + 0 = n := by sym_simp_default
-- Test: equation theorems from definition
example : myMul 3 2 = 6 := by sym_simp_default

View file

@ -0,0 +1,40 @@
/-
Tests for `Sym.simp` theorem set attributes.
-/
module
import SymSimpAttr.Decl
public meta import Lean.Elab.Tactic.Basic
public meta import Lean.Meta.Sym
open Lean Elab Tactic Meta
-- Add a proposition as a rewrite theorem
@[my_sym_simp] theorem add_zero_nat (a : Nat) : a + 0 = a := Nat.add_zero a
-- Add a definition (equation theorems)
@[my_sym_simp] def myAdd : Nat → Nat → Nat
| 0, b => b
| a + 1, b => (myAdd a b) + 1
-- Tactic that uses the theorem set
elab "sym_simp_set" "[" id:ident "]" : tactic => do
let some ext ← Sym.Simp.getSymSimpExtension? id.getId
| throwError "Unknown Sym.simp set: {id.getId}"
let thms ← ext.getTheorems
let rewrite := thms.rewrite
let methods : Sym.Simp.Methods := {
post := Sym.Simp.evalGround.andThen rewrite
}
liftMetaTactic1 fun mvarId => Sym.SymM.run do
let mvarId ← Sym.preprocessMVar mvarId
(← Sym.simpGoal mvarId methods).toOption
-- Test: ground evaluation
example : 2 + 3 = 5 := by sym_simp_set [my_sym_simp]
-- Test: rewrite theorem from the set
example (n : Nat) : n + 0 = n := by sym_simp_set [my_sym_simp]
-- Test: equation theorems from definition
example : myAdd 3 2 = 5 := by sym_simp_set [my_sym_simp]

View file

@ -0,0 +1,9 @@
/-
Declare a `Sym.simp` theorem set.
-/
module
public import Lean
public meta import Lean.Meta.Sym.Simp.Attr
register_sym_simp_attr my_sym_simp

View file

@ -0,0 +1,5 @@
import Lake
open System Lake DSL
package sym_simp_attr
@[default_target] lean_lib SymSimpAttr

View file

@ -0,0 +1 @@
../../../build/release/stage1

View file

@ -0,0 +1,2 @@
rm -rf .lake/build
lake build