@@ -797,6 +797,10 @@ namespace ts {
797797 case SyntaxKind . VariableDeclaration :
798798 bindVariableDeclarationFlow ( < VariableDeclaration > node ) ;
799799 break ;
800+ case SyntaxKind . PropertyAccessExpression :
801+ case SyntaxKind . ElementAccessExpression :
802+ bindAccessExpressionFlow ( < AccessExpression > node ) ;
803+ break ;
800804 case SyntaxKind . CallExpression :
801805 bindCallExpressionFlow ( < CallExpression > node ) ;
802806 break ;
@@ -941,7 +945,9 @@ namespace ts {
941945 }
942946 if ( expression . kind === SyntaxKind . TrueKeyword && flags & FlowFlags . FalseCondition ||
943947 expression . kind === SyntaxKind . FalseKeyword && flags & FlowFlags . TrueCondition ) {
944- return unreachableFlow ;
948+ if ( ! isOptionalChainRoot ( expression . parent ) ) {
949+ return unreachableFlow ;
950+ }
945951 }
946952 if ( ! isNarrowingExpression ( expression ) ) {
947953 return antecedent ;
@@ -1015,23 +1021,28 @@ namespace ts {
10151021 }
10161022
10171023 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 ) {
10211026 node = node . parent ;
10221027 }
1023- return ! isStatementCondition ( node ) && ! isLogicalExpression ( node . parent ) ;
1028+ return ! isStatementCondition ( node ) &&
1029+ ! isLogicalExpression ( node . parent ) &&
1030+ ! ( isOptionalChain ( node . parent ) && node . parent . expression === node ) ;
10241031 }
10251032
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 ;
10291036 currentTrueTarget = trueTarget ;
10301037 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 ) ) ) {
10351046 addAntecedent ( trueTarget , createFlowCondition ( FlowFlags . TrueCondition , currentFlow , node ) ) ;
10361047 addAntecedent ( falseTarget , createFlowCondition ( FlowFlags . FalseCondition , currentFlow , node ) ) ;
10371048 }
@@ -1536,22 +1547,96 @@ namespace ts {
15361547 }
15371548 }
15381549
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 ) ;
15511616 }
15521617 else {
15531618 bindEachChild ( node ) ;
15541619 }
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+ }
15551640 if ( node . expression . kind === SyntaxKind . PropertyAccessExpression ) {
15561641 const propertyAccess = < PropertyAccessExpression > node . expression ;
15571642 if ( isNarrowableOperand ( propertyAccess . expression ) && isPushOrUnshiftIdentifier ( propertyAccess . name ) ) {
@@ -3297,6 +3382,10 @@ namespace ts {
32973382 const callee = skipOuterExpressions ( node . expression ) ;
32983383 const expression = node . expression ;
32993384
3385+ if ( node . flags & NodeFlags . OptionalChain ) {
3386+ transformFlags |= TransformFlags . ContainsESNext ;
3387+ }
3388+
33003389 if ( node . typeArguments ) {
33013390 transformFlags |= TransformFlags . AssertTypeScript ;
33023391 }
@@ -3692,6 +3781,10 @@ namespace ts {
36923781 function computePropertyAccess ( node : PropertyAccessExpression , subtreeFlags : TransformFlags ) {
36933782 let transformFlags = subtreeFlags ;
36943783
3784+ if ( node . flags & NodeFlags . OptionalChain ) {
3785+ transformFlags |= TransformFlags . ContainsESNext ;
3786+ }
3787+
36953788 // If a PropertyAccessExpression starts with a super keyword, then it is
36963789 // ES6 syntax, and requires a lexical `this` binding.
36973790 if ( node . expression . kind === SyntaxKind . SuperKeyword ) {
@@ -3707,6 +3800,10 @@ namespace ts {
37073800 function computeElementAccess ( node : ElementAccessExpression , subtreeFlags : TransformFlags ) {
37083801 let transformFlags = subtreeFlags ;
37093802
3803+ if ( node . flags & NodeFlags . OptionalChain ) {
3804+ transformFlags |= TransformFlags . ContainsESNext ;
3805+ }
3806+
37103807 // If an ElementAccessExpression starts with a super keyword, then it is
37113808 // ES6 syntax, and requires a lexical `this` binding.
37123809 if ( node . expression . kind === SyntaxKind . SuperKeyword ) {
0 commit comments