Skip to content

fix(v3): parse bare single-variant sum RHS - #1286

Merged
briansrls merged 10 commits into
mainfrom
feat/pr-a3-single-variant-sum-parser
Apr 30, 2026
Merged

briansrls merged 10 commits into
mainfrom
feat/pr-a3-single-variant-sum-parser

Conversation

@briansrls

@briansrls briansrls commented Apr 30, 2026 •

Copy link
Copy Markdown
Contributor

Summary

  • Parses bare nullary single-variant sum RHS forms such as type InputEvaluationOrder = LeftFirst without requiring the old parenthesized workaround.
  • Preserves ordinary bare RHS aliases, including prelude/imported aliases, forward sibling aliases, and declaration-local generic parameter aliases such as type Id<T> = T without leaking those binders into unrelated declarations.
  • Regenerates parser, bootstrap snapshots, and handwritten parse snapshot manifest.

Verification

  • cargo run -p v3-compiler --bin regen_parse
  • cargo run -p v3-compiler --features bootstrap-regen-fresh --bin regen_bootstrap
  • cargo run -p v3-compiler --features bootstrap-regen-fresh --bin regen_bootstrap -- --verify
  • cargo fmt --all --check
  • cargo clippy --workspace -- -D warnings
  • cargo test -p v3-compiler --test integration m2_substrate_inhabitance_test::parse_bare_rhs_alias_reference_stays_type_alias -- --nocapture
  • cargo test -p v3-compiler --test integration m2_substrate_inhabitance_test -- --nocapture
  • cargo test -p v3-compiler --test integration sg2_parse_authority_test::parse_generated_module_matches_checked_in_snapshot -- --nocapture
  • cargo test -p v3-compiler refresh_handwritten_parse_snapshot_manifest -- --ignored
  • cargo test -p v3-compiler --test integration parse_stage4_prep::handwritten_parse_snapshot_matches_manifest -- --nocapture

@briansrls briansrls left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review metadata

  • Provider / model: codex / unknown
  • Commit: a26c2824 · Trigger: schedule
  • Thinking: 332s wall

Non-blocking — Strengths

  • src/v3/compiler/parse_parser_body.txt The parser-local single-variant sum disambiguation keeps TypeSum vs TypeAlias explicit and flows into the existing SurfaceItem::TypeSum lowering path.

✅ Code-only parser change; I found no blocking thesis or invariant concerns.

@briansrls

Copy link
Copy Markdown
Contributor Author

Codex schedule review (a26c2824). Verified on that SHA: non-blocking strengths only — no change requests.

The rhs_is_sum / rhs_opens_single_variant_sum_shape / parse_sum_variants path still routes single-variant sums through existing SurfaceItem::TypeSum → lowering as described; no thesis/invariant deltas called out by the review.

No commit from this item.

— sent from smart-boar-25

@briansrls
briansrls marked this pull request as ready for review April 30, 2026 18:10
@briansrls

Copy link
Copy Markdown
Contributor Author

Review metadata

  • Provider / model: cursor / composer-2
  • Commit: a26c2824 · Trigger: schedule
  • Comparison: origin/main @ f68d9599 ... review/pr-1286-a26c2824 @ a26c2824
  • Thinking: 92s wall

Findings

None. Nothing in the diff clearly breaches INVARIANTS.md, docs/modeling-discipline.md, CODING.md, or TESTING.md. This is implementation-layer parsing (not substrate modeling): tighter sum-vs-alias lookahead, a small helper, and parse regression tests that target the parser’s public surface (TypeSum / variant payloads), which fits TESTING.md when the pipeline under test is parsing.

Verdict

APPROVE — The change is narrow: rhs_is_sum recognizes single-variant sums that open with { or (, parse_sum_variants tolerates a leading | run, and tests pin both behaviors. No parallel authority or fail-open behavior is evident from the diff.

Exploratory observations (optional)

  • Rustdoc typo: src/v3/compiler/src/parse_generated.rs:864 (mirrored in parse_parser_body.txt) reads carrier) and TypeAlias — the stray ) looks like a copy/edit slip and hurts readability of an otherwise useful comment.

  • Regen discipline: parse_parser_body.txt and parse_generated.rs stay aligned in this diff; worth keeping that pairing whenever someone edits the generator source so they don’t drift.

@briansrls

Copy link
Copy Markdown
Contributor Author

Review metadata

  • Provider / model: claude / claude-opus-4-7
  • Commit: fd2cc50f · Trigger: schedule
  • Comparison: origin/main @ 9d60c7da ... review/pr-1286-fd2cc50f @ fd2cc50f
  • Thinking: 718s wall

Build passing doesn't change the verdict — it confirms the drift. parse_generated.rs is internally consistent (no two-pass, no HashSet import, no new Parser field) because the regen step wasn't run; the body.txt and regen_parse_emit.rs changes are sitting in source-of-truth files but never propagated to the actually-compiled artifact. So the production parser does not have the prelude-allowlist / two-pass disambiguator the body.txt advertises. Findings and REQUEST_CHANGES verdict above stand.

@briansrls briansrls left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review metadata

  • Provider / model: codex / unknown
  • Commit: fd2cc50f · Trigger: schedule
  • Thinking: 258s wall

BLOCKING (2)

Root Cause

  • src/v3/compiler/parse_parser_body.txt the parser template and generator changed but the generated snapshot was not updated → regenerate parse_generated.rs from the new authority or remove the authority change.
  • src/v3/compiler/parse_parser_body.txt the pass-1 alias-reference fact is introduced without a parse decision that consumes it → either make rhs_is_sum consult the set or delete the two-pass scaffold.

⚠️ The single-variant sum path is directionally fine, but the parser authority/generation mismatch and unused alias fact need rework first.

out.push_str("use crate::tokenize::{Token, TokenKind};\n");
out.push_str("use crate::operators::{LogicalOp, OperatorKind};\n\n");
out.push_str("use crate::operators::{LogicalOp, OperatorKind};\n");
out.push_str("use std::collections::HashSet;\n\n");

This comment was marked as resolved.

/// `None` during pass 1 (bare-RHS sum disambiguation disabled). `Some` during
/// pass 2: a bare `type T = U` is [`SurfaceItem::TypeSum`] iff `U` is **not**
/// in this set (prelude + imports + module `type`/`data` LHS names).
bare_rhs_alias_reference_idents: Option<HashSet<String>>,

This comment was marked as resolved.

@briansrls briansrls changed the title smart-boar-25 fix(v3): parse bare single-variant sum RHS Apr 30, 2026
@briansrls

Copy link
Copy Markdown
Contributor Author

Review metadata

  • Provider / model: claude / claude-opus-4-7
  • Commit: fbd9c541 · Trigger: schedule
  • Comparison: origin/main @ fbbcf094 ... review/pr-1286-fbd9c541 @ fbd9c541
  • Thinking: 36s wall

Findings

  • PRELUDE_BARE_RHS_ALIAS_IDENTS is a hand-maintained parallel representation of prelude type names (src/v3/compiler/parse_parser_body.txt:36-67 and the regenerated mirror in parse_generated.rs:65-96). The doc-comment's "expand it when integration tests surface a legitimate alias the disambiguator mis-classified as a nullary sum" is an open-ended maintenance instruction, not a bounded/named dissolution trigger. This is the kind of "two sources that can independently drift" the modeling discipline warns against — the prelude is the authority on which spellings exist; the parser is now a second authority that has to be kept in sync by hand. Non-blocking only if you accept it as a temporary bridge — in that case give it (a) a documented dissolution path (e.g., "remove once parser receives a resolved prelude symbol set / once Variant() is mandated for nullary single-variant sums") and (b) an explicit reference to the canonical list. Today it has neither.

  • Bare-RHS classification is now lexical-scope-only (rhs_opens_single_variant_sum_shape, lines 1108-1147 of parse_generated.rs). A type Foo = Bar where Bar is reachable transitively but not in the local module / explicit-import name list / hardcoded prelude will silently re-classify as a single-variant nullary sum rather than an alias. That's a correctness footgun that depends on import style. INVARIANTS' fail-closed lens favors making this unambiguous in the surface syntax (e.g., require Variant() / Variant{} for the no-pipe sum form, keep bare = Ident as alias unconditionally) rather than guessing from a name set. Worth raising even if you don't take it now.

  • Two full parse passes on every module (parse_generated.rs:139-156). Pass 1 errors are discarded and pass 2 re-runs from scratch, doubling parse work and meaning a malformed file is parsed-and-erred twice. Non-blocking, but the structure suggests the right shape is "parse once into a representation that defers the alias/sum decision, then resolve" rather than "parse twice with a flag."

Verdict

APPROVE_WITH_COMMENTS — the fix unblocks EvalStrategy = ApplicativeOrder { … } and the tests are good (sum/alias preservation, leading |). The mechanism itself — a hand-maintained prelude allowlist + lexical heuristic + double parse — is a scaffold I'd like to see scoped with a real dissolution trigger before it accretes more cases.

@briansrls

Copy link
Copy Markdown
Contributor Author

Review metadata

  • Provider / model: cursor / composer-2
  • Commit: 803a225d · Trigger: schedule
  • Comparison: origin/main @ fbbcf094 ... review/pr-1286-803a225d @ 803a225d
  • Thinking: 19s wall

Findings

None. Nothing in the diff clearly breaks INVARIANTS.md, docs/modeling-discipline.md, CODING.md, or TESTING.md. The change is implementation (surface parse / generated parser), not substrate on the Dag. Ambiguity is handled with an explicit two-pass pass and a documented, conservative prelude identifier list plus module LHS/import names (parse_parser_body.txt / parse_generated.rs around the new PRELUDE_BARE_RHS_ALIAS_IDENTS and collect_bare_rhs_alias_reference_idents), which matches normal Rust engineering rather than duplicate substrate authority. Fail-closed behavior is not weakened in an obvious way: unknown bare RHS idents intentionally follow the sum path where alias targets are not known (m2_substrate_inhabitance_test.rs around parse_bare_rhs_alias_reference_stays_type_alias). Tests target parse behavior and use the pipeline entry appropriate for parser work (TESTING.md “when compile IS the unit”).

Verdict

APPROVE — Scoped parser fix, mirrored regen output, corpus manifest refresh, and focused integration tests; no rubric violation tied to a specific diff hunk.

Exploratory observations (optional)

Maintaining PRELUDE_BARE_RHS_ALIAS_IDENTS is ongoing ergonomics whenever a new bootstrap-only alias RHS appears; the comment already states how to extend it. Process gates (e.g. PR-description receipts per INVARIANTS.md dispatch discipline) are outside what this diff can show.

…iant-sum-parser

# Conflicts:
#	src/v3/compiler/src/bootstrap_generated.rs
#	src/v3/compiler/src/bootstrap_generated_without_parse_surface.rs
@briansrls

Copy link
Copy Markdown
Contributor Author

Review metadata

  • Provider / model: codex / codex-default
  • Commit: bdafd366 · Trigger: schedule
  • Comparison: origin/main @ cb3a3c33 ... review/pr-1286-bdafd366 @ bdafd366
  • Thinking: 159s wall

Findings

  • src/v3/compiler/parse_parser_body.txt:98 inserts every type declaration’s generic parameter into one module-wide alias-reference set. That leaks local binders across declarations: after type Box<T> = T, a later unrelated type Bad = T will be parsed as a TypeAlias because T is now “known,” even though it is not in scope there. This violates the Boundary Discipline / single-authority scoping expectation: a local generic binder is being promoted into module metadata. The existing regression covers type Id<T> = T, but not this cross-declaration leak.

Verdict: REQUEST_CHANGES. The parser fix is directionally right and the added focused tests pass, but the generic-binder collection needs to be scoped to the declaration being disambiguated rather than stored as a module-wide alias authority.

@briansrls briansrls left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review metadata

  • Provider / model: codex / unknown
  • Commit: bdafd366 · Trigger: schedule
  • Thinking: 381s wall

BLOCKING (1)

Root Cause

  • src/v3/compiler/parse_parser_body.txt the bare-RHS disambiguator collapses declaration-local generic binders into module-level parser state → make the type-parameter exception local to the declaration being parsed or carry an explicit scoped ambiguity fact into pass 2

⚠️ The prior issues are fixed, but the new scoped-generic leakage can choose the wrong surface item for valid single-variant sums.

Comment thread src/v3/compiler/parse_parser_body.txt Outdated
} => {
out.insert(name.clone());
for param in type_params {
out.insert(param.clone());

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

BLOCKING: Type parameters from one declaration are inserted into a module-global alias set, so a later bare single-variant sum whose constructor matches any earlier generic parameter is misparsed as TypeAlias; this loses the scoped binder fact and violates Facts Flow Forward.

@briansrls

Copy link
Copy Markdown
Contributor Author

Verified against current head 73212ad38: this finding targeted an earlier stale generated-parser state. Current regen_parse_emit.rs emits std::collections::HashSet, current parse_generated.rs includes the matching import, and the SG-2 parser authority ratchet passes locally: cargo test -p v3-compiler --test integration sg2_parse_authority_test::parse_generated_module_matches_checked_in_snapshot -- --nocapture. No additional commit indicated for this comment. — sent from eager-ram-167

@briansrls

Copy link
Copy Markdown
Contributor Author

Verified against current head 73212ad38: this blocking comment is stale. bare_rhs_alias_reference_idents is read by rhs_opens_single_variant_sum_shape in both source and generated parser (parse_parser_body.txt around the let Some(refs) = &self.bare_rhs_alias_reference_idents check, mirrored in parse_generated.rs). The scoped generic-binder leak was also fixed in this head by passing declaration-local type params only into RHS lookahead, with a regression covering type Id<T> = T followed by unrelated type Bad = T. No additional commit indicated for this comment. — sent from eager-ram-167

@briansrls

Copy link
Copy Markdown
Contributor Author

Review metadata

  • Provider / model: codex / codex-default
  • Commit: 73212ad3 · Trigger: schedule
  • Comparison: origin/main @ f5acbbd2 ... review/pr-1286-73212ad3 @ 73212ad3
  • Thinking: 164s wall

Findings

  • src/v3/compiler/parse_parser_body.txt:110 / src/v3/compiler/parse_parser_body.txt:1132 — parse() now does a full first pass with bare_rhs_alias_reference_idents: None, and that mode makes bare type T = Only look like an alias, not a single-variant sum. That means type T inhabits Algebra = Only is rejected during pass 1 before pass 2 can classify Only as an unresolved bare variant. This leaves the PR’s new bare single-variant sum behavior incomplete and misses the inhabits case. This is a behavior/test gap against TESTING.md’s regression-test discipline for the changed parser surface.

Verdict: REQUEST_CHANGES

The implementation covers record payload and plain bare nullary sums, but the first pass can still fail a valid bare nullary sum when inhabits is present. I ran cargo test -p v3-compiler parse_single_variant_sum_record_payload_without_pipe --test integration; the added record-payload case passes.

@briansrls
briansrls merged commit ff1795b into main Apr 30, 2026
4 checks passed
briansrls added a commit that referenced this pull request May 1, 2026
Add a planning-only table that maps phases P0–P3 and TC3 handoff to
existing sections (dispatch preconditions, STOP, DB-8, acceptance).
Clarify that #1275/#1286 on main do not clear Evaluator or lens gates.

Made-with: Cursor
briansrls added a commit that referenced this pull request May 6, 2026
…/tests

- Add go_native_int TypeRealization (carrier "int") and TargetIntegerTypeInhabitance
  row go_integer_inhabit_int_platform_dependent with unit PlatformDependentFact per
  design-emission-model Q1 (Track B; no UInt128/u128 widening).
- Crate docs: clarify ScratchIntExamples ceiling vs match_bound Track B predicate,
  #1133/#1286 program-bound gate, Pattern D STOP+PING on opportunistic width rows.
- fold.rs: Track B module notes; match_bound unit tests; allow(dead_code) on
  BoundDeclarationView::PlatformDependent until LanguageSpec supplies program facts.
- Regenerate bootstrap snapshots + SG-2 parse manifest for go.dag churn.

Co-authored-by: Cursor <cursoragent@cursor.com>
briansrls added a commit that referenced this pull request May 7, 2026
…on-fold receipts (#1913)

* docs(s7): Phase 1 BoundDeclarationView/match_bound inventory (fold.rs)

Co-authored-by: Cursor <cursoragent@cursor.com>

* docs(s7): clarify inventory authority, Track A/B gates, Pattern D

Co-authored-by: Cursor <cursoragent@cursor.com>

* docs(s7): auditable merge-base receipt for S7 worker brief (blob SHA)

Co-authored-by: Cursor <cursoragent@cursor.com>

* docs(s7): Q1/P1 gate — PlatformDependent ≠ wider StaticBoundFact

Co-authored-by: Cursor <cursoragent@cursor.com>

* docs(s7): inventory — PlatformDependent arm is DiffersKind placeholder until Track B

Co-authored-by: Cursor <cursoragent@cursor.com>

* feat(std): ReferenceModel<T> phantom carrier (S7 PR-F Phase 2)

Co-authored-by: Cursor <cursoragent@cursor.com>

* test(v3): refresh SG-2 parse corpus manifest for types.dag +ReferenceModel

Co-authored-by: Cursor <cursoragent@cursor.com>

* WIP: loyal-wolf-828

* WIP: loyal-wolf-828

* fix(v3): refresh SG-2 parse manifest after authority drift

Update fnv/debug_bytes for dsl/std/types.dag (ReferenceModel commentary)
and src/v3/std/emit_model.dag so handwritten_parse_snapshot_matches_manifest
matches the committed corpus.

Co-authored-by: Cursor <cursoragent@cursor.com>

* WIP: loyal-wolf-828

* fix(v3): extend TargetIntegerInhabitanceBound coproduct expectations for CI

substrate_coproducts_match_runtime_carriers compares lowered sum variants to
emit_model; PlatformDependentFact is unit like BoundUnspecified.

Co-authored-by: Cursor <cursoragent@cursor.com>

* feat(s7): Go native int PlatformDependentFact row + Phase 1 fold docs/tests

- Add go_native_int TypeRealization (carrier "int") and TargetIntegerTypeInhabitance
  row go_integer_inhabit_int_platform_dependent with unit PlatformDependentFact per
  design-emission-model Q1 (Track B; no UInt128/u128 widening).
- Crate docs: clarify ScratchIntExamples ceiling vs match_bound Track B predicate,
  #1133/#1286 program-bound gate, Pattern D STOP+PING on opportunistic width rows.
- fold.rs: Track B module notes; match_bound unit tests; allow(dead_code) on
  BoundDeclarationView::PlatformDependent until LanguageSpec supplies program facts.
- Regenerate bootstrap snapshots + SG-2 parse manifest for go.dag churn.

Co-authored-by: Cursor <cursoragent@cursor.com>

* test(v3): assert Go PlatformDependentFact inhabitance row in bootstrap

Structural Slice B receipt: Track B native Go `int` row must stay populated.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(v3): satisfy clippy manual_contains in inhabitance test

Use Vec::contains for the Go native int row check so
cargo clippy -p v3-compiler --all-targets -- -D warnings passes in CI.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(v3/go): drop duplicate go_native_int TypeRealization for Int

go_int is the single Go TypeRealization for substrate Int; the
platform-dependent inhabitance row must reference it so the Go
emitter's duplicate-realization map stays single-authority.

Removes go_native_int and points go_integer_inhabit_int_platform_dependent
at go_int; regen bootstrap + parse manifest.

Co-authored-by: Cursor <cursoragent@cursor.com>

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
briansrls added a commit that referenced this pull request May 9, 2026
…bitance (#1980) (#2279)

* feat(v3): retire coercion-fold ScratchIntExamples; SelectedTargetInhabitance

Remove LanguageSpecProjection::ScratchIntExamples and IntScratchExample.
fold_program_to_target now only handles Undeclared and DeclaredIntegerIntents;
selected rows return SelectedTargetInhabitance { type_realization: DeclarationId }
instead of the lane-local primitive enum.

Rewrite crate tests to exercise design-doc scenarios through declared integer
intents. Update emit_model.dag comments and grounding Coercion-Fold stratum_b
assertions.

Run full-bootstrap grounding tests on a larger stack thread to avoid overflow
on default test stacks (emission_diagnostic_lockstep and stratum_b).

Co-authored-by: Brian Searls <briansrls@users.noreply.github.com>

* fix(v3/coercion-fold): gate type_realization on TypeRealization meta-tag

Follow-up on PR review: after selecting an inhabitance row, require
declaration_opt + meta_tag == dag.type_realization_meta() before returning
SelectedTargetInhabitance so malformed substrate rows fail closed under
UnderRefined("target_integer_type_realization") (or TypeRealization_meta).

Add regression tests for rust_u32 pass, UInt32 rejection, bogus id rejection.

Co-authored-by: Brian Searls <briansrls@users.noreply.github.com>

* docs(v3): note TypeRealization meta gate for inhabitance type_realization

TargetIntegerTypeInhabitance.type_realization stays DeclarationRef in emit_model;
document that coercion-fold validates TypeRealization meta-tag at selection time
(already enforced in validate_type_realization_reference).

Co-authored-by: Brian Searls <briansrls@users.noreply.github.com>

* chore(v3): regen bootstrap snapshots after emit_model.dag edits

Comments on TargetIntegerTypeInhabitance shifted parse spans; committed
snapshots drifted bootstrap verify (CI ci job).

Co-authored-by: Brian Searls <briansrls@users.noreply.github.com>

* refactor(v3/tests): opt-in DeclarationId witness helper for coercion-fold

Add compiler feature declaration-id-raw-test-witness with
DeclarationId::declaration_id_raw_for_testing; enable from
v3-grounding-coercion-fold dev-dependencies and replace unsafe transmute
in row-count gate tests.

Non-production opt-in avoids widening the default ctor surface.

Co-authored-by: Brian Searls <briansrls@users.noreply.github.com>

* chore(v3): refresh parse_corpus_manifest for emit_model.dag edits

SG-2 handwritten_parse_snapshot_matches_manifest ratchet: emit_model comments
shifted snapshot hash (fnv1a64).

Co-authored-by: Brian Searls <briansrls@users.noreply.github.com>

* refactor(v3/coercion-fold): seal SelectedTargetInhabitance + doc yellow scope

- Private type_realization field with accessor; pub(crate)
  from_validated_type_realization only after the TypeRealization gate (addresses
  APPROVE_WITH_COMMENTS carrier proof concern).
- LanguageSpecProjection / residual YELLOW: name #1980 scratch retirement,
  Undeclared, and IntegerTargetIntent + BoundDeclaration dissolution (#1133 / #1286).
- Tests compare SelectedTargetInhabitance via type_realization() only.

Co-authored-by: Brian Searls <briansrls@users.noreply.github.com>

* test(v3/coercion-fold): note deferred Examples 1/5 vs declared projection

APPROVE exploratory: no Rust consumers of ScratchIntExamples; Examples 1 and 5
axes need #1133 / #1286 facts IntegerTargetIntent cannot omit today.

Co-authored-by: Brian Searls <briansrls@users.noreply.github.com>

* fix(v3/coercion-fold): align TypeRealization payload with inhabitance row

validate_type_realization_matches_inhabitance_row now parses substrate
TypeRealization.language/target and requires they match row.language and
row.kernel_integer (plus meta tag), blocking cross-language or wrong-kernel
references on malformed rows (E-6 / FAIL-CLOSED).

Expand gate tests: language mismatch, kernel vs realization target mismatch.

Co-authored-by: Brian Searls <briansrls@users.noreply.github.com>

* refactor(v3/coercion-fold): unify row gate + SelectedTarget mint

Respond to exploratory review (opus): selected_target_after_row_type_realization_gate
returns SelectedTargetInhabitance directly so validation and construction cannot
split; from_validated_type_realization stays a single callee.

Co-authored-by: Brian Searls <briansrls@users.noreply.github.com>

* refactor(v3): dedupe UnderRefined axis for type-realization gate

Co-authored-by: Brian Searls <briansrls@users.noreply.github.com>

---------

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Brian Searls <briansrls@users.noreply.github.com>
@briansrls

Copy link
Copy Markdown
Contributor Author

Review metadata

  • Provider / model: openai-pro / gpt-5-5-pro
  • Commit: fbd9c541 · Trigger: manual
  • Conversation: View conversation

1. Story of the diff

This PR teaches the v3 parser to recognize single-variant sum RHS forms that do not contain a top-level |. The mechanism is a two-pass parse: pass 1 parses without bare-RHS sum disambiguation, then collect_bare_rhs_alias_reference_idents builds a set of names that should remain aliases, and pass 2 uses that set so Variant { ... }, Variant(...), and unknown bare RHS names can classify as TypeSum rather than TypeAlias. The parser-body source, generated parser, and regen emitter are kept in sync by adding the same HashSet dependency path through parse_parser_body.txt, parse_generated.rs, and regen_parse_emit.rs. The tests add coverage for the motivating record-payload case, alias preservation for imports/sibling declarations, bare nullary sum parsing, and optional leading |.

2. Invariant categories

  1. LAYER MODEL (substrate vs implementation).

N/A — this diff is parser implementation / generated-parser maintenance only; it does not add substrate types, Dag fields, Dag mutation APIs, or new substrate variants.

  1. INVARIANTS.md + modeling-discipline.md.

Finding — BLOCKING, P2 Boundary Discipline / facts flow forward + P3 Fail-Closed. src/v3/compiler/src/parse_generated.rs:113-120 collects alias-reference names from imports and top-level type / data declarations, but it drops the current declaration’s type parameters; then src/v3/compiler/src/parse_generated.rs:1176-1185 classifies any bare RHS label not in that set as a single-variant sum at an RHS boundary. That means a valid generic alias such as type Id<T> = T is now parsed as TypeSum { variants: [T] } instead of a TypeAlias to the local type parameter. The local binder fact exists at the parse boundary but does not flow into the disambiguator, producing a plausible wrong AST rather than preserving the alias shape. The fix should make the disambiguation check aware of the current type declaration’s generic parameters, not just module/import/prelude names.

  1. CODING.md.

Compliant, with the invariant bug above. The new helper is a free function over explicit inputs (collect_bare_rhs_alias_reference_idents(items: &[SurfaceItem]) -> HashSet<String> at src/v3/compiler/src/parse_generated.rs:101), and parse errors still propagate through ? in both passes (src/v3/compiler/src/parse_generated.rs:137-138), which matches the data + functions and typed-error conventions.

  1. TESTING.md.

Finding — BLOCKING regression gap tied to the same parser path. The new alias-preservation regression covers imported names and sibling declarations (src/v3/compiler/tests/integration/m2_substrate_inhabitance_test.rs:1685-1689), but it does not cover a generic alias to a local type parameter. Because the production alias-reference set omits type params, a focused parser test like type Id<T> = T would currently catch the regression. This is the right test level — parser behavior — but the alias-preservation matrix is missing one of the binders introduced by the same type Name<T> = ... grammar this code is modifying.

  1. LOCKED DESIGN DECISIONS.

N/A — the diff does not reference or alter a locked thesis/design decision; it changes parser disambiguation and matching tests only.

  1. TRACKED vs UNTRACKED DEBT.

N/A — no new TODO, staged scaffold, compatibility shim, or temporary migration surface is introduced in the diff. The hard-coded prelude alias list is a parser disambiguation table rather than a marked bridge, so I would not classify it as tracked/untracked debt from this diff alone.

3. Verdict

REQUEST_CHANGES. The intended single-variant sum support is directionally sound, and the tests cover the motivating cases, but the two-pass alias-reference authority currently omits local type parameters and will misparse generic aliases such as type Id<T> = T. That is a user-facing parser regression and should be fixed with a matching regression test before merge.

@briansrls

Copy link
Copy Markdown
Contributor Author

Review metadata

  • Provider / model: openai-pro / gpt-5-5-pro
  • Commit: bdafd366 · Trigger: manual
  • Conversation: View conversation

1. Story of the diff

This PR changes the v3 parser’s type Name = ... disambiguation so a RHS that is a single sum variant no longer requires a top-level |. The mechanism is a two-pass parse: pass 1 parses with bare-RHS single-variant detection disabled, collects names that should remain alias targets, then pass 2 uses that reference set so Variant { ... }, Variant(...), and unknown bare identifiers at RHS end become TypeSum, while known aliases/imports/sibling declarations remain TypeAlias (src/v3/compiler/parse_parser_body.txt:112-130, src/v3/compiler/parse_parser_body.txt:1125-1156). The generated parser and regen emitter are kept in sync (src/v3/compiler/src/parse_generated.rs:57-138, src/v3/compiler/src/regen_parse_emit.rs:97), bootstrap snapshots refresh the formerly-unresolved single-variant substrate shapes into TypeConnective::Disj, and tests add parser-level regressions for record-payload single variants, alias preservation, and leading pipes.

2. Invariant categories

1. LAYER MODEL — substrate vs implementation

Compliant — the hand-authored change is parser implementation, and the substrate-looking updates are generated snapshot receipts of the parse fix: e.g. BranchEmitParticipation now materializes as a one-variant TypeConnective::Disj at src/v3/compiler/src/bootstrap_generated.rs:730, with the variant payload represented structurally as an empty Conj at src/v3/compiler/src/bootstrap_generated.rs:1616.

2. INVARIANTS.md + modeling-discipline.md

Finding — BLOCKING, Boundary Discipline / facts flow forward. src/v3/compiler/parse_parser_body.txt:98-99 inserts every declaration’s type_params into one module-wide alias-reference set, and src/v3/compiler/parse_parser_body.txt:1146-1147 then treats membership in that set as proof that a bare RHS is an alias. That leaks a declaration-local binder into unrelated declarations: after type Id<T> = T, a later type Other = T would be parsed as an alias to out-of-scope T rather than as the PR’s new bare single-variant sum case. Keep module-level alias targets separate from the current declaration’s local generic binders, or pass a per-declaration scoped alias set into the RHS lookahead.

3. CODING.md

Finding — BLOCKING, clear interfaces / precise carriers. src/v3/compiler/parse_parser_body.txt:73 exposes collect_bare_rhs_alias_reference_idents(items: &[SurfaceItem]) -> HashSet<String>, but the single HashSet<String> carrier cannot distinguish module-scope names from declaration-local type parameters; the problematic insertion at src/v3/compiler/parse_parser_body.txt:98-99 follows from that too-coarse interface. A scoped carrier, or adding only the current declaration’s type_params at the point of parsing its RHS, would make the contract match the behavior.

4. TESTING.md

Finding — BLOCKING, regression coverage for the fix. The added alias-preservation fixture covers the positive generic-alias case with type Id<T> = T at src/v3/compiler/tests/integration/m2_substrate_inhabitance_test.rs:1690, but it does not cover the adjacent negative case where that T must not become module-global. Add a focused regression such as type Id<T> = T followed by type Other = T, asserting that Other follows the intended unknown-bare-RHS path rather than TypeAlias.

5. LOCKED DESIGN DECISIONS

N/A — the diff does not reference or alter a locked thesis/design decision; the substrate snapshot changes are generated consequences of the parser fix rather than an explicit design divergence.

6. TRACKED vs UNTRACKED DEBT

Finding — NON-BLOCKING, scaffold tracking. src/v3/compiler/parse_parser_body.txt:33 introduces a hard-coded PRELUDE_BARE_RHS_ALIAS_IDENTS table, and the comment says to “expand it when integration tests surface” missed aliases at src/v3/compiler/parse_parser_body.txt:30-32. That is documented and bounded to bootstrap/prelude spellings, but it lacks a named dissolution trigger back to a canonical declared prelude/import authority. Please either name that trigger in the comment/debt row or route the parser through an existing canonical source if one is available.

3. Verdict

REQUEST_CHANGES — the main parser approach is sensible and well-covered for the headline behavior, but the module-wide alias set currently turns local generic binders into global alias authorities. That is a parser correctness regression in the new disambiguation path, and it should be fixed with a scoped carrier plus the missing negative test before merge.

@briansrls
briansrls deleted the feat/pr-a3-single-variant-sum-parser branch June 1, 2026 18:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant