TypeGuards narrow types in if statement works per spec:

The type of a variable or parameter is narrowed in the following situations:
•	In the true branch statement of an ‘if’ statement, the type of a variable or parameter is narrowed by any type guard in the ‘if’ condition when true, provided the if statement contains no assignments to the variable or parameter.
•	In the false branch statement of an ‘if’ statement, the type of a variable or parameter is narrowed by any type guard in the ‘if’ condition when false, provided the if statement contains no assignments to the variable or parameter.
This commit is contained in:
Sheetal Nandi 2014-11-05 14:12:19 -08:00
parent 2145b2f445
commit e79bec5cbf
3 changed files with 806 additions and 0 deletions

View File

@ -0,0 +1,287 @@
//// [typeGuardsInIfStatement.ts]
// In the true branch statement of an <20>if<69> statement,
// the type of a variable or parameter is narrowed by any type guard in the <20>if<69> condition when true,
// provided the true branch statement contains no assignments to the variable or parameter.
// In the false branch statement of an <20>if<69> statement,
// the type of a variable or parameter is narrowed by any type guard in the <20>if<69> condition when false,
// provided the false branch statement contains no assignments to the variable or parameter
function foo(x: number | string) {
if (typeof x === "string") {
return x.length; // string
}
else {
return x++; // number
}
}
function foo2(x: number | string) {
// x is assigned in the if true branch, the type is not narrowed
if (typeof x === "string") {
x = 10;
return x; // string | number
}
else {
return x; // string | number
}
}
function foo3(x: number | string) {
// x is assigned in the if true branch, the type is not narrowed
if (typeof x === "string") {
x = "Hello"; // even though assigned using same type as narrowed expression
return x; // string | number
}
else {
return x; // string | number
}
}
function foo4(x: number | string) {
// false branch updates the variable - so here it is not number
if (typeof x === "string") {
return x; // string | number
}
else {
x = 10; // even though assigned number - this should result in x to be string | number
return x; // string | number
}
}
function foo5(x: number | string) {
// false branch updates the variable - so here it is not number
if (typeof x === "string") {
return x; // string | number
}
else {
x = "hello";
return x; // string | number
}
}
function foo6(x: number | string) {
// Modify in both branches
if (typeof x === "string") {
x = 10;
return x; // string | number
}
else {
x = "hello";
return x; // string | number
}
}
function foo7(x: number | string | boolean) {
if (typeof x === "string") {
return x === "hello"; // string
}
else if (typeof x === "boolean") {
return x; // boolean
}
else {
return x == 10; // number
}
}
function foo8(x: number | string | boolean) {
if (typeof x === "string") {
return x === "hello"; // string
}
else {
var b: number | boolean = x; // number | boolean
if (typeof x === "boolean") {
return x; // boolean
}
else {
return x == 10; // number
}
}
}
function foo9(x: number | string) {
var y = 10;
if (typeof x === "string") {
// usage of x or assignment to separate variable shouldn't cause narrowing of type to stop
y = x.length;
return x === "hello"; // string
}
else {
return x == 10; // number
}
}
function foo10(x: number | string | boolean) {
// Mixing typeguard narrowing in if statement with conditional expression typeguard
if (typeof x === "string") {
return x === "hello"; // string
}
else {
var y: boolean | string;
var b = x; // number | boolean
return typeof x === "number"
? x === 10 // number
: x; // x should be boolean
}
}
function foo11(x: number | string | boolean) {
// Mixing typeguard narrowing in if statement with conditional expression typeguard
// Assigning value to x deep inside another guard stops narrowing of type too
if (typeof x === "string") {
return x; // string | number | boolean - x changed in else branch
}
else {
var y: number| boolean | string;
var b = x; // number | boolean | string - because below we are changing value of x in if statement
return typeof x === "number"
? (
// change value of x
x = 10 && x.toString() // number | boolean | string
)
: (
// do not change value
y = x && x.toString() // number | boolean | string
);
}
}
function foo12(x: number | string | boolean) {
// Mixing typeguard narrowing in if statement with conditional expression typeguard
// Assigning value to x in outer guard shouldn't stop narrowing in the inner expression
if (typeof x === "string") {
return x.toString(); // string | number | boolean - x changed in else branch
}
else {
x = 10;
var b = x; // number | boolean | string
return typeof x === "number"
? x.toString() // number
: x.toString(); // boolean | string
}
}
//// [typeGuardsInIfStatement.js]
// In the true branch statement of an <20>if<69> statement,
// the type of a variable or parameter is narrowed by any type guard in the <20>if<69> condition when true,
// provided the true branch statement contains no assignments to the variable or parameter.
// In the false branch statement of an <20>if<69> statement,
// the type of a variable or parameter is narrowed by any type guard in the <20>if<69> condition when false,
// provided the false branch statement contains no assignments to the variable or parameter
function foo(x) {
if (typeof x === "string") {
return x.length; // string
}
else {
return x++; // number
}
}
function foo2(x) {
// x is assigned in the if true branch, the type is not narrowed
if (typeof x === "string") {
x = 10;
return x; // string | number
}
else {
return x; // string | number
}
}
function foo3(x) {
// x is assigned in the if true branch, the type is not narrowed
if (typeof x === "string") {
x = "Hello"; // even though assigned using same type as narrowed expression
return x; // string | number
}
else {
return x; // string | number
}
}
function foo4(x) {
// false branch updates the variable - so here it is not number
if (typeof x === "string") {
return x; // string | number
}
else {
x = 10; // even though assigned number - this should result in x to be string | number
return x; // string | number
}
}
function foo5(x) {
// false branch updates the variable - so here it is not number
if (typeof x === "string") {
return x; // string | number
}
else {
x = "hello";
return x; // string | number
}
}
function foo6(x) {
// Modify in both branches
if (typeof x === "string") {
x = 10;
return x; // string | number
}
else {
x = "hello";
return x; // string | number
}
}
function foo7(x) {
if (typeof x === "string") {
return x === "hello"; // string
}
else if (typeof x === "boolean") {
return x; // boolean
}
else {
return x == 10; // number
}
}
function foo8(x) {
if (typeof x === "string") {
return x === "hello"; // string
}
else {
var b = x; // number | boolean
if (typeof x === "boolean") {
return x; // boolean
}
else {
return x == 10; // number
}
}
}
function foo9(x) {
var y = 10;
if (typeof x === "string") {
// usage of x or assignment to separate variable shouldn't cause narrowing of type to stop
y = x.length;
return x === "hello"; // string
}
else {
return x == 10; // number
}
}
function foo10(x) {
// Mixing typeguard narrowing in if statement with conditional expression typeguard
if (typeof x === "string") {
return x === "hello"; // string
}
else {
var y;
var b = x; // number | boolean
return typeof x === "number" ? x === 10 : x; // x should be boolean
}
}
function foo11(x) {
// Mixing typeguard narrowing in if statement with conditional expression typeguard
// Assigning value to x deep inside another guard stops narrowing of type too
if (typeof x === "string") {
return x; // string | number | boolean - x changed in else branch
}
else {
var y;
var b = x; // number | boolean | string - because below we are changing value of x in if statement
return typeof x === "number" ? (x = 10 && x.toString()) : (y = x && x.toString());
}
}
function foo12(x) {
// Mixing typeguard narrowing in if statement with conditional expression typeguard
// Assigning value to x in outer guard shouldn't stop narrowing in the inner expression
if (typeof x === "string") {
return x.toString(); // string | number | boolean - x changed in else branch
}
else {
x = 10;
var b = x; // number | boolean | string
return typeof x === "number" ? x.toString() : x.toString(); // boolean | string
}
}

View File

@ -0,0 +1,371 @@
=== tests/cases/conformance/expressions/typeGuards/typeGuardsInIfStatement.ts ===
// In the true branch statement of an <20>if<69> statement,
// the type of a variable or parameter is narrowed by any type guard in the <20>if<69> condition when true,
// provided the true branch statement contains no assignments to the variable or parameter.
// In the false branch statement of an <20>if<69> statement,
// the type of a variable or parameter is narrowed by any type guard in the <20>if<69> condition when false,
// provided the false branch statement contains no assignments to the variable or parameter
function foo(x: number | string) {
>foo : (x: string | number) => number
>x : string | number
if (typeof x === "string") {
>typeof x === "string" : boolean
>typeof x : string
>x : string | number
return x.length; // string
>x.length : number
>x : string
>length : number
}
else {
return x++; // number
>x++ : number
>x : number
}
}
function foo2(x: number | string) {
>foo2 : (x: string | number) => string | number
>x : string | number
// x is assigned in the if true branch, the type is not narrowed
if (typeof x === "string") {
>typeof x === "string" : boolean
>typeof x : string
>x : string | number
x = 10;
>x = 10 : number
>x : string | number
return x; // string | number
>x : string | number
}
else {
return x; // string | number
>x : string | number
}
}
function foo3(x: number | string) {
>foo3 : (x: string | number) => string | number
>x : string | number
// x is assigned in the if true branch, the type is not narrowed
if (typeof x === "string") {
>typeof x === "string" : boolean
>typeof x : string
>x : string | number
x = "Hello"; // even though assigned using same type as narrowed expression
>x = "Hello" : string
>x : string | number
return x; // string | number
>x : string | number
}
else {
return x; // string | number
>x : string | number
}
}
function foo4(x: number | string) {
>foo4 : (x: string | number) => string | number
>x : string | number
// false branch updates the variable - so here it is not number
if (typeof x === "string") {
>typeof x === "string" : boolean
>typeof x : string
>x : string | number
return x; // string | number
>x : string | number
}
else {
x = 10; // even though assigned number - this should result in x to be string | number
>x = 10 : number
>x : string | number
return x; // string | number
>x : string | number
}
}
function foo5(x: number | string) {
>foo5 : (x: string | number) => string | number
>x : string | number
// false branch updates the variable - so here it is not number
if (typeof x === "string") {
>typeof x === "string" : boolean
>typeof x : string
>x : string | number
return x; // string | number
>x : string | number
}
else {
x = "hello";
>x = "hello" : string
>x : string | number
return x; // string | number
>x : string | number
}
}
function foo6(x: number | string) {
>foo6 : (x: string | number) => string | number
>x : string | number
// Modify in both branches
if (typeof x === "string") {
>typeof x === "string" : boolean
>typeof x : string
>x : string | number
x = 10;
>x = 10 : number
>x : string | number
return x; // string | number
>x : string | number
}
else {
x = "hello";
>x = "hello" : string
>x : string | number
return x; // string | number
>x : string | number
}
}
function foo7(x: number | string | boolean) {
>foo7 : (x: string | number | boolean) => boolean
>x : string | number | boolean
if (typeof x === "string") {
>typeof x === "string" : boolean
>typeof x : string
>x : string | number | boolean
return x === "hello"; // string
>x === "hello" : boolean
>x : string
}
else if (typeof x === "boolean") {
>typeof x === "boolean" : boolean
>typeof x : string
>x : number | boolean
return x; // boolean
>x : boolean
}
else {
return x == 10; // number
>x == 10 : boolean
>x : number
}
}
function foo8(x: number | string | boolean) {
>foo8 : (x: string | number | boolean) => boolean
>x : string | number | boolean
if (typeof x === "string") {
>typeof x === "string" : boolean
>typeof x : string
>x : string | number | boolean
return x === "hello"; // string
>x === "hello" : boolean
>x : string
}
else {
var b: number | boolean = x; // number | boolean
>b : number | boolean
>x : number | boolean
if (typeof x === "boolean") {
>typeof x === "boolean" : boolean
>typeof x : string
>x : number | boolean
return x; // boolean
>x : boolean
}
else {
return x == 10; // number
>x == 10 : boolean
>x : number
}
}
}
function foo9(x: number | string) {
>foo9 : (x: string | number) => boolean
>x : string | number
var y = 10;
>y : number
if (typeof x === "string") {
>typeof x === "string" : boolean
>typeof x : string
>x : string | number
// usage of x or assignment to separate variable shouldn't cause narrowing of type to stop
y = x.length;
>y = x.length : number
>y : number
>x.length : number
>x : string
>length : number
return x === "hello"; // string
>x === "hello" : boolean
>x : string
}
else {
return x == 10; // number
>x == 10 : boolean
>x : number
}
}
function foo10(x: number | string | boolean) {
>foo10 : (x: string | number | boolean) => boolean
>x : string | number | boolean
// Mixing typeguard narrowing in if statement with conditional expression typeguard
if (typeof x === "string") {
>typeof x === "string" : boolean
>typeof x : string
>x : string | number | boolean
return x === "hello"; // string
>x === "hello" : boolean
>x : string
}
else {
var y: boolean | string;
>y : string | boolean
var b = x; // number | boolean
>b : number | boolean
>x : number | boolean
return typeof x === "number"
>typeof x === "number" ? x === 10 // number : x : boolean
>typeof x === "number" : boolean
>typeof x : string
>x : number | boolean
? x === 10 // number
>x === 10 : boolean
>x : number
: x; // x should be boolean
>x : boolean
}
}
function foo11(x: number | string | boolean) {
>foo11 : (x: string | number | boolean) => string | number | boolean
>x : string | number | boolean
// Mixing typeguard narrowing in if statement with conditional expression typeguard
// Assigning value to x deep inside another guard stops narrowing of type too
if (typeof x === "string") {
>typeof x === "string" : boolean
>typeof x : string
>x : string | number | boolean
return x; // string | number | boolean - x changed in else branch
>x : string | number | boolean
}
else {
var y: number| boolean | string;
>y : string | number | boolean
var b = x; // number | boolean | string - because below we are changing value of x in if statement
>b : string | number | boolean
>x : string | number | boolean
return typeof x === "number"
>typeof x === "number" ? ( // change value of x x = 10 && x.toString() // number | boolean | string ) : ( // do not change value y = x && x.toString() // number | boolean | string ) : string
>typeof x === "number" : boolean
>typeof x : string
>x : string | number | boolean
? (
>( // change value of x x = 10 && x.toString() // number | boolean | string ) : string
// change value of x
x = 10 && x.toString() // number | boolean | string
>x = 10 && x.toString() : string
>x : string | number | boolean
>10 && x.toString() : string
>x.toString() : string
>x.toString : () => string
>x : string | number | boolean
>toString : () => string
)
: (
>( // do not change value y = x && x.toString() // number | boolean | string ) : string
// do not change value
y = x && x.toString() // number | boolean | string
>y = x && x.toString() : string
>y : string | number | boolean
>x && x.toString() : string
>x : string | number | boolean
>x.toString() : string
>x.toString : () => string
>x : string | number | boolean
>toString : () => string
);
}
}
function foo12(x: number | string | boolean) {
>foo12 : (x: string | number | boolean) => string
>x : string | number | boolean
// Mixing typeguard narrowing in if statement with conditional expression typeguard
// Assigning value to x in outer guard shouldn't stop narrowing in the inner expression
if (typeof x === "string") {
>typeof x === "string" : boolean
>typeof x : string
>x : string | number | boolean
return x.toString(); // string | number | boolean - x changed in else branch
>x.toString() : string
>x.toString : () => string
>x : string | number | boolean
>toString : () => string
}
else {
x = 10;
>x = 10 : number
>x : string | number | boolean
var b = x; // number | boolean | string
>b : string | number | boolean
>x : string | number | boolean
return typeof x === "number"
>typeof x === "number" ? x.toString() // number : x.toString() : string
>typeof x === "number" : boolean
>typeof x : string
>x : string | number | boolean
? x.toString() // number
>x.toString() : string
>x.toString : (radix?: number) => string
>x : number
>toString : (radix?: number) => string
: x.toString(); // boolean | string
>x.toString() : string
>x.toString : () => string
>x : string | boolean
>toString : () => string
}
}

View File

@ -0,0 +1,148 @@
// In the true branch statement of an if statement,
// the type of a variable or parameter is narrowed by any type guard in the if condition when true,
// provided the true branch statement contains no assignments to the variable or parameter.
// In the false branch statement of an if statement,
// the type of a variable or parameter is narrowed by any type guard in the if condition when false,
// provided the false branch statement contains no assignments to the variable or parameter
function foo(x: number | string) {
if (typeof x === "string") {
return x.length; // string
}
else {
return x++; // number
}
}
function foo2(x: number | string) {
// x is assigned in the if true branch, the type is not narrowed
if (typeof x === "string") {
x = 10;
return x; // string | number
}
else {
return x; // string | number
}
}
function foo3(x: number | string) {
// x is assigned in the if true branch, the type is not narrowed
if (typeof x === "string") {
x = "Hello"; // even though assigned using same type as narrowed expression
return x; // string | number
}
else {
return x; // string | number
}
}
function foo4(x: number | string) {
// false branch updates the variable - so here it is not number
if (typeof x === "string") {
return x; // string | number
}
else {
x = 10; // even though assigned number - this should result in x to be string | number
return x; // string | number
}
}
function foo5(x: number | string) {
// false branch updates the variable - so here it is not number
if (typeof x === "string") {
return x; // string | number
}
else {
x = "hello";
return x; // string | number
}
}
function foo6(x: number | string) {
// Modify in both branches
if (typeof x === "string") {
x = 10;
return x; // string | number
}
else {
x = "hello";
return x; // string | number
}
}
function foo7(x: number | string | boolean) {
if (typeof x === "string") {
return x === "hello"; // string
}
else if (typeof x === "boolean") {
return x; // boolean
}
else {
return x == 10; // number
}
}
function foo8(x: number | string | boolean) {
if (typeof x === "string") {
return x === "hello"; // string
}
else {
var b: number | boolean = x; // number | boolean
if (typeof x === "boolean") {
return x; // boolean
}
else {
return x == 10; // number
}
}
}
function foo9(x: number | string) {
var y = 10;
if (typeof x === "string") {
// usage of x or assignment to separate variable shouldn't cause narrowing of type to stop
y = x.length;
return x === "hello"; // string
}
else {
return x == 10; // number
}
}
function foo10(x: number | string | boolean) {
// Mixing typeguard narrowing in if statement with conditional expression typeguard
if (typeof x === "string") {
return x === "hello"; // string
}
else {
var y: boolean | string;
var b = x; // number | boolean
return typeof x === "number"
? x === 10 // number
: x; // x should be boolean
}
}
function foo11(x: number | string | boolean) {
// Mixing typeguard narrowing in if statement with conditional expression typeguard
// Assigning value to x deep inside another guard stops narrowing of type too
if (typeof x === "string") {
return x; // string | number | boolean - x changed in else branch
}
else {
var y: number| boolean | string;
var b = x; // number | boolean | string - because below we are changing value of x in if statement
return typeof x === "number"
? (
// change value of x
x = 10 && x.toString() // number | boolean | string
)
: (
// do not change value
y = x && x.toString() // number | boolean | string
);
}
}
function foo12(x: number | string | boolean) {
// Mixing typeguard narrowing in if statement with conditional expression typeguard
// Assigning value to x in outer guard shouldn't stop narrowing in the inner expression
if (typeof x === "string") {
return x.toString(); // string | number | boolean - x changed in else branch
}
else {
x = 10;
var b = x; // number | boolean | string
return typeof x === "number"
? x.toString() // number
: x.toString(); // boolean | string
}
}