Merge train 211: keep observable user code out of a folded builder (v0.5.1589) - #10415
Merged
Merged
Conversation
Folding `const o = {}; o.a = v;` into `const o = { a: v }` evaluates `v`
before `o` is initialized. `value_is_fold_safe` claimed such values run
no user code, yet admitted two ways to run it. First, every converting
operator: `"" + w`, `-w`, `w < 1`, `w == 1` and `` `${w}` `` all call
`w`'s `valueOf`/`toString`/`Symbol.toPrimitive`. Second, every bare
identifier: a name that is no binding reads the global object, whose
property may be an accessor. When that code reads `o`, node builds the
object and perry threw a TDZ ReferenceError.
Dropping both would stop the fold's own motivating example
(`o.b = r + i`) from folding. Instead, a new `FoldScope` asks whether
anything can read the binding early. User code can only read a binding
it names, so the scan looks for function-likes in the enclosing function
(or module) that mention the builder's name. To avoid giving up folds, it
ignores a function-like created after a `let`/`const` builder (the
binding is fresh per loop pass and execution within a pass only moves
forward) and one that re-binds the name at its own function level. It
always counts a hoisted function declaration and any observer of a
`var`, and treats `eval`, `with`, an export of the name and a
module-level `var` as observers outright.
Unobservable builders fold exactly as before. On an observable one, a
conversion needs operands that are primitive by construction, and a read
needs a proven declarative binding. `Visible` provides that proof from
declarations that cover the whole region: a list's own declarations,
parameters, function-scoped `var`s, loop heads, catch parameters, and
module imports and declarations. It never counts a sibling block, an
ambient `declare` (#10363), or anything in a module containing `with`.
The gap statements from #10355 take the same answers.
#10357 (cherry-picked onto main after #10353 landed as 2cb9c76) pushed builder_fold.rs to 2118 lines, over the 2000-line-per-file cap enforced by scripts/check_file_size.sh. The compile-time builder WIDTH-hint scan (#6812 w16: empty_builder_width_hints and its hint_*/const_build_loop_width/width_int_lit helpers) has no dependency on the fold-safety logic above it or vice versa — it only needs swc_ecma_ast — so it moves to builder_fold_width_hints.rs via the same `#[path = "..."] mod ...; pub(crate) use ...;` pattern codegen/method.rs uses for method_static.rs. Callers keep using `builder_fold::empty_builder_width_hints` unchanged.
|
Caution Review failedThe pull request is closed. ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Advanced Run ID: ⛔ Files ignored due to path filters (1)
📒 Files selected for processing (7)
📝 WalkthroughWalkthroughBuilder folding now analyzes whether conversions can observe a builder before initialization. It restricts unsafe folds, extracts width-hint scanning into a separate module, adds regression tests, and updates the package version and changelog. ChangesBuilder folding soundness
Estimated code review effort: 4 (Complex) | ~45 minutes Change: Bug fix · Severity of issue fixed: Medium Sequence Diagram(s)sequenceDiagram
participant SourceProgram
participant BuilderFold
participant FoldScope
participant Visible
participant GeneratedProgram
SourceProgram->>BuilderFold: provide module AST
BuilderFold->>FoldScope: scan observers and positions
BuilderFold->>Visible: collect resolvable bindings
BuilderFold->>BuilderFold: validate conversions and fold candidates
BuilderFold->>GeneratedProgram: emit folded or unfurled builder code
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
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. Comment |
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.
This train lands #10361 as v0.5.1589, on
d83e0414e1.#10357) — keeps observable user code out of a folded builder. The builder fold may skip statements between a{}binding and its stores, which sinks the allocation below them; this pins that folding cannot move anything a program can observe.The conflict was not a conflict
#10361 reported nine conflicting hunks in
crates/perry-hir/src/lower/builder_fold.rsagainstmain. It carries two commits, and the first —5f86350b96— is patch-id identical to2cb9c76f86already onmain(both hash to9bfff5497276e77d0b3fddf76e7018aa12a24f3f). That is #10353, which the PR forked from before it rebase-merged under a different SHA. Git's diff3 cannot see the two sides are the same patch, so it reports both independently touching the same lines.The resolution is therefore to skip that commit, not to hand-merge it. Cherry-picking only the real commit applies with zero conflicts. Hand-merging nine hunks of fold-safety logic against a copy of itself was the actual risk here, and the source proof records the skip by finding
5f86350b96's patch-id onmain, not by assuming it.Train repair
The file-size cap. Post-pick,
builder_fold.rsis 2118 lines — 118 over the 2000 cap. Split at a pre-existing// ---banner that already separated two independent subsystems: the fold-safety machinery stays, and #6812's compile-time builder WIDTH scan (empty_builder_width_hintsand its private helpers) moves tobuilder_fold_width_hints.rs. 1743 + 385 lines.Following the
method.rs→method_static.rsprecedent: a sibling file plus#[path] modand apub(crate) usere-export, sosuper::builder_fold::empty_builder_width_hintsatlower_module_fn.rs:925needs no change. Verified a pure move — 333 code lines out, 334 in, the single addition being theuse swc_ecma_ast as ast;the moved block needs. No visibility fixes, no orphaned attributes, no gate JSON names this file.Validation
Validated head
df08e26fe0. Five-package release build pinned and hash-verified, and re-verified after the gap run.The acceptance evidence is compile-and-run, because it has to be. HIR unit tests pin which shapes fold; only the semantics suites pin that folding them changes nothing a program can see. Both run inside the validated pin:
main's one known runtime failure.builder,fold,object,literal,class,spread.builder,foldandspreadare clean — the first two being the most direct for this change.Reds attributed
Three fixtures A/B'd against
main's own artifact set — all identical on both arms, with distinct build stamps per arm:test_issue_4034_object_literal_semantics(node=1, main=0, train=0),test_issue_3580_arguments_object_semantics(node=0, main=0, train=0),test_issue_927_jwt_verify_returns_object(node=1, main=0, train=0). The first matters most: this PR folds builders into object literals, so a prior verdict measured on binaries predating the change would not have transferred.The three
classfixtures need no A/B: node itself exits 1 inside its own module resolution (run_main,package_json_reader,esm/resolve), which is binary-independent, and this train does not touch those fixtures.test_issue58_object_stringis listed verbatim inrun_parity_tests.sh'sSKIP_TESTSonmain.Before merging, the pushed head and unchanged main are checked again. After merging, the rewritten commits are checked for preserved authorship and the main tree must match the validated train exactly.
Summary by CodeRabbit
Bug Fixes
varbindings, and dynamic scope features.Performance
Documentation