fix(54465): Broken emit with private field in class decorator (#54679)

This commit is contained in:
Oleksandr T
2023-06-23 17:09:48 -04:00
committed by GitHub
parent 39da6b1c63
commit de6e6abbac
14 changed files with 361 additions and 8 deletions
+15 -8
View File
@@ -248,6 +248,7 @@ import {
getCombinedModifierFlags,
getCombinedNodeFlags,
getContainingClass,
getContainingClassExcludingClassDecorators,
getContainingClassStaticBlock,
getContainingFunction,
getContainingFunctionOrClassStaticBlock,
@@ -31407,7 +31408,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
// Lookup the private identifier lexically.
function lookupSymbolForPrivateIdentifierDeclaration(propName: __String, location: Node): Symbol | undefined {
for (let containingClass = getContainingClass(location); !!containingClass; containingClass = getContainingClass(containingClass)) {
for (let containingClass = getContainingClassExcludingClassDecorators(location); !!containingClass; containingClass = getContainingClass(containingClass)) {
const { symbol } = containingClass;
const name = getSymbolNameForPrivateIdentifier(symbol, propName);
const prop = (symbol.members && symbol.members.get(name)) || (symbol.exports && symbol.exports.get(name));
@@ -31547,23 +31548,29 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
if (assignmentKind && lexicallyScopedSymbol && lexicallyScopedSymbol.valueDeclaration && isMethodDeclaration(lexicallyScopedSymbol.valueDeclaration)) {
grammarErrorOnNode(right, Diagnostics.Cannot_assign_to_private_method_0_Private_methods_are_not_writable, idText(right));
}
if (isAnyLike) {
if (lexicallyScopedSymbol) {
return isErrorType(apparentType) ? errorType : apparentType;
}
if (!getContainingClass(right)) {
if (getContainingClassExcludingClassDecorators(right) === undefined) {
grammarErrorOnNode(right, Diagnostics.Private_identifiers_are_not_allowed_outside_class_bodies);
return anyType;
}
}
prop = lexicallyScopedSymbol ? getPrivateIdentifierPropertyOfType(leftType, lexicallyScopedSymbol) : undefined;
// Check for private-identifier-specific shadowing and lexical-scoping errors.
if (!prop && checkPrivateIdentifierPropertyAccess(leftType, right, lexicallyScopedSymbol)) {
return errorType;
prop = lexicallyScopedSymbol && getPrivateIdentifierPropertyOfType(leftType, lexicallyScopedSymbol);
if (prop === undefined) {
// Check for private-identifier-specific shadowing and lexical-scoping errors.
if (checkPrivateIdentifierPropertyAccess(leftType, right, lexicallyScopedSymbol)) {
return errorType;
}
const containingClass = getContainingClassExcludingClassDecorators(right);
if (containingClass && isPlainJsFile(getSourceFileOfNode(containingClass), compilerOptions.checkJs)) {
grammarErrorOnNode(right, Diagnostics.Private_field_0_must_be_declared_in_an_enclosing_class, idText(right));
}
}
else {
const isSetonlyAccessor = prop && prop.flags & SymbolFlags.SetAccessor && !(prop.flags & SymbolFlags.GetAccessor);
const isSetonlyAccessor = prop.flags & SymbolFlags.SetAccessor && !(prop.flags & SymbolFlags.GetAccessor);
if (isSetonlyAccessor && assignmentKind !== AssignmentKind.Definite) {
error(node, Diagnostics.Private_accessor_was_defined_without_a_getter);
}
+4
View File
@@ -315,6 +315,10 @@
"category": "Error",
"code": 1110
},
"Private field '{0}' must be declared in an enclosing class.": {
"category": "Error",
"code": 1111
},
"A 'default' clause cannot appear more than once in a 'switch' statement.": {
"category": "Error",
"code": 1113
+1
View File
@@ -1430,6 +1430,7 @@ export const plainJSErrors: Set<number> = new Set([
Diagnostics.Class_constructor_may_not_be_an_accessor.code,
Diagnostics.await_expressions_are_only_allowed_within_async_functions_and_at_the_top_levels_of_modules.code,
Diagnostics.await_using_statements_are_only_allowed_within_async_functions_and_at_the_top_levels_of_modules.code,
Diagnostics.Private_field_0_must_be_declared_in_an_enclosing_class.code,
// Type errors
Diagnostics.This_condition_will_always_return_0_since_JavaScript_compares_objects_by_reference_not_value.code,
]);
+6
View File
@@ -2838,6 +2838,12 @@ export function getContainingFunctionOrClassStaticBlock(node: Node): SignatureDe
return findAncestor(node.parent, isFunctionLikeOrClassStaticBlockDeclaration);
}
/** @internal */
export function getContainingClassExcludingClassDecorators(node: Node): ClassLikeDeclaration | undefined {
const decorator = findAncestor(node.parent, n => isClassLike(n) ? "quit" : isDecorator(n));
return decorator && isClassLike(decorator.parent) ? getContainingClass(decorator.parent) : getContainingClass(decorator ?? node);
}
/** @internal */
export type ThisContainer =
| FunctionDeclaration
@@ -0,0 +1,33 @@
esDecorators-privateFieldAccess.ts(3,13): error TS18016: Private identifiers are not allowed outside class bodies.
esDecorators-privateFieldAccess.ts(11,18): error TS18013: Property '#foo' is not accessible outside class 'B' because it has a private identifier.
==== esDecorators-privateFieldAccess.ts (2 errors) ====
declare let dec: any;
@dec(x => x.#foo) // error
~~~~
!!! error TS18016: Private identifiers are not allowed outside class bodies.
class A {
#foo = 3;
@dec(this, (x: A) => x.#foo) // ok
m() {}
}
@dec((x: B) => x.#foo) // error
~~~~
!!! error TS18013: Property '#foo' is not accessible outside class 'B' because it has a private identifier.
class B {
#foo = 3;
}
class C {
#foo = 2;
m() {
@dec(() => this.#foo) // ok
class D {}
return D;
}
}
@@ -0,0 +1,48 @@
//// [tests/cases/conformance/esDecorators/esDecorators-privateFieldAccess.ts] ////
//// [esDecorators-privateFieldAccess.ts]
declare let dec: any;
@dec(x => x.#foo) // error
class A {
#foo = 3;
@dec(this, (x: A) => x.#foo) // ok
m() {}
}
@dec((x: B) => x.#foo) // error
class B {
#foo = 3;
}
class C {
#foo = 2;
m() {
@dec(() => this.#foo) // ok
class D {}
return D;
}
}
//// [esDecorators-privateFieldAccess.js]
@dec(x => x.#foo) // error
class A {
#foo = 3;
@dec(this, (x) => x.#foo) // ok
m() { }
}
@dec((x) => x.#foo) // error
class B {
#foo = 3;
}
class C {
#foo = 2;
m() {
@dec(() => this.#foo) // ok
class D {
}
return D;
}
}
@@ -0,0 +1,64 @@
//// [tests/cases/conformance/esDecorators/esDecorators-privateFieldAccess.ts] ////
=== esDecorators-privateFieldAccess.ts ===
declare let dec: any;
>dec : Symbol(dec, Decl(esDecorators-privateFieldAccess.ts, 0, 11))
@dec(x => x.#foo) // error
>dec : Symbol(dec, Decl(esDecorators-privateFieldAccess.ts, 0, 11))
>x : Symbol(x, Decl(esDecorators-privateFieldAccess.ts, 2, 5))
>x : Symbol(x, Decl(esDecorators-privateFieldAccess.ts, 2, 5))
class A {
>A : Symbol(A, Decl(esDecorators-privateFieldAccess.ts, 0, 21))
#foo = 3;
>#foo : Symbol(A.#foo, Decl(esDecorators-privateFieldAccess.ts, 3, 9))
@dec(this, (x: A) => x.#foo) // ok
>dec : Symbol(dec, Decl(esDecorators-privateFieldAccess.ts, 0, 11))
>this : Symbol(globalThis)
>x : Symbol(x, Decl(esDecorators-privateFieldAccess.ts, 6, 16))
>A : Symbol(A, Decl(esDecorators-privateFieldAccess.ts, 0, 21))
>x.#foo : Symbol(A.#foo, Decl(esDecorators-privateFieldAccess.ts, 3, 9))
>x : Symbol(x, Decl(esDecorators-privateFieldAccess.ts, 6, 16))
m() {}
>m : Symbol(A.m, Decl(esDecorators-privateFieldAccess.ts, 4, 13))
}
@dec((x: B) => x.#foo) // error
>dec : Symbol(dec, Decl(esDecorators-privateFieldAccess.ts, 0, 11))
>x : Symbol(x, Decl(esDecorators-privateFieldAccess.ts, 10, 6))
>B : Symbol(B, Decl(esDecorators-privateFieldAccess.ts, 8, 1))
>x : Symbol(x, Decl(esDecorators-privateFieldAccess.ts, 10, 6))
class B {
>B : Symbol(B, Decl(esDecorators-privateFieldAccess.ts, 8, 1))
#foo = 3;
>#foo : Symbol(B.#foo, Decl(esDecorators-privateFieldAccess.ts, 11, 9))
}
class C {
>C : Symbol(C, Decl(esDecorators-privateFieldAccess.ts, 13, 1))
#foo = 2;
>#foo : Symbol(C.#foo, Decl(esDecorators-privateFieldAccess.ts, 15, 9))
m() {
>m : Symbol(C.m, Decl(esDecorators-privateFieldAccess.ts, 16, 13))
@dec(() => this.#foo) // ok
>dec : Symbol(dec, Decl(esDecorators-privateFieldAccess.ts, 0, 11))
>this.#foo : Symbol(C.#foo, Decl(esDecorators-privateFieldAccess.ts, 15, 9))
>this : Symbol(C, Decl(esDecorators-privateFieldAccess.ts, 13, 1))
class D {}
>D : Symbol(D, Decl(esDecorators-privateFieldAccess.ts, 17, 9))
return D;
>D : Symbol(D, Decl(esDecorators-privateFieldAccess.ts, 17, 9))
}
}
@@ -0,0 +1,75 @@
//// [tests/cases/conformance/esDecorators/esDecorators-privateFieldAccess.ts] ////
=== esDecorators-privateFieldAccess.ts ===
declare let dec: any;
>dec : any
@dec(x => x.#foo) // error
>dec(x => x.#foo) : any
>dec : any
>x => x.#foo : (x: any) => any
>x : any
>x.#foo : any
>x : any
class A {
>A : A
#foo = 3;
>#foo : number
>3 : 3
@dec(this, (x: A) => x.#foo) // ok
>dec(this, (x: A) => x.#foo) : any
>dec : any
>this : typeof globalThis
>(x: A) => x.#foo : (x: A) => number
>x : A
>x.#foo : number
>x : A
m() {}
>m : () => void
}
@dec((x: B) => x.#foo) // error
>dec((x: B) => x.#foo) : any
>dec : any
>(x: B) => x.#foo : (x: B) => any
>x : B
>x.#foo : any
>x : B
class B {
>B : B
#foo = 3;
>#foo : number
>3 : 3
}
class C {
>C : C
#foo = 2;
>#foo : number
>2 : 2
m() {
>m : () => typeof D
@dec(() => this.#foo) // ok
>dec(() => this.#foo) : any
>dec : any
>() => this.#foo : () => number
>this.#foo : number
>this : this
class D {}
>D : D
return D;
>D : typeof D
}
}
@@ -0,0 +1,14 @@
plainJSGrammarErrors4.js(5,14): error TS1111: Private field '#b' must be declared in an enclosing class.
==== plainJSGrammarErrors4.js (1 errors) ====
class A {
#a;
m() {
this.#a; // ok
this.#b; // error
~~
!!! error TS1111: Private field '#b' must be declared in an enclosing class.
}
}
@@ -0,0 +1,20 @@
//// [tests/cases/conformance/salsa/plainJSGrammarErrors4.ts] ////
//// [plainJSGrammarErrors4.js]
class A {
#a;
m() {
this.#a; // ok
this.#b; // error
}
}
//// [plainJSGrammarErrors4.js]
class A {
#a;
m() {
this.#a; // ok
this.#b; // error
}
}
@@ -0,0 +1,21 @@
//// [tests/cases/conformance/salsa/plainJSGrammarErrors4.ts] ////
=== plainJSGrammarErrors4.js ===
class A {
>A : Symbol(A, Decl(plainJSGrammarErrors4.js, 0, 0))
#a;
>#a : Symbol(A.#a, Decl(plainJSGrammarErrors4.js, 0, 9))
m() {
>m : Symbol(A.m, Decl(plainJSGrammarErrors4.js, 1, 7))
this.#a; // ok
>this.#a : Symbol(A.#a, Decl(plainJSGrammarErrors4.js, 0, 9))
>this : Symbol(A, Decl(plainJSGrammarErrors4.js, 0, 0))
this.#b; // error
>this : Symbol(A, Decl(plainJSGrammarErrors4.js, 0, 0))
}
}
@@ -0,0 +1,22 @@
//// [tests/cases/conformance/salsa/plainJSGrammarErrors4.ts] ////
=== plainJSGrammarErrors4.js ===
class A {
>A : A
#a;
>#a : any
m() {
>m : () => void
this.#a; // ok
>this.#a : any
>this : this
this.#b; // error
>this.#b : any
>this : this
}
}
@@ -0,0 +1,26 @@
// @target: esnext
// @noEmitHelpers: true
declare let dec: any;
@dec(x => x.#foo) // error
class A {
#foo = 3;
@dec(this, (x: A) => x.#foo) // ok
m() {}
}
@dec((x: B) => x.#foo) // error
class B {
#foo = 3;
}
class C {
#foo = 2;
m() {
@dec(() => this.#foo) // ok
class D {}
return D;
}
}
@@ -0,0 +1,12 @@
// @outdir: out/
// @target: esnext
// @module: esnext
// @allowJs: true
// @filename: plainJSGrammarErrors4.js
class A {
#a;
m() {
this.#a; // ok
this.#b; // error
}
}