Skip to content

Commit 1296f24

Browse files
authored
Merge pull request #14074 from Microsoft/error-on-decl-of-extends-intersection
Error when emitting the declaration for an exported class that extends an intersection
2 parents ca6f1c3 + c2b2c78 commit 1296f24

15 files changed

+275
-99
lines changed

src/compiler/checker.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21392,6 +21392,9 @@ namespace ts {
2139221392
const classType = <InterfaceType>getDeclaredTypeOfSymbol(getSymbolOfNode(node));
2139321393
resolveBaseTypesOfClass(classType);
2139421394
const baseType = classType.resolvedBaseTypes.length ? classType.resolvedBaseTypes[0] : unknownType;
21395+
if (!baseType.symbol) {
21396+
writer.reportIllegalExtends();
21397+
}
2139521398
getSymbolDisplayBuilder().buildTypeDisplay(baseType, writer, enclosingDeclaration, flags);
2139621399
}
2139721400

src/compiler/declarationEmitter.ts

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,7 @@ namespace ts {
190190
const writer = <EmitTextWriterWithSymbolWriter>createTextWriter(newLine);
191191
writer.trackSymbol = trackSymbol;
192192
writer.reportInaccessibleThisError = reportInaccessibleThisError;
193+
writer.reportIllegalExtends = reportIllegalExtends;
193194
writer.writeKeyword = writer.write;
194195
writer.writeOperator = writer.write;
195196
writer.writePunctuation = writer.write;
@@ -313,6 +314,14 @@ namespace ts {
313314
recordTypeReferenceDirectivesIfNecessary(resolver.getTypeReferenceDirectivesForSymbol(symbol, meaning));
314315
}
315316

317+
function reportIllegalExtends() {
318+
if (errorNameNode) {
319+
reportedDeclarationError = true;
320+
emitterDiagnostics.add(createDiagnosticForNode(errorNameNode, Diagnostics.extends_clause_of_exported_class_0_refers_to_a_type_whose_name_cannot_be_referenced,
321+
declarationNameToString(errorNameNode)));
322+
}
323+
}
324+
316325
function reportInaccessibleThisError() {
317326
if (errorNameNode) {
318327
reportedDeclarationError = true;
@@ -1088,7 +1097,7 @@ namespace ts {
10881097
}
10891098
}
10901099

1091-
function emitHeritageClause(typeReferences: ExpressionWithTypeArguments[], isImplementsList: boolean) {
1100+
function emitHeritageClause(className: Identifier, typeReferences: ExpressionWithTypeArguments[], isImplementsList: boolean) {
10921101
if (typeReferences) {
10931102
write(isImplementsList ? " implements " : " extends ");
10941103
emitCommaList(typeReferences, emitTypeOfTypeReference);
@@ -1103,7 +1112,9 @@ namespace ts {
11031112
}
11041113
else {
11051114
writer.getSymbolAccessibilityDiagnostic = getHeritageClauseVisibilityError;
1115+
errorNameNode = className;
11061116
resolver.writeBaseConstructorTypeOfClass(<ClassLikeDeclaration>enclosingDeclaration, enclosingDeclaration, TypeFormatFlags.UseTypeOfFunction | TypeFormatFlags.UseTypeAliasValue, writer);
1117+
errorNameNode = undefined;
11071118
}
11081119

11091120
function getHeritageClauseVisibilityError(): SymbolAccessibilityDiagnostic {
@@ -1113,11 +1124,11 @@ namespace ts {
11131124
// Class or Interface implemented/extended is inaccessible
11141125
diagnosticMessage = isImplementsList ?
11151126
Diagnostics.Implements_clause_of_exported_class_0_has_or_is_using_private_name_1 :
1116-
Diagnostics.Extends_clause_of_exported_class_0_has_or_is_using_private_name_1;
1127+
Diagnostics.extends_clause_of_exported_class_0_has_or_is_using_private_name_1;
11171128
}
11181129
else {
11191130
// interface is inaccessible
1120-
diagnosticMessage = Diagnostics.Extends_clause_of_exported_interface_0_has_or_is_using_private_name_1;
1131+
diagnosticMessage = Diagnostics.extends_clause_of_exported_interface_0_has_or_is_using_private_name_1;
11211132
}
11221133

11231134
return {
@@ -1153,9 +1164,10 @@ namespace ts {
11531164
emitTypeParameters(node.typeParameters);
11541165
const baseTypeNode = getClassExtendsHeritageClauseElement(node);
11551166
if (baseTypeNode) {
1156-
emitHeritageClause([baseTypeNode], /*isImplementsList*/ false);
1167+
node.name
1168+
emitHeritageClause(node.name, [baseTypeNode], /*isImplementsList*/ false);
11571169
}
1158-
emitHeritageClause(getClassImplementsHeritageClauseElements(node), /*isImplementsList*/ true);
1170+
emitHeritageClause(node.name, getClassImplementsHeritageClauseElements(node), /*isImplementsList*/ true);
11591171
write(" {");
11601172
writeLine();
11611173
increaseIndent();
@@ -1177,7 +1189,7 @@ namespace ts {
11771189
emitTypeParameters(node.typeParameters);
11781190
const interfaceExtendsTypes = filter(getInterfaceBaseTypeNodes(node), base => isEntityNameExpression(base.expression));
11791191
if (interfaceExtendsTypes && interfaceExtendsTypes.length) {
1180-
emitHeritageClause(interfaceExtendsTypes, /*isImplementsList*/ false);
1192+
emitHeritageClause(node.name, interfaceExtendsTypes, /*isImplementsList*/ false);
11811193
}
11821194
write(" {");
11831195
writeLine();

src/compiler/diagnosticMessages.json

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2108,11 +2108,11 @@
21082108
"category": "Error",
21092109
"code": 4019
21102110
},
2111-
"Extends clause of exported class '{0}' has or is using private name '{1}'.": {
2111+
"'extends' clause of exported class '{0}' has or is using private name '{1}'.": {
21122112
"category": "Error",
21132113
"code": 4020
21142114
},
2115-
"Extends clause of exported interface '{0}' has or is using private name '{1}'.": {
2115+
"'extends' clause of exported interface '{0}' has or is using private name '{1}'.": {
21162116
"category": "Error",
21172117
"code": 4022
21182118
},
@@ -2364,6 +2364,10 @@
23642364
"category": "Error",
23652365
"code": 4092
23662366
},
2367+
"'extends' clause of exported class '{0}' refers to a type whose name cannot be referenced.": {
2368+
"category": "Error",
2369+
"code": 4093
2370+
},
23672371

23682372
"The current host does not support the '{0}' option.": {
23692373
"category": "Error",
@@ -3177,7 +3181,7 @@
31773181
"category": "Error",
31783182
"code": 8016
31793183
},
3180-
"Only identifiers/qualified-names with optional type arguments are currently supported in a class 'extends' clauses.": {
3184+
"Only identifiers/qualified-names with optional type arguments are currently supported in a class 'extends' clause.": {
31813185
"category": "Error",
31823186
"code": 9002
31833187
},

src/compiler/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2480,6 +2480,7 @@
24802480
// with import statements it previously saw (but chose not to emit).
24812481
trackSymbol(symbol: Symbol, enclosingDeclaration?: Node, meaning?: SymbolFlags): void;
24822482
reportInaccessibleThisError(): void;
2483+
reportIllegalExtends(): void;
24832484
}
24842485

24852486
export const enum TypeFormatFlags {

src/compiler/utilities.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,8 @@ namespace ts {
6767
decreaseIndent: noop,
6868
clear: () => str = "",
6969
trackSymbol: noop,
70-
reportInaccessibleThisError: noop
70+
reportInaccessibleThisError: noop,
71+
reportIllegalExtends: noop
7172
};
7273
}
7374

src/services/utilities.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1167,7 +1167,8 @@ namespace ts {
11671167
decreaseIndent: () => { indent--; },
11681168
clear: resetWriter,
11691169
trackSymbol: noop,
1170-
reportInaccessibleThisError: noop
1170+
reportInaccessibleThisError: noop,
1171+
reportIllegalExtends: noop
11711172
};
11721173

11731174
function writeIndent() {
@@ -1387,4 +1388,4 @@ namespace ts {
13871388
// First token is the open curly, this is where we want to put the 'super' call.
13881389
return constructor.body.getFirstToken(sourceFile).getEnd();
13891390
}
1390-
}
1391+
}

tests/baselines/reference/declarationEmitExpressionInExtends3.errors.txt

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
tests/cases/compiler/declarationEmitExpressionInExtends3.ts(29,30): error TS4020: Extends clause of exported class 'MyClass' has or is using private name 'LocalClass'.
2-
tests/cases/compiler/declarationEmitExpressionInExtends3.ts(37,31): error TS4020: Extends clause of exported class 'MyClass3' has or is using private name 'LocalInterface'.
1+
tests/cases/compiler/declarationEmitExpressionInExtends3.ts(29,30): error TS4020: 'extends' clause of exported class 'MyClass' has or is using private name 'LocalClass'.
2+
tests/cases/compiler/declarationEmitExpressionInExtends3.ts(37,31): error TS4020: 'extends' clause of exported class 'MyClass3' has or is using private name 'LocalInterface'.
33

44

55
==== tests/cases/compiler/declarationEmitExpressionInExtends3.ts (2 errors) ====
@@ -33,7 +33,7 @@ tests/cases/compiler/declarationEmitExpressionInExtends3.ts(37,31): error TS4020
3333

3434
export class MyClass extends getLocalClass<LocalInterface>(undefined)<string, number> { // error LocalClass is inaccisible
3535
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
36-
!!! error TS4020: Extends clause of exported class 'MyClass' has or is using private name 'LocalClass'.
36+
!!! error TS4020: 'extends' clause of exported class 'MyClass' has or is using private name 'LocalClass'.
3737
}
3838

3939

@@ -43,7 +43,7 @@ tests/cases/compiler/declarationEmitExpressionInExtends3.ts(37,31): error TS4020
4343

4444
export class MyClass3 extends getExportedClass<LocalInterface>(undefined)<LocalInterface> { // Error LocalInterface is inaccisble
4545
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
46-
!!! error TS4020: Extends clause of exported class 'MyClass3' has or is using private name 'LocalInterface'.
46+
!!! error TS4020: 'extends' clause of exported class 'MyClass3' has or is using private name 'LocalInterface'.
4747
}
4848

4949

tests/baselines/reference/declarationEmitExpressionInExtends4.errors.txt

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
tests/cases/compiler/declarationEmitExpressionInExtends4.ts(2,10): error TS4060: Return type of exported function has or is using private name 'D'.
2+
tests/cases/compiler/declarationEmitExpressionInExtends4.ts(6,7): error TS4093: 'extends' clause of exported class 'C' refers to a type whose name cannot be referenced.
23
tests/cases/compiler/declarationEmitExpressionInExtends4.ts(6,17): error TS2315: Type 'D' is not generic.
4+
tests/cases/compiler/declarationEmitExpressionInExtends4.ts(10,7): error TS4093: 'extends' clause of exported class 'C2' refers to a type whose name cannot be referenced.
35
tests/cases/compiler/declarationEmitExpressionInExtends4.ts(10,18): error TS2304: Cannot find name 'SomeUndefinedFunction'.
46
tests/cases/compiler/declarationEmitExpressionInExtends4.ts(15,18): error TS2304: Cannot find name 'SomeUndefinedFunction'.
5-
tests/cases/compiler/declarationEmitExpressionInExtends4.ts(15,18): error TS4020: Extends clause of exported class 'C3' has or is using private name 'SomeUndefinedFunction'.
7+
tests/cases/compiler/declarationEmitExpressionInExtends4.ts(15,18): error TS4020: 'extends' clause of exported class 'C3' has or is using private name 'SomeUndefinedFunction'.
68

79

8-
==== tests/cases/compiler/declarationEmitExpressionInExtends4.ts (5 errors) ====
10+
==== tests/cases/compiler/declarationEmitExpressionInExtends4.ts (7 errors) ====
911

1012
function getSomething() {
1113
~~~~~~~~~~~~
@@ -14,12 +16,16 @@ tests/cases/compiler/declarationEmitExpressionInExtends4.ts(15,18): error TS4020
1416
}
1517

1618
class C extends getSomething()<number, string> {
19+
~
20+
!!! error TS4093: 'extends' clause of exported class 'C' refers to a type whose name cannot be referenced.
1721
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1822
!!! error TS2315: Type 'D' is not generic.
1923

2024
}
2125

2226
class C2 extends SomeUndefinedFunction()<number, string> {
27+
~~
28+
!!! error TS4093: 'extends' clause of exported class 'C2' refers to a type whose name cannot be referenced.
2329
~~~~~~~~~~~~~~~~~~~~~
2430
!!! error TS2304: Cannot find name 'SomeUndefinedFunction'.
2531

@@ -30,6 +36,6 @@ tests/cases/compiler/declarationEmitExpressionInExtends4.ts(15,18): error TS4020
3036
~~~~~~~~~~~~~~~~~~~~~
3137
!!! error TS2304: Cannot find name 'SomeUndefinedFunction'.
3238
~~~~~~~~~~~~~~~~~~~~~
33-
!!! error TS4020: Extends clause of exported class 'C3' has or is using private name 'SomeUndefinedFunction'.
39+
!!! error TS4020: 'extends' clause of exported class 'C3' has or is using private name 'SomeUndefinedFunction'.
3440

3541
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
tests/cases/compiler/FinalClass.ts(4,14): error TS4093: 'extends' clause of exported class 'MyExtendedClass' refers to a type whose name cannot be referenced.
2+
3+
4+
==== tests/cases/compiler/BaseClass.ts (0 errors) ====
5+
export type Constructor<T> = new (...args: any[]) => T;
6+
7+
export class MyBaseClass<T> {
8+
baseProperty: string;
9+
constructor(value: T) {}
10+
}
11+
==== tests/cases/compiler/MixinClass.ts (0 errors) ====
12+
import { Constructor, MyBaseClass } from './BaseClass';
13+
14+
export interface MyMixin {
15+
mixinProperty: string;
16+
}
17+
18+
export function MyMixin<T extends Constructor<MyBaseClass<any>>>(base: T): T & Constructor<MyMixin> {
19+
return class extends base {
20+
mixinProperty: string;
21+
}
22+
}
23+
==== tests/cases/compiler/FinalClass.ts (1 errors) ====
24+
import { MyBaseClass } from './BaseClass';
25+
import { MyMixin } from './MixinClass';
26+
27+
export class MyExtendedClass extends MyMixin(MyBaseClass)<string> {
28+
~~~~~~~~~~~~~~~
29+
!!! error TS4093: 'extends' clause of exported class 'MyExtendedClass' refers to a type whose name cannot be referenced.
30+
extendedClassProperty: number;
31+
}
32+
==== tests/cases/compiler/Main.ts (0 errors) ====
33+
import { MyExtendedClass } from './FinalClass';
34+
import { MyMixin } from './MixinClass';
35+
36+
const myExtendedClass = new MyExtendedClass('string');
37+
38+
const AnotherMixedClass = MyMixin(MyExtendedClass);
39+
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
//// [tests/cases/compiler/exportClassExtendingIntersection.ts] ////
2+
3+
//// [BaseClass.ts]
4+
export type Constructor<T> = new (...args: any[]) => T;
5+
6+
export class MyBaseClass<T> {
7+
baseProperty: string;
8+
constructor(value: T) {}
9+
}
10+
//// [MixinClass.ts]
11+
import { Constructor, MyBaseClass } from './BaseClass';
12+
13+
export interface MyMixin {
14+
mixinProperty: string;
15+
}
16+
17+
export function MyMixin<T extends Constructor<MyBaseClass<any>>>(base: T): T & Constructor<MyMixin> {
18+
return class extends base {
19+
mixinProperty: string;
20+
}
21+
}
22+
//// [FinalClass.ts]
23+
import { MyBaseClass } from './BaseClass';
24+
import { MyMixin } from './MixinClass';
25+
26+
export class MyExtendedClass extends MyMixin(MyBaseClass)<string> {
27+
extendedClassProperty: number;
28+
}
29+
//// [Main.ts]
30+
import { MyExtendedClass } from './FinalClass';
31+
import { MyMixin } from './MixinClass';
32+
33+
const myExtendedClass = new MyExtendedClass('string');
34+
35+
const AnotherMixedClass = MyMixin(MyExtendedClass);
36+
37+
38+
//// [BaseClass.js]
39+
"use strict";
40+
exports.__esModule = true;
41+
var MyBaseClass = (function () {
42+
function MyBaseClass(value) {
43+
}
44+
return MyBaseClass;
45+
}());
46+
exports.MyBaseClass = MyBaseClass;
47+
//// [MixinClass.js]
48+
"use strict";
49+
var __extends = (this && this.__extends) || (function () {
50+
var extendStatics = Object.setPrototypeOf ||
51+
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
52+
function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
53+
return function (d, b) {
54+
extendStatics(d, b);
55+
function __() { this.constructor = d; }
56+
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
57+
};
58+
})();
59+
exports.__esModule = true;
60+
function MyMixin(base) {
61+
return (function (_super) {
62+
__extends(class_1, _super);
63+
function class_1() {
64+
return _super !== null && _super.apply(this, arguments) || this;
65+
}
66+
return class_1;
67+
}(base));
68+
}
69+
exports.MyMixin = MyMixin;
70+
//// [FinalClass.js]
71+
"use strict";
72+
var __extends = (this && this.__extends) || (function () {
73+
var extendStatics = Object.setPrototypeOf ||
74+
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
75+
function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
76+
return function (d, b) {
77+
extendStatics(d, b);
78+
function __() { this.constructor = d; }
79+
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
80+
};
81+
})();
82+
exports.__esModule = true;
83+
var BaseClass_1 = require("./BaseClass");
84+
var MixinClass_1 = require("./MixinClass");
85+
var MyExtendedClass = (function (_super) {
86+
__extends(MyExtendedClass, _super);
87+
function MyExtendedClass() {
88+
return _super !== null && _super.apply(this, arguments) || this;
89+
}
90+
return MyExtendedClass;
91+
}(MixinClass_1.MyMixin(BaseClass_1.MyBaseClass)));
92+
exports.MyExtendedClass = MyExtendedClass;
93+
//// [Main.js]
94+
"use strict";
95+
exports.__esModule = true;
96+
var FinalClass_1 = require("./FinalClass");
97+
var MixinClass_1 = require("./MixinClass");
98+
var myExtendedClass = new FinalClass_1.MyExtendedClass('string');
99+
var AnotherMixedClass = MixinClass_1.MyMixin(FinalClass_1.MyExtendedClass);
100+
101+
102+
//// [BaseClass.d.ts]
103+
export declare type Constructor<T> = new (...args: any[]) => T;
104+
export declare class MyBaseClass<T> {
105+
baseProperty: string;
106+
constructor(value: T);
107+
}
108+
//// [MixinClass.d.ts]
109+
import { Constructor, MyBaseClass } from './BaseClass';
110+
export interface MyMixin {
111+
mixinProperty: string;
112+
}
113+
export declare function MyMixin<T extends Constructor<MyBaseClass<any>>>(base: T): T & Constructor<MyMixin>;
114+
//// [Main.d.ts]

0 commit comments

Comments
 (0)