From d7c8d3bb9762b4eacb61a6931ff1a6028b716a18 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Fri, 15 May 2020 19:01:45 -0700 Subject: [PATCH 1/4] Use CFA graph to check this/super accesses are preceded by super() call --- src/compiler/binder.ts | 10 ++++- src/compiler/checker.ts | 96 ++++++++++++++++++++++++----------------- src/compiler/core.ts | 2 +- src/compiler/types.ts | 21 ++------- 4 files changed, 68 insertions(+), 61 deletions(-) diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index 8a47755498b34..8809c34d06053 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -985,7 +985,7 @@ namespace ts { return initFlowNode({ flags: FlowFlags.SwitchClause, antecedent, switchStatement, clauseStart, clauseEnd }); } - function createFlowMutation(flags: FlowFlags, antecedent: FlowNode, node: Node): FlowNode { + function createFlowMutation(flags: FlowFlags, antecedent: FlowNode, node: Expression | VariableDeclaration | ArrayBindingElement): FlowNode { setFlowNodeReferenced(antecedent); const result = initFlowNode({ flags, antecedent, node }); if (currentExceptionTarget) { @@ -1341,7 +1341,7 @@ namespace ts { // is potentially an assertion and is therefore included in the control flow. if (node.expression.kind === SyntaxKind.CallExpression) { const call = node.expression; - if (isDottedName(call.expression)) { + if (isDottedName(call.expression) && call.expression.kind !== SyntaxKind.SuperKeyword) { currentFlow = createFlowCall(currentFlow, call); } } @@ -1747,6 +1747,9 @@ namespace ts { } else { bindEachChild(node); + if (node.expression.kind === SyntaxKind.SuperKeyword) { + currentFlow = createFlowCall(currentFlow, node); + } } } if (node.expression.kind === SyntaxKind.PropertyAccessExpression) { @@ -2464,6 +2467,9 @@ namespace ts { node.flowNode = currentFlow; } return checkStrictModeIdentifier(node); + case SyntaxKind.SuperKeyword: + node.flowNode = currentFlow; + break; case SyntaxKind.PrivateIdentifier: return checkPrivateIdentifier(node as PrivateIdentifier); case SyntaxKind.PropertyAccessExpression: diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 48367ce63e0b6..32dbe2d408eb4 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -911,6 +911,7 @@ namespace ts { const sharedFlowNodes: FlowNode[] = []; const sharedFlowTypes: FlowType[] = []; const flowNodeReachable: (boolean | undefined)[] = []; + const flowNodePostSuper: (boolean | undefined)[] = []; const potentialThisCollisions: Node[] = []; const potentialNewTargetCollisions: Node[] = []; const potentialWeakMapCollisions: Node[] = []; @@ -20134,7 +20135,7 @@ namespace ts { noCacheCheck = false; } if (flags & (FlowFlags.Assignment | FlowFlags.Condition | FlowFlags.ArrayMutation)) { - flow = (flow).antecedent; + flow = (flow).antecedent; } else if (flags & FlowFlags.Call) { const signature = getEffectsSignature((flow).node); @@ -20184,6 +20185,51 @@ namespace ts { } } + // Return true if the given flow node is preceded by a 'super(...)' call in every possible code path + // leading to the node. + function isPostSuperFlowNode(flow: FlowNode, noCacheCheck: boolean): boolean { + while (true) { + const flags = flow.flags; + if (flags & FlowFlags.Shared) { + if (!noCacheCheck) { + const id = getFlowNodeId(flow); + const postSuper = flowNodePostSuper[id]; + return postSuper !== undefined ? postSuper : (flowNodePostSuper[id] = isPostSuperFlowNode(flow, /*noCacheCheck*/ true)); + } + noCacheCheck = false; + } + if (flags & (FlowFlags.Assignment | FlowFlags.Condition | FlowFlags.ArrayMutation | FlowFlags.SwitchClause)) { + flow = (flow).antecedent; + } + else if (flags & FlowFlags.Call) { + if ((flow).node.expression.kind === SyntaxKind.SuperKeyword) { + return true; + } + flow = (flow).antecedent; + } + else if (flags & FlowFlags.BranchLabel) { + // A branching point is post-super if every branch is post-super. + return every((flow).antecedents, f => isPostSuperFlowNode(f, /*noCacheCheck*/ false)); + } + else if (flags & FlowFlags.LoopLabel) { + // A loop is post-super if the control flow path that leads to the top is post-super. + flow = (flow).antecedents![0]; + } + else if (flags & FlowFlags.ReduceLabel) { + const target = (flow).target; + const saveAntecedents = target.antecedents; + target.antecedents = (flow).antecedents; + const result = isPostSuperFlowNode((flow).antecedent, /*noCacheCheck*/ false); + target.antecedents = saveAntecedents; + return result; + } + else { + // Unreachable nodes are considered post-super to silence errors + return !!(flags & FlowFlags.Unreachable); + } + } + } + function getFlowTypeOfReference(reference: Node, declaredType: Type, initialType = declaredType, flowContainer?: Node, couldBeUninitialized?: boolean) { let key: string | undefined; let keySet = false; @@ -21583,31 +21629,10 @@ namespace ts { } } - function findFirstSuperCall(n: Node): SuperCall | undefined { - if (isSuperCall(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): SuperCall | undefined { - const links = getNodeLinks(constructor); - - // 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 = findFirstSuperCall(constructor.body!); - links.hasSuperCall = links.superCall ? true : false; - } - return links.superCall!; + function findFirstSuperCall(node: Node): SuperCall | undefined { + return isSuperCall(node) ? node : + isFunctionLike(node) ? undefined : + forEachChild(node, findFirstSuperCall); } /** @@ -21630,17 +21655,7 @@ namespace ts { // 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(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 + if (node.flowNode && !isPostSuperFlowNode(node.flowNode, /*noCacheCheck*/ false)) { error(node, diagnosticMessage); } } @@ -21865,7 +21880,8 @@ namespace ts { function checkSuperExpression(node: Node): Type { const isCallExpression = node.parent.kind === SyntaxKind.CallExpression && (node.parent).expression === node; - let container = getSuperContainer(node, /*stopOnFunctions*/ true); + const immediateContainer = getSuperContainer(node, /*stopOnFunctions*/ true); + let container = immediateContainer; let needToCaptureLexicalThis = false; // adjust the container reference in case if super is used inside arrow functions with arbitrarily deep nesting @@ -21901,7 +21917,7 @@ namespace ts { return errorType; } - if (!isCallExpression && container.kind === SyntaxKind.Constructor) { + if (!isCallExpression && immediateContainer.kind === SyntaxKind.Constructor) { checkThisBeforeSuper(node, container, Diagnostics.super_must_be_called_before_accessing_a_property_of_super_in_the_constructor_of_a_derived_class); } @@ -29898,7 +29914,7 @@ namespace ts { if (getClassExtendsHeritageElement(containingClassDecl)) { captureLexicalThis(node.parent, containingClassDecl); const classExtendsNull = classDeclarationExtendsNull(containingClassDecl); - const superCall = getSuperCallInConstructor(node); + const superCall = findFirstSuperCall(node.body!); if (superCall) { if (classExtendsNull) { error(superCall, Diagnostics.A_constructor_cannot_contain_a_super_call_when_its_class_extends_null); diff --git a/src/compiler/core.ts b/src/compiler/core.ts index 63e293f2e8f1a..651f5fe82de40 100644 --- a/src/compiler/core.ts +++ b/src/compiler/core.ts @@ -150,7 +150,7 @@ namespace ts { * returns a falsey value, then returns false. * If no such value is found, the callback is applied to each element of array and `true` is returned. */ - export function every(array: readonly T[], callback: (element: T, index: number) => boolean): boolean { + export function every(array: readonly T[] | undefined, callback: (element: T, index: number) => boolean): boolean { if (array) { for (let i = 0; i < array.length; i++) { if (!callback(array[i], i)) { diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 54d889b2ff4fc..aef4364a6b5c8 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -2791,34 +2791,21 @@ namespace ts { } export type FlowNode = - | AfterFinallyFlow - | PreFinallyFlow | FlowStart | FlowLabel | FlowAssignment | FlowCall | FlowCondition | FlowSwitchClause - | FlowArrayMutation; + | FlowArrayMutation + | FlowCall + | FlowReduceLabel; export interface FlowNodeBase { flags: FlowFlags; id?: number; // Node id used by flow type cache in checker } - export interface FlowLock { - locked?: boolean; - } - - export interface AfterFinallyFlow extends FlowNodeBase, FlowLock { - antecedent: FlowNode; - } - - export interface PreFinallyFlow extends FlowNodeBase { - antecedent: FlowNode; - lock: FlowLock; - } - // FlowStart represents the start of a control flow. For a function expression or arrow // function, the node property references the function (which in turn has a flowNode // property for the containing control flow). @@ -4316,8 +4303,6 @@ namespace ts { resolvedJsxElementAttributesType?: Type; // resolved element attributes type of a JSX openinglike element resolvedJsxElementAllAttributesType?: Type; // resolved all element attributes type of a JSX openinglike element resolvedJSDocType?: Type; // Resolved type of a JSDoc type reference - 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?: SuperCall; // Cached first super-call found in the constructor. Used in checking whether super is called before this-accessing switchTypes?: Type[]; // Cached array of switch case expression types jsxNamespace?: Symbol | false; // Resolved jsx namespace symbol for this node contextFreeType?: Type; // Cached context-free type used by the first pass of inference; used when a function's return is partially contextually sensitive From 056930f1c85fe16c77378d336c946b072a737ea7 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Fri, 15 May 2020 19:02:43 -0700 Subject: [PATCH 2/4] Accept cleaned-up API baselines --- tests/baselines/reference/api/tsserverlibrary.d.ts | 12 +----------- tests/baselines/reference/api/typescript.d.ts | 12 +----------- 2 files changed, 2 insertions(+), 22 deletions(-) diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index 2c7742fb88b97..6ea68e933ab4b 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -1743,21 +1743,11 @@ declare namespace ts { Label = 12, Condition = 96 } - export type FlowNode = AfterFinallyFlow | PreFinallyFlow | FlowStart | FlowLabel | FlowAssignment | FlowCall | FlowCondition | FlowSwitchClause | FlowArrayMutation; + export type FlowNode = FlowStart | FlowLabel | FlowAssignment | FlowCall | FlowCondition | FlowSwitchClause | FlowArrayMutation | FlowCall | FlowReduceLabel; export interface FlowNodeBase { flags: FlowFlags; id?: number; } - export interface FlowLock { - locked?: boolean; - } - export interface AfterFinallyFlow extends FlowNodeBase, FlowLock { - antecedent: FlowNode; - } - export interface PreFinallyFlow extends FlowNodeBase { - antecedent: FlowNode; - lock: FlowLock; - } export interface FlowStart extends FlowNodeBase { node?: FunctionExpression | ArrowFunction | MethodDeclaration; } diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index c901f86e38499..d2c5d5ce805df 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -1743,21 +1743,11 @@ declare namespace ts { Label = 12, Condition = 96 } - export type FlowNode = AfterFinallyFlow | PreFinallyFlow | FlowStart | FlowLabel | FlowAssignment | FlowCall | FlowCondition | FlowSwitchClause | FlowArrayMutation; + export type FlowNode = FlowStart | FlowLabel | FlowAssignment | FlowCall | FlowCondition | FlowSwitchClause | FlowArrayMutation | FlowCall | FlowReduceLabel; export interface FlowNodeBase { flags: FlowFlags; id?: number; } - export interface FlowLock { - locked?: boolean; - } - export interface AfterFinallyFlow extends FlowNodeBase, FlowLock { - antecedent: FlowNode; - } - export interface PreFinallyFlow extends FlowNodeBase { - antecedent: FlowNode; - lock: FlowLock; - } export interface FlowStart extends FlowNodeBase { node?: FunctionExpression | ArrowFunction | MethodDeclaration; } From 76b9a633596c6042c5cb3513152c4af550e38c0a Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Fri, 15 May 2020 19:03:10 -0700 Subject: [PATCH 3/4] Accept new baselines --- ...eSuperPropertyAccessInSuperCall01.errors.txt | 17 ----------------- .../baselines/reference/superAccess2.errors.txt | 5 +---- 2 files changed, 1 insertion(+), 21 deletions(-) delete mode 100644 tests/baselines/reference/captureSuperPropertyAccessInSuperCall01.errors.txt diff --git a/tests/baselines/reference/captureSuperPropertyAccessInSuperCall01.errors.txt b/tests/baselines/reference/captureSuperPropertyAccessInSuperCall01.errors.txt deleted file mode 100644 index 6e2b7ef57878c..0000000000000 --- a/tests/baselines/reference/captureSuperPropertyAccessInSuperCall01.errors.txt +++ /dev/null @@ -1,17 +0,0 @@ -tests/cases/compiler/captureSuperPropertyAccessInSuperCall01.ts(9,24): error TS17011: 'super' must be called before accessing a property of 'super' in the constructor of a derived class. - - -==== tests/cases/compiler/captureSuperPropertyAccessInSuperCall01.ts (1 errors) ==== - class A { - constructor(f: () => string) { - } - public blah(): string { return ""; } - } - - class B extends A { - constructor() { - super(() => { return super.blah(); }) - ~~~~~ -!!! error TS17011: 'super' must be called before accessing a property of 'super' in the constructor of a derived class. - } - } \ No newline at end of file diff --git a/tests/baselines/reference/superAccess2.errors.txt b/tests/baselines/reference/superAccess2.errors.txt index 65d4c10794d5b..e5475595472a3 100644 --- a/tests/baselines/reference/superAccess2.errors.txt +++ b/tests/baselines/reference/superAccess2.errors.txt @@ -7,7 +7,6 @@ tests/cases/compiler/superAccess2.ts(11,33): error TS1034: 'super' must be follo tests/cases/compiler/superAccess2.ts(11,40): error TS2336: 'super' cannot be referenced in constructor arguments. tests/cases/compiler/superAccess2.ts(11,40): error TS17011: 'super' must be called before accessing a property of 'super' in the constructor of a derived class. tests/cases/compiler/superAccess2.ts(11,45): error TS1034: 'super' must be followed by an argument list or member access. -tests/cases/compiler/superAccess2.ts(11,59): error TS17011: 'super' must be called before accessing a property of 'super' in the constructor of a derived class. tests/cases/compiler/superAccess2.ts(11,64): error TS1034: 'super' must be followed by an argument list or member access. tests/cases/compiler/superAccess2.ts(15,19): error TS1034: 'super' must be followed by an argument list or member access. tests/cases/compiler/superAccess2.ts(17,15): error TS2576: Property 'y' is a static member of type 'P' @@ -15,7 +14,7 @@ tests/cases/compiler/superAccess2.ts(20,26): error TS1034: 'super' must be follo tests/cases/compiler/superAccess2.ts(21,15): error TS2339: Property 'x' does not exist on type 'typeof P'. -==== tests/cases/compiler/superAccess2.ts (15 errors) ==== +==== tests/cases/compiler/superAccess2.ts (14 errors) ==== class P { x() { } static y() { } @@ -45,8 +44,6 @@ tests/cases/compiler/superAccess2.ts(21,15): error TS2339: Property 'x' does not !!! error TS17011: 'super' must be called before accessing a property of 'super' in the constructor of a derived class. ~ !!! error TS1034: 'super' must be followed by an argument list or member access. - ~~~~~ -!!! error TS17011: 'super' must be called before accessing a property of 'super' in the constructor of a derived class. ~ !!! error TS1034: 'super' must be followed by an argument list or member access. super(); From 41baf41700c959ed7ab717997af06c8f0fc787f6 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Sat, 16 May 2020 10:50:52 -0700 Subject: [PATCH 4/4] Add tests --- .../checkSuperCallBeforeThisAccess.errors.txt | 123 ++++++++ .../checkSuperCallBeforeThisAccess.js | 161 ++++++++++ .../checkSuperCallBeforeThisAccess.symbols | 231 +++++++++++++++ .../checkSuperCallBeforeThisAccess.types | 279 ++++++++++++++++++ .../checkSuperCallBeforeThisAccess.ts | 83 ++++++ 5 files changed, 877 insertions(+) create mode 100644 tests/baselines/reference/checkSuperCallBeforeThisAccess.errors.txt create mode 100644 tests/baselines/reference/checkSuperCallBeforeThisAccess.js create mode 100644 tests/baselines/reference/checkSuperCallBeforeThisAccess.symbols create mode 100644 tests/baselines/reference/checkSuperCallBeforeThisAccess.types create mode 100644 tests/cases/compiler/checkSuperCallBeforeThisAccess.ts diff --git a/tests/baselines/reference/checkSuperCallBeforeThisAccess.errors.txt b/tests/baselines/reference/checkSuperCallBeforeThisAccess.errors.txt new file mode 100644 index 0000000000000..4b38d7146f42e --- /dev/null +++ b/tests/baselines/reference/checkSuperCallBeforeThisAccess.errors.txt @@ -0,0 +1,123 @@ +tests/cases/compiler/checkSuperCallBeforeThisAccess.ts(7,18): error TS17009: 'super' must be called before accessing 'this' in the constructor of a derived class. +tests/cases/compiler/checkSuperCallBeforeThisAccess.ts(8,18): error TS17009: 'super' must be called before accessing 'this' in the constructor of a derived class. +tests/cases/compiler/checkSuperCallBeforeThisAccess.ts(9,18): error TS17011: 'super' must be called before accessing a property of 'super' in the constructor of a derived class. +tests/cases/compiler/checkSuperCallBeforeThisAccess.ts(20,22): error TS17009: 'super' must be called before accessing 'this' in the constructor of a derived class. +tests/cases/compiler/checkSuperCallBeforeThisAccess.ts(21,22): error TS17009: 'super' must be called before accessing 'this' in the constructor of a derived class. +tests/cases/compiler/checkSuperCallBeforeThisAccess.ts(22,22): error TS17011: 'super' must be called before accessing a property of 'super' in the constructor of a derived class. +tests/cases/compiler/checkSuperCallBeforeThisAccess.ts(30,30): error TS17009: 'super' must be called before accessing 'this' in the constructor of a derived class. +tests/cases/compiler/checkSuperCallBeforeThisAccess.ts(39,22): error TS17009: 'super' must be called before accessing 'this' in the constructor of a derived class. +tests/cases/compiler/checkSuperCallBeforeThisAccess.ts(43,18): error TS17009: 'super' must be called before accessing 'this' in the constructor of a derived class. +tests/cases/compiler/checkSuperCallBeforeThisAccess.ts(44,18): error TS17009: 'super' must be called before accessing 'this' in the constructor of a derived class. +tests/cases/compiler/checkSuperCallBeforeThisAccess.ts(45,18): error TS17011: 'super' must be called before accessing a property of 'super' in the constructor of a derived class. +tests/cases/compiler/checkSuperCallBeforeThisAccess.ts(59,27): error TS17009: 'super' must be called before accessing 'this' in the constructor of a derived class. +tests/cases/compiler/checkSuperCallBeforeThisAccess.ts(75,27): error TS17009: 'super' must be called before accessing 'this' in the constructor of a derived class. + + +==== tests/cases/compiler/checkSuperCallBeforeThisAccess.ts (13 errors) ==== + class A { + x = 1; + } + + class C1 extends A { + constructor(n: number) { + let a1 = this; // Error + ~~~~ +!!! error TS17009: 'super' must be called before accessing 'this' in the constructor of a derived class. + let a2 = this.x; // Error + ~~~~ +!!! error TS17009: 'super' must be called before accessing 'this' in the constructor of a derived class. + let a3 = super.x; // Error + ~~~~~ +!!! error TS17011: 'super' must be called before accessing a property of 'super' in the constructor of a derived class. + let a4 = () => this; + let a5 = () => this.x; + let a6 = () => super.x; + if (!!true) { + super(); + let b1 = this; + let b2 = this.x; + let b3 = super.x; + } + else { + let c1 = this; // Error + ~~~~ +!!! error TS17009: 'super' must be called before accessing 'this' in the constructor of a derived class. + let c2 = this.x; // Error + ~~~~ +!!! error TS17009: 'super' must be called before accessing 'this' in the constructor of a derived class. + let c3 = super.x; // Error + ~~~~~ +!!! error TS17011: 'super' must be called before accessing a property of 'super' in the constructor of a derived class. + } + if (!!true) { + switch (n) { + case 1: + super(); + let d1 = this.x; + case 2: + let d2 = this.x; // Error + ~~~~ +!!! error TS17009: 'super' must be called before accessing 'this' in the constructor of a derived class. + default: + super(); + let d3 = this.x; + } + let d4 = this.x; + } + if (!!true) { + let e1 = { w: !!true ? super() : 0 }; + let e2 = this.x; // Error + ~~~~ +!!! error TS17009: 'super' must be called before accessing 'this' in the constructor of a derived class. + let e3 = { w: !!true ? super() : super() }; + let e4 = this.x; + } + let f1 = this; // Error + ~~~~ +!!! error TS17009: 'super' must be called before accessing 'this' in the constructor of a derived class. + let f2 = this.x; // Error + ~~~~ +!!! error TS17009: 'super' must be called before accessing 'this' in the constructor of a derived class. + let f3 = super.x; // Error + ~~~~~ +!!! error TS17011: 'super' must be called before accessing a property of 'super' in the constructor of a derived class. + } + } + + // Repro from #38512 + + export class Foo { + constructor(value: number) { + } + } + + export class BarCorrectlyFails extends Foo { + constructor(something: boolean) { + if (!something) { + const value = this.bar(); // Error + ~~~~ +!!! error TS17009: 'super' must be called before accessing 'this' in the constructor of a derived class. + super(value); + } + else { + super(1337); + } + } + bar(): number { return 4; } + } + + export class BarIncorrectlyWorks extends Foo { + constructor(something: boolean) { + if (something) { + super(1337); + } + else { + const value = this.bar(); // Error + ~~~~ +!!! error TS17009: 'super' must be called before accessing 'this' in the constructor of a derived class. + super(value); + } + } + bar(): number { return 4; } + } + \ No newline at end of file diff --git a/tests/baselines/reference/checkSuperCallBeforeThisAccess.js b/tests/baselines/reference/checkSuperCallBeforeThisAccess.js new file mode 100644 index 0000000000000..0bd17ed4fa3f3 --- /dev/null +++ b/tests/baselines/reference/checkSuperCallBeforeThisAccess.js @@ -0,0 +1,161 @@ +//// [checkSuperCallBeforeThisAccess.ts] +class A { + x = 1; +} + +class C1 extends A { + constructor(n: number) { + let a1 = this; // Error + let a2 = this.x; // Error + let a3 = super.x; // Error + let a4 = () => this; + let a5 = () => this.x; + let a6 = () => super.x; + if (!!true) { + super(); + let b1 = this; + let b2 = this.x; + let b3 = super.x; + } + else { + let c1 = this; // Error + let c2 = this.x; // Error + let c3 = super.x; // Error + } + if (!!true) { + switch (n) { + case 1: + super(); + let d1 = this.x; + case 2: + let d2 = this.x; // Error + default: + super(); + let d3 = this.x; + } + let d4 = this.x; + } + if (!!true) { + let e1 = { w: !!true ? super() : 0 }; + let e2 = this.x; // Error + let e3 = { w: !!true ? super() : super() }; + let e4 = this.x; + } + let f1 = this; // Error + let f2 = this.x; // Error + let f3 = super.x; // Error + } +} + +// Repro from #38512 + +export class Foo { + constructor(value: number) { + } +} + +export class BarCorrectlyFails extends Foo { + constructor(something: boolean) { + if (!something) { + const value = this.bar(); // Error + super(value); + } + else { + super(1337); + } + } + bar(): number { return 4; } +} + +export class BarIncorrectlyWorks extends Foo { + constructor(something: boolean) { + if (something) { + super(1337); + } + else { + const value = this.bar(); // Error + super(value); + } + } + bar(): number { return 4; } +} + + +//// [checkSuperCallBeforeThisAccess.js] +class A { + constructor() { + this.x = 1; + } +} +class C1 extends A { + constructor(n) { + let a1 = this; // Error + let a2 = this.x; // Error + let a3 = super.x; // Error + let a4 = () => this; + let a5 = () => this.x; + let a6 = () => super.x; + if (!!true) { + super(); + let b1 = this; + let b2 = this.x; + let b3 = super.x; + } + else { + let c1 = this; // Error + let c2 = this.x; // Error + let c3 = super.x; // Error + } + if (!!true) { + switch (n) { + case 1: + super(); + let d1 = this.x; + case 2: + let d2 = this.x; // Error + default: + super(); + let d3 = this.x; + } + let d4 = this.x; + } + if (!!true) { + let e1 = { w: !!true ? super() : 0 }; + let e2 = this.x; // Error + let e3 = { w: !!true ? super() : super() }; + let e4 = this.x; + } + let f1 = this; // Error + let f2 = this.x; // Error + let f3 = super.x; // Error + } +} +// Repro from #38512 +export class Foo { + constructor(value) { + } +} +export class BarCorrectlyFails extends Foo { + constructor(something) { + if (!something) { + const value = this.bar(); // Error + super(value); + } + else { + super(1337); + } + } + bar() { return 4; } +} +export class BarIncorrectlyWorks extends Foo { + constructor(something) { + if (something) { + super(1337); + } + else { + const value = this.bar(); // Error + super(value); + } + } + bar() { return 4; } +} diff --git a/tests/baselines/reference/checkSuperCallBeforeThisAccess.symbols b/tests/baselines/reference/checkSuperCallBeforeThisAccess.symbols new file mode 100644 index 0000000000000..fbf9bb9d1a57d --- /dev/null +++ b/tests/baselines/reference/checkSuperCallBeforeThisAccess.symbols @@ -0,0 +1,231 @@ +=== tests/cases/compiler/checkSuperCallBeforeThisAccess.ts === +class A { +>A : Symbol(A, Decl(checkSuperCallBeforeThisAccess.ts, 0, 0)) + + x = 1; +>x : Symbol(A.x, Decl(checkSuperCallBeforeThisAccess.ts, 0, 9)) +} + +class C1 extends A { +>C1 : Symbol(C1, Decl(checkSuperCallBeforeThisAccess.ts, 2, 1)) +>A : Symbol(A, Decl(checkSuperCallBeforeThisAccess.ts, 0, 0)) + + constructor(n: number) { +>n : Symbol(n, Decl(checkSuperCallBeforeThisAccess.ts, 5, 16)) + + let a1 = this; // Error +>a1 : Symbol(a1, Decl(checkSuperCallBeforeThisAccess.ts, 6, 11)) +>this : Symbol(C1, Decl(checkSuperCallBeforeThisAccess.ts, 2, 1)) + + let a2 = this.x; // Error +>a2 : Symbol(a2, Decl(checkSuperCallBeforeThisAccess.ts, 7, 11)) +>this.x : Symbol(A.x, Decl(checkSuperCallBeforeThisAccess.ts, 0, 9)) +>this : Symbol(C1, Decl(checkSuperCallBeforeThisAccess.ts, 2, 1)) +>x : Symbol(A.x, Decl(checkSuperCallBeforeThisAccess.ts, 0, 9)) + + let a3 = super.x; // Error +>a3 : Symbol(a3, Decl(checkSuperCallBeforeThisAccess.ts, 8, 11)) +>super.x : Symbol(A.x, Decl(checkSuperCallBeforeThisAccess.ts, 0, 9)) +>super : Symbol(A, Decl(checkSuperCallBeforeThisAccess.ts, 0, 0)) +>x : Symbol(A.x, Decl(checkSuperCallBeforeThisAccess.ts, 0, 9)) + + let a4 = () => this; +>a4 : Symbol(a4, Decl(checkSuperCallBeforeThisAccess.ts, 9, 11)) +>this : Symbol(C1, Decl(checkSuperCallBeforeThisAccess.ts, 2, 1)) + + let a5 = () => this.x; +>a5 : Symbol(a5, Decl(checkSuperCallBeforeThisAccess.ts, 10, 11)) +>this.x : Symbol(A.x, Decl(checkSuperCallBeforeThisAccess.ts, 0, 9)) +>this : Symbol(C1, Decl(checkSuperCallBeforeThisAccess.ts, 2, 1)) +>x : Symbol(A.x, Decl(checkSuperCallBeforeThisAccess.ts, 0, 9)) + + let a6 = () => super.x; +>a6 : Symbol(a6, Decl(checkSuperCallBeforeThisAccess.ts, 11, 11)) +>super.x : Symbol(A.x, Decl(checkSuperCallBeforeThisAccess.ts, 0, 9)) +>super : Symbol(A, Decl(checkSuperCallBeforeThisAccess.ts, 0, 0)) +>x : Symbol(A.x, Decl(checkSuperCallBeforeThisAccess.ts, 0, 9)) + + if (!!true) { + super(); +>super : Symbol(A, Decl(checkSuperCallBeforeThisAccess.ts, 0, 0)) + + let b1 = this; +>b1 : Symbol(b1, Decl(checkSuperCallBeforeThisAccess.ts, 14, 15)) +>this : Symbol(C1, Decl(checkSuperCallBeforeThisAccess.ts, 2, 1)) + + let b2 = this.x; +>b2 : Symbol(b2, Decl(checkSuperCallBeforeThisAccess.ts, 15, 15)) +>this.x : Symbol(A.x, Decl(checkSuperCallBeforeThisAccess.ts, 0, 9)) +>this : Symbol(C1, Decl(checkSuperCallBeforeThisAccess.ts, 2, 1)) +>x : Symbol(A.x, Decl(checkSuperCallBeforeThisAccess.ts, 0, 9)) + + let b3 = super.x; +>b3 : Symbol(b3, Decl(checkSuperCallBeforeThisAccess.ts, 16, 15)) +>super.x : Symbol(A.x, Decl(checkSuperCallBeforeThisAccess.ts, 0, 9)) +>super : Symbol(A, Decl(checkSuperCallBeforeThisAccess.ts, 0, 0)) +>x : Symbol(A.x, Decl(checkSuperCallBeforeThisAccess.ts, 0, 9)) + } + else { + let c1 = this; // Error +>c1 : Symbol(c1, Decl(checkSuperCallBeforeThisAccess.ts, 19, 15)) +>this : Symbol(C1, Decl(checkSuperCallBeforeThisAccess.ts, 2, 1)) + + let c2 = this.x; // Error +>c2 : Symbol(c2, Decl(checkSuperCallBeforeThisAccess.ts, 20, 15)) +>this.x : Symbol(A.x, Decl(checkSuperCallBeforeThisAccess.ts, 0, 9)) +>this : Symbol(C1, Decl(checkSuperCallBeforeThisAccess.ts, 2, 1)) +>x : Symbol(A.x, Decl(checkSuperCallBeforeThisAccess.ts, 0, 9)) + + let c3 = super.x; // Error +>c3 : Symbol(c3, Decl(checkSuperCallBeforeThisAccess.ts, 21, 15)) +>super.x : Symbol(A.x, Decl(checkSuperCallBeforeThisAccess.ts, 0, 9)) +>super : Symbol(A, Decl(checkSuperCallBeforeThisAccess.ts, 0, 0)) +>x : Symbol(A.x, Decl(checkSuperCallBeforeThisAccess.ts, 0, 9)) + } + if (!!true) { + switch (n) { +>n : Symbol(n, Decl(checkSuperCallBeforeThisAccess.ts, 5, 16)) + + case 1: + super(); +>super : Symbol(A, Decl(checkSuperCallBeforeThisAccess.ts, 0, 0)) + + let d1 = this.x; +>d1 : Symbol(d1, Decl(checkSuperCallBeforeThisAccess.ts, 27, 23)) +>this.x : Symbol(A.x, Decl(checkSuperCallBeforeThisAccess.ts, 0, 9)) +>this : Symbol(C1, Decl(checkSuperCallBeforeThisAccess.ts, 2, 1)) +>x : Symbol(A.x, Decl(checkSuperCallBeforeThisAccess.ts, 0, 9)) + + case 2: + let d2 = this.x; // Error +>d2 : Symbol(d2, Decl(checkSuperCallBeforeThisAccess.ts, 29, 23)) +>this.x : Symbol(A.x, Decl(checkSuperCallBeforeThisAccess.ts, 0, 9)) +>this : Symbol(C1, Decl(checkSuperCallBeforeThisAccess.ts, 2, 1)) +>x : Symbol(A.x, Decl(checkSuperCallBeforeThisAccess.ts, 0, 9)) + + default: + super(); +>super : Symbol(A, Decl(checkSuperCallBeforeThisAccess.ts, 0, 0)) + + let d3 = this.x; +>d3 : Symbol(d3, Decl(checkSuperCallBeforeThisAccess.ts, 32, 23)) +>this.x : Symbol(A.x, Decl(checkSuperCallBeforeThisAccess.ts, 0, 9)) +>this : Symbol(C1, Decl(checkSuperCallBeforeThisAccess.ts, 2, 1)) +>x : Symbol(A.x, Decl(checkSuperCallBeforeThisAccess.ts, 0, 9)) + } + let d4 = this.x; +>d4 : Symbol(d4, Decl(checkSuperCallBeforeThisAccess.ts, 34, 15)) +>this.x : Symbol(A.x, Decl(checkSuperCallBeforeThisAccess.ts, 0, 9)) +>this : Symbol(C1, Decl(checkSuperCallBeforeThisAccess.ts, 2, 1)) +>x : Symbol(A.x, Decl(checkSuperCallBeforeThisAccess.ts, 0, 9)) + } + if (!!true) { + let e1 = { w: !!true ? super() : 0 }; +>e1 : Symbol(e1, Decl(checkSuperCallBeforeThisAccess.ts, 37, 15)) +>w : Symbol(w, Decl(checkSuperCallBeforeThisAccess.ts, 37, 22)) +>super : Symbol(A, Decl(checkSuperCallBeforeThisAccess.ts, 0, 0)) + + let e2 = this.x; // Error +>e2 : Symbol(e2, Decl(checkSuperCallBeforeThisAccess.ts, 38, 15)) +>this.x : Symbol(A.x, Decl(checkSuperCallBeforeThisAccess.ts, 0, 9)) +>this : Symbol(C1, Decl(checkSuperCallBeforeThisAccess.ts, 2, 1)) +>x : Symbol(A.x, Decl(checkSuperCallBeforeThisAccess.ts, 0, 9)) + + let e3 = { w: !!true ? super() : super() }; +>e3 : Symbol(e3, Decl(checkSuperCallBeforeThisAccess.ts, 39, 15)) +>w : Symbol(w, Decl(checkSuperCallBeforeThisAccess.ts, 39, 22)) +>super : Symbol(A, Decl(checkSuperCallBeforeThisAccess.ts, 0, 0)) +>super : Symbol(A, Decl(checkSuperCallBeforeThisAccess.ts, 0, 0)) + + let e4 = this.x; +>e4 : Symbol(e4, Decl(checkSuperCallBeforeThisAccess.ts, 40, 15)) +>this.x : Symbol(A.x, Decl(checkSuperCallBeforeThisAccess.ts, 0, 9)) +>this : Symbol(C1, Decl(checkSuperCallBeforeThisAccess.ts, 2, 1)) +>x : Symbol(A.x, Decl(checkSuperCallBeforeThisAccess.ts, 0, 9)) + } + let f1 = this; // Error +>f1 : Symbol(f1, Decl(checkSuperCallBeforeThisAccess.ts, 42, 11)) +>this : Symbol(C1, Decl(checkSuperCallBeforeThisAccess.ts, 2, 1)) + + let f2 = this.x; // Error +>f2 : Symbol(f2, Decl(checkSuperCallBeforeThisAccess.ts, 43, 11)) +>this.x : Symbol(A.x, Decl(checkSuperCallBeforeThisAccess.ts, 0, 9)) +>this : Symbol(C1, Decl(checkSuperCallBeforeThisAccess.ts, 2, 1)) +>x : Symbol(A.x, Decl(checkSuperCallBeforeThisAccess.ts, 0, 9)) + + let f3 = super.x; // Error +>f3 : Symbol(f3, Decl(checkSuperCallBeforeThisAccess.ts, 44, 11)) +>super.x : Symbol(A.x, Decl(checkSuperCallBeforeThisAccess.ts, 0, 9)) +>super : Symbol(A, Decl(checkSuperCallBeforeThisAccess.ts, 0, 0)) +>x : Symbol(A.x, Decl(checkSuperCallBeforeThisAccess.ts, 0, 9)) + } +} + +// Repro from #38512 + +export class Foo { +>Foo : Symbol(Foo, Decl(checkSuperCallBeforeThisAccess.ts, 46, 1)) + + constructor(value: number) { +>value : Symbol(value, Decl(checkSuperCallBeforeThisAccess.ts, 51, 16)) + } +} + +export class BarCorrectlyFails extends Foo { +>BarCorrectlyFails : Symbol(BarCorrectlyFails, Decl(checkSuperCallBeforeThisAccess.ts, 53, 1)) +>Foo : Symbol(Foo, Decl(checkSuperCallBeforeThisAccess.ts, 46, 1)) + + constructor(something: boolean) { +>something : Symbol(something, Decl(checkSuperCallBeforeThisAccess.ts, 56, 16)) + + if (!something) { +>something : Symbol(something, Decl(checkSuperCallBeforeThisAccess.ts, 56, 16)) + + const value = this.bar(); // Error +>value : Symbol(value, Decl(checkSuperCallBeforeThisAccess.ts, 58, 17)) +>this.bar : Symbol(BarCorrectlyFails.bar, Decl(checkSuperCallBeforeThisAccess.ts, 64, 5)) +>this : Symbol(BarCorrectlyFails, Decl(checkSuperCallBeforeThisAccess.ts, 53, 1)) +>bar : Symbol(BarCorrectlyFails.bar, Decl(checkSuperCallBeforeThisAccess.ts, 64, 5)) + + super(value); +>super : Symbol(Foo, Decl(checkSuperCallBeforeThisAccess.ts, 46, 1)) +>value : Symbol(value, Decl(checkSuperCallBeforeThisAccess.ts, 58, 17)) + } + else { + super(1337); +>super : Symbol(Foo, Decl(checkSuperCallBeforeThisAccess.ts, 46, 1)) + } + } + bar(): number { return 4; } +>bar : Symbol(BarCorrectlyFails.bar, Decl(checkSuperCallBeforeThisAccess.ts, 64, 5)) +} + +export class BarIncorrectlyWorks extends Foo { +>BarIncorrectlyWorks : Symbol(BarIncorrectlyWorks, Decl(checkSuperCallBeforeThisAccess.ts, 66, 1)) +>Foo : Symbol(Foo, Decl(checkSuperCallBeforeThisAccess.ts, 46, 1)) + + constructor(something: boolean) { +>something : Symbol(something, Decl(checkSuperCallBeforeThisAccess.ts, 69, 16)) + + if (something) { +>something : Symbol(something, Decl(checkSuperCallBeforeThisAccess.ts, 69, 16)) + + super(1337); +>super : Symbol(Foo, Decl(checkSuperCallBeforeThisAccess.ts, 46, 1)) + } + else { + const value = this.bar(); // Error +>value : Symbol(value, Decl(checkSuperCallBeforeThisAccess.ts, 74, 17)) +>this.bar : Symbol(BarIncorrectlyWorks.bar, Decl(checkSuperCallBeforeThisAccess.ts, 77, 5)) +>this : Symbol(BarIncorrectlyWorks, Decl(checkSuperCallBeforeThisAccess.ts, 66, 1)) +>bar : Symbol(BarIncorrectlyWorks.bar, Decl(checkSuperCallBeforeThisAccess.ts, 77, 5)) + + super(value); +>super : Symbol(Foo, Decl(checkSuperCallBeforeThisAccess.ts, 46, 1)) +>value : Symbol(value, Decl(checkSuperCallBeforeThisAccess.ts, 74, 17)) + } + } + bar(): number { return 4; } +>bar : Symbol(BarIncorrectlyWorks.bar, Decl(checkSuperCallBeforeThisAccess.ts, 77, 5)) +} + diff --git a/tests/baselines/reference/checkSuperCallBeforeThisAccess.types b/tests/baselines/reference/checkSuperCallBeforeThisAccess.types new file mode 100644 index 0000000000000..7a6914107f204 --- /dev/null +++ b/tests/baselines/reference/checkSuperCallBeforeThisAccess.types @@ -0,0 +1,279 @@ +=== tests/cases/compiler/checkSuperCallBeforeThisAccess.ts === +class A { +>A : A + + x = 1; +>x : number +>1 : 1 +} + +class C1 extends A { +>C1 : C1 +>A : A + + constructor(n: number) { +>n : number + + let a1 = this; // Error +>a1 : this +>this : this + + let a2 = this.x; // Error +>a2 : number +>this.x : number +>this : this +>x : number + + let a3 = super.x; // Error +>a3 : number +>super.x : number +>super : A +>x : number + + let a4 = () => this; +>a4 : () => this +>() => this : () => this +>this : this + + let a5 = () => this.x; +>a5 : () => number +>() => this.x : () => number +>this.x : number +>this : this +>x : number + + let a6 = () => super.x; +>a6 : () => number +>() => super.x : () => number +>super.x : number +>super : A +>x : number + + if (!!true) { +>!!true : true +>!true : false +>true : true + + super(); +>super() : void +>super : typeof A + + let b1 = this; +>b1 : this +>this : this + + let b2 = this.x; +>b2 : number +>this.x : number +>this : this +>x : number + + let b3 = super.x; +>b3 : number +>super.x : number +>super : A +>x : number + } + else { + let c1 = this; // Error +>c1 : this +>this : this + + let c2 = this.x; // Error +>c2 : number +>this.x : number +>this : this +>x : number + + let c3 = super.x; // Error +>c3 : number +>super.x : number +>super : A +>x : number + } + if (!!true) { +>!!true : true +>!true : false +>true : true + + switch (n) { +>n : number + + case 1: +>1 : 1 + + super(); +>super() : void +>super : typeof A + + let d1 = this.x; +>d1 : number +>this.x : number +>this : this +>x : number + + case 2: +>2 : 2 + + let d2 = this.x; // Error +>d2 : number +>this.x : number +>this : this +>x : number + + default: + super(); +>super() : void +>super : typeof A + + let d3 = this.x; +>d3 : number +>this.x : number +>this : this +>x : number + } + let d4 = this.x; +>d4 : number +>this.x : number +>this : this +>x : number + } + if (!!true) { +>!!true : true +>!true : false +>true : true + + let e1 = { w: !!true ? super() : 0 }; +>e1 : { w: number | void; } +>{ w: !!true ? super() : 0 } : { w: number | void; } +>w : number | void +>!!true ? super() : 0 : void | 0 +>!!true : true +>!true : false +>true : true +>super() : void +>super : typeof A +>0 : 0 + + let e2 = this.x; // Error +>e2 : number +>this.x : number +>this : this +>x : number + + let e3 = { w: !!true ? super() : super() }; +>e3 : { w: void; } +>{ w: !!true ? super() : super() } : { w: void; } +>w : void +>!!true ? super() : super() : void +>!!true : true +>!true : false +>true : true +>super() : void +>super : typeof A +>super() : void +>super : typeof A + + let e4 = this.x; +>e4 : number +>this.x : number +>this : this +>x : number + } + let f1 = this; // Error +>f1 : this +>this : this + + let f2 = this.x; // Error +>f2 : number +>this.x : number +>this : this +>x : number + + let f3 = super.x; // Error +>f3 : number +>super.x : number +>super : A +>x : number + } +} + +// Repro from #38512 + +export class Foo { +>Foo : Foo + + constructor(value: number) { +>value : number + } +} + +export class BarCorrectlyFails extends Foo { +>BarCorrectlyFails : BarCorrectlyFails +>Foo : Foo + + constructor(something: boolean) { +>something : boolean + + if (!something) { +>!something : boolean +>something : boolean + + const value = this.bar(); // Error +>value : number +>this.bar() : number +>this.bar : () => number +>this : this +>bar : () => number + + super(value); +>super(value) : void +>super : typeof Foo +>value : number + } + else { + super(1337); +>super(1337) : void +>super : typeof Foo +>1337 : 1337 + } + } + bar(): number { return 4; } +>bar : () => number +>4 : 4 +} + +export class BarIncorrectlyWorks extends Foo { +>BarIncorrectlyWorks : BarIncorrectlyWorks +>Foo : Foo + + constructor(something: boolean) { +>something : boolean + + if (something) { +>something : boolean + + super(1337); +>super(1337) : void +>super : typeof Foo +>1337 : 1337 + } + else { + const value = this.bar(); // Error +>value : number +>this.bar() : number +>this.bar : () => number +>this : this +>bar : () => number + + super(value); +>super(value) : void +>super : typeof Foo +>value : number + } + } + bar(): number { return 4; } +>bar : () => number +>4 : 4 +} + diff --git a/tests/cases/compiler/checkSuperCallBeforeThisAccess.ts b/tests/cases/compiler/checkSuperCallBeforeThisAccess.ts new file mode 100644 index 0000000000000..1b97d7bea7a51 --- /dev/null +++ b/tests/cases/compiler/checkSuperCallBeforeThisAccess.ts @@ -0,0 +1,83 @@ +// @strict: true +// @target: esnext + +class A { + x = 1; +} + +class C1 extends A { + constructor(n: number) { + let a1 = this; // Error + let a2 = this.x; // Error + let a3 = super.x; // Error + let a4 = () => this; + let a5 = () => this.x; + let a6 = () => super.x; + if (!!true) { + super(); + let b1 = this; + let b2 = this.x; + let b3 = super.x; + } + else { + let c1 = this; // Error + let c2 = this.x; // Error + let c3 = super.x; // Error + } + if (!!true) { + switch (n) { + case 1: + super(); + let d1 = this.x; + case 2: + let d2 = this.x; // Error + default: + super(); + let d3 = this.x; + } + let d4 = this.x; + } + if (!!true) { + let e1 = { w: !!true ? super() : 0 }; + let e2 = this.x; // Error + let e3 = { w: !!true ? super() : super() }; + let e4 = this.x; + } + let f1 = this; // Error + let f2 = this.x; // Error + let f3 = super.x; // Error + } +} + +// Repro from #38512 + +export class Foo { + constructor(value: number) { + } +} + +export class BarCorrectlyFails extends Foo { + constructor(something: boolean) { + if (!something) { + const value = this.bar(); // Error + super(value); + } + else { + super(1337); + } + } + bar(): number { return 4; } +} + +export class BarIncorrectlyWorks extends Foo { + constructor(something: boolean) { + if (something) { + super(1337); + } + else { + const value = this.bar(); // Error + super(value); + } + } + bar(): number { return 4; } +}