Skip to content

fix(interpreter): fresh mutable containers get heap identity, like literals - #143

Merged
ivarvong merged 1 commit into
mainfrom
fix/fresh-container-heap-identity
Jul 2, 2026
Merged

fix(interpreter): fresh mutable containers get heap identity, like literals#143
ivarvong merged 1 commit into
mainfrom
fix/fresh-container-heap-identity

Conversation

@ivarvong

@ivarvong ivarvong commented Jul 2, 2026

Copy link
Copy Markdown
Collaborator

Summary

set()/list()/dict() constructor results (and list(iterable)) returned bare values, so an in-place mutation reached through a reference mutated a throwaway copy. The flagship symptom, straight from the playground's search-index example:

index = {}
index.setdefault(w, set()).add(name)   # set stays empty — every lookup: no hits

Literals ({...}, []) already heap-allocate for identity; constructors now do too (handle_builtin_result allocs fresh mutable containers), so aliasing and mutation-through-intermediate-receiver behave like CPython.

  • Set in-place mutators (add/remove/discard/pop/clear/update/difference_update/…) move to the :ok_raw path so their {:mutate, new, ret} writeback reaches the heap object — the same wiring list.append and dict mutators already had.
  • list(...) clauses route through one alloc_list helper; {:iter_to_list, ...} results heap-alloc on the way out.
  • json.dumps grows a {:ref} deref in apply_default, so default=list (which now returns a ref) recurses into the concrete-value clauses instead of re-invoking default on the ref forever.
  • set_method's @spec widened for the :ok_raw shape (Dialyzer).

Six regression tests: the inverted-index setdefault(w, set()).add(...) idiom, dict()-via-setdefault subscript writes, list()/list(iterable) aliasing + mutability, set mutators through an alias, and the json.dumps(default=list) fixpoint.

Provenance

Authored in a parallel working session, whose differential CPython/VM/Wasm harness surfaced the bug; landed here with formatting, the spec fix, and tests. The harness itself ships separately with the wasm build config.

Test plan

  • mix format --check-formatted
  • mix compile --warnings-as-errors (modulo the three pre-existing 1.20.2-local warnings on main)
  • mix test — 6518/6519 (the one failure is the known local-OTP zipfile CRC-message artifact, present on main)
  • mix dialyzer — clean
  • CI green (watching)

🤖 Generated with Claude Code

https://claude.ai/code/session_01KuxYKoh8pXEna5ohZUgJsn

…terals

set()/list()/dict() constructor results (and list(iterable)) returned
bare values, so an in-place mutation reached through a reference mutated
a throwaway copy: `index.setdefault(w, set()).add(name)` left every set
in the dict empty — the classic inverted-index idiom silently produced
no hits. Literals ({...}, []) already heap-allocate; constructors now do
too, via handle_builtin_result, so aliasing and mutation-through-
intermediate-receiver behave like CPython.

Set in-place mutators (add/remove/discard/pop/clear/update/…) move to
the :ok_raw path so their {:mutate, new, ret} writeback reaches the heap
object — the same wiring list.append and dict mutators already had.
json.dumps grows a {:ref} deref in apply_default so default=list (which
now returns a ref) terminates instead of recursing on itself.

Six regression tests cover the setdefault idioms, constructor aliasing,
list(iterable) mutability, and the json.dumps(default=list) fixpoint.

This fix was authored in a parallel working session (its differential
CPython/VM/Wasm harness found the bug); this PR lands it with formatting,
a widened set_method spec for Dialyzer, and the regression tests.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KuxYKoh8pXEna5ohZUgJsn
@ivarvong
ivarvong merged commit 4b83843 into main Jul 2, 2026
8 checks passed
ivarvong added a commit that referenced this pull request Jul 7, 2026
…rg (#145)

* feat: OTel attributes= kwarg, WasmGC target, and the differential harness

Three pieces from the wasm working session, extracted onto main (the
container-identity fix landed separately as #143):

- opentelemetry: start_as_current_span honors the attributes= kwarg,
  seeding the span at open; merges with set_attribute calls. Two tests.
- Pyex.Wasm: the `mix wasm.build` entry point — pyrun/3 runs a program
  in a fresh Ctx against an in-memory VFS and returns stdout, footprint,
  final files, and BOTH runtime and app spans as JSON. The beam2wasm
  compiler is a sibling-checkout dev dep, loaded only when the checkout
  exists, so CI and consumers resolve deps without it (the wasm: project
  config is inert without the dep).
- test/differential: a three-executor harness (CPython · pyex-VM ·
  pyex-Wasm) that auto-localizes divergences — CPython≠VM is an
  interpreter bug, VM≠Wasm is a compiler bug — gated by a checked-in
  LEDGER of known divergences. Aliasing lens (constructor × storage ×
  mutation product) plus a 25-program realistic corpus; 79/79 green at
  time of extraction. Runs via `mix run test/differential/run.exs`
  (needs wasm/pyex.wasm and python3), not in `mix test`.

Also: span_cm loses a dead default arg (warning), and Wasm.pyrun's
rescue clause interpolates type-safely without Exception.message/1
(protocol dispatch the wasm target can't lower).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KuxYKoh8pXEna5ohZUgJsn

* drop the wasm target + harness: moving to the pyex.dev composition repo

The ../elixir_wasm path-based dev dep was too fragile to ship, even
guarded. The wasm entry point, build config, and differential harness
move to a third project that composes pyex + beam2wasm via git deps
and owns the pyex.dev site and edge API.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KuxYKoh8pXEna5ohZUgJsn

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
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