Repository navigation
Represent array and list literals with spreads with a Pexp_spread node - #8905
Conversation
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## operand-line-suffix #8905 +/- ##
=======================================================
- Coverage 81.44% 81.42% -0.02%
=======================================================
Files 459 460 +1
Lines 64553 64633 +80
=======================================================
+ Hits 52573 52630 +57
- Misses 11980 12003 +23
🚀 New features to boost your workflow:
|
63aa4cf to
aa5c53b
Compare
rescript
@rescript/belt
@rescript/darwin-arm64
@rescript/linux-arm64
@rescript/linux-x64
@rescript/runtime
@rescript/win32-x64
commit: |
|
Developer playground preview: https://rescript-lang.github.io/rescript/dev-playground/?version=pr-8905 |
|
@codex review |
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
|
Codex Review: Didn't find any major issues. More of your lovely PRs please. Reviewed commit: ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex address that feedback". |
2e93ed3 to
798978d
Compare
Changelog🐛 Bug fix
💅 Polish
🏠 Internal
Semantic Diff2 commits · Full page Represent array and list literals with spreads with a Pexp_spread nodeb93e551 · cknitt + Claude · Oct 18, 2026, 18:22 UTC
Pexp_spread keeps the items and spreads in source order, and the type checker gives it a Texp_spread node. Translation emits new Parray_spread and Plist_spread primitives that compile to the same runtime calls. A list literal whose only spread is its tail stays a chain of ::. The parsetree and typed tree layouts change; the release bumps their magic numbers. AST modified (array literal with a spread) let x = [1, ...ys]
expression
- Pexp_apply
- expression
- attribute "res.spread"
- Pexp_ident "Primitive_array.spread"
- <arg>
- Nolabel
+ Pexp_spread array
+ Pspread_item
expression
- Pexp_array
- expression
- Pexp_array
- expression
- Pexp_constant PConst_int (1,None)
- expression
- Pexp_ident "ys"
+ Pexp_constant PConst_int (1,None)
+ Pspread
+ expression
+ Pexp_ident "ys"Typedtree added (Texp_spread) let ys = [2]
let x = [1, ...ys]
Format modified (literals with spreads) let x = [...[1, 2], 3, ...[]]
let y = @attr list{...xs, ...list{}, 1}
-let x = [1, 2, 3, ]
-let y = list{...xs, , 1}
+let x = [...[1, 2], 3, ...[]]
+let y = @attr list{...xs, ...list{}, 1}Review
Move the v0 spread encoding into the PPX bridge and test spread literalsa97a105 · cknitt + Claude · Oct 27, 2026, 07:00 UTC The res.spread call is only the v0 PPX wire format, so it now lives in the bridge: Ast_mapper_to0 builds it (spread_call, spread_marker) and Ast_mapper_from0 decodes it, with the module-name lookup local to the decoder. Ast_spread keeps what the type checker and analysis use: groups, parts and module_name. The ghost locations of the generated parts stay, as for any node the compiler makes up. Behaviour is unchanged. The new mocha test runs array literals with several spreads, items between them, a leading item and an empty spread, list literals with spreads before the tail and a tail-only spread, and checks that both kinds evaluate their items and spreads left to right; its generated JavaScript pins the Primitive_array.spread and Primitive_list.spread calls and the order of their parts. |
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 798978d3ad
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
798978d to
ca4983f
Compare
|
@codex review |
|
Codex Review: Didn't find any major issues. Keep it up! Reviewed commit: ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex address that feedback". |
a091db1 to
2693742
Compare
2693742 to
6562b42
Compare
|
@codex review |
|
Codex Review: Didn't find any major issues. Bravo. Reviewed commit: ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex address that feedback". |
Why:
The parser turned [a, ...b] and list{...a, b} into calls of
Primitive_array.spread and Primitive_list.spread marked with res.spread,
so the formatter had to rebuild the literal from the call and got it
wrong: spreads of literals were flattened, attributes on the literal
were dropped and an empty list spread printed as an empty element.
Pexp_spread keeps the items and spreads in source order, and the type
checker gives it a Texp_spread node. Translation emits new Parray_spread
and Plist_spread primitives that compile to the same runtime calls. A list
literal whose only spread is its tail stays a chain of ::. The parsetree
and typed tree layouts change; the release bumps their magic numbers.
AST modified (array literal with a spread):
Example:
let x = [1, ...ys]
Before (parse):
expression
Pexp_apply
expression
attribute "res.spread"
Pexp_ident "Primitive_array.spread"
[
<arg>
Nolabel
expression
Pexp_array
[
expression
Pexp_array
[
expression
Pexp_constant PConst_int (1,None)
]
expression
Pexp_ident "ys"
]
]
After (parse):
expression
Pexp_spread array
[
Pspread_item
expression
Pexp_constant PConst_int (1,None)
Pspread
expression
Pexp_ident "ys"
]
Typedtree added (Texp_spread):
Example:
let ys = [2]
let x = [1, ...ys]
After (typed):
expression
Texp_spread array
[
expression
Texp_array
[
expression
Texp_constant Const_int 1
]
expression
Texp_ident "ys/1001"
]
Format modified (literals with spreads):
Example:
let x = [...[1, 2], 3, ...[]]
let y = @attr list{...xs, ...list{}, 1}
Before (rescript):
let x = [1, 2, 3, ]
let y = list{...xs, , 1}
After (rescript):
let x = [...[1, 2], 3, ...[]]
let y = @attr list{...xs, ...list{}, 1}
Review: Why do the generated item groups have ghost locations? The v0 encoding has no marker on them, and the ghost flag tells a group of items from an array or list literal that is spread. A call built by a PPX with real locations decodes to spreads only, which concatenates the same values.
Review: Why does the typed node hold the concatenated parts rather than the elements? The parts are the arrays and cons chains the runtime call takes, so the items keep the errors of array and list literals and translation needs no list building of its own.
Area: syntax printer typechecker lambda codegen analysis reanalyze tests
Output: unchanged, tests/tests JS byte-identical after a rebuild; a
combinatorial harness of all item/spread sequences up to length 4 compiles
to identical JS with the old and new bsc
Formatting: changed spreads of array and list literals and attributes on
literals with spreads are kept; comments in list spreads stay in place
Formats: ast cmt (layouts changed; magic numbers left to yarn
changelog:release)
PPX: unchanged wire: the v0 encoding is the res.spread call it was, decoded
back to Pexp_spread (ast-mapping fixture SpreadLiterals.res)
Editor: unchanged: pipe completion types the literal as the runtime call;
SpreadLiterals.res covers AST dump, hints, highlighting and completion
Tests: +tests/syntax_tests/data/ast-mapping/SpreadLiterals.res
+tests/analysis_tests/tests/src/SpreadLiterals.res
+tests/build_tests/super_errors/fixtures/array_spread_type_mismatch.res
+tests/build_tests/super_errors/fixtures/list_spread_type_mismatch.res
+tests/build_tests/super_errors/fixtures/array_spread_annotation_mismatch.res
~tests/ounit_tests/ounit_ast_mapper0_tests.ml
~tests/syntax_tests/data/printer/expr/array.res
~tests/syntax_tests/data/printer/expr/list.res
~tests/syntax_tests/data/printer/comments/array.res
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EUpL6dKCiSwAeriDGRHqNr
Claude-Session: https://claude.ai/code/session_01Kr4cjoUi2f5VpMvE6wT9bo
Signed-off-by: Christoph Knittel <ck@cca.io>
The res.spread call is only the v0 PPX wire format, so it now lives in the bridge: Ast_mapper_to0 builds it (spread_call, spread_marker) and Ast_mapper_from0 decodes it, with the module-name lookup local to the decoder. Ast_spread keeps what the type checker and analysis use: groups, parts and module_name. The ghost locations of the generated parts stay, as for any node the compiler makes up. Behaviour is unchanged. The new mocha test runs array literals with several spreads, items between them, a leading item and an empty spread, list literals with spreads before the tail and a tail-only spread, and checks that both kinds evaluate their items and spreads left to right; its generated JavaScript pins the Primitive_array.spread and Primitive_list.spread calls and the order of their parts. Area: compiler/ml tests Output: unchanged Tests: +tests/tests/src/spread_literal_test.res Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Kr4cjoUi2f5VpMvE6wT9bo Signed-off-by: Christoph Knittel <ck@cca.io>
6562b42 to
a97a105
Compare
Part of #8624 (
res.spreaditem).The parser used to turn
[a, ...b]andlist{...a, b}into calls ofPrimitive_array.spread/Primitive_list.spreadwith ares.spreadattribute on the function. They are now aPexp_spread (kind, elements)node with items and spreads in source order, typed asTexp_spread, and translated to newParray_spread/Plist_spreadLambda primitives that compile to the same runtime calls. A list literal whose only spread is its tail (list{a, ...b}) stays a chain of::, as before.Effects
tests/testsrebuilds byte-identical, and every item/spread sequence up to length 4 (arrays and lists, top level and in functions) compiles to the same JS with the old and newbsc.[...[1, 2], 3, ...[]]printed as[1, 2, 3, ](invalid), andlist{...xs, ...list{}, 1}aslist{...xs, , 1}@attr [1, ...a]→[1, ...a])This has type: list<int>, but it's expected to have type: array<int>) instead of "This array item has type list…". With a type annotation, the mismatch is reported at the element, as for a plain array literal.to0emits the old call andfrom0decodes it back. The generated item groups get ghost locations, so a group of items can be told apart from a spread array/list literal, and the round trip is exact. A spread call built by a PPX with real locations decodes to spreads only, which concatenates the same values. Calls without the marker stay calls.ResImpl/ResIntfandcmtbumped. The new Lambda primitives come after the last constant constructor, asPdict_spreaddid.New tests
ast-mapping/SpreadLiterals.resand an ounit case for the v0 wire shape, the exact round trip, PPX-built calls and unmarked callssuper_errors:array_spread_type_mismatch,list_spread_type_mismatch,array_spread_annotation_mismatchSpreadLiterals.res(AST dump, hints, highlighting, pipe completion)A stacked follow-up will fix a formatter bug that predates this PR: a line comment inside a literal used as a
switch/if/try/loop operand moves on every reformat.🤖 Generated with Claude Code
https://claude.ai/code/session_01EUpL6dKCiSwAeriDGRHqNr