Skip to content

WASM: implement str() and repr(), fix print()'s float formatting - #332

Draft
martin-henz wants to merge 7 commits into
mainfrom
wasm-str-repr
Draft

martin-henz wants to merge 7 commits into
mainfrom
wasm-str-repr

Conversation

@martin-henz

Copy link
Copy Markdown
Member

Summary

Closes #323, which was originally filed as "repr() returns None instead of a string" but turned out to be larger in scope on re-investigation: str() didn't exist on WASM at all (NameError: name 'str' is not defined), and repr() was literally a copy of print()'s body — same void return, no string produced.

  • Added TO_STR_FX/TO_REPR_FX (new WASM functions, src/engines/wasm/runtime/stdlib.ts) that reuse LOG_FX's existing type-dispatch formatting by calling back into the exported log() and popping the rendered text — the same trick log_list already uses for its element rendering. The formatted text is then allocated as a real WASM string via the same malloc/encode/write/makeString sequence the tokenize host import uses (factored out into a shared allocateWasmString helper in hostImports.ts, also now used by tokenize itself instead of its own inline copy).
  • Wired str/repr into src/engines/wasm/library.ts's miscLib, replacing the broken repr entry.
  • repr() applies Python's exact string-quoting rules (single-quote-preferring, switches to double quotes when the string itself contains a ') by reusing escape() from src/stdlib/utils.ts (now exported) instead of reinventing it.

Two bugs found and fixed along the way

log_float formatting. wasm.import("console", "log").func("$_log_int") and wasm.import("console", "log").func("$_log_float") declared the same (module, field) import key, so both silently resolved to the same JS console.log at instantiation regardless of which local alias a call site referenced. $_log_float ended up bound to the int-formatting implementation (value.toString()), so print(5.0) rendered "5" instead of "5.0", and large floats never got exponential notation. Renamed the imports to distinct log_int/log_float fields and gave log_float its own implementation using toPythonFloat (the same function CSE's own toPythonString uses, now exported from stdlib/utils.ts), so WASM's float formatting now matches CSE exactly.

Shadow-stack leak (first implementation attempt). GET_LEX_ADDR_FX already pushes a fetched argument onto the shadow stack when it's GC'able (protecting it during any subsequent allocation before it's consumed). The first version of TO_STR_FX/TO_REPR_FX didn't account for this: it read the argument, computed a result, and returned a new string (itself pushed by MAKE_STRING_FX as a side effect) — leaving the original argument's shadow-stack entry never popped. That stray entry sat underneath the new result and threw off whatever GC'able-operand accounting the surrounding expression did next. Concretely, "[" + repr("hi") + "]" produced garbage (unrelated static string-table bytes) instead of "['hi']", because the + operator's string-concat branch unconditionally pops exactly two shadow-stack entries for its two operands and got the wrong ones. Fixed by popping the input's shadow-stack entry (if GC'able) before computing, mirroring the existing IS_INT_FX/IS_PAIR_FX/MAKE_PAIR_FX per-argument convention.

Test changes

  • src/tests/wasm/wasm-misc.spec.ts — new str() and repr() describe block: per-type formatting, quoting rules, and a regression test for the shadow-stack leak (string concatenation of a str()/repr() result).
  • src/tests/operator-conformance-wasm.test.ts — dropped the numeric-tolerance workaround for float results (previously needed only because of the log_float bug above); floats are now compared as exact text like every other type. Verified with a full 3933-case run.
  • src/tests/wasm-modules.test.ts — updated 3 expectations that had baked in the old float-formatting bug: imported numeric module values (DataType.NUMBER) map to Python floats at the WASM boundary (see moduleInterop.ts), so print() on an imported 42 now correctly renders "42.0", not "42".

Test plan

  • npx tsc --noEmit
  • yarn lint (targeted eslint on changed files)
  • yarn test — full suite, twice, both green (19234 passed / 3166 skipped)
  • operator-conformance-wasm.test.ts run standalone (3933 cases) with the tightened exact-text float comparison

🤖 Generated with Claude Code

https://claude.ai/code/session_01KrwJGug3rCXijRseRjys9V

repr() was a copy of print() that returned None instead of a string;
str() didn't exist at all (NameError). Both are now implemented via
new TO_STR_FX/TO_REPR_FX WASM functions that reuse LOG_FX's formatting
by calling back into the exported log() and allocating the rendered
text as a new WASM string (mirroring the tokenize host import's
malloc/encode/write/makeString sequence).

Two bugs surfaced along the way, both fixed:

- log_float shared its (module, field) import key with log_int
  ("console"/"log" for both), so both silently resolved to the same
  JS console.log at instantiation. print(5.0) rendered "5" instead of
  "5.0", and large floats never got exponential notation. Renamed to
  distinct log_int/log_float import fields and gave log_float its own
  Python-formatting implementation (reusing toPythonFloat, now
  exported from stdlib/utils.ts).

- The first str()/repr() implementation leaked a shadow-stack entry:
  GET_LEX_ADDR_FX already pushes a GC'able argument onto the shadow
  stack when fetching it, and the newly allocated result string pushed
  a second entry, but nothing popped the first. That stray entry
  corrupted whatever GC'able-operand accounting the surrounding
  expression did next (e.g. string concatenation reading garbage).
  Fixed by popping the input's shadow-stack entry before returning,
  mirroring IS_INT_FX/MAKE_PAIR_FX's existing per-argument convention.

Also updates operator-conformance-wasm.test.ts to drop its now-obsolete
numeric-tolerance workaround for float results (verified via a fresh
3933-case run with exact-text comparison) and fixes three
wasm-modules.test.ts expectations that had baked in the old float-
formatting bug (imported numeric module values are Python floats, so
print() on one now correctly renders "42.0" instead of "42").

Closes #323.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KrwJGug3rCXijRseRjys9V
@gemini-code-assist

Copy link
Copy Markdown
Contributor

Caution

The consumer version of Gemini Code Assist on GitHub has been sunset. All code review activity has officially ceased.

@martin-henz

Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 23, 2026 •

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai

coderabbitai Bot commented Jul 23, 2026 •

Copy link
Copy Markdown

Review Change Stack

Walkthrough

WASM now supports str() and repr() through runtime conversion intrinsics and JavaScript host imports. String allocation is centralized, integer and float logging are separated, tokenization reuses the allocator, and tests cover formatting, concatenation, imports, and numeric comparisons.

Changes

WASM string conversion

Layer / File(s) Summary
Host formatting and string allocation
src/engines/wasm/hostImports.ts, src/engines/wasm/builderGenerator.ts, src/stdlib/utils.ts
Adds host conversion imports, centralized UTF-8 WASM string allocation, separate integer/float logging, and str/repr host formatting. Tokenization reuses the allocator.
Runtime conversion intrinsics and library wiring
src/engines/wasm/runtime/stdlib.ts, src/engines/wasm/runtime/index.ts, src/engines/wasm/library.ts
Adds GC-safe TO_STR_FX and TO_REPR_FX runtime functions and exposes them through non-void str and repr library functions.
Formatting and integration validation
src/tests/wasm/wasm-misc.spec.ts, src/tests/wasm-modules.test.ts, src/tests/operator-conformance-wasm.test.ts
Tests string representations, quote selection, concatenation, float formatting, imported numeric output, and type-specific comparisons.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Library as miscLib.str/repr
  participant Runtime as TO_STR_FX/TO_REPR_FX
  participant Host as stringify.to_str/to_repr
  participant Output as runtime.output
  participant Memory as allocateWasmString
  Library->>Runtime: pass tagged WASM value
  Runtime->>Host: invoke host conversion
  Host->>Output: render and retrieve text
  Host->>Memory: encode and allocate result
  Memory-->>Library: return WASM string
Loading

Suggested reviewers: jonasongg

Poem

A rabbit hops through WASM bright,
Strings bloom in memory light.
str and repr now neatly sing,
Floats wear the proper ring.
Tests thump paws: “All right!”

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main WASM stringification and float-formatting changes.
Description check ✅ Passed The description is clearly aligned with the PR and explains the str/repr and float-formatting work in detail.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch wasm-str-repr

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

ESLint install timed out. The project may have too many dependencies for the sandbox.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@martin-henz

Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 23, 2026 •

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/engines/wasm/hostImports.ts`:
- Around line 144-150: Update the to_repr host import and its
collection-rendering path so nested string elements use repr semantics,
producing quoted values such as ['hi'] while preserving str output such as [hi].
Propagate repr awareness through log_list and child log_string calls, and add
regression coverage for repr and str on collections.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: da813468-5f8a-4d11-b359-0ed6aee78214

📥 Commits

Reviewing files that changed from the base of the PR and between 1bc9a77 and 1a92dc9.

📒 Files selected for processing (9)
  • src/engines/wasm/builderGenerator.ts
  • src/engines/wasm/hostImports.ts
  • src/engines/wasm/library.ts
  • src/engines/wasm/runtime/index.ts
  • src/engines/wasm/runtime/stdlib.ts
  • src/stdlib/utils.ts
  • src/tests/operator-conformance-wasm.test.ts
  • src/tests/wasm-modules.test.ts
  • src/tests/wasm/wasm-misc.spec.ts

Comment on lines +144 to +150
to_repr: (tag: number, value: bigint): [number, bigint] => {
if (!runtime.wasmExports) throw new Error("WASM exports not initialised");
runtime.wasmExports.log(tag, value);
const rendered = runtime.output.pop();
if (rendered === undefined) throw new Error("repr() logging did not produce rendered text");
const text = tag === TYPE_TAG.STRING ? escape(rendered) : rendered;
return allocateWasmString(runtime.wasmExports, memory, text);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

Render collection elements with repr semantics.

Line 149 only quotes a top-level string. repr(["hi"]) and str(["hi"]) flow through log_list, whose child log_string calls produce [hi] rather than Python’s ['hi']. Add recursive repr-aware collection rendering and regression coverage.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/engines/wasm/hostImports.ts` around lines 144 - 150, Update the to_repr
host import and its collection-rendering path so nested string elements use repr
semantics, producing quoted values such as ['hi'] while preserving str output
such as [hi]. Propagate repr awareness through log_list and child log_string
calls, and add regression coverage for repr and str on collections.

@martin-henz
martin-henz marked this pull request as draft July 26, 2026 13:54

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

WASM: repr() doesn't return a string — implemented as a print() copy

1 participant