@@ -38,7 +38,7 @@ import {
3838import { KnownInputs } from './known_inputs' ;
3939import { attemptRetrieveInputFromSymbol } from './nodes_to_input' ;
4040import { MigrationHost } from '../migration_host' ;
41- import { InputDescriptor } from '../utils/input_id' ;
41+ import { InputDescriptor , InputUniqueKey } from '../utils/input_id' ;
4242import { InputIncompatibilityReason } from './incompatibility' ;
4343import { BoundAttribute , BoundEvent } from '../../../../../../compiler/src/render3/r3_ast' ;
4444
@@ -51,8 +51,8 @@ export interface TmplInputExpressionReference<ExprContext> {
5151 targetInput : InputDescriptor ;
5252 read : PropertyRead ;
5353 context : ExprContext ;
54- isInsideNarrowingExpression : boolean ;
5554 isObjectShorthandExpression : boolean ;
55+ isLikelyNarrowed : boolean ;
5656}
5757
5858/**
@@ -67,12 +67,15 @@ export class TemplateReferenceVisitor extends TmplAstRecursiveVisitor {
6767
6868 /**
6969 * Whether we are currently descending into HTML AST nodes
70- * where bound attributes should be considered "narrowed" .
70+ * where all bound attributes are considered potentially narrowing .
7171 *
72- * TODO: Remove with: https://github.com/angular/angular/pull/55456 .
72+ * Keeps track of all referenced inputs in such attribute expressions .
7373 */
74- private isAttributeInsideNarrowingExpression = false ;
74+ private templateAttributeReferencedInputs : TmplInputExpressionReference < TmplAstNode > [ ] | null =
75+ null ;
76+
7577 private expressionVisitor : TemplateExpressionReferenceVisitor < TmplAstNode > ;
78+ private seenInputsCount = new Map < InputUniqueKey , number > ( ) ;
7679
7780 constructor (
7881 host : MigrationHost ,
@@ -88,16 +91,56 @@ export class TemplateReferenceVisitor extends TmplAstRecursiveVisitor {
8891 templateTypeChecker ,
8992 componentClass ,
9093 knownInputs ,
91- this . result ,
9294 ) ;
9395 }
9496
97+ private checkExpressionForReferencedInputs ( activeNode : TmplAstNode , expressionNode : AST ) {
98+ const referencedInputs = this . expressionVisitor . checkTemplateExpression (
99+ activeNode ,
100+ expressionNode ,
101+ ) ;
102+ // Add all references to the overall visitor result.
103+ this . result . push ( ...referencedInputs ) ;
104+
105+ // Count usages of seen input references. We'll use this to make decisions
106+ // based on whether inputs are potentially narrowed or not.
107+ for ( const input of referencedInputs ) {
108+ this . seenInputsCount . set (
109+ input . targetInput . key ,
110+ ( this . seenInputsCount . get ( input . targetInput . key ) ?? 0 ) + 1 ,
111+ ) ;
112+ }
113+
114+ return referencedInputs ;
115+ }
116+
117+ private descendAndCheckForNarrowedSimilarReferences (
118+ potentiallyNarrowedInputs : TmplInputExpressionReference < TmplAstNode > [ ] ,
119+ descend : ( ) => void ,
120+ ) {
121+ const inputs = potentiallyNarrowedInputs . map ( ( i ) => ( {
122+ ref : i ,
123+ key : i . targetInput . key ,
124+ pastCount : this . seenInputsCount . get ( i . targetInput . key ) ?? 0 ,
125+ } ) ) ;
126+
127+ descend ( ) ;
128+
129+ for ( const input of inputs ) {
130+ // Input was referenced inside a narrowable spot, and is used in child nodes.
131+ // This is a sign for the input to be narrowed. Mark it as such.
132+ if ( ( this . seenInputsCount . get ( input . key ) ?? 0 ) > input . pastCount ) {
133+ input . ref . isLikelyNarrowed = true ;
134+ }
135+ }
136+ }
137+
95138 override visitTemplate ( template : TmplAstTemplate ) : void {
96139 // Note: We assume all bound expressions for templates may be subject
97140 // to TCB narrowing. This is relevant for now until we support narrowing
98141 // of signal calls in templates.
99142 // TODO: Remove with: https://github.com/angular/angular/pull/55456.
100- this . isAttributeInsideNarrowingExpression = true ;
143+ this . templateAttributeReferencedInputs = [ ] ;
101144
102145 tmplAstVisitAll ( this , template . attributes ) ;
103146 tmplAstVisitAll ( this , template . templateAttrs ) ;
@@ -110,98 +153,77 @@ export class TemplateReferenceVisitor extends TmplAstRecursiveVisitor {
110153 tmplAstVisitAll ( this , template . outputs ) ;
111154 }
112155
113- this . isAttributeInsideNarrowingExpression = false ;
156+ const referencedInputs = this . templateAttributeReferencedInputs ;
157+ this . templateAttributeReferencedInputs = null ;
114158
115- tmplAstVisitAll ( this , template . children ) ;
116- tmplAstVisitAll ( this , template . references ) ;
117- tmplAstVisitAll ( this , template . variables ) ;
159+ this . descendAndCheckForNarrowedSimilarReferences ( referencedInputs , ( ) => {
160+ tmplAstVisitAll ( this , template . children ) ;
161+ tmplAstVisitAll ( this , template . references ) ;
162+ tmplAstVisitAll ( this , template . variables ) ;
163+ } ) ;
118164 }
119165
120166 override visitIfBlockBranch ( block : TmplAstIfBlockBranch ) : void {
121167 if ( block . expression ) {
122- this . expressionVisitor . checkTemplateExpression (
123- block ,
124- /* isInsideNarrowingExpression */ true ,
125- block . expression ,
126- ) ;
168+ const referencedInputs = this . checkExpressionForReferencedInputs ( block , block . expression ) ;
169+ this . descendAndCheckForNarrowedSimilarReferences ( referencedInputs , ( ) => {
170+ super . visitIfBlockBranch ( block ) ;
171+ } ) ;
172+ } else {
173+ super . visitIfBlockBranch ( block ) ;
127174 }
128- super . visitIfBlockBranch ( block ) ;
129175 }
130176
131177 override visitForLoopBlock ( block : TmplAstForLoopBlock ) : void {
132- this . expressionVisitor . checkTemplateExpression (
133- block ,
134- /* isInsideNarrowingExpression */ false ,
135- block . expression ,
136- ) ;
137- this . expressionVisitor . checkTemplateExpression (
138- block ,
139- /* isInsideNarrowingExpression */ false ,
140- block . trackBy ,
141- ) ;
178+ this . checkExpressionForReferencedInputs ( block , block . expression ) ;
179+ this . checkExpressionForReferencedInputs ( block , block . trackBy ) ;
142180 super . visitForLoopBlock ( block ) ;
143181 }
144182
145183 override visitSwitchBlock ( block : TmplAstSwitchBlock ) : void {
146- this . expressionVisitor . checkTemplateExpression (
147- block ,
148- /* isInsideNarrowingExpression */ true ,
149- block . expression ,
150- ) ;
151- super . visitSwitchBlock ( block ) ;
184+ const referencedInputs = this . checkExpressionForReferencedInputs ( block , block . expression ) ;
185+ this . descendAndCheckForNarrowedSimilarReferences ( referencedInputs , ( ) => {
186+ super . visitSwitchBlock ( block ) ;
187+ } ) ;
152188 }
153189
154190 override visitSwitchBlockCase ( block : TmplAstSwitchBlockCase ) : void {
155191 if ( block . expression ) {
156- this . expressionVisitor . checkTemplateExpression (
157- block ,
158- /* isInsideNarrowingExpression */ true ,
159- block . expression ,
160- ) ;
192+ const referencedInputs = this . checkExpressionForReferencedInputs ( block , block . expression ) ;
193+ this . descendAndCheckForNarrowedSimilarReferences ( referencedInputs , ( ) => {
194+ super . visitSwitchBlockCase ( block ) ;
195+ } ) ;
196+ } else {
197+ super . visitSwitchBlockCase ( block ) ;
161198 }
162- super . visitSwitchBlockCase ( block ) ;
163199 }
164200
165201 override visitDeferredBlock ( deferred : TmplAstDeferredBlock ) : void {
166202 if ( deferred . triggers . when ) {
167- this . expressionVisitor . checkTemplateExpression (
168- deferred ,
169- /* isInsideNarrowingExpression */ false ,
170- deferred . triggers . when . value ,
171- ) ;
203+ this . checkExpressionForReferencedInputs ( deferred , deferred . triggers . when . value ) ;
172204 }
173205 if ( deferred . prefetchTriggers . when ) {
174- this . expressionVisitor . checkTemplateExpression (
175- deferred ,
176- /* isInsideNarrowingExpression */ false ,
177- deferred . prefetchTriggers . when . value ,
178- ) ;
206+ this . checkExpressionForReferencedInputs ( deferred , deferred . prefetchTriggers . when . value ) ;
179207 }
180208 super . visitDeferredBlock ( deferred ) ;
181209 }
182210
183211 override visitBoundText ( text : TmplAstBoundText ) : void {
184- this . expressionVisitor . checkTemplateExpression (
185- text ,
186- /* isInsideNarrowingExpression */ false ,
187- text . value ,
188- ) ;
212+ this . checkExpressionForReferencedInputs ( text , text . value ) ;
189213 }
190214
191215 override visitBoundEvent ( attribute : TmplAstBoundEvent ) : void {
192- this . expressionVisitor . checkTemplateExpression (
193- attribute ,
194- /* isInsideNarrowingExpression */ false ,
195- attribute . handler ,
196- ) ;
216+ this . checkExpressionForReferencedInputs ( attribute , attribute . handler ) ;
197217 }
198218
199219 override visitBoundAttribute ( attribute : TmplAstBoundAttribute ) : void {
200- this . expressionVisitor . checkTemplateExpression (
201- attribute ,
202- /* isInsideNarrowingExpression */ this . isAttributeInsideNarrowingExpression ,
203- attribute . value ,
204- ) ;
220+ const referencedInputs = this . checkExpressionForReferencedInputs ( attribute , attribute . value ) ;
221+
222+ // Attributes inside templates are potentially "narrowed" and hence we
223+ // keep track of all referenced inputs to see if they actually are.
224+ if ( this . templateAttributeReferencedInputs !== null ) {
225+ this . templateAttributeReferencedInputs . push ( ...referencedInputs ) ;
226+ }
205227 }
206228}
207229
@@ -214,7 +236,8 @@ export class TemplateReferenceVisitor extends TmplAstRecursiveVisitor {
214236 */
215237export class TemplateExpressionReferenceVisitor < ExprContext > extends RecursiveAstVisitor {
216238 private activeTmplAstNode : ExprContext | null = null ;
217- private isInsideNarrowingExpression = false ;
239+ private detectedInputReferences : TmplInputExpressionReference < ExprContext > [ ] = [ ] ;
240+
218241 private isInsideObjectShorthandExpression = false ;
219242
220243 constructor (
@@ -223,21 +246,20 @@ export class TemplateExpressionReferenceVisitor<ExprContext> extends RecursiveAs
223246 private templateTypeChecker : TemplateTypeChecker | null ,
224247 private componentClass : ts . ClassDeclaration ,
225248 private knownInputs : KnownInputs ,
226- private result : TmplInputExpressionReference < ExprContext > [ ] ,
227249 ) {
228250 super ( ) ;
229251 }
230252
231253 /** Checks the given AST expression. */
232254 checkTemplateExpression (
233255 activeNode : ExprContext ,
234- isInsideNarrowingExpression : boolean ,
235256 expressionNode : AST ,
236- ) {
257+ ) : TmplInputExpressionReference < ExprContext > [ ] {
258+ this . detectedInputReferences = [ ] ;
237259 this . activeTmplAstNode = activeNode ;
238- this . isInsideNarrowingExpression = isInsideNarrowingExpression ;
239260
240261 expressionNode . visit ( this ) ;
262+ return this . detectedInputReferences ;
241263 }
242264
243265 // Keep track when we are inside an object shorthand expression. This is
@@ -316,12 +338,12 @@ export class TemplateExpressionReferenceVisitor<ExprContext> extends RecursiveAs
316338 return null ;
317339 }
318340
319- this . result . push ( {
341+ this . detectedInputReferences . push ( {
320342 target : targetInput . descriptor . node ,
321343 targetInput : targetInput . descriptor ,
322344 read : ast ,
323345 context : this . activeTmplAstNode ! ,
324- isInsideNarrowingExpression : this . isInsideNarrowingExpression ,
346+ isLikelyNarrowed : false ,
325347 isObjectShorthandExpression : this . isInsideObjectShorthandExpression ,
326348 } ) ;
327349
@@ -365,12 +387,12 @@ export class TemplateExpressionReferenceVisitor<ExprContext> extends RecursiveAs
365387 return null ;
366388 }
367389
368- this . result . push ( {
390+ this . detectedInputReferences . push ( {
369391 target : matchingTarget . descriptor . node ,
370392 targetInput : matchingTarget . descriptor ,
371393 read : ast ,
372394 context : this . activeTmplAstNode ! ,
373- isInsideNarrowingExpression : this . isInsideNarrowingExpression ,
395+ isLikelyNarrowed : false ,
374396 isObjectShorthandExpression : this . isInsideObjectShorthandExpression ,
375397 } ) ;
376398 return matchingTarget . descriptor ;
0 commit comments