fix(ecmascript): treat collection constructor with variable arg as side-effectful#20383
Merged
graphite-app[bot] merged 1 commit intomainfrom Mar 16, 2026
Merged
Conversation
Merging this PR will not alter performance
Comparing Footnotes
|
Member
Author
How to use the Graphite Merge QueueAdd either label to this PR to merge it via the merge queue:
You must have a Graphite account in order to use the merge queue. Sign up using this link. An organization admin has enabled the Graphite Merge Queue in this repository. Please do not merge from GitHub as this will restart CI on PRs being processed by the merge queue. This stack of pull requests is managed by Graphite. Learn more about stacking. |
3 tasks
sapphi-red
approved these changes
Mar 15, 2026
Dunqing
added a commit
that referenced
this pull request
Mar 16, 2026
…e-effect detection Address review feedback from #20383 and #20395. The "Coercion Methods Are Pure" assumption (ASSUMPTIONS.md) means toString/valueOf/Symbol.toPrimitive are assumed side-effect-free, so per-constructor ToPrimitive validation is unnecessary for String, Number, Date, ArrayBuffer, and Error types. Changes: - String, Number, Date, ArrayBuffer, Error constructors now unconditionally pure (coercion assumed pure) - String(), Number(), Symbol() call expressions simplified to pure - TypedArray uses value_type() check: object args call @@iterator (NOT covered by coercion assumption), BigInt args cause ToNumber to throw - Removed 3 unnecessary helpers (new_expr_with_to_{string,number,primitive}_check) - Removed invalid issue link (XXXX) - Fixed is_pure_callable_constructor doc comment - BigInt() retains special-case validation (throws on invalid values) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Merged
4 tasks
Contributor
Merge activity
|
…de-effectful (#20383) ## Summary `new Map(x)`, `new Set(x)`, `new WeakMap(x)`, `new WeakSet(x)` were incorrectly considered side-effect-free when the argument is a variable reference. These constructors iterate their argument via `Symbol.iterator`, which can execute arbitrary user code (custom iterators, proxies, etc.). **Before:** `is_pure_constructor` unconditionally included `Map`/`Set`/`WeakMap`/`WeakSet`, so `new Map(variable)` was treated as pure. **After:** Collection constructors are only pure with provably safe arguments: - No arguments: `new Set()` - `null` or `undefined`: `new Set(null)` - Array literals: `new Set([1, 2, 3])`, `new Map([[k, v]])` - Variable references or other expressions: **NOT pure** This matches the behavior of **esbuild** and **Rollup**: - esbuild: only marks pure with no args, null, undefined, or array literals ([test cases](https://github.com/evanw/esbuild/blob/main/internal/js_parser/js_parser_test.go)) - Rollup: uses `PURE_WITH_ARRAY` — only array literals are trusted ([knownGlobals.ts](https://github.com/rollup/rollup/blob/master/src/ast/nodes/shared/knownGlobals.ts)) **Found via:** Rolldown's `tree-shake-iterable` test failing after delegating `NewExpression` side-effect analysis to Oxc. ## Test plan - [x] Added tests for all cases: null, undefined, array literals, variable refs, Map with non-array entries - [x] All existing `oxc_minifier` tests pass (473 passed) - [x] Verified against Rolldown's `tree-shake-iterable` and `manual-pure-functions` test suites 🤖 Generated with [Claude Code](https://claude.com/claude-code)
74c9ddd to
edb8677
Compare
graphite-app bot
pushed a commit
that referenced
this pull request
Mar 16, 2026
…e-effect detection Address review feedback from #20383 and #20395. The "Coercion Methods Are Pure" assumption (ASSUMPTIONS.md) means toString/valueOf/Symbol.toPrimitive are assumed side-effect-free, so per-constructor ToPrimitive validation is unnecessary for String, Number, Date, ArrayBuffer, and Error types. Changes: - String, Number, Date, ArrayBuffer, Error constructors now unconditionally pure (coercion assumed pure) - String(), Number(), Symbol() call expressions simplified to pure - TypedArray uses value_type() check: object args call @@iterator (NOT covered by coercion assumption), BigInt args cause ToNumber to throw - Removed 3 unnecessary helpers (new_expr_with_to_{string,number,primitive}_check) - Removed invalid issue link (XXXX) - Fixed is_pure_callable_constructor doc comment - BigInt() retains special-case validation (throws on invalid values) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
camc314
pushed a commit
that referenced
this pull request
Mar 16, 2026
### 🐛 Bug Fixes - edb8677 ecmascript: Treat collection constructor with variable arg as side-effectful (#20383) (Dunqing) - 1f65c3f transformer: Emit design:paramtypes when class has static anonymous class expression (#20382) (bab) - fa70d5c transformer: Use implementation signature for design:paramtypes when constructor is overloaded (#20394) (bab) - ed5a7fb parser: Report syntax error for `new super()` (#20384) (Boshen) - e62524d minifier: Treat object spread of getters as having side effects (#20380) (Boshen) - f8fbd6e linter/plugins: Remove `hashbang` property from AST (#20365) (overlookmotel) ### ⚡ Performance - 30a2b0f minifier: Use atom_from_strs_array for template literal concat (#20386) (Boshen) - 690ce17 minifier: Use Vec::with_capacity for inline template expressions (#20389) (Boshen) - 9cd612f linter/plugins: Recycle comment objects (#20362) (overlookmotel) Co-authored-by: Boshen <1430279+Boshen@users.noreply.github.com>
Dunqing
added a commit
that referenced
this pull request
Mar 16, 2026
…e-effect detection Address review feedback from #20383 and #20395. The "Coercion Methods Are Pure" assumption (ASSUMPTIONS.md) means toString/valueOf/Symbol.toPrimitive are assumed side-effect-free, so per-constructor ToPrimitive validation is unnecessary for String, Number, Date, ArrayBuffer, and Error types. Changes: - String, Number, Date, ArrayBuffer, Error constructors now unconditionally pure (coercion assumed pure) - String(), Number(), Symbol() call expressions simplified to pure - TypedArray uses value_type() check: object args call @@iterator (NOT covered by coercion assumption), BigInt args cause ToNumber to throw - Removed 3 unnecessary helpers (new_expr_with_to_{string,number,primitive}_check) - Removed invalid issue link (XXXX) - Fixed is_pure_callable_constructor doc comment - BigInt() retains special-case validation (throws on invalid values) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
graphite-app bot
pushed a commit
that referenced
this pull request
Mar 17, 2026
…e-effect detection (#20420) ## Summary Address review feedback from #20383 and #20395. Apply the "Coercion Methods Are Pure" assumption (`ASSUMPTIONS.md`) — `.toString()`/`.valueOf()`/`[Symbol.toPrimitive]()` are assumed side-effect-free — while still checking for spec-mandated throws that are NOT covered by that assumption. ### Changes **`new String/Number(arg)`** — `ToString(Symbol)` / `ToNumber(Symbol)` throws TypeError. Check via `new_expr_first_arg_may_be_symbol`. BigInt is fine for both (`ToString(BigInt)` works, Number converts BigInt→Number). **`new Date/ArrayBuffer(arg)`** — `ToNumber(Symbol)` AND `ToNumber(BigInt)` throw TypeError. Check via `new_expr_first_arg_may_be_symbol_or_bigint`. **Error constructors** — `new Error(Symbol())` / `Error(Symbol())` throw (`ToString(Symbol)`). Removed from `is_unconditionally_pure_constructor` and `is_pure_callable_constructor`. Added `is_error_constructor()` helper with Symbol check. **TypedArray constructors** — use `value_type()` instead of `to_primitive()`: object args call `@@iterator` (NOT a coercion method), BigInt args cause `ToNumber` to throw. Only known-safe primitive value types (Number/String/Boolean/Null/Undefined) accepted. **`String(x)` call** — unconditionally pure. `String()` as a function has special Symbol handling per spec (`String(Symbol())` → `"Symbol()"` without throwing). Added to `is_pure_callable_constructor`. **`Number(x)` / `Symbol(x)` calls** — keep special handling (`ToNumeric(Symbol)` / `ToString(Symbol)` throw). **`is_pure_collection_constructor`** — now verifies `undefined` is actually global (not shadowed). Removed invalid issue link (`XXXX`). **Also fixes:** typo `"BitUint64Array"` → `"BigUint64Array"` in `is_known_global_constructor`. ### Cross-tool comparison | Expression | ES Spec | esbuild | Terser | Rollup | SWC | Oxc (this PR) | |---|---|---|---|---|---|---| | `new String(x)` | impure (Symbol) | impure | impure | pure | impure | **impure** (Symbol check) | | `new Number(x)` | impure (Symbol) | impure | impure | pure | impure | **impure** (Symbol check) | | `new Date(x)` | impure (Symbol/BigInt) | impure | impure | pure | impure | **impure** (Symbol+BigInt) | | `new Date(0n)` | impure (BigInt) | impure | impure | pure | impure | **impure** (BigInt check) | | `new ArrayBuffer(x)` | impure (Symbol/BigInt) | impure | impure | pure | impure | **impure** (Symbol+BigInt) | | `new Error(x)` | impure (Symbol) | impure | impure | pure | impure | **impure** (Symbol check) | | `new Uint8Array(x)` | impure (@@iterator) | impure | impure | pure | impure | **impure** (value_type) | | `new Uint8Array({})` | impure (@@iterator) | impure | impure | pure | impure | **impure** (value_type) | | `String(x)` call | safe (Symbol handled) | impure | impure | pure | impure | **pure** (coercion assumption) | | `Error(x)` call | impure (Symbol) | impure | impure | pure | impure | **impure** (Symbol check) | Oxc sits between esbuild (most conservative) and Rollup (most aggressive): uses the coercion assumption to skip ToPrimitive checks on objects, but catches spec-mandated throws (`ToString(Symbol)`, `ToNumber(Symbol)`, `ToNumber(BigInt)`, `@@iterator` on objects). ## Test plan - [x] All 473 `oxc_minifier` tests pass (39 ignored) - [x] All 13 `oxc_ecmascript` tests pass - [x] Updated test expectations with new cases (`new Date(0n)`, `new ArrayBuffer(0n)`, `new Error(x)`, `Error(x)`) - [x] Cross-tool research verified against ES Spec, esbuild, Terser, Rollup, SWC
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

Summary
new Map(x),new Set(x),new WeakMap(x),new WeakSet(x)were incorrectly considered side-effect-free when the argument is a variable reference. These constructors iterate their argument viaSymbol.iterator, which can execute arbitrary user code (custom iterators, proxies, etc.).Before:
is_pure_constructorunconditionally includedMap/Set/WeakMap/WeakSet, sonew Map(variable)was treated as pure.After: Collection constructors are only pure with provably safe arguments:
new Set()nullorundefined:new Set(null)new Set([1, 2, 3]),new Map([[k, v]])This matches the behavior of esbuild and Rollup:
PURE_WITH_ARRAY— only array literals are trusted (knownGlobals.ts)Found via: Rolldown's
tree-shake-iterabletest failing after delegatingNewExpressionside-effect analysis to Oxc.Test plan
oxc_minifiertests pass (473 passed)tree-shake-iterableandmanual-pure-functionstest suites🤖 Generated with Claude Code