Skip to content

Commit 6b5a53c

Browse files
authored
Merge pull request #11458 from Microsoft/narrowedConst
Narrowed consts flow through object literal or class expression method
2 parents a6443e3 + 402f1f6 commit 6b5a53c

File tree

8 files changed

+176
-5
lines changed

8 files changed

+176
-5
lines changed

src/compiler/binder.ts

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ namespace ts {
8484
IsFunctionExpression = 1 << 4,
8585
HasLocals = 1 << 5,
8686
IsInterface = 1 << 6,
87+
IsObjectLiteralOrClassExpressionMethod = 1 << 7,
8788
}
8889

8990
const binder = createBinder();
@@ -486,8 +487,8 @@ namespace ts {
486487
}
487488
else {
488489
currentFlow = { flags: FlowFlags.Start };
489-
if (containerFlags & ContainerFlags.IsFunctionExpression) {
490-
(<FlowStart>currentFlow).container = <FunctionExpression | ArrowFunction>node;
490+
if (containerFlags & (ContainerFlags.IsFunctionExpression | ContainerFlags.IsObjectLiteralOrClassExpressionMethod)) {
491+
(<FlowStart>currentFlow).container = <FunctionExpression | ArrowFunction | MethodDeclaration>node;
491492
}
492493
currentReturnTarget = undefined;
493494
}
@@ -1237,9 +1238,12 @@ namespace ts {
12371238
case SyntaxKind.SourceFile:
12381239
return ContainerFlags.IsContainer | ContainerFlags.IsControlFlowContainer | ContainerFlags.HasLocals;
12391240

1241+
case SyntaxKind.MethodDeclaration:
1242+
if (isObjectLiteralOrClassExpressionMethod(node)) {
1243+
return ContainerFlags.IsContainer | ContainerFlags.IsControlFlowContainer | ContainerFlags.HasLocals | ContainerFlags.IsFunctionLike | ContainerFlags.IsObjectLiteralOrClassExpressionMethod;
1244+
}
12401245
case SyntaxKind.Constructor:
12411246
case SyntaxKind.FunctionDeclaration:
1242-
case SyntaxKind.MethodDeclaration:
12431247
case SyntaxKind.MethodSignature:
12441248
case SyntaxKind.GetAccessor:
12451249
case SyntaxKind.SetAccessor:
@@ -2238,6 +2242,10 @@ namespace ts {
22382242
}
22392243
}
22402244

2245+
if (currentFlow && isObjectLiteralOrClassExpressionMethod(node)) {
2246+
node.flowNode = currentFlow;
2247+
}
2248+
22412249
return hasDynamicName(node)
22422250
? bindAnonymousDeclaration(node, symbolFlags, "__computed")
22432251
: declareSymbolAndAddToSymbolTable(node, symbolFlags, symbolExcludes);

src/compiler/checker.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9191,7 +9191,9 @@ namespace ts {
91919191
// a const variable or parameter from an outer function, we extend the origin of the control flow
91929192
// analysis to include the immediately enclosing function.
91939193
while (flowContainer !== declarationContainer &&
9194-
(flowContainer.kind === SyntaxKind.FunctionExpression || flowContainer.kind === SyntaxKind.ArrowFunction) &&
9194+
(flowContainer.kind === SyntaxKind.FunctionExpression ||
9195+
flowContainer.kind === SyntaxKind.ArrowFunction ||
9196+
isObjectLiteralOrClassExpressionMethod(flowContainer)) &&
91959197
(isReadonlySymbol(localOrExportSymbol) || isParameter && !isParameterAssigned(localOrExportSymbol))) {
91969198
flowContainer = getControlFlowContainer(flowContainer);
91979199
}

src/compiler/types.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1922,7 +1922,7 @@ namespace ts {
19221922
// function, the container property references the function (which in turn has a flowNode
19231923
// property for the containing control flow).
19241924
export interface FlowStart extends FlowNode {
1925-
container?: FunctionExpression | ArrowFunction;
1925+
container?: FunctionExpression | ArrowFunction | MethodDeclaration;
19261926
}
19271927

19281928
// FlowLabel represents a junction with multiple possible preceding control flows.

src/compiler/utilities.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -895,6 +895,12 @@ namespace ts {
895895
return node && node.kind === SyntaxKind.MethodDeclaration && node.parent.kind === SyntaxKind.ObjectLiteralExpression;
896896
}
897897

898+
export function isObjectLiteralOrClassExpressionMethod(node: Node): node is MethodDeclaration {
899+
return node.kind === SyntaxKind.MethodDeclaration &&
900+
(node.parent.kind === SyntaxKind.ObjectLiteralExpression ||
901+
node.parent.kind === SyntaxKind.ClassExpression);
902+
}
903+
898904
export function isIdentifierTypePredicate(predicate: TypePredicate): predicate is IdentifierTypePredicate {
899905
return predicate && predicate.kind === TypePredicateKind.Identifier;
900906
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
//// [narrowedConstInMethod.ts]
2+
3+
function f() {
4+
const x: string | null = <any>{};
5+
if (x !== null) {
6+
return {
7+
bar() { return x.length; } // Error: possibly null x
8+
};
9+
}
10+
}
11+
12+
function f2() {
13+
const x: string | null = <any>{};
14+
if (x !== null) {
15+
return class {
16+
bar() { return x.length; } // Error: possibly null x
17+
};
18+
}
19+
}
20+
21+
//// [narrowedConstInMethod.js]
22+
function f() {
23+
var x = {};
24+
if (x !== null) {
25+
return {
26+
bar: function () { return x.length; } // Error: possibly null x
27+
};
28+
}
29+
}
30+
function f2() {
31+
var x = {};
32+
if (x !== null) {
33+
return (function () {
34+
function class_1() {
35+
}
36+
class_1.prototype.bar = function () { return x.length; }; // Error: possibly null x
37+
return class_1;
38+
}());
39+
}
40+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
=== tests/cases/compiler/narrowedConstInMethod.ts ===
2+
3+
function f() {
4+
>f : Symbol(f, Decl(narrowedConstInMethod.ts, 0, 0))
5+
6+
const x: string | null = <any>{};
7+
>x : Symbol(x, Decl(narrowedConstInMethod.ts, 2, 9))
8+
9+
if (x !== null) {
10+
>x : Symbol(x, Decl(narrowedConstInMethod.ts, 2, 9))
11+
12+
return {
13+
bar() { return x.length; } // Error: possibly null x
14+
>bar : Symbol(bar, Decl(narrowedConstInMethod.ts, 4, 16))
15+
>x.length : Symbol(String.length, Decl(lib.d.ts, --, --))
16+
>x : Symbol(x, Decl(narrowedConstInMethod.ts, 2, 9))
17+
>length : Symbol(String.length, Decl(lib.d.ts, --, --))
18+
19+
};
20+
}
21+
}
22+
23+
function f2() {
24+
>f2 : Symbol(f2, Decl(narrowedConstInMethod.ts, 8, 1))
25+
26+
const x: string | null = <any>{};
27+
>x : Symbol(x, Decl(narrowedConstInMethod.ts, 11, 9))
28+
29+
if (x !== null) {
30+
>x : Symbol(x, Decl(narrowedConstInMethod.ts, 11, 9))
31+
32+
return class {
33+
bar() { return x.length; } // Error: possibly null x
34+
>bar : Symbol((Anonymous class).bar, Decl(narrowedConstInMethod.ts, 13, 22))
35+
>x.length : Symbol(String.length, Decl(lib.d.ts, --, --))
36+
>x : Symbol(x, Decl(narrowedConstInMethod.ts, 11, 9))
37+
>length : Symbol(String.length, Decl(lib.d.ts, --, --))
38+
39+
};
40+
}
41+
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
=== tests/cases/compiler/narrowedConstInMethod.ts ===
2+
3+
function f() {
4+
>f : () => { bar(): number; } | undefined
5+
6+
const x: string | null = <any>{};
7+
>x : string | null
8+
>null : null
9+
><any>{} : any
10+
>{} : {}
11+
12+
if (x !== null) {
13+
>x !== null : boolean
14+
>x : string | null
15+
>null : null
16+
17+
return {
18+
>{ bar() { return x.length; } // Error: possibly null x } : { bar(): number; }
19+
20+
bar() { return x.length; } // Error: possibly null x
21+
>bar : () => number
22+
>x.length : number
23+
>x : string
24+
>length : number
25+
26+
};
27+
}
28+
}
29+
30+
function f2() {
31+
>f2 : () => typeof (Anonymous class) | undefined
32+
33+
const x: string | null = <any>{};
34+
>x : string | null
35+
>null : null
36+
><any>{} : any
37+
>{} : {}
38+
39+
if (x !== null) {
40+
>x !== null : boolean
41+
>x : string | null
42+
>null : null
43+
44+
return class {
45+
>class { bar() { return x.length; } // Error: possibly null x } : typeof (Anonymous class)
46+
47+
bar() { return x.length; } // Error: possibly null x
48+
>bar : () => number
49+
>x.length : number
50+
>x : string
51+
>length : number
52+
53+
};
54+
}
55+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
// @strictNullChecks: true
2+
3+
function f() {
4+
const x: string | null = <any>{};
5+
if (x !== null) {
6+
return {
7+
bar() { return x.length; } // Error: possibly null x
8+
};
9+
}
10+
}
11+
12+
function f2() {
13+
const x: string | null = <any>{};
14+
if (x !== null) {
15+
return class {
16+
bar() { return x.length; } // Error: possibly null x
17+
};
18+
}
19+
}

0 commit comments

Comments
 (0)