-
Notifications
You must be signed in to change notification settings - Fork 12.9k
Add support for Optional Chaining #33294
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 15 commits
c95daab
c2f53fc
0f5d5d6
24de747
7be47ab
e073c05
72f44d9
488c9e6
2ffd8e1
096bb49
1bf2d56
1d7446f
b282b62
be3e21f
fd8c0d4
7c9ef50
ad7c33c
6b49a03
aaa30f4
5ea7cb5
7463860
0828674
d408e81
c2070be
dfc798f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -193,6 +193,8 @@ namespace ts { | |
let currentReturnTarget: FlowLabel | undefined; | ||
let currentTrueTarget: FlowLabel | undefined; | ||
let currentFalseTarget: FlowLabel | undefined; | ||
let currentPresentTarget: FlowLabel | undefined; | ||
let currentMissingTarget: FlowLabel | undefined; | ||
let preSwitchCaseFlow: FlowNode | undefined; | ||
let activeLabels: ActiveLabel[] | undefined; | ||
let hasExplicitReturn: boolean; | ||
|
@@ -261,6 +263,8 @@ namespace ts { | |
currentReturnTarget = undefined; | ||
currentTrueTarget = undefined; | ||
currentFalseTarget = undefined; | ||
currentPresentTarget = undefined; | ||
currentMissingTarget = undefined; | ||
activeLabels = undefined!; | ||
hasExplicitReturn = false; | ||
emitFlags = NodeFlags.None; | ||
|
@@ -797,6 +801,10 @@ namespace ts { | |
case SyntaxKind.VariableDeclaration: | ||
bindVariableDeclarationFlow(<VariableDeclaration>node); | ||
break; | ||
case SyntaxKind.PropertyAccessExpression: | ||
case SyntaxKind.ElementAccessExpression: | ||
bindAccessExpressionFlow(<AccessExpression>node); | ||
break; | ||
case SyntaxKind.CallExpression: | ||
bindCallExpressionFlow(<CallExpression>node); | ||
break; | ||
|
@@ -932,6 +940,22 @@ namespace ts { | |
} | ||
} | ||
|
||
function findAntecedent(flowNode: FlowNode, flags: FlowFlags) { | ||
if (flowNode.flags & flags) { | ||
return flowNode; | ||
} | ||
if (flowNode.flags & FlowFlags.BranchLabel) { | ||
const branch = flowNode as FlowLabel; | ||
if (branch.antecedents) { | ||
for (const antecedent of branch.antecedents) { | ||
if (antecedent.flags & flags) { | ||
return antecedent; | ||
} | ||
} | ||
} | ||
} | ||
} | ||
|
||
function createFlowCondition(flags: FlowFlags, antecedent: FlowNode, expression: Expression | undefined): FlowNode { | ||
if (antecedent.flags & FlowFlags.Unreachable) { | ||
return antecedent; | ||
|
@@ -943,13 +967,36 @@ namespace ts { | |
expression.kind === SyntaxKind.FalseKeyword && flags & FlowFlags.TrueCondition) { | ||
return unreachableFlow; | ||
} | ||
|
||
if (flags & FlowFlags.TrueCondition) { | ||
// If the antecedent is an optional chain, only the Present branch can exist on the `true` condition | ||
const presentFlow = findAntecedent(currentFlow, FlowFlags.Present); | ||
if (presentFlow) { | ||
antecedent = presentFlow; | ||
} | ||
} | ||
|
||
if (!isNarrowingExpression(expression)) { | ||
return antecedent; | ||
} | ||
setFlowNodeReferenced(antecedent); | ||
return flowNodeCreated({ flags, antecedent, node: expression }); | ||
} | ||
|
||
function createFlowOptionalChain(flags: FlowFlags, antecedent: FlowNode, expression: Expression): FlowNode { | ||
if (antecedent.flags & FlowFlags.Unreachable) { | ||
return antecedent; | ||
} | ||
if (flags & FlowFlags.Present) { | ||
const presentFlow = findAntecedent(currentFlow, FlowFlags.Present); | ||
if (presentFlow) { | ||
antecedent = presentFlow; | ||
} | ||
} | ||
setFlowNodeReferenced(antecedent); | ||
return flowNodeCreated({ flags, antecedent, node: expression }); | ||
} | ||
|
||
function createFlowSwitchClause(antecedent: FlowNode, switchStatement: SwitchStatement, clauseStart: number, clauseEnd: number): FlowNode { | ||
if (!isNarrowingExpression(switchStatement.expression)) { | ||
return antecedent; | ||
|
@@ -1542,22 +1589,79 @@ namespace ts { | |
} | ||
} | ||
|
||
function bindCallExpressionFlow(node: CallExpression) { | ||
// If the target of the call expression is a function expression or arrow function we have | ||
// an immediately invoked function expression (IIFE). Initialize the flowNode property to | ||
// the current control flow (which includes evaluation of the IIFE arguments). | ||
let expr: Expression = node.expression; | ||
while (expr.kind === SyntaxKind.ParenthesizedExpression) { | ||
expr = (<ParenthesizedExpression>expr).expression; | ||
} | ||
if (expr.kind === SyntaxKind.FunctionExpression || expr.kind === SyntaxKind.ArrowFunction) { | ||
bindEach(node.typeArguments); | ||
bindEach(node.arguments); | ||
bind(node.expression); | ||
function bindOptionalExpression(node: Expression, presentTarget: FlowLabel, missingTarget: FlowLabel) { | ||
const savedPresentTarget = currentPresentTarget; | ||
const savedMissingTarget = currentMissingTarget; | ||
currentPresentTarget = presentTarget; | ||
currentMissingTarget = missingTarget; | ||
bind(node); | ||
currentPresentTarget = savedPresentTarget; | ||
currentMissingTarget = savedMissingTarget; | ||
if (!isValidOptionalChain(node) || isOutermostOptionalChain(node)) { | ||
addAntecedent(presentTarget, createFlowOptionalChain(FlowFlags.Present, currentFlow, node)); | ||
addAntecedent(missingTarget, createFlowOptionalChain(FlowFlags.Missing, currentFlow, node)); | ||
} | ||
} | ||
|
||
function isOutermostOptionalChain(node: ValidOptionalChain) { | ||
return !isOptionalChain(node.parent) || !!node.parent.questionDotToken || node.parent.expression !== node; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Does this definition of outermost handle parenthesis? eg, There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Optional chains stop at parentheses (i.e. |
||
} | ||
|
||
function bindOptionalChainFlow(node: ValidOptionalChain) { | ||
const postExpressionLabel = isOutermostOptionalChain(node) ? createBranchLabel() : undefined; | ||
const presentTarget = postExpressionLabel ? createBranchLabel() : Debug.assertDefined(currentPresentTarget); | ||
const missingTarget = postExpressionLabel ? createBranchLabel() : Debug.assertDefined(currentMissingTarget); | ||
bindOptionalExpression(node.expression, presentTarget, missingTarget); | ||
if (!isValidOptionalChain(node.expression) || node.questionDotToken) { | ||
currentFlow = finishFlowLabel(presentTarget); | ||
} | ||
bind(node.questionDotToken); | ||
switch (node.kind) { | ||
case SyntaxKind.PropertyAccessExpression: | ||
bind(node.name); | ||
break; | ||
case SyntaxKind.ElementAccessExpression: | ||
bind(node.argumentExpression); | ||
break; | ||
case SyntaxKind.CallExpression: | ||
bindEach(node.typeArguments); | ||
bindEach(node.arguments); | ||
break; | ||
} | ||
if (postExpressionLabel) { | ||
addAntecedent(postExpressionLabel, currentFlow); | ||
addAntecedent(postExpressionLabel, finishFlowLabel(missingTarget)); | ||
currentFlow = finishFlowLabel(postExpressionLabel); | ||
} | ||
} | ||
|
||
function bindAccessExpressionFlow(node: AccessExpression) { | ||
if (isValidOptionalChain(node)) { | ||
bindOptionalChainFlow(node); | ||
} | ||
else { | ||
bindEachChild(node); | ||
} | ||
} | ||
|
||
function bindCallExpressionFlow(node: CallExpression) { | ||
if (isValidOptionalChain(node)) { | ||
bindOptionalChainFlow(node); | ||
} | ||
else { | ||
// If the target of the call expression is a function expression or arrow function we have | ||
// an immediately invoked function expression (IIFE). Initialize the flowNode property to | ||
// the current control flow (which includes evaluation of the IIFE arguments). | ||
const expr = skipParentheses(node.expression); | ||
if (expr.kind === SyntaxKind.FunctionExpression || expr.kind === SyntaxKind.ArrowFunction) { | ||
bindEach(node.typeArguments); | ||
bindEach(node.arguments); | ||
bind(node.expression); | ||
} | ||
else { | ||
bindEachChild(node); | ||
} | ||
} | ||
if (node.expression.kind === SyntaxKind.PropertyAccessExpression) { | ||
const propertyAccess = <PropertyAccessExpression>node.expression; | ||
if (isNarrowableOperand(propertyAccess.expression) && isPushOrUnshiftIdentifier(propertyAccess.name)) { | ||
|
@@ -3245,6 +3349,10 @@ namespace ts { | |
const callee = skipOuterExpressions(node.expression); | ||
const expression = node.expression; | ||
|
||
if (node.flags & NodeFlags.OptionalChain) { | ||
transformFlags |= TransformFlags.ContainsESNext; | ||
} | ||
|
||
if (node.typeArguments) { | ||
transformFlags |= TransformFlags.AssertTypeScript; | ||
} | ||
|
@@ -3640,6 +3748,10 @@ namespace ts { | |
function computePropertyAccess(node: PropertyAccessExpression, subtreeFlags: TransformFlags) { | ||
let transformFlags = subtreeFlags; | ||
|
||
if (node.flags & NodeFlags.OptionalChain) { | ||
transformFlags |= TransformFlags.ContainsESNext; | ||
} | ||
|
||
// If a PropertyAccessExpression starts with a super keyword, then it is | ||
// ES6 syntax, and requires a lexical `this` binding. | ||
if (node.expression.kind === SyntaxKind.SuperKeyword) { | ||
|
@@ -3655,6 +3767,10 @@ namespace ts { | |
function computeElementAccess(node: ElementAccessExpression, subtreeFlags: TransformFlags) { | ||
let transformFlags = subtreeFlags; | ||
|
||
if (node.flags & NodeFlags.OptionalChain) { | ||
transformFlags |= TransformFlags.ContainsESNext; | ||
} | ||
|
||
// If an ElementAccessExpression starts with a super keyword, then it is | ||
// ES6 syntax, and requires a lexical `this` binding. | ||
if (node.expression.kind === SyntaxKind.SuperKeyword) { | ||
|
Uh oh!
There was an error while loading. Please reload this page.