@@ -94,19 +94,24 @@ fn optimize_track_expression<'a>(
9494 // to the non-optimizable case below, which creates a wrapper function like:
9595 // `function _forTrack($index,$item) { return this.trackByFn; }`
9696
97- // First check if the expression contains any ContextExpr or AST expressions
98- // that reference the component instance (implicit receiver)
99- let has_context = expression_contains_context ( & rep. track , expressions) ;
100- if has_context {
101- rep. uses_component_instance = true ;
102- }
103-
104- // For non-optimizable tracks, replace ContextExpr with TrackContextExpr
105- // This signals that context reads in track expressions need special handling
97+ // The track function could not be optimized.
98+ // Replace context reads with TrackContextExpr, since context reads in a track
99+ // function are emitted specially (as `this` instead of `ctx`).
100+ //
101+ // Following Angular's implementation (track_fn_optimization.ts:54-70), we set
102+ // usesComponentInstance inside the transform callback when a ContextExpr is found.
103+ // This is the authoritative detection — transformExpressionsInExpression traverses
104+ // all expression variants, so no ContextExpr can be missed regardless of nesting.
105+ //
106+ // By phase 34 (this phase), resolve_names (phase 31) has already converted all
107+ // ImplicitReceiver AST nodes into Context IR expressions, so checking for Context
108+ // during the transform is sufficient.
109+ let found_context = std:: cell:: Cell :: new ( false ) ;
106110 transform_expressions_in_expression (
107111 & mut rep. track ,
108112 & |expr, _flags| {
109113 if let IrExpression :: Context ( ctx) = expr {
114+ found_context. set ( true ) ;
110115 * expr = IrExpression :: TrackContext ( oxc_allocator:: Box :: new_in (
111116 TrackContextExpr { view : ctx. view , source_span : None } ,
112117 allocator,
@@ -115,6 +120,9 @@ fn optimize_track_expression<'a>(
115120 } ,
116121 VisitorContextFlag :: NONE ,
117122 ) ;
123+ if found_context. get ( ) {
124+ rep. uses_component_instance = true ;
125+ }
118126
119127 // Also create an op list for the tracking expression since it may need
120128 // additional ops when generating the final code (e.g. temporary variables).
@@ -146,179 +154,6 @@ fn optimize_track_expression<'a>(
146154 rep. track_by_ops = Some ( track_by_ops) ;
147155}
148156
149- /// Check if an expression contains any Context expressions or AST expressions that
150- /// reference the component instance (implicit receiver).
151- fn expression_contains_context (
152- expr : & IrExpression < ' _ > ,
153- expressions : & crate :: pipeline:: expression_store:: ExpressionStore < ' _ > ,
154- ) -> bool {
155- match expr {
156- IrExpression :: Context ( _) => true ,
157- // Check AST expressions for implicit receiver usage (this.property, this.method())
158- IrExpression :: Ast ( ast) => ast_contains_implicit_receiver ( ast) ,
159- // Check ExpressionRef by looking up the stored expression
160- IrExpression :: ExpressionRef ( id) => {
161- let stored_expr = expressions. get ( * id) ;
162- ast_contains_implicit_receiver ( stored_expr)
163- }
164- // Resolved expressions (created by resolveNames phase)
165- IrExpression :: ResolvedCall ( rc) => {
166- expression_contains_context ( & rc. receiver , expressions)
167- || rc. args . iter ( ) . any ( |e| expression_contains_context ( e, expressions) )
168- }
169- IrExpression :: ResolvedPropertyRead ( rp) => {
170- expression_contains_context ( & rp. receiver , expressions)
171- }
172- IrExpression :: ResolvedBinary ( rb) => {
173- expression_contains_context ( & rb. left , expressions)
174- || expression_contains_context ( & rb. right , expressions)
175- }
176- IrExpression :: ResolvedKeyedRead ( rk) => {
177- expression_contains_context ( & rk. receiver , expressions)
178- || expression_contains_context ( & rk. key , expressions)
179- }
180- IrExpression :: ResolvedSafePropertyRead ( rsp) => {
181- expression_contains_context ( & rsp. receiver , expressions)
182- }
183- IrExpression :: SafeTernary ( st) => {
184- expression_contains_context ( & st. guard , expressions)
185- || expression_contains_context ( & st. expr , expressions)
186- }
187- IrExpression :: SafePropertyRead ( sp) => {
188- expression_contains_context ( & sp. receiver , expressions)
189- }
190- IrExpression :: SafeKeyedRead ( sk) => {
191- expression_contains_context ( & sk. receiver , expressions)
192- || expression_contains_context ( & sk. index , expressions)
193- }
194- IrExpression :: SafeInvokeFunction ( sf) => {
195- expression_contains_context ( & sf. receiver , expressions)
196- || sf. args . iter ( ) . any ( |e| expression_contains_context ( e, expressions) )
197- }
198- IrExpression :: PipeBinding ( pb) => {
199- pb. args . iter ( ) . any ( |e| expression_contains_context ( e, expressions) )
200- }
201- IrExpression :: PipeBindingVariadic ( pbv) => {
202- expression_contains_context ( & pbv. args , expressions)
203- }
204- IrExpression :: PureFunction ( pf) => {
205- pf. args . iter ( ) . any ( |e| expression_contains_context ( e, expressions) )
206- }
207- IrExpression :: Interpolation ( i) => {
208- i. expressions . iter ( ) . any ( |e| expression_contains_context ( e, expressions) )
209- }
210- IrExpression :: ResetView ( rv) => expression_contains_context ( & rv. expr , expressions) ,
211- IrExpression :: ConditionalCase ( cc) => {
212- cc. expr . as_ref ( ) . is_some_and ( |e| expression_contains_context ( e, expressions) )
213- }
214- IrExpression :: TwoWayBindingSet ( tbs) => {
215- expression_contains_context ( & tbs. target , expressions)
216- || expression_contains_context ( & tbs. value , expressions)
217- }
218- IrExpression :: StoreLet ( sl) => expression_contains_context ( & sl. value , expressions) ,
219- IrExpression :: ConstCollected ( cc) => expression_contains_context ( & cc. expr , expressions) ,
220- IrExpression :: RestoreView ( rv) => {
221- if let crate :: ir:: expression:: RestoreViewTarget :: Dynamic ( e) = & rv. view {
222- expression_contains_context ( e, expressions)
223- } else {
224- false
225- }
226- }
227- // Leaf expressions
228- _ => false ,
229- }
230- }
231-
232- /// Check if an Angular AST expression contains any reference to the implicit receiver (this).
233- /// This includes property reads like `this.foo` and method calls like `this.bar()`.
234- fn ast_contains_implicit_receiver ( ast : & crate :: ast:: expression:: AngularExpression < ' _ > ) -> bool {
235- use crate :: ast:: expression:: AngularExpression ;
236-
237- match ast {
238- // Direct implicit receiver reference
239- AngularExpression :: ImplicitReceiver ( _) => true ,
240- // Property read - check if it's on implicit receiver or recurse
241- AngularExpression :: PropertyRead ( pr) => ast_contains_implicit_receiver ( & pr. receiver ) ,
242- // Safe property read
243- AngularExpression :: SafePropertyRead ( pr) => ast_contains_implicit_receiver ( & pr. receiver ) ,
244- // Keyed read
245- AngularExpression :: KeyedRead ( kr) => {
246- ast_contains_implicit_receiver ( & kr. receiver ) || ast_contains_implicit_receiver ( & kr. key )
247- }
248- // Safe keyed read
249- AngularExpression :: SafeKeyedRead ( kr) => {
250- ast_contains_implicit_receiver ( & kr. receiver ) || ast_contains_implicit_receiver ( & kr. key )
251- }
252- // Function call
253- AngularExpression :: Call ( call) => {
254- ast_contains_implicit_receiver ( & call. receiver )
255- || call. args . iter ( ) . any ( ast_contains_implicit_receiver)
256- }
257- // Safe call
258- AngularExpression :: SafeCall ( call) => {
259- ast_contains_implicit_receiver ( & call. receiver )
260- || call. args . iter ( ) . any ( ast_contains_implicit_receiver)
261- }
262- // Binary expression
263- AngularExpression :: Binary ( b) => {
264- ast_contains_implicit_receiver ( & b. left ) || ast_contains_implicit_receiver ( & b. right )
265- }
266- // Unary expression
267- AngularExpression :: Unary ( u) => ast_contains_implicit_receiver ( & u. expr ) ,
268- // Conditional (ternary)
269- AngularExpression :: Conditional ( c) => {
270- ast_contains_implicit_receiver ( & c. condition )
271- || ast_contains_implicit_receiver ( & c. true_exp )
272- || ast_contains_implicit_receiver ( & c. false_exp )
273- }
274- // Pipe binding
275- AngularExpression :: BindingPipe ( p) => {
276- ast_contains_implicit_receiver ( & p. exp )
277- || p. args . iter ( ) . any ( ast_contains_implicit_receiver)
278- }
279- // Not expressions
280- AngularExpression :: PrefixNot ( n) => ast_contains_implicit_receiver ( & n. expression ) ,
281- AngularExpression :: NonNullAssert ( n) => ast_contains_implicit_receiver ( & n. expression ) ,
282- // Typeof/void expressions
283- AngularExpression :: TypeofExpression ( t) => ast_contains_implicit_receiver ( & t. expression ) ,
284- AngularExpression :: VoidExpression ( v) => ast_contains_implicit_receiver ( & v. expression ) ,
285- AngularExpression :: SpreadElement ( spread) => {
286- ast_contains_implicit_receiver ( & spread. expression )
287- }
288- // Chain - check all expressions
289- AngularExpression :: Chain ( c) => c. expressions . iter ( ) . any ( ast_contains_implicit_receiver) ,
290- // Interpolation - check all expressions
291- AngularExpression :: Interpolation ( i) => {
292- i. expressions . iter ( ) . any ( ast_contains_implicit_receiver)
293- }
294- // Template literals
295- AngularExpression :: TemplateLiteral ( t) => {
296- t. expressions . iter ( ) . any ( ast_contains_implicit_receiver)
297- }
298- AngularExpression :: TaggedTemplateLiteral ( t) => {
299- ast_contains_implicit_receiver ( & t. tag )
300- || t. template . expressions . iter ( ) . any ( ast_contains_implicit_receiver)
301- }
302- // Array literal
303- AngularExpression :: LiteralArray ( arr) => {
304- arr. expressions . iter ( ) . any ( ast_contains_implicit_receiver)
305- }
306- // Map literal
307- AngularExpression :: LiteralMap ( map) => map. values . iter ( ) . any ( ast_contains_implicit_receiver) ,
308- // Parenthesized expression
309- AngularExpression :: ParenthesizedExpression ( p) => {
310- ast_contains_implicit_receiver ( & p. expression )
311- }
312- // Arrow function - check the body
313- AngularExpression :: ArrowFunction ( arrow) => ast_contains_implicit_receiver ( & arrow. body ) ,
314- // Literals and other leaf nodes don't contain implicit receiver
315- AngularExpression :: LiteralPrimitive ( _)
316- | AngularExpression :: Empty ( _)
317- | AngularExpression :: ThisReceiver ( _)
318- | AngularExpression :: RegularExpressionLiteral ( _) => false ,
319- }
320- }
321-
322157/// Check if the track expression is a simple variable read of $index or $item.
323158fn check_simple_track_variable (
324159 track : & IrExpression < ' _ > ,
0 commit comments