Skip to content

Primitive type guards are order independent #9036

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Jun 13, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 17 additions & 2 deletions src/compiler/binder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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((<TypeOfExpression>expr.left).expression) && expr.right.kind === SyntaxKind.StringLiteral) {
if (isTypeOfNarrowingBinaryExpression(expr)) {
return true;
}
return false;
Expand All @@ -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((<TypeOfExpression>typeOf).expression);
}

function createBranchLabel(): FlowLabel {
return {
flags: FlowFlags.BranchLabel,
Expand Down
27 changes: 15 additions & 12 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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);
Expand All @@ -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((<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;
Expand All @@ -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);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -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;
}
}
Original file line number Diff line number Diff line change
@@ -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))
}
}

Original file line number Diff line number Diff line change
@@ -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
}
}

Original file line number Diff line number Diff line change
@@ -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;
}
Original file line number Diff line number Diff line change
@@ -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))
}

Loading