Skip to content

Commit 9e60af8

Browse files
committed
Merge pull request #7033 from Microsoft/port6860
Port PR#6860 lexically check calling super before this
2 parents af9dc41 + 0aad5e5 commit 9e60af8

31 files changed

+741
-28
lines changed

src/compiler/checker.ts

+68-17
Original file line numberDiff line numberDiff line change
@@ -7328,17 +7328,72 @@ namespace ts {
73287328
}
73297329
}
73307330

7331+
function findFirstSuperCall(n: Node): Node {
7332+
if (isSuperCallExpression(n)) {
7333+
return n;
7334+
}
7335+
else if (isFunctionLike(n)) {
7336+
return undefined;
7337+
}
7338+
return forEachChild(n, findFirstSuperCall);
7339+
}
7340+
7341+
/**
7342+
* Return a cached result if super-statement is already found.
7343+
* Otherwise, find a super statement in a given constructor function and cache the result in the node-links of the constructor
7344+
*
7345+
* @param constructor constructor-function to look for super statement
7346+
*/
7347+
function getSuperCallInConstructor(constructor: ConstructorDeclaration): ExpressionStatement {
7348+
const links = getNodeLinks(constructor);
7349+
7350+
// Only trying to find super-call if we haven't yet tried to find one. Once we try, we will record the result
7351+
if (links.hasSuperCall === undefined) {
7352+
links.superCall = <ExpressionStatement>findFirstSuperCall(constructor.body);
7353+
links.hasSuperCall = links.superCall ? true : false;
7354+
}
7355+
return links.superCall;
7356+
}
7357+
7358+
/**
7359+
* Check if the given class-declaration extends null then return true.
7360+
* Otherwise, return false
7361+
* @param classDecl a class declaration to check if it extends null
7362+
*/
7363+
function classDeclarationExtendsNull(classDecl: ClassDeclaration): boolean {
7364+
const classSymbol = getSymbolOfNode(classDecl);
7365+
const classInstanceType = <InterfaceType>getDeclaredTypeOfSymbol(classSymbol);
7366+
const baseConstructorType = getBaseConstructorTypeOfClass(classInstanceType);
7367+
7368+
return baseConstructorType === nullType;
7369+
}
7370+
73317371
function checkThisExpression(node: Node): Type {
73327372
// Stop at the first arrow function so that we can
73337373
// tell whether 'this' needs to be captured.
73347374
let container = getThisContainer(node, /* includeArrowFunctions */ true);
73357375
let needToCaptureLexicalThis = false;
73367376

73377377
if (container.kind === SyntaxKind.Constructor) {
7338-
const baseTypeNode = getClassExtendsHeritageClauseElement(<ClassLikeDeclaration>container.parent);
7339-
if (baseTypeNode && !(getNodeCheckFlags(container) & NodeCheckFlags.HasSeenSuperCall)) {
7340-
// In ES6, super inside constructor of class-declaration has to precede "this" accessing
7341-
error(node, Diagnostics.super_must_be_called_before_accessing_this_in_the_constructor_of_a_derived_class);
7378+
const containingClassDecl = <ClassDeclaration>container.parent;
7379+
const baseTypeNode = getClassExtendsHeritageClauseElement(containingClassDecl);
7380+
7381+
// If a containing class does not have extends clause or the class extends null
7382+
// skip checking whether super statement is called before "this" accessing.
7383+
if (baseTypeNode && !classDeclarationExtendsNull(containingClassDecl)) {
7384+
const superCall = getSuperCallInConstructor(<ConstructorDeclaration>container);
7385+
7386+
// We should give an error in the following cases:
7387+
// - No super-call
7388+
// - "this" is accessing before super-call.
7389+
// i.e super(this)
7390+
// this.x; super();
7391+
// We want to make sure that super-call is done before accessing "this" so that
7392+
// "this" is not accessed as a parameter of the super-call.
7393+
if (!superCall || superCall.end > node.pos) {
7394+
// In ES6, super inside constructor of class-declaration has to precede "this" accessing
7395+
error(node, Diagnostics.super_must_be_called_before_accessing_this_in_the_constructor_of_a_derived_class);
7396+
}
73427397
}
73437398
}
73447399

@@ -10252,14 +10307,11 @@ namespace ts {
1025210307
checkGrammarTypeArguments(node, node.typeArguments) || checkGrammarArguments(node, node.arguments);
1025310308

1025410309
const signature = getResolvedSignature(node);
10255-
if (node.expression.kind === SyntaxKind.SuperKeyword) {
10256-
const containingFunction = getContainingFunction(node.expression);
1025710310

10258-
if (containingFunction && containingFunction.kind === SyntaxKind.Constructor) {
10259-
getNodeLinks(containingFunction).flags |= NodeCheckFlags.HasSeenSuperCall;
10260-
}
10311+
if (node.expression.kind === SyntaxKind.SuperKeyword) {
1026110312
return voidType;
1026210313
}
10314+
1026310315
if (node.kind === SyntaxKind.NewExpression) {
1026410316
const declaration = signature.declaration;
1026510317

@@ -11844,13 +11896,11 @@ namespace ts {
1184411896
// constructors of derived classes must contain at least one super call somewhere in their function body.
1184511897
const containingClassDecl = <ClassDeclaration>node.parent;
1184611898
if (getClassExtendsHeritageClauseElement(containingClassDecl)) {
11847-
const containingClassSymbol = getSymbolOfNode(containingClassDecl);
11848-
const containingClassInstanceType = <InterfaceType>getDeclaredTypeOfSymbol(containingClassSymbol);
11849-
const baseConstructorType = getBaseConstructorTypeOfClass(containingClassInstanceType);
11850-
11851-
if (containsSuperCall(node.body)) {
11852-
if (baseConstructorType === nullType) {
11853-
error(node, Diagnostics.A_constructor_cannot_contain_a_super_call_when_its_class_extends_null);
11899+
const classExtendsNull = classDeclarationExtendsNull(containingClassDecl);
11900+
const superCall = getSuperCallInConstructor(node);
11901+
if (superCall) {
11902+
if (classExtendsNull) {
11903+
error(superCall, Diagnostics.A_constructor_cannot_contain_a_super_call_when_its_class_extends_null);
1185411904
}
1185511905

1185611906
// The first statement in the body of a constructor (excluding prologue directives) must be a super call
@@ -11867,6 +11917,7 @@ namespace ts {
1186711917
if (superCallShouldBeFirst) {
1186811918
const statements = (<Block>node.body).statements;
1186911919
let superCallStatement: ExpressionStatement;
11920+
1187011921
for (const statement of statements) {
1187111922
if (statement.kind === SyntaxKind.ExpressionStatement && isSuperCallExpression((<ExpressionStatement>statement).expression)) {
1187211923
superCallStatement = <ExpressionStatement>statement;
@@ -11881,7 +11932,7 @@ namespace ts {
1188111932
}
1188211933
}
1188311934
}
11884-
else if (baseConstructorType !== nullType) {
11935+
else if (!classExtendsNull) {
1188511936
error(node, Diagnostics.Constructors_for_derived_classes_must_contain_a_super_call);
1188611937
}
1188711938
}

src/compiler/types.ts

+5-5
Original file line numberDiff line numberDiff line change
@@ -426,7 +426,6 @@ namespace ts {
426426
IntrinsicElement = IntrinsicNamedElement | IntrinsicIndexedElement,
427427
}
428428

429-
430429
/* @internal */
431430
export const enum RelationComparisonResult {
432431
Succeeded = 1, // Should be truthy
@@ -2037,10 +2036,9 @@ namespace ts {
20372036
LoopWithCapturedBlockScopedBinding = 0x00010000, // Loop that contains block scoped variable captured in closure
20382037
CapturedBlockScopedBinding = 0x00020000, // Block-scoped binding that is captured in some function
20392038
BlockScopedBindingInLoop = 0x00040000, // Block-scoped binding with declaration nested inside iteration statement
2040-
HasSeenSuperCall = 0x00080000, // Set during the binding when encounter 'super'
2041-
ClassWithBodyScopedClassBinding = 0x00100000, // Decorated class that contains a binding to itself inside of the class body.
2042-
BodyScopedClassBinding = 0x00200000, // Binding to a decorated class inside of the class's body.
2043-
NeedsLoopOutParameter = 0x00400000, // Block scoped binding whose value should be explicitly copied outside of the converted loop
2039+
ClassWithBodyScopedClassBinding = 0x00080000, // Decorated class that contains a binding to itself inside of the class body.
2040+
BodyScopedClassBinding = 0x00100000, // Binding to a decorated class inside of the class's body.
2041+
NeedsLoopOutParameter = 0x00200000, // Block scoped binding whose value should be explicitly copied outside of the converted loop
20442042
}
20452043

20462044
/* @internal */
@@ -2060,6 +2058,8 @@ namespace ts {
20602058
importOnRightSide?: Symbol; // for import declarations - import that appear on the right side
20612059
jsxFlags?: JsxFlags; // flags for knowing what kind of element/attributes we're dealing with
20622060
resolvedJsxType?: Type; // resolved element attributes type of a JSX openinglike element
2061+
hasSuperCall?: boolean; // recorded result when we try to find super-call. We only try to find one if this flag is undefined, indicating that we haven't made an attempt.
2062+
superCall?: ExpressionStatement; // Cached first super-call found in the constructor. Used in checking whether super is called before this-accessing
20632063
}
20642064

20652065
export const enum TypeFlags {

tests/baselines/reference/classExtendsNull.errors.txt

+3-6
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,14 @@
1-
tests/cases/compiler/classExtendsNull.ts(2,5): error TS17005: A constructor cannot contain a 'super' call when its class extends 'null'
1+
tests/cases/compiler/classExtendsNull.ts(3,9): error TS17005: A constructor cannot contain a 'super' call when its class extends 'null'
22

33

44
==== tests/cases/compiler/classExtendsNull.ts (1 errors) ====
55
class C extends null {
66
constructor() {
7-
~~~~~~~~~~~~~~~
87
super();
9-
~~~~~~~~~~~~~~~~
8+
~~~~~~~
9+
!!! error TS17005: A constructor cannot contain a 'super' call when its class extends 'null'
1010
return Object.create(null);
11-
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1211
}
13-
~~~~~
14-
!!! error TS17005: A constructor cannot contain a 'super' call when its class extends 'null'
1512
}
1613

1714
class D extends null {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
//// [superCallBeforeThisAccessing1.ts]
2+
declare var Factory: any
3+
4+
class Base {
5+
constructor(c) { }
6+
}
7+
class D extends Base {
8+
private _t;
9+
constructor() {
10+
super(i);
11+
var s = {
12+
t: this._t
13+
}
14+
var i = Factory.create(s);
15+
}
16+
}
17+
18+
19+
//// [superCallBeforeThisAccessing1.js]
20+
var __extends = (this && this.__extends) || function (d, b) {
21+
for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p];
22+
function __() { this.constructor = d; }
23+
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
24+
};
25+
var Base = (function () {
26+
function Base(c) {
27+
}
28+
return Base;
29+
}());
30+
var D = (function (_super) {
31+
__extends(D, _super);
32+
function D() {
33+
_super.call(this, i);
34+
var s = {
35+
t: this._t
36+
};
37+
var i = Factory.create(s);
38+
}
39+
return D;
40+
}(Base));
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
=== tests/cases/conformance/es6/classDeclaration/superCallBeforeThisAccessing1.ts ===
2+
declare var Factory: any
3+
>Factory : Symbol(Factory, Decl(superCallBeforeThisAccessing1.ts, 0, 11))
4+
5+
class Base {
6+
>Base : Symbol(Base, Decl(superCallBeforeThisAccessing1.ts, 0, 24))
7+
8+
constructor(c) { }
9+
>c : Symbol(c, Decl(superCallBeforeThisAccessing1.ts, 3, 16))
10+
}
11+
class D extends Base {
12+
>D : Symbol(D, Decl(superCallBeforeThisAccessing1.ts, 4, 1))
13+
>Base : Symbol(Base, Decl(superCallBeforeThisAccessing1.ts, 0, 24))
14+
15+
private _t;
16+
>_t : Symbol(_t, Decl(superCallBeforeThisAccessing1.ts, 5, 22))
17+
18+
constructor() {
19+
super(i);
20+
>super : Symbol(Base, Decl(superCallBeforeThisAccessing1.ts, 0, 24))
21+
>i : Symbol(i, Decl(superCallBeforeThisAccessing1.ts, 12, 11))
22+
23+
var s = {
24+
>s : Symbol(s, Decl(superCallBeforeThisAccessing1.ts, 9, 11))
25+
26+
t: this._t
27+
>t : Symbol(t, Decl(superCallBeforeThisAccessing1.ts, 9, 17))
28+
>this._t : Symbol(_t, Decl(superCallBeforeThisAccessing1.ts, 5, 22))
29+
>this : Symbol(D, Decl(superCallBeforeThisAccessing1.ts, 4, 1))
30+
>_t : Symbol(_t, Decl(superCallBeforeThisAccessing1.ts, 5, 22))
31+
}
32+
var i = Factory.create(s);
33+
>i : Symbol(i, Decl(superCallBeforeThisAccessing1.ts, 12, 11))
34+
>Factory : Symbol(Factory, Decl(superCallBeforeThisAccessing1.ts, 0, 11))
35+
>s : Symbol(s, Decl(superCallBeforeThisAccessing1.ts, 9, 11))
36+
}
37+
}
38+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
=== tests/cases/conformance/es6/classDeclaration/superCallBeforeThisAccessing1.ts ===
2+
declare var Factory: any
3+
>Factory : any
4+
5+
class Base {
6+
>Base : Base
7+
8+
constructor(c) { }
9+
>c : any
10+
}
11+
class D extends Base {
12+
>D : D
13+
>Base : Base
14+
15+
private _t;
16+
>_t : any
17+
18+
constructor() {
19+
super(i);
20+
>super(i) : void
21+
>super : typeof Base
22+
>i : any
23+
24+
var s = {
25+
>s : { t: any; }
26+
>{ t: this._t } : { t: any; }
27+
28+
t: this._t
29+
>t : any
30+
>this._t : any
31+
>this : this
32+
>_t : any
33+
}
34+
var i = Factory.create(s);
35+
>i : any
36+
>Factory.create(s) : any
37+
>Factory.create : any
38+
>Factory : any
39+
>create : any
40+
>s : { t: any; }
41+
}
42+
}
43+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
//// [superCallBeforeThisAccessing2.ts]
2+
class Base {
3+
constructor(c) { }
4+
}
5+
class D extends Base {
6+
private _t;
7+
constructor() {
8+
super(() => { this._t }); // no error. only check when this is directly accessing in constructor
9+
}
10+
}
11+
12+
13+
//// [superCallBeforeThisAccessing2.js]
14+
var __extends = (this && this.__extends) || function (d, b) {
15+
for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p];
16+
function __() { this.constructor = d; }
17+
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
18+
};
19+
var Base = (function () {
20+
function Base(c) {
21+
}
22+
return Base;
23+
}());
24+
var D = (function (_super) {
25+
__extends(D, _super);
26+
function D() {
27+
var _this = this;
28+
_super.call(this, function () { _this._t; }); // no error. only check when this is directly accessing in constructor
29+
}
30+
return D;
31+
}(Base));
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
=== tests/cases/conformance/es6/classDeclaration/superCallBeforeThisAccessing2.ts ===
2+
class Base {
3+
>Base : Symbol(Base, Decl(superCallBeforeThisAccessing2.ts, 0, 0))
4+
5+
constructor(c) { }
6+
>c : Symbol(c, Decl(superCallBeforeThisAccessing2.ts, 1, 16))
7+
}
8+
class D extends Base {
9+
>D : Symbol(D, Decl(superCallBeforeThisAccessing2.ts, 2, 1))
10+
>Base : Symbol(Base, Decl(superCallBeforeThisAccessing2.ts, 0, 0))
11+
12+
private _t;
13+
>_t : Symbol(_t, Decl(superCallBeforeThisAccessing2.ts, 3, 22))
14+
15+
constructor() {
16+
super(() => { this._t }); // no error. only check when this is directly accessing in constructor
17+
>super : Symbol(Base, Decl(superCallBeforeThisAccessing2.ts, 0, 0))
18+
>this._t : Symbol(_t, Decl(superCallBeforeThisAccessing2.ts, 3, 22))
19+
>this : Symbol(D, Decl(superCallBeforeThisAccessing2.ts, 2, 1))
20+
>_t : Symbol(_t, Decl(superCallBeforeThisAccessing2.ts, 3, 22))
21+
}
22+
}
23+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
=== tests/cases/conformance/es6/classDeclaration/superCallBeforeThisAccessing2.ts ===
2+
class Base {
3+
>Base : Base
4+
5+
constructor(c) { }
6+
>c : any
7+
}
8+
class D extends Base {
9+
>D : D
10+
>Base : Base
11+
12+
private _t;
13+
>_t : any
14+
15+
constructor() {
16+
super(() => { this._t }); // no error. only check when this is directly accessing in constructor
17+
>super(() => { this._t }) : void
18+
>super : typeof Base
19+
>() => { this._t } : () => void
20+
>this._t : any
21+
>this : this
22+
>_t : any
23+
}
24+
}
25+

0 commit comments

Comments
 (0)