-
Notifications
You must be signed in to change notification settings - Fork 12.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Check using "super" before "this" lexically #6860
Changes from all commits
a5cf7c1
76b6bff
ed09628
7e1088b
2dbc998
aff6775
1d4c1d1
47a2f3b
1cfce12
dc8e0cc
61e954b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -7306,17 +7306,72 @@ namespace ts { | |
} | ||
} | ||
|
||
function findFirstSuperCall(n: Node): Node { | ||
if (isSuperCallExpression(n)) { | ||
return n; | ||
} | ||
else if (isFunctionLike(n)) { | ||
return undefined; | ||
} | ||
return forEachChild(n, findFirstSuperCall); | ||
} | ||
|
||
/** | ||
* Return a cached result if super-statement is already found. | ||
* Otherwise, find a super statement in a given constructor function and cache the result in the node-links of the constructor | ||
* | ||
* @param constructor constructor-function to look for super statement | ||
*/ | ||
function getSuperCallInConstructor(constructor: ConstructorDeclaration): ExpressionStatement { | ||
const links = getNodeLinks(constructor); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. what if constructor does not have There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, unfortunately. |
||
|
||
// Only trying to find super-call if we haven't yet tried to find one. Once we try, we will record the result | ||
if (links.hasSuperCall === undefined) { | ||
links.superCall = <ExpressionStatement>findFirstSuperCall(constructor.body); | ||
links.hasSuperCall = links.superCall ? true : false; | ||
} | ||
return links.superCall; | ||
} | ||
|
||
/** | ||
* Check if the given class-declaration extends null then return true. | ||
* Otherwise, return false | ||
* @param classDecl a class declaration to check if it extends null | ||
*/ | ||
function classDeclarationExtendsNull(classDecl: ClassDeclaration): boolean { | ||
const classSymbol = getSymbolOfNode(classDecl); | ||
const classInstanceType = <InterfaceType>getDeclaredTypeOfSymbol(classSymbol); | ||
const baseConstructorType = getBaseConstructorTypeOfClass(classInstanceType); | ||
|
||
return baseConstructorType === nullType; | ||
} | ||
|
||
function checkThisExpression(node: Node): Type { | ||
// Stop at the first arrow function so that we can | ||
// tell whether 'this' needs to be captured. | ||
let container = getThisContainer(node, /* includeArrowFunctions */ true); | ||
let needToCaptureLexicalThis = false; | ||
|
||
if (container.kind === SyntaxKind.Constructor) { | ||
const baseTypeNode = getClassExtendsHeritageClauseElement(<ClassLikeDeclaration>container.parent); | ||
if (baseTypeNode && !(getNodeCheckFlags(container) & NodeCheckFlags.HasSeenSuperCall)) { | ||
// In ES6, super inside constructor of class-declaration has to precede "this" accessing | ||
error(node, Diagnostics.super_must_be_called_before_accessing_this_in_the_constructor_of_a_derived_class); | ||
const containingClassDecl = <ClassDeclaration>container.parent; | ||
const baseTypeNode = getClassExtendsHeritageClauseElement(containingClassDecl); | ||
|
||
// If a containing class does not have extends clause or the class extends null | ||
// skip checking whether super statement is called before "this" accessing. | ||
if (baseTypeNode && !classDeclarationExtendsNull(containingClassDecl)) { | ||
const superCall = getSuperCallInConstructor(<ConstructorDeclaration>container); | ||
|
||
// We should give an error in the following cases: | ||
// - No super-call | ||
// - "this" is accessing before super-call. | ||
// i.e super(this) | ||
// this.x; super(); | ||
// We want to make sure that super-call is done before accessing "this" so that | ||
// "this" is not accessed as a parameter of the super-call. | ||
if (!superCall || superCall.end > node.pos) { | ||
// In ES6, super inside constructor of class-declaration has to precede "this" accessing | ||
error(node, Diagnostics.super_must_be_called_before_accessing_this_in_the_constructor_of_a_derived_class); | ||
} | ||
} | ||
} | ||
|
||
|
@@ -10263,14 +10318,11 @@ namespace ts { | |
checkGrammarTypeArguments(node, node.typeArguments) || checkGrammarArguments(node, node.arguments); | ||
|
||
const signature = getResolvedSignature(node); | ||
if (node.expression.kind === SyntaxKind.SuperKeyword) { | ||
const containgFunction = getContainingFunction(node.expression); | ||
|
||
if (containgFunction && containgFunction.kind === SyntaxKind.Constructor) { | ||
getNodeLinks(containgFunction).flags |= NodeCheckFlags.HasSeenSuperCall; | ||
} | ||
if (node.expression.kind === SyntaxKind.SuperKeyword) { | ||
return voidType; | ||
} | ||
|
||
if (node.kind === SyntaxKind.NewExpression) { | ||
const declaration = signature.declaration; | ||
|
||
|
@@ -11813,23 +11865,6 @@ namespace ts { | |
return; | ||
} | ||
|
||
function containsSuperCallAsComputedPropertyName(n: Declaration): boolean { | ||
return n.name && containsSuperCall(n.name); | ||
} | ||
|
||
function containsSuperCall(n: Node): boolean { | ||
if (isSuperCallExpression(n)) { | ||
return true; | ||
} | ||
else if (isFunctionLike(n)) { | ||
return false; | ||
} | ||
else if (isClassLike(n)) { | ||
return forEach((<ClassLikeDeclaration>n).members, containsSuperCallAsComputedPropertyName); | ||
} | ||
return forEachChild(n, containsSuperCall); | ||
} | ||
|
||
function markThisReferencesAsErrors(n: Node): void { | ||
if (n.kind === SyntaxKind.ThisKeyword) { | ||
error(n, Diagnostics.this_cannot_be_referenced_in_current_location); | ||
|
@@ -11850,13 +11885,11 @@ namespace ts { | |
// constructors of derived classes must contain at least one super call somewhere in their function body. | ||
const containingClassDecl = <ClassDeclaration>node.parent; | ||
if (getClassExtendsHeritageClauseElement(containingClassDecl)) { | ||
const containingClassSymbol = getSymbolOfNode(containingClassDecl); | ||
const containingClassInstanceType = <InterfaceType>getDeclaredTypeOfSymbol(containingClassSymbol); | ||
const baseConstructorType = getBaseConstructorTypeOfClass(containingClassInstanceType); | ||
|
||
if (containsSuperCall(node.body)) { | ||
if (baseConstructorType === nullType) { | ||
error(node, Diagnostics.A_constructor_cannot_contain_a_super_call_when_its_class_extends_null); | ||
const classExtendsNull = classDeclarationExtendsNull(containingClassDecl); | ||
const superCall = getSuperCallInConstructor(node); | ||
if (superCall) { | ||
if (classExtendsNull) { | ||
error(superCall, Diagnostics.A_constructor_cannot_contain_a_super_call_when_its_class_extends_null); | ||
} | ||
|
||
// The first statement in the body of a constructor (excluding prologue directives) must be a super call | ||
|
@@ -11873,6 +11906,7 @@ namespace ts { | |
if (superCallShouldBeFirst) { | ||
const statements = (<Block>node.body).statements; | ||
let superCallStatement: ExpressionStatement; | ||
|
||
for (const statement of statements) { | ||
if (statement.kind === SyntaxKind.ExpressionStatement && isSuperCallExpression((<ExpressionStatement>statement).expression)) { | ||
superCallStatement = <ExpressionStatement>statement; | ||
|
@@ -11887,7 +11921,7 @@ namespace ts { | |
} | ||
} | ||
} | ||
else if (baseConstructorType !== nullType) { | ||
else if (!classExtendsNull) { | ||
error(node, Diagnostics.Constructors_for_derived_classes_must_contain_a_super_call); | ||
} | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -459,7 +459,6 @@ namespace ts { | |
IntrinsicElement = IntrinsicNamedElement | IntrinsicIndexedElement, | ||
} | ||
|
||
|
||
/* @internal */ | ||
export const enum RelationComparisonResult { | ||
Succeeded = 1, // Should be truthy | ||
|
@@ -2068,10 +2067,9 @@ namespace ts { | |
LoopWithCapturedBlockScopedBinding = 0x00010000, // Loop that contains block scoped variable captured in closure | ||
CapturedBlockScopedBinding = 0x00020000, // Block-scoped binding that is captured in some function | ||
BlockScopedBindingInLoop = 0x00040000, // Block-scoped binding with declaration nested inside iteration statement | ||
HasSeenSuperCall = 0x00080000, // Set during the binding when encounter 'super' | ||
ClassWithBodyScopedClassBinding = 0x00100000, // Decorated class that contains a binding to itself inside of the class body. | ||
BodyScopedClassBinding = 0x00200000, // Binding to a decorated class inside of the class's body. | ||
NeedsLoopOutParameter = 0x00400000, // Block scoped binding whose value should be explicitly copied outside of the converted loop | ||
ClassWithBodyScopedClassBinding = 0x0080000, // Decorated class that contains a binding to itself inside of the class body. | ||
BodyScopedClassBinding = 0x00100000, // Binding to a decorated class inside of the class's body. | ||
NeedsLoopOutParameter = 0x00200000, // Block scoped binding whose value should be explicitly copied outside of the converted loop | ||
} | ||
|
||
/* @internal */ | ||
|
@@ -2090,6 +2088,8 @@ namespace ts { | |
importOnRightSide?: Symbol; // for import declarations - import that appear on the right side | ||
jsxFlags?: JsxFlags; // flags for knowning what kind of element/attributes we're dealing with | ||
resolvedJsxType?: Type; // resolved element attributes type of a JSX openinglike element | ||
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. | ||
superCall?: ExpressionStatement; // Cached first super-call found in the constructor. Used in checking whether super is called before this-accessing | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You could just use There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
} | ||
|
||
export const enum TypeFlags { | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
//// [superCallBeforeThisAccessing1.ts] | ||
declare var Factory: any | ||
|
||
class Base { | ||
constructor(c) { } | ||
} | ||
class D extends Base { | ||
private _t; | ||
constructor() { | ||
super(i); | ||
var s = { | ||
t: this._t | ||
} | ||
var i = Factory.create(s); | ||
} | ||
} | ||
|
||
|
||
//// [superCallBeforeThisAccessing1.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 Base = (function () { | ||
function Base(c) { | ||
} | ||
return Base; | ||
}()); | ||
var D = (function (_super) { | ||
__extends(D, _super); | ||
function D() { | ||
_super.call(this, i); | ||
var s = { | ||
t: this._t | ||
}; | ||
var i = Factory.create(s); | ||
} | ||
return D; | ||
}(Base)); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
=== tests/cases/conformance/es6/classDeclaration/superCallBeforeThisAccessing1.ts === | ||
declare var Factory: any | ||
>Factory : Symbol(Factory, Decl(superCallBeforeThisAccessing1.ts, 0, 11)) | ||
|
||
class Base { | ||
>Base : Symbol(Base, Decl(superCallBeforeThisAccessing1.ts, 0, 24)) | ||
|
||
constructor(c) { } | ||
>c : Symbol(c, Decl(superCallBeforeThisAccessing1.ts, 3, 16)) | ||
} | ||
class D extends Base { | ||
>D : Symbol(D, Decl(superCallBeforeThisAccessing1.ts, 4, 1)) | ||
>Base : Symbol(Base, Decl(superCallBeforeThisAccessing1.ts, 0, 24)) | ||
|
||
private _t; | ||
>_t : Symbol(_t, Decl(superCallBeforeThisAccessing1.ts, 5, 22)) | ||
|
||
constructor() { | ||
super(i); | ||
>super : Symbol(Base, Decl(superCallBeforeThisAccessing1.ts, 0, 24)) | ||
>i : Symbol(i, Decl(superCallBeforeThisAccessing1.ts, 12, 11)) | ||
|
||
var s = { | ||
>s : Symbol(s, Decl(superCallBeforeThisAccessing1.ts, 9, 11)) | ||
|
||
t: this._t | ||
>t : Symbol(t, Decl(superCallBeforeThisAccessing1.ts, 9, 17)) | ||
>this._t : Symbol(_t, Decl(superCallBeforeThisAccessing1.ts, 5, 22)) | ||
>this : Symbol(D, Decl(superCallBeforeThisAccessing1.ts, 4, 1)) | ||
>_t : Symbol(_t, Decl(superCallBeforeThisAccessing1.ts, 5, 22)) | ||
} | ||
var i = Factory.create(s); | ||
>i : Symbol(i, Decl(superCallBeforeThisAccessing1.ts, 12, 11)) | ||
>Factory : Symbol(Factory, Decl(superCallBeforeThisAccessing1.ts, 0, 11)) | ||
>s : Symbol(s, Decl(superCallBeforeThisAccessing1.ts, 9, 11)) | ||
} | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
=== tests/cases/conformance/es6/classDeclaration/superCallBeforeThisAccessing1.ts === | ||
declare var Factory: any | ||
>Factory : any | ||
|
||
class Base { | ||
>Base : Base | ||
|
||
constructor(c) { } | ||
>c : any | ||
} | ||
class D extends Base { | ||
>D : D | ||
>Base : Base | ||
|
||
private _t; | ||
>_t : any | ||
|
||
constructor() { | ||
super(i); | ||
>super(i) : void | ||
>super : typeof Base | ||
>i : any | ||
|
||
var s = { | ||
>s : { t: any; } | ||
>{ t: this._t } : { t: any; } | ||
|
||
t: this._t | ||
>t : any | ||
>this._t : any | ||
>this : this | ||
>_t : any | ||
} | ||
var i = Factory.create(s); | ||
>i : any | ||
>Factory.create(s) : any | ||
>Factory.create : any | ||
>Factory : any | ||
>create : any | ||
>s : { t: any; } | ||
} | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
//// [superCallBeforeThisAccessing2.ts] | ||
class Base { | ||
constructor(c) { } | ||
} | ||
class D extends Base { | ||
private _t; | ||
constructor() { | ||
super(() => { this._t }); // no error. only check when this is directly accessing in constructor | ||
} | ||
} | ||
|
||
|
||
//// [superCallBeforeThisAccessing2.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 Base = (function () { | ||
function Base(c) { | ||
} | ||
return Base; | ||
}()); | ||
var D = (function (_super) { | ||
__extends(D, _super); | ||
function D() { | ||
var _this = this; | ||
_super.call(this, function () { _this._t; }); // no error. only check when this is directly accessing in constructor | ||
} | ||
return D; | ||
}(Base)); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
=== tests/cases/conformance/es6/classDeclaration/superCallBeforeThisAccessing2.ts === | ||
class Base { | ||
>Base : Symbol(Base, Decl(superCallBeforeThisAccessing2.ts, 0, 0)) | ||
|
||
constructor(c) { } | ||
>c : Symbol(c, Decl(superCallBeforeThisAccessing2.ts, 1, 16)) | ||
} | ||
class D extends Base { | ||
>D : Symbol(D, Decl(superCallBeforeThisAccessing2.ts, 2, 1)) | ||
>Base : Symbol(Base, Decl(superCallBeforeThisAccessing2.ts, 0, 0)) | ||
|
||
private _t; | ||
>_t : Symbol(_t, Decl(superCallBeforeThisAccessing2.ts, 3, 22)) | ||
|
||
constructor() { | ||
super(() => { this._t }); // no error. only check when this is directly accessing in constructor | ||
>super : Symbol(Base, Decl(superCallBeforeThisAccessing2.ts, 0, 0)) | ||
>this._t : Symbol(_t, Decl(superCallBeforeThisAccessing2.ts, 3, 22)) | ||
>this : Symbol(D, Decl(superCallBeforeThisAccessing2.ts, 2, 1)) | ||
>_t : Symbol(_t, Decl(superCallBeforeThisAccessing2.ts, 3, 22)) | ||
} | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The funny thing is that you don't actually use the returned value except to determine whether you've found a super call - so you should consider reducing your needs to
NodeLinks.hasSuperCall
orNodeLinks.superCall
, but not both.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
used down below.