@@ -85,7 +85,7 @@ export function generateTypeCheckBlock(
8585 const tcb = new Context (
8686 env , domSchemaChecker , oobRecorder , meta . id , meta . boundTarget , meta . pipes , meta . schemas ,
8787 meta . isStandalone ) ;
88- const scope = Scope . forNodes ( tcb , null , tcb . boundTarget . target . template ! , /* guard */ null ) ;
88+ const scope = Scope . forNodes ( tcb , null , null , tcb . boundTarget . target . template ! , /* guard */ null ) ;
8989 const ctxRawType = env . referenceType ( ref ) ;
9090 if ( ! ts . isTypeReferenceNode ( ctxRawType ) ) {
9191 throw new Error (
@@ -379,7 +379,8 @@ class TcbTemplateBodyOp extends TcbOp {
379379
380380 // Create a new Scope for the template. This constructs the list of operations for the template
381381 // children, as well as tracks bindings within the template.
382- const tmplScope = Scope . forNodes ( this . tcb , this . scope , this . template , guard ) ;
382+ const tmplScope =
383+ Scope . forNodes ( this . tcb , this . scope , this . template , this . template . children , guard ) ;
383384
384385 // Render the template's `Scope` into its statements.
385386 const statements = tmplScope . render ( ) ;
@@ -1228,14 +1229,31 @@ class TcbIfOp extends TcbOp {
12281229 return undefined ;
12291230 }
12301231
1231- const branchScope = Scope . forNodes ( this . tcb , this . scope , branch , null ) ;
1232-
12331232 // If the expression is null, it means that it's an `else` statement.
1234- return branch . expression === null ?
1235- ts . factory . createBlock ( branchScope . render ( ) ) :
1236- ts . factory . createIfStatement (
1237- tcbExpression ( branch . expression , this . tcb , branchScope ) ,
1238- ts . factory . createBlock ( branchScope . render ( ) ) , this . generateBranch ( index + 1 ) ) ;
1233+ if ( branch . expression === null ) {
1234+ const branchScope = Scope . forNodes ( this . tcb , this . scope , null , branch . children , null ) ;
1235+ return ts . factory . createBlock ( branchScope . render ( ) ) ;
1236+ }
1237+
1238+ let branchParentScope : Scope ;
1239+
1240+ if ( branch . expressionAlias === null ) {
1241+ branchParentScope = this . scope ;
1242+ } else {
1243+ // If the expression is aliased, we create a scope with a variable containing the expression.
1244+ // Further down we'll use the variable instead of the expression itself in the `if` statement.
1245+ // This allows for the type of the alias to be narrowed.
1246+ branchParentScope = Scope . forNodes ( this . tcb , this . scope , branch , [ ] , null ) ;
1247+ branchParentScope . render ( ) . forEach ( stmt => this . scope . addStatement ( stmt ) ) ;
1248+ }
1249+
1250+ const branchScope = Scope . forNodes ( this . tcb , branchParentScope , null , branch . children , null ) ;
1251+ const expression = branch . expressionAlias === null ?
1252+ tcbExpression ( branch . expression , this . tcb , branchScope ) :
1253+ branchScope . resolve ( branch . expressionAlias ) ;
1254+
1255+ return ts . factory . createIfStatement (
1256+ expression , ts . factory . createBlock ( branchScope . render ( ) ) , this . generateBranch ( index + 1 ) ) ;
12391257 }
12401258}
12411259
@@ -1260,7 +1278,7 @@ class TcbSwitchOp extends TcbOp {
12601278 for ( const current of this . block . cases ) {
12611279 // Our template switch statements don't fall through so we always have a break at the end.
12621280 const breakStatement = ts . factory . createBreakStatement ( ) ;
1263- const clauseScope = Scope . forNodes ( this . tcb , this . scope , current . children , null ) ;
1281+ const clauseScope = Scope . forNodes ( this . tcb , this . scope , null , current . children , null ) ;
12641282
12651283 if ( current . expression === null ) {
12661284 clauses . push ( ts . factory . createDefaultClause ( [ ...clauseScope . render ( ) , breakStatement ] ) ) ;
@@ -1294,7 +1312,7 @@ class TcbForOfOp extends TcbOp {
12941312 }
12951313
12961314 override execute ( ) : null {
1297- const loopScope = Scope . forNodes ( this . tcb , this . scope , this . block , null ) ;
1315+ const loopScope = Scope . forNodes ( this . tcb , this . scope , this . block , this . block . children , null ) ;
12981316 const initializer = ts . factory . createVariableDeclarationList (
12991317 [ ts . factory . createVariableDeclaration ( this . block . item . name ) ] , ts . NodeFlags . Const ) ;
13001318 // It's common to have a for loop over a nullable value (e.g. produced by the `async` pipe).
@@ -1431,67 +1449,60 @@ class Scope {
14311449 * Constructs a `Scope` given either a `TmplAstTemplate` or a list of `TmplAstNode`s.
14321450 *
14331451 * @param tcb the overall context of TCB generation.
1434- * @param parent the `Scope` of the parent template (if any) or `null` if this is the root
1452+ * @param parentScope the `Scope` of the parent template (if any) or `null` if this is the root
14351453 * `Scope`.
1436- * @param blockOrNodes either a `TmplAstTemplate` representing the template for which to
1437- * calculate the `Scope`, or a list of nodes if no outer template object is available.
1454+ * @param scopedNode Node that provides the scope around the child nodes (e.g. a
1455+ * `TmplAstTemplate` node exposing variables to its children).
1456+ * @param children Child nodes that should be appended to the TCB.
14381457 * @param guard an expression that is applied to this scope for type narrowing purposes.
14391458 */
14401459 static forNodes (
1441- tcb : Context , parent : Scope | null ,
1442- blockOrNodes : TmplAstTemplate | TmplAstIfBlockBranch | TmplAstForLoopBlock | TmplAstNode [ ] ,
1443- guard : ts . Expression | null ) : Scope {
1444- const scope = new Scope ( tcb , parent , guard ) ;
1460+ tcb : Context , parentScope : Scope | null ,
1461+ scopedNode : TmplAstTemplate | TmplAstIfBlockBranch | TmplAstForLoopBlock | null ,
1462+ children : TmplAstNode [ ] , guard : ts . Expression | null ) : Scope {
1463+ const scope = new Scope ( tcb , parentScope , guard ) ;
14451464
1446- if ( parent === null && tcb . env . config . enableTemplateTypeChecker ) {
1465+ if ( parentScope === null && tcb . env . config . enableTemplateTypeChecker ) {
14471466 // Add an autocompletion point for the component context.
14481467 scope . opQueue . push ( new TcbComponentContextCompletionOp ( scope ) ) ;
14491468 }
14501469
1451- let children : TmplAstNode [ ] ;
1452-
14531470 // If given an actual `TmplAstTemplate` instance, then process any additional information it
14541471 // has.
1455- if ( blockOrNodes instanceof TmplAstTemplate ) {
1472+ if ( scopedNode instanceof TmplAstTemplate ) {
14561473 // The template's variable declarations need to be added as `TcbVariableOp`s.
14571474 const varMap = new Map < string , TmplAstVariable > ( ) ;
14581475
1459- for ( const v of blockOrNodes . variables ) {
1476+ for ( const v of scopedNode . variables ) {
14601477 // Validate that variables on the `TmplAstTemplate` are only declared once.
14611478 if ( ! varMap . has ( v . name ) ) {
14621479 varMap . set ( v . name , v ) ;
14631480 } else {
14641481 const firstDecl = varMap . get ( v . name ) ! ;
14651482 tcb . oobRecorder . duplicateTemplateVar ( tcb . id , v , firstDecl ) ;
14661483 }
1467- this . registerVariable ( scope , v , new TcbTemplateVariableOp ( tcb , scope , blockOrNodes , v ) ) ;
1484+ this . registerVariable ( scope , v , new TcbTemplateVariableOp ( tcb , scope , scopedNode , v ) ) ;
14681485 }
1469- children = blockOrNodes . children ;
1470- } else if ( blockOrNodes instanceof TmplAstIfBlockBranch ) {
1471- const { expression, expressionAlias} = blockOrNodes ;
1486+ } else if ( scopedNode instanceof TmplAstIfBlockBranch ) {
1487+ const { expression, expressionAlias} = scopedNode ;
14721488 if ( expression !== null && expressionAlias !== null ) {
14731489 this . registerVariable (
14741490 scope , expressionAlias ,
14751491 new TcbBlockVariableOp (
14761492 tcb , scope , tcbExpression ( expression , tcb , scope ) , expressionAlias ) ) ;
14771493 }
1478- children = blockOrNodes . children ;
1479- } else if ( blockOrNodes instanceof TmplAstForLoopBlock ) {
1494+ } else if ( scopedNode instanceof TmplAstForLoopBlock ) {
14801495 this . registerVariable (
1481- scope , blockOrNodes . item ,
1496+ scope , scopedNode . item ,
14821497 new TcbBlockVariableOp (
1483- tcb , scope , ts . factory . createIdentifier ( blockOrNodes . item . name ) , blockOrNodes . item ) ) ;
1498+ tcb , scope , ts . factory . createIdentifier ( scopedNode . item . name ) , scopedNode . item ) ) ;
14841499
1485- for ( const variable of Object . values ( blockOrNodes . contextVariables ) ) {
1500+ for ( const variable of Object . values ( scopedNode . contextVariables ) ) {
14861501 // Note: currently all context variables are assumed to be number types.
14871502 const type = ts . factory . createKeywordTypeNode ( ts . SyntaxKind . NumberKeyword ) ;
14881503 this . registerVariable (
14891504 scope , variable , new TcbBlockImplicitVariableOp ( tcb , scope , type , variable ) ) ;
14901505 }
1491-
1492- children = blockOrNodes . children ;
1493- } else {
1494- children = blockOrNodes ;
14951506 }
14961507 for ( const node of children ) {
14971508 scope . appendNode ( node ) ;
0 commit comments