From 93721658119d0aef2f8d2050c57db47dd0c67be0 Mon Sep 17 00:00:00 2001 From: Wesley Wigham Date: Mon, 10 Aug 2020 13:31:42 -0700 Subject: [PATCH 1/3] Allow private symbols to be control flow narrowed --- src/compiler/binder.ts | 3 ++- src/compiler/checker.ts | 3 ++- .../reference/controlFlowPrivateClassField.js | 24 ++++++++++++++++++ .../controlFlowPrivateClassField.symbols | 24 ++++++++++++++++++ .../controlFlowPrivateClassField.types | 25 +++++++++++++++++++ .../compiler/controlFlowPrivateClassField.ts | 13 ++++++++++ 6 files changed, 90 insertions(+), 2 deletions(-) create mode 100644 tests/baselines/reference/controlFlowPrivateClassField.js create mode 100644 tests/baselines/reference/controlFlowPrivateClassField.symbols create mode 100644 tests/baselines/reference/controlFlowPrivateClassField.types create mode 100644 tests/cases/compiler/controlFlowPrivateClassField.ts diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index 7a6aaa1861859..b5b7cf7e87ae5 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -816,6 +816,7 @@ namespace ts { function isNarrowingExpression(expr: Expression): boolean { switch (expr.kind) { case SyntaxKind.Identifier: + case SyntaxKind.PrivateIdentifier: case SyntaxKind.ThisKeyword: case SyntaxKind.PropertyAccessExpression: case SyntaxKind.ElementAccessExpression: @@ -835,7 +836,7 @@ namespace ts { } function isNarrowableReference(expr: Expression): boolean { - return expr.kind === SyntaxKind.Identifier || expr.kind === SyntaxKind.ThisKeyword || expr.kind === SyntaxKind.SuperKeyword || + return expr.kind === SyntaxKind.Identifier || expr.kind === SyntaxKind.PrivateIdentifier || expr.kind === SyntaxKind.ThisKeyword || expr.kind === SyntaxKind.SuperKeyword || (isPropertyAccessExpression(expr) || isNonNullExpression(expr) || isParenthesizedExpression(expr)) && isNarrowableReference(expr.expression) || isElementAccessExpression(expr) && isStringOrNumericLiteralLike(expr.argumentExpression) && isNarrowableReference(expr.expression) || isAssignmentExpression(expr) && isNarrowableReference(expr.left); diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 748cab985dab1..f7541cbce3b4e 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -7925,7 +7925,7 @@ namespace ts { } function getFlowTypeInConstructor(symbol: Symbol, constructor: ConstructorDeclaration) { - const reference = factory.createPropertyAccessExpression(factory.createThis(), unescapeLeadingUnderscores(symbol.escapedName)); + const reference = factory.createPropertyAccessExpression(factory.createThis(), startsWith(symbol.escapedName as string, "__#") ? factory.createPrivateIdentifier((symbol.escapedName as string).split("@")[1]) : unescapeLeadingUnderscores(symbol.escapedName)); setParent(reference.expression, reference); setParent(reference, constructor); reference.flowNode = constructor.returnFlowNode; @@ -20155,6 +20155,7 @@ namespace ts { } switch (source.kind) { case SyntaxKind.Identifier: + case SyntaxKind.PrivateIdentifier: return target.kind === SyntaxKind.Identifier && getResolvedSymbol(source) === getResolvedSymbol(target) || (target.kind === SyntaxKind.VariableDeclaration || target.kind === SyntaxKind.BindingElement) && getExportSymbolOfValueSymbolIfExported(getResolvedSymbol(source)) === getSymbolOfNode(target); diff --git a/tests/baselines/reference/controlFlowPrivateClassField.js b/tests/baselines/reference/controlFlowPrivateClassField.js new file mode 100644 index 0000000000000..eede2b40034df --- /dev/null +++ b/tests/baselines/reference/controlFlowPrivateClassField.js @@ -0,0 +1,24 @@ +//// [controlFlowPrivateClassField.ts] +class Example { + #test; + + constructor(test: number) { + this.#test = test; + } + + get test() { + return this.#test + } +} + +//// [controlFlowPrivateClassField.js] +"use strict"; +class Example { + constructor(test) { + this.#test = test; + } + #test; + get test() { + return this.#test; + } +} diff --git a/tests/baselines/reference/controlFlowPrivateClassField.symbols b/tests/baselines/reference/controlFlowPrivateClassField.symbols new file mode 100644 index 0000000000000..8d472cc2cac68 --- /dev/null +++ b/tests/baselines/reference/controlFlowPrivateClassField.symbols @@ -0,0 +1,24 @@ +=== tests/cases/compiler/controlFlowPrivateClassField.ts === +class Example { +>Example : Symbol(Example, Decl(controlFlowPrivateClassField.ts, 0, 0)) + + #test; +>#test : Symbol(Example.#test, Decl(controlFlowPrivateClassField.ts, 0, 15)) + + constructor(test: number) { +>test : Symbol(test, Decl(controlFlowPrivateClassField.ts, 3, 16)) + + this.#test = test; +>this.#test : Symbol(Example.#test, Decl(controlFlowPrivateClassField.ts, 0, 15)) +>this : Symbol(Example, Decl(controlFlowPrivateClassField.ts, 0, 0)) +>test : Symbol(test, Decl(controlFlowPrivateClassField.ts, 3, 16)) + } + + get test() { +>test : Symbol(Example.test, Decl(controlFlowPrivateClassField.ts, 5, 5)) + + return this.#test +>this.#test : Symbol(Example.#test, Decl(controlFlowPrivateClassField.ts, 0, 15)) +>this : Symbol(Example, Decl(controlFlowPrivateClassField.ts, 0, 0)) + } +} diff --git a/tests/baselines/reference/controlFlowPrivateClassField.types b/tests/baselines/reference/controlFlowPrivateClassField.types new file mode 100644 index 0000000000000..3226f1e23c082 --- /dev/null +++ b/tests/baselines/reference/controlFlowPrivateClassField.types @@ -0,0 +1,25 @@ +=== tests/cases/compiler/controlFlowPrivateClassField.ts === +class Example { +>Example : Example + + #test; +>#test : number + + constructor(test: number) { +>test : number + + this.#test = test; +>this.#test = test : number +>this.#test : number +>this : this +>test : number + } + + get test() { +>test : number + + return this.#test +>this.#test : number +>this : this + } +} diff --git a/tests/cases/compiler/controlFlowPrivateClassField.ts b/tests/cases/compiler/controlFlowPrivateClassField.ts new file mode 100644 index 0000000000000..b550fe660c23e --- /dev/null +++ b/tests/cases/compiler/controlFlowPrivateClassField.ts @@ -0,0 +1,13 @@ +// @strict: true +// @target: esnext +class Example { + #test; + + constructor(test: number) { + this.#test = test; + } + + get test() { + return this.#test + } +} \ No newline at end of file From 50c8691260e9f1ac2fa385ce6802a1ca50eb61cc Mon Sep 17 00:00:00 2001 From: Wesley Wigham Date: Mon, 10 Aug 2020 13:35:34 -0700 Subject: [PATCH 2/3] Add test with narrowing of inferred control flow type for completeness --- .../reference/controlFlowPrivateClassField.js | 27 ++++++++++++++++ .../controlFlowPrivateClassField.symbols | 30 +++++++++++++++++ .../controlFlowPrivateClassField.types | 32 +++++++++++++++++++ .../compiler/controlFlowPrivateClassField.ts | 15 +++++++++ 4 files changed, 104 insertions(+) diff --git a/tests/baselines/reference/controlFlowPrivateClassField.js b/tests/baselines/reference/controlFlowPrivateClassField.js index eede2b40034df..0ec517e098d14 100644 --- a/tests/baselines/reference/controlFlowPrivateClassField.js +++ b/tests/baselines/reference/controlFlowPrivateClassField.js @@ -9,6 +9,21 @@ class Example { get test() { return this.#test } +} + +class Example2 { + #test; + + constructor(test: number | undefined) { + this.#test = test; + } + + get test() { + if (this.#test) { + return this.#test + } + return 0; + } } //// [controlFlowPrivateClassField.js] @@ -22,3 +37,15 @@ class Example { return this.#test; } } +class Example2 { + constructor(test) { + this.#test = test; + } + #test; + get test() { + if (this.#test) { + return this.#test; + } + return 0; + } +} diff --git a/tests/baselines/reference/controlFlowPrivateClassField.symbols b/tests/baselines/reference/controlFlowPrivateClassField.symbols index 8d472cc2cac68..d2d9faf0bc4c9 100644 --- a/tests/baselines/reference/controlFlowPrivateClassField.symbols +++ b/tests/baselines/reference/controlFlowPrivateClassField.symbols @@ -22,3 +22,33 @@ class Example { >this : Symbol(Example, Decl(controlFlowPrivateClassField.ts, 0, 0)) } } + +class Example2 { +>Example2 : Symbol(Example2, Decl(controlFlowPrivateClassField.ts, 10, 1)) + + #test; +>#test : Symbol(Example2.#test, Decl(controlFlowPrivateClassField.ts, 12, 16)) + + constructor(test: number | undefined) { +>test : Symbol(test, Decl(controlFlowPrivateClassField.ts, 15, 16)) + + this.#test = test; +>this.#test : Symbol(Example2.#test, Decl(controlFlowPrivateClassField.ts, 12, 16)) +>this : Symbol(Example2, Decl(controlFlowPrivateClassField.ts, 10, 1)) +>test : Symbol(test, Decl(controlFlowPrivateClassField.ts, 15, 16)) + } + + get test() { +>test : Symbol(Example2.test, Decl(controlFlowPrivateClassField.ts, 17, 5)) + + if (this.#test) { +>this.#test : Symbol(Example2.#test, Decl(controlFlowPrivateClassField.ts, 12, 16)) +>this : Symbol(Example2, Decl(controlFlowPrivateClassField.ts, 10, 1)) + + return this.#test +>this.#test : Symbol(Example2.#test, Decl(controlFlowPrivateClassField.ts, 12, 16)) +>this : Symbol(Example2, Decl(controlFlowPrivateClassField.ts, 10, 1)) + } + return 0; + } +} diff --git a/tests/baselines/reference/controlFlowPrivateClassField.types b/tests/baselines/reference/controlFlowPrivateClassField.types index 3226f1e23c082..cab3017d94f96 100644 --- a/tests/baselines/reference/controlFlowPrivateClassField.types +++ b/tests/baselines/reference/controlFlowPrivateClassField.types @@ -23,3 +23,35 @@ class Example { >this : this } } + +class Example2 { +>Example2 : Example2 + + #test; +>#test : number | undefined + + constructor(test: number | undefined) { +>test : number | undefined + + this.#test = test; +>this.#test = test : number | undefined +>this.#test : number | undefined +>this : this +>test : number | undefined + } + + get test() { +>test : number + + if (this.#test) { +>this.#test : number | undefined +>this : this + + return this.#test +>this.#test : number +>this : this + } + return 0; +>0 : 0 + } +} diff --git a/tests/cases/compiler/controlFlowPrivateClassField.ts b/tests/cases/compiler/controlFlowPrivateClassField.ts index b550fe660c23e..a9e8f5569b714 100644 --- a/tests/cases/compiler/controlFlowPrivateClassField.ts +++ b/tests/cases/compiler/controlFlowPrivateClassField.ts @@ -10,4 +10,19 @@ class Example { get test() { return this.#test } +} + +class Example2 { + #test; + + constructor(test: number | undefined) { + this.#test = test; + } + + get test() { + if (this.#test) { + return this.#test + } + return 0; + } } \ No newline at end of file From 3909daf1bab41b7f9038e55f035d8cf23838c6ca Mon Sep 17 00:00:00 2001 From: Wesley Wigham Date: Sat, 5 Sep 2020 02:26:18 -0700 Subject: [PATCH 3/3] Reformat long line --- src/compiler/checker.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 930bb34da912b..6c0781bccec96 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -8023,7 +8023,10 @@ namespace ts { } function getFlowTypeInConstructor(symbol: Symbol, constructor: ConstructorDeclaration) { - const reference = factory.createPropertyAccessExpression(factory.createThis(), startsWith(symbol.escapedName as string, "__#") ? factory.createPrivateIdentifier((symbol.escapedName as string).split("@")[1]) : unescapeLeadingUnderscores(symbol.escapedName)); + const accessName = startsWith(symbol.escapedName as string, "__#") + ? factory.createPrivateIdentifier((symbol.escapedName as string).split("@")[1]) + : unescapeLeadingUnderscores(symbol.escapedName); + const reference = factory.createPropertyAccessExpression(factory.createThis(), accessName); setParent(reference.expression, reference); setParent(reference, constructor); reference.flowNode = constructor.returnFlowNode;