@@ -797,6 +797,10 @@ namespace ts {
797
797
case SyntaxKind . VariableDeclaration :
798
798
bindVariableDeclarationFlow ( < VariableDeclaration > node ) ;
799
799
break ;
800
+ case SyntaxKind . PropertyAccessExpression :
801
+ case SyntaxKind . ElementAccessExpression :
802
+ bindAccessExpressionFlow ( < AccessExpression > node ) ;
803
+ break ;
800
804
case SyntaxKind . CallExpression :
801
805
bindCallExpressionFlow ( < CallExpression > node ) ;
802
806
break ;
@@ -941,7 +945,9 @@ namespace ts {
941
945
}
942
946
if ( expression . kind === SyntaxKind . TrueKeyword && flags & FlowFlags . FalseCondition ||
943
947
expression . kind === SyntaxKind . FalseKeyword && flags & FlowFlags . TrueCondition ) {
944
- return unreachableFlow ;
948
+ if ( ! isOptionalChainRoot ( expression . parent ) ) {
949
+ return unreachableFlow ;
950
+ }
945
951
}
946
952
if ( ! isNarrowingExpression ( expression ) ) {
947
953
return antecedent ;
@@ -1015,23 +1021,28 @@ namespace ts {
1015
1021
}
1016
1022
1017
1023
function isTopLevelLogicalExpression ( node : Node ) : boolean {
1018
- while ( node . parent . kind === SyntaxKind . ParenthesizedExpression ||
1019
- node . parent . kind === SyntaxKind . PrefixUnaryExpression &&
1020
- ( < PrefixUnaryExpression > node . parent ) . operator === SyntaxKind . ExclamationToken ) {
1024
+ while ( isParenthesizedExpression ( node . parent ) ||
1025
+ isPrefixUnaryExpression ( node . parent ) && node . parent . operator === SyntaxKind . ExclamationToken ) {
1021
1026
node = node . parent ;
1022
1027
}
1023
- return ! isStatementCondition ( node ) && ! isLogicalExpression ( node . parent ) ;
1028
+ return ! isStatementCondition ( node ) &&
1029
+ ! isLogicalExpression ( node . parent ) &&
1030
+ ! ( isOptionalChain ( node . parent ) && node . parent . expression === node ) ;
1024
1031
}
1025
1032
1026
- function bindCondition ( node : Expression | undefined , trueTarget : FlowLabel , falseTarget : FlowLabel ) {
1027
- const saveTrueTarget = currentTrueTarget ;
1028
- const saveFalseTarget = currentFalseTarget ;
1033
+ function doWithConditionalBranches < T > ( action : ( value : T ) => void , value : T , trueTarget : FlowLabel , falseTarget : FlowLabel ) {
1034
+ const savedTrueTarget = currentTrueTarget ;
1035
+ const savedFalseTarget = currentFalseTarget ;
1029
1036
currentTrueTarget = trueTarget ;
1030
1037
currentFalseTarget = falseTarget ;
1031
- bind ( node ) ;
1032
- currentTrueTarget = saveTrueTarget ;
1033
- currentFalseTarget = saveFalseTarget ;
1034
- if ( ! node || ! isLogicalExpression ( node ) ) {
1038
+ action ( value ) ;
1039
+ currentTrueTarget = savedTrueTarget ;
1040
+ currentFalseTarget = savedFalseTarget ;
1041
+ }
1042
+
1043
+ function bindCondition ( node : Expression | undefined , trueTarget : FlowLabel , falseTarget : FlowLabel ) {
1044
+ doWithConditionalBranches ( bind , node , trueTarget , falseTarget ) ;
1045
+ if ( ! node || ! isLogicalExpression ( node ) && ! ( isOptionalChain ( node ) && isOutermostOptionalChain ( node ) ) ) {
1035
1046
addAntecedent ( trueTarget , createFlowCondition ( FlowFlags . TrueCondition , currentFlow , node ) ) ;
1036
1047
addAntecedent ( falseTarget , createFlowCondition ( FlowFlags . FalseCondition , currentFlow , node ) ) ;
1037
1048
}
@@ -1536,22 +1547,96 @@ namespace ts {
1536
1547
}
1537
1548
}
1538
1549
1539
- function bindCallExpressionFlow ( node : CallExpression ) {
1540
- // If the target of the call expression is a function expression or arrow function we have
1541
- // an immediately invoked function expression (IIFE). Initialize the flowNode property to
1542
- // the current control flow (which includes evaluation of the IIFE arguments).
1543
- let expr : Expression = node . expression ;
1544
- while ( expr . kind === SyntaxKind . ParenthesizedExpression ) {
1545
- expr = ( < ParenthesizedExpression > expr ) . expression ;
1546
- }
1547
- if ( expr . kind === SyntaxKind . FunctionExpression || expr . kind === SyntaxKind . ArrowFunction ) {
1548
- bindEach ( node . typeArguments ) ;
1549
- bindEach ( node . arguments ) ;
1550
- bind ( node . expression ) ;
1550
+ function isOutermostOptionalChain ( node : OptionalChain ) {
1551
+ return ! isOptionalChain ( node . parent ) || isOptionalChainRoot ( node . parent ) || node !== node . parent . expression ;
1552
+ }
1553
+
1554
+ function bindOptionalExpression ( node : Expression , trueTarget : FlowLabel , falseTarget : FlowLabel ) {
1555
+ doWithConditionalBranches ( bind , node , trueTarget , falseTarget ) ;
1556
+ if ( ! isOptionalChain ( node ) || isOutermostOptionalChain ( node ) ) {
1557
+ addAntecedent ( trueTarget , createFlowCondition ( FlowFlags . TrueCondition , currentFlow , node ) ) ;
1558
+ addAntecedent ( falseTarget , createFlowCondition ( FlowFlags . FalseCondition , currentFlow , node ) ) ;
1559
+ }
1560
+ }
1561
+
1562
+ function bindOptionalChainRest ( node : OptionalChain ) {
1563
+ bind ( node . questionDotToken ) ;
1564
+ switch ( node . kind ) {
1565
+ case SyntaxKind . PropertyAccessExpression :
1566
+ bind ( node . name ) ;
1567
+ break ;
1568
+ case SyntaxKind . ElementAccessExpression :
1569
+ bind ( node . argumentExpression ) ;
1570
+ break ;
1571
+ case SyntaxKind . CallExpression :
1572
+ bindEach ( node . typeArguments ) ;
1573
+ bindEach ( node . arguments ) ;
1574
+ break ;
1575
+ }
1576
+ }
1577
+
1578
+ function bindOptionalChain ( node : OptionalChain , trueTarget : FlowLabel , falseTarget : FlowLabel ) {
1579
+ // For an optional chain, we emulate the behavior of a logical expression:
1580
+ //
1581
+ // a?.b -> a && a.b
1582
+ // a?.b.c -> a && a.b.c
1583
+ // a?.b?.c -> a && a.b && a.b.c
1584
+ // a?.[x = 1] -> a && a[x = 1]
1585
+ //
1586
+ // To do this we descend through the chain until we reach the root of a chain (the expression with a `?.`)
1587
+ // and build it's CFA graph as if it were the first condition (`a && ...`). Then we bind the rest
1588
+ // of the node as part of the "true" branch, and continue to do so as we ascend back up to the outermost
1589
+ // chain node. We then treat the entire node as the right side of the expression.
1590
+ const preChainLabel = node . questionDotToken ? createBranchLabel ( ) : undefined ;
1591
+ bindOptionalExpression ( node . expression , preChainLabel || trueTarget , falseTarget ) ;
1592
+ if ( preChainLabel ) {
1593
+ currentFlow = finishFlowLabel ( preChainLabel ) ;
1594
+ }
1595
+ doWithConditionalBranches ( bindOptionalChainRest , node , trueTarget , falseTarget ) ;
1596
+ if ( isOutermostOptionalChain ( node ) ) {
1597
+ addAntecedent ( trueTarget , createFlowCondition ( FlowFlags . TrueCondition , currentFlow , node ) ) ;
1598
+ addAntecedent ( falseTarget , createFlowCondition ( FlowFlags . FalseCondition , currentFlow , node ) ) ;
1599
+ }
1600
+ }
1601
+
1602
+ function bindOptionalChainFlow ( node : OptionalChain ) {
1603
+ if ( isTopLevelLogicalExpression ( node ) ) {
1604
+ const postExpressionLabel = createBranchLabel ( ) ;
1605
+ bindOptionalChain ( node , postExpressionLabel , postExpressionLabel ) ;
1606
+ currentFlow = finishFlowLabel ( postExpressionLabel ) ;
1607
+ }
1608
+ else {
1609
+ bindOptionalChain ( node , currentTrueTarget ! , currentFalseTarget ! ) ;
1610
+ }
1611
+ }
1612
+
1613
+ function bindAccessExpressionFlow ( node : AccessExpression ) {
1614
+ if ( isOptionalChain ( node ) ) {
1615
+ bindOptionalChainFlow ( node ) ;
1551
1616
}
1552
1617
else {
1553
1618
bindEachChild ( node ) ;
1554
1619
}
1620
+ }
1621
+
1622
+ function bindCallExpressionFlow ( node : CallExpression ) {
1623
+ if ( isOptionalChain ( node ) ) {
1624
+ bindOptionalChainFlow ( node ) ;
1625
+ }
1626
+ else {
1627
+ // If the target of the call expression is a function expression or arrow function we have
1628
+ // an immediately invoked function expression (IIFE). Initialize the flowNode property to
1629
+ // the current control flow (which includes evaluation of the IIFE arguments).
1630
+ const expr = skipParentheses ( node . expression ) ;
1631
+ if ( expr . kind === SyntaxKind . FunctionExpression || expr . kind === SyntaxKind . ArrowFunction ) {
1632
+ bindEach ( node . typeArguments ) ;
1633
+ bindEach ( node . arguments ) ;
1634
+ bind ( node . expression ) ;
1635
+ }
1636
+ else {
1637
+ bindEachChild ( node ) ;
1638
+ }
1639
+ }
1555
1640
if ( node . expression . kind === SyntaxKind . PropertyAccessExpression ) {
1556
1641
const propertyAccess = < PropertyAccessExpression > node . expression ;
1557
1642
if ( isNarrowableOperand ( propertyAccess . expression ) && isPushOrUnshiftIdentifier ( propertyAccess . name ) ) {
@@ -3297,6 +3382,10 @@ namespace ts {
3297
3382
const callee = skipOuterExpressions ( node . expression ) ;
3298
3383
const expression = node . expression ;
3299
3384
3385
+ if ( node . flags & NodeFlags . OptionalChain ) {
3386
+ transformFlags |= TransformFlags . ContainsESNext ;
3387
+ }
3388
+
3300
3389
if ( node . typeArguments ) {
3301
3390
transformFlags |= TransformFlags . AssertTypeScript ;
3302
3391
}
@@ -3692,6 +3781,10 @@ namespace ts {
3692
3781
function computePropertyAccess ( node : PropertyAccessExpression , subtreeFlags : TransformFlags ) {
3693
3782
let transformFlags = subtreeFlags ;
3694
3783
3784
+ if ( node . flags & NodeFlags . OptionalChain ) {
3785
+ transformFlags |= TransformFlags . ContainsESNext ;
3786
+ }
3787
+
3695
3788
// If a PropertyAccessExpression starts with a super keyword, then it is
3696
3789
// ES6 syntax, and requires a lexical `this` binding.
3697
3790
if ( node . expression . kind === SyntaxKind . SuperKeyword ) {
@@ -3707,6 +3800,10 @@ namespace ts {
3707
3800
function computeElementAccess ( node : ElementAccessExpression , subtreeFlags : TransformFlags ) {
3708
3801
let transformFlags = subtreeFlags ;
3709
3802
3803
+ if ( node . flags & NodeFlags . OptionalChain ) {
3804
+ transformFlags |= TransformFlags . ContainsESNext ;
3805
+ }
3806
+
3710
3807
// If an ElementAccessExpression starts with a super keyword, then it is
3711
3808
// ES6 syntax, and requires a lexical `this` binding.
3712
3809
if ( node . expression . kind === SyntaxKind . SuperKeyword ) {
0 commit comments