Faster proof encoding: congruence via :merge + single self-referential UF (~1.8x)#6
Faster proof encoding: congruence via :merge + single self-referential UF (~1.8x)#6oflatt-claude wants to merge 26 commits into
Conversation
|
Important Review skippedDraft detected. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Organization UI Review profile: ASSERTIVE Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
📝 WalkthroughWalkthroughThis PR adds tuple-output function support to egglog (multi-value columns, a ChangesTuple outputs and proof encoding rework
Estimated code review effort: 5 (Critical) | ~120 minutes Sequence Diagram(s)sequenceDiagram
participant Rule as EgglogRule
participant TableAction as TableAction
participant MergeFn as ResolvedMergeFn
participant Table as UF Table
Rule->>TableAction: insert/merge row for key
TableAction->>MergeFn: run(cur, new, n_keys, self_col, ts)
MergeFn->>Table: TableInsert/Construct staged write
MergeFn-->>TableAction: merged value column
TableAction->>Table: write_table_row
Suggested reviewers: 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Ports egraphs-good/egglog#938 ("Add tuple-output functions") into the vendored egglog/ subtree. A function may declare more than one output sort, stored as separate value columns (no boxing); outputs are destructured in queries, written in actions, and merged per column with a (values ...) clause. Restricted to plain functions and unsupported by the term/proof encoding. The upstream diff applied cleanly except for one context conflict in src/proofs/proof_encoding_helpers.rs (this fork carries an extra NaiveEqSortPrimitiveFact reason); the TupleOutputFunction variant and guard were reapplied by hand. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The workspace root now builds egglog-experimental alongside egglog, so it must handle the tuple-output additions from the #938 port: add `extra_outputs: vec![]` to the fresh! constructor Schema, and a `ResolvedCall::Values(_) => Context::Pure` arm (tuple construct/destructure reads/writes no tables). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
7f5ea8a to
3213931
Compare
|
Got this run : |
Fix a doubled word and grammar in the `function` command doc, drop the redundant sentence on `Schema::extra_outputs` (already covered by `Schema::outputs`), and trim the memory-representation aside from the CHANGELOG entry. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
7cb0ef8 to
61905dd
Compare
Merging this PR will improve performance by 39.1%
Performance Changes
Tip Curious why this is faster? Comment Comparing |
|
@coderabbitai full review |
✅ Action performedFull review finished. |
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (2)
egglog/tests/tuple_outputs.rs (1)
111-119: 🚀 Performance & Scalability | 🔵 TrivialConsider adding relation/view-table tuple-output rejection tests.
CHANGELOG documents that tuple outputs are disallowed for constructors, relations, and view tables, but only the constructor case (
constructor_tuple_output_rejected) is tested here. Adding parallel tests forrelationand view-table declarations would close the coverage gap for the documented restriction.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@egglog/tests/tuple_outputs.rs` around lines 111 - 119, Add test coverage for the tuple-output restriction beyond constructor_tuple_output_rejected by adding parallel rejection tests for relation and view-table declarations in tuple_outputs.rs. Reuse the existing EGraph::default().parse_and_run_program(None, ...) pattern and assert the same TypeError::TupleOutputNotAllowed result for the relation and view-table symbols so the documented restriction is covered consistently.egglog/egglog-bridge/src/lib.rs (1)
548-559: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick winConsider a hard
assert!for the reserved-table-id invariant.The knot-tying relies on
add_table_namedassigning exactly the id returned bynext_table_id(), and the merge callback is built against that reserved id. If this ever diverges (e.g. a future refactor adds a table between the reserve and the create), the self-referential merge would be wired to the wrong backing table and silently corrupt writes. Sincedebug_assert_eq!compiles out in release, the invariant is unchecked in production builds. A plainassert_eq!here is cheap insurance for a correctness-critical wiring step.♻️ Proposed change
- debug_assert_eq!(assigned_table_id, table_id); + assert_eq!( + assigned_table_id, table_id, + "reserved table id for self-referential merge diverged from assigned id" + );Also applies to: 572-578
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@egglog/egglog-bridge/src/lib.rs` around lines 548 - 559, The reserved-table-id check in the knot-tying flow is only a debug assertion, so the invariant is unchecked in release builds. Update the `add_table_named`/`FunctionInfo` wiring in `egglog-bridge/src/lib.rs` to use a hard assertion (`assert_eq!`) for the `next_table_id()` versus created function/table id match, including the same guarantee in the later `add_table_named` merge setup referenced by the comment. This keeps the self-referential merge callback bound to the reserved table id and prevents silent miswiring if the ids ever diverge.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@egglog/src/lib.rs`:
- Around line 1084-1090: The TSV input parsing for custom functions is still
using only func.schema.output, so tuple-output functions can be rejected or
built with the wrong row shape. Update the input type check and the row_schema
construction in input_file to use func.schema.outputs() instead of the single
output field, and make sure the row width matches the full backend schema
registered by the function config.
---
Nitpick comments:
In `@egglog/egglog-bridge/src/lib.rs`:
- Around line 548-559: The reserved-table-id check in the knot-tying flow is
only a debug assertion, so the invariant is unchecked in release builds. Update
the `add_table_named`/`FunctionInfo` wiring in `egglog-bridge/src/lib.rs` to use
a hard assertion (`assert_eq!`) for the `next_table_id()` versus created
function/table id match, including the same guarantee in the later
`add_table_named` merge setup referenced by the comment. This keeps the
self-referential merge callback bound to the reserved table id and prevents
silent miswiring if the ids ever diverge.
In `@egglog/tests/tuple_outputs.rs`:
- Around line 111-119: Add test coverage for the tuple-output restriction beyond
constructor_tuple_output_rejected by adding parallel rejection tests for
relation and view-table declarations in tuple_outputs.rs. Reuse the existing
EGraph::default().parse_and_run_program(None, ...) pattern and assert the same
TypeError::TupleOutputNotAllowed result for the relation and view-table symbols
so the documented restriction is covered consistently.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 929ef4c8-0ed1-46e4-b295-814acdee9c14
⛔ Files ignored due to path filters (3)
egglog/src/proofs/snapshots/egglog__proofs__proof_tests__tests__doc_example_add_function1.snapis excluded by!**/*.snapegglog/src/proofs/snapshots/egglog__proofs__proof_tests__tests__doc_example_add_function2.snapis excluded by!**/*.snapegglog/tests/snapshots/files__shared_snapshot_eqsat_basic_proof.snapis excluded by!**/*.snap
📒 Files selected for processing (26)
egglog-experimental/src/fresh_macro.rsegglog-experimental/src/primitive.rsegglog/CHANGELOG.mdegglog/core-relations/src/free_join/mod.rsegglog/egglog-bridge/src/lib.rsegglog/egglog-bridge/src/rule.rsegglog/src/ast/desugar.rsegglog/src/ast/mod.rsegglog/src/ast/parse.rsegglog/src/ast/proof_global_remover.rsegglog/src/ast/remove_globals.rsegglog/src/constraint.rsegglog/src/core.rsegglog/src/exec_state.rsegglog/src/extract.rsegglog/src/lib.rsegglog/src/prelude.rsegglog/src/proofs/proof_checker.rsegglog/src/proofs/proof_encoding.mdegglog/src/proofs/proof_encoding.rsegglog/src/proofs/proof_encoding_helpers.rsegglog/src/proofs/proof_extraction.rsegglog/src/serialize.rsegglog/src/typechecking.rsegglog/src/util.rsegglog/tests/tuple_outputs.rs
`input_file` built rows and validated types using only `schema.output`, so loading a tuple-output custom function from a TSV rejected valid rows (too many fields) and would have inserted too few columns. Use `schema.outputs()` for both the type check and the row schema. Adds a regression test. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…/Construct/IfEq)
Add a general "run arbitrary actions" capability to the bridge merge language,
following egglog PR #933: MergeFn::{TableInsert, Seq, Construct, IfEq} plus
TableAction::lookup_or_insert_multi, wired through fill_deps/resolve/run. These
let a function's :merge stage a row into another table, build a value-tuple
constructor term, sequence effects, and branch — enough to express term-encoding
congruence (union edge + proof composition) as a native merge instead of a rule.
Dormant: no encoder emits these yet. Workspace builds clean.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add EGraph::native_congruence_merge, which builds the term-encoding congruence merge for a proof-mode constructor view `(children) -> (eclass, proof)` using the general merge-action variants: on an FD conflict it keeps the old (eclass, proof) and stages `(@UF_S new_eclass old_eclass) = (Trans new_proof (Sym old_proof))` into the per-sort UF table — the exact edge/proof the rule-encoded congruence produces. declare_function calls it, falling back to the ordinary merge lowering. Inert until the encoder emits 2-output views (next commit): the guard requires a term-constructor view with two outputs, which nothing produces yet. Builds clean. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
In proof mode each constructor's view becomes a functional-dependency tuple `(children) -> (eclass, proof)` whose native :merge resolves congruence, instead of the rule-encoded self-join. On an FD conflict the merge stages the congruence edge `(@UF_S new old) = (Trans new_pf (Sym old_pf))` into the per-sort UF table (built from the general merge-action variants) and keeps the current row; the existing UF/path-compression/rebuild machinery is unchanged. The `@congruence_rule` is dropped for constructors — the rebuild's re-`set` at canonical children triggers the merge, subsuming it. Encoder (proof_encoding.rs): FD view declaration, `update_fd_view`/`query_fd_view`, FD-aware `add_term_and_view`, `rebuilding_rules_fd`, fact-reader destructuring, and FD delete/subsume rules. Backend: `native_congruence_merge` builds the merge in declare_function; serialization, extraction helpers, the delete-arity check, the typecheck tuple-output/0-input carve-outs, and multi-output `subsume` all updated to handle the tuple view. Verified: all `proofs`, `proof_testing`, `term_encoding`, and normal file treatments pass, plus the proof unit/extraction/regression suites. KNOWN LIMITATION: the 7 `desugar_proof_testing` round-trip tests fail — the merge is built from proof_state, so a dumped-and-re-run encoded program (fresh egraph, no proof_state) loses the Trans/Sym/UF bindings and congruence degrades. Fix is to carry those names in a view annotation so the program is self-contained. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Port PR #927's annotation mechanism so a desugared proof program re-parses self-contained. The Proof sort now carries :internal-proof-names <congr> <trans> <sym>; declaring it repopulates proof_state.proof_names, and native_congruence_merge is detected purely by view shape (term-constructor + eq-sort first output + a second proof output) rather than a live proof_state flag. Together with the existing :internal-uf -> uf_parent round-trip, a dumped encoded program rebuilds the congruence merge in a fresh egraph. Threads a proof_ctors field through the Sort command (parse/Display/desugar/ typecheck/declare), mirroring uf/proof_func. Fixes the 7 desugar_proof_testing failures. Full files suite: 747/747 across all treatments (normal, term_encoding, proofs, proof_testing, desugar). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
In the merge_simple fast path, pre-seed the merging table's own write-buffer so a :merge can stage a row back into the very table it is resolving. This is what a self-referential union-find merge needs (redirect the losing edge into @UF_S during @UF_S's own merge). Non-self-referential merges get an empty, unused buffer; the strata path already handled this via write-deps. Foundation for the single-table self-referential UF (no effect on its own). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add EGraph::peek_next_function_id and reorder add_table to reserve the table id and push the new FunctionInfo BEFORE building the merge callback. This lets a merge reference the very table it defines (e.g. the single-table UF whose :merge does a TableInsert into itself), since merge resolution reads self.funcs[self_id].table. Non-self-referential merges are unaffected — the new ordering is a strict superset. Foundation for the self-referential UF (inert). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Replace the two-table UF (@UF_S relation + @UF_Sf index) with one self-referential function @uf : (S) -> S per sort, on the non-proof term-encoding path (proof mode unchanged). - Union writes a single-key edge `(set (@uf (ordering-max a b)) (ordering-min a b))`. - @uf's :merge is native self-referential (build_uf_self_merge, from the committed Seq/TableInsert/IfEq actions + peek_next_function_id + core-relations self-write): keep min, and TableInsert the losing edge into its own table. - Keep only path_compress; drop single_parent, uf_function_index, the @UF_Sf index, the per-term self-loop seed, and their schedule steps. - Rebuild fans out to one rule per eq-sort view column (root columns don't fire, so no self-loop / default lookup / Pair needed). - Extraction find_canonical chases the single-key @uf to a fixpoint (miss = self). Validates the self-referential-merge mechanism end-to-end. Full files suite 747/747 across all treatments (normal, term_encoding, proofs, proof_testing, desugar); proof unit tests pass; make nits clean. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Replace the two-table proof UF (@UF_S (S S)->Proof relation + @UF_Sf (S)->Pair index) with one self-referential native-tuple function @uf : (S) -> (S, Proof) per sort, mirroring the term path. - Union writes `(set (@uf larger) (values smaller proof))`. - @uf's :merge is native, proof-composing self-referential (build_uf_self_merge proof branch): keep min eclass + its proof, and TableInsert the displaced edge `(@uf max) = (values min (Trans (Sym max_pf) min_pf))` into its own table. - The constructor view's congruence merge (native_congruence_merge) now orients max->min and stages `(@uf max) = (values min (Trans max_vp (Sym min_vp)))` into the single @uf (was an unoriented edge into the relation; orientation is required since there is no single_parent rule to normalize). - path_compress composes proofs `(Trans pb pc)`; single_parent, uf_function_index, the @UF_Sf pair index, self-loops, and their schedule steps are dropped. - Rebuild reads the single @uf via `(= (values leader proof) (@uf ci))`, fanned out per eq-sort column. Extraction chases the 1-key @uf (unchanged). - Sort re-registers its :internal-uf name in self_merge_uf_functions so a re-parsed desugared program reattaches the self-merge (self-contained). Full files suite 747/747 across all treatments (proofs/proof_testing run the proof checker); proof unit tests pass; make nits clean. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Update proof_encoding.md's Term Encoding section to match the current single self-referential UF (was still describing the old two-table UF_<Sort>/UF_<Sort>f design, single_parent/uf_function_index rulesets, and per-term self-loops). Rewrite the proof-tracking instrumented-rule example to the real FD-view / single-key UF shapes. Fix stale @UF_S comment references to @uf, and correct extract.rs's find_canonical comment to describe the two-key branch as a user-provided (child, parent) :internal-uf rather than a proof-mode @UF_S relation. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
61905dd to
ab88652
Compare
- Add `TypeError::CannotExtractTupleOutput`, raised in the `extract` typecheck, so `(extract (f ...))` on a tuple-output function gives a clear, actionable error instead of a confusing arity mismatch. - Correct the CHANGELOG wording on `:`-prefixed names: `:` is reserved as an identifier/definition name, but is still accepted in expression position so command macros can consume their own option markers. - Add regression tests for tuple outputs: extract rejection, zero-input tuples, delete, proof-mode rejection, and unnumbered old/new merge vars. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… merge - `ifeq_merge_selects_branch` exercises both branches of `MergeFn::IfEq`. - `self_referential_merge_union_find` builds a mini union-find whose merge writes back into its own table, covering `peek_next_function_id`, `MergeFn::TableInsert` into self, `Seq`, and the core-relations self-write buffer pre-seed in one test. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Proposal (not implemented) for a follow-up: express `:merge` with the rule action IR plus identity/payload columns and pure orientation primitives, so the bespoke `Seq`/`TableInsert`/`Construct`/`IfEq` merge DSL and `If` can be removed and merge typechecking becomes principled. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add `FunctionConfig.n_identity_vals`: how many leading value columns are identity (participate in FD-conflict detection); the rest are payload carried alongside. The merge closure now keeps the existing row (skips the merge) when a key collision leaves the identity columns unchanged. Inert for every current table (all declare `n_identity_vals == n_vals`); this is the foundation that lets the ported encoding merges drop their equality guard. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
When the identity columns are unchanged, keep the existing value columns (skip the per-column merges) but still flow through the subsume/changed/write logic, so a subsume-only update on a payload + subsumable function (the FD view) is still applied instead of being dropped. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
`FunctionConfig.n_identity_vals` is now `Option<usize>`: `None` (the default, used by every ordinary function) runs the merge on every collision as before, preserving non-idempotent merge semantics; `Some(k)` opts a function into skip-on-unchanged for its first `k` value columns. Only merges idempotent on equal inputs should opt in — which is what lets the encoding's `@UF`/view merges (and the term UF) drop their equality guard without changing any ordinary function's behavior. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The proof-mode `@UF`/view merges and the term UF no longer use a conditional
merge node:
- role 1 ("only act when the eclass/parent changed") is handled by opting the
encoding tables into the identity-column guard (`n_identity_vals = Some(1)`),
so the merge body runs only on a real conflict and stages unconditionally;
- role 2 (orient the composed proof) uses two pure `#`-polymorphic primitives
`proof-of-min`/`proof-of-max` that return the proof paired with the smaller /
larger value, resolved by name like `ordering-min`/`ordering-max`.
With no remaining users, `MergeFn::IfEq` / `ResolvedMergeFn::IfEq` are removed.
`Seq`/`TableInsert`/`Construct` still express the union-staging and proof
construction; converting those to the shared action IR is the remaining step.
Full release suite (incl. proof-checking treatments) passes with no snapshot
changes.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…path
`:merge` now accepts a value-producing action block: `:merge (<action>* <expr>)`
— zero or more actions (run first, with `old`/`new`, or `old0`/`new0`/... for
tuple outputs, bound) followed by the merged-value expression. A bare
`:merge <expr>` is the no-actions case, unchanged; the wrapped form is
disambiguated by its first element (a list ⇒ action block, an atom ⇒ ordinary
result expression), so every existing merge keeps parsing as before.
- New `GenericMerge { actions, result }` replaces `Option<GenericExpr>` as the
merge; threaded through visit/map/Display/aliases.
- Typechecking binds old/new and runs the block's actions through the SAME
action typechecker as rule bodies (`Context::Write`) — principled, not a
bespoke path — then typechecks the result.
- Lowering keeps the existing `MergeFn` interpreter: a result-only merge lowers
as before; actions are lowered and sequenced before the result. Only `set`
actions are supported for now (→ `TableInsert`); other actions error clearly,
pending the interpreter rewrite (step 3).
make test + make nits green, no snapshot changes.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The bespoke merge action DSL is gone. A `:merge` now lowers to a small,
action-block-shaped backend IR that mirrors the frontend `GenericMerge`:
- `MergeFn::Block { actions: Vec<MergeAction>, result }` (was `Seq`) — the
value-producing block: run the effects once, then evaluate the per-column
result. `to_callback` runs the actions once (skipped, with the old columns
kept, when the identity-column guard fires) then computes the columns.
- `MergeAction::Set(f, args)` (was `TableInsert`) — stage a row into `f`.
- `MergeFn::Lookup(f, args)` (was `Construct` with empty value_args) — a
constructor value expression (mint-on-miss), e.g. building a proof term.
Deps for the strata ordering are derived from the block (Set → write dep,
Lookup → read+write dep), preserving the read-dep behaviour. The encoding's
`@UF`/view merges (`build_uf_self_merge`, `native_congruence_merge`) now build
`Block { actions: [Set(@uf, ...)], result: Columns([...]) }` — exact semantics
preserved (full suite green, no snapshot changes, proofs bench flat).
`Let`/`Union` inside a `:merge` remain a clear error (only `set` actions are
lowered today; nothing exercises the others). The scalar value producers are
kept as the merge value language.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Whitespace-only. Fixes mis-indented n_vals/n_identity_vals in the egglog-bridge examples (left by earlier scripted edits; make nits runs from egglog/ and did not reach the nested example targets). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- `tests/tuple-output.egg`: a multi-output function (interval analysis, `(Math) -> (i64 i64)`), written/destructured with `(values ...)`. - `tests/merge-action-block.egg`: the `:merge (<action>* <expr>)` form — a merge that records a discarded value via a `set` action, then keeps the max. Both run off-only through the files.rs treatment harness: the proof encoding already rejects tuple outputs, and this also marks a `:merge` action block unsupported by the term/proof encoding (`ProofEncodingUnsupportedReason:: MergeActionBlock`) — it only instruments the merged value, so an action block would be silently proof-incomplete. `file_supports_proofs` then omits both files from the term/proofs treatments (and from the proof-support snapshot). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Pulled into #13 |
Stacked on #3 (tuple-output functions) — review/merge that first. Until #3 merges the diff includes its commits; the encoding work here is the last 8 commits.
Brings the term/proof encoding much closer to egglog-duckdb's converged design, making proofs substantially faster. Two coordinated changes:
:mergeinstead of a generated self-join rule.Result
Proofs on
tests/math-microbenchmark.egg((run 11), release):@rebuildingsearch:merge(2-table UF)≈1.83× faster than baseline. Output equivalent; full
tests/files.rssuite 747/747 across all treatments (theproofs/proof_testingtreatments run the proof checker, so proof validity is verified), proof unit tests pass,make nitsclean.How it works
:merge. A constructor's view is the FD tuple(children) -> (eclass, proof); on an FD conflict its:mergeorientsmax→minand stages the congruence edge (with a composedTrans/Symproof) into the sort's UF. The@congruence_ruleself-join is gone.@UF : (S) -> (S, Proof)(native(values …), one table per sort). Union writes a single-key edge; the UF's own:mergekeepsminandTableInserts the displaced edge back into itself, composing the transitivity proof. Single-parent is automatic (keyed by the node), sosingle_parent,uf_function_index, the@UF_Sfindex, and the per-term self-loops are all dropped — only a proof-composingpath_compressremains.(= (values leader proof) (@UF ci))). A canonical (root) column has no@UFrow, so its rule doesn't fire — which is exactly what lets us drop the self-loops, thePairindex, and any default-on-miss lookup (nothing needsfind(root)to hit).:internal-proof-names(ported from egglog #927) and:internal-ufre-register the proof constructors and the self-merge on a desugar re-parse.Engine additions (small, general — reused everywhere)
egglog-bridge: general merge-actionMergeFn::{Seq, TableInsert, Construct, IfEq}+lookup_or_insert_multi(following egglog #933);peek_next_function_id+ anadd_tablereorder so a:mergecan name its own table.core-relations: pre-seed a merging table's own write-buffer (~16 lines) so a:mergecan stage into its own table.No
DefaultVal::Identity, no backend-trait layer, no--native-uf, no dataflow backends.🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
Bug Fixes
valuesexpressions across parsing, type checking, execution, and proof flows.