Skip to content

fix(decorator): hoist consts referenced by emitted Ivy definitions#302

Merged
Brooooooklyn merged 21 commits into
mainfrom
fix/hoist-tdz-consts-issue-287
May 28, 2026
Merged

fix(decorator): hoist consts referenced by emitted Ivy definitions#302
Brooooooklyn merged 21 commits into
mainfrom
fix/hoist-tdz-consts-issue-287

Conversation

@Brooooooklyn
Copy link
Copy Markdown
Member

@Brooooooklyn Brooooooklyn commented May 27, 2026

Summary

  • Hoists top-level const/let/var declarations referenced by Angular decorator metadata when they're declared after the decorated class, matching Angular's official compiler.
  • Fixes the TDZ ReferenceError: Cannot access 'TOKEN' before initialization thrown at module load when @Component({ providers: [{ provide: TOKEN, useValue: 1 }] }) is followed by const TOKEN = …; (the reported repro from No TDZ-safe hoisting of consts referenced by emitted Ivy definitions #287).
  • New module crates/oxc_angular_compiler/src/component/hoist.rs builds a (delete original, insert before class) edit pair per referenced binding. Edits are merged into the existing transform_angular_file edit pipeline; multiple hoists targeting the same class are bundled into one insert to preserve source order.

Closes #287.

What changes

  • component/hoist.rs (new): collect_hoist_edits walks each Angular-decorated class's decorator argument for bare identifier references, stops at function/arrow/class bodies (lazy refs like useFactory: () => DEP don't trigger a hoist), looks up the referencing identifier in a top-level VariableDeclaration map, and schedules a hoist if the declaration starts after the class body end. Shared consts hoist once, ahead of the earliest referencing class.
  • component/transform.rs: appends the hoist edits to the wider edit set right before apply_edits. Hoist inserts use priority = 5 so they land above the compiler's own pre-class declarations at the same offset.
  • component/mod.rs: registers the new module.

What is intentionally NOT hoisted

  • Function declarations — JS already hoists them with body.
  • Class declarations — would race the rest of the transform pipeline, which inserts static fields and surrounding declarations at the class's original position. Forward-referenced classes are rare in Angular code and out of scope for this fix.
  • Destructured bindings (const { a } = x;) — moving them could change observable side-effect order on the RHS.
  • References inside factory/arrow/method bodies — those fire when the factory runs, never at class init time.

Test plan

  • cargo test -p oxc_angular_compiler — 2,461 tests pass (340 integration including 8 new tests for the bug).
  • cargo run -p oxc_angular_conformance — 1,252/1,252 (100%).
  • pnpm test (NAPI vitest) — 189/189 pass.
  • cargo fmt --all -- --check — clean.

New tests (tests/integration_test.rs)

  • component_providers_const_after_class_is_hoisted — exact repro from the issue.
  • component_view_providers_const_after_class_is_hoistedviewProviders parity.
  • component_multiple_provider_consts_after_class_are_hoisted_in_order — relative-order preservation.
  • component_provider_const_before_class_is_not_hoisted — guards against pointless rewrites.
  • component_use_factory_dependency_const_is_hoisted_when_referenced_at_top_leveldeps: array entries hoist.
  • component_shared_provider_const_is_hoisted_once_for_multiple_classes — dedup ahead of earliest user.
  • component_const_referenced_only_inside_factory_body_is_not_hoisted — guards against over-hoisting.

🤖 Generated with Claude Code


Note

Medium Risk
Changes module evaluation order for real Angular components via a large, conservative hoist pass; incorrect edge-case handling could skip needed hoists or leave TDZ, but behavior is gated and aligned with Angular’s compiler.

Overview
Adds TDZ-safe hoisting so top-level const/let/var bindings referenced from Angular decorator metadata (e.g. providers: [{ provide: TOKEN }]) are moved above the decorated class when they appear later in the file—matching the official Angular compiler and fixing module-load ReferenceError (#287).

The new component/hoist.rs builds delete/insert Edits via oxc_semantic symbol resolution: it walks decorator arguments (not lazy factory/arrow bodies), plans hoists per decorated class with transitive initializer dependencies, topologically sorts hoisted statements, and applies safe-skip / cascade un-plan rules so hoisting does not introduce new TDZ on classes or unreachable deps. transform_angular_file appends those edits before apply_edits, with priority skew so hoists sit above the class without clobbering compiler insertions; a cheap program_has_angular_decorated_class gate skips the semantic pass when no Angular-decorated classes exist.

Workspace deps add oxc_ast_visit and oxc_syntax to the compiler crate.

Reviewed by Cursor Bugbot for commit 342d625. Bugbot is set up for automated code reviews on this repo. Configure here.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 483e24edaa

ℹ️ 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".

Comment thread crates/oxc_angular_compiler/src/component/hoist.rs Outdated
Comment thread crates/oxc_angular_compiler/src/component/hoist.rs
Closes #287.

When `@Component` metadata such as `providers` references a top-level
`const` declared *after* the class, the emitted `static ɵcmp` field
evaluates `ɵɵProvidersFeature([…])` eagerly at class-init time. With
the binding still in TDZ, this threw `ReferenceError` at module load.

Match Angular's official compiler by hoisting referenced VariableDeclaration
statements above the earliest class that needs them. Identifier
collection walks decorator metadata but stops at function/arrow/class
bodies, so lazy references like `useFactory: () => DEP` don't trigger
unnecessary moves. Function declarations are JS-hoisted already, and
class declarations are intentionally skipped to avoid clobbering the
transform pipeline's existing edits.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@Brooooooklyn Brooooooklyn force-pushed the fix/hoist-tdz-consts-issue-287 branch from 483e24e to 0961ef6 Compare May 27, 2026 09:50
After #299 turned `emit_class_metadata` on by default, the raw
`${UNRESOLVED}-tag` template literal is intentionally preserved verbatim
inside `ɵsetClassMetadata(...)` to mirror ngc — that's runtime metadata,
not the compiled selector. The original test asserted on the full output
and now panics on every CI run since d83445f landed on main.

Narrow the assertion to the `ɵcmp` definition itself and additionally
verify the compiled selector falls back to `ng-component`, matching the
test's stated intent ("unresolved interpolation must not produce a
partial/garbage selector").

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 8ceae120b0

ℹ️ 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".

Comment thread crates/oxc_angular_compiler/src/component/hoist.rs Outdated
Two PR-review bugs in the hoist pass:

1. **Transitive deps were dropped.** When `@Component({ providers: PROVIDERS
   })` was followed by `const PROVIDERS = [{ provide: TOKEN, ... }]; const
   TOKEN = ...;`, only `PROVIDERS` got hoisted. The hoisted `const PROVIDERS
   = [...]` then evaluated above the class and TDZ-threw on `TOKEN` — just
   moving the same `ReferenceError` one frame deeper.

2. **Multi-declarator dedup was nondeterministic.** The plan was keyed by
   binding *name*, so `const A = 1, B = 2;` referenced by two different
   classes produced two `HoistEntry` values sharing `stmt_start` but
   carrying different `insert_at` targets. The `emitted_stmts` dedup kept
   whichever HashMap iteration visited first — often the *later* class —
   leaving the earlier class in the TDZ.

Fix:

* Key the plan by `stmt_start` and merge collisions by taking MIN
  `insert_at` (no more nondeterministic dedup; multi-declarators collapse
  to one entry by construction).
* Per-statement, collect the union of identifier references across every
  declarator initializer and feed them back into a BFS worklist — so
  hoisting `PROVIDERS` also schedules `TOKEN` (transitive closure).
* Topologically sort the planned statements before emission so
  dependencies land *before* their dependents in the hoisted prelude
  (e.g. `const TOKEN` precedes `const PROVIDERS = [{ provide: TOKEN, ...
  }]`). DFS is iterative to avoid stack overflow on deep chains; cycles
  are broken silently because they can't yield a valid evaluation order
  anyway.

Adds two integration tests that lock in both fixes:
* `component_provider_aggregate_const_pulls_in_transitive_tdz_dep`
* `component_shared_multideclarator_const_hoists_above_earliest_referencer`
  (the latter was flaky against the old code — 7/20 failure rate locally
  before this change, 30/30 passes after).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 96f8c9afbe

ℹ️ 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".

Comment thread crates/oxc_angular_compiler/src/component/hoist.rs
Comment thread crates/oxc_angular_compiler/src/component/hoist.rs Outdated
Two PR #302 review fixes for the const-hoist:

1. Off-by-one at the class-body boundary (Cursor): the check
   `stmt_start <= class_body_end` skipped a const declared at exactly
   `class.body.span.end` (no whitespace between `}` and `const`).
   `class.body.span.end` is the exclusive end of the body, so a stmt
   starting there is the very next byte after the class and still needs
   hoisting. Switched to `<`. To keep the new delete from chewing into
   the `decls_after_class` insert at the same offset, the hoist delete
   now runs with a negative priority so it applies before that insert.

2. Transitive deps through function calls (Codex): a hoisted initializer
   that calls a top-level function (`const PROVIDERS = makeProviders()`)
   evaluates that function's body at module load, so any later-declared
   binding the body reads still TDZ-throws. Indexed top-level function
   declarations via `oxc_ast_visit::Visit` (skipping nested function /
   arrow / class expression bodies for the same lazy-evaluation reason
   as the existing expression walker), and chase identifiers through
   them in the BFS and in the topological-sort edge expansion.

Added two regression tests:
- `component_provider_const_immediately_after_class_brace_is_hoisted`
- `component_provider_const_via_function_call_pulls_in_transitive_tdz_dep`

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 2893ba7b07

ℹ️ 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".

Comment thread crates/oxc_angular_compiler/src/component/hoist.rs Outdated
Comment thread crates/oxc_angular_compiler/src/component/hoist.rs
Replace the hand-rolled name-string indexing in `collect_hoist_edits`
with SymbolId-based resolution backed by `oxc_semantic`. The AOT path
in `transform_angular_file` now builds a `Semantic` and threads it
into the hoister so every `IdentifierReference` resolves through the
symbol table to the actual declaring binding.

Previously, `binding_to_stmt: HashMap<&str, u32>` and
`fn_body_idents: HashMap<&str, HashSet<&str>>` keyed everything by
the identifier's spelling. If a nested scope shadowed a top-level
binding with the same name, the walker couldn't tell them apart and
might count a non-top-level reference as a TDZ-relevant hit on the
top-level binding. After this refactor, every reference is resolved
to a `SymbolId` and matched against the top-level binding set, so
shadows are impossible.

No observable behavior change is intended: all 344 integration tests
(including the two regression tests from PR #302
`component_provider_const_immediately_after_class_brace_is_hoisted`
and `component_provider_const_via_function_call_pulls_in_transitive_tdz_dep`)
continue to pass. The hoist priorities, off-by-one boundary check,
topological sort, and lazy-body skipping are all preserved.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 616588614a

ℹ️ 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".

Comment thread crates/oxc_angular_compiler/src/component/hoist.rs Outdated
Three Codex review findings on PR #302:

1. Lazy fn-value over-hoisting: when a top-level function is referenced
   from decorator metadata as a *value* (e.g. `useFactory: makeFactory`)
   the BFS still chased its body refs and hoisted them above the class.
   Since Angular invokes such factories lazily, this could introduce a
   fresh TDZ that didn't exist in the source. Introduce an
   `eagerly_called` closure (direct callees in any top-level initializer
   or decorator metadata, transitively expanded through
   `fn_body_called_symbols`) and gate the BFS body-chase branch plus
   `expand_through_functions` on it. Regression test:
   `component_provider_useFactory_function_value_does_not_hoist_body_deps`.

2. `Expression::ChainExpression` swallowed by catch-all: optional
   chaining like `{ provide: TOKEN?.id, useValue: 1 }` recorded no
   reference to `TOKEN`. Add an explicit arm that dispatches each
   `ChainElement` variant (`CallExpression`, member variants,
   `TSNonNullExpression`) to the matching collection logic, also
   recording the inner call's direct callee for `f?.()`. Regression
   test: `component_provider_optional_chain_token_is_hoisted`.

3. Destructured top-level bindings not indexed:
   `collect_top_level_bindings` only handled
   `BindingPattern::BindingIdentifier`, so `const { TOKEN } = TOKENS;`
   never made it into `symbol_to_stmt`. Add
   `for_each_binding_identifier` recursive walker covering
   `ObjectPattern`/`ArrayPattern`/`AssignmentPattern` (plus rest
   elements and nested patterns). Regression test:
   `component_provider_destructured_top_level_token_is_hoisted`.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 4d4a3fc9f0

ℹ️ 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".

Comment thread crates/oxc_angular_compiler/src/component/hoist.rs
Comment thread crates/oxc_angular_compiler/src/component/hoist.rs
Comment thread crates/oxc_angular_compiler/src/component/hoist.rs
… guard

Three bot-review findings on the post-refactor hoist:

1. Multi-declarator over-hoist (Codex). When a `const TOKEN = 'tok',
   BACKREF = TestComponent;` declarator list is hoisted because TOKEN is
   referenced in decorator metadata, the peer `BACKREF = TestComponent`
   moves above the class and introduces a new TDZ. Added a safe-skip:
   refuse to hoist a statement when any of its initializer symbols
   resolves to a top-level class declared at position >=
   `effective_start`. Indexed via a new `collect_top_level_class_positions`
   helper. (Split-hoist is intentionally out of scope; the safe-skip
   preserves the "no regressions" invariant — the user's existing TDZ on
   the directly-referenced symbol stays, but we don't introduce a new one.)

2. IIFE bodies in decorator metadata missed (Codex). `providers: (() =>
   [{ provide: TOKEN }])()` runs the arrow body eagerly at class init,
   but the lazy-bodies rule was treating every arrow/function expression
   body as opaque. New `walk_iife_callee_body` detects an immediately-
   invoked function/arrow callee (peeling parens + TS wrappers) and
   walks the body via `FunctionBodyIdentVisitor`; the wider lazy rule
   still applies elsewhere. Symmetric handling for `NewExpression` and
   `ChainElement::CallExpression`.

3. Global `eagerly_called` bleeds across classes (Cursor). Previously
   seeded from every top-level initializer's call sites + every
   decorator's call sites, so `const X = foo()` in one part of the
   module would mark `foo` as eagerly-called for every other class
   too — even classes that only reference `foo` as a value
   (`useFactory: foo`). Replaced the global precomputation with a
   per-class closure inside the BFS: seeded only from THIS class's
   `decorator_called`, extended incrementally as the BFS plans
   bindings (adding each planned init's `init_called_symbols` and
   re-closing through `fn_body_called_symbols`). Functions popped
   before they became eagerly_called are deferred and belatedly
   chased when promotion happens. Topological sort uses the union
   of per-class sets so dependency edges still see every fn whose
   body fires at module load for some hoisted statement.

Regression tests for each finding:
- component_provider_multi_declarator_with_class_self_ref_skips_hoist
- component_provider_iife_metadata_hoists_inner_token
- component_provider_useFactory_value_does_not_chase_global_eager_caller

All 350 integration tests pass; fmt clean.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 62de526973

ℹ️ 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".

Comment thread crates/oxc_angular_compiler/src/component/hoist.rs
Parameter default expressions (`function f(x = TOKEN)` /
`function f({ a = X } = {})`) evaluate at call time, before the body
runs. When a hoisted initializer eagerly calls a top-level function,
any later-declared binding read by a parameter default is just as
TDZ-relevant as a body ref. The previous scan only walked the function
body, so such defaults left their referenced bindings below the class
and the hoisted call-site threw `ReferenceError: Cannot access ... before
initialization`.

Add a `for_each_pattern_default` helper that yields every nested
`AssignmentPattern::right` inside a `BindingPattern`. Use it (alongside
`FormalParameter::initializer` for top-level defaults) in
`collect_top_level_bindings` for top-level function declarations and in
`walk_iife_callee_body` for IIFE callee arrow/function expressions. The
collected refs and direct callees join the same `fn_body_symbol_refs` /
`fn_body_called_symbols` sets the body walk populates, so the existing
BFS and eagerly-called closure pick them up transparently.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 311a41a046

ℹ️ 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".

Comment thread crates/oxc_angular_compiler/src/component/hoist.rs
Comment thread crates/oxc_angular_compiler/src/component/hoist.rs
…-called fns

`collect_top_level_bindings` now walks `AssignmentPattern.right` defaults
nested in each declarator's binding pattern, so `const { TOKEN = FALLBACK }
= {}` records `FALLBACK` as an init-time dep and keeps the dependency
graph TDZ-correct. `FunctionBodyIdentVisitor` now detects IIFE callees in
`visit_call_expression` / `visit_new_expression` and walks their bodies
eagerly via `walk_iife_callee_body`, so IIFEs inside an eagerly-called
top-level function no longer drop their identifier reads. Addresses Codex
P2 #3311274924 and Cursor #3311313158 on PR #302.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 437d4da4c8

ℹ️ 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".

Comment thread crates/oxc_angular_compiler/src/component/hoist.rs
Comment thread crates/oxc_angular_compiler/src/component/hoist.rs
Resolves the two new inline bot comments on PR #302 and the follow-on
adversarial findings the fix surfaced.

* Codex P2 #3311493528 — the `stmt_references_later_class` guard now
  closes `init_called_symbols` through `fn_body_called_symbols` and
  checks each function's `fn_body_symbol_refs` for class refs, so an
  initializer that eagerly calls a function whose body reads a later
  class is no longer hoisted above that class.
* Cursor Low #3311551145 — `collect_expr_symbols` gains explicit
  `AssignmentExpression` / `UpdateExpression` arms plus
  `collect_assignment_target_symbols` and siblings, so identifier
  refs on assignment-target lvalues (including pattern targets with
  defaults and member targets) flow into the dependency graph.
* Cascade un-planning ("Step 2c"): when a planned statement's full
  dep closure reaches a top-level binding that isn't planned at an
  `insert_at` ≤ the dependent's `insert_at`, the dependent is dropped
  and the loop iterates to a fixed point — prevents leaving caller
  hoists stranded when a transitive dep was guard-skipped or planned
  only for a later class.
* Function-valued bindings indexed: each per-declarator `const f =
  () => …` / `function() { … }` populates `fn_body_*` maps so the
  guard chases through them like a function declaration. Covers
  single- and multi-declarator forms.
* Indirect call shapes: `fn.call(...)`, `fn.apply(...)`, and
  `fn.bind(...)()` recorded as eager invocations of `fn` at every
  call/new visit site.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 375382728c

ℹ️ 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".

Comment thread crates/oxc_angular_compiler/src/component/hoist.rs
Comment thread crates/oxc_angular_compiler/src/component/hoist.rs
Comment thread crates/oxc_angular_compiler/src/component/hoist.rs
Resolves the new inline bot comments on PR #302 and the follow-on
adversarial findings the fix surfaced.

* Codex P2 #3311913006 — when a popped symbol both has `symbol_to_stmt`
  and `fn_body_symbol_refs`, the BFS branch now plans the statement
  AND chases body refs (or defers them via `deferred_fns` when the
  binding isn't yet eagerly called).
* Cursor Low #3311962888 — `topological_order` no longer takes the
  global `combined_eagerly_called`; per-S `stmt_eager_sets` and a
  shared `stmt_fn_valued_bindings` map drive the dep-edge construction,
  matching the cascade's reasoning.

Adversarial follow-up: the safe-skip guard and cascade un-planning
were blind to fn-valued bindings whose arrow body reads a class — for
`@Component({providers: make()}) class C {} const make = () => C;`
the planner hoisted `make` above `C` and Ivy's eager `make()` then
hit TDZ on `C`. The fix lifts `stmt_fn_valued_bindings` to right after
`collect_top_level_bindings` and folds each statement's eagerly-called
fn-valued binding bodies into both the per-class safe-skip closure
(via the BFS's `eagerly_called`) and the cascade closure (via
`combined_eagerly_called`), keeping all three passes symmetric.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 9c3c38d26a

ℹ️ 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".

Comment thread crates/oxc_angular_compiler/src/component/hoist.rs
Comment thread crates/oxc_angular_compiler/src/component/hoist.rs Outdated
* Codex P2 #3312108552 — top-level class declarations are now indexed
  into `fn_body_symbol_refs` / `fn_body_called_symbols` (with
  `include_constructor: true`) via a new `walk_class_eager_parts` helper.
  The helper covers the union of class-definition-time eager refs
  (super_class, computed keys, static field / accessor initializers,
  static blocks) and `new`-time eager refs (constructor body + parameter
  defaults, instance field / accessor initializers). The BFS body-chase
  branch then chases through `new ClassName()` callers in hoisted
  initializers — `const PROVIDERS = [new S()]` no longer hoists past
  `class TestComponent` when `S`'s constructor reads a later const.
* Codex P2 #3312108558 — `collect_expr_symbols`'s previously-opaque
  `E::ClassExpression` arm now calls `walk_class_eager_parts` with
  `include_constructor: false`, so the eager parts of an inline class
  expression (super_class, computed keys, static initializers, static
  blocks) feed the dependency graph.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 0de5f1cc83

ℹ️ 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".

Comment thread crates/oxc_angular_compiler/src/component/hoist.rs Outdated
Comment thread crates/oxc_angular_compiler/src/component/hoist.rs
Comment thread crates/oxc_angular_compiler/src/component/hoist.rs Outdated
…te tags

* Codex P2 #3314767088 — `FunctionBodyIdentVisitor::visit_class` was a
  no-op, dropping inline `class extends TOKEN {}` defined inside an
  eagerly-called function body. It now calls `walk_class_eager_parts`
  with `include_constructor: true` so super_class, computed keys,
  static initializers/blocks, and (conservatively) constructor body
  refs feed `fn_body_symbol_refs` / `fn_body_called_symbols`.
* Codex P2 #3314767091 — `record_direct_callee` only recognised bare
  identifiers under parens / TS wrappers. Restructured as an iterative
  worklist that also descends into both branches of
  `ConditionalExpression` / `LogicalExpression` and the last expression
  of a `SequenceExpression`, so callees like `(cond ? makeA : makeB)()`
  and `(makeA || makeB)()` enter the eager-call set.
* Cursor Low #3314770575 — `FunctionBodyIdentVisitor` gained
  `visit_tagged_template_expression`, recording the tag via the same
  direct/indirect/bind callee helpers as `visit_call_expression` so
  `tag\`…\`` inside a top-level function body chases the tag's body
  refs.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Comment thread crates/oxc_angular_compiler/src/component/hoist.rs
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 174c796c1d

ℹ️ 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".

Comment thread crates/oxc_angular_compiler/src/component/hoist.rs
`collect_expr_symbols::E::TaggedTemplateExpression` only called
`record_direct_callee`, while the call/new arms and the body visitor's
override already invoke all three callee helpers. Decorator metadata
like `make.bind(null)\`...\`` or `make.call(this)\`...\`` therefore
recorded `make` as a value reference but never as an eager callee, so
its body wasn't chased and a later-declared binding it reads stayed
below the class. Now mirrors call/new: direct + indirect + bind. Closes
Cursor Low #3314809112 and Codex P2 #3314810080.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 730983c3c4

ℹ️ 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".

Comment thread crates/oxc_angular_compiler/src/component/hoist.rs
Brooooooklyn and others added 2 commits May 28, 2026 09:47
The BFS branch for a `symbol_to_stmt` hit ran the fn-valued binding
body-chase only AFTER the `stmt_start < class_body_end` early-continue,
so a `const make = () => [{ provide: TOKEN }];` declared *before* the
decorated class slipped past the chase entirely. `make`'s binding is
indeed initialized at class-eval time, but the arrow body still fires
when the decorator calls `make()` — and its later-declared reads stay
in TDZ. Moved the body-chase block to BEFORE the early-continue so it
runs regardless of stmt position; removed the now-duplicate post-plan
copy. Closes Codex P2 #3314836115.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: b4934906fb

ℹ️ 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".

Comment thread crates/oxc_angular_compiler/src/component/hoist.rs Outdated
Removes trailing `See PR #302 ...`, `Regression for/test for ... review #...`,
`Round N follow-on review`, `Codex P2/P3 review Finding N`, etc. — the
references that belong in PR descriptions and rot as the codebase moves.
Substantive technical justifications kept; only the bookkeeping tails
removed.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 73482730d9

ℹ️ 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".

Comment thread crates/oxc_angular_compiler/src/component/hoist.rs Outdated
Copy link
Copy Markdown

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 7348273. Configure here.

Comment thread crates/oxc_angular_compiler/src/component/hoist.rs
* `record_indirect_callee` / `record_bind_callee` previously required the
  static-member receiver to be a bare identifier, missing shapes like
  `(cond ? makeA : makeB).call(null)` and `(cond ? makeA : makeB).bind(null)()`.
  Both now delegate the receiver descent to `record_direct_callee`, which
  already peels parens / TS wrappers and walks conditional / logical /
  sequence branches.
* The topo-precompute eager set was closed under `fn_body_called_symbols`
  BEFORE folding in fn-valued binding symbols, while the cascade did the
  opposite order. Reordered the precompute to match the cascade so both
  passes expand through the same transitive callees and dependency edges
  through fn-valued bindings stay visible to the topological sort.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 9575f2cb19

ℹ️ 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".

Comment thread crates/oxc_angular_compiler/src/component/hoist.rs
`FunctionBodyIdentVisitor::visit_function` no-opped on every nested
function, so a local declaration like
`function outer() { function inner() { return TOKEN; } return inner(); }`
called from an eagerly-evaluated body never contributed `TOKEN` to the
eager surface — `inner` is a local symbol that's not indexed in any
`fn_body_*` map, so `close_eagerly_called` can't chase through it. The
visitor now walks the body and parameter defaults of any nested
`Function` with `id: Some(_)`. Anonymous functions and arrow expressions
remain lazy.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 9e5074478f

ℹ️ 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".

Comment thread crates/oxc_angular_compiler/src/component/hoist.rs
`FunctionBodyIdentVisitor` skipped arrows entirely, so a local binding
like `const inner = () => TOKEN;` inside an eagerly-called body left
`inner` recorded as a callee but with no body to chase — `TOKEN` stayed
below the class. Added a per-visitor `local_fn_bodies` map populated by
a new `visit_variable_declarator` override that indexes arrow/function
inits via a scratch walk. At each `CallExpression` / `NewExpression` /
`TaggedTemplateExpression` we resolve every reachable callee identifier
(through parens, TS wrappers, conditional / logical / sequence branches,
`.call` / `.apply` receivers, and the inner call of `.bind(...)()`) and
fold the indexed body's refs / callees into the eager surface, with a
`folded` guard for self- and mutually-recursive arrows. Value-passed
arrows that are never invoked inside the body (`useFactory: lazy`) stay
lazy because the fold only fires at call sites.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 7df59fff65

ℹ️ 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".

Comment thread crates/oxc_angular_compiler/src/component/hoist.rs
The prior round made `FunctionBodyIdentVisitor::visit_function` walk
named nested function bodies unconditionally, which broke laziness — an
uncalled `function unused() { return TOKEN; }` declared inside an
eagerly-called helper folded `TOKEN` into the eager surface and could
hoist it above the class for no reason. Mirror the local-arrow path:
`visit_function` now indexes the function into `local_fn_bodies` and
defers the fold to call sites. A new `visit_function_body` /
`visit_block_statement` pre-pass indexes hoisted function declarations
before source-order walking so a call that textually precedes its
declaration still resolves.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@Brooooooklyn Brooooooklyn merged commit 7ca1ba6 into main May 28, 2026
10 checks passed
@Brooooooklyn Brooooooklyn deleted the fix/hoist-tdz-consts-issue-287 branch May 28, 2026 03:36
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.

No TDZ-safe hoisting of consts referenced by emitted Ivy definitions

1 participant