This PR replaces the previous tactic configuration system with a
significantly more efficient one that supports custom configuration
syntaxes and processing. On a simple benchmark, configuration evaluation
takes 6.2% of the time it used to. The `declare_config_elab` command
generates a configuration elaborator that now directly constructs
configuration objects; previously it relied on `Meta.evalExpr'`, which
involved running a configuration through the full term elaboration,
compilation, and evaluation processes. The generated configuration
elaborators now also have the capability to do direct `Syntax`
evaluation in common cases, skipping term elaboration. Furthermore, the
elaborator accepts configurations more liberally: any user-defined
syntax that has the form of an `optConfig`-style configuration or
configuration item (including, e.g., `namedArgument`s) is accepted.
Import `Lean.Elab.ConfigEval` to use the system; see this module for
some documentation in addition to the docstrings in
`Lean.Elab.ConfigEval.Commands`. Furthermore, the `simp` tactic now also
has `(user.optionName := ...)` user configuration options, which can be
declared using a global `tactic.simp.user.optionName` option; use
`getUserConfigOption` and `withUserConfig` to access and set these in
metaprograms.
Other features:
- `declare_config_elab` creates a function that exposes an `init`
parameter for the configuration that will be modified. It also now has a
`where` clause, enabling defining custom handlers for specific keys.
- Elaborators can be given additional binders, to make parameterized
elaborators. This is used by `simp` and `grind ` to support multiple
default configurations with the correct expected type for `(config :=
...)` elaboration.
- The `EvalTerm` class supports direct evaluation of `Syntax`, skipping
term elaboration. The system will attempt to automatically derive this
class when generating the elaborator.
- In case `EvalTerm` does not recognize the term, then the syntax is
elaborated to an expression and an `EvalExpr` instance is applied to
evaluate the expression. The system will similarly automatically derive
these instances if possible.
- Automatic derivation is *transitive*. It is able to seek instances
through other instances; e.g. if it needs an `EvalTerm (List T)`
instance it will be able to reduce this to seeking an `EvalTerm T`
instance.
- The system is designed to be flexible, and the various components can
be combined to construct a configuration elaborator. There are also now
`declare_core_config_elab` and `declare_term_config_elab` for
conveniently generating elaborators for `CoreM` and `TermElabM`. The
difference is that the first takes an explicit flag for whether to log
exceptions, and the second uses the current `errToSorry` state.
**Warning:** if you use the `TermElabM` one from `TacticM`, it will be
unaware of the current `recover` state. The only differences between
these macros are the ways error recovery is handled per monad.
Other changes:
- `#reduce` tactic configuration now makes use of this system and has
more options
- The module `Lean.Elab.Tactic.ConfigSetter` is removed; the
`declare_config_elab`-family macros subsume its functionality.
- The module `Lean.Elab.Tactic.Config` is deprecated and will be
removed; migration notes appear in the module docs there. Import
`Lean.Elab.ConfigEval` instead.
- One of the mvcgen benchmarks got significantly slower, but it turned
out to be caused by the new tactic configuration elaboration no longer
resetting the MetaM caches. Adding an explicit `resetCache` into the
test driver fixed the benchmark.
### Notes for metaprogram authors
If you are using the module system, you just need a `meta import
Lean.Elab.ConfigEval` to use the macros, and it should serve as a
drop-in replacement to the previous system so long as
1. your configuration type is a `structure` with no parameters, indices,
or universes (only `Type` is supported);
2. default values are self-contained and not dependent on other fields;
and
3. all fields have types that are composed from `Option`, `List`,
`Array`, `String`, `DataValue`, and inductive types in `Type` with no
parameters or indices, whose fields are similarly composed.
The macros synthesize a self-contained configuration elaboration
procedure, analyzing the `EvalTerm` and `EvalExpr` instances that are
currently available or can be automatically derived. These are the
components of the system:
- `EvalTerm` instances provide `Term → TermElabM α` functions for
evaluation of raw syntax; these handle the common cases where an option
value is a identifier, application, or other simple expression. They are
responsible for adding TermInfo when info is enabled, to support hovers.
One can invoke derivation of a `EvalTerm T` instance with the
`ensure_eval_term_instance T` command (after `open scoped
Lean.Elab.ConfigEval`).
- `EvalExpr` instances provide `Expr → TermElabM α` functions for
evaluation of elaborated expressions; these handle cases where an option
value may require reduction to evaluate. Similarly, one can invoke
derivation of an `EvalExpr T` instance with the
`ensure_eval_expr_instance T` command. If needed, there's also
`derive_eval_expr_instance_using_meta_eval T` for creating a
`Meta.evalExpr'`-based evaluator.
- Functions like `ConfigEval.evalExprWithElab` compose `EvalTerm` and
`EvalExpr` instances into a single procedure that first attempts
`EvalTerm`, and, if that fails, applies the standard term elaborator and
then attempts `EvalExpr`. This way term elaboration can be skipped in
all but uncommon cases.
- Configuration item interpretation is through `ConfigEval.foldConfigM`,
which is a procedure with a lax specification for what counts as a
configuration item, calling the provided function on each recognized
configuration item. The idea is:
- Null nodes are lists of configurations
- One-argument nodes are considered to be wrappers like `optConfig` or
`configItem`
- Two-argument nodes of the form `("+"<|>"-") (atom<|>ident)` are
considered to be boolean options
- Five-argument nodes of the form `"(" (atom<|>ident) ":=" syntax ")"`
are considered to be general configuration items. (It only checks for
the presence of `(` and that there are two-to-five arguments.)
- Bare atoms are considered to be positive boolean options
- Configuration evaluation then uses `EvalConfigItem.set` on each item
produced by the fold, for an `EvalConfigItem` structure defined for the
given configuration type. The `def_eval_config_item` command can be used
to generate this structure. It analyzes which `EvalTerm` and `EvalExpr`
instances exist and derives missing ones, then builds an efficient
procedure to process configuration items and apply evaluators.
- Lastly, there are the `declare_core_config_elab`,
`declare_term_config_elab`, `declare_config_elab`, and
`declare_command_config_elab` macros for conveniently running the
`def_eval_config_item` command and constructing a self-contained
elaboration function.
The derivation procedures analyze which `EvalTerm`/`EvalExpr` instances
already exist and only derive the "leaf" instances that are necessary to
construct `EvalTerm` and `EvalExpr` instances. The derived instances are
made `private local` — since configuration elaborators are meant to be
self-contained, we decided not to let the additional instances be a side
effect of the macros. The instances can be globally added by manually
using the `ensure_*` commands.
The macros support making parameterized elaborators with arbitrary
additional binders. See `make_elab_grind_config` and
`make_elab_simp_config` in core Lean for examples of generating a single
elaborator that's used with multiple default value configurations.
To see how to create a key handler that matches all configuration keys
with a given prefix, see `make_elab_simp_config`.
There is a todo item at `Lean.Elab.ConfigEval.ReflectConfigItems` for
reflecting configurations back to syntax, which is not yet supported.
### Performance evaluation
A legacy configuration parser was temporarily added to
`Lean.Elab.Tactic.Grind.Config` using `declare_term_config_elab_legacy
elabGrindConfigLegacy Grind.Config`, and then this file was used for
measuring elaboration time:
```lean
import Lean
open Lean Elab Meta Tactic Parser
def cfgs : Array Syntax := Unhygienic.run do
return #[
← `(Tactic.optConfig| ),
← `(Tactic.optConfig| +clean),
← `(Tactic.optConfig| +trace +markInstances -lookahead -useSorry),
← `(Tactic.optConfig| (trace := true) (markInstances := true) (lookahead := false) (useSorry := false)),
← `(Tactic.optConfig| -trace (splits := 20) +revert (maxSuggestions := some 3) (ematch := 2)),
← `(Tactic.optConfig| (gen := 5) -reducible +splitImp -funCC),
← `(Tactic.optConfig| (config := { trace := true, lookahead := false, maxSuggestions := some 3 })),
]
def testGrindElab (cfgs : Array Syntax) (n : Nat) : TacticM Unit := do
profileitM Exception "test grind elab" (← getOptions) do
let mut ematch := 0
for _ in [0:n] do
for cfg in cfgs do
let c ← Tactic.elabGrindConfig cfg
ematch := ematch + c.ematch
logInfo m!"sum = {ematch}"
def testGrindElabLegacy (cfgs : Array Syntax) (n : Nat) : TacticM Unit := do
profileitM Exception "test grind elab legacy" (← getOptions) do
let mut ematch := 0
for _ in [0:n] do
for cfg in cfgs do
let c ← Tactic.elabGrindConfigLegacy cfg
ematch := ematch + c.ematch
logInfo m!"sum = {ematch}"
def runTest (info : Bool) (test : TacticM Unit) : TermElabM Unit := do
withEnableInfoTree info do
let mvar ← mkFreshExprMVar none
discard <| Tactic.run mvar.mvarId! test
set_option maxHeartbeats 0
set_option profiler true
set_option profiler.threshold 1
def iters : Nat := 1000
#eval runTest false <| testGrindElab cfgs iters
#eval runTest true <| testGrindElab cfgs iters
#eval runTest false <| testGrindElabLegacy cfgs iters
#eval runTest true <| testGrindElabLegacy cfgs iters
```
A representative output is
```
test grind elab took 315ms
test grind elab took 333ms
test grind elab legacy took 5.22s
test grind elab legacy took 5.33s
```
Computing `(315.0 + 333.0) / (5220 + 5330)` and rounding up to the
nearest tenth gives the 6.2% figure.
---
The #13426 draft PR includes some LSP modifications to support
completions for `simp` user configuration options.
440 lines
10 KiB
Text
440 lines
10 KiB
Text
import Lean
|
||
/-!
|
||
# Tests for tactic configuration elaboration
|
||
-/
|
||
|
||
open Lean
|
||
|
||
/-!
|
||
Simple tactic configuration
|
||
-/
|
||
structure MyTacticConfig where
|
||
x : Nat := 0
|
||
y : Bool := false
|
||
deriving Repr
|
||
|
||
declare_config_elab elabMyTacticConfig MyTacticConfig
|
||
|
||
elab "my_tactic" cfg:Parser.Tactic.optConfig : tactic => do
|
||
let config ← elabMyTacticConfig cfg
|
||
logInfo m!"config is {repr config}"
|
||
|
||
/--
|
||
info: config is { x := 0, y := false }
|
||
---
|
||
info: config is { x := 0, y := true }
|
||
---
|
||
info: config is { x := 1, y := false }
|
||
---
|
||
info: config is { x := 2, y := false }
|
||
---
|
||
info: config is { x := 1, y := true }
|
||
---
|
||
info: config is { x := 0, y := false }
|
||
-/
|
||
#guard_msgs in
|
||
example : True := by
|
||
my_tactic
|
||
my_tactic +y
|
||
my_tactic (x := 1)
|
||
my_tactic -y (x := 2)
|
||
my_tactic (config := {x := 1, y := true})
|
||
my_tactic +y (config := {y := false})
|
||
trivial
|
||
|
||
/-!
|
||
Basic errors
|
||
-/
|
||
|
||
/--
|
||
error: Option is not boolean-valued, so `(x := ...)` syntax must be used
|
||
---
|
||
info: config is { x := 0, y := false }
|
||
---
|
||
error: unsolved goals
|
||
⊢ True
|
||
-/
|
||
#guard_msgs in example : True := by my_tactic +x
|
||
|
||
/--
|
||
error: Invalid configuration option `w` for `MyTacticConfig`
|
||
---
|
||
info: config is { x := 0, y := false }
|
||
---
|
||
error: unsolved goals
|
||
⊢ True
|
||
-/
|
||
#guard_msgs in example : True := by my_tactic +w
|
||
|
||
/--
|
||
error: Invalid configuration option `x.a` for `MyTacticConfig`
|
||
---
|
||
info: config is { x := 0, y := false }
|
||
---
|
||
error: unsolved goals
|
||
⊢ True
|
||
-/
|
||
#guard_msgs in example : True := by my_tactic +x.a
|
||
|
||
/-!
|
||
A tactic configuration extending another with different default values.
|
||
-/
|
||
structure MyTacticConfig' extends MyTacticConfig where
|
||
x := 22
|
||
y := true
|
||
deriving Repr
|
||
|
||
declare_config_elab elabMyTacticConfig' MyTacticConfig'
|
||
|
||
elab "my_tactic'" cfg:Parser.Tactic.optConfig : tactic => do
|
||
let config ← elabMyTacticConfig' cfg
|
||
logInfo m!"config is {repr config}"
|
||
|
||
/--
|
||
info: config is { toMyTacticConfig := { x := 22, y := true } }
|
||
---
|
||
info: config is { toMyTacticConfig := { x := 22, y := true } }
|
||
---
|
||
info: config is { toMyTacticConfig := { x := 1, y := true } }
|
||
---
|
||
info: config is { toMyTacticConfig := { x := 2, y := false } }
|
||
---
|
||
info: config is { toMyTacticConfig := { x := 2, y := false } }
|
||
---
|
||
info: config is { toMyTacticConfig := { x := 1, y := true } }
|
||
---
|
||
info: config is { toMyTacticConfig := { x := 22, y := false } }
|
||
-/
|
||
#guard_msgs in
|
||
example : True := by
|
||
my_tactic'
|
||
my_tactic' +y
|
||
my_tactic' (x := 1)
|
||
my_tactic' -y (x := 2)
|
||
my_tactic' (x := 2) -y
|
||
my_tactic' (config := {x := 1, y := true})
|
||
my_tactic' +y (config := {y := false})
|
||
trivial
|
||
|
||
/-!
|
||
Evaluation failure
|
||
-/
|
||
opaque fooNat : Nat := 22
|
||
/--
|
||
error: Could not evaluate the expression:
|
||
fooNat
|
||
of type `Nat`.
|
||
---
|
||
info: config is { x := 0, y := true }
|
||
-/
|
||
#guard_msgs in
|
||
example : True := by
|
||
my_tactic (x := fooNat) +y
|
||
trivial
|
||
|
||
/-!
|
||
Tactic configurations with hierarchical fields.
|
||
The `toA` parent projections are not made available for use.
|
||
-/
|
||
|
||
structure A where
|
||
x : Bool := true
|
||
deriving Repr
|
||
structure B extends A
|
||
deriving Repr
|
||
structure C where
|
||
b : B := {}
|
||
deriving Repr
|
||
declare_config_elab elabC C
|
||
|
||
elab "ctac" cfg:Parser.Tactic.optConfig : tactic => do
|
||
let config ← elabC cfg
|
||
logInfo m!"config is {repr config}"
|
||
|
||
/-- info: config is { b := { toA := { x := false } } } -/
|
||
#guard_msgs in
|
||
example : True := by
|
||
ctac -b.x
|
||
trivial
|
||
|
||
/--
|
||
error: Invalid configuration option `b.toA.x` for `C`
|
||
---
|
||
info: config is { b := { toA := { x := true } } }
|
||
-/
|
||
#guard_msgs in
|
||
example : True := by
|
||
ctac -b.toA.x
|
||
trivial
|
||
|
||
/-!
|
||
Responds to recovery mode. In these, `ctac` continues even though configuration elaboration failed.
|
||
-/
|
||
|
||
/--
|
||
error: Invalid configuration option `x` for `C`
|
||
---
|
||
info: config is { b := { toA := { x := true } } }
|
||
---
|
||
trace: ⊢ True
|
||
-/
|
||
#guard_msgs in
|
||
example : True := by
|
||
ctac -x
|
||
trace_state
|
||
trivial
|
||
|
||
-- Check that when recovery mode is false, no error is reported, since there was an exception.
|
||
/-- trace: ⊢ True -/
|
||
#guard_msgs in
|
||
example : True := by
|
||
fail_if_success ctac -x
|
||
trace_state
|
||
trivial
|
||
|
||
/--
|
||
error: Invalid configuration option `x` for `C`
|
||
---
|
||
info: config is { b := { toA := { x := true } } }
|
||
---
|
||
error: unsolved goals
|
||
⊢ True
|
||
-/
|
||
#guard_msgs in
|
||
example : True := by
|
||
ctac -x
|
||
done
|
||
|
||
/-!
|
||
Responds to recovery mode. In this, `ctac` fails, doesn't report anything, and then execution continues to `exact`.
|
||
-/
|
||
|
||
/-- error: Unknown identifier `blah` -/
|
||
#guard_msgs in
|
||
example : True := by
|
||
first | ctac +x | exact blah
|
||
|
||
/-!
|
||
Elaboration errors cause the tactic to use the default configuration.
|
||
-/
|
||
|
||
/--
|
||
error: Type mismatch
|
||
"oops"
|
||
has type
|
||
String
|
||
but is expected to have type
|
||
Bool
|
||
---
|
||
info: config is { b := { toA := { x := true } } }
|
||
---
|
||
error: unsolved goals
|
||
⊢ True
|
||
-/
|
||
#guard_msgs in
|
||
example : True := by
|
||
ctac (b.x := "oops")
|
||
done
|
||
|
||
|
||
/-!
|
||
Elaboration for command configuration
|
||
-/
|
||
|
||
structure MyCommandConfig where
|
||
x : Nat := 0
|
||
y : Bool := false
|
||
deriving Repr
|
||
|
||
declare_command_config_elab elabMyCommandConfig MyCommandConfig
|
||
|
||
elab "my_command" cfg:Parser.Tactic.optConfig : command => do
|
||
let config ← elabMyCommandConfig cfg
|
||
logInfo m!"config is {repr config}"
|
||
|
||
/-- info: config is { x := 0, y := false } -/
|
||
#guard_msgs in my_command
|
||
/-- info: config is { x := 0, y := true } -/
|
||
#guard_msgs in my_command +y
|
||
/-- info: config is { x := 1, y := true } -/
|
||
#guard_msgs in my_command (x := 1) (y := true)
|
||
/-- info: config is { x := 0, y := false } -/
|
||
#guard_msgs in my_command (x := 1) (y := true) (config := {})
|
||
/--
|
||
error: Type mismatch
|
||
true
|
||
has type
|
||
Bool
|
||
but is expected to have type
|
||
Nat
|
||
---
|
||
info: config is { x := 0, y := false }
|
||
-/
|
||
#guard_msgs in my_command (x := true)
|
||
|
||
/-!
|
||
Testing `Occurrences.pos`
|
||
-/
|
||
/--
|
||
trace: a : Nat
|
||
this : a = 0 + a
|
||
⊢ 0 + a = 0 + a
|
||
-/
|
||
#guard_msgs in
|
||
example (a : Nat) : a = 0 + a := by
|
||
have : a = 0 + a := by rw [Nat.zero_add]
|
||
rewrite (occs := .pos [1]) [this]
|
||
trace_state
|
||
rfl
|
||
/--
|
||
trace: a : Nat
|
||
this : a = 0 + a
|
||
⊢ 0 + a = 0 + a
|
||
-/
|
||
#guard_msgs in
|
||
example (a : Nat) : a = 0 + a := by
|
||
have : a = 0 + a := by rw [Nat.zero_add]
|
||
rewrite (occs := [1]) [this]
|
||
trace_state
|
||
rfl
|
||
|
||
/-!
|
||
Pretty printing of configuration, checking whitespace is present.
|
||
-/
|
||
elab "#pp_tac " t:tactic : command => Elab.Command.liftTermElabM do
|
||
logInfo (← PrettyPrinter.ppTactic t)
|
||
|
||
/-- info: simp +contextual -/
|
||
#guard_msgs in #pp_tac simp +contextual
|
||
/-- info: simp +contextual -/
|
||
#guard_msgs in #pp_tac simp+contextual
|
||
/-- info: simp (contextual := true) +zeta -/
|
||
#guard_msgs in #pp_tac simp (contextual := true) +zeta
|
||
/-- info: simp (contextual := true) +zeta -/
|
||
#guard_msgs in #pp_tac simp(contextual := true)+zeta
|
||
|
||
|
||
/-!
|
||
Simp user configuration.
|
||
-/
|
||
|
||
open Meta.Simp Elab.Tactic in
|
||
simproc testUserConfig (_) := fun _ => do
|
||
let v1 ← getUserConfigOption tactic.simp.user.exampleBool
|
||
let v2 ← getUserConfigOption tactic.simp.user.exampleNat
|
||
let v3 ← getUserConfigOption tactic.simp.user.exampleInt
|
||
let v4 ← getUserConfigOption tactic.simp.user.exampleString
|
||
logInfo m!"exampleBool: {v1} exampleNat: {v2} exampleInt: {v3} exampleString: {repr v4}"
|
||
return .continue
|
||
|
||
/--
|
||
info: exampleBool: false exampleNat: 0 exampleInt: 0 exampleString: ""
|
||
---
|
||
info: exampleBool: true exampleNat: 0 exampleInt: 0 exampleString: ""
|
||
---
|
||
info: exampleBool: false exampleNat: 22 exampleInt: 0 exampleString: ""
|
||
---
|
||
info: exampleBool: false exampleNat: 0 exampleInt: -22 exampleString: ""
|
||
---
|
||
info: exampleBool: false exampleNat: 0 exampleInt: 0 exampleString: "hi"
|
||
---
|
||
info: exampleBool: true exampleNat: 22 exampleInt: -22 exampleString: "hi"
|
||
---
|
||
error: User options are of the form `user.optionName`
|
||
---
|
||
info: exampleBool: false exampleNat: 0 exampleInt: 0 exampleString: ""
|
||
-/
|
||
#guard_msgs in
|
||
example (h : False) : False := by
|
||
simp -failIfUnchanged
|
||
simp -failIfUnchanged +user.exampleBool
|
||
simp -failIfUnchanged (user.exampleNat := 22)
|
||
simp -failIfUnchanged (user.exampleInt := -22)
|
||
simp -failIfUnchanged (user.exampleString := "hi")
|
||
simp -failIfUnchanged +user.exampleBool (user.exampleNat := 22) (user.exampleInt := -22) (user.exampleString := "hi")
|
||
simp -failIfUnchanged +user
|
||
exact h
|
||
|
||
/-!
|
||
Testing the `derive_eval_expr_instance_using_meta_eval` instance.
|
||
-/
|
||
section
|
||
open Lean.Elab.ConfigEval
|
||
|
||
structure MetaEvalTest where
|
||
x : Nat
|
||
b : Bool
|
||
f : Nat → Nat
|
||
|
||
derive_eval_expr_instance_using_meta_eval MetaEvalTest
|
||
|
||
/-- info: x: 3, b: true, f 10: 12, f 100: 102 -/
|
||
#guard_msgs in
|
||
#eval do
|
||
let stx ← `({ x := 3, b := true, f := (·+2) })
|
||
let c ← evalExprWithElab (α := MetaEvalTest) stx
|
||
logInfo m!"x: {c.x}, b: {c.b}, f 10: {c.f 10}, f 100: {c.f 100}"
|
||
|
||
/-!
|
||
Testing bare atoms for positive options
|
||
-/
|
||
structure TestBareConfig where
|
||
only : Bool := false
|
||
x : Nat := 0
|
||
deriving Repr
|
||
syntax testBareConfigOnly := &"only"
|
||
syntax testBareConfigCfg := many(testBareConfigOnly <|> Parser.Term.configItem)
|
||
|
||
declare_command_config_elab elabTestBareConfig TestBareConfig
|
||
|
||
elab "#test_bare_config" cfg:testBareConfigCfg : command => do
|
||
let config ← elabTestBareConfig cfg
|
||
logInfo m!"config is {repr config}"
|
||
|
||
/-- info: config is { only := false, x := 0 } -/
|
||
#guard_msgs in #test_bare_config
|
||
/-- info: config is { only := true, x := 0 } -/
|
||
#guard_msgs in #test_bare_config only
|
||
/-- info: config is { only := true, x := 0 } -/
|
||
#guard_msgs in #test_bare_config +only
|
||
/-- info: config is { only := true, x := 0 } -/
|
||
#guard_msgs in #test_bare_config (only := true)
|
||
/-- info: config is { only := true, x := 2 } -/
|
||
#guard_msgs in #test_bare_config (x := 2) only
|
||
/-- info: config is { only := true, x := 2 } -/
|
||
#guard_msgs in #test_bare_config only (x := 2)
|
||
|
||
/-!
|
||
Testing auto-derivations
|
||
-/
|
||
namespace AutoDeriveTest
|
||
|
||
structure A where
|
||
n : Nat
|
||
|
||
inductive B where
|
||
| ctor1
|
||
| ctor2 (a : Option A)
|
||
|
||
structure C where
|
||
opt1 : List A
|
||
opt2 : Option (Array B)
|
||
|
||
open scoped Lean.Elab.ConfigEval
|
||
|
||
ensure_eval_term_expr_instances C
|
||
|
||
/-- info: instEvalTermA -/
|
||
#guard_msgs in #synth EvalTerm A
|
||
/-- info: instEvalTermB -/
|
||
#guard_msgs in #synth EvalTerm B
|
||
/-- info: instEvalTermC -/
|
||
#guard_msgs in #synth EvalTerm C
|
||
/-- info: instEvalExprA -/
|
||
#guard_msgs in #synth EvalExpr A
|
||
/-- info: instEvalExprB -/
|
||
#guard_msgs in #synth EvalExpr B
|
||
/-- info: instEvalExprC -/
|
||
#guard_msgs in #synth EvalExpr C
|
||
|
||
end AutoDeriveTest
|