Skip to content

Commit

Permalink
fix: class field is not accessible via super
Browse files Browse the repository at this point in the history
  • Loading branch information
Jack-Works committed Jul 15, 2023
1 parent 2b4bf5a commit 4b820cb
Show file tree
Hide file tree
Showing 13 changed files with 303 additions and 1 deletion.
12 changes: 12 additions & 0 deletions src/compiler/checker.ts
Expand Up @@ -475,6 +475,7 @@ import {
isClassDeclaration,
isClassElement,
isClassExpression,
isClassFieldAndNotAutoAccessor,
isClassLike,
isClassStaticBlockDeclaration,
isCommaSequence,
Expand Down Expand Up @@ -31182,6 +31183,17 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
}
return false;
}
// A class field cannot be accessed via super.* from a derived class.
// This is true for both [[Set]] (old) and [[Define]] (ES spec) semantics.
if (!(flags & ModifierFlags.Static) && prop.declarations?.some(isClassFieldAndNotAutoAccessor)) {
if (errorNode) {
error(errorNode,
Diagnostics.Class_field_0_defined_by_the_parent_class_is_not_accessible_in_the_child_class_via_super,
symbolToString(prop)
);
}
return false;
}
}

// Referencing abstract properties within their own constructors is not allowed
Expand Down
4 changes: 4 additions & 0 deletions src/compiler/diagnosticMessages.json
Expand Up @@ -3659,6 +3659,10 @@
"category": "Error",
"code": 2854
},
"Class field '{0}' defined by the parent class is not accessible in the child class via super.": {
"category": "Error",
"code": 2855
},

"Import declaration '{0}' is using private name '{1}'.": {
"category": "Error",
Expand Down
5 changes: 5 additions & 0 deletions src/compiler/utilitiesPublic.ts
Expand Up @@ -1704,6 +1704,11 @@ export function isAutoAccessorPropertyDeclaration(node: Node): node is AutoAcces
return isPropertyDeclaration(node) && hasAccessorModifier(node);
}

/** @internal */
export function isClassFieldAndNotAutoAccessor(node: Node): boolean {
return node.parent && isClassLike(node.parent) && isPropertyDeclaration(node) && !hasAccessorModifier(node);
}

/** @internal */
export function isMethodOrAccessor(node: Node): node is MethodDeclaration | AccessorDeclaration {
switch (node.kind) {
Expand Down
@@ -1,19 +1,24 @@
checkSuperCallBeforeThisAccess.ts(7,18): error TS17009: 'super' must be called before accessing 'this' in the constructor of a derived class.
checkSuperCallBeforeThisAccess.ts(8,18): error TS17009: 'super' must be called before accessing 'this' in the constructor of a derived class.
checkSuperCallBeforeThisAccess.ts(9,18): error TS17011: 'super' must be called before accessing a property of 'super' in the constructor of a derived class.
checkSuperCallBeforeThisAccess.ts(9,24): error TS2855: Class field 'x' defined by the parent class is not accessible in the child class via super.
checkSuperCallBeforeThisAccess.ts(12,30): error TS2855: Class field 'x' defined by the parent class is not accessible in the child class via super.
checkSuperCallBeforeThisAccess.ts(17,28): error TS2855: Class field 'x' defined by the parent class is not accessible in the child class via super.
checkSuperCallBeforeThisAccess.ts(20,22): error TS17009: 'super' must be called before accessing 'this' in the constructor of a derived class.
checkSuperCallBeforeThisAccess.ts(21,22): error TS17009: 'super' must be called before accessing 'this' in the constructor of a derived class.
checkSuperCallBeforeThisAccess.ts(22,22): error TS17011: 'super' must be called before accessing a property of 'super' in the constructor of a derived class.
checkSuperCallBeforeThisAccess.ts(22,28): error TS2855: Class field 'x' defined by the parent class is not accessible in the child class via super.
checkSuperCallBeforeThisAccess.ts(30,30): error TS17009: 'super' must be called before accessing 'this' in the constructor of a derived class.
checkSuperCallBeforeThisAccess.ts(39,22): error TS17009: 'super' must be called before accessing 'this' in the constructor of a derived class.
checkSuperCallBeforeThisAccess.ts(43,18): error TS17009: 'super' must be called before accessing 'this' in the constructor of a derived class.
checkSuperCallBeforeThisAccess.ts(44,18): error TS17009: 'super' must be called before accessing 'this' in the constructor of a derived class.
checkSuperCallBeforeThisAccess.ts(45,18): error TS17011: 'super' must be called before accessing a property of 'super' in the constructor of a derived class.
checkSuperCallBeforeThisAccess.ts(45,24): error TS2855: Class field 'x' defined by the parent class is not accessible in the child class via super.
checkSuperCallBeforeThisAccess.ts(59,27): error TS17009: 'super' must be called before accessing 'this' in the constructor of a derived class.
checkSuperCallBeforeThisAccess.ts(75,27): error TS17009: 'super' must be called before accessing 'this' in the constructor of a derived class.


==== checkSuperCallBeforeThisAccess.ts (13 errors) ====
==== checkSuperCallBeforeThisAccess.ts (18 errors) ====
class A {
x = 1;
}
Expand All @@ -29,14 +34,20 @@ checkSuperCallBeforeThisAccess.ts(75,27): error TS17009: 'super' must be called
let a3 = super.x; // Error
~~~~~
!!! error TS17011: 'super' must be called before accessing a property of 'super' in the constructor of a derived class.
~
!!! error TS2855: Class field 'x' defined by the parent class is not accessible in the child class via super.
let a4 = () => this;
let a5 = () => this.x;
let a6 = () => super.x;
~
!!! error TS2855: Class field 'x' defined by the parent class is not accessible in the child class via super.
if (!!true) {
super();
let b1 = this;
let b2 = this.x;
let b3 = super.x;
~
!!! error TS2855: Class field 'x' defined by the parent class is not accessible in the child class via super.
}
else {
let c1 = this; // Error
Expand All @@ -48,6 +59,8 @@ checkSuperCallBeforeThisAccess.ts(75,27): error TS17009: 'super' must be called
let c3 = super.x; // Error
~~~~~
!!! error TS17011: 'super' must be called before accessing a property of 'super' in the constructor of a derived class.
~
!!! error TS2855: Class field 'x' defined by the parent class is not accessible in the child class via super.
}
if (!!true) {
switch (n) {
Expand Down Expand Up @@ -81,6 +94,8 @@ checkSuperCallBeforeThisAccess.ts(75,27): error TS17009: 'super' must be called
let f3 = super.x; // Error
~~~~~
!!! error TS17011: 'super' must be called before accessing a property of 'super' in the constructor of a derived class.
~
!!! error TS2855: Class field 'x' defined by the parent class is not accessible in the child class via super.
}
}

Expand Down
37 changes: 37 additions & 0 deletions tests/baselines/reference/classFieldSuperAccessible.js
@@ -0,0 +1,37 @@
//// [tests/cases/compiler/classFieldSuperAccessible.ts] ////

//// [classFieldSuperAccessible.ts]
class A extends class Expr {} {
static {
console.log(super.name);
}
}
class B extends Number {
static {
console.log(super.EPSILON);
}
}
class C extends Array {
foo() {
console.log(super.length);
}
}


//// [classFieldSuperAccessible.js]
class A extends class Expr {
} {
static {
console.log(super.name);
}
}
class B extends Number {
static {
console.log(super.EPSILON);
}
}
class C extends Array {
foo() {
console.log(super.length);
}
}
48 changes: 48 additions & 0 deletions tests/baselines/reference/classFieldSuperAccessible.symbols
@@ -0,0 +1,48 @@
//// [tests/cases/compiler/classFieldSuperAccessible.ts] ////

=== classFieldSuperAccessible.ts ===
class A extends class Expr {} {
>A : Symbol(A, Decl(classFieldSuperAccessible.ts, 0, 0))
>Expr : Symbol(Expr, Decl(classFieldSuperAccessible.ts, 0, 15))

static {
console.log(super.name);
>console.log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --))
>console : Symbol(console, Decl(lib.dom.d.ts, --, --))
>log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --))
>super.name : Symbol(Function.name, Decl(lib.es2015.core.d.ts, --, --))
>super : Symbol(Expr, Decl(classFieldSuperAccessible.ts, 0, 15))
>name : Symbol(Function.name, Decl(lib.es2015.core.d.ts, --, --))
}
}
class B extends Number {
>B : Symbol(B, Decl(classFieldSuperAccessible.ts, 4, 1))
>Number : Symbol(Number, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es2020.number.d.ts, --, --))

static {
console.log(super.EPSILON);
>console.log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --))
>console : Symbol(console, Decl(lib.dom.d.ts, --, --))
>log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --))
>super.EPSILON : Symbol(NumberConstructor.EPSILON, Decl(lib.es2015.core.d.ts, --, --))
>super : Symbol(NumberConstructor, Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.core.d.ts, --, --))
>EPSILON : Symbol(NumberConstructor.EPSILON, Decl(lib.es2015.core.d.ts, --, --))
}
}
class C extends Array {
>C : Symbol(C, Decl(classFieldSuperAccessible.ts, 9, 1))
>Array : Symbol(Array, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.core.d.ts, --, --), Decl(lib.es2015.iterable.d.ts, --, --), Decl(lib.es2015.symbol.wellknown.d.ts, --, --) ... and 4 more)

foo() {
>foo : Symbol(C.foo, Decl(classFieldSuperAccessible.ts, 10, 23))

console.log(super.length);
>console.log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --))
>console : Symbol(console, Decl(lib.dom.d.ts, --, --))
>log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --))
>super.length : Symbol(Array.length, Decl(lib.es5.d.ts, --, --))
>super : Symbol(Array, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.core.d.ts, --, --), Decl(lib.es2015.iterable.d.ts, --, --), Decl(lib.es2015.symbol.wellknown.d.ts, --, --) ... and 4 more)
>length : Symbol(Array.length, Decl(lib.es5.d.ts, --, --))
}
}

52 changes: 52 additions & 0 deletions tests/baselines/reference/classFieldSuperAccessible.types
@@ -0,0 +1,52 @@
//// [tests/cases/compiler/classFieldSuperAccessible.ts] ////

=== classFieldSuperAccessible.ts ===
class A extends class Expr {} {
>A : A
>class Expr {} : Expr
>Expr : typeof Expr

static {
console.log(super.name);
>console.log(super.name) : void
>console.log : (...data: any[]) => void
>console : Console
>log : (...data: any[]) => void
>super.name : string
>super : typeof Expr
>name : string
}
}
class B extends Number {
>B : B
>Number : Number

static {
console.log(super.EPSILON);
>console.log(super.EPSILON) : void
>console.log : (...data: any[]) => void
>console : Console
>log : (...data: any[]) => void
>super.EPSILON : number
>super : NumberConstructor
>EPSILON : number
}
}
class C extends Array {
>C : C
>Array : any[]

foo() {
>foo : () => void

console.log(super.length);
>console.log(super.length) : void
>console.log : (...data: any[]) => void
>console : Console
>log : (...data: any[]) => void
>super.length : number
>super : any[]
>length : number
}
}

17 changes: 17 additions & 0 deletions tests/baselines/reference/classFieldSuperNotAccessible.errors.txt
@@ -0,0 +1,17 @@
classFieldSuperNotAccessible.ts(6,15): error TS2855: Class field 'field' defined by the parent class is not accessible in the child class via super.


==== classFieldSuperNotAccessible.ts (1 errors) ====
class T {
field = () => {}
}
class T2 extends T {
f() {
super.field() // error
~~~~~
!!! error TS2855: Class field 'field' defined by the parent class is not accessible in the child class via super.
}
}

new T2().f()

25 changes: 25 additions & 0 deletions tests/baselines/reference/classFieldSuperNotAccessible.js
@@ -0,0 +1,25 @@
//// [tests/cases/compiler/classFieldSuperNotAccessible.ts] ////

//// [classFieldSuperNotAccessible.ts]
class T {
field = () => {}
}
class T2 extends T {
f() {
super.field() // error
}
}

new T2().f()


//// [classFieldSuperNotAccessible.js]
class T {
field = () => { };
}
class T2 extends T {
f() {
super.field(); // error
}
}
new T2().f();
28 changes: 28 additions & 0 deletions tests/baselines/reference/classFieldSuperNotAccessible.symbols
@@ -0,0 +1,28 @@
//// [tests/cases/compiler/classFieldSuperNotAccessible.ts] ////

=== classFieldSuperNotAccessible.ts ===
class T {
>T : Symbol(T, Decl(classFieldSuperNotAccessible.ts, 0, 0))

field = () => {}
>field : Symbol(T.field, Decl(classFieldSuperNotAccessible.ts, 0, 9))
}
class T2 extends T {
>T2 : Symbol(T2, Decl(classFieldSuperNotAccessible.ts, 2, 1))
>T : Symbol(T, Decl(classFieldSuperNotAccessible.ts, 0, 0))

f() {
>f : Symbol(T2.f, Decl(classFieldSuperNotAccessible.ts, 3, 20))

super.field() // error
>super.field : Symbol(T.field, Decl(classFieldSuperNotAccessible.ts, 0, 9))
>super : Symbol(T, Decl(classFieldSuperNotAccessible.ts, 0, 0))
>field : Symbol(T.field, Decl(classFieldSuperNotAccessible.ts, 0, 9))
}
}

new T2().f()
>new T2().f : Symbol(T2.f, Decl(classFieldSuperNotAccessible.ts, 3, 20))
>T2 : Symbol(T2, Decl(classFieldSuperNotAccessible.ts, 2, 1))
>f : Symbol(T2.f, Decl(classFieldSuperNotAccessible.ts, 3, 20))

32 changes: 32 additions & 0 deletions tests/baselines/reference/classFieldSuperNotAccessible.types
@@ -0,0 +1,32 @@
//// [tests/cases/compiler/classFieldSuperNotAccessible.ts] ////

=== classFieldSuperNotAccessible.ts ===
class T {
>T : T

field = () => {}
>field : () => void
>() => {} : () => void
}
class T2 extends T {
>T2 : T2
>T : T

f() {
>f : () => void

super.field() // error
>super.field() : void
>super.field : () => void
>super : T
>field : () => void
}
}

new T2().f()
>new T2().f() : void
>new T2().f : () => void
>new T2() : T2
>T2 : typeof T2
>f : () => void

16 changes: 16 additions & 0 deletions tests/cases/compiler/classFieldSuperAccessible.ts
@@ -0,0 +1,16 @@
// @target: esnext
class A extends class Expr {} {
static {
console.log(super.name);
}
}
class B extends Number {
static {
console.log(super.EPSILON);
}
}
class C extends Array {
foo() {
console.log(super.length);
}
}
11 changes: 11 additions & 0 deletions tests/cases/compiler/classFieldSuperNotAccessible.ts
@@ -0,0 +1,11 @@
// @target: esnext
class T {
field = () => {}
}
class T2 extends T {
f() {
super.field() // error
}
}

new T2().f()

0 comments on commit 4b820cb

Please sign in to comment.