Merge pull request #37727 from Kingwl/logical_assignment

Add logical assignment operator
This commit is contained in:
Daniel Rosenwasser
2020-06-09 14:44:08 -07:00
committed by GitHub
108 changed files with 6398 additions and 594 deletions
+30 -7
View File
@@ -905,6 +905,9 @@ namespace ts {
function isNarrowingBinaryExpression(expr: BinaryExpression) {
switch (expr.operatorToken.kind) {
case SyntaxKind.EqualsToken:
case SyntaxKind.BarBarEqualsToken:
case SyntaxKind.AmpersandAmpersandEqualsToken:
case SyntaxKind.QuestionQuestionEqualsToken:
return containsNarrowableReference(expr.left);
case SyntaxKind.EqualsEqualsToken:
case SyntaxKind.ExclamationEqualsToken:
@@ -1041,12 +1044,18 @@ namespace ts {
}
}
function isLogicalAssignmentExpression(node: Node) {
node = skipParentheses(node);
return isBinaryExpression(node) && isLogicalOrCoalescingAssignmentOperator(node.operatorToken.kind);
}
function isTopLevelLogicalExpression(node: Node): boolean {
while (isParenthesizedExpression(node.parent) ||
isPrefixUnaryExpression(node.parent) && node.parent.operator === SyntaxKind.ExclamationToken) {
node = node.parent;
}
return !isStatementCondition(node) &&
!isLogicalAssignmentExpression(node.parent) &&
!isLogicalExpression(node.parent) &&
!(isOptionalChain(node.parent) && node.parent.expression === node);
}
@@ -1063,7 +1072,7 @@ namespace ts {
function bindCondition(node: Expression | undefined, trueTarget: FlowLabel, falseTarget: FlowLabel) {
doWithConditionalBranches(bind, node, trueTarget, falseTarget);
if (!node || !isLogicalExpression(node) && !(isOptionalChain(node) && isOutermostOptionalChain(node))) {
if (!node || !isLogicalAssignmentExpression(node) && !isLogicalExpression(node) && !(isOptionalChain(node) && isOutermostOptionalChain(node))) {
addAntecedent(trueTarget, createFlowCondition(FlowFlags.TrueCondition, currentFlow, node));
addAntecedent(falseTarget, createFlowCondition(FlowFlags.FalseCondition, currentFlow, node));
}
@@ -1404,9 +1413,9 @@ namespace ts {
}
}
function bindLogicalExpression(node: BinaryExpression, trueTarget: FlowLabel, falseTarget: FlowLabel) {
function bindLogicalLikeExpression(node: BinaryExpression, trueTarget: FlowLabel, falseTarget: FlowLabel) {
const preRightLabel = createBranchLabel();
if (node.operatorToken.kind === SyntaxKind.AmpersandAmpersandToken) {
if (node.operatorToken.kind === SyntaxKind.AmpersandAmpersandToken || node.operatorToken.kind === SyntaxKind.AmpersandAmpersandEqualsToken) {
bindCondition(node.left, preRightLabel, falseTarget);
}
else {
@@ -1414,7 +1423,17 @@ namespace ts {
}
currentFlow = finishFlowLabel(preRightLabel);
bind(node.operatorToken);
bindCondition(node.right, trueTarget, falseTarget);
if (isLogicalOrCoalescingAssignmentOperator(node.operatorToken.kind)) {
doWithConditionalBranches(bind, node.right, trueTarget, falseTarget);
bindAssignmentTargetFlow(node.left);
addAntecedent(trueTarget, createFlowCondition(FlowFlags.TrueCondition, currentFlow, node));
addAntecedent(falseTarget, createFlowCondition(FlowFlags.FalseCondition, currentFlow, node));
}
else {
bindCondition(node.right, trueTarget, falseTarget);
}
}
function bindPrefixUnaryExpressionFlow(node: PrefixUnaryExpression) {
@@ -1500,14 +1519,15 @@ namespace ts {
// TODO: bindLogicalExpression is recursive - if we want to handle deeply nested `&&` expressions
// we'll need to handle the `bindLogicalExpression` scenarios in this state machine, too
// For now, though, since the common cases are chained `+`, leaving it recursive is fine
if (operator === SyntaxKind.AmpersandAmpersandToken || operator === SyntaxKind.BarBarToken || operator === SyntaxKind.QuestionQuestionToken) {
if (operator === SyntaxKind.AmpersandAmpersandToken || operator === SyntaxKind.BarBarToken || operator === SyntaxKind.QuestionQuestionToken ||
isLogicalOrCoalescingAssignmentOperator(operator)) {
if (isTopLevelLogicalExpression(node)) {
const postExpressionLabel = createBranchLabel();
bindLogicalExpression(node, postExpressionLabel, postExpressionLabel);
bindLogicalLikeExpression(node, postExpressionLabel, postExpressionLabel);
currentFlow = finishFlowLabel(postExpressionLabel);
}
else {
bindLogicalExpression(node, currentTrueTarget!, currentFalseTarget!);
bindLogicalLikeExpression(node, currentTrueTarget!, currentFalseTarget!);
}
completeNode();
}
@@ -3607,6 +3627,9 @@ namespace ts {
if (operatorTokenKind === SyntaxKind.QuestionQuestionToken) {
transformFlags |= TransformFlags.AssertES2020;
}
else if (isLogicalOrCoalescingAssignmentOperator(operatorTokenKind)) {
transformFlags |= TransformFlags.AssertESNext;
}
else if (operatorTokenKind === SyntaxKind.EqualsToken && leftKind === SyntaxKind.ObjectLiteralExpression) {
// Destructuring object assignments with are ES2015 syntax
// and possibly ES2018 if they contain rest
+30 -3
View File
@@ -19864,6 +19864,9 @@ namespace ts {
case SyntaxKind.BinaryExpression:
switch ((<BinaryExpression>node).operatorToken.kind) {
case SyntaxKind.EqualsToken:
case SyntaxKind.BarBarEqualsToken:
case SyntaxKind.AmpersandAmpersandEqualsToken:
case SyntaxKind.QuestionQuestionEqualsToken:
return getReferenceCandidate((<BinaryExpression>node).left);
case SyntaxKind.CommaToken:
return getReferenceCandidate((<BinaryExpression>node).right);
@@ -20838,6 +20841,9 @@ namespace ts {
function narrowTypeByBinaryExpression(type: Type, expr: BinaryExpression, assumeTrue: boolean): Type {
switch (expr.operatorToken.kind) {
case SyntaxKind.EqualsToken:
case SyntaxKind.BarBarEqualsToken:
case SyntaxKind.AmpersandAmpersandEqualsToken:
case SyntaxKind.QuestionQuestionEqualsToken:
return narrowTypeByTruthiness(narrowType(type, expr.right, assumeTrue), expr.left, assumeTrue);
case SyntaxKind.EqualsEqualsToken:
case SyntaxKind.ExclamationEqualsToken:
@@ -22462,6 +22468,9 @@ namespace ts {
const { left, operatorToken, right } = binaryExpression;
switch (operatorToken.kind) {
case SyntaxKind.EqualsToken:
case SyntaxKind.AmpersandAmpersandEqualsToken:
case SyntaxKind.BarBarEqualsToken:
case SyntaxKind.QuestionQuestionEqualsToken:
if (node !== right) {
return undefined;
}
@@ -28758,17 +28767,35 @@ namespace ts {
case SyntaxKind.InKeyword:
return checkInExpression(left, right, leftType, rightType);
case SyntaxKind.AmpersandAmpersandToken:
return getTypeFacts(leftType) & TypeFacts.Truthy ?
case SyntaxKind.AmpersandAmpersandEqualsToken: {
const resultType = getTypeFacts(leftType) & TypeFacts.Truthy ?
getUnionType([extractDefinitelyFalsyTypes(strictNullChecks ? leftType : getBaseTypeOfLiteralType(rightType)), rightType]) :
leftType;
if (operator === SyntaxKind.AmpersandAmpersandEqualsToken) {
checkAssignmentOperator(rightType);
}
return resultType;
}
case SyntaxKind.BarBarToken:
return getTypeFacts(leftType) & TypeFacts.Falsy ?
case SyntaxKind.BarBarEqualsToken: {
const resultType = getTypeFacts(leftType) & TypeFacts.Falsy ?
getUnionType([removeDefinitelyFalsyTypes(leftType), rightType], UnionReduction.Subtype) :
leftType;
if (operator === SyntaxKind.BarBarEqualsToken) {
checkAssignmentOperator(rightType);
}
return resultType;
}
case SyntaxKind.QuestionQuestionToken:
return getTypeFacts(leftType) & TypeFacts.EQUndefinedOrNull ?
case SyntaxKind.QuestionQuestionEqualsToken: {
const resultType = getTypeFacts(leftType) & TypeFacts.EQUndefinedOrNull ?
getUnionType([getNonNullableType(leftType), rightType], UnionReduction.Subtype) :
leftType;
if (operator === SyntaxKind.QuestionQuestionEqualsToken) {
checkAssignmentOperator(rightType);
}
return resultType;
}
case SyntaxKind.EqualsToken:
const declKind = isBinaryExpression(left.parent) ? getAssignmentDeclarationKind(left.parent) : AssignmentDeclarationKind.None;
checkAssignmentDeclaration(declKind, rightType);
+18 -8
View File
@@ -208,6 +208,9 @@ namespace ts {
"&=": SyntaxKind.AmpersandEqualsToken,
"|=": SyntaxKind.BarEqualsToken,
"^=": SyntaxKind.CaretEqualsToken,
"||=": SyntaxKind.BarBarEqualsToken,
"&&=": SyntaxKind.AmpersandAmpersandEqualsToken,
"??=": SyntaxKind.QuestionQuestionEqualsToken,
"@": SyntaxKind.AtToken,
"`": SyntaxKind.BacktickToken
});
@@ -1667,6 +1670,9 @@ namespace ts {
return token = SyntaxKind.PercentToken;
case CharacterCodes.ampersand:
if (text.charCodeAt(pos + 1) === CharacterCodes.ampersand) {
if (text.charCodeAt(pos + 2) === CharacterCodes.equals) {
return pos += 3, token = SyntaxKind.AmpersandAmpersandEqualsToken;
}
return pos += 2, token = SyntaxKind.AmpersandAmpersandToken;
}
if (text.charCodeAt(pos + 1) === CharacterCodes.equals) {
@@ -1928,15 +1934,16 @@ namespace ts {
pos++;
return token = SyntaxKind.GreaterThanToken;
case CharacterCodes.question:
if (text.charCodeAt(pos + 1) === CharacterCodes.dot && !isDigit(text.charCodeAt(pos + 2))) {
return pos += 2, token = SyntaxKind.QuestionDotToken;
}
if (text.charCodeAt(pos + 1) === CharacterCodes.question) {
if (text.charCodeAt(pos + 2) === CharacterCodes.equals) {
return pos += 3, token = SyntaxKind.QuestionQuestionEqualsToken;
}
return pos += 2, token = SyntaxKind.QuestionQuestionToken;
}
pos++;
if (text.charCodeAt(pos) === CharacterCodes.dot && !isDigit(text.charCodeAt(pos + 1))) {
pos++;
return token = SyntaxKind.QuestionDotToken;
}
if (text.charCodeAt(pos) === CharacterCodes.question) {
pos++;
return token = SyntaxKind.QuestionQuestionToken;
}
return token = SyntaxKind.QuestionToken;
case CharacterCodes.openBracket:
pos++;
@@ -1965,6 +1972,9 @@ namespace ts {
}
if (text.charCodeAt(pos + 1) === CharacterCodes.bar) {
if (text.charCodeAt(pos + 2) === CharacterCodes.equals) {
return pos += 3, token = SyntaxKind.BarBarEqualsToken;
}
return pos += 2, token = SyntaxKind.BarBarToken;
}
if (text.charCodeAt(pos + 1) === CharacterCodes.equals) {
+4 -12
View File
@@ -73,7 +73,7 @@ namespace ts {
let thisArg: Expression | undefined;
if (captureThisArg) {
if (shouldCaptureInTempVariable(expression)) {
if (!isSimpleCopiableExpression(expression)) {
thisArg = createTempVariable(hoistVariableDeclaration);
expression = createAssignment(thisArg, expression);
// if (inParameterInitializer) tempVariableInParameter = true;
@@ -113,7 +113,7 @@ namespace ts {
const leftThisArg = isSyntheticReference(left) ? left.thisArg : undefined;
let leftExpression = isSyntheticReference(left) ? left.expression : left;
let capturedLeft: Expression = leftExpression;
if (shouldCaptureInTempVariable(leftExpression)) {
if (!isSimpleCopiableExpression(leftExpression)) {
capturedLeft = createTempVariable(hoistVariableDeclaration);
leftExpression = createAssignment(capturedLeft, leftExpression);
// if (inParameterInitializer) tempVariableInParameter = true;
@@ -126,7 +126,7 @@ namespace ts {
case SyntaxKind.PropertyAccessExpression:
case SyntaxKind.ElementAccessExpression:
if (i === chain.length - 1 && captureThisArg) {
if (shouldCaptureInTempVariable(rightExpression)) {
if (!isSimpleCopiableExpression(rightExpression)) {
thisArg = createTempVariable(hoistVariableDeclaration);
rightExpression = createAssignment(thisArg, rightExpression);
// if (inParameterInitializer) tempVariableInParameter = true;
@@ -184,7 +184,7 @@ namespace ts {
function transformNullishCoalescingExpression(node: BinaryExpression) {
let left = visitNode(node.left, visitor, isExpression);
let right = left;
if (shouldCaptureInTempVariable(left)) {
if (!isSimpleCopiableExpression(left)) {
right = createTempVariable(hoistVariableDeclaration);
left = createAssignment(right, left);
// if (inParameterInitializer) tempVariableInParameter = true;
@@ -196,14 +196,6 @@ namespace ts {
);
}
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;
}
function visitDeleteExpression(node: DeleteExpression) {
return isOptionalChain(skipParentheses(node.expression))
? setOriginalNode(visitNonOptionalExpression(node.expression, /*captureThisArg*/ false, /*isDelete*/ true), node)
+57
View File
@@ -1,6 +1,9 @@
/*@internal*/
namespace ts {
export function transformESNext(context: TransformationContext) {
const {
hoistVariableDeclaration
} = context;
return chainBundle(transformSourceFile);
function transformSourceFile(node: SourceFile) {
@@ -16,9 +19,63 @@ namespace ts {
return node;
}
switch (node.kind) {
case SyntaxKind.BinaryExpression:
const binaryExpression = <BinaryExpression>node;
if (isLogicalOrCoalescingAssignmentExpression(binaryExpression)) {
return transformLogicalAssignment(binaryExpression);
}
// falls through
default:
return visitEachChild(node, visitor, context);
}
}
function transformLogicalAssignment(binaryExpression: AssignmentExpression<Token<LogicalOrCoalescingAssignmentOperator>>): VisitResult<Node> {
const operator = binaryExpression.operatorToken;
const nonAssignmentOperator = getNonAssignmentOperatorForCompoundAssignment(operator.kind);
let left = skipParentheses(visitNode(binaryExpression.left, visitor, isLeftHandSideExpression));
let assignmentTarget = left;
const right = skipParentheses(visitNode(binaryExpression.right, visitor, isExpression));
if (isAccessExpression(left)) {
const tempVariable = createTempVariable(hoistVariableDeclaration);
if (isPropertyAccessExpression(left)) {
assignmentTarget = createPropertyAccess(
tempVariable,
left.name
);
left = createPropertyAccess(
createAssignment(
tempVariable,
left.expression
),
left.name
);
}
else {
assignmentTarget = createElementAccess(
tempVariable,
left.argumentExpression
);
left = createElementAccess(
createAssignment(
tempVariable,
left.expression
),
left.argumentExpression
);
}
}
return createBinary(
left,
nonAssignmentOperator,
createParen(
createAssignment(
assignmentTarget,
right
)
)
);
}
}
}
+5 -1
View File
@@ -259,7 +259,7 @@ namespace ts {
&& kind <= SyntaxKind.LastCompoundAssignment;
}
export function getNonAssignmentOperatorForCompoundAssignment(kind: CompoundAssignmentOperator): BitwiseOperatorOrHigher {
export function getNonAssignmentOperatorForCompoundAssignment(kind: CompoundAssignmentOperator): LogicalOperatorOrHigher | SyntaxKind.QuestionQuestionToken {
switch (kind) {
case SyntaxKind.PlusEqualsToken: return SyntaxKind.PlusToken;
case SyntaxKind.MinusEqualsToken: return SyntaxKind.MinusToken;
@@ -273,6 +273,10 @@ namespace ts {
case SyntaxKind.AmpersandEqualsToken: return SyntaxKind.AmpersandToken;
case SyntaxKind.BarEqualsToken: return SyntaxKind.BarToken;
case SyntaxKind.CaretEqualsToken: return SyntaxKind.CaretToken;
case SyntaxKind.BarBarEqualsToken: return SyntaxKind.BarBarToken;
case SyntaxKind.AmpersandAmpersandEqualsToken: return SyntaxKind.AmpersandAmpersandToken;
case SyntaxKind.QuestionQuestionEqualsToken: return SyntaxKind.QuestionQuestionToken;
}
}
+12
View File
@@ -203,6 +203,9 @@ namespace ts {
GreaterThanGreaterThanGreaterThanEqualsToken,
AmpersandEqualsToken,
BarEqualsToken,
BarBarEqualsToken,
AmpersandAmpersandEqualsToken,
QuestionQuestionEqualsToken,
CaretEqualsToken,
// Identifiers and PrivateIdentifiers
Identifier,
@@ -1608,6 +1611,9 @@ namespace ts {
| SyntaxKind.LessThanLessThanEqualsToken
| SyntaxKind.GreaterThanGreaterThanGreaterThanEqualsToken
| SyntaxKind.GreaterThanGreaterThanEqualsToken
| SyntaxKind.BarBarEqualsToken
| SyntaxKind.AmpersandAmpersandEqualsToken
| SyntaxKind.QuestionQuestionEqualsToken
;
// see: https://tc39.github.io/ecma262/#prod-AssignmentExpression
@@ -1629,6 +1635,12 @@ namespace ts {
| SyntaxKind.CommaToken
;
export type LogicalOrCoalescingAssignmentOperator
= SyntaxKind.AmpersandAmpersandEqualsToken
| SyntaxKind.BarBarEqualsToken
| SyntaxKind.QuestionQuestionEqualsToken
;
export type BinaryOperatorToken = Token<BinaryOperator>;
export interface BinaryExpression extends Expression, Declaration {
+17 -1
View File
@@ -2594,7 +2594,7 @@ namespace ts {
switch (parent.kind) {
case SyntaxKind.BinaryExpression:
const binaryOperator = (<BinaryExpression>parent).operatorToken.kind;
return isAssignmentOperator(binaryOperator) && (<BinaryExpression>parent).left === node ?
return isAssignmentOperator(binaryOperator) && !isLogicalOrCoalescingAssignmentOperator(binaryOperator) && (<BinaryExpression>parent).left === node ?
binaryOperator === SyntaxKind.EqualsToken ? AssignmentKind.Definite : AssignmentKind.Compound :
AssignmentKind.None;
case SyntaxKind.PrefixUnaryExpression:
@@ -3226,6 +3226,9 @@ namespace ts {
case SyntaxKind.AmpersandEqualsToken:
case SyntaxKind.CaretEqualsToken:
case SyntaxKind.BarEqualsToken:
case SyntaxKind.BarBarEqualsToken:
case SyntaxKind.AmpersandAmpersandEqualsToken:
case SyntaxKind.QuestionQuestionEqualsToken:
return Associativity.Right;
}
}
@@ -3282,6 +3285,9 @@ namespace ts {
case SyntaxKind.AmpersandEqualsToken:
case SyntaxKind.CaretEqualsToken:
case SyntaxKind.BarEqualsToken:
case SyntaxKind.BarBarEqualsToken:
case SyntaxKind.AmpersandAmpersandEqualsToken:
case SyntaxKind.QuestionQuestionEqualsToken:
return 3;
default:
@@ -4454,6 +4460,16 @@ namespace ts {
|| token === SyntaxKind.ExclamationToken;
}
export function isLogicalOrCoalescingAssignmentOperator(token: SyntaxKind): token is LogicalOrCoalescingAssignmentOperator {
return token === SyntaxKind.BarBarEqualsToken
|| token === SyntaxKind.AmpersandAmpersandEqualsToken
|| token === SyntaxKind.QuestionQuestionEqualsToken;
}
export function isLogicalOrCoalescingAssignmentExpression(expr: BinaryExpression): expr is AssignmentExpression<Token<LogicalOrCoalescingAssignmentOperator>> {
return isLogicalOrCoalescingAssignmentOperator(expr.operatorToken.kind);
}
export function isAssignmentOperator(token: SyntaxKind): boolean {
return token >= SyntaxKind.FirstAssignment && token <= SyntaxKind.LastAssignment;
}