Skip to content

refactor(test): use <+ template writing in test files - #4111

Merged
bobzhang merged 1 commit into
mainfrom
simplify-template-writing-tests
Aug 19, 2026
Merged

refactor(test): use <+ template writing in test files#4111
bobzhang merged 1 commit into
mainfrom
simplify-template-writing-tests

Conversation

@bobzhang

Copy link
Copy Markdown
Contributor

Part 1 of 2, split out of #4110. Test and benchmark files only — no
library code is touched, and no expected output changes.

18 files, −85 / +52 lines.

What changes

  • buf.write_string("...\{x}...")buf <+ "...\{x}...". The old form built
    a throwaway StringBuilder + String for the interpolation and then copied
    the result into buf; the template writes the pieces straight through.

  • Runs of writes collapse into one template:

    arr.rev_eachi((i, x) => {
      buf.write_object(i)
      buf.write_string(": ")
      buf.write_object(x)
      buf.write_string("\n")
    })
    // becomes
    arr.rev_eachi((i, x) => buf <+ "\{i}: \{x}\n")

    Adjacent literal chunks are concatenated by the desugarer at compile time,
    so the literal part of a run becomes a single write_string.

Note on the hole form

These use the plain \{x} hole, which desugars to
write_string_interpolation(x) and coerces x to &Show — one box per hole.
That is the right trade in a three-element assertion, but not in library code,
which is why #4110 uses the inline-writer form \{l => l.write_object(x)}
there instead. The reasoning and benchmarks for that choice live in #4110.

Scope boundary

Strictly *_test.mbt / *_wbtest.mbt files. builtin/fixedarray.mbt also has
a <+ rewrite confined to an in-file test {} block, but it is a library file,
so it stays in #4110 rather than here.

Verification

  • moon check --deny-warn --target all
  • moon test on wasm / wasm-gc / js / native — 7458 / 7459 / 7403 / 7375
    passed, 0 failed
  • moon fmt clean

Mechanical sweep of test and benchmark files only; no library code and no
change to any expected output.

Two shapes:

* `buf.write_string("...\{x}...")` -> `buf <+ "...\{x}..."`. The old form
  built a throwaway `StringBuilder` + `String` for the interpolation and
  then copied the result into `buf`; the template writes the pieces straight
  through.
* Runs of `write_string`/`write_char`/`write_object` collapsed into one
  template, e.g. `write_object(i); write_string(": "); write_object(x);
  write_string("\n")` -> `buf <+ "\{i}: \{x}\n"`. Adjacent literal chunks are
  concatenated by the desugarer at compile time, so the literal part of a run
  becomes a single `write_string`.

Test bodies use the plain `\{x}` hole rather than the inline-writer form
`\{l => l.write_object(x)}`; `\{x}` coerces to `&Show` and allocates a box
per hole, which is the right trade in a three-element assertion but not in
library code.

Split out of the library-side sweep so it can land on its own.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Copilot AI lite review requested due to automatic review settings August 19, 2026 14:49
bobzhang added a commit that referenced this pull request Aug 19, 2026
Replaces `write_string`/`write_char`/`write_object` sequences and
`write_string("...\{x}...")` calls with the `<+` template-writing operator
in library code. The test-file half of this sweep is #4111.

Three shapes appear, chosen by what the old code did:

* `buf.write_string("...\{x}...")` -> `buf <+ "...\{x}..."`. The old form
  built a throwaway `StringBuilder` + `String` for the interpolation and
  then copied it into `buf`; the template writes the pieces straight through.
* Adjacent literal writes -> one template. The desugarer concatenates
  adjacent literal chunks at compile time, so `write_string("a")` +
  `write_string("b")` becomes a single `write_string("ab")`.
* `write_object(x)` -> `\{l => l.write_object(x)}`, not `\{x}`. Plain
  `\{x}` desugars to `write_string_interpolation(x)`, which coerces `x` to
  `&Show` and allocates a trait object per interpolation (~40% slower on
  both native and js). The inline-writer form compiles to exactly the same
  code as the `write_object` call it replaces.

`json`'s `escape`, `v128`'s `u64_hex` and `BytesView`'s byte escaping grew
writer variants (`escape_to` / `u64_hex_to`) so that their callers stream
into the target logger instead of materializing an intermediate `String`.
`BytesView::escape_to` also de-duplicates the escaping loop that `Show` and
`ToJson` each had a copy of; it is generic over the logger rather than
taking `&Logger`, so `ToJson`'s concrete `StringBuilder` keeps direct
dispatch (taking `&Logger` there costs ~5% on `Bytes::to_json`).

Measured on `json/stringify_bench_test.mbt` (added here), escape-heavy
document, n=200:

    native  152.0 us -> 122.8 us (1.24x), indent=2 158.4 us -> 131.5 us
    js      193.6 us -> 175.8 us (1.10x), indent=2 206.3 us -> 186.2 us

`builtin/tuple_show.mbt` is deliberately left alone: its `write_object`
runs would need the inline-writer form to stay allocation-free, and a
16-element tuple would become a single ~450 character string literal that
`moon fmt` cannot break.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Refactors MoonBit test and benchmark files to use the <+ template-writing operator instead of write_string / write_char / write_object sequences (and string-interpolation passed into write_string), reducing intermediate string allocations while preserving existing snapshot/expected outputs.

Changes:

  • Replace buf.write_string("...\{x}...")-style interpolation (which materializes an intermediate string) with buf <+ "...\{x}..." in tests/benches.
  • Collapse multi-call write sequences into single <+ templates (often adding \n literals directly in the template).
  • Minor formatting adjustments to keep the test output identical while simplifying write logic.

Reviewed changes

Copilot reviewed 18 out of 18 changed files in this pull request and generated no comments.

Show a summary per file
File Description
strconv/quickcheck_test.mbt Use <+ templates to build structured numeric strings without intermediate to_string() writes.
strconv/double_differential_test.mbt Collapse per-line output assembly into single <+ templates for probe rendering.
sorted_set/set_test.mbt Replace per-element interpolated write_string with <+ in iteration output.
sorted_map/map_test.mbt Use <+ templates for map iteration formatting in tests.
set/linked_hash_set_test.mbt Convert iterator element formatting to <+ templates.
priority_queue/priority_queue_test.mbt Use <+ templates for priority queue iteration formatting.
option/option_test.mbt Replace multi-step newline writing with "<+ \"\\{x}\\n\"" in iterator tests.
json/number_bench_test.mbt Collapse float-number JSON element writes into a single <+ template.
immut/vector/vector_test.mbt Convert vector iteration formatting to <+ templates.
immut/sorted_map/utils_test.mbt Collapse key/value formatting sequences into <+ templates (including Repr cases).
immut/sorted_map/map_test.mbt Replace interpolated write_string calls with <+ in iterator tests.
immut/priority_queue/priority_queue_test.mbt Convert iteration formatting to <+ templates in immut priority queue tests.
deque/deque_test.mbt Replace to_string() + newline writes with a single <+ template.
builtin/stringview_test.mbt Use <+ for per-iteration formatted output in core StringView test.
builtin/linked_hash_map_wbtest.mbt Convert debug-entry formatting to <+ template within whitebox tests.
builtin/iter_test.mbt Use <+ templates for mapped iterator output with newlines.
builtin/array_test.mbt Collapse rev_eachi write sequences into a single <+ template.
bigint/bigint_nonjs_wbtest.mbt Switch Show output implementation in wbtest fixture to <+ template writing.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

@bobzhang

Copy link
Copy Markdown
Contributor Author

Split from #4110. This half is test-only and can land independently; #4110 now carries the library changes and is the one that needs review.

@coveralls

Copy link
Copy Markdown
Collaborator

Coverage Report for CI Build 6200

Coverage remained the same at 90.718%

Details

  • Coverage remained the same as the base build.
  • Patch coverage: No coverable lines changed in this PR.
  • No coverage regressions found.

Uncovered Changes

No uncovered changes found.

Coverage Regressions

No coverage regressions found.


Coverage Stats

Coverage Status
Relevant Lines: 17960
Covered Lines: 16293
Line Coverage: 90.72%
Coverage Strength: 332058.84 hits per line

💛 - Coveralls

@bobzhang
bobzhang merged commit b6d1ab8 into main Aug 19, 2026
16 checks passed
@bobzhang
bobzhang deleted the simplify-template-writing-tests branch August 19, 2026 15:02
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.

3 participants