Skip to content

Commit 2d4bc0c

Browse files
committed
Merge pull request #6860 from Microsoft/checksuperbeforethislexically
Check using "super" before "this" lexically
2 parents 1256352 + 61e954b commit 2d4bc0c

31 files changed

+741
-45
lines changed

src/compiler/checker.ts

Lines changed: 68 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -7306,17 +7306,72 @@ namespace ts {
73067306
}
73077307
}
73087308

7309+
function findFirstSuperCall(n: Node): Node {
7310+
if (isSuperCallExpression(n)) {
7311+
return n;
7312+
}
7313+
else if (isFunctionLike(n)) {
7314+
return undefined;
7315+
}
7316+
return forEachChild(n, findFirstSuperCall);
7317+
}
7318+
7319+
/**
7320+
* Return a cached result if super-statement is already found.
7321+
* Otherwise, find a super statement in a given constructor function and cache the result in the node-links of the constructor
7322+
*
7323+
* @param constructor constructor-function to look for super statement
7324+
*/
7325+
function getSuperCallInConstructor(constructor: ConstructorDeclaration): ExpressionStatement {
7326+
const links = getNodeLinks(constructor);
7327+
7328+
// Only trying to find super-call if we haven't yet tried to find one. Once we try, we will record the result
7329+
if (links.hasSuperCall === undefined) {
7330+
links.superCall = <ExpressionStatement>findFirstSuperCall(constructor.body);
7331+
links.hasSuperCall = links.superCall ? true : false;
7332+
}
7333+
return links.superCall;
7334+
}
7335+
7336+
/**
7337+
* Check if the given class-declaration extends null then return true.
7338+
* Otherwise, return false
7339+
* @param classDecl a class declaration to check if it extends null
7340+
*/
7341+
function classDeclarationExtendsNull(classDecl: ClassDeclaration): boolean {
7342+
const classSymbol = getSymbolOfNode(classDecl);
7343+
const classInstanceType = <InterfaceType>getDeclaredTypeOfSymbol(classSymbol);
7344+
const baseConstructorType = getBaseConstructorTypeOfClass(classInstanceType);
7345+
7346+
return baseConstructorType === nullType;
7347+
}
7348+
73097349
function checkThisExpression(node: Node): Type {
73107350
// Stop at the first arrow function so that we can
73117351
// tell whether 'this' needs to be captured.
73127352
let container = getThisContainer(node, /* includeArrowFunctions */ true);
73137353
let needToCaptureLexicalThis = false;
73147354

73157355
if (container.kind === SyntaxKind.Constructor) {
7316-
const baseTypeNode = getClassExtendsHeritageClauseElement(<ClassLikeDeclaration>container.parent);
7317-
if (baseTypeNode && !(getNodeCheckFlags(container) & NodeCheckFlags.HasSeenSuperCall)) {
7318-
// In ES6, super inside constructor of class-declaration has to precede "this" accessing
7319-
error(node, Diagnostics.super_must_be_called_before_accessing_this_in_the_constructor_of_a_derived_class);
7356+
const containingClassDecl = <ClassDeclaration>container.parent;
7357+
const baseTypeNode = getClassExtendsHeritageClauseElement(containingClassDecl);
7358+
7359+
// If a containing class does not have extends clause or the class extends null
7360+
// skip checking whether super statement is called before "this" accessing.
7361+
if (baseTypeNode && !classDeclarationExtendsNull(containingClassDecl)) {
7362+
const superCall = getSuperCallInConstructor(<ConstructorDeclaration>container);
7363+
7364+
// We should give an error in the following cases:
7365+
// - No super-call
7366+
// - "this" is accessing before super-call.
7367+
// i.e super(this)
7368+
// this.x; super();
7369+
// We want to make sure that super-call is done before accessing "this" so that
7370+
// "this" is not accessed as a parameter of the super-call.
7371+
if (!superCall || superCall.end > node.pos) {
7372+
// In ES6, super inside constructor of class-declaration has to precede "this" accessing
7373+
error(node, Diagnostics.super_must_be_called_before_accessing_this_in_the_constructor_of_a_derived_class);
7374+
}
73207375
}
73217376
}
73227377

@@ -10263,14 +10318,11 @@ namespace ts {
1026310318
checkGrammarTypeArguments(node, node.typeArguments) || checkGrammarArguments(node, node.arguments);
1026410319

1026510320
const signature = getResolvedSignature(node);
10266-
if (node.expression.kind === SyntaxKind.SuperKeyword) {
10267-
const containgFunction = getContainingFunction(node.expression);
1026810321

10269-
if (containgFunction && containgFunction.kind === SyntaxKind.Constructor) {
10270-
getNodeLinks(containgFunction).flags |= NodeCheckFlags.HasSeenSuperCall;
10271-
}
10322+
if (node.expression.kind === SyntaxKind.SuperKeyword) {
1027210323
return voidType;
1027310324
}
10325+
1027410326
if (node.kind === SyntaxKind.NewExpression) {
1027510327
const declaration = signature.declaration;
1027610328

@@ -11813,23 +11865,6 @@ namespace ts {
1181311865
return;
1181411866
}
1181511867

11816-
function containsSuperCallAsComputedPropertyName(n: Declaration): boolean {
11817-
return n.name && containsSuperCall(n.name);
11818-
}
11819-
11820-
function containsSuperCall(n: Node): boolean {
11821-
if (isSuperCallExpression(n)) {
11822-
return true;
11823-
}
11824-
else if (isFunctionLike(n)) {
11825-
return false;
11826-
}
11827-
else if (isClassLike(n)) {
11828-
return forEach((<ClassLikeDeclaration>n).members, containsSuperCallAsComputedPropertyName);
11829-
}
11830-
return forEachChild(n, containsSuperCall);
11831-
}
11832-
1183311868
function markThisReferencesAsErrors(n: Node): void {
1183411869
if (n.kind === SyntaxKind.ThisKeyword) {
1183511870
error(n, Diagnostics.this_cannot_be_referenced_in_current_location);
@@ -11850,13 +11885,11 @@ namespace ts {
1185011885
// constructors of derived classes must contain at least one super call somewhere in their function body.
1185111886
const containingClassDecl = <ClassDeclaration>node.parent;
1185211887
if (getClassExtendsHeritageClauseElement(containingClassDecl)) {
11853-
const containingClassSymbol = getSymbolOfNode(containingClassDecl);
11854-
const containingClassInstanceType = <InterfaceType>getDeclaredTypeOfSymbol(containingClassSymbol);
11855-
const baseConstructorType = getBaseConstructorTypeOfClass(containingClassInstanceType);
11856-
11857-
if (containsSuperCall(node.body)) {
11858-
if (baseConstructorType === nullType) {
11859-
error(node, Diagnostics.A_constructor_cannot_contain_a_super_call_when_its_class_extends_null);
11888+
const classExtendsNull = classDeclarationExtendsNull(containingClassDecl);
11889+
const superCall = getSuperCallInConstructor(node);
11890+
if (superCall) {
11891+
if (classExtendsNull) {
11892+
error(superCall, Diagnostics.A_constructor_cannot_contain_a_super_call_when_its_class_extends_null);
1186011893
}
1186111894

1186211895
// The first statement in the body of a constructor (excluding prologue directives) must be a super call
@@ -11873,6 +11906,7 @@ namespace ts {
1187311906
if (superCallShouldBeFirst) {
1187411907
const statements = (<Block>node.body).statements;
1187511908
let superCallStatement: ExpressionStatement;
11909+
1187611910
for (const statement of statements) {
1187711911
if (statement.kind === SyntaxKind.ExpressionStatement && isSuperCallExpression((<ExpressionStatement>statement).expression)) {
1187811912
superCallStatement = <ExpressionStatement>statement;
@@ -11887,7 +11921,7 @@ namespace ts {
1188711921
}
1188811922
}
1188911923
}
11890-
else if (baseConstructorType !== nullType) {
11924+
else if (!classExtendsNull) {
1189111925
error(node, Diagnostics.Constructors_for_derived_classes_must_contain_a_super_call);
1189211926
}
1189311927
}

src/compiler/types.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -459,7 +459,6 @@ namespace ts {
459459
IntrinsicElement = IntrinsicNamedElement | IntrinsicIndexedElement,
460460
}
461461

462-
463462
/* @internal */
464463
export const enum RelationComparisonResult {
465464
Succeeded = 1, // Should be truthy
@@ -2068,10 +2067,9 @@ namespace ts {
20682067
LoopWithCapturedBlockScopedBinding = 0x00010000, // Loop that contains block scoped variable captured in closure
20692068
CapturedBlockScopedBinding = 0x00020000, // Block-scoped binding that is captured in some function
20702069
BlockScopedBindingInLoop = 0x00040000, // Block-scoped binding with declaration nested inside iteration statement
2071-
HasSeenSuperCall = 0x00080000, // Set during the binding when encounter 'super'
2072-
ClassWithBodyScopedClassBinding = 0x00100000, // Decorated class that contains a binding to itself inside of the class body.
2073-
BodyScopedClassBinding = 0x00200000, // Binding to a decorated class inside of the class's body.
2074-
NeedsLoopOutParameter = 0x00400000, // Block scoped binding whose value should be explicitly copied outside of the converted loop
2070+
ClassWithBodyScopedClassBinding = 0x0080000, // Decorated class that contains a binding to itself inside of the class body.
2071+
BodyScopedClassBinding = 0x00100000, // Binding to a decorated class inside of the class's body.
2072+
NeedsLoopOutParameter = 0x00200000, // Block scoped binding whose value should be explicitly copied outside of the converted loop
20752073
}
20762074

20772075
/* @internal */
@@ -2090,6 +2088,8 @@ namespace ts {
20902088
importOnRightSide?: Symbol; // for import declarations - import that appear on the right side
20912089
jsxFlags?: JsxFlags; // flags for knowning what kind of element/attributes we're dealing with
20922090
resolvedJsxType?: Type; // resolved element attributes type of a JSX openinglike element
2091+
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.
2092+
superCall?: ExpressionStatement; // Cached first super-call found in the constructor. Used in checking whether super is called before this-accessing
20932093
}
20942094

20952095
export const enum TypeFlags {

tests/baselines/reference/classExtendsNull.errors.txt

Lines changed: 3 additions & 6 deletions
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 {
Lines changed: 40 additions & 0 deletions
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));
Lines changed: 38 additions & 0 deletions
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+
Lines changed: 43 additions & 0 deletions
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+
Lines changed: 31 additions & 0 deletions
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));
Lines changed: 23 additions & 0 deletions
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+

0 commit comments

Comments
 (0)