Skip to content

fix: compare optional payloads structurally, and finish the where-clause edges - #814

Merged
kacy merged 1 commit into
mainfrom
fix-optional-eq-and-where-gaps
Aug 19, 2026
Merged

fix: compare optional payloads structurally, and finish the where-clause edges#814
kacy merged 1 commit into
mainfrom
fix-optional-eq-and-where-gaps

Conversation

@kacy

@kacy kacy commented Aug 19, 2026

Copy link
Copy Markdown
Owner

Two live defects found while reviewing the sprint's work, plus the where-clause edges the review surfaced.

optional payload equality

The widened T? == T compare (#807) — and the pre-existing T? == T? compare — lowered every non-string payload to raw i64 equality. For Bytes and boxed enums the register holds a pointer, so two equal-content values silently compared unequal:

ob: Bytes? := bytes.from_string_utf8("abc")
ob == bytes.from_string_utf8("abc")   # was false, now true
s: Shade? := Shade.Tinted(3)
s == Shade.Tinted(3)                  # was false, now true

ir_emit_inner_value_eq (shared by both compares) now dispatches exactly the way the bare type's == does in ir_emit_value_binary: strings through __str_eq, bytes through bytes_eq, boxed enums through their per-enum structural helper, everything else i64.

The generated __enum_eq_<name> also gained a null guard after its identity fast path: a none optional's payload slot hands the helper a 0, and the widened compare reads the slot without branching on the flag — one null side now answers false instead of faulting on the tag read. (bytes_eq was already null-safe.)

where-clause edges

  • A clause on an interface member signature parsed as expected kw_fn (E240) because parse_fn_sig never looked for it. The clause now parses through the same parse_optional_where_clause as functions and impl methods. Generic interface members themselves remain unsupported (E202 in the member's body, either bound spelling — pre-existing, now stated in docs/limitations.md); the invalid test pins that the clause reaches E202, not a parse stumble.
  • E264 reported at the token after the whole bound list; it now lands on the offending parameter name.

noticed, not fixed

Generic interface members (E202) and impls on generic instances not satisfying bounds (E226, from #812's review) are both pre-existing, documented, and orthogonal.

what was tested

  • New golden tests/cases/test_optional_payload_eq.pith: Bytes?/Bytes both orders, eq and neq, unequal content, Bytes? == Bytes?, boxed enum with payload and without, and the none-vs-variant case that faulted before the null guard (11 assertions).
  • tests/cases/test_where_clauses.pith extended: a clause on a function with no return type, inline+clause bounds merged on the same parameter, and where used as an ordinary identifier; golden regenerated.
  • New tests/invalid/where_interface_generic_member.pith (E202+E219); tests/invalid/where_unknown_param.pith confirms the E264 caret lands on the bad name.
  • make check-invalid-only (54 passed), make run-regressions-only (371 passed), make bootstrap-verify fixed point after make refresh-bootstrap-seed.
  • fmt/lint clean on all changed sources.

@kacy
kacy merged commit 0c9d2ab into main Aug 19, 2026
1 of 2 checks passed
@kacy
kacy deleted the fix-optional-eq-and-where-gaps branch August 19, 2026 15:55
kacy added a commit that referenced this pull request Aug 21, 2026
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