From 029fbfc7e0851b4dfcf60e63fc1a4e869c63e74b Mon Sep 17 00:00:00 2001 From: Ron Buckton Date: Fri, 6 Oct 2017 13:18:16 -0700 Subject: [PATCH] Initial support for coalesce --- src/compiler/binder.ts | 12 ++++++++--- src/compiler/checker.ts | 9 +++++++-- src/compiler/parser.ts | 2 ++ src/compiler/scanner.ts | 4 ++++ src/compiler/transformers/esnext.ts | 31 +++++++++++++++++++++++++++++ src/compiler/types.ts | 2 ++ src/compiler/utilities.ts | 1 + 7 files changed, 56 insertions(+), 5 deletions(-) diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index 796c9603423..6469d67af97 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -857,7 +857,8 @@ namespace ts { else { return node.kind === SyntaxKind.BinaryExpression && ( (node).operatorToken.kind === SyntaxKind.AmpersandAmpersandToken || - (node).operatorToken.kind === SyntaxKind.BarBarToken); + (node).operatorToken.kind === SyntaxKind.BarBarToken || + (node).operatorToken.kind === SyntaxKind.QuestionQuestionToken); } } } @@ -1256,9 +1257,11 @@ namespace ts { } } + // TODO(rbuckton): Determine how to hook ?? into flow typing function bindBinaryExpressionFlow(node: BinaryExpression) { const operator = node.operatorToken.kind; - if (operator === SyntaxKind.AmpersandAmpersandToken || operator === SyntaxKind.BarBarToken) { + if (operator === SyntaxKind.AmpersandAmpersandToken || + operator === SyntaxKind.BarBarToken) { if (isTopLevelLogicalExpression(node)) { const postExpressionLabel = createBranchLabel(); bindLogicalExpression(node, postExpressionLabel, postExpressionLabel); @@ -2737,7 +2740,10 @@ namespace ts { const operatorTokenKind = node.operatorToken.kind; const leftKind = node.left.kind; - if (operatorTokenKind === SyntaxKind.EqualsToken && leftKind === SyntaxKind.ObjectLiteralExpression) { + if (operatorTokenKind === SyntaxKind.QuestionQuestionToken) { + transformFlags |= TransformFlags.AssertESNext; + } + else if (operatorTokenKind === SyntaxKind.EqualsToken && leftKind === SyntaxKind.ObjectLiteralExpression) { // Destructuring object assignments with are ES2015 syntax // and possibly ESNext if they contain rest transformFlags |= TransformFlags.AssertESNext | TransformFlags.AssertES2015 | TransformFlags.AssertDestructuringAssignment; diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 718fd76625f..02b95c5c073 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -8385,7 +8385,8 @@ namespace ts { return isContextSensitive((node).whenTrue) || isContextSensitive((node).whenFalse); case SyntaxKind.BinaryExpression: - return (node).operatorToken.kind === SyntaxKind.BarBarToken && + return ((node).operatorToken.kind === SyntaxKind.BarBarToken || + (node).operatorToken.kind === SyntaxKind.QuestionQuestionToken) && (isContextSensitive((node).left) || isContextSensitive((node).right)); case SyntaxKind.PropertyAssignment: return isContextSensitive((node).initializer); @@ -13301,7 +13302,7 @@ namespace ts { return getTypeOfExpression(binaryExpression.left); } } - else if (operator === SyntaxKind.BarBarToken) { + else if (operator === SyntaxKind.BarBarToken || operator === SyntaxKind.QuestionQuestionToken) { // When an || expression has a contextual type, the operands are contextually typed by that type. When an || // expression has no contextual type, the right operand is contextually typed by the type of the left operand. let type = getContextualType(binaryExpression); @@ -17989,6 +17990,10 @@ namespace ts { return getTypeFacts(leftType) & TypeFacts.Falsy ? getBestChoiceType(removeDefinitelyFalsyTypes(leftType), rightType) : leftType; + case SyntaxKind.QuestionQuestionToken: + return getTypeFacts(leftType) & TypeFacts.EQUndefinedOrNull ? + getBestChoiceType(getNonNullableType(leftType), rightType) : + leftType; case SyntaxKind.EqualsToken: checkAssignmentOperator(rightType); return getRegularTypeOfObjectLiteral(rightType); diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index ebdf390f5b2..4537a56c594 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -3549,6 +3549,7 @@ namespace ts { function getBinaryOperatorPrecedence(): number { switch (token()) { + case SyntaxKind.QuestionQuestionToken: case SyntaxKind.BarBarToken: return 1; case SyntaxKind.AmpersandAmpersandToken: @@ -4310,6 +4311,7 @@ namespace ts { case SyntaxKind.ColonToken: // foo: case SyntaxKind.SemicolonToken: // foo; case SyntaxKind.QuestionToken: // foo? + case SyntaxKind.QuestionQuestionToken: // foo ?? case SyntaxKind.EqualsEqualsToken: // foo == case SyntaxKind.EqualsEqualsEqualsToken: // foo === case SyntaxKind.ExclamationEqualsToken: // foo != diff --git a/src/compiler/scanner.ts b/src/compiler/scanner.ts index b19a1466328..4fb4713d75d 100644 --- a/src/compiler/scanner.ts +++ b/src/compiler/scanner.ts @@ -169,6 +169,7 @@ namespace ts { "&&": SyntaxKind.AmpersandAmpersandToken, "||": SyntaxKind.BarBarToken, "?": SyntaxKind.QuestionToken, + "??": SyntaxKind.QuestionQuestionToken, ":": SyntaxKind.ColonToken, "=": SyntaxKind.EqualsToken, "+=": SyntaxKind.PlusEqualsToken, @@ -1553,6 +1554,9 @@ namespace ts { pos++; return token = SyntaxKind.GreaterThanToken; case CharacterCodes.question: + if (text.charCodeAt(pos + 1) === CharacterCodes.question) { + return pos += 2, token = SyntaxKind.QuestionQuestionToken; + } pos++; return token = SyntaxKind.QuestionToken; case CharacterCodes.openBracket: diff --git a/src/compiler/transformers/esnext.ts b/src/compiler/transformers/esnext.ts index 5b4c12b4f23..d95078a807c 100644 --- a/src/compiler/transformers/esnext.ts +++ b/src/compiler/transformers/esnext.ts @@ -225,6 +225,34 @@ namespace ts { return visitEachChild(node, visitor, context); } + function createNotUndefinedCondition(node: Expression) { + return isIdentifier(node) && !isGeneratedIdentifier(node) + ? createStrictInequality(createTypeOf(node), createLiteral("undefined")) + : createStrictInequality(node, createVoidZero()); + } + + function createNotNullCondition(node: Expression) { + return createStrictInequality(node, createNull()); + } + + function transformCoalesceExpression(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; + } + expressions.push( + createConditional( + createLogicalAnd( + createNotUndefinedCondition(left), + createNotNullCondition(left)), + left, + visitNode(node.right, visitor, isExpression))); + return inlineExpressions(expressions); + } + /** * Visits a BinaryExpression that contains a destructuring assignment. * @@ -240,6 +268,9 @@ namespace ts { !noDestructuringValue ); } + else if (node.operatorToken.kind === SyntaxKind.QuestionQuestionToken) { + return transformCoalesceExpression(node); + } else if (node.operatorToken.kind === SyntaxKind.CommaToken) { return updateBinary( node, diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 578c2d23c4f..b70066f22ce 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -110,6 +110,7 @@ namespace ts { AmpersandAmpersandToken, BarBarToken, QuestionToken, + QuestionQuestionToken, ColonToken, AtToken, // Assignments @@ -1270,6 +1271,7 @@ namespace ts { export type LogicalOperator = SyntaxKind.AmpersandAmpersandToken | SyntaxKind.BarBarToken + | SyntaxKind.QuestionQuestionToken ; // see: https://tc39.github.io/ecma262/#prod-LogicalANDExpression diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index f0eb394adb7..52ca4c118ae 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -2225,6 +2225,7 @@ namespace ts { case SyntaxKind.AmpersandAmpersandToken: return 6; + case SyntaxKind.QuestionQuestionToken: case SyntaxKind.BarBarToken: return 5;