Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 21 additions & 11 deletions internal/checker/checker.go
Original file line number Diff line number Diff line change
Expand Up @@ -10531,9 +10531,7 @@ func (c *Checker) needCollisionCheckForIdentifier(node *ast.Node, identifier *as

func (c *Checker) setNodeLinksForPrivateIdentifierScope(node *ast.Node) {
if name := node.Name(); ast.IsPrivateIdentifier(name) {
if c.languageVersion < LanguageFeatureMinimumTarget.PrivateNamesAndClassStaticBlocks ||
c.languageVersion < LanguageFeatureMinimumTarget.ClassAndClassElementDecorators ||
!c.compilerOptions.GetUseDefineForClassFields() {
if c.languageVersion < LanguageFeatureMinimumTarget.PrivateNamesAndClassStaticBlocks {
for lexicalScope := ast.GetEnclosingBlockScopeContainer(node); lexicalScope != nil; lexicalScope = ast.GetEnclosingBlockScopeContainer(lexicalScope) {
c.nodeLinks.Get(lexicalScope).flags |= NodeCheckFlagsContainsClassWithPrivateIdentifiers
}
Expand Down Expand Up @@ -11271,17 +11269,15 @@ func (c *Checker) checkPropertyAccessExpressionOrQualifiedName(node *ast.Node, l
isAnyLike := IsTypeAny(apparentType) || apparentType == c.silentNeverType
var prop *ast.Symbol
if ast.IsPrivateIdentifier(right) {
if c.languageVersion < LanguageFeatureMinimumTarget.PrivateNamesAndClassStaticBlocks ||
c.languageVersion < LanguageFeatureMinimumTarget.ClassAndClassElementDecorators ||
!c.compilerOptions.GetUseDefineForClassFields() {
lexicallyScopedSymbol := c.lookupSymbolForPrivateIdentifierDeclaration(right.Text(), right)
if c.languageVersion < LanguageFeatureMinimumTarget.PrivateNamesAndClassStaticBlocks || c.isStaticPrivateElementOfDecoratedClass(lexicallyScopedSymbol) {
if assignmentKind != AssignmentKindNone {
c.checkExternalEmitHelpers(node, ExternalEmitHelpersClassPrivateFieldSet)
}
if assignmentKind != AssignmentKindDefinite {
c.checkExternalEmitHelpers(node, ExternalEmitHelpersClassPrivateFieldGet)
}
}
lexicallyScopedSymbol := c.lookupSymbolForPrivateIdentifierDeclaration(right.Text(), right)
if assignmentKind != AssignmentKindNone && lexicallyScopedSymbol != nil && lexicallyScopedSymbol.ValueDeclaration != nil && ast.IsMethodDeclaration(lexicallyScopedSymbol.ValueDeclaration) {
c.grammarErrorOnNode(right, diagnostics.Cannot_assign_to_private_method_0_Private_methods_are_not_writable, right.Text())
}
Expand Down Expand Up @@ -11496,6 +11492,21 @@ func (c *Checker) getPrivateIdentifierPropertyOfType(leftType *Type, lexicallySc
return c.getPropertyOfType(leftType, lexicallyScopedIdentifier.Name)
}

// isStaticPrivateElementOfDecoratedClass reports whether symbol is a static private class element
// (field, method, accessor, or auto-accessor) declared in a class whose native (non-legacy) decorators
// will actually be lowered. Decorator lowering hoists a decorated class's static private elements out
// of the class body into a wrapping closure, which forces accesses to them to use the
// classPrivateField* emit helpers even at targets that otherwise support private fields natively - see
// esDecoratorTransformer's hasStaticPrivateClassElements/shouldTransformPrivateStaticElementsInClass.
func (c *Checker) isStaticPrivateElementOfDecoratedClass(symbol *ast.Symbol) bool {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This currently doesn't actually check that the member in question is privately named (or produces a private name) - I'd expect a ast.IsPrivateIdentifierClassElementDeclaration(member) || ast.IsAutoAccessorPropertyDeclaration(member) like you see in the esdecorator transform somewhere.

if symbol == nil || symbol.ValueDeclaration == nil || c.legacyDecorators || !ast.HasStaticModifier(symbol.ValueDeclaration) {
return false
}
class := ast.GetContainingClass(symbol.ValueDeclaration)
return class != nil && ast.HasDecorators(class) &&
(c.languageVersion < LanguageFeatureMinimumTarget.ClassAndClassElementDecorators || !c.compilerOptions.GetUseDefineForClassFields())
}

func (c *Checker) checkPrivateIdentifierPropertyAccess(leftType *Type, right *ast.Node, lexicallyScopedIdentifier *ast.Symbol) bool {
// Either the identifier could not be looked up in the lexical scope OR the lexically scoped identifier did not exist on the type.
// Find a private identifier with the same description on the type.
Expand Down Expand Up @@ -13084,14 +13095,13 @@ func (c *Checker) checkInExpression(left *ast.Expression, right *ast.Expression,
return c.silentNeverType
}
if ast.IsPrivateIdentifier(left) {
if c.languageVersion < LanguageFeatureMinimumTarget.PrivateNamesAndClassStaticBlocks ||
c.languageVersion < LanguageFeatureMinimumTarget.ClassAndClassElementDecorators ||
!c.compilerOptions.GetUseDefineForClassFields() {
lexicallyScopedSymbol := c.symbolNodeLinks.Get(left).resolvedSymbol
if c.languageVersion < LanguageFeatureMinimumTarget.PrivateNamesAndClassStaticBlocks || c.isStaticPrivateElementOfDecoratedClass(lexicallyScopedSymbol) {
c.checkExternalEmitHelpers(left, ExternalEmitHelpersClassPrivateFieldIn)
}
// Unlike in 'checkPrivateIdentifierExpression' we now have access to the RHS type
// which provides us with the opportunity to emit more detailed errors
if c.symbolNodeLinks.Get(left).resolvedSymbol == nil && ast.GetContainingClass(left) != nil {
if lexicallyScopedSymbol == nil && ast.GetContainingClass(left) != nil {
isUncheckedJS := c.isUncheckedJSSuggestion(left, rightType.symbol, true /*excludeClasses*/)
c.reportNonexistentProperty(left, rightType, isUncheckedJS)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
//// [tests/cases/compiler/importHelpersNoHelpersForPrivateFieldsAtES2022.ts] ////

//// [main.ts]
export class Foo {
#field = true;
static #staticField = true;
#method() {}
static #staticMethod() {}
get #accessor() { return this.#field; }
set #accessor(v: boolean) { this.#field = v; }
accessor #autoAccessor = true;
static accessor #staticAutoAccessor = true;
static {
Foo.#staticField = true;
}
f() {
this.#field = this.#field;
this.#method();
Foo.#staticField = Foo.#staticField;
Foo.#staticMethod();
this.#accessor = this.#accessor;
this.#autoAccessor = this.#autoAccessor;
Foo.#staticAutoAccessor = Foo.#staticAutoAccessor;
#field in this;
}
}


//// [main.js]
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.Foo = void 0;
class Foo {
#field = true;
static #staticField = true;
#method() { }
static #staticMethod() { }
get #accessor() { return this.#field; }
set #accessor(v) { this.#field = v; }
#autoAccessor_accessor_storage = true;
get #autoAccessor() { return this.#autoAccessor_accessor_storage; }
set #autoAccessor(value) { this.#autoAccessor_accessor_storage = value; }
static #staticAutoAccessor_accessor_storage = true;
static get #staticAutoAccessor() { return Foo.#staticAutoAccessor_accessor_storage; }
static set #staticAutoAccessor(value) { Foo.#staticAutoAccessor_accessor_storage = value; }
static {
Foo.#staticField = true;
}
f() {
this.#field = this.#field;
this.#method();
Foo.#staticField = Foo.#staticField;
Foo.#staticMethod();
this.#accessor = this.#accessor;
this.#autoAccessor = this.#autoAccessor;
Foo.#staticAutoAccessor = Foo.#staticAutoAccessor;
#field in this;
}
}
exports.Foo = Foo;
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
//// [tests/cases/compiler/importHelpersNoHelpersForPrivateFieldsAtES2022.ts] ////

=== main.ts ===
export class Foo {
>Foo : Symbol(Foo, Decl(main.ts, 0, 0))

#field = true;
>#field : Symbol(Foo.#field, Decl(main.ts, 0, 18))

static #staticField = true;
>#staticField : Symbol(Foo.#staticField, Decl(main.ts, 1, 18))

#method() {}
>#method : Symbol(Foo.#method, Decl(main.ts, 2, 31))

static #staticMethod() {}
>#staticMethod : Symbol(Foo.#staticMethod, Decl(main.ts, 3, 16))

get #accessor() { return this.#field; }
>#accessor : Symbol(Foo.#accessor, Decl(main.ts, 4, 29), Decl(main.ts, 5, 43))
>this.#field : Symbol(Foo.#field, Decl(main.ts, 0, 18))
>this : Symbol(Foo, Decl(main.ts, 0, 0))

set #accessor(v: boolean) { this.#field = v; }
>#accessor : Symbol(Foo.#accessor, Decl(main.ts, 4, 29), Decl(main.ts, 5, 43))
>v : Symbol(v, Decl(main.ts, 6, 18))
>this.#field : Symbol(Foo.#field, Decl(main.ts, 0, 18))
>this : Symbol(Foo, Decl(main.ts, 0, 0))
>v : Symbol(v, Decl(main.ts, 6, 18))

accessor #autoAccessor = true;
>#autoAccessor : Symbol(Foo.#autoAccessor, Decl(main.ts, 6, 50))

static accessor #staticAutoAccessor = true;
>#staticAutoAccessor : Symbol(Foo.#staticAutoAccessor, Decl(main.ts, 7, 34))

static {
Foo.#staticField = true;
>Foo.#staticField : Symbol(Foo.#staticField, Decl(main.ts, 1, 18))
>Foo : Symbol(Foo, Decl(main.ts, 0, 0))
}
f() {
>f : Symbol(Foo.f, Decl(main.ts, 11, 5))

this.#field = this.#field;
>this.#field : Symbol(Foo.#field, Decl(main.ts, 0, 18))
>this : Symbol(Foo, Decl(main.ts, 0, 0))
>this.#field : Symbol(Foo.#field, Decl(main.ts, 0, 18))
>this : Symbol(Foo, Decl(main.ts, 0, 0))

this.#method();
>this.#method : Symbol(Foo.#method, Decl(main.ts, 2, 31))
>this : Symbol(Foo, Decl(main.ts, 0, 0))

Foo.#staticField = Foo.#staticField;
>Foo.#staticField : Symbol(Foo.#staticField, Decl(main.ts, 1, 18))
>Foo : Symbol(Foo, Decl(main.ts, 0, 0))
>Foo.#staticField : Symbol(Foo.#staticField, Decl(main.ts, 1, 18))
>Foo : Symbol(Foo, Decl(main.ts, 0, 0))

Foo.#staticMethod();
>Foo.#staticMethod : Symbol(Foo.#staticMethod, Decl(main.ts, 3, 16))
>Foo : Symbol(Foo, Decl(main.ts, 0, 0))

this.#accessor = this.#accessor;
>this.#accessor : Symbol(Foo.#accessor, Decl(main.ts, 4, 29), Decl(main.ts, 5, 43))
>this : Symbol(Foo, Decl(main.ts, 0, 0))
>this.#accessor : Symbol(Foo.#accessor, Decl(main.ts, 4, 29), Decl(main.ts, 5, 43))
>this : Symbol(Foo, Decl(main.ts, 0, 0))

this.#autoAccessor = this.#autoAccessor;
>this.#autoAccessor : Symbol(Foo.#autoAccessor, Decl(main.ts, 6, 50))
>this : Symbol(Foo, Decl(main.ts, 0, 0))
>this.#autoAccessor : Symbol(Foo.#autoAccessor, Decl(main.ts, 6, 50))
>this : Symbol(Foo, Decl(main.ts, 0, 0))

Foo.#staticAutoAccessor = Foo.#staticAutoAccessor;
>Foo.#staticAutoAccessor : Symbol(Foo.#staticAutoAccessor, Decl(main.ts, 7, 34))
>Foo : Symbol(Foo, Decl(main.ts, 0, 0))
>Foo.#staticAutoAccessor : Symbol(Foo.#staticAutoAccessor, Decl(main.ts, 7, 34))
>Foo : Symbol(Foo, Decl(main.ts, 0, 0))

#field in this;
>#field : Symbol(Foo.#field, Decl(main.ts, 0, 18))
>this : Symbol(Foo, Decl(main.ts, 0, 0))
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
//// [tests/cases/compiler/importHelpersNoHelpersForPrivateFieldsAtES2022.ts] ////

=== main.ts ===
export class Foo {
>Foo : Foo

#field = true;
>#field : boolean
>true : true

static #staticField = true;
>#staticField : boolean
>true : true

#method() {}
>#method : () => void

static #staticMethod() {}
>#staticMethod : () => void

get #accessor() { return this.#field; }
>#accessor : boolean
>this.#field : boolean
>this : this

set #accessor(v: boolean) { this.#field = v; }
>#accessor : boolean
>v : boolean
>this.#field = v : boolean
>this.#field : boolean
>this : this
>v : boolean

accessor #autoAccessor = true;
>#autoAccessor : boolean
>true : true

static accessor #staticAutoAccessor = true;
>#staticAutoAccessor : boolean
>true : true

static {
Foo.#staticField = true;
>Foo.#staticField = true : true
>Foo.#staticField : boolean
>Foo : typeof Foo
>true : true
}
f() {
>f : () => void

this.#field = this.#field;
>this.#field = this.#field : boolean
>this.#field : boolean
>this : this
>this.#field : boolean
>this : this

this.#method();
>this.#method() : void
>this.#method : () => void
>this : this

Foo.#staticField = Foo.#staticField;
>Foo.#staticField = Foo.#staticField : boolean
>Foo.#staticField : boolean
>Foo : typeof Foo
>Foo.#staticField : boolean
>Foo : typeof Foo

Foo.#staticMethod();
>Foo.#staticMethod() : void
>Foo.#staticMethod : () => void
>Foo : typeof Foo

this.#accessor = this.#accessor;
>this.#accessor = this.#accessor : boolean
>this.#accessor : boolean
>this : this
>this.#accessor : boolean
>this : this

this.#autoAccessor = this.#autoAccessor;
>this.#autoAccessor = this.#autoAccessor : boolean
>this.#autoAccessor : boolean
>this : this
>this.#autoAccessor : boolean
>this : this

Foo.#staticAutoAccessor = Foo.#staticAutoAccessor;
>Foo.#staticAutoAccessor = Foo.#staticAutoAccessor : boolean
>Foo.#staticAutoAccessor : boolean
>Foo : typeof Foo
>Foo.#staticAutoAccessor : boolean
>Foo : typeof Foo

#field in this;
>#field in this : boolean
>#field : any
>this : this
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
//// [tests/cases/compiler/importHelpersNoHelpersForPrivateFieldsAtES2022UseDefineForClassFieldsFalse.ts] ////

//// [main.ts]
export class Foo {
#field = true;
static #staticField = true;
#method() {}
static #staticMethod() {}
f() {
this.#field = this.#field;
this.#method();
Foo.#staticField = Foo.#staticField;
Foo.#staticMethod();
#field in this;
}
}


//// [main.js]
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.Foo = void 0;
class Foo {
#field = true;
static #staticField = true;
#method() { }
static #staticMethod() { }
f() {
this.#field = this.#field;
this.#method();
Foo.#staticField = Foo.#staticField;
Foo.#staticMethod();
#field in this;
}
}
exports.Foo = Foo;
Loading