Skip to content

Commit b1c8245

Browse files
committed
Merge pull request microsoft#5626 from weswigham/type-guard-narrowing
Make type guards continue to narrow within classes/nested function declarations
2 parents bd84b84 + 9531d92 commit b1c8245

9 files changed

Lines changed: 134 additions & 19 deletions

src/compiler/checker.ts

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6427,6 +6427,8 @@ namespace ts {
64276427
// Only narrow when symbol is variable of type any or an object, union, or type parameter type
64286428
if (node && symbol.flags & SymbolFlags.Variable) {
64296429
if (isTypeAny(type) || type.flags & (TypeFlags.ObjectType | TypeFlags.Union | TypeFlags.TypeParameter)) {
6430+
const declaration = getDeclarationOfKind(symbol, SyntaxKind.VariableDeclaration);
6431+
const top = declaration && getDeclarationContainer(declaration);
64306432
const originalType = type;
64316433
const nodeStack: {node: Node, child: Node}[] = [];
64326434
loop: while (node.parent) {
@@ -6440,15 +6442,12 @@ namespace ts {
64406442
break;
64416443
case SyntaxKind.SourceFile:
64426444
case SyntaxKind.ModuleDeclaration:
6443-
case SyntaxKind.FunctionDeclaration:
6444-
case SyntaxKind.MethodDeclaration:
6445-
case SyntaxKind.MethodSignature:
6446-
case SyntaxKind.GetAccessor:
6447-
case SyntaxKind.SetAccessor:
6448-
case SyntaxKind.Constructor:
6449-
// Stop at the first containing function or module declaration
6445+
// Stop at the first containing file or module declaration
64506446
break loop;
64516447
}
6448+
if (node === top) {
6449+
break;
6450+
}
64526451
}
64536452

64546453
let nodes: {node: Node, child: Node};
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
//// [typeGuardInClass.ts]
2+
let x: string | number;
3+
4+
if (typeof x === "string") {
5+
let n = class {
6+
constructor() {
7+
let y: string = x;
8+
}
9+
}
10+
}
11+
else {
12+
let m = class {
13+
constructor() {
14+
let y: number = x;
15+
}
16+
}
17+
}
18+
19+
20+
//// [typeGuardInClass.js]
21+
var x;
22+
if (typeof x === "string") {
23+
var n = (function () {
24+
function class_1() {
25+
var y = x;
26+
}
27+
return class_1;
28+
})();
29+
}
30+
else {
31+
var m = (function () {
32+
function class_2() {
33+
var y = x;
34+
}
35+
return class_2;
36+
})();
37+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
=== tests/cases/conformance/expressions/typeGuards/typeGuardInClass.ts ===
2+
let x: string | number;
3+
>x : Symbol(x, Decl(typeGuardInClass.ts, 0, 3))
4+
5+
if (typeof x === "string") {
6+
>x : Symbol(x, Decl(typeGuardInClass.ts, 0, 3))
7+
8+
let n = class {
9+
>n : Symbol(n, Decl(typeGuardInClass.ts, 3, 7))
10+
11+
constructor() {
12+
let y: string = x;
13+
>y : Symbol(y, Decl(typeGuardInClass.ts, 5, 15))
14+
>x : Symbol(x, Decl(typeGuardInClass.ts, 0, 3))
15+
}
16+
}
17+
}
18+
else {
19+
let m = class {
20+
>m : Symbol(m, Decl(typeGuardInClass.ts, 10, 7))
21+
22+
constructor() {
23+
let y: number = x;
24+
>y : Symbol(y, Decl(typeGuardInClass.ts, 12, 15))
25+
>x : Symbol(x, Decl(typeGuardInClass.ts, 0, 3))
26+
}
27+
}
28+
}
29+
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
=== tests/cases/conformance/expressions/typeGuards/typeGuardInClass.ts ===
2+
let x: string | number;
3+
>x : string | number
4+
5+
if (typeof x === "string") {
6+
>typeof x === "string" : boolean
7+
>typeof x : string
8+
>x : string | number
9+
>"string" : string
10+
11+
let n = class {
12+
>n : typeof (Anonymous class)
13+
>class { constructor() { let y: string = x; } } : typeof (Anonymous class)
14+
15+
constructor() {
16+
let y: string = x;
17+
>y : string
18+
>x : string
19+
}
20+
}
21+
}
22+
else {
23+
let m = class {
24+
>m : typeof (Anonymous class)
25+
>class { constructor() { let y: number = x; } } : typeof (Anonymous class)
26+
27+
constructor() {
28+
let y: number = x;
29+
>y : number
30+
>x : number
31+
}
32+
}
33+
}
34+

tests/baselines/reference/typeGuardsInFunctionAndModuleBlock.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -41,12 +41,12 @@ function foo4(x: number | string | boolean) {
4141
: x.toString(); // number
4242
})(x); // x here is narrowed to number | boolean
4343
}
44-
// Type guards affect nested function expressions, but not nested function declarations
44+
// Type guards affect nested function expressions and nested function declarations
4545
function foo5(x: number | string | boolean) {
4646
if (typeof x === "string") {
4747
var y = x; // string;
4848
function foo() {
49-
var z = x; // number | string | boolean, type guard has no effect
49+
var z = x; // string
5050
}
5151
}
5252
}
@@ -121,12 +121,12 @@ function foo4(x) {
121121
: x.toString(); // number
122122
})(x); // x here is narrowed to number | boolean
123123
}
124-
// Type guards affect nested function expressions, but not nested function declarations
124+
// Type guards affect nested function expressions and nested function declarations
125125
function foo5(x) {
126126
if (typeof x === "string") {
127127
var y = x; // string;
128128
function foo() {
129-
var z = x; // number | string | boolean, type guard has no effect
129+
var z = x; // string
130130
}
131131
}
132132
}

tests/baselines/reference/typeGuardsInFunctionAndModuleBlock.symbols

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,7 @@ function foo4(x: number | string | boolean) {
130130
})(x); // x here is narrowed to number | boolean
131131
>x : Symbol(x, Decl(typeGuardsInFunctionAndModuleBlock.ts, 32, 14))
132132
}
133-
// Type guards affect nested function expressions, but not nested function declarations
133+
// Type guards affect nested function expressions and nested function declarations
134134
function foo5(x: number | string | boolean) {
135135
>foo5 : Symbol(foo5, Decl(typeGuardsInFunctionAndModuleBlock.ts, 41, 1))
136136
>x : Symbol(x, Decl(typeGuardsInFunctionAndModuleBlock.ts, 43, 14))
@@ -145,7 +145,7 @@ function foo5(x: number | string | boolean) {
145145
function foo() {
146146
>foo : Symbol(foo, Decl(typeGuardsInFunctionAndModuleBlock.ts, 45, 18))
147147

148-
var z = x; // number | string | boolean, type guard has no effect
148+
var z = x; // string
149149
>z : Symbol(z, Decl(typeGuardsInFunctionAndModuleBlock.ts, 47, 15))
150150
>x : Symbol(x, Decl(typeGuardsInFunctionAndModuleBlock.ts, 43, 14))
151151
}

tests/baselines/reference/typeGuardsInFunctionAndModuleBlock.types

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -181,7 +181,7 @@ function foo4(x: number | string | boolean) {
181181
})(x); // x here is narrowed to number | boolean
182182
>x : number | boolean
183183
}
184-
// Type guards affect nested function expressions, but not nested function declarations
184+
// Type guards affect nested function expressions and nested function declarations
185185
function foo5(x: number | string | boolean) {
186186
>foo5 : (x: number | string | boolean) => void
187187
>x : number | string | boolean
@@ -199,9 +199,9 @@ function foo5(x: number | string | boolean) {
199199
function foo() {
200200
>foo : () => void
201201

202-
var z = x; // number | string | boolean, type guard has no effect
203-
>z : number | string | boolean
204-
>x : number | string | boolean
202+
var z = x; // string
203+
>z : string
204+
>x : string
205205
}
206206
}
207207
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
let x: string | number;
2+
3+
if (typeof x === "string") {
4+
let n = class {
5+
constructor() {
6+
let y: string = x;
7+
}
8+
}
9+
}
10+
else {
11+
let m = class {
12+
constructor() {
13+
let y: number = x;
14+
}
15+
}
16+
}

tests/cases/conformance/expressions/typeGuards/typeGuardsInFunctionAndModuleBlock.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,12 +40,12 @@ function foo4(x: number | string | boolean) {
4040
: x.toString(); // number
4141
})(x); // x here is narrowed to number | boolean
4242
}
43-
// Type guards affect nested function expressions, but not nested function declarations
43+
// Type guards affect nested function expressions and nested function declarations
4444
function foo5(x: number | string | boolean) {
4545
if (typeof x === "string") {
4646
var y = x; // string;
4747
function foo() {
48-
var z = x; // number | string | boolean, type guard has no effect
48+
var z = x; // string
4949
}
5050
}
5151
}

0 commit comments

Comments
 (0)