diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index db956cca647bc..cd9246c2d21df 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -609,10 +609,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((expr.left).expression) && expr.right.kind === SyntaxKind.StringLiteral) { + if (isTypeOfNarrowingBinaryExpression(expr)) { return true; } return false; @@ -624,6 +625,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((typeOf).expression); + } + function createBranchLabel(): FlowLabel { return { flags: FlowFlags.BranchLabel, diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index a60866b90f012..c4a0a85175ecf 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -7884,10 +7884,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; @@ -7900,18 +7901,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); @@ -7920,12 +7923,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((expr.left).expression); - const right = expr.right; - if (!isMatchingReference(reference, left)) { + const narrowed = getReferenceFromExpression(((expr.left.kind === SyntaxKind.TypeOfExpression ? expr.left : expr.right)).expression); + const literal = (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; @@ -7938,14 +7941,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); } diff --git a/tests/baselines/reference/nullOrUndefinedTypeGuardIsOrderIndependent.js b/tests/baselines/reference/nullOrUndefinedTypeGuardIsOrderIndependent.js new file mode 100644 index 0000000000000..f62bd2a3f118a --- /dev/null +++ b/tests/baselines/reference/nullOrUndefinedTypeGuardIsOrderIndependent.js @@ -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; + } +} diff --git a/tests/baselines/reference/nullOrUndefinedTypeGuardIsOrderIndependent.symbols b/tests/baselines/reference/nullOrUndefinedTypeGuardIsOrderIndependent.symbols new file mode 100644 index 0000000000000..bb1d18432893b --- /dev/null +++ b/tests/baselines/reference/nullOrUndefinedTypeGuardIsOrderIndependent.symbols @@ -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)) + } +} + diff --git a/tests/baselines/reference/nullOrUndefinedTypeGuardIsOrderIndependent.types b/tests/baselines/reference/nullOrUndefinedTypeGuardIsOrderIndependent.types new file mode 100644 index 0000000000000..f599366e66e62 --- /dev/null +++ b/tests/baselines/reference/nullOrUndefinedTypeGuardIsOrderIndependent.types @@ -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 + } +} + diff --git a/tests/baselines/reference/typeGuardOfFormTypeOfIsOrderIndependent.js b/tests/baselines/reference/typeGuardOfFormTypeOfIsOrderIndependent.js new file mode 100644 index 0000000000000..7d9a1651f08d7 --- /dev/null +++ b/tests/baselines/reference/typeGuardOfFormTypeOfIsOrderIndependent.js @@ -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; +} diff --git a/tests/baselines/reference/typeGuardOfFormTypeOfIsOrderIndependent.symbols b/tests/baselines/reference/typeGuardOfFormTypeOfIsOrderIndependent.symbols new file mode 100644 index 0000000000000..f33908631a408 --- /dev/null +++ b/tests/baselines/reference/typeGuardOfFormTypeOfIsOrderIndependent.symbols @@ -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)) +} + diff --git a/tests/baselines/reference/typeGuardOfFormTypeOfIsOrderIndependent.types b/tests/baselines/reference/typeGuardOfFormTypeOfIsOrderIndependent.types new file mode 100644 index 0000000000000..2d91abb9613ac --- /dev/null +++ b/tests/baselines/reference/typeGuardOfFormTypeOfIsOrderIndependent.types @@ -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 +} + diff --git a/tests/cases/conformance/expressions/typeGuards/nullOrUndefinedTypeGuardIsOrderIndependent.ts b/tests/cases/conformance/expressions/typeGuards/nullOrUndefinedTypeGuardIsOrderIndependent.ts new file mode 100644 index 0000000000000..2da52406609ec --- /dev/null +++ b/tests/cases/conformance/expressions/typeGuards/nullOrUndefinedTypeGuardIsOrderIndependent.ts @@ -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; + } +} diff --git a/tests/cases/conformance/expressions/typeGuards/typeGuardOfFormTypeOfIsOrderIndependent.ts b/tests/cases/conformance/expressions/typeGuards/typeGuardOfFormTypeOfIsOrderIndependent.ts new file mode 100644 index 0000000000000..b7a5caf41daf5 --- /dev/null +++ b/tests/cases/conformance/expressions/typeGuards/typeGuardOfFormTypeOfIsOrderIndependent.ts @@ -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; +}