From 50ea0bfc711ff68bee03f954eb86e439ff138d5c Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Fri, 19 Feb 2016 09:32:56 -0800 Subject: [PATCH] Support x == null and x != null in non-null guards. Also, allow == and != in type guards. --- src/compiler/checker.ts | 60 ++++++++++++++++++++++++++++------------- 1 file changed, 42 insertions(+), 18 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 7000ace8aff..874dcb40d5a 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -7065,17 +7065,54 @@ namespace ts { return strictNullChecks && assumeTrue && getResolvedSymbol(expr) === symbol ? getNonNullableType(type) : type; } - function narrowTypeByEquality(type: Type, expr: BinaryExpression, assumeTrue: boolean): Type { - // Check that we have 'typeof ' on the left and string literal on the right - if (expr.left.kind !== SyntaxKind.TypeOfExpression || expr.right.kind !== SyntaxKind.StringLiteral) { + function narrowTypeByBinaryExpression(type: Type, expr: BinaryExpression, assumeTrue: boolean): Type { + switch (expr.operatorToken.kind) { + case SyntaxKind.EqualsEqualsToken: + case SyntaxKind.ExclamationEqualsToken: + if (expr.right.kind === SyntaxKind.NullKeyword) { + return narrowTypeByNullCheck(type, expr, assumeTrue); + } + // Fall through + case SyntaxKind.EqualsEqualsEqualsToken: + case SyntaxKind.ExclamationEqualsEqualsToken: + if (expr.left.kind === SyntaxKind.TypeOfExpression && expr.right.kind === SyntaxKind.StringLiteral) { + return narrowTypeByTypeof(type, expr, assumeTrue); + } + break; + case SyntaxKind.AmpersandAmpersandToken: + return narrowTypeByAnd(type, expr, assumeTrue); + case SyntaxKind.BarBarToken: + return narrowTypeByOr(type, expr, assumeTrue); + case SyntaxKind.InstanceOfKeyword: + return narrowTypeByInstanceof(type, expr, assumeTrue); + } + return type; + } + + function narrowTypeByNullCheck(type: Type, expr: BinaryExpression, assumeTrue: boolean): Type { + // We have '==' or '!=' operator with 'null' on the right + if (expr.operatorToken.kind === SyntaxKind.ExclamationEqualsToken) { + assumeTrue = !assumeTrue; + } + if (!strictNullChecks || assumeTrue) { return type; } + if (expr.left.kind !== SyntaxKind.Identifier || getResolvedSymbol(expr.left) !== symbol) { + return type; + } + return getNonNullableType(type); + } + + function narrowTypeByTypeof(type: Type, expr: BinaryExpression, assumeTrue: boolean): Type { + // We have '==', '!=', '====', or !==' operator with 'typeof xxx' on the left + // and string literal on the right const left = expr.left; const right = expr.right; if (left.expression.kind !== SyntaxKind.Identifier || getResolvedSymbol(left.expression) !== symbol) { return type; } - if (expr.operatorToken.kind === SyntaxKind.ExclamationEqualsEqualsToken) { + if (expr.operatorToken.kind === SyntaxKind.ExclamationEqualsToken || + expr.operatorToken.kind === SyntaxKind.ExclamationEqualsEqualsToken) { assumeTrue = !assumeTrue; } const typeInfo = primitiveTypeInfo[right.text]; @@ -7260,20 +7297,7 @@ namespace ts { case SyntaxKind.ParenthesizedExpression: return narrowType(type, (expr).expression, assumeTrue); case SyntaxKind.BinaryExpression: - const operator = (expr).operatorToken.kind; - if (operator === SyntaxKind.EqualsEqualsEqualsToken || operator === SyntaxKind.ExclamationEqualsEqualsToken) { - return narrowTypeByEquality(type, expr, assumeTrue); - } - else if (operator === SyntaxKind.AmpersandAmpersandToken) { - return narrowTypeByAnd(type, expr, assumeTrue); - } - else if (operator === SyntaxKind.BarBarToken) { - return narrowTypeByOr(type, expr, assumeTrue); - } - else if (operator === SyntaxKind.InstanceOfKeyword) { - return narrowTypeByInstanceof(type, expr, assumeTrue); - } - break; + return narrowTypeByBinaryExpression(type, expr, assumeTrue); case SyntaxKind.PrefixUnaryExpression: if ((expr).operator === SyntaxKind.ExclamationToken) { return narrowType(type, (expr).operand, !assumeTrue);