Merge pull request #9036 from Microsoft/primitive-type-guards-are-order-independent

Primitive type guards are order independent
This commit is contained in:
Nathan Shively-Sanders 2016-06-13 10:52:06 -07:00 committed by GitHub
commit 8b093128b3
10 changed files with 423 additions and 14 deletions

View file

@ -610,10 +610,11 @@ namespace ts {
case SyntaxKind.ExclamationEqualsToken:
case SyntaxKind.EqualsEqualsEqualsToken:
case SyntaxKind.ExclamationEqualsEqualsToken:
if (isNarrowingExpression(expr.left) && (expr.right.kind === SyntaxKind.NullKeyword || expr.right.kind === SyntaxKind.Identifier)) {
if ((isNarrowingExpression(expr.left) && (expr.right.kind === SyntaxKind.NullKeyword || expr.right.kind === SyntaxKind.Identifier)) ||
(isNarrowingExpression(expr.right) && (expr.left.kind === SyntaxKind.NullKeyword || expr.left.kind === SyntaxKind.Identifier))) {
return true;
}
if (expr.left.kind === SyntaxKind.TypeOfExpression && isNarrowingExpression((<TypeOfExpression>expr.left).expression) && expr.right.kind === SyntaxKind.StringLiteral) {
if (isTypeOfNarrowingBinaryExpression(expr)) {
return true;
}
return false;
@ -625,6 +626,20 @@ namespace ts {
return false;
}
function isTypeOfNarrowingBinaryExpression(expr: BinaryExpression) {
let typeOf: Expression;
if (expr.left.kind === SyntaxKind.StringLiteral) {
typeOf = expr.right;
}
else if (expr.right.kind === SyntaxKind.StringLiteral) {
typeOf = expr.left;
}
else {
typeOf = undefined;
}
return typeOf && typeOf.kind === SyntaxKind.TypeOfExpression && isNarrowingExpression((<TypeOfExpression>typeOf).expression);
}
function createBranchLabel(): FlowLabel {
return {
flags: FlowFlags.BranchLabel,

View file

@ -7895,10 +7895,11 @@ namespace ts {
case SyntaxKind.ExclamationEqualsToken:
case SyntaxKind.EqualsEqualsEqualsToken:
case SyntaxKind.ExclamationEqualsEqualsToken:
if (isNullOrUndefinedLiteral(expr.right)) {
if (isNullOrUndefinedLiteral(expr.left) || isNullOrUndefinedLiteral(expr.right)) {
return narrowTypeByNullCheck(type, expr, assumeTrue);
}
if (expr.left.kind === SyntaxKind.TypeOfExpression && expr.right.kind === SyntaxKind.StringLiteral) {
if (expr.left.kind === SyntaxKind.TypeOfExpression && expr.right.kind === SyntaxKind.StringLiteral ||
expr.left.kind === SyntaxKind.StringLiteral && expr.right.kind === SyntaxKind.TypeOfExpression) {
return narrowTypeByTypeof(type, expr, assumeTrue);
}
break;
@ -7911,18 +7912,20 @@ namespace ts {
}
function narrowTypeByNullCheck(type: Type, expr: BinaryExpression, assumeTrue: boolean): Type {
// We have '==', '!=', '===', or '!==' operator with 'null' or 'undefined' on the right
// We have '==', '!=', '===', or '!==' operator with 'null' or 'undefined' on one side
const operator = expr.operatorToken.kind;
const nullLike = isNullOrUndefinedLiteral(expr.left) ? expr.left : expr.right;
const narrowed = isNullOrUndefinedLiteral(expr.left) ? expr.right : expr.left;
if (operator === SyntaxKind.ExclamationEqualsToken || operator === SyntaxKind.ExclamationEqualsEqualsToken) {
assumeTrue = !assumeTrue;
}
if (!strictNullChecks || !isMatchingReference(reference, getReferenceFromExpression(expr.left))) {
if (!strictNullChecks || !isMatchingReference(reference, getReferenceFromExpression(narrowed))) {
return type;
}
const doubleEquals = operator === SyntaxKind.EqualsEqualsToken || operator === SyntaxKind.ExclamationEqualsToken;
const facts = doubleEquals ?
assumeTrue ? TypeFacts.EQUndefinedOrNull : TypeFacts.NEUndefinedOrNull :
expr.right.kind === SyntaxKind.NullKeyword ?
nullLike.kind === SyntaxKind.NullKeyword ?
assumeTrue ? TypeFacts.EQNull : TypeFacts.NENull :
assumeTrue ? TypeFacts.EQUndefined : TypeFacts.NEUndefined;
return getTypeWithFacts(type, facts);
@ -7931,12 +7934,12 @@ namespace ts {
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 = getReferenceFromExpression((<TypeOfExpression>expr.left).expression);
const right = <LiteralExpression>expr.right;
if (!isMatchingReference(reference, left)) {
const narrowed = getReferenceFromExpression((<TypeOfExpression>(expr.left.kind === SyntaxKind.TypeOfExpression ? expr.left : expr.right)).expression);
const literal = <LiteralExpression>(expr.right.kind === SyntaxKind.StringLiteral ? expr.right : expr.left);
if (!isMatchingReference(reference, narrowed)) {
// For a reference of the form 'x.y', a 'typeof x === ...' type guard resets the
// narrowed type of 'y' to its declared type.
if (containsMatchingReference(reference, left)) {
if (containsMatchingReference(reference, narrowed)) {
return declaredType;
}
return type;
@ -7949,14 +7952,14 @@ namespace ts {
// We narrow a non-union type to an exact primitive type if the non-union type
// is a supertype of that primtive type. For example, type 'any' can be narrowed
// to one of the primitive types.
const targetType = getProperty(typeofTypesByName, right.text);
const targetType = getProperty(typeofTypesByName, literal.text);
if (targetType && isTypeSubtypeOf(targetType, type)) {
return targetType;
}
}
const facts = assumeTrue ?
getProperty(typeofEQFacts, right.text) || TypeFacts.TypeofEQHostObject :
getProperty(typeofNEFacts, right.text) || TypeFacts.TypeofNEHostObject;
getProperty(typeofEQFacts, literal.text) || TypeFacts.TypeofEQHostObject :
getProperty(typeofNEFacts, literal.text) || TypeFacts.TypeofNEHostObject;
return getTypeWithFacts(type, facts);
}

View file

@ -0,0 +1,30 @@
//// [nullOrUndefinedTypeGuardIsOrderIndependent.ts]
function test(strOrNull: string | null, strOrUndefined: string | undefined) {
var str: string = "original";
var nil: null;
if (null === strOrNull) {
nil = strOrNull;
}
else {
str = strOrNull;
}
if (undefined !== strOrUndefined) {
str = strOrUndefined;
}
}
//// [nullOrUndefinedTypeGuardIsOrderIndependent.js]
function test(strOrNull, strOrUndefined) {
var str = "original";
var nil;
if (null === strOrNull) {
nil = strOrNull;
}
else {
str = strOrNull;
}
if (undefined !== strOrUndefined) {
str = strOrUndefined;
}
}

View file

@ -0,0 +1,34 @@
=== tests/cases/conformance/expressions/typeGuards/nullOrUndefinedTypeGuardIsOrderIndependent.ts ===
function test(strOrNull: string | null, strOrUndefined: string | undefined) {
>test : Symbol(test, Decl(nullOrUndefinedTypeGuardIsOrderIndependent.ts, 0, 0))
>strOrNull : Symbol(strOrNull, Decl(nullOrUndefinedTypeGuardIsOrderIndependent.ts, 0, 14))
>strOrUndefined : Symbol(strOrUndefined, Decl(nullOrUndefinedTypeGuardIsOrderIndependent.ts, 0, 39))
var str: string = "original";
>str : Symbol(str, Decl(nullOrUndefinedTypeGuardIsOrderIndependent.ts, 1, 7))
var nil: null;
>nil : Symbol(nil, Decl(nullOrUndefinedTypeGuardIsOrderIndependent.ts, 2, 7))
if (null === strOrNull) {
>strOrNull : Symbol(strOrNull, Decl(nullOrUndefinedTypeGuardIsOrderIndependent.ts, 0, 14))
nil = strOrNull;
>nil : Symbol(nil, Decl(nullOrUndefinedTypeGuardIsOrderIndependent.ts, 2, 7))
>strOrNull : Symbol(strOrNull, Decl(nullOrUndefinedTypeGuardIsOrderIndependent.ts, 0, 14))
}
else {
str = strOrNull;
>str : Symbol(str, Decl(nullOrUndefinedTypeGuardIsOrderIndependent.ts, 1, 7))
>strOrNull : Symbol(strOrNull, Decl(nullOrUndefinedTypeGuardIsOrderIndependent.ts, 0, 14))
}
if (undefined !== strOrUndefined) {
>undefined : Symbol(undefined)
>strOrUndefined : Symbol(strOrUndefined, Decl(nullOrUndefinedTypeGuardIsOrderIndependent.ts, 0, 39))
str = strOrUndefined;
>str : Symbol(str, Decl(nullOrUndefinedTypeGuardIsOrderIndependent.ts, 1, 7))
>strOrUndefined : Symbol(strOrUndefined, Decl(nullOrUndefinedTypeGuardIsOrderIndependent.ts, 0, 39))
}
}

View file

@ -0,0 +1,43 @@
=== tests/cases/conformance/expressions/typeGuards/nullOrUndefinedTypeGuardIsOrderIndependent.ts ===
function test(strOrNull: string | null, strOrUndefined: string | undefined) {
>test : (strOrNull: string | null, strOrUndefined: string | undefined) => void
>strOrNull : string | null
>null : null
>strOrUndefined : string | undefined
var str: string = "original";
>str : string
>"original" : string
var nil: null;
>nil : null
>null : null
if (null === strOrNull) {
>null === strOrNull : boolean
>null : null
>strOrNull : string | null
nil = strOrNull;
>nil = strOrNull : null
>nil : null
>strOrNull : null
}
else {
str = strOrNull;
>str = strOrNull : string
>str : string
>strOrNull : string
}
if (undefined !== strOrUndefined) {
>undefined !== strOrUndefined : boolean
>undefined : undefined
>strOrUndefined : string | undefined
str = strOrUndefined;
>str = strOrUndefined : string
>str : string
>strOrUndefined : string
}
}

View file

@ -0,0 +1,69 @@
//// [typeGuardOfFormTypeOfIsOrderIndependent.ts]
var strOrNum: string | number;
var strOrBool: string | boolean;
var strOrFunc: string | (() => void);
var numOrBool: number | boolean
var str: string;
var num: number;
var bool: boolean;
var func: () => void;
if ("string" === typeof strOrNum) {
str = strOrNum;
}
else {
num = strOrNum;
}
if ("function" === typeof strOrFunc) {
func = strOrFunc;
}
else {
str = strOrFunc;
}
if ("number" === typeof numOrBool) {
num = numOrBool;
}
else {
bool = numOrBool;
}
if ("boolean" === typeof strOrBool) {
bool = strOrBool;
}
else {
str = strOrBool;
}
//// [typeGuardOfFormTypeOfIsOrderIndependent.js]
var strOrNum;
var strOrBool;
var strOrFunc;
var numOrBool;
var str;
var num;
var bool;
var func;
if ("string" === typeof strOrNum) {
str = strOrNum;
}
else {
num = strOrNum;
}
if ("function" === typeof strOrFunc) {
func = strOrFunc;
}
else {
str = strOrFunc;
}
if ("number" === typeof numOrBool) {
num = numOrBool;
}
else {
bool = numOrBool;
}
if ("boolean" === typeof strOrBool) {
bool = strOrBool;
}
else {
str = strOrBool;
}

View file

@ -0,0 +1,74 @@
=== tests/cases/conformance/expressions/typeGuards/typeGuardOfFormTypeOfIsOrderIndependent.ts ===
var strOrNum: string | number;
>strOrNum : Symbol(strOrNum, Decl(typeGuardOfFormTypeOfIsOrderIndependent.ts, 0, 3))
var strOrBool: string | boolean;
>strOrBool : Symbol(strOrBool, Decl(typeGuardOfFormTypeOfIsOrderIndependent.ts, 1, 3))
var strOrFunc: string | (() => void);
>strOrFunc : Symbol(strOrFunc, Decl(typeGuardOfFormTypeOfIsOrderIndependent.ts, 2, 3))
var numOrBool: number | boolean
>numOrBool : Symbol(numOrBool, Decl(typeGuardOfFormTypeOfIsOrderIndependent.ts, 3, 3))
var str: string;
>str : Symbol(str, Decl(typeGuardOfFormTypeOfIsOrderIndependent.ts, 4, 3))
var num: number;
>num : Symbol(num, Decl(typeGuardOfFormTypeOfIsOrderIndependent.ts, 5, 3))
var bool: boolean;
>bool : Symbol(bool, Decl(typeGuardOfFormTypeOfIsOrderIndependent.ts, 6, 3))
var func: () => void;
>func : Symbol(func, Decl(typeGuardOfFormTypeOfIsOrderIndependent.ts, 7, 3))
if ("string" === typeof strOrNum) {
>strOrNum : Symbol(strOrNum, Decl(typeGuardOfFormTypeOfIsOrderIndependent.ts, 0, 3))
str = strOrNum;
>str : Symbol(str, Decl(typeGuardOfFormTypeOfIsOrderIndependent.ts, 4, 3))
>strOrNum : Symbol(strOrNum, Decl(typeGuardOfFormTypeOfIsOrderIndependent.ts, 0, 3))
}
else {
num = strOrNum;
>num : Symbol(num, Decl(typeGuardOfFormTypeOfIsOrderIndependent.ts, 5, 3))
>strOrNum : Symbol(strOrNum, Decl(typeGuardOfFormTypeOfIsOrderIndependent.ts, 0, 3))
}
if ("function" === typeof strOrFunc) {
>strOrFunc : Symbol(strOrFunc, Decl(typeGuardOfFormTypeOfIsOrderIndependent.ts, 2, 3))
func = strOrFunc;
>func : Symbol(func, Decl(typeGuardOfFormTypeOfIsOrderIndependent.ts, 7, 3))
>strOrFunc : Symbol(strOrFunc, Decl(typeGuardOfFormTypeOfIsOrderIndependent.ts, 2, 3))
}
else {
str = strOrFunc;
>str : Symbol(str, Decl(typeGuardOfFormTypeOfIsOrderIndependent.ts, 4, 3))
>strOrFunc : Symbol(strOrFunc, Decl(typeGuardOfFormTypeOfIsOrderIndependent.ts, 2, 3))
}
if ("number" === typeof numOrBool) {
>numOrBool : Symbol(numOrBool, Decl(typeGuardOfFormTypeOfIsOrderIndependent.ts, 3, 3))
num = numOrBool;
>num : Symbol(num, Decl(typeGuardOfFormTypeOfIsOrderIndependent.ts, 5, 3))
>numOrBool : Symbol(numOrBool, Decl(typeGuardOfFormTypeOfIsOrderIndependent.ts, 3, 3))
}
else {
bool = numOrBool;
>bool : Symbol(bool, Decl(typeGuardOfFormTypeOfIsOrderIndependent.ts, 6, 3))
>numOrBool : Symbol(numOrBool, Decl(typeGuardOfFormTypeOfIsOrderIndependent.ts, 3, 3))
}
if ("boolean" === typeof strOrBool) {
>strOrBool : Symbol(strOrBool, Decl(typeGuardOfFormTypeOfIsOrderIndependent.ts, 1, 3))
bool = strOrBool;
>bool : Symbol(bool, Decl(typeGuardOfFormTypeOfIsOrderIndependent.ts, 6, 3))
>strOrBool : Symbol(strOrBool, Decl(typeGuardOfFormTypeOfIsOrderIndependent.ts, 1, 3))
}
else {
str = strOrBool;
>str : Symbol(str, Decl(typeGuardOfFormTypeOfIsOrderIndependent.ts, 4, 3))
>strOrBool : Symbol(strOrBool, Decl(typeGuardOfFormTypeOfIsOrderIndependent.ts, 1, 3))
}

View file

@ -0,0 +1,94 @@
=== tests/cases/conformance/expressions/typeGuards/typeGuardOfFormTypeOfIsOrderIndependent.ts ===
var strOrNum: string | number;
>strOrNum : string | number
var strOrBool: string | boolean;
>strOrBool : string | boolean
var strOrFunc: string | (() => void);
>strOrFunc : string | (() => void)
var numOrBool: number | boolean
>numOrBool : number | boolean
var str: string;
>str : string
var num: number;
>num : number
var bool: boolean;
>bool : boolean
var func: () => void;
>func : () => void
if ("string" === typeof strOrNum) {
>"string" === typeof strOrNum : boolean
>"string" : string
>typeof strOrNum : string
>strOrNum : string | number
str = strOrNum;
>str = strOrNum : string
>str : string
>strOrNum : string
}
else {
num = strOrNum;
>num = strOrNum : number
>num : number
>strOrNum : number
}
if ("function" === typeof strOrFunc) {
>"function" === typeof strOrFunc : boolean
>"function" : string
>typeof strOrFunc : string
>strOrFunc : string | (() => void)
func = strOrFunc;
>func = strOrFunc : () => void
>func : () => void
>strOrFunc : () => void
}
else {
str = strOrFunc;
>str = strOrFunc : string
>str : string
>strOrFunc : string
}
if ("number" === typeof numOrBool) {
>"number" === typeof numOrBool : boolean
>"number" : string
>typeof numOrBool : string
>numOrBool : number | boolean
num = numOrBool;
>num = numOrBool : number
>num : number
>numOrBool : number
}
else {
bool = numOrBool;
>bool = numOrBool : boolean
>bool : boolean
>numOrBool : boolean
}
if ("boolean" === typeof strOrBool) {
>"boolean" === typeof strOrBool : boolean
>"boolean" : string
>typeof strOrBool : string
>strOrBool : string | boolean
bool = strOrBool;
>bool = strOrBool : boolean
>bool : boolean
>strOrBool : boolean
}
else {
str = strOrBool;
>str = strOrBool : string
>str : string
>strOrBool : string
}

View file

@ -0,0 +1,14 @@
// @strictNullChecks: true
function test(strOrNull: string | null, strOrUndefined: string | undefined) {
var str: string = "original";
var nil: null;
if (null === strOrNull) {
nil = strOrNull;
}
else {
str = strOrNull;
}
if (undefined !== strOrUndefined) {
str = strOrUndefined;
}
}

View file

@ -0,0 +1,33 @@
var strOrNum: string | number;
var strOrBool: string | boolean;
var strOrFunc: string | (() => void);
var numOrBool: number | boolean
var str: string;
var num: number;
var bool: boolean;
var func: () => void;
if ("string" === typeof strOrNum) {
str = strOrNum;
}
else {
num = strOrNum;
}
if ("function" === typeof strOrFunc) {
func = strOrFunc;
}
else {
str = strOrFunc;
}
if ("number" === typeof numOrBool) {
num = numOrBool;
}
else {
bool = numOrBool;
}
if ("boolean" === typeof strOrBool) {
bool = strOrBool;
}
else {
str = strOrBool;
}