diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 11d91875838..6224e37ea53 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -4319,13 +4319,9 @@ module ts { var type = getTypeOfSymbol(symbol); // Only narrow when symbol is variable of a structured type if (node && (symbol.flags & SymbolFlags.Variable && type.flags & TypeFlags.Structured)) { - while (true) { + loop: while (true) { var child = node; node = node.parent; - // Stop at containing function or module block - if (!node || node.kind === SyntaxKind.FunctionBlock || node.kind === SyntaxKind.ModuleBlock) { - break; - } var narrowedType = type; switch (node.kind) { case SyntaxKind.IfStatement: @@ -4351,9 +4347,18 @@ module ts { } } break; + case SyntaxKind.SourceFile: + case SyntaxKind.ModuleDeclaration: + case SyntaxKind.FunctionDeclaration: + case SyntaxKind.Method: + case SyntaxKind.GetAccessor: + case SyntaxKind.SetAccessor: + case SyntaxKind.Constructor: + // Stop at the first containing function or module declaration + break loop; } - // Only use narrowed type if construct contains no assignments to variable - if (narrowedType !== type) { + // Use narrowed type if it is a subtype and construct contains no assignments to variable + if (narrowedType !== type && isTypeSubtypeOf(narrowedType, type)) { if (isVariableAssignedWithin(symbol, node)) { break; } diff --git a/tests/baselines/reference/typeGuardOfFormTypeOfBoolean.js b/tests/baselines/reference/typeGuardOfFormTypeOfBoolean.js index 4b675ffd65a..3b0ead5b3c7 100644 --- a/tests/baselines/reference/typeGuardOfFormTypeOfBoolean.js +++ b/tests/baselines/reference/typeGuardOfFormTypeOfBoolean.js @@ -17,12 +17,6 @@ var c: C; // where s is a string literal with the value 'string', 'number', or 'boolean', // - when true, narrows the type of x to the given primitive type, or // - when false, removes the primitive type from the type of x. -if (typeof strOrNum === "boolean") { - bool = strOrNum; // boolean -} -else { - var z: string | number = strOrNum; // string | number -} if (typeof strOrBool === "boolean") { bool = strOrBool; // boolean } @@ -48,15 +42,18 @@ else { c = boolOrC; // C } +// Narrowing occurs only if target type is a subtype of variable type +if (typeof strOrNum === "boolean") { + var z1: string | number = strOrNum; // string | number +} +else { + var z2: string | number = strOrNum; // string | number +} + + // A type guard of the form typeof x !== s, where s is a string literal, // - when true, narrows the type of x by typeof x === s when false, or // - when false, narrows the type of x by typeof x === s when true. -if (typeof strOrNum !== "boolean") { - var z: string | number = strOrNum; // string | number -} -else { - bool = strOrNum; // boolean -} if (typeof strOrBool !== "boolean") { str = strOrBool; // string } @@ -80,7 +77,16 @@ if (typeof boolOrC !== "boolean") { } else { bool = boolOrC; // boolean -} +} + +// Narrowing occurs only if target type is a subtype of variable type +if (typeof strOrNum !== "boolean") { + var z1: string | number = strOrNum; // string | number +} +else { + var z2: string | number = strOrNum; // string | number +} + //// [typeGuardOfFormTypeOfBoolean.js] var C = (function () { @@ -104,12 +110,6 @@ var c; // where s is a string literal with the value 'string', 'number', or 'boolean', // - when true, narrows the type of x to the given primitive type, or // - when false, removes the primitive type from the type of x. -if (typeof strOrNum === "boolean") { - bool = strOrNum; // boolean -} -else { - var z = strOrNum; // string | number -} if (typeof strOrBool === "boolean") { bool = strOrBool; // boolean } @@ -134,15 +134,16 @@ if (typeof boolOrC === "boolean") { else { c = boolOrC; // C } +// Narrowing occurs only if target type is a subtype of variable type +if (typeof strOrNum === "boolean") { + var z1 = strOrNum; // string | number +} +else { + var z2 = strOrNum; // string | number +} // A type guard of the form typeof x !== s, where s is a string literal, // - when true, narrows the type of x by typeof x === s when false, or // - when false, narrows the type of x by typeof x === s when true. -if (typeof strOrNum !== "boolean") { - var z = strOrNum; // string | number -} -else { - bool = strOrNum; // boolean -} if (typeof strOrBool !== "boolean") { str = strOrBool; // string } @@ -167,3 +168,10 @@ if (typeof boolOrC !== "boolean") { else { bool = boolOrC; // boolean } +// Narrowing occurs only if target type is a subtype of variable type +if (typeof strOrNum !== "boolean") { + var z1 = strOrNum; // string | number +} +else { + var z2 = strOrNum; // string | number +} diff --git a/tests/baselines/reference/typeGuardOfFormTypeOfBoolean.types b/tests/baselines/reference/typeGuardOfFormTypeOfBoolean.types index 6dc9792768c..6bbf5c87c40 100644 --- a/tests/baselines/reference/typeGuardOfFormTypeOfBoolean.types +++ b/tests/baselines/reference/typeGuardOfFormTypeOfBoolean.types @@ -44,21 +44,6 @@ var c: C; // where s is a string literal with the value 'string', 'number', or 'boolean', // - when true, narrows the type of x to the given primitive type, or // - when false, removes the primitive type from the type of x. -if (typeof strOrNum === "boolean") { ->typeof strOrNum === "boolean" : boolean ->typeof strOrNum : string ->strOrNum : string | number - - bool = strOrNum; // boolean ->bool = strOrNum : boolean ->bool : boolean ->strOrNum : boolean -} -else { - var z: string | number = strOrNum; // string | number ->z : string | number ->strOrNum : string | number -} if (typeof strOrBool === "boolean") { >typeof strOrBool === "boolean" : boolean >typeof strOrBool : string @@ -124,24 +109,26 @@ else { >boolOrC : C } -// A type guard of the form typeof x !== s, where s is a string literal, -// - when true, narrows the type of x by typeof x === s when false, or -// - when false, narrows the type of x by typeof x === s when true. -if (typeof strOrNum !== "boolean") { ->typeof strOrNum !== "boolean" : boolean +// Narrowing occurs only if target type is a subtype of variable type +if (typeof strOrNum === "boolean") { +>typeof strOrNum === "boolean" : boolean >typeof strOrNum : string >strOrNum : string | number - var z: string | number = strOrNum; // string | number ->z : string | number + var z1: string | number = strOrNum; // string | number +>z1 : string | number >strOrNum : string | number } else { - bool = strOrNum; // boolean ->bool = strOrNum : boolean ->bool : boolean ->strOrNum : boolean + var z2: string | number = strOrNum; // string | number +>z2 : string | number +>strOrNum : string | number } + + +// A type guard of the form typeof x !== s, where s is a string literal, +// - when true, narrows the type of x by typeof x === s when false, or +// - when false, narrows the type of x by typeof x === s when true. if (typeof strOrBool !== "boolean") { >typeof strOrBool !== "boolean" : boolean >typeof strOrBool : string @@ -206,3 +193,20 @@ else { >bool : boolean >boolOrC : boolean } + +// Narrowing occurs only if target type is a subtype of variable type +if (typeof strOrNum !== "boolean") { +>typeof strOrNum !== "boolean" : boolean +>typeof strOrNum : string +>strOrNum : string | number + + var z1: string | number = strOrNum; // string | number +>z1 : string | number +>strOrNum : string | number +} +else { + var z2: string | number = strOrNum; // string | number +>z2 : string | number +>strOrNum : string | number +} + diff --git a/tests/baselines/reference/typeGuardOfFormTypeOfNumber.js b/tests/baselines/reference/typeGuardOfFormTypeOfNumber.js index ff3a43430ba..b65ccbb882f 100644 --- a/tests/baselines/reference/typeGuardOfFormTypeOfNumber.js +++ b/tests/baselines/reference/typeGuardOfFormTypeOfNumber.js @@ -23,12 +23,6 @@ if (typeof strOrNum === "number") { else { str === strOrNum; // string } -if (typeof strOrBool === "number") { - num = strOrBool; // number -} -else { - var y: string | boolean = strOrBool; // string | boolean -} if (typeof numOrBool === "number") { num = numOrBool; // number } @@ -48,6 +42,14 @@ else { c = numOrC; // C } +// Narrowing occurs only if target type is a subtype of variable type +if (typeof strOrBool === "number") { + var y1: string | boolean = strOrBool; // string | boolean +} +else { + var y2: string | boolean = strOrBool; // string | boolean +} + // A type guard of the form typeof x !== s, where s is a string literal, // - when true, narrows the type of x by typeof x === s when false, or // - when false, narrows the type of x by typeof x === s when true. @@ -57,12 +59,6 @@ if (typeof strOrNum !== "number") { else { num = strOrNum; // number } -if (typeof strOrBool !== "number") { - var y: string | boolean = strOrBool; // string | boolean -} -else { - num = strOrBool; // number -} if (typeof numOrBool !== "number") { var x: number | boolean = numOrBool; // number | boolean } @@ -80,7 +76,16 @@ if (typeof numOrC !== "number") { } else { num = numOrC; // number -} +} + +// Narrowing occurs only if target type is a subtype of variable type +if (typeof strOrBool !== "number") { + var y1: string | boolean = strOrBool; // string | boolean +} +else { + var y2: string | boolean = strOrBool; // string | boolean +} + //// [typeGuardOfFormTypeOfNumber.js] var C = (function () { @@ -110,12 +115,6 @@ if (typeof strOrNum === "number") { else { str === strOrNum; // string } -if (typeof strOrBool === "number") { - num = strOrBool; // number -} -else { - var y = strOrBool; // string | boolean -} if (typeof numOrBool === "number") { num = numOrBool; // number } @@ -134,6 +133,13 @@ if (typeof numOrC === "number") { else { c = numOrC; // C } +// Narrowing occurs only if target type is a subtype of variable type +if (typeof strOrBool === "number") { + var y1 = strOrBool; // string | boolean +} +else { + var y2 = strOrBool; // string | boolean +} // A type guard of the form typeof x !== s, where s is a string literal, // - when true, narrows the type of x by typeof x === s when false, or // - when false, narrows the type of x by typeof x === s when true. @@ -143,12 +149,6 @@ if (typeof strOrNum !== "number") { else { num = strOrNum; // number } -if (typeof strOrBool !== "number") { - var y = strOrBool; // string | boolean -} -else { - num = strOrBool; // number -} if (typeof numOrBool !== "number") { var x = numOrBool; // number | boolean } @@ -167,3 +167,10 @@ if (typeof numOrC !== "number") { else { num = numOrC; // number } +// Narrowing occurs only if target type is a subtype of variable type +if (typeof strOrBool !== "number") { + var y1 = strOrBool; // string | boolean +} +else { + var y2 = strOrBool; // string | boolean +} diff --git a/tests/baselines/reference/typeGuardOfFormTypeOfNumber.types b/tests/baselines/reference/typeGuardOfFormTypeOfNumber.types index 7fd8855ec92..1949f5aa17d 100644 --- a/tests/baselines/reference/typeGuardOfFormTypeOfNumber.types +++ b/tests/baselines/reference/typeGuardOfFormTypeOfNumber.types @@ -60,21 +60,6 @@ else { >str : string >strOrNum : string } -if (typeof strOrBool === "number") { ->typeof strOrBool === "number" : boolean ->typeof strOrBool : string ->strOrBool : string | boolean - - num = strOrBool; // number ->num = strOrBool : number ->num : number ->strOrBool : number -} -else { - var y: string | boolean = strOrBool; // string | boolean ->y : string | boolean ->strOrBool : string | boolean -} if (typeof numOrBool === "number") { >typeof numOrBool === "number" : boolean >typeof numOrBool : string @@ -123,6 +108,22 @@ else { >numOrC : C } +// Narrowing occurs only if target type is a subtype of variable type +if (typeof strOrBool === "number") { +>typeof strOrBool === "number" : boolean +>typeof strOrBool : string +>strOrBool : string | boolean + + var y1: string | boolean = strOrBool; // string | boolean +>y1 : string | boolean +>strOrBool : string | boolean +} +else { + var y2: string | boolean = strOrBool; // string | boolean +>y2 : string | boolean +>strOrBool : string | boolean +} + // A type guard of the form typeof x !== s, where s is a string literal, // - when true, narrows the type of x by typeof x === s when false, or // - when false, narrows the type of x by typeof x === s when true. @@ -142,21 +143,6 @@ else { >num : number >strOrNum : number } -if (typeof strOrBool !== "number") { ->typeof strOrBool !== "number" : boolean ->typeof strOrBool : string ->strOrBool : string | boolean - - var y: string | boolean = strOrBool; // string | boolean ->y : string | boolean ->strOrBool : string | boolean -} -else { - num = strOrBool; // number ->num = strOrBool : number ->num : number ->strOrBool : number -} if (typeof numOrBool !== "number") { >typeof numOrBool !== "number" : boolean >typeof numOrBool : string @@ -204,3 +190,20 @@ else { >num : number >numOrC : number } + +// Narrowing occurs only if target type is a subtype of variable type +if (typeof strOrBool !== "number") { +>typeof strOrBool !== "number" : boolean +>typeof strOrBool : string +>strOrBool : string | boolean + + var y1: string | boolean = strOrBool; // string | boolean +>y1 : string | boolean +>strOrBool : string | boolean +} +else { + var y2: string | boolean = strOrBool; // string | boolean +>y2 : string | boolean +>strOrBool : string | boolean +} + diff --git a/tests/baselines/reference/typeGuardOfFormTypeOfOther.js b/tests/baselines/reference/typeGuardOfFormTypeOfOther.js index 155b2b9ce57..3bb1a2091f6 100644 --- a/tests/baselines/reference/typeGuardOfFormTypeOfOther.js +++ b/tests/baselines/reference/typeGuardOfFormTypeOfOther.js @@ -19,12 +19,6 @@ var c: C; // - when true, removes the primitive types string, number, and boolean from the type of x, or // - when false, has no effect on the type of x. -if (typeof strOrNumOrBool === "Object") { - emptyObj = strOrNumOrBool; // {} -} -else { - var r1: string | number | boolean = strOrNumOrBool; // string | number | boolean -} if (typeof strOrC === "Object") { c = strOrC; // C } @@ -44,15 +38,17 @@ else { var r4: boolean | C = boolOrC; // boolean | C } +// Narrowing occurs only if target type is a subtype of variable type +if (typeof strOrNumOrBool === "Object") { + var q1: string | number | boolean = strOrNumOrBool; // string | number | boolean +} +else { + var q2: string | number | boolean = strOrNumOrBool; // string | number | boolean +} + // A type guard of the form typeof x !== s, where s is a string literal, // - when true, narrows the type of x by typeof x === s when false, or // - when false, narrows the type of x by typeof x === s when true. -if (typeof strOrNumOrBool !== "Object") { - var r1: string | number | boolean = strOrNumOrBool; // string | number | boolean -} -else { - emptyObj = strOrNumOrBool; // {} -} if (typeof strOrC !== "Object") { var r2: string | C = strOrC; // string | C } @@ -70,7 +66,16 @@ if (typeof boolOrC !== "Object") { } else { c = boolOrC; // C -} +} + +// Narrowing occurs only if target type is a subtype of variable type +if (typeof strOrNumOrBool !== "Object") { + var q1: string | number | boolean = strOrNumOrBool; // string | number | boolean +} +else { + var q2: string | number | boolean = strOrNumOrBool; // string | number | boolean +} + //// [typeGuardOfFormTypeOfOther.js] var C = (function () { @@ -95,12 +100,6 @@ var c; // where s is a string literal with any value but 'string', 'number' or 'boolean', // - when true, removes the primitive types string, number, and boolean from the type of x, or // - when false, has no effect on the type of x. -if (typeof strOrNumOrBool === "Object") { - emptyObj = strOrNumOrBool; // {} -} -else { - var r1 = strOrNumOrBool; // string | number | boolean -} if (typeof strOrC === "Object") { c = strOrC; // C } @@ -119,15 +118,16 @@ if (typeof boolOrC === "Object") { else { var r4 = boolOrC; // boolean | C } +// Narrowing occurs only if target type is a subtype of variable type +if (typeof strOrNumOrBool === "Object") { + var q1 = strOrNumOrBool; // string | number | boolean +} +else { + var q2 = strOrNumOrBool; // string | number | boolean +} // A type guard of the form typeof x !== s, where s is a string literal, // - when true, narrows the type of x by typeof x === s when false, or // - when false, narrows the type of x by typeof x === s when true. -if (typeof strOrNumOrBool !== "Object") { - var r1 = strOrNumOrBool; // string | number | boolean -} -else { - emptyObj = strOrNumOrBool; // {} -} if (typeof strOrC !== "Object") { var r2 = strOrC; // string | C } @@ -146,3 +146,10 @@ if (typeof boolOrC !== "Object") { else { c = boolOrC; // C } +// Narrowing occurs only if target type is a subtype of variable type +if (typeof strOrNumOrBool !== "Object") { + var q1 = strOrNumOrBool; // string | number | boolean +} +else { + var q2 = strOrNumOrBool; // string | number | boolean +} diff --git a/tests/baselines/reference/typeGuardOfFormTypeOfOther.types b/tests/baselines/reference/typeGuardOfFormTypeOfOther.types index 729c255b147..04060571f83 100644 --- a/tests/baselines/reference/typeGuardOfFormTypeOfOther.types +++ b/tests/baselines/reference/typeGuardOfFormTypeOfOther.types @@ -48,21 +48,6 @@ var c: C; // - when true, removes the primitive types string, number, and boolean from the type of x, or // - when false, has no effect on the type of x. -if (typeof strOrNumOrBool === "Object") { ->typeof strOrNumOrBool === "Object" : boolean ->typeof strOrNumOrBool : string ->strOrNumOrBool : string | number | boolean - - emptyObj = strOrNumOrBool; // {} ->emptyObj = strOrNumOrBool : {} ->emptyObj : {} ->strOrNumOrBool : {} -} -else { - var r1: string | number | boolean = strOrNumOrBool; // string | number | boolean ->r1 : string | number | boolean ->strOrNumOrBool : string | number | boolean -} if (typeof strOrC === "Object") { >typeof strOrC === "Object" : boolean >typeof strOrC : string @@ -112,24 +97,25 @@ else { >boolOrC : boolean | C } -// A type guard of the form typeof x !== s, where s is a string literal, -// - when true, narrows the type of x by typeof x === s when false, or -// - when false, narrows the type of x by typeof x === s when true. -if (typeof strOrNumOrBool !== "Object") { ->typeof strOrNumOrBool !== "Object" : boolean +// Narrowing occurs only if target type is a subtype of variable type +if (typeof strOrNumOrBool === "Object") { +>typeof strOrNumOrBool === "Object" : boolean >typeof strOrNumOrBool : string >strOrNumOrBool : string | number | boolean - var r1: string | number | boolean = strOrNumOrBool; // string | number | boolean ->r1 : string | number | boolean + var q1: string | number | boolean = strOrNumOrBool; // string | number | boolean +>q1 : string | number | boolean >strOrNumOrBool : string | number | boolean } else { - emptyObj = strOrNumOrBool; // {} ->emptyObj = strOrNumOrBool : {} ->emptyObj : {} ->strOrNumOrBool : {} + var q2: string | number | boolean = strOrNumOrBool; // string | number | boolean +>q2 : string | number | boolean +>strOrNumOrBool : string | number | boolean } + +// A type guard of the form typeof x !== s, where s is a string literal, +// - when true, narrows the type of x by typeof x === s when false, or +// - when false, narrows the type of x by typeof x === s when true. if (typeof strOrC !== "Object") { >typeof strOrC !== "Object" : boolean >typeof strOrC : string @@ -178,3 +164,20 @@ else { >c : C >boolOrC : C } + +// Narrowing occurs only if target type is a subtype of variable type +if (typeof strOrNumOrBool !== "Object") { +>typeof strOrNumOrBool !== "Object" : boolean +>typeof strOrNumOrBool : string +>strOrNumOrBool : string | number | boolean + + var q1: string | number | boolean = strOrNumOrBool; // string | number | boolean +>q1 : string | number | boolean +>strOrNumOrBool : string | number | boolean +} +else { + var q2: string | number | boolean = strOrNumOrBool; // string | number | boolean +>q2 : string | number | boolean +>strOrNumOrBool : string | number | boolean +} + diff --git a/tests/baselines/reference/typeGuardOfFormTypeOfString.js b/tests/baselines/reference/typeGuardOfFormTypeOfString.js index 311f499e171..4f6652c440a 100644 --- a/tests/baselines/reference/typeGuardOfFormTypeOfString.js +++ b/tests/baselines/reference/typeGuardOfFormTypeOfString.js @@ -29,12 +29,6 @@ if (typeof strOrBool === "string") { else { bool = strOrBool; // boolean } -if (typeof numOrBool === "string") { - str = numOrBool; // string -} -else { - var x : number | boolean = numOrBool; // number | boolean -} if (typeof strOrNumOrBool === "string") { str = strOrNumOrBool; // string } @@ -48,6 +42,14 @@ else { c = strOrC; // C } +// Narrowing occurs only if target type is a subtype of variable type +if (typeof numOrBool === "string") { + var x1: number | boolean = numOrBool; // number | boolean +} +else { + var x2: number | boolean = numOrBool; // number | boolean +} + // A type guard of the form typeof x !== s, where s is a string literal, // - when true, narrows the type of x by typeof x === s when false, or // - when false, narrows the type of x by typeof x === s when true. @@ -63,12 +65,6 @@ if (typeof strOrBool !== "string") { else { str = strOrBool; // string } -if (typeof numOrBool !== "string") { - var x: number | boolean = numOrBool; // number | boolean -} -else { - str = numOrBool; // string -} if (typeof strOrNumOrBool !== "string") { numOrBool = strOrNumOrBool; // number | boolean } @@ -80,7 +76,16 @@ if (typeof strOrC !== "string") { } else { str = strOrC; // string -} +} + +// Narrowing occurs only if target type is a subtype of variable type +if (typeof numOrBool !== "string") { + var x1: number | boolean = numOrBool; // number | boolean +} +else { + var x2: number | boolean = numOrBool; // number | boolean +} + //// [typeGuardOfFormTypeOfString.js] var C = (function () { @@ -116,12 +121,6 @@ if (typeof strOrBool === "string") { else { bool = strOrBool; // boolean } -if (typeof numOrBool === "string") { - str = numOrBool; // string -} -else { - var x = numOrBool; // number | boolean -} if (typeof strOrNumOrBool === "string") { str = strOrNumOrBool; // string } @@ -134,6 +133,13 @@ if (typeof strOrC === "string") { else { c = strOrC; // C } +// Narrowing occurs only if target type is a subtype of variable type +if (typeof numOrBool === "string") { + var x1 = numOrBool; // number | boolean +} +else { + var x2 = numOrBool; // number | boolean +} // A type guard of the form typeof x !== s, where s is a string literal, // - when true, narrows the type of x by typeof x === s when false, or // - when false, narrows the type of x by typeof x === s when true. @@ -149,12 +155,6 @@ if (typeof strOrBool !== "string") { else { str = strOrBool; // string } -if (typeof numOrBool !== "string") { - var x = numOrBool; // number | boolean -} -else { - str = numOrBool; // string -} if (typeof strOrNumOrBool !== "string") { numOrBool = strOrNumOrBool; // number | boolean } @@ -167,3 +167,10 @@ if (typeof strOrC !== "string") { else { str = strOrC; // string } +// Narrowing occurs only if target type is a subtype of variable type +if (typeof numOrBool !== "string") { + var x1 = numOrBool; // number | boolean +} +else { + var x2 = numOrBool; // number | boolean +} diff --git a/tests/baselines/reference/typeGuardOfFormTypeOfString.types b/tests/baselines/reference/typeGuardOfFormTypeOfString.types index e6d90b74c66..90719fce92b 100644 --- a/tests/baselines/reference/typeGuardOfFormTypeOfString.types +++ b/tests/baselines/reference/typeGuardOfFormTypeOfString.types @@ -76,21 +76,6 @@ else { >bool : boolean >strOrBool : boolean } -if (typeof numOrBool === "string") { ->typeof numOrBool === "string" : boolean ->typeof numOrBool : string ->numOrBool : number | boolean - - str = numOrBool; // string ->str = numOrBool : string ->str : string ->numOrBool : string -} -else { - var x : number | boolean = numOrBool; // number | boolean ->x : number | boolean ->numOrBool : number | boolean -} if (typeof strOrNumOrBool === "string") { >typeof strOrNumOrBool === "string" : boolean >typeof strOrNumOrBool : string @@ -124,6 +109,22 @@ else { >strOrC : C } +// Narrowing occurs only if target type is a subtype of variable type +if (typeof numOrBool === "string") { +>typeof numOrBool === "string" : boolean +>typeof numOrBool : string +>numOrBool : number | boolean + + var x1: number | boolean = numOrBool; // number | boolean +>x1 : number | boolean +>numOrBool : number | boolean +} +else { + var x2: number | boolean = numOrBool; // number | boolean +>x2 : number | boolean +>numOrBool : number | boolean +} + // A type guard of the form typeof x !== s, where s is a string literal, // - when true, narrows the type of x by typeof x === s when false, or // - when false, narrows the type of x by typeof x === s when true. @@ -159,21 +160,6 @@ else { >str : string >strOrBool : string } -if (typeof numOrBool !== "string") { ->typeof numOrBool !== "string" : boolean ->typeof numOrBool : string ->numOrBool : number | boolean - - var x: number | boolean = numOrBool; // number | boolean ->x : number | boolean ->numOrBool : number | boolean -} -else { - str = numOrBool; // string ->str = numOrBool : string ->str : string ->numOrBool : string -} if (typeof strOrNumOrBool !== "string") { >typeof strOrNumOrBool !== "string" : boolean >typeof strOrNumOrBool : string @@ -206,3 +192,20 @@ else { >str : string >strOrC : string } + +// Narrowing occurs only if target type is a subtype of variable type +if (typeof numOrBool !== "string") { +>typeof numOrBool !== "string" : boolean +>typeof numOrBool : string +>numOrBool : number | boolean + + var x1: number | boolean = numOrBool; // number | boolean +>x1 : number | boolean +>numOrBool : number | boolean +} +else { + var x2: number | boolean = numOrBool; // number | boolean +>x2 : number | boolean +>numOrBool : number | boolean +} + diff --git a/tests/baselines/reference/typeGuardsDefeat.js b/tests/baselines/reference/typeGuardsDefeat.js index b5e1e0c362c..9670c260b57 100644 --- a/tests/baselines/reference/typeGuardsDefeat.js +++ b/tests/baselines/reference/typeGuardsDefeat.js @@ -18,23 +18,24 @@ function foo2(x: number | string) { return x.length; // string } else { - (function f() { - x = 10; - })(); - return x++; // number + var f = function () { + return x * x; + }; } + x = "hello"; + f(); } function foo3(x: number | string) { if (typeof x === "string") { return x.length; // string } else { - (() => { - x = 10; - })(); - return x++; // number + var f = () => x * x; } -} + x = "hello"; + f(); +} + //// [typeGuardsDefeat.js] // Also note that it is possible to defeat a type guard by calling a function that changes the @@ -56,20 +57,20 @@ function foo2(x) { return x.length; // string } else { - (function f() { - x = 10; - })(); - return x++; // number + var f = function () { + return x * x; + }; } + x = "hello"; + f(); } function foo3(x) { if (typeof x === "string") { return x.length; // string } else { - (function () { - x = 10; - })(); - return x++; // number + var f = function () { return x * x; }; } + x = "hello"; + f(); } diff --git a/tests/baselines/reference/typeGuardsDefeat.types b/tests/baselines/reference/typeGuardsDefeat.types index 6c4153cc3e2..5daaa75d979 100644 --- a/tests/baselines/reference/typeGuardsDefeat.types +++ b/tests/baselines/reference/typeGuardsDefeat.types @@ -47,21 +47,24 @@ function foo2(x: number | string) { >length : number } else { - (function f() { ->(function f() { x = 10; })() : void ->(function f() { x = 10; }) : () => void ->function f() { x = 10; } : () => void ->f : () => void + var f = function () { +>f : () => number +>function () { return x * x; } : () => number - x = 10; ->x = 10 : number + return x * x; +>x * x : number +>x : number +>x : number + + }; + } + x = "hello"; +>x = "hello" : string >x : string | number - })(); - return x++; // number ->x++ : number ->x : number - } + f(); +>f() : number +>f : () => number } function foo3(x: number | string) { >foo3 : (x: string | number) => number @@ -78,18 +81,19 @@ function foo3(x: number | string) { >length : number } else { - (() => { ->(() => { x = 10; })() : void ->(() => { x = 10; }) : () => void ->() => { x = 10; } : () => void - - x = 10; ->x = 10 : number ->x : string | number - - })(); - return x++; // number ->x++ : number + var f = () => x * x; +>f : () => number +>() => x * x : () => number +>x * x : number +>x : number >x : number } + x = "hello"; +>x = "hello" : string +>x : string | number + + f(); +>f() : number +>f : () => number } + diff --git a/tests/baselines/reference/typeGuardsInFunctionAndModuleBlock.js b/tests/baselines/reference/typeGuardsInFunctionAndModuleBlock.js index 1152cd8d8b5..317017c9acc 100644 --- a/tests/baselines/reference/typeGuardsInFunctionAndModuleBlock.js +++ b/tests/baselines/reference/typeGuardsInFunctionAndModuleBlock.js @@ -5,42 +5,51 @@ function foo(x: number | string | boolean) { return typeof x === "string" ? x : function f() { - var b = x; // new scope - number | boolean | string + var b = x; // number | boolean return typeof x === "boolean" ? x.toString() // boolean - : x.toString(); // number | string + : x.toString(); // number } (); } function foo2(x: number | string | boolean) { return typeof x === "string" ? x : function f(a: number | boolean) { - var b = x; // new scope - number | boolean | string + var b = x; // new scope - number | boolean return typeof x === "boolean" ? x.toString() // boolean - : x.toString(); // number | string + : x.toString(); // number } (x); // x here is narrowed to number | boolean } function foo3(x: number | string | boolean) { return typeof x === "string" ? x : (() => { - var b = x; // new scope - number | boolean | string + var b = x; // new scope - number | boolean return typeof x === "boolean" ? x.toString() // boolean - : x.toString(); // number | string + : x.toString(); // number })(); } function foo4(x: number | string | boolean) { return typeof x === "string" ? x : ((a: number | boolean) => { - var b = x; // new scope - number | boolean | string + var b = x; // new scope - number | boolean return typeof x === "boolean" ? x.toString() // boolean - : x.toString(); // number | string + : x.toString(); // number })(x); // x here is narrowed to number | boolean } +// Type guards affect nested function expressions, but not nested function declarations +function foo5(x: number | string | boolean) { + if (typeof x === "string") { + var y = x; // string; + function foo() { + var z = x; // number | string | boolean, type guard has no effect + } + } +} module m { var x: number | string | boolean; module m2 { @@ -74,28 +83,37 @@ module m1 { // typeguards are scoped in function/module block function foo(x) { return typeof x === "string" ? x : function f() { - var b = x; // new scope - number | boolean | string - return typeof x === "boolean" ? x.toString() : x.toString(); // number | string + var b = x; // number | boolean + return typeof x === "boolean" ? x.toString() : x.toString(); // number }(); } function foo2(x) { return typeof x === "string" ? x : function f(a) { - var b = x; // new scope - number | boolean | string - return typeof x === "boolean" ? x.toString() : x.toString(); // number | string + var b = x; // new scope - number | boolean + return typeof x === "boolean" ? x.toString() : x.toString(); // number }(x); // x here is narrowed to number | boolean } function foo3(x) { return typeof x === "string" ? x : (function () { - var b = x; // new scope - number | boolean | string - return typeof x === "boolean" ? x.toString() : x.toString(); // number | string + var b = x; // new scope - number | boolean + return typeof x === "boolean" ? x.toString() : x.toString(); // number })(); } function foo4(x) { return typeof x === "string" ? x : (function (a) { - var b = x; // new scope - number | boolean | string - return typeof x === "boolean" ? x.toString() : x.toString(); // number | string + var b = x; // new scope - number | boolean + return typeof x === "boolean" ? x.toString() : x.toString(); // number })(x); // x here is narrowed to number | boolean } +// Type guards affect nested function expressions, but not nested function declarations +function foo5(x) { + if (typeof x === "string") { + var y = x; // string; + function foo() { + var z = x; // number | string | boolean, type guard has no effect + } + } +} var m; (function (m) { var x; diff --git a/tests/baselines/reference/typeGuardsInFunctionAndModuleBlock.types b/tests/baselines/reference/typeGuardsInFunctionAndModuleBlock.types index c1ba1a7d639..3d914b5c80b 100644 --- a/tests/baselines/reference/typeGuardsInFunctionAndModuleBlock.types +++ b/tests/baselines/reference/typeGuardsInFunctionAndModuleBlock.types @@ -6,7 +6,7 @@ function foo(x: number | string | boolean) { >x : string | number | boolean return typeof x === "string" ->typeof x === "string" ? x : function f() { var b = x; // new scope - number | boolean | string return typeof x === "boolean" ? x.toString() // boolean : x.toString(); // number | string } () : string +>typeof x === "string" ? x : function f() { var b = x; // number | boolean return typeof x === "boolean" ? x.toString() // boolean : x.toString(); // number } () : string >typeof x === "string" : boolean >typeof x : string >x : string | number | boolean @@ -15,19 +15,19 @@ function foo(x: number | string | boolean) { >x : string : function f() { ->function f() { var b = x; // new scope - number | boolean | string return typeof x === "boolean" ? x.toString() // boolean : x.toString(); // number | string } () : string ->function f() { var b = x; // new scope - number | boolean | string return typeof x === "boolean" ? x.toString() // boolean : x.toString(); // number | string } : () => string +>function f() { var b = x; // number | boolean return typeof x === "boolean" ? x.toString() // boolean : x.toString(); // number } () : string +>function f() { var b = x; // number | boolean return typeof x === "boolean" ? x.toString() // boolean : x.toString(); // number } : () => string >f : () => string - var b = x; // new scope - number | boolean | string ->b : string | number | boolean ->x : string | number | boolean + var b = x; // number | boolean +>b : number | boolean +>x : number | boolean return typeof x === "boolean" >typeof x === "boolean" ? x.toString() // boolean : x.toString() : string >typeof x === "boolean" : boolean >typeof x : string ->x : string | number | boolean +>x : number | boolean ? x.toString() // boolean >x.toString() : string @@ -35,11 +35,11 @@ function foo(x: number | string | boolean) { >x : boolean >toString : () => string - : x.toString(); // number | string + : x.toString(); // number >x.toString() : string ->x.toString : () => string ->x : string | number ->toString : () => string +>x.toString : (radix?: number) => string +>x : number +>toString : (radix?: number) => string } (); } @@ -48,7 +48,7 @@ function foo2(x: number | string | boolean) { >x : string | number | boolean return typeof x === "string" ->typeof x === "string" ? x : function f(a: number | boolean) { var b = x; // new scope - number | boolean | string return typeof x === "boolean" ? x.toString() // boolean : x.toString(); // number | string } (x) : string +>typeof x === "string" ? x : function f(a: number | boolean) { var b = x; // new scope - number | boolean return typeof x === "boolean" ? x.toString() // boolean : x.toString(); // number } (x) : string >typeof x === "string" : boolean >typeof x : string >x : string | number | boolean @@ -57,20 +57,20 @@ function foo2(x: number | string | boolean) { >x : string : function f(a: number | boolean) { ->function f(a: number | boolean) { var b = x; // new scope - number | boolean | string return typeof x === "boolean" ? x.toString() // boolean : x.toString(); // number | string } (x) : string ->function f(a: number | boolean) { var b = x; // new scope - number | boolean | string return typeof x === "boolean" ? x.toString() // boolean : x.toString(); // number | string } : (a: number | boolean) => string +>function f(a: number | boolean) { var b = x; // new scope - number | boolean return typeof x === "boolean" ? x.toString() // boolean : x.toString(); // number } (x) : string +>function f(a: number | boolean) { var b = x; // new scope - number | boolean return typeof x === "boolean" ? x.toString() // boolean : x.toString(); // number } : (a: number | boolean) => string >f : (a: number | boolean) => string >a : number | boolean - var b = x; // new scope - number | boolean | string ->b : string | number | boolean ->x : string | number | boolean + var b = x; // new scope - number | boolean +>b : number | boolean +>x : number | boolean return typeof x === "boolean" >typeof x === "boolean" ? x.toString() // boolean : x.toString() : string >typeof x === "boolean" : boolean >typeof x : string ->x : string | number | boolean +>x : number | boolean ? x.toString() // boolean >x.toString() : string @@ -78,11 +78,11 @@ function foo2(x: number | string | boolean) { >x : boolean >toString : () => string - : x.toString(); // number | string + : x.toString(); // number >x.toString() : string ->x.toString : () => string ->x : string | number ->toString : () => string +>x.toString : (radix?: number) => string +>x : number +>toString : (radix?: number) => string } (x); // x here is narrowed to number | boolean >x : number | boolean @@ -92,7 +92,7 @@ function foo3(x: number | string | boolean) { >x : string | number | boolean return typeof x === "string" ->typeof x === "string" ? x : (() => { var b = x; // new scope - number | boolean | string return typeof x === "boolean" ? x.toString() // boolean : x.toString(); // number | string })() : string +>typeof x === "string" ? x : (() => { var b = x; // new scope - number | boolean return typeof x === "boolean" ? x.toString() // boolean : x.toString(); // number })() : string >typeof x === "string" : boolean >typeof x : string >x : string | number | boolean @@ -101,19 +101,19 @@ function foo3(x: number | string | boolean) { >x : string : (() => { ->(() => { var b = x; // new scope - number | boolean | string return typeof x === "boolean" ? x.toString() // boolean : x.toString(); // number | string })() : string ->(() => { var b = x; // new scope - number | boolean | string return typeof x === "boolean" ? x.toString() // boolean : x.toString(); // number | string }) : () => string ->() => { var b = x; // new scope - number | boolean | string return typeof x === "boolean" ? x.toString() // boolean : x.toString(); // number | string } : () => string +>(() => { var b = x; // new scope - number | boolean return typeof x === "boolean" ? x.toString() // boolean : x.toString(); // number })() : string +>(() => { var b = x; // new scope - number | boolean return typeof x === "boolean" ? x.toString() // boolean : x.toString(); // number }) : () => string +>() => { var b = x; // new scope - number | boolean return typeof x === "boolean" ? x.toString() // boolean : x.toString(); // number } : () => string - var b = x; // new scope - number | boolean | string ->b : string | number | boolean ->x : string | number | boolean + var b = x; // new scope - number | boolean +>b : number | boolean +>x : number | boolean return typeof x === "boolean" >typeof x === "boolean" ? x.toString() // boolean : x.toString() : string >typeof x === "boolean" : boolean >typeof x : string ->x : string | number | boolean +>x : number | boolean ? x.toString() // boolean >x.toString() : string @@ -121,11 +121,11 @@ function foo3(x: number | string | boolean) { >x : boolean >toString : () => string - : x.toString(); // number | string + : x.toString(); // number >x.toString() : string ->x.toString : () => string ->x : string | number ->toString : () => string +>x.toString : (radix?: number) => string +>x : number +>toString : (radix?: number) => string })(); } @@ -134,7 +134,7 @@ function foo4(x: number | string | boolean) { >x : string | number | boolean return typeof x === "string" ->typeof x === "string" ? x : ((a: number | boolean) => { var b = x; // new scope - number | boolean | string return typeof x === "boolean" ? x.toString() // boolean : x.toString(); // number | string })(x) : string +>typeof x === "string" ? x : ((a: number | boolean) => { var b = x; // new scope - number | boolean return typeof x === "boolean" ? x.toString() // boolean : x.toString(); // number })(x) : string >typeof x === "string" : boolean >typeof x : string >x : string | number | boolean @@ -143,20 +143,20 @@ function foo4(x: number | string | boolean) { >x : string : ((a: number | boolean) => { ->((a: number | boolean) => { var b = x; // new scope - number | boolean | string return typeof x === "boolean" ? x.toString() // boolean : x.toString(); // number | string })(x) : string ->((a: number | boolean) => { var b = x; // new scope - number | boolean | string return typeof x === "boolean" ? x.toString() // boolean : x.toString(); // number | string }) : (a: number | boolean) => string ->(a: number | boolean) => { var b = x; // new scope - number | boolean | string return typeof x === "boolean" ? x.toString() // boolean : x.toString(); // number | string } : (a: number | boolean) => string +>((a: number | boolean) => { var b = x; // new scope - number | boolean return typeof x === "boolean" ? x.toString() // boolean : x.toString(); // number })(x) : string +>((a: number | boolean) => { var b = x; // new scope - number | boolean return typeof x === "boolean" ? x.toString() // boolean : x.toString(); // number }) : (a: number | boolean) => string +>(a: number | boolean) => { var b = x; // new scope - number | boolean return typeof x === "boolean" ? x.toString() // boolean : x.toString(); // number } : (a: number | boolean) => string >a : number | boolean - var b = x; // new scope - number | boolean | string ->b : string | number | boolean ->x : string | number | boolean + var b = x; // new scope - number | boolean +>b : number | boolean +>x : number | boolean return typeof x === "boolean" >typeof x === "boolean" ? x.toString() // boolean : x.toString() : string >typeof x === "boolean" : boolean >typeof x : string ->x : string | number | boolean +>x : number | boolean ? x.toString() // boolean >x.toString() : string @@ -164,15 +164,38 @@ function foo4(x: number | string | boolean) { >x : boolean >toString : () => string - : x.toString(); // number | string + : x.toString(); // number >x.toString() : string ->x.toString : () => string ->x : string | number ->toString : () => string +>x.toString : (radix?: number) => string +>x : number +>toString : (radix?: number) => string })(x); // x here is narrowed to number | boolean >x : number | boolean } +// Type guards affect nested function expressions, but not nested function declarations +function foo5(x: number | string | boolean) { +>foo5 : (x: string | number | boolean) => void +>x : string | number | boolean + + if (typeof x === "string") { +>typeof x === "string" : boolean +>typeof x : string +>x : string | number | boolean + + var y = x; // string; +>y : string +>x : string + + function foo() { +>foo : () => void + + var z = x; // number | string | boolean, type guard has no effect +>z : string | number | boolean +>x : string | number | boolean + } + } +} module m { >m : typeof m diff --git a/tests/cases/conformance/expressions/typeGuards/typeGuardOfFormTypeOfBoolean.ts b/tests/cases/conformance/expressions/typeGuards/typeGuardOfFormTypeOfBoolean.ts index dc6166852ae..fb699ce8ce0 100644 --- a/tests/cases/conformance/expressions/typeGuards/typeGuardOfFormTypeOfBoolean.ts +++ b/tests/cases/conformance/expressions/typeGuards/typeGuardOfFormTypeOfBoolean.ts @@ -16,12 +16,6 @@ var c: C; // where s is a string literal with the value 'string', 'number', or 'boolean', // - when true, narrows the type of x to the given primitive type, or // - when false, removes the primitive type from the type of x. -if (typeof strOrNum === "boolean") { - bool = strOrNum; // boolean -} -else { - var z: string | number = strOrNum; // string | number -} if (typeof strOrBool === "boolean") { bool = strOrBool; // boolean } @@ -47,15 +41,18 @@ else { c = boolOrC; // C } +// Narrowing occurs only if target type is a subtype of variable type +if (typeof strOrNum === "boolean") { + var z1: string | number = strOrNum; // string | number +} +else { + var z2: string | number = strOrNum; // string | number +} + + // A type guard of the form typeof x !== s, where s is a string literal, // - when true, narrows the type of x by typeof x === s when false, or // - when false, narrows the type of x by typeof x === s when true. -if (typeof strOrNum !== "boolean") { - var z: string | number = strOrNum; // string | number -} -else { - bool = strOrNum; // boolean -} if (typeof strOrBool !== "boolean") { str = strOrBool; // string } @@ -79,4 +76,12 @@ if (typeof boolOrC !== "boolean") { } else { bool = boolOrC; // boolean -} \ No newline at end of file +} + +// Narrowing occurs only if target type is a subtype of variable type +if (typeof strOrNum !== "boolean") { + var z1: string | number = strOrNum; // string | number +} +else { + var z2: string | number = strOrNum; // string | number +} diff --git a/tests/cases/conformance/expressions/typeGuards/typeGuardOfFormTypeOfNumber.ts b/tests/cases/conformance/expressions/typeGuards/typeGuardOfFormTypeOfNumber.ts index b05e5a3a002..2868b114077 100644 --- a/tests/cases/conformance/expressions/typeGuards/typeGuardOfFormTypeOfNumber.ts +++ b/tests/cases/conformance/expressions/typeGuards/typeGuardOfFormTypeOfNumber.ts @@ -22,12 +22,6 @@ if (typeof strOrNum === "number") { else { str === strOrNum; // string } -if (typeof strOrBool === "number") { - num = strOrBool; // number -} -else { - var y: string | boolean = strOrBool; // string | boolean -} if (typeof numOrBool === "number") { num = numOrBool; // number } @@ -47,6 +41,14 @@ else { c = numOrC; // C } +// Narrowing occurs only if target type is a subtype of variable type +if (typeof strOrBool === "number") { + var y1: string | boolean = strOrBool; // string | boolean +} +else { + var y2: string | boolean = strOrBool; // string | boolean +} + // A type guard of the form typeof x !== s, where s is a string literal, // - when true, narrows the type of x by typeof x === s when false, or // - when false, narrows the type of x by typeof x === s when true. @@ -56,12 +58,6 @@ if (typeof strOrNum !== "number") { else { num = strOrNum; // number } -if (typeof strOrBool !== "number") { - var y: string | boolean = strOrBool; // string | boolean -} -else { - num = strOrBool; // number -} if (typeof numOrBool !== "number") { var x: number | boolean = numOrBool; // number | boolean } @@ -79,4 +75,12 @@ if (typeof numOrC !== "number") { } else { num = numOrC; // number -} \ No newline at end of file +} + +// Narrowing occurs only if target type is a subtype of variable type +if (typeof strOrBool !== "number") { + var y1: string | boolean = strOrBool; // string | boolean +} +else { + var y2: string | boolean = strOrBool; // string | boolean +} diff --git a/tests/cases/conformance/expressions/typeGuards/typeGuardOfFormTypeOfOther.ts b/tests/cases/conformance/expressions/typeGuards/typeGuardOfFormTypeOfOther.ts index 18109890c27..9d7d555fc18 100644 --- a/tests/cases/conformance/expressions/typeGuards/typeGuardOfFormTypeOfOther.ts +++ b/tests/cases/conformance/expressions/typeGuards/typeGuardOfFormTypeOfOther.ts @@ -18,12 +18,6 @@ var c: C; // - when true, removes the primitive types string, number, and boolean from the type of x, or // - when false, has no effect on the type of x. -if (typeof strOrNumOrBool === "Object") { - emptyObj = strOrNumOrBool; // {} -} -else { - var r1: string | number | boolean = strOrNumOrBool; // string | number | boolean -} if (typeof strOrC === "Object") { c = strOrC; // C } @@ -43,15 +37,17 @@ else { var r4: boolean | C = boolOrC; // boolean | C } +// Narrowing occurs only if target type is a subtype of variable type +if (typeof strOrNumOrBool === "Object") { + var q1: string | number | boolean = strOrNumOrBool; // string | number | boolean +} +else { + var q2: string | number | boolean = strOrNumOrBool; // string | number | boolean +} + // A type guard of the form typeof x !== s, where s is a string literal, // - when true, narrows the type of x by typeof x === s when false, or // - when false, narrows the type of x by typeof x === s when true. -if (typeof strOrNumOrBool !== "Object") { - var r1: string | number | boolean = strOrNumOrBool; // string | number | boolean -} -else { - emptyObj = strOrNumOrBool; // {} -} if (typeof strOrC !== "Object") { var r2: string | C = strOrC; // string | C } @@ -69,4 +65,12 @@ if (typeof boolOrC !== "Object") { } else { c = boolOrC; // C -} \ No newline at end of file +} + +// Narrowing occurs only if target type is a subtype of variable type +if (typeof strOrNumOrBool !== "Object") { + var q1: string | number | boolean = strOrNumOrBool; // string | number | boolean +} +else { + var q2: string | number | boolean = strOrNumOrBool; // string | number | boolean +} diff --git a/tests/cases/conformance/expressions/typeGuards/typeGuardOfFormTypeOfString.ts b/tests/cases/conformance/expressions/typeGuards/typeGuardOfFormTypeOfString.ts index 8c6bbdaf907..a356858e6c8 100644 --- a/tests/cases/conformance/expressions/typeGuards/typeGuardOfFormTypeOfString.ts +++ b/tests/cases/conformance/expressions/typeGuards/typeGuardOfFormTypeOfString.ts @@ -28,12 +28,6 @@ if (typeof strOrBool === "string") { else { bool = strOrBool; // boolean } -if (typeof numOrBool === "string") { - str = numOrBool; // string -} -else { - var x : number | boolean = numOrBool; // number | boolean -} if (typeof strOrNumOrBool === "string") { str = strOrNumOrBool; // string } @@ -47,6 +41,14 @@ else { c = strOrC; // C } +// Narrowing occurs only if target type is a subtype of variable type +if (typeof numOrBool === "string") { + var x1: number | boolean = numOrBool; // number | boolean +} +else { + var x2: number | boolean = numOrBool; // number | boolean +} + // A type guard of the form typeof x !== s, where s is a string literal, // - when true, narrows the type of x by typeof x === s when false, or // - when false, narrows the type of x by typeof x === s when true. @@ -62,12 +64,6 @@ if (typeof strOrBool !== "string") { else { str = strOrBool; // string } -if (typeof numOrBool !== "string") { - var x: number | boolean = numOrBool; // number | boolean -} -else { - str = numOrBool; // string -} if (typeof strOrNumOrBool !== "string") { numOrBool = strOrNumOrBool; // number | boolean } @@ -79,4 +75,12 @@ if (typeof strOrC !== "string") { } else { str = strOrC; // string -} \ No newline at end of file +} + +// Narrowing occurs only if target type is a subtype of variable type +if (typeof numOrBool !== "string") { + var x1: number | boolean = numOrBool; // number | boolean +} +else { + var x2: number | boolean = numOrBool; // number | boolean +} diff --git a/tests/cases/conformance/expressions/typeGuards/typeGuardsDefeat.ts b/tests/cases/conformance/expressions/typeGuards/typeGuardsDefeat.ts index 9c78e725397..0d4fbbc96f1 100644 --- a/tests/cases/conformance/expressions/typeGuards/typeGuardsDefeat.ts +++ b/tests/cases/conformance/expressions/typeGuards/typeGuardsDefeat.ts @@ -17,20 +17,20 @@ function foo2(x: number | string) { return x.length; // string } else { - (function f() { - x = 10; - })(); - return x++; // number + var f = function () { + return x * x; + }; } + x = "hello"; + f(); } function foo3(x: number | string) { if (typeof x === "string") { return x.length; // string } else { - (() => { - x = 10; - })(); - return x++; // number + var f = () => x * x; } -} \ No newline at end of file + x = "hello"; + f(); +} diff --git a/tests/cases/conformance/expressions/typeGuards/typeGuardsInFunctionAndModuleBlock.ts b/tests/cases/conformance/expressions/typeGuards/typeGuardsInFunctionAndModuleBlock.ts index 244e9f8fa64..ec23b75b590 100644 --- a/tests/cases/conformance/expressions/typeGuards/typeGuardsInFunctionAndModuleBlock.ts +++ b/tests/cases/conformance/expressions/typeGuards/typeGuardsInFunctionAndModuleBlock.ts @@ -4,42 +4,51 @@ function foo(x: number | string | boolean) { return typeof x === "string" ? x : function f() { - var b = x; // new scope - number | boolean | string + var b = x; // number | boolean return typeof x === "boolean" ? x.toString() // boolean - : x.toString(); // number | string + : x.toString(); // number } (); } function foo2(x: number | string | boolean) { return typeof x === "string" ? x : function f(a: number | boolean) { - var b = x; // new scope - number | boolean | string + var b = x; // new scope - number | boolean return typeof x === "boolean" ? x.toString() // boolean - : x.toString(); // number | string + : x.toString(); // number } (x); // x here is narrowed to number | boolean } function foo3(x: number | string | boolean) { return typeof x === "string" ? x : (() => { - var b = x; // new scope - number | boolean | string + var b = x; // new scope - number | boolean return typeof x === "boolean" ? x.toString() // boolean - : x.toString(); // number | string + : x.toString(); // number })(); } function foo4(x: number | string | boolean) { return typeof x === "string" ? x : ((a: number | boolean) => { - var b = x; // new scope - number | boolean | string + var b = x; // new scope - number | boolean return typeof x === "boolean" ? x.toString() // boolean - : x.toString(); // number | string + : x.toString(); // number })(x); // x here is narrowed to number | boolean } +// Type guards affect nested function expressions, but not nested function declarations +function foo5(x: number | string | boolean) { + if (typeof x === "string") { + var y = x; // string; + function foo() { + var z = x; // number | string | boolean, type guard has no effect + } + } +} module m { var x: number | string | boolean; module m2 {