Skip to content

fix(wasm): recover tuple pointer element type through the ? boundary#78

Merged
nelsonduarte merged 1 commit into
mainfrom
fix/wasm-tuple-pointer-through-try
Jul 15, 2026
Merged

fix(wasm): recover tuple pointer element type through the ? boundary#78
nelsonduarte merged 1 commit into
mainfrom
fix/wasm-tuple-pointer-through-try

Conversation

@nelsonduarte

Copy link
Copy Markdown
Owner

Problem

A tuple with a pointer-shaped element (Map/List/Set/struct) returned through a ?/Result boundary and destructured (let (m, s) = f()?) passed capa --check and ran on the Python backend but emitted INVALID Wasm ("type mismatch: expected i32, found i64"). A silent miscompile: only the Wasm validator caught it. Found by dogfooding a TOML parser.

Root cause (two parts)

  1. _lower_try defaulted the ? result type to Unknown, so the tuple binders bound as Unknown; the Wasm tuple emitter then sized a Map element as i64 while it is an i32 heap pointer.
  2. That surfaced a second, previously-masked bug: _emit_try_unwrap's pointer-payload decode did not recognize a tuple payload, loading the i32 payload as a bare i64.load.

Fix

  • _lower_try recovers the unwrapped payload type from the operand's Result<T,E>/Option<T> via a new _unwrap_try_payload_ty helper, so tuple binders get their precise pointer type.
  • _emit_try_unwrap uses the existing _is_pointer_shape_ty predicate (covers tuples).
  • Defense in depth: the tuple emitter's i64 fallthrough now raises a clear WasmEmissionError when a pointer-shaped value reaches an unresolved slot, instead of silently emitting invalid Wasm. Scoped so it never fires on Int/Bool/Float tuple elements.

Tests

New TestWasmTuplePointerElementThroughTry + parity class covering Map/List/match Ok((m,s)) through ?, plus pure emitter unit tests for the guard (not skip-guarded, so the no-wasm CI job runs them). Full suite: 4221 passed, 18 skipped, exit 0. Adversarially reviewed (guard scoping + no regression to the pre-existing match-form/struct-in-tuple/bare-pointer paths).

A tuple whose element is pointer-shaped (Map / List / Set) returned
through a ?/Result boundary and destructured lost its element type on
the Wasm backend, emitting invalid Wasm. It passed --check and ran on
the Python backend, so only the Wasm validator rejected it.

_lower_try defaulted the ? result type to Unknown when the analyzer
left the Try node untyped. That Unknown flowed into the let-tuple
binders, so the Wasm tuple emitter sized a Map element as an i64 slot
while a Map is an i32 heap pointer, tripping the validator.

Fixes:
- _lower_try now recovers the unwrapped payload from the operand's
  Result<T,E> / Option<T> type via _unwrap_try_payload_ty, so the
  binders carry the precise pointer type.
- _emit_try_unwrap now decodes any pointer-shaped payload (including
  a tuple) via _is_pointer_shape_ty instead of an ad-hoc
  struct/sum/List/Map/Set check that missed tuples.
- The tuple slot emitter (store and index) now fails loud with a
  clear diagnostic when a pointer-shaped value reaches an unresolved
  slot type, so a future type-propagation gap can never again ship as
  invalid Wasm. Scoped to fire only on pointer-shaped values, so
  Int/Bool/Float elements are unaffected.

Adds regression tests on both backends for the let-destructure and
match-Ok forms plus the fail-loud guard.
@nelsonduarte
nelsonduarte merged commit ddb27cc into main Jul 15, 2026
14 checks passed
@nelsonduarte
nelsonduarte deleted the fix/wasm-tuple-pointer-through-try branch July 15, 2026 21:25
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