optimize transform of optional chaining and nullish coalescing (#34951)

* optimize transform of optional chaining and nullish coalescing

* remove unnecessary condition

* typo

* fix lint

* prevent capturing of super

* swap branches again

* accept new baselines

* avoid temporary objects
This commit is contained in:
Klaus Meinhardt
2019-11-15 02:34:13 +01:00
committed by Ron Buckton
parent aa0cb889da
commit 8f40ac06cc
36 changed files with 533 additions and 390 deletions

View File

@@ -58,43 +58,29 @@ namespace ts {
return updateParen(node, expression);
}
function visitNonOptionalPropertyAccessExpression(node: PropertyAccessExpression, captureThisArg: boolean): Expression {
function visitNonOptionalPropertyOrElementAccessExpression(node: AccessExpression, captureThisArg: boolean): Expression {
if (isOptionalChain(node)) {
// If `node` is an optional chain, then it is the outermost chain of an optional expression.
return visitOptionalExpression(node, captureThisArg);
}
let expression = visitNode(node.expression, visitor, isExpression);
let expression: Expression = visitNode(node.expression, visitor, isExpression);
Debug.assertNotNode(expression, isSyntheticReference);
let thisArg: Expression | undefined;
if (captureThisArg) {
// `a.b` -> { expression: `(_a = a).b`, thisArg: `_a` }
thisArg = createTempVariable(hoistVariableDeclaration);
expression = createParen(createAssignment(thisArg, expression));
if (shouldCaptureInTempVariable(expression)) {
thisArg = createTempVariable(hoistVariableDeclaration);
expression = createAssignment(thisArg, expression);
}
else {
thisArg = expression;
}
}
expression = updatePropertyAccess(node, expression, visitNode(node.name, visitor, isIdentifier));
return thisArg ? createSyntheticReferenceExpression(expression, thisArg) : expression;
}
function visitNonOptionalElementAccessExpression(node: ElementAccessExpression, captureThisArg: boolean): Expression {
if (isOptionalChain(node)) {
// If `node` is an optional chain, then it is the outermost chain of an optional expression.
return visitOptionalExpression(node, captureThisArg);
}
let expression = visitNode(node.expression, visitor, isExpression);
Debug.assertNotNode(expression, isSyntheticReference);
let thisArg: Expression | undefined;
if (captureThisArg) {
// `a[b]` -> { expression: `(_a = a)[b]`, thisArg: `_a` }
thisArg = createTempVariable(hoistVariableDeclaration);
expression = createParen(createAssignment(thisArg, expression));
}
expression = updateElementAccess(node, expression, visitNode(node.argumentExpression, visitor, isExpression));
expression = node.kind === SyntaxKind.PropertyAccessExpression
? updatePropertyAccess(node, expression, visitNode(node.name, visitor, isIdentifier))
: updateElementAccess(node, expression, visitNode(node.argumentExpression, visitor, isExpression));
return thisArg ? createSyntheticReferenceExpression(expression, thisArg) : expression;
}
@@ -109,8 +95,8 @@ namespace ts {
function visitNonOptionalExpression(node: Expression, captureThisArg: boolean): Expression {
switch (node.kind) {
case SyntaxKind.ParenthesizedExpression: return visitNonOptionalParenthesizedExpression(node as ParenthesizedExpression, captureThisArg);
case SyntaxKind.PropertyAccessExpression: return visitNonOptionalPropertyAccessExpression(node as PropertyAccessExpression, captureThisArg);
case SyntaxKind.ElementAccessExpression: return visitNonOptionalElementAccessExpression(node as ElementAccessExpression, captureThisArg);
case SyntaxKind.PropertyAccessExpression:
case SyntaxKind.ElementAccessExpression: return visitNonOptionalPropertyOrElementAccessExpression(node as AccessExpression, captureThisArg);
case SyntaxKind.CallExpression: return visitNonOptionalCallExpression(node as CallExpression, captureThisArg);
default: return visitNode(node, visitor, isExpression);
}
@@ -119,39 +105,38 @@ namespace ts {
function visitOptionalExpression(node: OptionalChain, captureThisArg: boolean): Expression {
const { expression, chain } = flattenChain(node);
const left = visitNonOptionalExpression(expression, isCallChain(chain[0]));
const temp = createTempVariable(hoistVariableDeclaration);
const leftThisArg = isSyntheticReference(left) ? left.thisArg : undefined;
const leftExpression = isSyntheticReference(left) ? left.expression : left;
let rightExpression: Expression = temp;
let leftExpression = isSyntheticReference(left) ? left.expression : left;
let capturedLeft: Expression = leftExpression;
if (shouldCaptureInTempVariable(leftExpression)) {
capturedLeft = createTempVariable(hoistVariableDeclaration);
leftExpression = createAssignment(capturedLeft, leftExpression);
}
let rightExpression = capturedLeft;
let thisArg: Expression | undefined;
for (let i = 0; i < chain.length; i++) {
const segment = chain[i];
switch (segment.kind) {
case SyntaxKind.PropertyAccessExpression:
if (i === chain.length - 1 && captureThisArg) {
thisArg = createTempVariable(hoistVariableDeclaration);
rightExpression = createParen(createAssignment(thisArg, rightExpression));
}
rightExpression = createPropertyAccess(
rightExpression,
visitNode(segment.name, visitor, isIdentifier)
);
break;
case SyntaxKind.ElementAccessExpression:
if (i === chain.length - 1 && captureThisArg) {
thisArg = createTempVariable(hoistVariableDeclaration);
rightExpression = createParen(createAssignment(thisArg, rightExpression));
if (shouldCaptureInTempVariable(rightExpression)) {
thisArg = createTempVariable(hoistVariableDeclaration);
rightExpression = createAssignment(thisArg, rightExpression);
}
else {
thisArg = rightExpression;
}
}
rightExpression = createElementAccess(
rightExpression,
visitNode(segment.argumentExpression, visitor, isExpression)
);
rightExpression = segment.kind === SyntaxKind.PropertyAccessExpression
? createPropertyAccess(rightExpression, visitNode(segment.name, visitor, isIdentifier))
: createElementAccess(rightExpression, visitNode(segment.argumentExpression, visitor, isExpression));
break;
case SyntaxKind.CallExpression:
if (i === 0 && leftThisArg) {
rightExpression = createFunctionCall(
rightExpression,
leftThisArg,
leftThisArg.kind === SyntaxKind.SuperKeyword ? createThis() : leftThisArg,
visitNodes(segment.arguments, visitor, isExpression)
);
}
@@ -168,48 +153,49 @@ namespace ts {
}
const target = createConditional(
createLogicalOr(
createStrictEquality(createAssignment(temp, leftExpression), createNull()),
createStrictEquality(temp, createVoidZero())
),
createNotNullCondition(leftExpression, capturedLeft, /*invert*/ true),
createVoidZero(),
rightExpression
rightExpression,
);
return thisArg ? createSyntheticReferenceExpression(target, thisArg) : target;
}
function createNotNullCondition(node: Expression) {
function createNotNullCondition(left: Expression, right: Expression, invert?: boolean) {
return createBinary(
createBinary(
node,
createToken(SyntaxKind.ExclamationEqualsEqualsToken),
left,
createToken(invert ? SyntaxKind.EqualsEqualsEqualsToken : SyntaxKind.ExclamationEqualsEqualsToken),
createNull()
),
createToken(SyntaxKind.AmpersandAmpersandToken),
createToken(invert ? SyntaxKind.BarBarToken : SyntaxKind.AmpersandAmpersandToken),
createBinary(
node,
createToken(SyntaxKind.ExclamationEqualsEqualsToken),
right,
createToken(invert ? SyntaxKind.EqualsEqualsEqualsToken : SyntaxKind.ExclamationEqualsEqualsToken),
createVoidZero()
)
);
}
function transformNullishCoalescingExpression(node: BinaryExpression) {
const expressions: Expression[] = [];
let left = visitNode(node.left, visitor, isExpression);
if (!isIdentifier(left)) {
const temp = createTempVariable(hoistVariableDeclaration);
expressions.push(createAssignment(temp, left));
left = temp;
let right = left;
if (shouldCaptureInTempVariable(left)) {
right = createTempVariable(hoistVariableDeclaration);
left = createAssignment(right, left);
}
expressions.push(
createParen(
createConditional(
createNotNullCondition(left),
left,
visitNode(node.right, visitor, isExpression)))
);
return inlineExpressions(expressions);
return createConditional(
createNotNullCondition(left, right),
right,
visitNode(node.right, visitor, isExpression),
);
}
function shouldCaptureInTempVariable(expression: Expression): boolean {
// don't capture identifiers and `this` in a temporary variable
// `super` cannot be captured as it's no real variable
return !isIdentifier(expression) &&
expression.kind !== SyntaxKind.ThisKeyword &&
expression.kind !== SyntaxKind.SuperKeyword;
}
}
}