Skip to content

Commit 7306b13

Browse files
author
Andy
authored
Don't issue a use-before-declared error for a property that exists in a superclass (#17910)
* Don't issue a use-before-declared error for a property that exists in a superclass * Simplify isInPropertyInitializer * Respond to PR comments
1 parent 30b3cb0 commit 7306b13

File tree

4 files changed

+198
-22
lines changed

4 files changed

+198
-22
lines changed

src/compiler/checker.ts

Lines changed: 48 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -14671,18 +14671,8 @@ namespace ts {
1467114671
}
1467214672
return unknownType;
1467314673
}
14674-
if (prop.valueDeclaration) {
14675-
if (isInPropertyInitializer(node) &&
14676-
!isBlockScopedNameDeclaredBeforeUse(prop.valueDeclaration, right)) {
14677-
error(right, Diagnostics.Block_scoped_variable_0_used_before_its_declaration, unescapeLeadingUnderscores(right.escapedText));
14678-
}
14679-
if (prop.valueDeclaration.kind === SyntaxKind.ClassDeclaration &&
14680-
node.parent && node.parent.kind !== SyntaxKind.TypeReference &&
14681-
!isInAmbientContext(prop.valueDeclaration) &&
14682-
!isBlockScopedNameDeclaredBeforeUse(prop.valueDeclaration, right)) {
14683-
error(right, Diagnostics.Class_0_used_before_its_declaration, unescapeLeadingUnderscores(right.escapedText));
14684-
}
14685-
}
14674+
14675+
checkPropertyNotUsedBeforeDeclaration(prop, node, right);
1468614676

1468714677
markPropertyAsReferenced(prop);
1468814678

@@ -14712,6 +14702,52 @@ namespace ts {
1471214702
return assignmentKind ? getBaseTypeOfLiteralType(flowType) : flowType;
1471314703
}
1471414704

14705+
function checkPropertyNotUsedBeforeDeclaration(prop: Symbol, node: PropertyAccessExpression | QualifiedName, right: Identifier): void {
14706+
const { valueDeclaration } = prop;
14707+
if (!valueDeclaration) {
14708+
return;
14709+
}
14710+
14711+
if (findAncestor(node, node => node.kind === SyntaxKind.PropertyDeclaration ? true : isExpression(node) ? false : "quit") &&
14712+
!isBlockScopedNameDeclaredBeforeUse(valueDeclaration, right)
14713+
&& !isPropertyDeclaredInAncestorClass(prop)) {
14714+
error(right, Diagnostics.Block_scoped_variable_0_used_before_its_declaration, unescapeLeadingUnderscores(right.escapedText));
14715+
}
14716+
else if (valueDeclaration.kind === SyntaxKind.ClassDeclaration &&
14717+
node.parent.kind !== SyntaxKind.TypeReference &&
14718+
!isInAmbientContext(valueDeclaration) &&
14719+
!isBlockScopedNameDeclaredBeforeUse(valueDeclaration, right)) {
14720+
error(right, Diagnostics.Class_0_used_before_its_declaration, unescapeLeadingUnderscores(right.escapedText));
14721+
}
14722+
}
14723+
14724+
/**
14725+
* It's possible that "prop.valueDeclaration" is a local declaration, but the property was also declared in a superclass.
14726+
* In that case we won't consider it used before its declaration, because it gets its value from the superclass' declaration.
14727+
*/
14728+
function isPropertyDeclaredInAncestorClass(prop: Symbol): boolean {
14729+
let classType = getTypeOfSymbol(prop.parent) as InterfaceType;
14730+
while (true) {
14731+
classType = getSuperClass(classType);
14732+
if (!classType) {
14733+
return false;
14734+
}
14735+
const superProperty = getPropertyOfType(classType, prop.escapedName);
14736+
if (superProperty && superProperty.valueDeclaration) {
14737+
return true;
14738+
}
14739+
}
14740+
}
14741+
14742+
function getSuperClass(classType: InterfaceType): InterfaceType | undefined {
14743+
const x = getBaseTypes(classType);
14744+
if (x.length === 0) {
14745+
return undefined;
14746+
}
14747+
Debug.assert(x.length === 1);
14748+
return x[0] as InterfaceType;
14749+
}
14750+
1471514751
function reportNonexistentProperty(propNode: Identifier, containingType: Type) {
1471614752
let errorInfo: DiagnosticMessageChain;
1471714753
if (containingType.flags & TypeFlags.Union && !(containingType.flags & TypeFlags.Primitive)) {
@@ -14832,16 +14868,6 @@ namespace ts {
1483214868
}
1483314869
}
1483414870

14835-
function isInPropertyInitializer(node: Node): boolean {
14836-
while (node) {
14837-
if (node.parent && node.parent.kind === SyntaxKind.PropertyDeclaration && (node.parent as PropertyDeclaration).initializer === node) {
14838-
return true;
14839-
}
14840-
node = node.parent;
14841-
}
14842-
return false;
14843-
}
14844-
1484514871
function isValidPropertyAccess(node: PropertyAccessExpression | QualifiedName, propertyName: __String): boolean {
1484614872
const left = node.kind === SyntaxKind.PropertyAccessExpression
1484714873
? (<PropertyAccessExpression>node).expression
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
tests/cases/compiler/useBeforeDeclaration_superClass.ts(25,18): error TS2448: Block-scoped variable 'x' used before its declaration.
2+
3+
4+
==== tests/cases/compiler/useBeforeDeclaration_superClass.ts (1 errors) ====
5+
class C {
6+
x = 0;
7+
}
8+
class D extends C {
9+
// Not an error -- this will access the parent's initialized value for `x`, not the one on the child.
10+
old_x = this.x;
11+
x = 1;
12+
}
13+
14+
// Test that it works on chains of classes
15+
class X {
16+
x = 0;
17+
}
18+
class Y extends X {}
19+
class Z extends Y {
20+
old_x = this.x;
21+
x = 1;
22+
}
23+
24+
// Interface doesn't count
25+
interface I {
26+
x: number;
27+
}
28+
class J implements I {
29+
old_x = this.x;
30+
~
31+
!!! error TS2448: Block-scoped variable 'x' used before its declaration.
32+
x = 1;
33+
}
34+
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
//// [useBeforeDeclaration_superClass.ts]
2+
class C {
3+
x = 0;
4+
}
5+
class D extends C {
6+
// Not an error -- this will access the parent's initialized value for `x`, not the one on the child.
7+
old_x = this.x;
8+
x = 1;
9+
}
10+
11+
// Test that it works on chains of classes
12+
class X {
13+
x = 0;
14+
}
15+
class Y extends X {}
16+
class Z extends Y {
17+
old_x = this.x;
18+
x = 1;
19+
}
20+
21+
// Interface doesn't count
22+
interface I {
23+
x: number;
24+
}
25+
class J implements I {
26+
old_x = this.x;
27+
x = 1;
28+
}
29+
30+
31+
//// [useBeforeDeclaration_superClass.js]
32+
var __extends = (this && this.__extends) || (function () {
33+
var extendStatics = Object.setPrototypeOf ||
34+
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
35+
function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
36+
return function (d, b) {
37+
extendStatics(d, b);
38+
function __() { this.constructor = d; }
39+
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
40+
};
41+
})();
42+
var C = /** @class */ (function () {
43+
function C() {
44+
this.x = 0;
45+
}
46+
return C;
47+
}());
48+
var D = /** @class */ (function (_super) {
49+
__extends(D, _super);
50+
function D() {
51+
var _this = _super !== null && _super.apply(this, arguments) || this;
52+
// Not an error -- this will access the parent's initialized value for `x`, not the one on the child.
53+
_this.old_x = _this.x;
54+
_this.x = 1;
55+
return _this;
56+
}
57+
return D;
58+
}(C));
59+
// Test that it works on chains of classes
60+
var X = /** @class */ (function () {
61+
function X() {
62+
this.x = 0;
63+
}
64+
return X;
65+
}());
66+
var Y = /** @class */ (function (_super) {
67+
__extends(Y, _super);
68+
function Y() {
69+
return _super !== null && _super.apply(this, arguments) || this;
70+
}
71+
return Y;
72+
}(X));
73+
var Z = /** @class */ (function (_super) {
74+
__extends(Z, _super);
75+
function Z() {
76+
var _this = _super !== null && _super.apply(this, arguments) || this;
77+
_this.old_x = _this.x;
78+
_this.x = 1;
79+
return _this;
80+
}
81+
return Z;
82+
}(Y));
83+
var J = /** @class */ (function () {
84+
function J() {
85+
this.old_x = this.x;
86+
this.x = 1;
87+
}
88+
return J;
89+
}());
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
class C {
2+
x = 0;
3+
}
4+
class D extends C {
5+
// Not an error -- this will access the parent's initialized value for `x`, not the one on the child.
6+
old_x = this.x;
7+
x = 1;
8+
}
9+
10+
// Test that it works on chains of classes
11+
class X {
12+
x = 0;
13+
}
14+
class Y extends X {}
15+
class Z extends Y {
16+
old_x = this.x;
17+
x = 1;
18+
}
19+
20+
// Interface doesn't count
21+
interface I {
22+
x: number;
23+
}
24+
class J implements I {
25+
old_x = this.x;
26+
x = 1;
27+
}

0 commit comments

Comments
 (0)