Skip to content

wasm-gc: tuple elements keep generic payload types; Char.toCode returns Unicode scalars#504

Merged
jasisz merged 1 commit into
mainfrom
wasmgc-tuple-generics-char-scalar
Jun 12, 2026
Merged

wasm-gc: tuple elements keep generic payload types; Char.toCode returns Unicode scalars#504
jasisz merged 1 commit into
mainfrom
wasmgc-tuple-generics-char-scalar

Conversation

@jasisz

@jasisz jasisz commented Jun 12, 2026

Copy link
Copy Markdown
Owner

Fixes the two wasm-gc residuals documented in the 0.25.0 changelog, plus a type-checker bug the repro matrix surfaced.

What was broken

  • Tuple<Option<Int>, Int> with an Option.None branch failed to compile on wasm-gc with cannot lower type `Option<T>` to a wasm representation — the None literal nested in a tuple element never inherited the concrete payload type. Same for tuples holding Result (verify expectations like (Result.Ok(2), Result.Ok(0))). VM ran both fine.
  • Char.toCode on wasm-gc returned the first UTF-8 byte instead of the Unicode scalar: 'ż' gave 197 instead of 380, '😀' gave 240 instead of 128512. ASCII masked the bug.
  • Bonus from the discriminator probes: (Result.Ok(n), n) against a declared Tuple<Result<Int,String>, Int> was a false type error even on the VM.

The fix

  • Tuple literals in driven positions (declared return, annotated binding, verify expectation) adopt the expected Tuple<...> element-wise — the same bidirectional context list elements already receive. Side effect, now documented in the changelog: an element that doesn't fit the annotation is rejected at check time instead of failing at run time.
  • Char.toCode lowers through a demand-driven helper that decodes the first UTF-8 scalar (2/3/4-byte lead masks per spec). Modules that never call it are byte-identical to before.
  • The expected-type path deliberately excludes (...)! products, so their "element must be a function call" validation still fires — pinned by new type-checker tests (this was caught in adversarial review of the first version of this change).

Verification

  • Repro matrix of 19 probes (16 canonical + 3 discriminators), every probe VM-green; failing rows now pass on wasm-gc; the Some-only controls confirm no behavior change where things already worked.
  • Adversarial review in a fresh worktree: 13 extra probes (4-byte emoji, multi-byte char equality, empty strings, deep tuple nesting, two-module program sharing the same tuple instantiation, legal (f(), g())!), byte-reproducibility cmp 3×, zero drift on untouched modules (order_total.wasm byte-identical), test honesty revert-checked (new tests fail without the source fix).
  • Independent second-opinion review (different model family) cross-checked the UTF-8 mask math and enumerated checker entry points; its remaining finding — hand-written == between such tuples outside verify — stays a documented residual rather than a speculative fix.
  • cargo test --workspace --features wasm,wasip2 (80 suites), fmt, clippy: green. wasip2 smoke compile with Char.toCode: green.

🤖 Generated with Claude Code

…ns Unicode scalars

Two backend fixes plus a type-checker fix they surfaced:

- A tuple literal in a driven position (declared return, annotated
  binding, verify expectation) now adopts the expected Tuple<...> type
  element-wise, so generic constructors in tuple slots - (Option.None, n),
  (Result.Ok(2), Result.Ok(0)) - keep their concrete payload types
  instead of reaching the wasm-gc lowerer as Option<T> / Result<Int,E>.
  The same change fixes a false type error on (Result.Ok(n), n) against
  a declared Tuple<Result<Int,String>, Int>.
- Char.toCode on wasm-gc decodes the first UTF-8 scalar instead of
  returning the first byte, so multi-byte characters match the VM and
  roundtrip through Char.fromCode. Lowering moved from an inline MIR arm
  to a demand-driven helper; modules that never call it are unchanged
  byte-for-byte.
- Independent products keep their element validation: the expected-type
  path deliberately excludes (...)! so a non-call element is still
  rejected at check time (pinned by new type-checker tests).

Known residuals stay documented: a hand-written == between such tuples
outside a verify block, and reading back compound Option values from
Vector<Option<T>> in verify diagnostics.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@jasisz jasisz merged commit 24027fd into main Jun 12, 2026
5 checks passed
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