feat: add cbv_simproc infrastructure for user-extensible cbv simplification procedures (#12597)

This PR adds a `cbv_simproc` system for the `cbv` tactic, mirroring
simp's `simproc` infrastructure but tailored to cbv's three-phase
pipeline (`↓` pre, `cbv_eval` eval, `↑` post). User-defined
simplification procedures are indexed by discrimination tree patterns
and dispatched during cbv normalization.

New syntax:
- `cbv_simproc [↓|↑|cbv_eval] name (pattern) := body` — define and
register a cbv simproc
- `cbv_simproc_decl name (pattern) := body` — define without registering
- `attribute [cbv_simproc [↓|↑|cbv_eval]] name` — register an existing
declaration
- `builtin_cbv_simproc` variants for the internal use

New files:
- `src/Init/CbvSimproc.lean` — syntax and macros
- `src/Lean/Meta/Tactic/Cbv/CbvSimproc.lean` — types, env extensions,
registration, dispatch
- `src/Lean/Elab/Tactic/CbvSimproc.lean` — pattern elaboration and
command elaborators

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Wojciech Różowski
2026-03-10 10:59:13 +00:00
committed by GitHub
parent 85d38cba84
commit 9b1973ada7
13 changed files with 988 additions and 9 deletions

View File

@@ -30,6 +30,7 @@ public import Init.Hints
public import Init.Conv
public import Init.Guard
public import Init.Simproc
public import Init.CbvSimproc
public import Init.SizeOfLemmas
public import Init.BinderPredicates
public import Init.Ext

71
src/Init/CbvSimproc.lean Normal file
View File

@@ -0,0 +1,71 @@
/-
Copyright (c) 2026 Lean FRO, LLC. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Wojciech Różowski
-/
module
prelude
public meta import Init.Data.ToString.Name -- shake: keep (transitive public meta dep, fix)
public import Init.Tactics
import Init.Meta.Defs
public section
namespace Lean.Parser
syntax cbvSimprocEval := "cbv_eval"
/--
A user-defined simplification procedure used by the `cbv` tactic.
The body must have type `Lean.Meta.Sym.Simp.Simproc` (`Expr → SimpM Result`).
Procedures are indexed by a discrimination tree pattern and fire at one of three phases:
`↓` (pre), `cbv_eval` (eval), or `↑` (post, default).
-/
syntax (docComment)? attrKind "cbv_simproc " (Tactic.simpPre <|> Tactic.simpPost <|> cbvSimprocEval)? ident " (" term ")" " := " term : command
/--
A `cbv_simproc` declaration without automatically adding it to the cbv simproc set.
To activate, use `attribute [cbv_simproc]`.
-/
syntax (docComment)? "cbv_simproc_decl " ident " (" term ")" " := " term : command
syntax (docComment)? attrKind "builtin_cbv_simproc " (Tactic.simpPre <|> Tactic.simpPost <|> cbvSimprocEval)? ident " (" term ")" " := " term : command
syntax (docComment)? "builtin_cbv_simproc_decl " ident " (" term ")" " := " term : command
syntax (name := cbvSimprocPattern) "cbv_simproc_pattern% " term " => " ident : command
syntax (name := cbvSimprocPatternBuiltin) "builtin_cbv_simproc_pattern% " term " => " ident : command
namespace Attr
syntax (name := cbvSimprocAttr) "cbv_simproc" (Tactic.simpPre <|> Tactic.simpPost <|> cbvSimprocEval)? : attr
syntax (name := cbvSimprocBuiltinAttr) "builtin_cbv_simproc" (Tactic.simpPre <|> Tactic.simpPost <|> cbvSimprocEval)? : attr
end Attr
macro_rules
| `($[$doc?:docComment]? cbv_simproc_decl $n:ident ($pattern:term) := $body) => do
let simprocType := `Lean.Meta.Sym.Simp.Simproc
`($[$doc?:docComment]? meta def $n:ident : $(mkIdent simprocType) := $body
cbv_simproc_pattern% $pattern => $n)
macro_rules
| `($[$doc?:docComment]? builtin_cbv_simproc_decl $n:ident ($pattern:term) := $body) => do
let simprocType := `Lean.Meta.Sym.Simp.Simproc
`($[$doc?:docComment]? def $n:ident : $(mkIdent simprocType) := $body
builtin_cbv_simproc_pattern% $pattern => $n)
macro_rules
| `($[$doc?:docComment]? $kind:attrKind cbv_simproc $[$phase?]? $n:ident ($pattern:term) := $body) => do
`($[$doc?:docComment]? cbv_simproc_decl $n ($pattern) := $body
attribute [$kind cbv_simproc $[$phase?]?] $n)
macro_rules
| `($[$doc?:docComment]? $kind:attrKind builtin_cbv_simproc $[$phase?]? $n:ident ($pattern:term) := $body) => do
`($[$doc?:docComment]? builtin_cbv_simproc_decl $n ($pattern) := $body
attribute [$kind builtin_cbv_simproc $[$phase?]?] $n)
end Lean.Parser

View File

@@ -17,6 +17,7 @@ public import Lean.Elab.Tactic.Location
public import Lean.Elab.Tactic.SimpTrace
public import Lean.Elab.Tactic.Simp
public import Lean.Elab.Tactic.Simproc
public import Lean.Elab.Tactic.CbvSimproc
public import Lean.Elab.Tactic.BuiltinTactic
public import Lean.Elab.Tactic.Split
public import Lean.Elab.Tactic.Conv

View File

@@ -0,0 +1,75 @@
/-
Copyright (c) 2026 Lean FRO, LLC. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Wojciech Różowski
-/
module
prelude
public import Init.CbvSimproc
public import Lean.Meta.Tactic.Cbv.CbvSimproc
public import Lean.Elab.Command
public section
namespace Lean.Elab
open Lean Meta Sym
-- NB: This duplicates `elabSimprocPattern` from `Lean.Elab.Tactic.Simproc`
-- to avoid pulling in simp simproc elaboration as a dependency.
def elabCbvSimprocPattern (stx : Syntax) : MetaM Expr := do
let go : TermElabM Expr := do
let pattern Term.elabTerm stx none
Term.synthesizeSyntheticMVars
return pattern
go.run'
-- **Note**: `abstractMVars` returns a lambda expression, but `mkPatternFromExpr` expects a
-- proof term (it calls `inferType` to get the forall type used as the pattern). We convert the
-- lambda telescope into a forall telescope, then create a metavariable of that forall type so
-- that `mkPatternFromExpr` can recover it via `inferType`. This is a workaround until the
-- pattern API supports lambda telescopes directly (see `Sym.mkSimprocPatternFromExpr'`).
public def mkSimprocPatternFromExpr (e : Expr) : MetaM Pattern := do
let result abstractMVars e
let forallExpr lambdaTelescope result.expr fun args body => mkForallFVars args body
let term mkFreshExprMVar forallExpr
mkPatternFromExpr term result.paramNames.toList
def elabCbvSimprocKeys (stx : Syntax) : MetaM (Array DiscrTree.Key) := do
let pattern elabCbvSimprocPattern stx
let symPattern mkSimprocPatternFromExpr pattern
return symPattern.mkDiscrTreeKeys
def checkCbvSimprocType (declName : Name) : CoreM Unit := do
let decl getConstInfo declName
match decl.type with
| .const ``Sym.Simp.Simproc _ => pure ()
| _ => throwError "Unexpected type for cbv simproc pattern: Expected \
`{.ofConstName ``Sym.Simp.Simproc}`, but `{declName}` has type{indentExpr decl.type}"
namespace Command
open Lean.Meta.Tactic.Cbv
@[builtin_command_elab Lean.Parser.cbvSimprocPattern] def elabCbvSimprocPattern : CommandElab := fun stx => do
let `(cbv_simproc_pattern% $pattern => $declName) := stx | throwUnsupportedSyntax
liftTermElabM do
let declName realizeGlobalConstNoOverload declName
checkCbvSimprocType declName
let keys elabCbvSimprocKeys pattern
registerCbvSimproc declName keys
@[builtin_command_elab Lean.Parser.cbvSimprocPatternBuiltin] def elabCbvSimprocPatternBuiltin : CommandElab := fun stx => do
let `(builtin_cbv_simproc_pattern% $pattern => $declName) := stx | throwUnsupportedSyntax
liftTermElabM do
let declName realizeGlobalConstNoOverload declName
checkCbvSimprocType declName
let keys elabCbvSimprocKeys pattern
let val := mkAppN (mkConst ``registerBuiltinCbvSimproc)
#[toExpr declName, toExpr keys, mkConst declName]
let initDeclName mkFreshUserName (declName ++ `declare)
declareBuiltin initDeclName val
end Command
end Lean.Elab

View File

@@ -17,6 +17,7 @@ import Lean.Meta.Sym.ProofInstInfo
import Lean.Meta.Sym.AlphaShareBuilder
import Lean.Meta.Sym.Offset
import Lean.Meta.Sym.Eta
import Lean.Meta.AbstractMVars
import Init.Data.List.MapIdx
import Init.Data.Nat.Linear
import Std.Do.Triple.Basic
@@ -260,6 +261,45 @@ public def mkEqPatternFromDecl (declName : Name) : MetaM (Pattern × Expr) := do
let_expr Eq _ lhs rhs := type | throwError "conclusion is not a equality{indentExpr type}"
return (lhs, rhs)
/--
Like `mkPatternCore` but takes a lambda expression instead of a forall type.
Uses `lambdaBoundedTelescope` to open binders and detect instance/proof arguments.
-/
def mkPatternCoreFromLambda (lam : Expr) (levelParams : List Name)
(varTypes : Array Expr) (pattern : Expr) : MetaM Pattern := do
let fnInfos mkProofInstInfoMapFor pattern
let checkTypeMask := mkCheckTypeMask pattern varTypes.size
let checkTypeMask? := if checkTypeMask.all (· == false) then none else some checkTypeMask
let varInfos? lambdaBoundedTelescope lam varTypes.size fun xs _ =>
mkProofInstArgInfo? xs
return { levelParams, varTypes, pattern, fnInfos, varInfos?, checkTypeMask? }
def mkPatternFromLambda (levelParams : List Name) (lam : Expr) : MetaM Pattern := do
let rec go (pattern : Expr) (varTypes : Array Expr) : MetaM Pattern := do
if let .lam _ d b _ := pattern then
return ( go b (varTypes.push d))
let pattern preprocessType pattern
mkPatternCoreFromLambda lam levelParams varTypes pattern
go lam #[]
/--
Creates a `Pattern` from an expression containing metavariables.
Metavariables in `e` become pattern variables (wildcards). For example,
`Nat.succ ?m` produces a pattern matching `Nat.succ _` with discrimination
tree keys `[Nat.succ, *]`.
This is used for user-registered simproc patterns where the user provides
an expression with underscores (elaborated as metavariables) to specify
what the simproc should match.
-/
public def mkSimprocPatternFromExpr (e : Expr) : MetaM Pattern := do
let result abstractMVars e
let levelParams := result.paramNames.mapIdx fun i _ => Name.num uvarPrefix i
let us := levelParams.toList.map mkLevelParam
let expr := result.expr.instantiateLevelParamsArray result.paramNames us.toArray
mkPatternFromLambda levelParams.toList expr
structure UnifyM.Context where
pattern : Pattern
unify : Bool := true

View File

@@ -16,5 +16,6 @@ namespace Lean
builtin_initialize registerTraceClass `Meta.Tactic.cbv
builtin_initialize registerTraceClass `Debug.Meta.Tactic.cbv
builtin_initialize registerTraceClass `Debug.Meta.Tactic.cbv.simprocs (inherited := true)
end Lean

View File

@@ -0,0 +1,274 @@
/-
Copyright (c) 2026 Lean FRO, LLC. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Wojciech Różowski
-/
module
prelude
public import Lean.Compiler.InitAttr
public import Lean.ScopedEnvExtension
public import Lean.Meta.Sym.Simp.SimpM
public import Lean.Meta.Sym.Simp.Result
public import Lean.Meta.Sym.Simp.App
public import Lean.Meta.Sym.Simp.DiscrTree
public import Lean.Meta.Sym.Pattern
public section
namespace Lean.Meta.Tactic.Cbv
open Lean.Meta.Sym
open Lean.Meta.Sym.Simp
/--
The phase at which a `cbv_simproc` fires during cbv normalization.
- `pre` (`↓`): Fires on each subexpression *before* cbv reduction, so arguments are still
unreduced. Runs before cbv-specific pre-steps (projection reduction, unfolding, etc.).
- `eval` (`cbv_eval`): Fires during the post phase, after ground evaluation but *before*
`handleApp` attempts equation lemmas, unfolding, and recursion reduction.
Arguments have already been reduced.
- `post` (`↑`, default): Fires during the post phase, *after* `handleApp` has attempted
standard reduction. Arguments have already been reduced.
-/
inductive CbvSimprocPhase where
| pre | eval | post
deriving Inhabited, BEq, Hashable, Repr
instance : ToExpr CbvSimprocPhase where
toExpr
| .pre => mkConst ``CbvSimprocPhase.pre
| .eval => mkConst ``CbvSimprocPhase.eval
| .post => mkConst ``CbvSimprocPhase.post
toTypeExpr := mkConst ``CbvSimprocPhase
structure CbvSimprocOLeanEntry where
declName : Name
phase : CbvSimprocPhase := .post
keys : Array DiscrTree.Key := #[]
deriving Inhabited
structure CbvSimprocEntry extends CbvSimprocOLeanEntry where
proc : Simproc
instance : BEq CbvSimprocEntry where
beq e₁ e₂ := e₁.declName == e₂.declName
instance : ToFormat CbvSimprocEntry where
format e := format e.declName
structure CbvSimprocs where
pre : DiscrTree CbvSimprocEntry := {}
eval : DiscrTree CbvSimprocEntry := {}
post : DiscrTree CbvSimprocEntry := {}
simprocNames : PHashSet Name := {}
erased : PHashSet Name := {}
deriving Inhabited
def CbvSimprocs.addCore (s : CbvSimprocs) (keys : Array DiscrTree.Key) (declName : Name)
(phase : CbvSimprocPhase) (proc : Simproc) : CbvSimprocs :=
let entry : CbvSimprocEntry := { declName, phase, keys, proc }
let s := { s with simprocNames := s.simprocNames.insert declName, erased := s.erased.erase declName }
match phase with
| .pre => { s with pre := s.pre.insertKeyValue keys entry }
| .eval => { s with eval := s.eval.insertKeyValue keys entry }
| .post => { s with post := s.post.insertKeyValue keys entry }
def CbvSimprocs.erase (s : CbvSimprocs) (declName : Name) : CbvSimprocs :=
{ s with erased := s.erased.insert declName, simprocNames := s.simprocNames.erase declName }
structure BuiltinCbvSimprocs where
keys : Std.HashMap Name (Array DiscrTree.Key) := {}
procs : Std.HashMap Name Simproc := {}
deriving Inhabited
builtin_initialize builtinCbvSimprocDeclsRef : IO.Ref BuiltinCbvSimprocs IO.mkRef {}
def registerBuiltinCbvSimproc (declName : Name)
(keys : Array DiscrTree.Key) (proc : Simproc) : IO Unit := do
unless ( initializing) do
throw (IO.userError s!"Invalid builtin cbv simproc declaration: \
It can only be registered during initialization")
if ( builtinCbvSimprocDeclsRef.get).keys.contains declName then
throw (IO.userError s!"Invalid builtin cbv simproc declaration \
`{privateToUserName declName}`: It has already been declared")
builtinCbvSimprocDeclsRef.modify fun { keys := ks, procs } =>
{ keys := ks.insert declName keys, procs := procs.insert declName proc }
structure CbvSimprocDecl where
declName : Name
keys : Array DiscrTree.Key
deriving Inhabited
def CbvSimprocDecl.lt (d₁ d₂ : CbvSimprocDecl) : Bool :=
Name.quickLt d₁.declName d₂.declName
structure CbvSimprocDeclExtState where
builtin : Std.HashMap Name (Array DiscrTree.Key)
newEntries : PHashMap Name (Array DiscrTree.Key) := {}
deriving Inhabited
builtin_initialize cbvSimprocDeclExt :
PersistentEnvExtension CbvSimprocDecl CbvSimprocDecl CbvSimprocDeclExtState
registerPersistentEnvExtension {
mkInitial := return { builtin := ( builtinCbvSimprocDeclsRef.get).keys }
addImportedFn := fun _ => return { builtin := ( builtinCbvSimprocDeclsRef.get).keys }
addEntryFn := fun s d =>
{ s with newEntries := s.newEntries.insert d.declName d.keys }
exportEntriesFn := fun s =>
let result := s.newEntries.foldl (init := #[]) fun result declName keys =>
result.push { declName, keys }
result.qsort CbvSimprocDecl.lt
}
def getCbvSimprocDeclKeys? (declName : Name) : CoreM (Option (Array DiscrTree.Key)) := do
let env getEnv
let keys? match env.getModuleIdxFor? declName with
| some modIdx => do
let some decl :=
(cbvSimprocDeclExt.getModuleEntries env modIdx).binSearch { declName, keys := #[] } CbvSimprocDecl.lt
| pure none
pure (some decl.keys)
| none => pure ((cbvSimprocDeclExt.getState env).newEntries.find? declName)
if let some keys := keys? then
return some keys
else
return (cbvSimprocDeclExt.getState env).builtin[declName]?
def isCbvSimproc (declName : Name) : CoreM Bool :=
return ( getCbvSimprocDeclKeys? declName).isSome
def isBuiltinCbvSimproc (declName : Name) : CoreM Bool := do
return (cbvSimprocDeclExt.getState ( getEnv)).builtin.contains declName
def registerCbvSimproc (declName : Name) (keys : Array DiscrTree.Key) : CoreM Unit := do
let env getEnv
unless (env.getModuleIdxFor? declName).isNone do
throwError "Invalid cbv simproc declaration `{.ofConstName declName}`: \
It is declared in an imported module"
if ( isCbvSimproc declName) then
throwError "Invalid cbv simproc declaration `{.ofConstName declName}`: \
It has already been declared"
modifyEnv fun env => cbvSimprocDeclExt.modifyState env fun s =>
{ s with newEntries := s.newEntries.insert declName keys }
abbrev CbvSimprocExtension := ScopedEnvExtension CbvSimprocOLeanEntry CbvSimprocEntry CbvSimprocs
unsafe def getCbvSimprocFromDeclImpl (declName : Name) : ImportM Simproc := do
let ctx read
match ctx.env.find? declName with
| none => throw <| IO.userError ("Unknown constant `" ++ toString declName ++ "`")
| some info =>
match info.type with
| .const ``Simproc _ =>
IO.ofExcept <| ctx.env.evalConst Simproc ctx.opts declName
| _ => throw <| IO.userError s!"Cbv simproc `{privateToUserName declName}` has an \
unexpected type: Expected `Simproc`, but found `{info.type}`"
@[implemented_by getCbvSimprocFromDeclImpl]
opaque getCbvSimprocFromDecl (declName : Name) : ImportM Simproc
def toCbvSimprocEntry (e : CbvSimprocOLeanEntry) : ImportM CbvSimprocEntry := do
return { toCbvSimprocOLeanEntry := e, proc := ( getCbvSimprocFromDecl e.declName) }
builtin_initialize builtinCbvSimprocsRef : IO.Ref CbvSimprocs IO.mkRef {}
builtin_initialize cbvSimprocExtension : CbvSimprocExtension
registerScopedEnvExtension {
name := `cbvSimprocExt
mkInitial := builtinCbvSimprocsRef.get
ofOLeanEntry := fun _ => toCbvSimprocEntry
toOLeanEntry := fun e => e.toCbvSimprocOLeanEntry
addEntry := fun s e => s.addCore e.keys e.declName e.phase e.proc
}
def eraseCbvSimprocAttr (declName : Name) : AttrM Unit := do
let s := cbvSimprocExtension.getState ( getEnv)
unless s.simprocNames.contains declName do
throwError "`{.ofConstName declName}` does not have a [cbv_simproc] attribute"
modifyEnv fun env => cbvSimprocExtension.modifyState env fun s => s.erase declName
def addCbvSimprocAttrCore (declName : Name) (kind : AttributeKind)
(phase : CbvSimprocPhase) : CoreM Unit := do
let proc getCbvSimprocFromDecl declName
let some keys getCbvSimprocDeclKeys? declName |
throwError "Invalid `[cbv_simproc]` attribute: \
`{.ofConstName declName}` is not a cbv simproc"
cbvSimprocExtension.add { declName, phase, keys, proc } kind
def parseCbvSimprocPhase (stx : Syntax) : CbvSimprocPhase :=
if stx.isNone then .post
else
let inner := stx[0]
if inner.getKind == `Lean.Parser.Tactic.simpPre then .pre
else if inner.getKind == `Lean.Parser.cbvSimprocEval then .eval
else .post
def addCbvSimprocAttr (declName : Name) (stx : Syntax)
(attrKind : AttributeKind) : AttrM Unit := do
ensureAttrDeclIsMeta `cbvSimprocAttr declName attrKind
let go : MetaM Unit := do
addCbvSimprocAttrCore declName attrKind (parseCbvSimprocPhase stx[1])
discard <| go.run {} {}
def addCbvSimprocBuiltinAttrCore (ref : IO.Ref CbvSimprocs) (declName : Name)
(phase : CbvSimprocPhase) (proc : Simproc) : IO Unit := do
let some keys := ( builtinCbvSimprocDeclsRef.get).keys[declName]? |
throw (IO.userError s!"Invalid `[builtin_cbv_simproc]` attribute: \
`{privateToUserName declName}` is not a builtin cbv simproc")
ref.modify fun s => s.addCore keys declName phase proc
def addCbvSimprocBuiltinAttr (declName : Name) (phase : CbvSimprocPhase)
(proc : Simproc) : IO Unit :=
addCbvSimprocBuiltinAttrCore builtinCbvSimprocsRef declName phase proc
private def addBuiltinCbvSimproc (declName : Name) (stx : Syntax) : AttrM Unit := do
let go : MetaM Unit := do
let phase := parseCbvSimprocPhase stx[1]
let val := mkAppN (mkConst ``addCbvSimprocBuiltinAttr)
#[toExpr declName, toExpr phase, mkConst declName]
let initDeclName mkFreshUserName (declName ++ `declare)
declareBuiltin initDeclName val
go.run' {}
builtin_initialize
registerBuiltinAttribute {
ref := by exact decl_name%
name := `cbvSimprocAttr
descr := "Cbv simplification procedure"
applicationTime := AttributeApplicationTime.afterCompilation
add := fun declName stx attrKind => addCbvSimprocAttr declName stx attrKind
erase := fun declName => eraseCbvSimprocAttr declName
}
builtin_initialize
registerBuiltinAttribute {
ref := by exact decl_name%
name := `cbvSimprocBuiltinAttr
descr := "Builtin cbv simplification procedure"
applicationTime := AttributeApplicationTime.afterCompilation
erase := fun _ => throwError "Not implemented yet, [-builtin_cbv_simproc]"
add := fun declName stx _ => addBuiltinCbvSimproc declName stx
}
def getCbvSimprocs : CoreM CbvSimprocs :=
return cbvSimprocExtension.getState ( getEnv)
def cbvSimprocDispatch (tree : DiscrTree CbvSimprocEntry)
(erased : PHashSet Name) : Simproc := fun e => do
let candidates := Sym.getMatchWithExtra tree e
if candidates.isEmpty then
trace[Debug.Meta.Tactic.cbv.simprocs] "no cbv simprocs found for {e}"
return .rfl
for (entry, numExtra) in candidates do
unless erased.contains entry.declName do
let result if numExtra == 0 then
entry.proc e
else
simpOverApplied e numExtra entry.proc
if result matches .step _ _ _ then
trace[Debug.Meta.Tactic.cbv.simprocs] "cbv simproc `{entry.declName}` result {e} => {result.getResultExpr e}"
return result
if result matches .rfl (done := true) then
return result
return .rfl
end Lean.Meta.Tactic.Cbv

View File

@@ -13,6 +13,7 @@ public import Lean.Meta.Tactic.Cbv.ControlFlow
import Lean.Meta.Tactic.Cbv.Util
import Lean.Meta.Tactic.Cbv.TheoremsLookup
import Lean.Meta.Tactic.Cbv.CbvEvalExt
import Lean.Meta.Tactic.Cbv.CbvSimproc
import Lean.Meta.Sym
import Lean.Meta.Tactic.Refl
import Lean.Meta.Tactic.Replace
@@ -272,22 +273,30 @@ def cbvPreStep : Simproc := fun e => do
| .forallE .. | .lam .. | .fvar .. | .mvar .. | .bvar .. | .sort .. => return .rfl (done := true)
| _ => return .rfl
/-- Pre-pass: skip builtin values and proofs, then dispatch structurally. -/
def cbvPre : Simproc := isBuiltinValue <|> isProofTerm <|> cbvPreStep
/-- Pre-pass: skip builtin values and proofs, run pre simprocs, then dispatch structurally. -/
def cbvPre (simprocs : CbvSimprocs) : Simproc :=
isBuiltinValue <|> isProofTerm <|> cbvSimprocDispatch simprocs.pre simprocs.erased <|> cbvPreStep
/-- Post-pass: evaluate ground arithmetic, then try unfolding/beta-reducing applications. -/
def cbvPost : Simproc := evalGround <|> handleApp
/-- Post-pass: evaluate ground arithmetic, then try eval simprocs, then try unfolding/beta-reducing applications and finally run post simprocs -/
def cbvPost (simprocs : CbvSimprocs) : Simproc :=
evalGround <|> cbvSimprocDispatch simprocs.eval simprocs.erased <|> handleApp <|> cbvSimprocDispatch simprocs.post simprocs.erased
def cbvCore (e : Expr) (config : Sym.Simp.Config := {}) : Sym.SymM Result :=
SimpM.run' (methods := {pre := cbvPre, post := cbvPost}) (config := config)
def mkCbvMethods (simprocs : CbvSimprocs) : Methods :=
{ pre := cbvPre simprocs, post := cbvPost simprocs }
def cbvCore (e : Expr) (config : Sym.Simp.Config := {}) : Sym.SymM Result := do
let simprocs getCbvSimprocs
let methods := mkCbvMethods simprocs
SimpM.run' (methods := methods) (config := config)
<| simp e
/-- Reduce a single expression. Unfolds reducibles, shares subterms, then runs the
simplifier with `cbvPre`/`cbvPost`. Used by `conv => cbv`. -/
public def cbvEntry (e : Expr) : MetaM Result := do
trace[Meta.Tactic.cbv] "Called cbv tactic to simplify {e}"
let simprocs getCbvSimprocs
let config : Sym.Simp.Config := { maxSteps := cbv.maxSteps.get ( getOptions) }
let methods := {pre := cbvPre, post := cbvPost}
let methods := mkCbvMethods simprocs
let e Sym.unfoldReducible e
Sym.SymM.run do
let e Sym.shareCommon e

View File

@@ -1,5 +1,5 @@
#include "util/options.h"
//update stage0
namespace lean {
options get_default_options() {
options opts;

285
tests/elab/cbv_simproc.lean Normal file
View File

@@ -0,0 +1,285 @@
import Lean
set_option cbv.warning false
open Lean Meta Sym.Simp
cbv_simproc_decl myDeclProc (Nat.succ _) := fun _ => do
return .rfl
cbv_simproc myPostProc (Nat.add _ _) := fun _ => do
return .rfl
cbv_simproc myPreProc (Nat.mul _ _) := fun _ => do
return .rfl
cbv_simproc cbv_eval myEvalProc (Nat.sub _ _) := fun _ => do
return .rfl
cbv_simproc myExplicitPostProc (Nat.mod _ _) := fun _ => do
return .rfl
cbv_simproc_decl myLateProc (Nat.div _ _) := fun _ => do
return .rfl
attribute [cbv_simproc cbv_eval] myLateProc
opaque myFun : Nat Nat
-- Pre simproc sees unreduced arguments
section
local cbv_simproc preTest (myFun _) := fun e => do
let .app _f arg := e | return .rfl
logInfo m!"pre saw: {arg}"
return .rfl
/--
info: pre saw: 1 + 2
---
info: pre saw: 3
-/
#guard_msgs in
example : myFun (1 + 2) = myFun 3 := by cbv
end
-- Eval simproc sees reduced arguments (fires before handleApp)
section
local cbv_simproc cbv_eval evalTest (myFun _) := fun e => do
let .app _f arg := e | return .rfl
logInfo m!"eval saw: {arg}"
return .rfl
/--
info: eval saw: 3
---
info: eval saw: 3
---
info: eval saw: 4
---
error: unsolved goals
⊢ myFun 3 = myFun 4
-/
#guard_msgs in
example : myFun (1 + 2) = myFun 4 := by cbv
end
-- Post simproc sees reduced arguments (fires after handleApp)
section
local cbv_simproc postTest (myFun _) := fun e => do
let .app _f arg := e | return .rfl
logInfo m!"post saw: {arg}"
return .rfl
/--
info: post saw: 3
---
info: post saw: 3
---
info: post saw: 4
---
error: unsolved goals
⊢ myFun 3 = myFun 4
-/
#guard_msgs in
example : myFun (1 + 2) = myFun 4 := by cbv
end
section
cbv_simproc stringToList (String.toList _) := fun e => do
let_expr String.toList s := e | return .rfl
let some str := getStringValue? s | return .rfl
let result Sym.share <| toExpr str.toList
let typeExpr := mkApp (mkConst ``List [Lean.Level.zero]) (mkConst ``Char)
return .step result (mkApp2 (mkConst ``Eq.refl [1]) typeExpr result) (done := true)
theorem simprocTest : "a".toList = ['a'] := by cbv
/--
info: theorem simprocTest : "a".toList = ['a'] :=
of_eq_true (Eq.trans (congrFun' (congrArg Eq (Eq.refl ['a'])) ['a']) (eq_self ['a']))
-/
#guard_msgs in
#print simprocTest
end
-- Erase attribute test
cbv_simproc_decl erasableProc (Nat.gcd _ _) := fun _ => return .rfl (done := true)
theorem erasableProcTest : Nat.gcd 1 2 = 1 := by cbv
/--
info: theorem erasableProcTest : Nat.gcd 1 2 = 1 :=
of_eq_true (Eq.trans (congrFun' (congrArg Eq (Eq.refl 1)) 1) (eq_self 1))
-/
#guard_msgs in
#print erasableProcTest
/-- error: `erasableProc` does not have a [cbv_simproc] attribute -/
#guard_msgs in
attribute [-cbvSimprocAttr] erasableProc
attribute [cbv_simproc] erasableProc
attribute [-cbvSimprocAttr] erasableProc
-- Pattern with @
section
local cbv_simproc testAt (@Nat.succ _) := fun e => do
let_expr Nat.succ arg := e | return .rfl
logInfo m!"@ saw: {arg}"
return .rfl
/-- info: @ saw: 1 + 2 -/
#guard_msgs in
example : Nat.succ (1 + 2) = 4 := by cbv
end
-- Pattern with explicit type args via @
section
local cbv_simproc testExplicitList (@List.cons Nat _ _) := fun _ => do
logInfo m!"explicit list cons"
return .rfl
/--
info: explicit list cons
---
info: explicit list cons
---
info: explicit list cons
-/
#guard_msgs in
example : ([1, 2, 3] : List Nat) = [1, 2, 3] := by cbv
end
-- Implicit args are auto-filled: `List.cons _ _` works the same as `@List.cons Nat _ _`
section
local cbv_simproc testImplicitList (List.cons _ _) := fun _ => do
logInfo m!"implicit list cons"
return .rfl
/--
info: implicit list cons
---
info: implicit list cons
---
info: implicit list cons
-/
#guard_msgs in
example : ([1, 2, 3] : List Nat) = [1, 2, 3] := by cbv
end
-- Typeclass-heavy pattern
section
local cbv_simproc testHAdd (HAdd.hAdd _ _) := fun _ => do
logInfo m!"HAdd matched"
return .rfl
/-- info: HAdd matched -/
#guard_msgs in
example : (1 : Nat) + 2 = 3 := by cbv
end
-- Nested pattern
section
local cbv_simproc testNested (Nat.succ (Nat.succ _)) := fun _ => do
logInfo m!"nested succ"
return .rfl
/--
info: nested succ
---
info: nested succ
-/
#guard_msgs in
example : Nat.succ (Nat.succ 0) = 2 := by cbv
end
-- Explicit type args on Prod.mk
section
local cbv_simproc testProd (@Prod.mk Nat Nat _ _) := fun _ => do
logInfo m!"prod matched"
return .rfl
/--
info: prod matched
---
info: prod matched
-/
#guard_msgs in
example : (1 + 2, 3) = (3, 3) := by cbv
end
-- Scoped simproc: active when namespace is open, inactive when closed
namespace ScopedTest
scoped cbv_simproc scopedProc (myFun _) := fun e => do
let .app _f arg := e | return .rfl
logInfo m!"scoped saw: {arg}"
return .rfl
/-- info: scoped saw: 0 -/
#guard_msgs in
example : myFun 0 = myFun 0 := by cbv
end ScopedTest
-- Outside the namespace, the simproc should not fire
example : myFun 0 = myFun 0 := by cbv
-- Re-opening the namespace reactivates the simproc
open ScopedTest in
/-- info: scoped saw: 0 -/
#guard_msgs in
example : myFun 0 = myFun 0 := by cbv
-- Over-applied pattern: simproc registered for `myBinFun _` fires on `myBinFun x y`
opaque myBinFun : Nat Nat Nat
section
local cbv_simproc overAppliedProc (myBinFun _) := fun e => do
logInfo m!"over-applied saw: {e}"
return .rfl
/--
info: over-applied saw: myBinFun (1 + 2)
---
info: over-applied saw: myBinFun 3
-/
#guard_msgs in
example : myBinFun (1 + 2) 4 = myBinFun 3 4 := by cbv
end
-- Multiple simprocs matching the same head: first non-rfl result wins
section
local cbv_simproc multiFirst (myFun _) := fun _ => do
logInfo m!"first fired"
return .rfl (done := true)
local cbv_simproc multiSecond (myFun _) := fun _ => do
logInfo m!"second fired"
return .rfl
/-- info: first fired -/
#guard_msgs in
example : myFun 0 = myFun 0 := by cbv
end
-- Multiple simprocs: first returns .rfl (skip), second succeeds with .step
section
@[irreducible] def myConst : Nat := 42
local cbv_simproc skipFirst (myConst) := fun _e => do
logInfo m!"first skipped"
return .rfl
local cbv_simproc succeedSecond (myConst) := fun _e => do
logInfo m!"second succeeded"
let result := toExpr (42 : Nat)
return .step result (mkConst ``myConst.eq_def) (done := true)
/--
info: first skipped
---
info: second succeeded
-/
#guard_msgs in
example : myConst = 42 := by cbv
end

View File

@@ -0,0 +1,34 @@
import Lean
open Lean Meta Sym.Simp
-- Attribute on a def that was never declared as a cbv simproc
meta def notASimproc : Simproc := fun _ => return .rfl
/-- error: Invalid `[cbv_simproc]` attribute: `notASimproc` is not a cbv simproc -/
#guard_msgs in
attribute [cbv_simproc] notASimproc
-- Attribute on a def with wrong type
def plainNat : Nat := 42
/-- error: Cbv simproc `plainNat` has an unexpected type: Expected `Simproc`, but found `Nat` -/
#guard_msgs in
attribute [cbv_simproc] plainNat
-- Erase from something without the attribute
cbv_simproc_decl untaggedProc (Nat.gcd _ _) := fun _ => return .rfl
/-- error: `untaggedProc` does not have a [cbv_simproc] attribute -/
#guard_msgs in
attribute [-cbvSimprocAttr] untaggedProc
-- Wrong type in cbv_simproc_decl pattern registration
def wrongType : Nat := 42
/--
error: Unexpected type for cbv simproc pattern: Expected `Simproc`, but `wrongType` has type
Nat
-/
#guard_msgs in
cbv_simproc_pattern% Nat.succ _ => wrongType

View File

@@ -0,0 +1,188 @@
import Lean
set_option cbv.warning false
/-! # cbv_simproc for Nat.fib using fast doubling
This test demonstrates a cbv_simproc ported from Mathlib's `norm_num` extension that evaluates `Nat.fib.
-/
open Lean Meta Sym.Simp
-- Define fib
def Nat.fib : Nat Nat
| 0 => 0
| 1 => 1
| n + 2 => Nat.fib n + Nat.fib (n + 1)
theorem Nat.fib_zero : Nat.fib 0 = 0 := rfl
theorem Nat.fib_one : Nat.fib 1 = 1 := rfl
theorem Nat.fib_two : Nat.fib 2 = 1 := rfl
def IsFibAux (n a b : Nat) := Nat.fib n = a Nat.fib (n + 1) = b
theorem isFibAux_zero : IsFibAux 0 0 1 :=
Nat.fib_zero, Nat.fib_one
theorem isFibAux_one : IsFibAux 1 1 1 :=
Nat.fib_one, Nat.fib_two
theorem fib_add_two {n : Nat} : Nat.fib (n + 2) = Nat.fib n + Nat.fib (n + 1) := by grind [Nat.fib]
theorem fib_add (m n : Nat) : Nat.fib (m + n + 1) = Nat.fib m * Nat.fib n + Nat.fib (m + 1) * Nat.fib (n + 1) := by
induction n generalizing m with
| zero => grind [Nat.fib]
| succ n ih =>
specialize ih (m + 1)
rw [Nat.add_assoc m 1 n, Nat.add_comm 1 n] at ih
simp only [fib_add_two, ih]
grind
theorem fib_two_mul (n : Nat) : Nat.fib (2 * n) = Nat.fib n * (2 * Nat.fib (n + 1) - Nat.fib n) := by
cases n
· simp [Nat.fib]
· rw [Nat.two_mul, Nat.add_assoc, fib_add, fib_add_two, Nat.two_mul]
have add_tsub_cancel_right : (a b : Nat), a + b - b = a := by grind
simp only [ Nat.add_assoc, add_tsub_cancel_right]
grind
theorem fib_two_mul_add_one (n : Nat) : Nat.fib (2 * n + 1) = Nat.fib (n + 1) ^ 2 + Nat.fib n ^ 2 := by
rw [Nat.two_mul, fib_add]
grind [Nat.two_mul, fib_add]
theorem isFibAux_two_mul {n a b n' a' b' : Nat} (H : IsFibAux n a b)
(hn : 2 * n = n') (h1 : a * (2 * b - a) = a') (h2 : a * a + b * b = b') :
IsFibAux n' a' b' :=
by rw [ hn, fib_two_mul, H.1, H.2, h1],
by rw [ hn, fib_two_mul_add_one, H.1, H.2, Nat.pow_two, Nat.pow_two, Nat.add_comm, h2]
theorem fib_two_mul_add_two (n : Nat) :
Nat.fib (2 * n + 2) = Nat.fib (n + 1) * (2 * Nat.fib n + Nat.fib (n + 1)) := by
rw [fib_add_two, fib_two_mul, fib_two_mul_add_one]
induction n <;> grind [Nat.fib]
theorem isFibAux_two_mul_add_one {n a b n' a' b' : Nat} (H : IsFibAux n a b)
(hn : 2 * n + 1 = n') (h1 : a * a + b * b = a') (h2 : b * (2 * a + b) = b') :
IsFibAux n' a' b' :=
by rw [ hn, fib_two_mul_add_one, H.1, H.2, Nat.pow_two, Nat.pow_two, Nat.add_comm, h1],
by rw [ hn, fib_two_mul_add_two, H.1, H.2, h2]
theorem isFibAux_two_mul_done {n a b n' a' : Nat} (H : IsFibAux n a b)
(hn : 2 * n = n') (h : a * (2 * b - a) = a') : Nat.fib n' = a' :=
(isFibAux_two_mul H hn h rfl).1
theorem isFibAux_two_mul_add_one_done {n a b n' a' : Nat} (H : IsFibAux n a b)
(hn : 2 * n + 1 = n') (h : a * a + b * b = a') : Nat.fib n' = a' :=
(isFibAux_two_mul_add_one H hn h rfl).1
namespace FibSimproc
partial def proveNatFibAux (n : Nat) : Nat × Nat × Expr :=
match n with
| 0 => 0, 1, mkConst ``isFibAux_zero
| 1 => 1, 1, mkConst ``isFibAux_one
| n =>
let half := n / 2
let a, b, H := proveNatFibAux half
let en := mkNatLit half
let ea := mkNatLit a
let eb := mkNatLit b
if n % 2 == 0 then
let a' := a * (2 * b - a)
let b' := a * a + b * b
let en' := mkNatLit n
let ea' := mkNatLit a'
let eb' := mkNatLit b'
let hn := mkApp2 (mkConst ``Eq.refl [1]) (mkConst ``Nat) en'
let h1 := mkApp2 (mkConst ``Eq.refl [1]) (mkConst ``Nat) ea'
let h2 := mkApp2 (mkConst ``Eq.refl [1]) (mkConst ``Nat) eb'
let proof := mkAppN (mkConst ``isFibAux_two_mul) #[en, ea, eb, en', ea', eb', H, hn, h1, h2]
a', b', proof
else
let a' := a * a + b * b
let b' := b * (2 * a + b)
let en' := mkNatLit n
let ea' := mkNatLit a'
let eb' := mkNatLit b'
let hn := mkApp2 (mkConst ``Eq.refl [1]) (mkConst ``Nat) en'
let h1 := mkApp2 (mkConst ``Eq.refl [1]) (mkConst ``Nat) ea'
let h2 := mkApp2 (mkConst ``Eq.refl [1]) (mkConst ``Nat) eb'
let proof := mkAppN (mkConst ``isFibAux_two_mul_add_one) #[en, ea, eb, en', ea', eb', H, hn, h1, h2]
a', b', proof
def proveNatFib (n : Nat) : Nat × Expr :=
match n with
| 0 => 0, mkConst ``Nat.fib_zero
| 1 => 1, mkConst ``Nat.fib_one
| 2 => 1, mkConst ``Nat.fib_two
| n =>
let half := n / 2
let a, b, H := proveNatFibAux half
let en := mkNatLit half
let ea := mkNatLit a
let eb := mkNatLit b
if n % 2 == 0 then
let result := a * (2 * b - a)
let en' := mkNatLit n
let eresult := mkNatLit result
let hn := mkApp2 (mkConst ``Eq.refl [1]) (mkConst ``Nat) en'
let h := mkApp2 (mkConst ``Eq.refl [1]) (mkConst ``Nat) eresult
let proof := mkAppN (mkConst ``isFibAux_two_mul_done) #[en, ea, eb, en', eresult, H, hn, h]
result, proof
else
let result := a * a + b * b
let en' := mkNatLit n
let eresult := mkNatLit result
let hn := mkApp2 (mkConst ``Eq.refl [1]) (mkConst ``Nat) en'
let h := mkApp2 (mkConst ``Eq.refl [1]) (mkConst ``Nat) eresult
let proof := mkAppN (mkConst ``isFibAux_two_mul_add_one_done) #[en, ea, eb, en', eresult, H, hn, h]
result, proof
end FibSimproc
section
cbv_simproc cbv_eval evalFib (Nat.fib _) := fun e => do
let_expr Nat.fib arg := e | return .rfl
let some n := Sym.getNatValue? arg | return .rfl
let result, proof := FibSimproc.proveNatFib n
let resultExpr Sym.share (mkNatLit result)
return .step resultExpr proof (done := true)
theorem fib_0 : Nat.fib 0 = 0 := by cbv
theorem fib_1 : Nat.fib 1 = 1 := by cbv
theorem fib_2 : Nat.fib 2 = 1 := by cbv
theorem fib_10 : Nat.fib 10 = 55 := by cbv
theorem fib_20 : Nat.fib 20 = 6765 := by cbv
theorem fib_30 : Nat.fib 30 = 832040 := by cbv
theorem fib_50 : Nat.fib 50 = 12586269025 := by cbv
theorem fib_100 : Nat.fib 100 = 354224848179261915075 := by cbv
theorem fib_sum : Nat.fib 10 + Nat.fib 20 = 6820 := by cbv
/--
info: theorem fib_sum : Nat.fib 10 + Nat.fib 20 = 6820 :=
of_eq_true
(Eq.trans
(congrFun'
(congrArg Eq
(Eq.trans
(congr
(congrArg HAdd.hAdd
(isFibAux_two_mul_done
(isFibAux_two_mul_add_one (isFibAux_two_mul isFibAux_one (Eq.refl 2) (Eq.refl 1) (Eq.refl 2))
(Eq.refl 5) (Eq.refl 5) (Eq.refl 8))
(Eq.refl 10) (Eq.refl 55)))
(isFibAux_two_mul_done
(isFibAux_two_mul
(isFibAux_two_mul_add_one (isFibAux_two_mul isFibAux_one (Eq.refl 2) (Eq.refl 1) (Eq.refl 2))
(Eq.refl 5) (Eq.refl 5) (Eq.refl 8))
(Eq.refl 10) (Eq.refl 55) (Eq.refl 89))
(Eq.refl 20) (Eq.refl 6765)))
(Eq.refl 6820)))
6820)
(eq_self 6820))
-/
#guard_msgs in
#print fib_sum
end

View File

@@ -1 +1 @@
versoDocMissing.lean:10:0: error: unexpected end of input; expected '#guard_msgs', 'abbrev', 'add_decl_doc', 'axiom', 'binder_predicate', 'builtin_dsimproc', 'builtin_dsimproc_decl', 'builtin_grind_propagator', 'builtin_initialize', 'builtin_simproc', 'builtin_simproc_decl', 'class', 'coinductive', 'declare_simp_like_tactic', 'declare_syntax_cat', 'def', 'dsimproc', 'dsimproc_decl', 'elab', 'elab_rules', 'example', 'grind_propagator', 'inductive', 'infix', 'infixl', 'infixr', 'initialize', 'instance', 'macro', 'macro_rules', 'notation', 'opaque', 'postfix', 'prefix', 'recommended_spelling', 'register_error_explanation', 'register_tactic_tag', 'register_try?_tactic', 'simproc', 'simproc_decl', 'structure', 'syntax', 'tactic_extension', 'theorem' or 'unif_hint'
versoDocMissing.lean:10:0: error: unexpected end of input; expected '#guard_msgs', 'abbrev', 'add_decl_doc', 'axiom', 'binder_predicate', 'builtin_cbv_simproc', 'builtin_cbv_simproc_decl', 'builtin_dsimproc', 'builtin_dsimproc_decl', 'builtin_grind_propagator', 'builtin_initialize', 'builtin_simproc', 'builtin_simproc_decl', 'cbv_simproc', 'cbv_simproc_decl', 'class', 'coinductive', 'declare_simp_like_tactic', 'declare_syntax_cat', 'def', 'dsimproc', 'dsimproc_decl', 'elab', 'elab_rules', 'example', 'grind_propagator', 'inductive', 'infix', 'infixl', 'infixr', 'initialize', 'instance', 'macro', 'macro_rules', 'notation', 'opaque', 'postfix', 'prefix', 'recommended_spelling', 'register_error_explanation', 'register_tactic_tag', 'register_try?_tactic', 'simproc', 'simproc_decl', 'structure', 'syntax', 'tactic_extension', 'theorem' or 'unif_hint'