Skip to content

Commit 990d2fa

Browse files
authored
Merge pull request #14307 from Microsoft/master-13893
[Master] Fix 13893: Fix runtime crash when class is used before declaration
2 parents b6dfa39 + a8ffb5c commit 990d2fa

File tree

258 files changed

+1947
-1689
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

258 files changed

+1947
-1689
lines changed

src/compiler/checker.ts

Lines changed: 26 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1068,9 +1068,10 @@ namespace ts {
10681068
// block-scoped variable and namespace module. However, only when we
10691069
// try to resolve name in /*1*/ which is used in variable position,
10701070
// we want to check for block-scoped
1071-
if (meaning & SymbolFlags.BlockScopedVariable) {
1071+
if (meaning & SymbolFlags.BlockScopedVariable ||
1072+
((meaning & SymbolFlags.Class || meaning & SymbolFlags.Enum) && (meaning & SymbolFlags.Value) === SymbolFlags.Value)) {
10721073
const exportOrLocalSymbol = getExportSymbolOfValueSymbolIfExported(result);
1073-
if (exportOrLocalSymbol.flags & SymbolFlags.BlockScopedVariable) {
1074+
if (exportOrLocalSymbol.flags & SymbolFlags.BlockScopedVariable || exportOrLocalSymbol.flags & SymbolFlags.Class || exportOrLocalSymbol.flags & SymbolFlags.Enum) {
10741075
checkResolvedBlockScopedVariable(exportOrLocalSymbol, errorLocation);
10751076
}
10761077
}
@@ -1183,14 +1184,22 @@ namespace ts {
11831184
}
11841185

11851186
function checkResolvedBlockScopedVariable(result: Symbol, errorLocation: Node): void {
1186-
Debug.assert((result.flags & SymbolFlags.BlockScopedVariable) !== 0);
1187+
Debug.assert(!!(result.flags & SymbolFlags.BlockScopedVariable || result.flags & SymbolFlags.Class || result.flags & SymbolFlags.Enum));
11871188
// Block-scoped variables cannot be used before their definition
1188-
const declaration = forEach(result.declarations, d => isBlockOrCatchScoped(d) ? d : undefined);
1189+
const declaration = forEach(result.declarations, d => isBlockOrCatchScoped(d) || isClassLike(d) || (d.kind === SyntaxKind.EnumDeclaration) ? d : undefined);
11891190

1190-
Debug.assert(declaration !== undefined, "Block-scoped variable declaration is undefined");
1191+
Debug.assert(declaration !== undefined, "Declaration to checkResolvedBlockScopedVariable is undefined");
11911192

11921193
if (!isInAmbientContext(declaration) && !isBlockScopedNameDeclaredBeforeUse(declaration, errorLocation)) {
1193-
error(errorLocation, Diagnostics.Block_scoped_variable_0_used_before_its_declaration, declarationNameToString(declaration.name));
1194+
if (result.flags & SymbolFlags.BlockScopedVariable) {
1195+
error(errorLocation, Diagnostics.Block_scoped_variable_0_used_before_its_declaration, declarationNameToString(declaration.name));
1196+
}
1197+
else if (result.flags & SymbolFlags.Class) {
1198+
error(errorLocation, Diagnostics.Class_0_used_before_its_declaration, declarationNameToString(declaration.name));
1199+
}
1200+
else if (result.flags & SymbolFlags.Enum) {
1201+
error(errorLocation, Diagnostics.Enum_0_used_before_its_declaration, declarationNameToString(declaration.name));
1202+
}
11941203
}
11951204
}
11961205

@@ -13322,10 +13331,17 @@ namespace ts {
1332213331
}
1332313332
return unknownType;
1332413333
}
13325-
if (prop.valueDeclaration &&
13326-
isInPropertyInitializer(node) &&
13327-
!isBlockScopedNameDeclaredBeforeUse(prop.valueDeclaration, right)) {
13328-
error(right, Diagnostics.Block_scoped_variable_0_used_before_its_declaration, right.text);
13334+
if (prop.valueDeclaration) {
13335+
if (isInPropertyInitializer(node) &&
13336+
!isBlockScopedNameDeclaredBeforeUse(prop.valueDeclaration, right)) {
13337+
error(right, Diagnostics.Block_scoped_variable_0_used_before_its_declaration, right.text);
13338+
}
13339+
if (prop.valueDeclaration.kind === SyntaxKind.ClassDeclaration &&
13340+
node.parent && node.parent.kind !== SyntaxKind.TypeReference &&
13341+
!isInAmbientContext(prop.valueDeclaration) &&
13342+
!isBlockScopedNameDeclaredBeforeUse(prop.valueDeclaration, right)) {
13343+
error(right, Diagnostics.Class_0_used_before_its_declaration, right.text);
13344+
}
1332913345
}
1333013346

1333113347
markPropertyAsReferenced(prop);
@@ -19572,14 +19588,6 @@ namespace ts {
1957219588
error(node.name || node, Diagnostics.A_mixin_class_must_have_a_constructor_with_a_single_rest_parameter_of_type_any);
1957319589
}
1957419590

19575-
if (baseType.symbol && baseType.symbol.valueDeclaration &&
19576-
!isInAmbientContext(baseType.symbol.valueDeclaration) &&
19577-
baseType.symbol.valueDeclaration.kind === SyntaxKind.ClassDeclaration) {
19578-
if (!isBlockScopedNameDeclaredBeforeUse(baseType.symbol.valueDeclaration, node)) {
19579-
error(baseTypeNode, Diagnostics.A_class_must_be_declared_after_its_base_class);
19580-
}
19581-
}
19582-
1958319591
if (!(staticBaseType.symbol && staticBaseType.symbol.flags & SymbolFlags.Class) && !(baseConstructorType.flags & TypeFlags.TypeVariable)) {
1958419592
// When the static base type is a "class-like" constructor function (but not actually a class), we verify
1958519593
// that all instantiated base constructor signatures return the same type. We can simply compare the type

src/compiler/diagnosticMessages.json

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1435,6 +1435,14 @@
14351435
"category": "Error",
14361436
"code": 2448
14371437
},
1438+
"Class '{0}' used before its declaration.": {
1439+
"category": "Error",
1440+
"code": 2449
1441+
},
1442+
"Enum '{0}' used before its declaration.": {
1443+
"category": "Error",
1444+
"code": 2450
1445+
},
14381446
"Cannot redeclare block-scoped variable '{0}'.": {
14391447
"category": "Error",
14401448
"code": 2451
@@ -2019,10 +2027,6 @@
20192027
"category": "Error",
20202028
"code": 2689
20212029
},
2022-
"A class must be declared after its base class.": {
2023-
"category": "Error",
2024-
"code": 2690
2025-
},
20262030
"An import path cannot end with a '{0}' extension. Consider importing '{1}' instead.": {
20272031
"category": "Error",
20282032
"code": 2691

tests/baselines/reference/ES5For-ofTypeCheck10.errors.txt

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,8 @@
1-
tests/cases/conformance/statements/for-ofStatements/ES5For-ofTypeCheck10.ts(1,15): error TS2495: Type 'StringIterator' is not an array type or a string type.
2-
tests/cases/conformance/statements/for-ofStatements/ES5For-ofTypeCheck10.ts(11,6): error TS2304: Cannot find name 'Symbol'.
1+
tests/cases/conformance/statements/for-ofStatements/ES5For-ofTypeCheck10.ts(10,6): error TS2304: Cannot find name 'Symbol'.
2+
tests/cases/conformance/statements/for-ofStatements/ES5For-ofTypeCheck10.ts(15,15): error TS2495: Type 'StringIterator' is not an array type or a string type.
33

44

55
==== tests/cases/conformance/statements/for-ofStatements/ES5For-ofTypeCheck10.ts (2 errors) ====
6-
for (var v of new StringIterator) { }
7-
~~~~~~~~~~~~~~~~~~
8-
!!! error TS2495: Type 'StringIterator' is not an array type or a string type.
96

107
// In ES3/5, you cannot for...of over an arbitrary iterable.
118
class StringIterator {
@@ -20,4 +17,8 @@ tests/cases/conformance/statements/for-ofStatements/ES5For-ofTypeCheck10.ts(11,6
2017
!!! error TS2304: Cannot find name 'Symbol'.
2118
return this;
2219
}
23-
}
20+
}
21+
22+
for (var v of new StringIterator) { }
23+
~~~~~~~~~~~~~~~~~~
24+
!!! error TS2495: Type 'StringIterator' is not an array type or a string type.

tests/baselines/reference/ES5For-ofTypeCheck10.js

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
//// [ES5For-ofTypeCheck10.ts]
2-
for (var v of new StringIterator) { }
32

43
// In ES3/5, you cannot for...of over an arbitrary iterable.
54
class StringIterator {
@@ -12,12 +11,11 @@ class StringIterator {
1211
[Symbol.iterator]() {
1312
return this;
1413
}
15-
}
14+
}
15+
16+
for (var v of new StringIterator) { }
1617

1718
//// [ES5For-ofTypeCheck10.js]
18-
for (var _i = 0, _a = new StringIterator; _i < _a.length; _i++) {
19-
var v = _a[_i];
20-
}
2119
// In ES3/5, you cannot for...of over an arbitrary iterable.
2220
var StringIterator = (function () {
2321
function StringIterator() {
@@ -33,3 +31,6 @@ var StringIterator = (function () {
3331
};
3432
return StringIterator;
3533
}());
34+
for (var _i = 0, _a = new StringIterator; _i < _a.length; _i++) {
35+
var v = _a[_i];
36+
}

tests/baselines/reference/ModuleAndClassWithSameNameAndCommonRoot.errors.txt

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
tests/cases/conformance/internalModules/DeclarationMerging/module.ts(2,19): error TS2433: A namespace declaration cannot be in a different file from a class or function with which it is merged.
22
tests/cases/conformance/internalModules/DeclarationMerging/simple.ts(1,8): error TS2434: A namespace declaration cannot be located prior to a class or function with which it is merged.
3+
tests/cases/conformance/internalModules/DeclarationMerging/simple.ts(2,31): error TS2449: Class 'A' used before its declaration.
34

45

56
==== tests/cases/conformance/internalModules/DeclarationMerging/module.ts (1 errors) ====
@@ -24,11 +25,13 @@ tests/cases/conformance/internalModules/DeclarationMerging/simple.ts(1,8): error
2425
}
2526
}
2627

27-
==== tests/cases/conformance/internalModules/DeclarationMerging/simple.ts (1 errors) ====
28+
==== tests/cases/conformance/internalModules/DeclarationMerging/simple.ts (2 errors) ====
2829
module A {
2930
~
3031
!!! error TS2434: A namespace declaration cannot be located prior to a class or function with which it is merged.
3132
export var Instance = new A();
33+
~
34+
!!! error TS2449: Class 'A' used before its declaration.
3235
}
3336

3437
// duplicate identifier

tests/baselines/reference/circularImportAlias.errors.txt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
tests/cases/conformance/internalModules/importDeclarations/circularImportAlias.ts(5,28): error TS2690: A class must be declared after its base class.
1+
tests/cases/conformance/internalModules/importDeclarations/circularImportAlias.ts(5,30): error TS2449: Class 'C' used before its declaration.
22

33

44
==== tests/cases/conformance/internalModules/importDeclarations/circularImportAlias.ts (1 errors) ====
@@ -7,8 +7,8 @@ tests/cases/conformance/internalModules/importDeclarations/circularImportAlias.t
77
module B {
88
export import a = A;
99
export class D extends a.C {
10-
~~~
11-
!!! error TS2690: A class must be declared after its base class.
10+
~
11+
!!! error TS2449: Class 'C' used before its declaration.
1212
id: number;
1313
}
1414
}

tests/baselines/reference/classAbstractInstantiations2.errors.txt

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,14 @@ tests/cases/conformance/classes/classDeclarations/classAbstractKeyword/classAbst
33
Cannot assign an abstract constructor type to a non-abstract constructor type.
44
tests/cases/conformance/classes/classDeclarations/classAbstractKeyword/classAbstractInstantiations2.ts(17,5): error TS2511: Cannot create an instance of the abstract class 'B'.
55
tests/cases/conformance/classes/classDeclarations/classAbstractKeyword/classAbstractInstantiations2.ts(21,1): error TS2511: Cannot create an instance of the abstract class 'B'.
6+
tests/cases/conformance/classes/classDeclarations/classAbstractKeyword/classAbstractInstantiations2.ts(23,15): error TS2449: Class 'C' used before its declaration.
67
tests/cases/conformance/classes/classDeclarations/classAbstractKeyword/classAbstractInstantiations2.ts(26,7): error TS2515: Non-abstract class 'C' does not implement inherited abstract member 'bar' from class 'B'.
78
tests/cases/conformance/classes/classDeclarations/classAbstractKeyword/classAbstractInstantiations2.ts(46,5): error TS2391: Function implementation is missing or not immediately following the declaration.
89
tests/cases/conformance/classes/classDeclarations/classAbstractKeyword/classAbstractInstantiations2.ts(46,5): error TS2512: Overload signatures must all be abstract or non-abstract.
910
tests/cases/conformance/classes/classDeclarations/classAbstractKeyword/classAbstractInstantiations2.ts(50,5): error TS1244: Abstract methods can only appear within an abstract class.
1011

1112

12-
==== tests/cases/conformance/classes/classDeclarations/classAbstractKeyword/classAbstractInstantiations2.ts (8 errors) ====
13+
==== tests/cases/conformance/classes/classDeclarations/classAbstractKeyword/classAbstractInstantiations2.ts (9 errors) ====
1314
class A {
1415
// ...
1516
}
@@ -42,6 +43,8 @@ tests/cases/conformance/classes/classDeclarations/classAbstractKeyword/classAbst
4243
!!! error TS2511: Cannot create an instance of the abstract class 'B'.
4344

4445
var x : any = C;
46+
~
47+
!!! error TS2449: Class 'C' used before its declaration.
4548
new x; // okay -- undefined behavior at runtime
4649

4750
class C extends B { } // error -- not declared abstract
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
//// [classDeclarationCheckUsedBeforeDefinitionInFunctionDeclaration.ts]
2+
function f() {
3+
new C2(); // OK
4+
}
5+
class C2 { }
6+
7+
//// [classDeclarationCheckUsedBeforeDefinitionInFunctionDeclaration.js]
8+
function f() {
9+
new C2(); // OK
10+
}
11+
var C2 = (function () {
12+
function C2() {
13+
}
14+
return C2;
15+
}());
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
=== tests/cases/compiler/classDeclarationCheckUsedBeforeDefinitionInFunctionDeclaration.ts ===
2+
function f() {
3+
>f : Symbol(f, Decl(classDeclarationCheckUsedBeforeDefinitionInFunctionDeclaration.ts, 0, 0))
4+
5+
new C2(); // OK
6+
>C2 : Symbol(C2, Decl(classDeclarationCheckUsedBeforeDefinitionInFunctionDeclaration.ts, 2, 1))
7+
}
8+
class C2 { }
9+
>C2 : Symbol(C2, Decl(classDeclarationCheckUsedBeforeDefinitionInFunctionDeclaration.ts, 2, 1))
10+
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
=== tests/cases/compiler/classDeclarationCheckUsedBeforeDefinitionInFunctionDeclaration.ts ===
2+
function f() {
3+
>f : () => void
4+
5+
new C2(); // OK
6+
>new C2() : C2
7+
>C2 : typeof C2
8+
}
9+
class C2 { }
10+
>C2 : C2
11+
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
//// [classDeclarationCheckUsedBeforeDefinitionInItself.ts]
2+
class C3 {
3+
static intance = new C3(); // ok
4+
}
5+
6+
//// [classDeclarationCheckUsedBeforeDefinitionInItself.js]
7+
class C3 {
8+
}
9+
C3.intance = new C3(); // ok
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
=== tests/cases/compiler/classDeclarationCheckUsedBeforeDefinitionInItself.ts ===
2+
class C3 {
3+
>C3 : Symbol(C3, Decl(classDeclarationCheckUsedBeforeDefinitionInItself.ts, 0, 0))
4+
5+
static intance = new C3(); // ok
6+
>intance : Symbol(C3.intance, Decl(classDeclarationCheckUsedBeforeDefinitionInItself.ts, 0, 10))
7+
>C3 : Symbol(C3, Decl(classDeclarationCheckUsedBeforeDefinitionInItself.ts, 0, 0))
8+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
=== tests/cases/compiler/classDeclarationCheckUsedBeforeDefinitionInItself.ts ===
2+
class C3 {
3+
>C3 : C3
4+
5+
static intance = new C3(); // ok
6+
>intance : C3
7+
>new C3() : C3
8+
>C3 : typeof C3
9+
}
Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
11
//// [classDoesNotDependOnBaseTypes.ts]
2-
var x: StringTree;
3-
if (typeof x !== "string") {
4-
x[0] = "";
5-
x[0] = new StringTreeCollection;
6-
}
7-
82
type StringTree = string | StringTreeCollection;
93
class StringTreeCollectionBase {
104
[n: number]: StringTree;
115
}
126

13-
class StringTreeCollection extends StringTreeCollectionBase { }
7+
class StringTreeCollection extends StringTreeCollectionBase { }
8+
9+
var x: StringTree;
10+
if (typeof x !== "string") {
11+
x[0] = "";
12+
x[0] = new StringTreeCollection;
13+
}
1414

1515
//// [classDoesNotDependOnBaseTypes.js]
1616
var __extends = (this && this.__extends) || (function () {
@@ -23,11 +23,6 @@ var __extends = (this && this.__extends) || (function () {
2323
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
2424
};
2525
})();
26-
var x;
27-
if (typeof x !== "string") {
28-
x[0] = "";
29-
x[0] = new StringTreeCollection;
30-
}
3126
var StringTreeCollectionBase = (function () {
3227
function StringTreeCollectionBase() {
3328
}
@@ -40,3 +35,8 @@ var StringTreeCollection = (function (_super) {
4035
}
4136
return StringTreeCollection;
4237
}(StringTreeCollectionBase));
38+
var x;
39+
if (typeof x !== "string") {
40+
x[0] = "";
41+
x[0] = new StringTreeCollection;
42+
}
Lines changed: 21 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,31 @@
11
=== tests/cases/conformance/types/typeAliases/classDoesNotDependOnBaseTypes.ts ===
2-
var x: StringTree;
3-
>x : Symbol(x, Decl(classDoesNotDependOnBaseTypes.ts, 0, 3))
4-
>StringTree : Symbol(StringTree, Decl(classDoesNotDependOnBaseTypes.ts, 4, 1))
5-
6-
if (typeof x !== "string") {
7-
>x : Symbol(x, Decl(classDoesNotDependOnBaseTypes.ts, 0, 3))
8-
9-
x[0] = "";
10-
>x : Symbol(x, Decl(classDoesNotDependOnBaseTypes.ts, 0, 3))
11-
12-
x[0] = new StringTreeCollection;
13-
>x : Symbol(x, Decl(classDoesNotDependOnBaseTypes.ts, 0, 3))
14-
>StringTreeCollection : Symbol(StringTreeCollection, Decl(classDoesNotDependOnBaseTypes.ts, 9, 1))
15-
}
16-
172
type StringTree = string | StringTreeCollection;
18-
>StringTree : Symbol(StringTree, Decl(classDoesNotDependOnBaseTypes.ts, 4, 1))
19-
>StringTreeCollection : Symbol(StringTreeCollection, Decl(classDoesNotDependOnBaseTypes.ts, 9, 1))
3+
>StringTree : Symbol(StringTree, Decl(classDoesNotDependOnBaseTypes.ts, 0, 0))
4+
>StringTreeCollection : Symbol(StringTreeCollection, Decl(classDoesNotDependOnBaseTypes.ts, 3, 1))
205

216
class StringTreeCollectionBase {
22-
>StringTreeCollectionBase : Symbol(StringTreeCollectionBase, Decl(classDoesNotDependOnBaseTypes.ts, 6, 48))
7+
>StringTreeCollectionBase : Symbol(StringTreeCollectionBase, Decl(classDoesNotDependOnBaseTypes.ts, 0, 48))
238

249
[n: number]: StringTree;
25-
>n : Symbol(n, Decl(classDoesNotDependOnBaseTypes.ts, 8, 5))
26-
>StringTree : Symbol(StringTree, Decl(classDoesNotDependOnBaseTypes.ts, 4, 1))
10+
>n : Symbol(n, Decl(classDoesNotDependOnBaseTypes.ts, 2, 5))
11+
>StringTree : Symbol(StringTree, Decl(classDoesNotDependOnBaseTypes.ts, 0, 0))
2712
}
2813

2914
class StringTreeCollection extends StringTreeCollectionBase { }
30-
>StringTreeCollection : Symbol(StringTreeCollection, Decl(classDoesNotDependOnBaseTypes.ts, 9, 1))
31-
>StringTreeCollectionBase : Symbol(StringTreeCollectionBase, Decl(classDoesNotDependOnBaseTypes.ts, 6, 48))
15+
>StringTreeCollection : Symbol(StringTreeCollection, Decl(classDoesNotDependOnBaseTypes.ts, 3, 1))
16+
>StringTreeCollectionBase : Symbol(StringTreeCollectionBase, Decl(classDoesNotDependOnBaseTypes.ts, 0, 48))
3217

18+
var x: StringTree;
19+
>x : Symbol(x, Decl(classDoesNotDependOnBaseTypes.ts, 7, 3))
20+
>StringTree : Symbol(StringTree, Decl(classDoesNotDependOnBaseTypes.ts, 0, 0))
21+
22+
if (typeof x !== "string") {
23+
>x : Symbol(x, Decl(classDoesNotDependOnBaseTypes.ts, 7, 3))
24+
25+
x[0] = "";
26+
>x : Symbol(x, Decl(classDoesNotDependOnBaseTypes.ts, 7, 3))
27+
28+
x[0] = new StringTreeCollection;
29+
>x : Symbol(x, Decl(classDoesNotDependOnBaseTypes.ts, 7, 3))
30+
>StringTreeCollection : Symbol(StringTreeCollection, Decl(classDoesNotDependOnBaseTypes.ts, 3, 1))
31+
}

0 commit comments

Comments
 (0)