Skip to content
Closed
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
61 changes: 47 additions & 14 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6577,6 +6577,21 @@ namespace ts {
let container = getThisContainer(node, /* includeArrowFunctions */ true);
let needToCaptureLexicalThis = false;


Copy link
Contributor

Choose a reason for hiding this comment

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

Should we special case this before super, or should we consider a more general purpose approach to TDZ that encompasses both this before super and use before declaration for let and const? I imagine there may be more cases like this in the future.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I agree with you though I think that will need to be a separate change. Here is a issue #5232

if (container.kind === SyntaxKind.Constructor) {
// Keep track of whether we have seen "super" before encounter "this" so that
// we can report appropriate error later in checkConstructorDeclaration
// We have to do the check here to make sure we won't give false error when
// "this" is used in arrow functions
// For example:
// constructor() {
// (()=>this); // No Error
// super();
// }
let nodeLinks = getNodeLinks(container);
nodeLinks.flags |= NodeCheckFlags.HasSeenThisCall;
}

// Now skip arrow functions to get the "real" owner of 'this'.
if (container.kind === SyntaxKind.ArrowFunction) {
container = getThisContainer(container, /* includeArrowFunctions */ false);
Expand Down Expand Up @@ -9285,6 +9300,14 @@ namespace ts {

let signature = getResolvedSignature(node);
if (node.expression.kind === SyntaxKind.SuperKeyword) {
let containgFunction = getContainingFunction(node.expression);

if (containgFunction && containgFunction.kind === SyntaxKind.Constructor) {
let nodeLinks = getNodeLinks(containgFunction);
if (!(nodeLinks.flags & NodeCheckFlags.HasSeenThisCall)) {
nodeLinks.flags |= NodeCheckFlags.HasSeenSuperBeforeThis;
}
}
return voidType;
}
if (node.kind === SyntaxKind.NewExpression) {
Expand Down Expand Up @@ -10794,8 +10817,25 @@ namespace ts {
let containingClassSymbol = getSymbolOfNode(containingClassDecl);
let containingClassInstanceType = <InterfaceType>getDeclaredTypeOfSymbol(containingClassSymbol);
let baseConstructorType = getBaseConstructorTypeOfClass(containingClassInstanceType);
let statements = (<Block>node.body).statements;
let superCallStatement: ExpressionStatement;
let isSuperCallFirstStatment: boolean;

for (let statement of statements) {
if (statement.kind === SyntaxKind.ExpressionStatement && isSuperCallExpression((<ExpressionStatement>statement).expression)) {
superCallStatement = <ExpressionStatement>statement;
if (isSuperCallFirstStatment === undefined) {
isSuperCallFirstStatment = true;
}
}
else if (isSuperCallFirstStatment === undefined && !isPrologueDirective(statement)) {
isSuperCallFirstStatment = false;
}
}

if (containsSuperCall(node.body)) {
// The main different between looping through each statement in constructor and calling containsSuperCall is that,
// containsSuperCall will consider "super" inside computed-property for inner class declaration
if (superCallStatement || containsSuperCall(node.body)) {
if (baseConstructorType === nullType) {
error(node, Diagnostics.A_constructor_cannot_contain_a_super_call_when_its_class_extends_null);
}
Expand All @@ -10812,25 +10852,18 @@ namespace ts {
// Skip past any prologue directives to find the first statement
// to ensure that it was a super call.
if (superCallShouldBeFirst) {
let statements = (<Block>node.body).statements;
let superCallStatement: ExpressionStatement;
for (let statement of statements) {
if (statement.kind === SyntaxKind.ExpressionStatement && isSuperCallExpression((<ExpressionStatement>statement).expression)) {
superCallStatement = <ExpressionStatement>statement;
break;
}
if (!isPrologueDirective(statement)) {
break;
}
}
if (!superCallStatement) {
error(node, Diagnostics.A_super_call_must_be_the_first_statement_in_the_constructor_when_a_class_contains_initialized_properties_or_has_parameter_properties);
if (!isSuperCallFirstStatment) {
error(superCallStatement, Diagnostics.A_super_call_must_be_the_first_statement_in_the_constructor_when_a_class_contains_initialized_properties_or_has_parameter_properties);
Copy link
Contributor

Choose a reason for hiding this comment

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

Is this our requirement? In ES6 it is legal to have statements before super as long as they do not access this. I know that our emit doesn't handle currently though.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The check our requirement instead of the ES6. In particular, the error is used in the following case:

class B {}
class C extends B {
    public x = 10;  // without this we won't give an error
    constructor() {
        var x = 10;
        super();
    }
}

}
else {
// In such a required super call, it is a compile-time error for argument expressions to reference this.
markThisReferencesAsErrors(superCallStatement.expression);
}
}
else if (!(getNodeCheckFlags(node) & NodeCheckFlags.HasSeenSuperBeforeThis)) {
// In ES6, super inside constructor of class-declaration has to precede "this" accessing
error(superCallStatement, Diagnostics.super_must_be_called_before_accessing_this_in_the_constructor_of_a_derived_class);
}
}
else if (baseConstructorType !== nullType) {
error(node, Diagnostics.Constructors_for_derived_classes_must_contain_a_super_call);
Expand Down
4 changes: 4 additions & 0 deletions src/compiler/diagnosticMessages.json
Original file line number Diff line number Diff line change
Expand Up @@ -2469,5 +2469,9 @@
"A constructor cannot contain a 'super' call when its class extends 'null'": {
"category": "Error",
"code": 17005
},
"'super' must be called before accessing 'this' in the constructor of a derived class.": {
"category": "Error",
"code": 17006
}
}
14 changes: 9 additions & 5 deletions src/compiler/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1746,6 +1746,8 @@ namespace ts {
EnumValuesComputed = 0x00002000,
BlockScopedBindingInLoop = 0x00004000,
LexicalModuleMergesWithClass= 0x00008000, // Instantiated lexical module declaration is merged with a previous class declaration.
HasSeenSuperBeforeThis = 0x00010000, // Set during the binding if the 'super' is used before 'this' in constructor function
HasSeenThisCall = 0x00020000, // Set during the binding when encounter 'this'
}

/* @internal */
Expand Down Expand Up @@ -2298,7 +2300,7 @@ namespace ts {
export interface ModuleResolutionHost {
fileExists(fileName: string): boolean;
// readFile function is used to read arbitrary text files on disk, i.e. when resolution procedure needs the content of 'package.json'
// to determine location of bundled typings for node module
// to determine location of bundled typings for node module
readFile(fileName: string): string;
}

Expand All @@ -2318,6 +2320,8 @@ namespace ts {
failedLookupLocations: string[];
}

export type ModuleNameResolver = (moduleName: string, containingFile: string, options: CompilerOptions, host: ModuleResolutionHost) => ResolvedModule;

export interface CompilerHost extends ModuleResolutionHost {
getSourceFile(fileName: string, languageVersion: ScriptTarget, onError?: (message: string) => void): SourceFile;
getCancellationToken?(): CancellationToken;
Expand All @@ -2329,11 +2333,11 @@ namespace ts {
getNewLine(): string;

/*
* CompilerHost must either implement resolveModuleNames (in case if it wants to be completely in charge of
* module name resolution) or provide implementation for methods from ModuleResolutionHost (in this case compiler
* CompilerHost must either implement resolveModuleNames (in case if it wants to be completely in charge of
* module name resolution) or provide implementation for methods from ModuleResolutionHost (in this case compiler
* will appply built-in module resolution logic and use members of ModuleResolutionHost to ask host specific questions).
* If resolveModuleNames is implemented then implementation for members from ModuleResolutionHost can be just
* 'throw new Error("NotImplemented")'
* If resolveModuleNames is implemented then implementation for members from ModuleResolutionHost can be just
* 'throw new Error("NotImplemented")'
*/
resolveModuleNames?(moduleNames: string[], containingFile: string): ResolvedModule[];
}
Expand Down
33 changes: 33 additions & 0 deletions tests/baselines/reference/checkSuperCallBeforeThisAccessing1.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
//// [checkSuperCallBeforeThisAccessing1.ts]
class Based { }
class Derived extends Based {
public x: number;
constructor() {
super();
this;
this.x = 10;
var that = this;
}
}

//// [checkSuperCallBeforeThisAccessing1.js]
var __extends = (this && this.__extends) || function (d, b) {
for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p];
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
var Based = (function () {
function Based() {
}
return Based;
})();
var Derived = (function (_super) {
__extends(Derived, _super);
function Derived() {
_super.call(this);
this;
this.x = 10;
var that = this;
}
return Derived;
})(Based);
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
=== tests/cases/compiler/checkSuperCallBeforeThisAccessing1.ts ===
class Based { }
>Based : Symbol(Based, Decl(checkSuperCallBeforeThisAccessing1.ts, 0, 0))

class Derived extends Based {
>Derived : Symbol(Derived, Decl(checkSuperCallBeforeThisAccessing1.ts, 0, 15))
>Based : Symbol(Based, Decl(checkSuperCallBeforeThisAccessing1.ts, 0, 0))

public x: number;
>x : Symbol(x, Decl(checkSuperCallBeforeThisAccessing1.ts, 1, 29))

constructor() {
super();
>super : Symbol(Based, Decl(checkSuperCallBeforeThisAccessing1.ts, 0, 0))

this;
>this : Symbol(Derived, Decl(checkSuperCallBeforeThisAccessing1.ts, 0, 15))

this.x = 10;
>this.x : Symbol(x, Decl(checkSuperCallBeforeThisAccessing1.ts, 1, 29))
>this : Symbol(Derived, Decl(checkSuperCallBeforeThisAccessing1.ts, 0, 15))
>x : Symbol(x, Decl(checkSuperCallBeforeThisAccessing1.ts, 1, 29))

var that = this;
>that : Symbol(that, Decl(checkSuperCallBeforeThisAccessing1.ts, 7, 11))
>this : Symbol(Derived, Decl(checkSuperCallBeforeThisAccessing1.ts, 0, 15))
}
}
31 changes: 31 additions & 0 deletions tests/baselines/reference/checkSuperCallBeforeThisAccessing1.types
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
=== tests/cases/compiler/checkSuperCallBeforeThisAccessing1.ts ===
class Based { }
>Based : Based

class Derived extends Based {
>Derived : Derived
>Based : Based

public x: number;
>x : number

constructor() {
super();
>super() : void
>super : typeof Based

this;
>this : this

this.x = 10;
>this.x = 10 : number
>this.x : number
>this : this
>x : number
>10 : number

var that = this;
>that : this
>this : this
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
tests/cases/compiler/checkSuperCallBeforeThisAccessing2.ts(6,9): error TS17006: 'super' must be called before accessing 'this' in the constructor of a derived class.


==== tests/cases/compiler/checkSuperCallBeforeThisAccessing2.ts (1 errors) ====
class Based { }
class Derived extends Based {
public x: number;
constructor() {
this.x = 100;
super();
~~~~~~~~
!!! error TS17006: 'super' must be called before accessing 'this' in the constructor of a derived class.
this.x = 10;
var that = this;
}
}
33 changes: 33 additions & 0 deletions tests/baselines/reference/checkSuperCallBeforeThisAccessing2.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
//// [checkSuperCallBeforeThisAccessing2.ts]
class Based { }
class Derived extends Based {
public x: number;
constructor() {
this.x = 100;
super();
this.x = 10;
var that = this;
}
}

//// [checkSuperCallBeforeThisAccessing2.js]
var __extends = (this && this.__extends) || function (d, b) {
for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p];
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
var Based = (function () {
function Based() {
}
return Based;
})();
var Derived = (function (_super) {
__extends(Derived, _super);
function Derived() {
this.x = 100;
_super.call(this);
this.x = 10;
var that = this;
}
return Derived;
})(Based);
43 changes: 43 additions & 0 deletions tests/baselines/reference/checkSuperCallBeforeThisAccessing3.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
//// [checkSuperCallBeforeThisAccessing3.ts]
class Based { }
class Derived extends Based {
public x: number;
constructor() {
class innver {
public y: boolean;
constructor() {
this.y = true;
}
}
super();
this.x = 10;
var that = this;
}
}

//// [checkSuperCallBeforeThisAccessing3.js]
var __extends = (this && this.__extends) || function (d, b) {
for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p];
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
var Based = (function () {
function Based() {
}
return Based;
})();
var Derived = (function (_super) {
__extends(Derived, _super);
function Derived() {
var innver = (function () {
function innver() {
this.y = true;
}
return innver;
})();
_super.call(this);
this.x = 10;
var that = this;
}
return Derived;
})(Based);
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
=== tests/cases/compiler/checkSuperCallBeforeThisAccessing3.ts ===
class Based { }
>Based : Symbol(Based, Decl(checkSuperCallBeforeThisAccessing3.ts, 0, 0))

class Derived extends Based {
>Derived : Symbol(Derived, Decl(checkSuperCallBeforeThisAccessing3.ts, 0, 15))
>Based : Symbol(Based, Decl(checkSuperCallBeforeThisAccessing3.ts, 0, 0))

public x: number;
>x : Symbol(x, Decl(checkSuperCallBeforeThisAccessing3.ts, 1, 29))

constructor() {
class innver {
>innver : Symbol(innver, Decl(checkSuperCallBeforeThisAccessing3.ts, 3, 19))

public y: boolean;
>y : Symbol(y, Decl(checkSuperCallBeforeThisAccessing3.ts, 4, 22))

constructor() {
this.y = true;
>this.y : Symbol(y, Decl(checkSuperCallBeforeThisAccessing3.ts, 4, 22))
>this : Symbol(innver, Decl(checkSuperCallBeforeThisAccessing3.ts, 3, 19))
>y : Symbol(y, Decl(checkSuperCallBeforeThisAccessing3.ts, 4, 22))
}
}
super();
>super : Symbol(Based, Decl(checkSuperCallBeforeThisAccessing3.ts, 0, 0))

this.x = 10;
>this.x : Symbol(x, Decl(checkSuperCallBeforeThisAccessing3.ts, 1, 29))
>this : Symbol(Derived, Decl(checkSuperCallBeforeThisAccessing3.ts, 0, 15))
>x : Symbol(x, Decl(checkSuperCallBeforeThisAccessing3.ts, 1, 29))

var that = this;
>that : Symbol(that, Decl(checkSuperCallBeforeThisAccessing3.ts, 12, 11))
>this : Symbol(Derived, Decl(checkSuperCallBeforeThisAccessing3.ts, 0, 15))
}
}
Loading