Skip to content

Commit e5fd0dd

Browse files
authored
Allow private symbols to be control flow narrowed (microsoft#39978)
* Allow private symbols to be control flow narrowed * Add test with narrowing of inferred control flow type for completeness * Reformat long line
1 parent 8d7afc2 commit e5fd0dd

File tree

6 files changed

+197
-2
lines changed

6 files changed

+197
-2
lines changed

Diff for: src/compiler/binder.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -831,6 +831,7 @@ namespace ts {
831831
function isNarrowingExpression(expr: Expression): boolean {
832832
switch (expr.kind) {
833833
case SyntaxKind.Identifier:
834+
case SyntaxKind.PrivateIdentifier:
834835
case SyntaxKind.ThisKeyword:
835836
case SyntaxKind.PropertyAccessExpression:
836837
case SyntaxKind.ElementAccessExpression:
@@ -850,7 +851,7 @@ namespace ts {
850851
}
851852

852853
function isNarrowableReference(expr: Expression): boolean {
853-
return expr.kind === SyntaxKind.Identifier || expr.kind === SyntaxKind.ThisKeyword || expr.kind === SyntaxKind.SuperKeyword ||
854+
return expr.kind === SyntaxKind.Identifier || expr.kind === SyntaxKind.PrivateIdentifier || expr.kind === SyntaxKind.ThisKeyword || expr.kind === SyntaxKind.SuperKeyword ||
854855
(isPropertyAccessExpression(expr) || isNonNullExpression(expr) || isParenthesizedExpression(expr)) && isNarrowableReference(expr.expression) ||
855856
isElementAccessExpression(expr) && isStringOrNumericLiteralLike(expr.argumentExpression) && isNarrowableReference(expr.expression) ||
856857
isAssignmentExpression(expr) && isNarrowableReference(expr.left);

Diff for: src/compiler/checker.ts

+5-1
Original file line numberDiff line numberDiff line change
@@ -8023,7 +8023,10 @@ namespace ts {
80238023
}
80248024

80258025
function getFlowTypeInConstructor(symbol: Symbol, constructor: ConstructorDeclaration) {
8026-
const reference = factory.createPropertyAccessExpression(factory.createThis(), unescapeLeadingUnderscores(symbol.escapedName));
8026+
const accessName = startsWith(symbol.escapedName as string, "__#")
8027+
? factory.createPrivateIdentifier((symbol.escapedName as string).split("@")[1])
8028+
: unescapeLeadingUnderscores(symbol.escapedName);
8029+
const reference = factory.createPropertyAccessExpression(factory.createThis(), accessName);
80278030
setParent(reference.expression, reference);
80288031
setParent(reference, constructor);
80298032
reference.flowNode = constructor.returnFlowNode;
@@ -20232,6 +20235,7 @@ namespace ts {
2023220235
}
2023320236
switch (source.kind) {
2023420237
case SyntaxKind.Identifier:
20238+
case SyntaxKind.PrivateIdentifier:
2023520239
return target.kind === SyntaxKind.Identifier && getResolvedSymbol(<Identifier>source) === getResolvedSymbol(<Identifier>target) ||
2023620240
(target.kind === SyntaxKind.VariableDeclaration || target.kind === SyntaxKind.BindingElement) &&
2023720241
getExportSymbolOfValueSymbolIfExported(getResolvedSymbol(<Identifier>source)) === getSymbolOfNode(target);
+51
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
//// [controlFlowPrivateClassField.ts]
2+
class Example {
3+
#test;
4+
5+
constructor(test: number) {
6+
this.#test = test;
7+
}
8+
9+
get test() {
10+
return this.#test
11+
}
12+
}
13+
14+
class Example2 {
15+
#test;
16+
17+
constructor(test: number | undefined) {
18+
this.#test = test;
19+
}
20+
21+
get test() {
22+
if (this.#test) {
23+
return this.#test
24+
}
25+
return 0;
26+
}
27+
}
28+
29+
//// [controlFlowPrivateClassField.js]
30+
"use strict";
31+
class Example {
32+
constructor(test) {
33+
this.#test = test;
34+
}
35+
#test;
36+
get test() {
37+
return this.#test;
38+
}
39+
}
40+
class Example2 {
41+
constructor(test) {
42+
this.#test = test;
43+
}
44+
#test;
45+
get test() {
46+
if (this.#test) {
47+
return this.#test;
48+
}
49+
return 0;
50+
}
51+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
=== tests/cases/compiler/controlFlowPrivateClassField.ts ===
2+
class Example {
3+
>Example : Symbol(Example, Decl(controlFlowPrivateClassField.ts, 0, 0))
4+
5+
#test;
6+
>#test : Symbol(Example.#test, Decl(controlFlowPrivateClassField.ts, 0, 15))
7+
8+
constructor(test: number) {
9+
>test : Symbol(test, Decl(controlFlowPrivateClassField.ts, 3, 16))
10+
11+
this.#test = test;
12+
>this.#test : Symbol(Example.#test, Decl(controlFlowPrivateClassField.ts, 0, 15))
13+
>this : Symbol(Example, Decl(controlFlowPrivateClassField.ts, 0, 0))
14+
>test : Symbol(test, Decl(controlFlowPrivateClassField.ts, 3, 16))
15+
}
16+
17+
get test() {
18+
>test : Symbol(Example.test, Decl(controlFlowPrivateClassField.ts, 5, 5))
19+
20+
return this.#test
21+
>this.#test : Symbol(Example.#test, Decl(controlFlowPrivateClassField.ts, 0, 15))
22+
>this : Symbol(Example, Decl(controlFlowPrivateClassField.ts, 0, 0))
23+
}
24+
}
25+
26+
class Example2 {
27+
>Example2 : Symbol(Example2, Decl(controlFlowPrivateClassField.ts, 10, 1))
28+
29+
#test;
30+
>#test : Symbol(Example2.#test, Decl(controlFlowPrivateClassField.ts, 12, 16))
31+
32+
constructor(test: number | undefined) {
33+
>test : Symbol(test, Decl(controlFlowPrivateClassField.ts, 15, 16))
34+
35+
this.#test = test;
36+
>this.#test : Symbol(Example2.#test, Decl(controlFlowPrivateClassField.ts, 12, 16))
37+
>this : Symbol(Example2, Decl(controlFlowPrivateClassField.ts, 10, 1))
38+
>test : Symbol(test, Decl(controlFlowPrivateClassField.ts, 15, 16))
39+
}
40+
41+
get test() {
42+
>test : Symbol(Example2.test, Decl(controlFlowPrivateClassField.ts, 17, 5))
43+
44+
if (this.#test) {
45+
>this.#test : Symbol(Example2.#test, Decl(controlFlowPrivateClassField.ts, 12, 16))
46+
>this : Symbol(Example2, Decl(controlFlowPrivateClassField.ts, 10, 1))
47+
48+
return this.#test
49+
>this.#test : Symbol(Example2.#test, Decl(controlFlowPrivateClassField.ts, 12, 16))
50+
>this : Symbol(Example2, Decl(controlFlowPrivateClassField.ts, 10, 1))
51+
}
52+
return 0;
53+
}
54+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
=== tests/cases/compiler/controlFlowPrivateClassField.ts ===
2+
class Example {
3+
>Example : Example
4+
5+
#test;
6+
>#test : number
7+
8+
constructor(test: number) {
9+
>test : number
10+
11+
this.#test = test;
12+
>this.#test = test : number
13+
>this.#test : number
14+
>this : this
15+
>test : number
16+
}
17+
18+
get test() {
19+
>test : number
20+
21+
return this.#test
22+
>this.#test : number
23+
>this : this
24+
}
25+
}
26+
27+
class Example2 {
28+
>Example2 : Example2
29+
30+
#test;
31+
>#test : number | undefined
32+
33+
constructor(test: number | undefined) {
34+
>test : number | undefined
35+
36+
this.#test = test;
37+
>this.#test = test : number | undefined
38+
>this.#test : number | undefined
39+
>this : this
40+
>test : number | undefined
41+
}
42+
43+
get test() {
44+
>test : number
45+
46+
if (this.#test) {
47+
>this.#test : number | undefined
48+
>this : this
49+
50+
return this.#test
51+
>this.#test : number
52+
>this : this
53+
}
54+
return 0;
55+
>0 : 0
56+
}
57+
}

Diff for: tests/cases/compiler/controlFlowPrivateClassField.ts

+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
// @strict: true
2+
// @target: esnext
3+
class Example {
4+
#test;
5+
6+
constructor(test: number) {
7+
this.#test = test;
8+
}
9+
10+
get test() {
11+
return this.#test
12+
}
13+
}
14+
15+
class Example2 {
16+
#test;
17+
18+
constructor(test: number | undefined) {
19+
this.#test = test;
20+
}
21+
22+
get test() {
23+
if (this.#test) {
24+
return this.#test
25+
}
26+
return 0;
27+
}
28+
}

0 commit comments

Comments
 (0)