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+

0 commit comments

Comments
 (0)