From ed0962883b9711f0f4c324c3e904bd776c5c9305 Mon Sep 17 00:00:00 2001 From: Kanchalai Tanglertsampan Date: Tue, 2 Feb 2016 13:06:07 -0800 Subject: [PATCH] Check using "super" before "this" lexically instead of using the NodeCheckFlags Remove "type-checking" way of checking if super is used before this. Instead check using whether super occurs before this syntactically Refactor the code Dive down to get super call --- src/compiler/checker.ts | 99 +++++++++++++++++++++++++---------------- src/compiler/types.ts | 6 +-- 2 files changed, 64 insertions(+), 41 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index e0caf84fa9d..71cd41522f0 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -7262,6 +7262,47 @@ namespace ts { } } + function isSuperCallExpression(n: Node): boolean { + return n.kind === SyntaxKind.CallExpression && (n).expression.kind === SyntaxKind.SuperKeyword; + } + + /** + * 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 getSuperStatementInConstructor(constructor: ConstructorDeclaration): ExpressionStatement { + function getContainingSuperCall(n: Node): Node { + if (isSuperCallExpression(n)) { + return n; + } + else if (isFunctionLike(n)) { + return undefined; + } + return forEachChild(n, getContainingSuperCall); + } + + const links = getNodeLinks(constructor); + if (!links.superStatement) { + links.superStatement = getContainingSuperCall(constructor.body); + } + return links.superStatement; + } + + /** + * 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 isClassDeclarationExtendNull(classDecl: ClassDeclaration): boolean { + const classSymbol = getSymbolOfNode(classDecl); + const classInstanceType = 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. @@ -7269,10 +7310,17 @@ namespace ts { let needToCaptureLexicalThis = false; if (container.kind === SyntaxKind.Constructor) { - const baseTypeNode = getClassExtendsHeritageClauseElement(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 = 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 && !isClassDeclarationExtendNull(containingClassDecl)) { + const superStatement = getSuperStatementInConstructor(container); + if (!superStatement || (superStatement && superStatement.pos > node.pos || superStatement.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); + } } } @@ -10219,14 +10267,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; @@ -11766,27 +11811,6 @@ namespace ts { return; } - function isSuperCallExpression(n: Node): boolean { - return n.kind === SyntaxKind.CallExpression && (n).expression.kind === SyntaxKind.SuperKeyword; - } - - 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((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); @@ -11803,16 +11827,14 @@ namespace ts { } // TS 1.0 spec (April 2014): 8.3.2 - // Constructors of classes with no extends clause may not contain super calls, whereas - // constructors of derived classes must contain at least one super call somewhere in their function body. + // Constructors of classes with no extends clause and constructors of classes that extends null may not contain super calls, + // whereas constructors of derived classes must contain at least one super call somewhere in their function body. const containingClassDecl = node.parent; if (getClassExtendsHeritageClauseElement(containingClassDecl)) { - const containingClassSymbol = getSymbolOfNode(containingClassDecl); - const containingClassInstanceType = getDeclaredTypeOfSymbol(containingClassSymbol); - const baseConstructorType = getBaseConstructorTypeOfClass(containingClassInstanceType); + const isClassExtendNull = isClassDeclarationExtendNull(containingClassDecl); - if (containsSuperCall(node.body)) { - if (baseConstructorType === nullType) { + if (getSuperStatementInConstructor(node)) { + if (isClassExtendNull) { error(node, Diagnostics.A_constructor_cannot_contain_a_super_call_when_its_class_extends_null); } @@ -11830,6 +11852,7 @@ namespace ts { if (superCallShouldBeFirst) { const statements = (node.body).statements; let superCallStatement: ExpressionStatement; + for (const statement of statements) { if (statement.kind === SyntaxKind.ExpressionStatement && isSuperCallExpression((statement).expression)) { superCallStatement = statement; @@ -11844,7 +11867,7 @@ namespace ts { } } } - else if (baseConstructorType !== nullType) { + else if (!isClassExtendNull) { error(node, Diagnostics.Constructors_for_derived_classes_must_contain_a_super_call); } } diff --git a/src/compiler/types.ts b/src/compiler/types.ts index ce3dbe7f459..5fdd40c14a1 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -2068,9 +2068,8 @@ 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. + 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. } /* @internal */ @@ -2089,6 +2088,7 @@ 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 + superStatement?: ExpressionStatement; // Cached super-statement found in the constructor. Used in checking whether super is called before this-accessing } export const enum TypeFlags {