Proof support for containers #927
Conversation
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #927 +/- ##
==========================================
- Coverage 86.86% 86.71% -0.16%
==========================================
Files 91 92 +1
Lines 27305 28609 +1304
==========================================
+ Hits 23718 24807 +1089
- Misses 3587 3802 +215 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
Merging this PR will not alter performance
Performance Changes
Comparing Footnotes
|
There was a problem hiding this comment.
Pull request overview
Adds end-to-end proof/term-encoding support for built-in container sorts (Vec/Set/Map/MultiSet/Pair) by introducing deterministic container term normalization and container-aware rebuild/proof generation, enabling previously-unsupported programs to participate in proof production and checking.
Changes:
- Add deterministic AST-structural ordering (
TermDag::ast_cmp) and shared container canonicalization (normalize_container_term) used by reconstruction, validators, and proof checking. - Extend proof encoding to rebuild container columns (including nested containers) and introduce a
ContainerNormalizeproof justification to bridge reorder/merge canonicalization steps. - Add new container rebuild tests and proof snapshots; update unsupported-proof-file snapshot list and document the feature in the changelog/docs.
Reviewed changes
Copilot reviewed 25 out of 25 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/snapshots/files__shared_snapshot_container_set_collapse.snap | New snapshot covering a proof involving collapsing set normalization. |
| tests/snapshots/files__proof_unsupported_files.snap | Updates list of files considered unsupported in proof mode (now fewer). |
| tests/proofs/container-set-collapse.egg | New proof-focused .egg program exercising collapsing set rebuild/normalization. |
| tests/container_rebuild.rs | New end-to-end Rust tests for container rebuild behavior in term-only and proof modes. |
| src/typechecking.rs | Registers container rebuild primitives from serialized sort annotations during typechecking. |
| src/termdag.rs | Adds ast_cmp and normalize_container_term (set/multiset/map canonicalization) plus tests. |
| src/sort/vec.rs | Adds proof validators for vec term forms (vec-of/vec-empty). |
| src/sort/set.rs | Adds proof validators and canonical reconstruction for sets via shared normalization. |
| src/sort/pair.rs | Adds proof validators for pair/pair-first/pair-second. |
| src/sort/multiset.rs | Adds proof validators and canonical reconstruction for multisets via shared normalization. |
| src/sort/mod.rs | Adds Sort::rebuild_container_with_leaders hook for value-level container rebuilds. |
| src/sort/map.rs | Introduces canonical flat (map-of ...) form, validator support, and custom typing for map-of. |
| src/proofs/snapshots/egglog__proofs__proof_tests__tests__doc_example_add_function1.snap | Snapshot update reflecting new UF variable naming in encoded rules. |
| src/proofs/proof_simplification.rs | Adds simplification to drop redundant ContainerNormalize and updates term mapping logic. |
| src/proofs/proof_format.rs | Adds ContainerNormalize to proof format parsing/printing and conversion pipeline. |
| src/proofs/proof_encoding.rs | Implements container rebuild primitives/spec serialization and container-aware rebuild rules in encoding. |
| src/proofs/proof_encoding.md | Documents container participation in term/proof encoding and normalization mechanism. |
| src/proofs/proof_encoding_helpers.rs | Extends proof header and proof-encoding support checks to include built-in container presorts. |
| src/proofs/proof_checker.rs | Adds checker support and a dedicated error for ContainerNormalize proof verification. |
| src/prelude.rs | Implements generic container rebuild via ContainerValues::rebuild_val_with for container sorts. |
| src/ast/parse.rs | Parses :internal-container-rebuild option on container sort declarations. |
| src/ast/mod.rs | Threads container_rebuild through AST commands and ensures it round-trips in formatting. |
| src/ast/desugar.rs | Propagates container_rebuild field through desugaring paths. |
| core-relations/src/containers/mod.rs | Adds ContainerValues::rebuild_val_with and a closure-based rebuilder helper. |
| CHANGELOG.md | Documents new container support in term/proof encoding and associated user-visible extraction changes. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Follow-up cleanups for "Proof support for containers", addressing review
comments from saulshanabrook, Copilot, and oflatt:
- pair-first/pair-second validators: guard argument arity with `let [pair] =
args else { return None }` before indexing, matching the other validators
(Copilot).
- map-of: reject odd-arity argument lists in the validator, and leave
malformed map terms unchanged during normalization instead of silently
dropping the unpaired key/value (Copilot).
- ast_cmp: document why the Lit/Var arms are spelled out rather than
delegating to `l.cmp(r)` -- Term intentionally has no derived Ord, and a
derived Ord on App would order children by insertion order, which is the
thing ast_cmp exists to avoid (saulshanabrook).
- typechecking: `use` register_container_rebuild_from_spec instead of calling
it via an absolute crate path (oflatt).
- termdag: replace the hard-coded head-name match in normalize_container_term
with a ContainerNormalization strategy + a single
builtin_container_normalization(head) source of truth, and extract a
reusable `sort_terms_by_ast` helper that custom container sorts can call
after e.g. their own dedup (oflatt/saulshanabrook).
- proof_encoding: build and parse the :internal-container-rebuild spec as an
egglog s-expression via the existing parser (SexpParser / Sexp::expect_*)
instead of pushing and re-splitting space-separated tokens (oflatt).
- core-relations: split Rebuilder into a value-level ValueRebuilder supertrait
plus the table-level Rebuilder. ContainerValue::rebuild_contents now takes
&dyn ValueRebuilder, so ClosureRebuilder (the single-container rebuilder)
drops its two unreachable!() table-level methods entirely (oflatt).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Follow-up cleanups for "Proof support for containers", addressing review
comments from saulshanabrook, Copilot, and oflatt:
- pair-first/pair-second validators: guard argument arity with `let [pair] =
args else { return None }` before indexing, matching the other validators
(Copilot).
- map-of: reject odd-arity argument lists in the validator, and leave
malformed map terms unchanged during normalization instead of silently
dropping the unpaired key/value (Copilot).
- ast_cmp: document why the Lit/Var arms are spelled out rather than
delegating to `l.cmp(r)` -- Term intentionally has no derived Ord, and a
derived Ord on App would order children by insertion order, which is the
thing ast_cmp exists to avoid (saulshanabrook).
- typechecking: `use` register_container_rebuild_from_spec instead of calling
it via an absolute crate path (oflatt).
- termdag: replace the hard-coded head-name match in normalize_container_term
with a ContainerNormalization strategy + a single
builtin_container_normalization(head) source of truth, and extract a
reusable `sort_terms_by_ast` helper that custom container sorts can call
after e.g. their own dedup (oflatt/saulshanabrook).
- proof_encoding: build and parse the :internal-container-rebuild spec as an
egglog s-expression via the existing parser (SexpParser / Sexp::expect_*)
instead of pushing and re-splitting space-separated tokens (oflatt).
- core-relations: split Rebuilder into a value-level ValueRebuilder supertrait
plus the table-level Rebuilder. ContainerValue::rebuild_contents now takes
&dyn ValueRebuilder, so ClosureRebuilder (the single-container rebuilder)
drops its two unreachable!() table-level methods entirely (oflatt).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Follow-up cleanups for "Proof support for containers", addressing review
comments from saulshanabrook, Copilot, and oflatt:
- pair-first/pair-second validators: guard argument arity with `let [pair] =
args else { return None }` before indexing, matching the other validators
(Copilot).
- map-of: reject odd-arity argument lists in the validator, and leave
malformed map terms unchanged during normalization instead of silently
dropping the unpaired key/value (Copilot).
- ast_cmp: document why the Lit/Var arms are spelled out rather than
delegating to `l.cmp(r)` -- Term intentionally has no derived Ord, and a
derived Ord on App would order children by insertion order, which is the
thing ast_cmp exists to avoid (saulshanabrook).
- typechecking: `use` register_container_rebuild_from_spec instead of calling
it via an absolute crate path (oflatt).
- normalization is now per-container, with no "built-in container" special
casing: TermDag carries no container knowledge (only the generic `ast_cmp`
and a reusable `sort_terms_by_ast` helper). Each container sort normalizes
its own term form in its constructor validator (set: sort+dedup; multiset:
sort; map: collapse to flat `map-of`), exactly as a user-defined container
would. The proof checker recomputes a container's canonical form by applying
the validator registered for the term's head (collected from the e-graph's
primitives), so built-in and user containers are handled identically
(oflatt/saulshanabrook).
- proof_encoding: build and parse the :internal-container-rebuild spec as an
egglog s-expression via the existing parser (SexpParser / Sexp::expect_*)
instead of pushing and re-splitting space-separated tokens (oflatt).
- core-relations: split Rebuilder into a value-level ValueRebuilder supertrait
plus the table-level Rebuilder. ContainerValue::rebuild_contents now takes
&dyn ValueRebuilder, so ClosureRebuilder (the single-container rebuilder)
drops its two unreachable!() table-level methods entirely (oflatt).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Follow-up cleanups for "Proof support for containers", addressing review
comments from saulshanabrook, Copilot, and oflatt:
- pair-first/pair-second validators: guard argument arity with `let [pair] =
args else { return None }` before indexing, matching the other validators
(Copilot).
- map-of: reject odd-arity argument lists in the validator, and leave
malformed map terms unchanged during normalization instead of silently
dropping the unpaired key/value (Copilot).
- ast_cmp: document why the Lit/Var arms are spelled out rather than
delegating to `l.cmp(r)` -- Term intentionally has no derived Ord, and a
derived Ord on App would order children by insertion order, which is the
thing ast_cmp exists to avoid (saulshanabrook).
- typechecking: `use` register_container_rebuild_from_spec instead of calling
it via an absolute crate path (oflatt).
- normalization is now per-container, with no "built-in container" special
casing: TermDag carries no container knowledge (only the generic `ast_cmp`
and a reusable `sort_terms_by_ast` helper). Each container sort normalizes
its own term form in its constructor validator (set: sort+dedup; multiset:
sort; map: collapse to flat `map-of`), exactly as a user-defined container
would. The proof checker recomputes a container's canonical form by applying
the validator registered for the term's head (collected from the e-graph's
primitives), so built-in and user containers are handled identically
(oflatt/saulshanabrook).
- proof_encoding: build and parse the :internal-container-rebuild spec as an
egglog s-expression via the existing parser (SexpParser / Sexp::expect_*)
instead of pushing and re-splitting space-separated tokens (oflatt).
- move the container-rebuild primitives + their runtime + the spec decoder out
of proof_encoding.rs (2027 -> 1674 lines) into a new
proofs/proof_container_rebuild.rs; proof_encoding.rs keeps only the
encoder-side spec build. Pure relocation, no behavior change.
- core-relations: split Rebuilder into a value-level ValueRebuilder supertrait
plus the table-level Rebuilder. ContainerValue::rebuild_contents now takes
&dyn ValueRebuilder, so ClosureRebuilder (the single-container rebuilder)
drops its two unreachable!() table-level methods entirely (oflatt).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Follow-up cleanups for "Proof support for containers", addressing review
comments from saulshanabrook, Copilot, and oflatt:
- pair-first/pair-second validators: guard argument arity with `let [pair] =
args else { return None }` before indexing, matching the other validators
(Copilot).
- map-of: reject odd-arity argument lists in the validator, and leave
malformed map terms unchanged during normalization instead of silently
dropping the unpaired key/value (Copilot).
- ast_cmp: document why the Lit/Var arms are spelled out rather than
delegating to `l.cmp(r)` -- Term intentionally has no derived Ord, and a
derived Ord on App would order children by insertion order, which is the
thing ast_cmp exists to avoid (saulshanabrook).
- typechecking: `use` register_container_rebuild_from_spec instead of calling
it via an absolute crate path (oflatt).
- normalization is now per-container, with no "built-in container" special
casing: TermDag carries no container knowledge (only the generic `ast_cmp`
and a reusable `sort_terms_by_ast` helper). Each container sort normalizes
its own term form in its constructor validator (set: sort+dedup; multiset:
sort; map: collapse to flat `map-of`), exactly as a user-defined container
would. The proof checker recomputes a container's canonical form by applying
the validator registered for the term's head (collected from the e-graph's
primitives), so built-in and user containers are handled identically
(oflatt/saulshanabrook).
- proof_encoding: build and parse the :internal-container-rebuild spec as an
egglog s-expression via the existing parser (SexpParser / Sexp::expect_*)
instead of pushing and re-splitting space-separated tokens (oflatt).
- move the container-rebuild primitives + their runtime + the spec decoder out
of proof_encoding.rs (2027 -> 1674 lines) into a new
proofs/proof_container_rebuild.rs; proof_encoding.rs keeps only the
encoder-side spec build. Pure relocation, no behavior change.
- core-relations: split Rebuilder into a value-level ValueRebuilder supertrait
plus the table-level Rebuilder. ContainerValue::rebuild_contents now takes
&dyn ValueRebuilder, so ClosureRebuilder (the single-container rebuilder)
drops its two unreachable!() table-level methods entirely (oflatt).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Follow-up cleanups for "Proof support for containers", addressing review
comments from saulshanabrook, Copilot, and oflatt:
- pair-first/pair-second validators: guard argument arity with `let [pair] =
args else { return None }` before indexing, matching the other validators
(Copilot).
- map-of: reject odd-arity argument lists in the validator, and leave
malformed map terms unchanged during normalization instead of silently
dropping the unpaired key/value (Copilot).
- ast_cmp: document why the Lit/Var arms are spelled out rather than
delegating to `l.cmp(r)` -- Term intentionally has no derived Ord, and a
derived Ord on App would order children by insertion order, which is the
thing ast_cmp exists to avoid (saulshanabrook).
- typechecking: `use` register_container_rebuild_from_spec instead of calling
it via an absolute crate path (oflatt).
- normalization is now per-container, with no "built-in container" special
casing: TermDag carries no container knowledge (only the generic `ast_cmp`
and a reusable `sort_terms_by_ast` helper). Each container sort normalizes
its own term form in its constructor validator (set: sort+dedup; multiset:
sort; map: collapse to flat `map-of`), exactly as a user-defined container
would. The proof checker recomputes a container's canonical form by applying
the validator registered for the term's head (collected from the e-graph's
primitives), so built-in and user containers are handled identically
(oflatt/saulshanabrook).
- :internal-container-rebuild is a typed AST field (ast::ContainerRebuildSpec)
rather than an opaque string: the egglog parser parses it once into the struct
(ast/parse.rs) and Display serializes it, so register_container_rebuild_from_spec
just reads the struct — no hand-rolled tokenizing or re-parsing in the proof
pass; build constructs the struct directly (oflatt).
- move the container-rebuild primitives + their runtime + the spec decoder out
of proof_encoding.rs (2027 -> 1674 lines) into a new
proofs/proof_container_rebuild.rs; proof_encoding.rs keeps only the
encoder-side spec build. Pure relocation, no behavior change.
- core-relations: split Rebuilder into a value-level ValueRebuilder supertrait
plus the table-level Rebuilder. ContainerValue::rebuild_contents now takes
&dyn ValueRebuilder, so ClosureRebuilder (the single-container rebuilder)
drops its two unreachable!() table-level methods entirely (oflatt).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Follow-up cleanups for "Proof support for containers", addressing review
comments from saulshanabrook, Copilot, and oflatt:
- pair-first/pair-second validators: guard argument arity with `let [pair] =
args else { return None }` before indexing, matching the other validators
(Copilot).
- map-of: reject odd-arity argument lists in the validator, and leave
malformed map terms unchanged during normalization instead of silently
dropping the unpaired key/value (Copilot).
- ast_cmp: document why the Lit/Var arms are spelled out rather than
delegating to `l.cmp(r)` -- Term intentionally has no derived Ord, and a
derived Ord on App would order children by insertion order, which is the
thing ast_cmp exists to avoid (saulshanabrook).
- typechecking: `use` register_container_rebuild_from_spec instead of calling
it via an absolute crate path (oflatt).
- normalization is now per-container, with no "built-in container" special
casing: TermDag carries no container knowledge (only the generic `ast_cmp`
and a reusable `sort_terms_by_ast` helper). Each container sort normalizes
its own term form in its constructor validator (set: sort+dedup; multiset:
sort; map: collapse to flat `map-of`), exactly as a user-defined container
would. The proof checker recomputes a container's canonical form by applying
the validator registered for the term's head (collected from the e-graph's
primitives), so built-in and user containers are handled identically
(oflatt/saulshanabrook).
- :internal-container-rebuild is a typed AST field (ast::ContainerRebuildSpec)
rather than an opaque string: the egglog parser parses it once into the struct
(ast/parse.rs) and Display serializes it, so register_container_rebuild_from_spec
just reads the struct — no hand-rolled tokenizing or re-parsing in the proof
pass; build constructs the struct directly (oflatt).
- trim the spec to the minimum (oflatt): the per-element UF index names are
dropped from every container's spec and recovered at register time from each
element sort's :internal-uf (now `:internal-uf <constructor> [<index>]`,
repopulating proof_state on re-parse). value_prim renamed internal_rebuild_prim.
- move the container-rebuild primitives + their runtime + the spec decoder out
of proof_encoding.rs (2027 -> 1674 lines) into a new
proofs/proof_container_rebuild.rs; proof_encoding.rs keeps only the
encoder-side spec build. Pure relocation, no behavior change.
- core-relations: split Rebuilder into a value-level ValueRebuilder supertrait
plus the table-level Rebuilder. ContainerValue::rebuild_contents now takes
&dyn ValueRebuilder, so ClosureRebuilder (the single-container rebuilder)
drops its two unreachable!() table-level methods entirely (oflatt).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Follow-up cleanups for "Proof support for containers", addressing review
comments from saulshanabrook, Copilot, and oflatt:
- pair-first/pair-second validators: guard argument arity with `let [pair] =
args else { return None }` before indexing, matching the other validators
(Copilot).
- map-of: reject odd-arity argument lists in the validator, and leave
malformed map terms unchanged during normalization instead of silently
dropping the unpaired key/value (Copilot).
- ast_cmp: document why the Lit/Var arms are spelled out rather than
delegating to `l.cmp(r)` -- Term intentionally has no derived Ord, and a
derived Ord on App would order children by insertion order, which is the
thing ast_cmp exists to avoid (saulshanabrook).
- typechecking: `use` register_container_rebuild_from_spec instead of calling
it via an absolute crate path (oflatt).
- normalization is now per-container, with no "built-in container" special
casing: TermDag carries no container knowledge (only the generic `ast_cmp`
and a reusable `sort_terms_by_ast` helper). Each container sort normalizes
its own term form in its constructor validator (set: sort+dedup; multiset:
sort; map: collapse to flat `map-of`), exactly as a user-defined container
would. The proof checker recomputes a container's canonical form by applying
the validator registered for the term's head (collected from the e-graph's
primitives), so built-in and user containers are handled identically
(oflatt/saulshanabrook).
- :internal-container-rebuild is a typed AST field (ast::ContainerRebuildSpec)
rather than an opaque string: the egglog parser parses it once into the struct
(ast/parse.rs) and Display serializes it, so register_container_rebuild_from_spec
just reads the struct — no hand-rolled tokenizing or re-parsing in the proof
pass; build constructs the struct directly (oflatt).
- trim the spec to the minimum (oflatt): the per-element UF index names and the
per-container `<CSort>Proof` table names are dropped from every container's
spec and recovered at register time from proof_state, which is repopulated on
re-parse from existing per-sort annotations -- element sorts' :internal-uf
(now `:internal-uf <constructor> [<index>]`) and container sorts'
:internal-proof-func (now emitted for containers too). value_prim renamed
internal_rebuild_prim.
- move the container-rebuild primitives + their runtime + the spec decoder out
of proof_encoding.rs (2027 -> 1674 lines) into a new
proofs/proof_container_rebuild.rs; proof_encoding.rs keeps only the
encoder-side spec build. Pure relocation, no behavior change.
- core-relations: split Rebuilder into a value-level ValueRebuilder supertrait
plus the table-level Rebuilder. ContainerValue::rebuild_contents now takes
&dyn ValueRebuilder, so ClosureRebuilder (the single-container rebuilder)
drops its two unreachable!() table-level methods entirely (oflatt).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Follow-up cleanups for "Proof support for containers", addressing review
comments from saulshanabrook, Copilot, and oflatt:
- pair-first/pair-second validators: guard argument arity with `let [pair] =
args else { return None }` before indexing, matching the other validators
(Copilot).
- map-of: reject odd-arity argument lists in the validator, and leave
malformed map terms unchanged during normalization instead of silently
dropping the unpaired key/value (Copilot).
- ast_cmp: document why the Lit/Var arms are spelled out rather than
delegating to `l.cmp(r)` -- Term intentionally has no derived Ord, and a
derived Ord on App would order children by insertion order, which is the
thing ast_cmp exists to avoid (saulshanabrook).
- typechecking: `use` register_container_rebuild_from_spec instead of calling
it via an absolute crate path (oflatt).
- normalization is now per-container, with no "built-in container" special
casing: TermDag carries no container knowledge (only the generic `ast_cmp`
and a reusable `sort_terms_by_ast` helper). Each container sort normalizes
its own term form in its constructor validator (set: sort+dedup; multiset:
sort; map: collapse to flat `map-of`), exactly as a user-defined container
would. The proof checker recomputes a container's canonical form by applying
the validator registered for the term's head (collected from the e-graph's
primitives), so built-in and user containers are handled identically
(oflatt/saulshanabrook).
- :internal-container-rebuild is a typed AST field (ast::ContainerRebuildSpec)
rather than an opaque string: the egglog parser parses it once into the struct
(ast/parse.rs) and Display serializes it, so register_container_rebuild_from_spec
just reads the struct — no hand-rolled tokenizing or re-parsing in the proof
pass; build constructs the struct directly (oflatt).
- trim the spec to the minimum (oflatt): it is now just
`(container-rebuild-spec <prim> [<proof-prim>])`. Everything else is recovered
at register time from proof_state, repopulated on re-parse from per-sort/global
annotations rather than listed per-container:
* per-element UF index names <- element sorts' :internal-uf (now
`:internal-uf <constructor> [<index>]`);
* per-container `<CSort>Proof` tables <- container sorts' :internal-proof-func
(now emitted for containers too);
* the global Congr/Trans/Sym/ContainerNormalize/Proof names <- a single
:internal-proof-names record on the `Proof` sort.
value_prim renamed internal_rebuild_prim.
- proof extraction: parse_proof recognizes each extracted proof term's head by
exact match against the recorded constructor names (EncodingNames) instead of
fragile `head.contains("Congr")`-style substring guessing (oflatt).
- move the container-rebuild primitives + their runtime + the spec decoder out
of proof_encoding.rs (2027 -> 1674 lines) into a new
proofs/proof_container_rebuild.rs; proof_encoding.rs keeps only the
encoder-side spec build. Pure relocation, no behavior change.
- core-relations: split Rebuilder into a value-level ValueRebuilder supertrait
plus the table-level Rebuilder. ContainerValue::rebuild_contents now takes
&dyn ValueRebuilder, so ClosureRebuilder (the single-container rebuilder)
drops its two unreachable!() table-level methods entirely (oflatt).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Follow-up cleanups for "Proof support for containers", addressing review
comments from saulshanabrook, Copilot, and oflatt:
- pair-first/pair-second validators: guard argument arity with `let [pair] =
args else { return None }` before indexing, matching the other validators
(Copilot).
- map-of: reject odd-arity argument lists in the validator, and leave
malformed map terms unchanged during normalization instead of silently
dropping the unpaired key/value (Copilot).
- ast_cmp: document why the Lit/Var arms are spelled out rather than
delegating to `l.cmp(r)` -- Term intentionally has no derived Ord, and a
derived Ord on App would order children by insertion order, which is the
thing ast_cmp exists to avoid (saulshanabrook).
- typechecking: `use` register_container_rebuild_from_spec instead of calling
it via an absolute crate path (oflatt).
- normalization is now per-container, with no "built-in container" special
casing: TermDag carries no container knowledge (only the generic `ast_cmp`
and a reusable `sort_terms_by_ast` helper). Each container sort normalizes
its own term form in its constructor validator (set: sort+dedup; multiset:
sort; map: collapse to flat `map-of`), exactly as a user-defined container
would. The proof checker recomputes a container's canonical form by applying
the validator registered for the term's head (collected from the e-graph's
primitives), so built-in and user containers are handled identically
(oflatt/saulshanabrook).
- :internal-container-rebuild is a typed AST field (ast::ContainerRebuildSpec)
rather than an opaque string: the egglog parser parses it once into the struct
(ast/parse.rs) and Display serializes it, so register_container_rebuild_from_spec
just reads the struct — no hand-rolled tokenizing or re-parsing in the proof
pass; build constructs the struct directly (oflatt).
- trim the spec to the minimum (oflatt): it is now just
`(container-rebuild-spec <prim> [<proof-prim>])`. Everything else is recovered
at register time from proof_state, repopulated on re-parse from per-sort/global
annotations rather than listed per-container:
* per-element UF index names <- element sorts' :internal-uf (now
`:internal-uf <constructor> [<index>]`);
* per-container `<CSort>Proof` tables <- container sorts' :internal-proof-func
(now emitted for containers too);
* the global Congr/Trans/Sym/ContainerNormalize/Proof names <- a single
:internal-proof-names record on the `Proof` sort.
value_prim renamed internal_rebuild_prim.
- proof extraction: parse_proof recognizes each extracted proof term's head by
exact match against the recorded constructor names (EncodingNames) instead of
fragile `head.contains("Congr")`-style substring guessing (oflatt).
- move the container-rebuild primitives + their runtime + the spec decoder out
of proof_encoding.rs (2027 -> 1674 lines) into a new
proofs/proof_container_rebuild.rs; proof_encoding.rs keeps only the
encoder-side spec build. Pure relocation, no behavior change.
- core-relations: split Rebuilder into a value-level ValueRebuilder supertrait
plus the table-level Rebuilder. ContainerValue::rebuild_contents now takes
&dyn ValueRebuilder, so ClosureRebuilder (the single-container rebuilder)
drops its two unreachable!() table-level methods entirely (oflatt).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Follow-up cleanups for "Proof support for containers", addressing review
comments from saulshanabrook, Copilot, and oflatt:
- pair-first/pair-second validators: guard argument arity with `let [pair] =
args else { return None }` before indexing, matching the other validators
(Copilot).
- map-of: reject odd-arity argument lists in the validator, and leave
malformed map terms unchanged during normalization instead of silently
dropping the unpaired key/value (Copilot).
- ast_cmp: document why the Lit/Var arms are spelled out rather than
delegating to `l.cmp(r)` -- Term intentionally has no derived Ord, and a
derived Ord on App would order children by insertion order, which is the
thing ast_cmp exists to avoid (saulshanabrook).
- typechecking: `use` register_container_rebuild_from_spec instead of calling
it via an absolute crate path (oflatt).
- normalization is now per-container, with no "built-in container" special
casing: TermDag carries no container knowledge (only the generic `ast_cmp`
and a reusable `sort_terms_by_ast` helper). Each container sort normalizes
its own term form in its constructor validator (set: sort+dedup; multiset:
sort; map: collapse to flat `map-of`), exactly as a user-defined container
would. The proof checker recomputes a container's canonical form by applying
the validator registered for the term's head (collected from the e-graph's
primitives), so built-in and user containers are handled identically
(oflatt/saulshanabrook).
- :internal-container-rebuild is a typed AST field (ast::ContainerRebuildSpec)
rather than an opaque string: the egglog parser parses it once into the struct
(ast/parse.rs) and Display serializes it, so register_container_rebuild_from_spec
just reads the struct — no hand-rolled tokenizing or re-parsing in the proof
pass; build constructs the struct directly (oflatt).
- trim the spec to the minimum (oflatt): it is now just
`(container-rebuild-spec <prim> [<proof-prim>])`. Everything else is recovered
at register time from proof_state, repopulated on re-parse from per-sort/global
annotations rather than listed per-container:
* per-element UF index names <- element sorts' :internal-uf (now
`:internal-uf <constructor> [<index>]`);
* per-container `<CSort>Proof` tables <- container sorts' :internal-proof-func
(now emitted for containers too);
* the global Congr/Trans/Sym/ContainerNormalize/Proof names <- a single
:internal-proof-names record on the `Proof` sort.
value_prim renamed internal_rebuild_prim.
- proof extraction: parse_proof recognizes each extracted proof term's head by
exact match against the recorded constructor names (EncodingNames) instead of
fragile `head.contains("Congr")`-style substring guessing (oflatt).
- move the container-rebuild primitives + their runtime + the spec decoder out
of proof_encoding.rs (2027 -> 1674 lines) into a new
proofs/proof_container_rebuild.rs; proof_encoding.rs keeps only the
encoder-side spec build. Pure relocation, no behavior change.
- core-relations: split Rebuilder into a value-level ValueRebuilder supertrait
plus the table-level Rebuilder. ContainerValue::rebuild_contents now takes
&dyn ValueRebuilder, so ClosureRebuilder (the single-container rebuilder)
drops its two unreachable!() table-level methods entirely (oflatt).
- docs: concision pass over the proof/term-encoding docs -- tightened
proof_encoding.md (kept structure + examples) and shortened verbose doc
comments throughout, and fixed two pre-existing broken intra-doc links
(`Fact`, `Propostion`) in proof_format.rs (oflatt).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Programs using container sorts (Vec, Set, Map, MultiSet, Pair) now work under the term/proof encoding (previously rejected), including containers read (vec-get, map-get, …) or constructed (vec-of, set-of, …) in a rule body. During rebuilding, a per-container rebuild primitive canonicalizes a container's eq-sort elements to their union-find leaders, recursing through nested containers (e.g. (Vec (Vec Math))). In proof mode it produces a Congr-chain proof (anchored on a per-sort <Sort>Proof reflexive table) that the old container equals the rebuilt one. Because the primitive reads the element sorts' UF indices, the rebuild rule runs :naive. A container built in the query body is a side condition with no carryable proof: it is marked with an Eval proof step and re-evaluated against the typed rule when checked, so it can be read or matched in the query but not carried into an action (that is rejected). Extraction changes: container terms extract in a deterministic, reproducible order rather than value-id order, and maps extract in a flat (map-of k0 v0 …) form (new map-of constructor) instead of nested map-inserts. See src/proofs/proof_encoding.md. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
273a9c8 to
5fca83b
Compare
Condense the Containers section to the current contract and add examples: a small container program (Wrap over a MathVec) and the actual generated rebuild rule for a view table with a container column (the MathVec_rebuild primitive, the UF_Mathf representative lookup, the bool-!= guard, :naive). Drop implementation trivia that belongs in code comments (rebuild_val_with, ast_cmp ordering, the :internal-container-rebuild round-trip). The rest of the walkthrough is unchanged. Co-authored-by: Oliver Flatt <oflatt@gmail.com> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
|
Added one last comment and seeing if the AI has anything else useful, since a lot of the term encoding code I didn't look through super carefully: @coderabbitai full review |
|
✅ Action performedFull review finished. |
📝 WalkthroughSummary by CodeRabbit
WalkthroughThis PR adds container-value support to egglog's term/proof encoding: a new ChangesContainer rebuild and proof encoding
Estimated code review effort: 5 (Critical) | ~120 minutes Sequence Diagram(s)sequenceDiagram
participant Rule
participant RebuildingRules
participant ContainerRebuildPrim
participant UFTable
participant CSortProofTable
Rule->>RebuildingRules: eq_container column update
RebuildingRules->>ContainerRebuildPrim: rebuild_val_with(remap closure)
ContainerRebuildPrim->>UFTable: resolve element leaders
ContainerRebuildPrim-->>RebuildingRules: rebuilt canonical container value
RebuildingRules->>CSortProofTable: mint Congr/ContainerNormalize proof
CSortProofTable-->>Rule: reflexive anchor on rebuilt container
sequenceDiagram
participant ProofStore
participant ProofChecker
participant SideCondition
participant Term
ProofStore->>ProofChecker: check_proof_with_context(rule)
ProofChecker->>SideCondition: is_container_side_condition(fact)
SideCondition->>Term: eval_side(lhs), eval_side(rhs)
Term-->>SideCondition: evaluated container values
SideCondition-->>ProofChecker: extended substitution
ProofChecker->>ProofChecker: assert_body_proof_normal_form
Possibly related PRs
Suggested labels: Suggested reviewers: 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 inconclusive)
✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
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 |
There was a problem hiding this comment.
Actionable comments posted: 4
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/ast/mod.rs (1)
1671-1690: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
container_rebuild/proof_constructorsnames skipped bymap_string_symbols.
ufandproof_funcare run throughfun(the sanitizer) here, butcontainer_rebuildandproof_constructorsare passed straight through unchanged, even though they carry the same kind of internally-generated symbol names (internal_rebuild_prim,internal_rebuild_proof_prim,congr,trans,sym,normalize). Since this function is documented as being used bysanitize_internal_symbols, these fields will retain un-sanitized internal names in output whereuf/proof_funcwould not, which looks like an oversight rather than intentional.🐛 Suggested fix
} => GenericCommand::Sort { span, name: fun(name), presort_and_args, uf: uf.map(|(ctor, index)| (fun(ctor), index.map(&mut *fun))), proof_func: proof_func.map(&mut *fun), - container_rebuild, - proof_constructors, + container_rebuild: container_rebuild.map(|spec| ContainerRebuildSpec { + internal_rebuild_prim: fun(spec.internal_rebuild_prim), + internal_rebuild_proof_prim: spec.internal_rebuild_proof_prim.map(&mut *fun), + }), + proof_constructors: proof_constructors.map(|pc| ProofConstructorNames { + congr: fun(pc.congr), + trans: fun(pc.trans), + sym: fun(pc.sym), + normalize: fun(pc.normalize), + }), unionable, },🤖 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 `@src/ast/mod.rs` around lines 1671 - 1690, `map_string_symbols` is skipping internal symbol sanitization for `GenericCommand::Sort`’s `container_rebuild` and `proof_constructors` fields while already sanitizing `name`, `uf`, and `proof_func`. Update the `GenericCommand::Sort` match arm in `src/ast/mod.rs` so these fields are also passed through the same `fun` sanitizer, preserving consistency with `sanitize_internal_symbols` and preventing raw internal names from leaking through.
🤖 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 `@src/sort/set.rs`:
- Around line 148-160: The set-get validator is using a different ordering
source than the runtime path, so it can pick a different element than the actual
primitive. Update the set-get logic around set_get_validator to use the same
ordering as the runtime implementation (the same BTreeSet<Value> ordering used
by set-get at execution), or derive the validator’s result from that shared
order instead of iterating the AST-based BTreeSet<OrdTerm>.
In `@src/termdag.rs`:
- Around line 177-197: `ast_cmp` in `TermDag` still compares `App` nodes
recursively, so deeply nested terms can overflow the stack during `OrdTerm::cmp`
and canonicalization. Update the `ast_cmp` logic to avoid structural recursion
for `App` chains by using an iterative comparison strategy or another bounded
approach, while preserving the existing ordering behavior for `Lit`, `Var`, and
`App` and keeping the comparison entry point in `ast_cmp`/`OrdTerm::cmp`
unchanged.
In `@tests/container_rebuild.rs`:
- Around line 248-273: The comment in set_rebuild_noncollapse_proof_mode is
stale and contradicts the later proof-mode collapse test in the same file.
Update the documentation near set_rebuild_noncollapse_proof_mode and
set_rebuild_collapse_proof_mode to remove the “known limitation” claim or
replace it with the current behavior, so the comments accurately reflect what
EGraph::new_with_proofs().with_proof_testing() now supports.
- Around line 326-349: `set_rebuild_reorder_proof_mode` is duplicating
`set_rebuild_noncollapse_proof_mode` instead of exercising the intended reorder
path. Update the test body in `set_rebuild_reorder_proof_mode` so the program
actually forces a leader to move to a different slot after union/rebuild, using
a distinct `parse_and_run_program` scenario with different element ordering and
a post-union `check` that validates the re-sorted result; if no such scenario is
needed, remove the redundant test.
---
Outside diff comments:
In `@src/ast/mod.rs`:
- Around line 1671-1690: `map_string_symbols` is skipping internal symbol
sanitization for `GenericCommand::Sort`’s `container_rebuild` and
`proof_constructors` fields while already sanitizing `name`, `uf`, and
`proof_func`. Update the `GenericCommand::Sort` match arm in `src/ast/mod.rs` so
these fields are also passed through the same `fun` sanitizer, preserving
consistency with `sanitize_internal_symbols` and preventing raw internal names
from leaking through.
🪄 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: Organization UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: ec028ae6-cf07-4e95-a4f5-b4547fc8b1bf
⛔ Files ignored due to path filters (6)
src/proofs/snapshots/egglog__proofs__proof_tests__tests__doc_example_add_function1.snapis excluded by!**/*.snaptests/container-proofs.eggis excluded by!**/*.eggtests/proofs/container-set-collapse.eggis excluded by!**/*.eggtests/snapshots/files__proof_unsupported_files.snapis excluded by!**/*.snaptests/snapshots/files__shared_snapshot_container_proofs.snapis excluded by!**/*.snaptests/snapshots/files__shared_snapshot_container_set_collapse.snapis excluded by!**/*.snap
📒 Files selected for processing (33)
CHANGELOG.mdcore-relations/src/containers/mod.rscore-relations/src/containers/tests.rscore-relations/src/lib.rscore-relations/src/table_spec.rscore-relations/src/uf/mod.rsegglog-bridge/src/tests.rssrc/ast/desugar.rssrc/ast/mod.rssrc/ast/parse.rssrc/lib.rssrc/prelude.rssrc/proofs/mod.rssrc/proofs/proof_checker.rssrc/proofs/proof_container_rebuild.rssrc/proofs/proof_encoding.mdsrc/proofs/proof_encoding.rssrc/proofs/proof_encoding_helpers.rssrc/proofs/proof_extraction.rssrc/proofs/proof_format.rssrc/proofs/proof_normal_form.rssrc/proofs/proof_simplification.rssrc/proofs/proof_tests.rssrc/sort/fn.rssrc/sort/map.rssrc/sort/mod.rssrc/sort/multiset.rssrc/sort/pair.rssrc/sort/set.rssrc/sort/vec.rssrc/termdag.rssrc/typechecking.rstests/container_rebuild.rs
📜 Review details
⚠️ CI failures not shown inline (29)
GitHub Actions: Build / 26_wasm.txt: Proof support for containers
Conclusion: failure
##[group]Run bail() {
�[36;1mbail() {�[0m
�[36;1m printf '::error::install-action: %s\n' "$*"�[0m
GitHub Actions: Build / 0_benchmark (ubuntu-latest, math_normal).txt: Proof support for containers
Conclusion: failure
##[group]Run bail() {
�[36;1mbail() {�[0m
�[36;1m printf '::error::install-action: %s\n' "$*"�[0m
GitHub Actions: Build / 3_benchmark (ubuntu-latest, proof_testing_eqsat-basic).txt: Proof support for containers
Conclusion: failure
##[group]Run bail() {
�[36;1mbail() {�[0m
�[36;1m printf '::error::install-action: %s\n' "$*"�[0m
GitHub Actions: Build / 4_benchmark (ubuntu-latest, luminal-llama).txt: Proof support for containers
Conclusion: failure
##[group]Run bail() {
�[36;1mbail() {�[0m
�[36;1m printf '::error::install-action: %s\n' "$*"�[0m
GitHub Actions: Build / 2_benchmark (ubuntu-latest, rectangle).txt: Proof support for containers
Conclusion: failure
##[group]Run bail() {
�[36;1mbail() {�[0m
�[36;1m printf '::error::install-action: %s\n' "$*"�[0m
GitHub Actions: Build / 5_benchmark (ubuntu-latest, taylor51).txt: Proof support for containers
Conclusion: failure
##[group]Run bail() {
�[36;1mbail() {�[0m
�[36;1m printf '::error::install-action: %s\n' "$*"�[0m
GitHub Actions: Build / 6_benchmark (ubuntu-latest, conv1d_128).txt: Proof support for containers
Conclusion: failure
##[group]Run bail() {
�[36;1mbail() {�[0m
�[36;1m printf '::error::install-action: %s\n' "$*"�[0m
GitHub Actions: Build / 8_benchmark (ubuntu-latest, rust_rule_insert_loop).txt: Proof support for containers
Conclusion: failure
##[group]Run bail() {
�[36;1mbail() {�[0m
�[36;1m printf '::error::install-action: %s\n' "$*"�[0m
GitHub Actions: Build / 7_benchmark (ubuntu-latest, extract-vec-bench).txt: Proof support for containers
Conclusion: failure
##[group]Run bail() {
�[36;1mbail() {�[0m
�[36;1m printf '::error::install-action: %s\n' "$*"�[0m
GitHub Actions: Build / 9_benchmark (ubuntu-latest, conv1d_32).txt: Proof support for containers
Conclusion: failure
##[group]Run bail() {
�[36;1mbail() {�[0m
�[36;1m printf '::error::install-action: %s\n' "$*"�[0m
GitHub Actions: Build / 10_benchmark (ubuntu-latest, rust_rule_match_overhead).txt: Proof support for containers
Conclusion: failure
##[group]Run bail() {
�[36;1mbail() {�[0m
�[36;1m printf '::error::install-action: %s\n' "$*"�[0m
GitHub Actions: Build / 14_benchmark (ubuntu-latest, typeinfer).txt: Proof support for containers
Conclusion: failure
##[group]Run bail() {
�[36;1mbail() {�[0m
�[36;1m printf '::error::install-action: %s\n' "$*"�[0m
GitHub Actions: Build / 11_benchmark (ubuntu-latest, proof_testing_math).txt: Proof support for containers
Conclusion: failure
##[group]Run bail() {
�[36;1mbail() {�[0m
�[36;1m printf '::error::install-action: %s\n' "$*"�[0m
GitHub Actions: Build / 15_benchmark (ubuntu-latest, stresstest_large_expr).txt: Proof support for containers
Conclusion: failure
##[group]Run bail() {
�[36;1mbail() {�[0m
�[36;1m printf '::error::install-action: %s\n' "$*"�[0m
GitHub Actions: Build / 12_benchmark (ubuntu-latest, herbie).txt: Proof support for containers
Conclusion: failure
##[group]Run bail() {
�[36;1mbail() {�[0m
�[36;1m printf '::error::install-action: %s\n' "$*"�[0m
GitHub Actions: Build / 16_benchmark (ubuntu-latest, rust_rule_match_with_serialize).txt: Proof support for containers
Conclusion: failure
##[group]Run bail() {
�[36;1mbail() {�[0m
�[36;1m printf '::error::install-action: %s\n' "$*"�[0m
GitHub Actions: Build / 1_benchmark (ubuntu-latest, python_array_optimize).txt: Proof support for containers
Conclusion: failure
##[group]Run bail() {
�[36;1mbail() {�[0m
�[36;1m printf '::error::install-action: %s\n' "$*"�[0m
GitHub Actions: Build / 19_benchmark (ubuntu-latest, eggcc-extraction).txt: Proof support for containers
Conclusion: failure
##[group]Run bail() {
�[36;1mbail() {�[0m
�[36;1m printf '::error::install-action: %s\n' "$*"�[0m
GitHub Actions: Build / 17_benchmark (ubuntu-latest, factoring-multisets).txt: Proof support for containers
Conclusion: failure
##[group]Run bail() {
�[36;1mbail() {�[0m
�[36;1m printf '::error::install-action: %s\n' "$*"�[0m
GitHub Actions: Build / 18_benchmark (ubuntu-latest, cykjson).txt: Proof support for containers
Conclusion: failure
##[group]Run bail() {
�[36;1mbail() {�[0m
�[36;1m printf '::error::install-action: %s\n' "$*"�[0m
GitHub Actions: Build / 21_benchmark (ubuntu-latest, proof_testing_typecheck).txt: Proof support for containers
Conclusion: failure
##[group]Run bail() {
�[36;1mbail() {�[0m
�[36;1m printf '::error::install-action: %s\n' "$*"�[0m
GitHub Actions: Build / 20_benchmark (ubuntu-latest, repro-665-set-union).txt: Proof support for containers
Conclusion: failure
##[group]Run bail() {
�[36;1mbail() {�[0m
�[36;1m printf '::error::install-action: %s\n' "$*"�[0m
GitHub Actions: Build / 23_benchmark (ubuntu-latest, proof_testing_unify).txt: Proof support for containers
Conclusion: failure
##[group]Run bail() {
�[36;1mbail() {�[0m
�[36;1m printf '::error::install-action: %s\n' "$*"�[0m
GitHub Actions: Build / 24_benchmark (ubuntu-latest, rust_rule_tableaction_hot_path).txt: Proof support for containers
Conclusion: failure
##[group]Run bail() {
�[36;1mbail() {�[0m
�[36;1m printf '::error::install-action: %s\n' "$*"�[0m
GitHub Actions: Build / 22_benchmark (ubuntu-latest, rust_rule_fib).txt: Proof support for containers
Conclusion: failure
##[group]Run bail() {
�[36;1mbail() {�[0m
�[36;1m printf '::error::install-action: %s\n' "$*"�[0m
GitHub Actions: Build / 25_benchmark (ubuntu-latest, math-microbenchmark).txt: Proof support for containers
Conclusion: failure
##[group]Run bail() {
�[36;1mbail() {�[0m
�[36;1m printf '::error::install-action: %s\n' "$*"�[0m
GitHub Actions: Build / 13_benchmark (ubuntu-latest, eggcc-2mm).txt: Proof support for containers
Conclusion: failure
##[group]Run bail() {
�[36;1mbail() {�[0m
�[36;1m printf '::error::install-action: %s\n' "$*"�[0m
GitHub Actions: Build / 27_test.txt: Proof support for containers
Conclusion: failure
##[group]Run bail() {
�[36;1mbail() {�[0m
�[36;1m printf '::error::install-action: %s\n' "$*"�[0m
GitHub Actions: Build / 29_coverage.txt: Proof support for containers
Conclusion: failure
##[group]Run bail() {
�[36;1mbail() {�[0m
�[36;1m printf '::error::install-action: %s\n' "$*"�[0m
🧰 Additional context used
📓 Path-based instructions (1)
CHANGELOG.md
📄 CodeRabbit inference engine (CLAUDE.md)
Update
CHANGELOG.mdwith a concise bullet when making major changes such as breaking changes or new features.
Files:
CHANGELOG.md
🧠 Learnings (1)
📚 Learning: 2026-06-07T21:55:12.804Z
Learnt from: saulshanabrook
Repo: egraphs-good/egglog PR: 923
File: src/sort/pair.rs:65-67
Timestamp: 2026-06-07T21:55:12.804Z
Learning: In egglog’s Rust sort implementations, treat `Presort::supports_proof_encoding()` for `PairSort` and `EitherSort` as presort-wide: it must return `true` unconditionally to indicate constructor/projection proof metadata is available regardless of inner sort types. Do not change it to require that all inner fields are eq/container sorts. Instead, control rebuild/canonicalization behavior via the existing gates: use `is_eq_container_sort()` plus per-field rebuild flags (`do_rebuild_first`/`do_rebuild_second` in `PairContainer`, `do_rebuild_left`/`do_rebuild_right` in `EitherContainer`) so mixed-sort instantiations like `(Pair Math i64)` and `(Either Math i64)` are allowed, with rebuild work contributed only by the eq-sort fields.
Applied to files:
src/sort/pair.rs
🔇 Additional comments (49)
CHANGELOG.md (1)
20-20: LGTM!tests/container_rebuild.rs (1)
1-401: LGTM! Good coverage across term-only vs. proof-mode pairs for each container sort, exercising collapse/non-collapse and nested cases.src/termdag.rs (3)
26-63: LGTM!
199-210: LGTM!
552-579: LGTM!src/proofs/proof_encoding.rs (6)
13-19: LGTM!Also applies to: 45-46
113-142: LGTM!Also applies to: 161-207, 217-224
561-644: The container rebuild path looks correct: the reflexive anchorTrans(Sym p, p)onrebuilt_varand the per-childCongr(current_proof, i, proof_var)chain compose soundly, andcontainer_proof_bindingsare emitted beforepf_codesoproof_varis bound before use. The multi-column congruence chain also keepschild_index ivalid because earlier columns rewrite different indices.LGTM!
Also applies to: 655-732
869-896: 🎯 Functional Correctness | 💤 Low valueConfirm
ResolvedFact::to_string()emits re-parseable surface syntax for the side condition.Unlike the sibling branch (which rebuilds the fact from instrumented sub-expressions), the container side-condition path pushes the raw
fact.to_string()back into the encoded program to be re-parsed. This relies on the resolved fact stringifying to valid egglog and on all its argument variables already being bound in the query. The path is exercised byproof_mode_query_constructed_container_not_used_in_action, but please confirm it holds for facts whose container primitive arguments are non-trivial (e.g. nested container primitives already lifted byproof_normal_form).
1025-1044: LGTM!Also applies to: 1147-1173, 1325-1346
1496-1550: LGTM!Also applies to: 1664-1726
src/proofs/proof_encoding_helpers.rs (2)
31-32: LGTM!Also applies to: 74-75, 371-418
455-458: The container-in-query-used-in-action detection correctly scans both(lhs,rhs)orderings and walks action expressions, and theSort { presort_and_args: Some(_) }arm now accepts genuine container sorts viatype_info.get_sort_by_name(name).filter(is_container_sort)— addressing the earlier request to treat all containers as supported.LGTM!
Also applies to: 591-641
src/proofs/proof_extraction.rs (1)
97-109: Collectingcontainer_normalizerskeyed by canonical head is consistent withnormalize_container's head-based lookup. Note that two container sorts of the same kind (e.g.Vecover different element types) both yield the headvec-of, so one validator overwrites the other in the map — this is benign because container normalization is purely structural and element-type independent.LGTM!
src/proofs/proof_checker.rs (3)
23-40: LGTM!Also applies to: 477-511
644-673: The reworked premise loop is sound:working_substis seeded fromsubstitution(already computed over all non-side-condition facts bycompute_rule_substitution), so any variable feeding a side condition's primitive is bound beforecheck_side_conditionre-evaluates it, regardless of body order.LGTM!
914-941:ContainerNormalizevalidates lhs-identity plus rhs-equals-normalized-raw, andEvalcorrectly errors when reached directly (side conditions are dispatched tocheck_side_condition, nevercheck_proof).check_side_condition/eval_side/assert_body_proof_normal_formare consistent with the encoder's Eval-marker contract.LGTM!
Also applies to: 952-1064
src/proofs/proof_format.rs (3)
5-46: LGTM!Also applies to: 72-108
202-306: Switching proof-head dispatch to exact equality againstEncodingNames(instead of substring matching) is a meaningful robustness fix, since fresh-gensym'd constructor names could otherwise be substrings of one another.LGTM!
372-384: LGTM!Also applies to: 407-413, 522-541, 578-583, 754-759, 851-859
src/proofs/proof_normal_form.rs (1)
6-19: The constructor-argument lift correctly targets only container-producing primitives (custom functions and primitives are handled by the earlier arms), and the recursion normalizes inner arguments first, matchingassert_body_proof_normal_form's expectations in the checker.LGTM!
Also applies to: 177-213
src/proofs/proof_simplification.rs (1)
77-152:opt_redundant_container_normalizesafely collapses a no-op normalization (outer rhs equals inner rhs, so the inner proof already proves the identical proposition), andmap_child_proofscorrectly recomputes the proposition vianormalize_containerafter remapping the inner proof.LGTM!
Also applies to: 330-344, 386-388
src/proofs/proof_tests.rs (1)
5-6: Good coverage: the refactor to a real presort-declaredEqContainersort (registering the primitive on both the egraph andoriginal_typechecking) exercises the actual rebuild-primitive path, and the two new tests cover both the accepted (container not carried into action) and rejected (ContainerCreatedInQueryUsedInAction) cases.LGTM!
Also applies to: 181-238, 240-301
core-relations/src/table_spec.rs (2)
137-157: LGTM!
106-136: 🩺 Stability & AvailabilityTrait upcasting is fine here
&dyn Rebuildercan coerce to&dyn ValueRebuilder, and the workspace toolchain is Rust 1.91.0, so this path compiles as written.> Likely an incorrect or invalid review comment.core-relations/src/lib.rs (1)
43-43: LGTM!core-relations/src/containers/mod.rs (1)
31-31: LGTM!Also applies to: 165-189, 290-294, 320-328, 387-399, 787-799
core-relations/src/containers/tests.rs (1)
12-13: LGTM!Also applies to: 26-26, 44-93
core-relations/src/uf/mod.rs (1)
21-21: LGTM!Also applies to: 67-77
egglog-bridge/src/tests.rs (1)
14-14: LGTM!Also applies to: 452-452
src/sort/fn.rs (1)
57-57: LGTM!src/sort/multiset.rs (1)
12-45: LGTM!Also applies to: 148-177, 192-194, 260-272
src/sort/mod.rs (2)
87-95: 📐 Maintainability & Code QualityPast feedback on this method placement appears unaddressed.
A prior review comment ("inline or move out of sort") suggested reconsidering whether
rebuild_container_normalizerbelongs on the genericSorttrait. It's still defined here unchanged, duplicating the near-identical method onContainerSort(seesrc/prelude.rs'sContainerSort::rebuild_container_normalizer, whichContainerSortImplforwards into this one). If this was intentionally kept as-is, feel free to disregard.
11-11: LGTM!src/sort/map.rs (2)
122-133: 🎯 Functional Correctness
map-removestill has no proof term-form/validator — prior concern likely still open.
map-removeis registered via plainadd_primitive!(Line 260) with no validator, unlikemap-empty/map-of/map-insert/map-get/etc. This means any query-body use ofmap-removewill failcommand_supports_proof_encoding's "all primitives have validators" check. This is the same limitation flagged in a previous review ("what's the deal with accepting map-insert + map-of + map-empty but not other map functions?"), which appears to remain unresolved here.Also applies to: 260-260
12-37: LGTM!Also applies to: 39-91, 196-264, 267-379
src/ast/mod.rs (2)
1009-1051: 🗄️ Data Integrity & IntegrationUnresolved: where is
container_rebuildactually populated for a sort?A prior review thread asked exactly this question ("where is
:internal-container-rebuildactually written to a sort? ... where did we set this in the code? didn't see it in proof instrumentation") and it doesn't appear resolved within the files in this cohort. TheDisplayarm forpresort_and_args: Nonealso silently dropscontainer_rebuildvia.., which is fine only if it's guaranteedNonein that branch — worth confirming against the writer intypechecking.rs/proof_encoding.rs(not in this review batch).
23-54: LGTM!Also applies to: 91-109, 151-171, 283-301, 603-620, 1943-1971
src/ast/parse.rs (1)
114-138: LGTM!Also applies to: 289-394
src/ast/desugar.rs (1)
68-78: LGTM!Also applies to: 90-100, 145-163, 233-248, 290-300, 410-428
src/proofs/mod.rs (1)
2-2: LGTM!src/proofs/proof_encoding.md (1)
366-416: LGTM!src/sort/pair.rs (1)
12-12: LGTM!Also applies to: 31-51, 143-170, 184-187
src/sort/set.rs (1)
11-11: LGTM!Also applies to: 26-49, 161-251, 261-272
src/sort/vec.rs (1)
14-14: LGTM!Also applies to: 38-56, 136-191, 235-244
src/prelude.rs (1)
761-762: LGTM!Also applies to: 918-925, 996-999
src/typechecking.rs (1)
4-4: LGTM!Also applies to: 419-474
src/proofs/proof_container_rebuild.rs (1)
1-368: LGTM!src/lib.rs (1)
73-73: LGTM!Also applies to: 1592-1620, 1973-1976, 2963-3020
No description provided.