Skip to content

Commit bc914c0

Browse files
authored
Merge pull request #15932 from Microsoft/decl-emit-class-expr-as-type-literal
Declaration-emit class expressions as type literals
2 parents 515a0e8 + ecaf44d commit bc914c0

14 files changed

+564
-39
lines changed

src/compiler/checker.ts

Lines changed: 32 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3161,7 +3161,7 @@ namespace ts {
31613161
}
31623162

31633163
function buildTypeDisplay(type: Type, writer: SymbolWriter, enclosingDeclaration?: Node, globalFlags?: TypeFormatFlags, symbolStack?: Symbol[]) {
3164-
const globalFlagsToPass = globalFlags & TypeFormatFlags.WriteOwnNameForAnyLike;
3164+
const globalFlagsToPass = globalFlags & (TypeFormatFlags.WriteOwnNameForAnyLike | TypeFormatFlags.WriteClassExpressionAsTypeLiteral);
31653165
let inObjectTypeLiteral = false;
31663166
return writeType(type, globalFlags);
31673167

@@ -3278,6 +3278,11 @@ namespace ts {
32783278
writeTypeList(type.typeArguments.slice(0, getTypeReferenceArity(type)), SyntaxKind.CommaToken);
32793279
writePunctuation(writer, SyntaxKind.CloseBracketToken);
32803280
}
3281+
else if (flags & TypeFormatFlags.WriteClassExpressionAsTypeLiteral &&
3282+
type.symbol.valueDeclaration &&
3283+
type.symbol.valueDeclaration.kind === SyntaxKind.ClassExpression) {
3284+
writeAnonymousType(getDeclaredTypeOfClassOrInterface(type.symbol), flags);
3285+
}
32813286
else {
32823287
// Write the type reference in the format f<A>.g<B>.C<X, Y> where A and B are type arguments
32833288
// for outer type parameters, and f and g are the respective declaring containers of those
@@ -3325,7 +3330,9 @@ namespace ts {
33253330
const symbol = type.symbol;
33263331
if (symbol) {
33273332
// Always use 'typeof T' for type of class, enum, and module objects
3328-
if (symbol.flags & SymbolFlags.Class && !getBaseTypeVariableOfClass(symbol) ||
3333+
if (symbol.flags & SymbolFlags.Class &&
3334+
!getBaseTypeVariableOfClass(symbol) &&
3335+
!(symbol.valueDeclaration.kind === SyntaxKind.ClassExpression && flags & TypeFormatFlags.WriteClassExpressionAsTypeLiteral) ||
33293336
symbol.flags & (SymbolFlags.Enum | SymbolFlags.ValueModule)) {
33303337
writeTypeOfSymbol(type, flags);
33313338
}
@@ -3347,12 +3354,23 @@ namespace ts {
33473354
else {
33483355
// Since instantiations of the same anonymous type have the same symbol, tracking symbols instead
33493356
// of types allows us to catch circular references to instantiations of the same anonymous type
3357+
// However, in case of class expressions, we want to write both the static side and the instance side.
3358+
// We skip adding the static side so that the instance side has a chance to be written
3359+
// before checking for circular references.
33503360
if (!symbolStack) {
33513361
symbolStack = [];
33523362
}
3353-
symbolStack.push(symbol);
3354-
writeLiteralType(type, flags);
3355-
symbolStack.pop();
3363+
const isConstructorObject = type.flags & TypeFlags.Object &&
3364+
getObjectFlags(type) & ObjectFlags.Anonymous &&
3365+
type.symbol && type.symbol.flags & SymbolFlags.Class;
3366+
if (isConstructorObject) {
3367+
writeLiteralType(type, flags);
3368+
}
3369+
else {
3370+
symbolStack.push(symbol);
3371+
writeLiteralType(type, flags);
3372+
symbolStack.pop();
3373+
}
33563374
}
33573375
}
33583376
else {
@@ -3471,6 +3489,14 @@ namespace ts {
34713489
buildIndexSignatureDisplay(resolved.stringIndexInfo, writer, IndexKind.String, enclosingDeclaration, globalFlags, symbolStack);
34723490
buildIndexSignatureDisplay(resolved.numberIndexInfo, writer, IndexKind.Number, enclosingDeclaration, globalFlags, symbolStack);
34733491
for (const p of resolved.properties) {
3492+
if (globalFlags & TypeFormatFlags.WriteClassExpressionAsTypeLiteral) {
3493+
if (p.flags & SymbolFlags.Prototype) {
3494+
continue;
3495+
}
3496+
if (getDeclarationModifierFlagsFromSymbol(p) & (ModifierFlags.Private | ModifierFlags.Protected)) {
3497+
writer.reportPrivateInBaseOfClassExpression(p.name);
3498+
}
3499+
}
34743500
const t = getTypeOfSymbol(p);
34753501
if (p.flags & (SymbolFlags.Function | SymbolFlags.Method) && !getPropertiesOfObjectType(t).length) {
34763502
const signatures = getSignaturesOfType(t, SignatureKind.Call);
@@ -3485,7 +3511,7 @@ namespace ts {
34853511
writePropertyWithModifiers(p);
34863512
writePunctuation(writer, SyntaxKind.ColonToken);
34873513
writeSpace(writer);
3488-
writeType(t, TypeFormatFlags.None);
3514+
writeType(t, globalFlags & TypeFormatFlags.WriteClassExpressionAsTypeLiteral);
34893515
writePunctuation(writer, SyntaxKind.SemicolonToken);
34903516
writer.writeLine();
34913517
}

src/compiler/declarationEmitter.ts

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -190,7 +190,7 @@ namespace ts {
190190
const writer = <EmitTextWriterWithSymbolWriter>createTextWriter(newLine);
191191
writer.trackSymbol = trackSymbol;
192192
writer.reportInaccessibleThisError = reportInaccessibleThisError;
193-
writer.reportIllegalExtends = reportIllegalExtends;
193+
writer.reportPrivateInBaseOfClassExpression = reportPrivateInBaseOfClassExpression;
194194
writer.writeKeyword = writer.write;
195195
writer.writeOperator = writer.write;
196196
writer.writePunctuation = writer.write;
@@ -314,11 +314,11 @@ namespace ts {
314314
recordTypeReferenceDirectivesIfNecessary(resolver.getTypeReferenceDirectivesForSymbol(symbol, meaning));
315315
}
316316

317-
function reportIllegalExtends() {
317+
function reportPrivateInBaseOfClassExpression(propertyName: string) {
318318
if (errorNameNode) {
319319
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)));
320+
emitterDiagnostics.add(
321+
createDiagnosticForNode(errorNameNode, Diagnostics.Property_0_of_exported_class_expression_may_not_be_private_or_protected, propertyName));
322322
}
323323
}
324324

@@ -344,7 +344,9 @@ namespace ts {
344344
}
345345
else {
346346
errorNameNode = declaration.name;
347-
const format = TypeFormatFlags.UseTypeOfFunction | TypeFormatFlags.UseTypeAliasValue |
347+
const format = TypeFormatFlags.UseTypeOfFunction |
348+
TypeFormatFlags.WriteClassExpressionAsTypeLiteral |
349+
TypeFormatFlags.UseTypeAliasValue |
348350
(shouldUseResolverType ? TypeFormatFlags.AddUndefined : 0);
349351
resolver.writeTypeOfDeclaration(declaration, enclosingDeclaration, format, writer);
350352
errorNameNode = undefined;
@@ -360,7 +362,11 @@ namespace ts {
360362
}
361363
else {
362364
errorNameNode = signature.name;
363-
resolver.writeReturnTypeOfSignatureDeclaration(signature, enclosingDeclaration, TypeFormatFlags.UseTypeOfFunction | TypeFormatFlags.UseTypeAliasValue, writer);
365+
resolver.writeReturnTypeOfSignatureDeclaration(
366+
signature,
367+
enclosingDeclaration,
368+
TypeFormatFlags.UseTypeOfFunction | TypeFormatFlags.UseTypeAliasValue | TypeFormatFlags.WriteClassExpressionAsTypeLiteral,
369+
writer);
364370
errorNameNode = undefined;
365371
}
366372
}
@@ -621,7 +627,11 @@ namespace ts {
621627
write(tempVarName);
622628
write(": ");
623629
writer.getSymbolAccessibilityDiagnostic = () => diagnostic;
624-
resolver.writeTypeOfExpression(expr, enclosingDeclaration, TypeFormatFlags.UseTypeOfFunction | TypeFormatFlags.UseTypeAliasValue, writer);
630+
resolver.writeTypeOfExpression(
631+
expr,
632+
enclosingDeclaration,
633+
TypeFormatFlags.UseTypeOfFunction | TypeFormatFlags.UseTypeAliasValue | TypeFormatFlags.WriteClassExpressionAsTypeLiteral,
634+
writer);
625635
write(";");
626636
writeLine();
627637
return tempVarName;

src/compiler/diagnosticMessages.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2456,9 +2456,9 @@
24562456
"category": "Error",
24572457
"code": 4092
24582458
},
2459-
"'extends' clause of exported class '{0}' refers to a type whose name cannot be referenced.": {
2459+
"Property '{0}' of exported class expression may not be private or protected.": {
24602460
"category": "Error",
2461-
"code": 4093
2461+
"code": 4094
24622462
},
24632463

24642464
"The current host does not support the '{0}' option.": {

src/compiler/types.ts

Lines changed: 16 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2651,24 +2651,25 @@ namespace ts {
26512651
// with import statements it previously saw (but chose not to emit).
26522652
trackSymbol(symbol: Symbol, enclosingDeclaration?: Node, meaning?: SymbolFlags): void;
26532653
reportInaccessibleThisError(): void;
2654-
reportIllegalExtends(): void;
2654+
reportPrivateInBaseOfClassExpression(propertyName: string): void;
26552655
}
26562656

26572657
export const enum TypeFormatFlags {
2658-
None = 0x00000000,
2659-
WriteArrayAsGenericType = 0x00000001, // Write Array<T> instead T[]
2660-
UseTypeOfFunction = 0x00000002, // Write typeof instead of function type literal
2661-
NoTruncation = 0x00000004, // Don't truncate typeToString result
2662-
WriteArrowStyleSignature = 0x00000008, // Write arrow style signature
2663-
WriteOwnNameForAnyLike = 0x00000010, // Write symbol's own name instead of 'any' for any like types (eg. unknown, __resolving__ etc)
2664-
WriteTypeArgumentsOfSignature = 0x00000020, // Write the type arguments instead of type parameters of the signature
2665-
InElementType = 0x00000040, // Writing an array or union element type
2666-
UseFullyQualifiedType = 0x00000080, // Write out the fully qualified type name (eg. Module.Type, instead of Type)
2667-
InFirstTypeArgument = 0x00000100, // Writing first type argument of the instantiated type
2668-
InTypeAlias = 0x00000200, // Writing type in type alias declaration
2669-
UseTypeAliasValue = 0x00000400, // Serialize the type instead of using type-alias. This is needed when we emit declaration file.
2670-
SuppressAnyReturnType = 0x00000800, // If the return type is any-like, don't offer a return type.
2671-
AddUndefined = 0x00001000, // Add undefined to types of initialized, non-optional parameters
2658+
None = 0,
2659+
WriteArrayAsGenericType = 1 << 0, // Write Array<T> instead T[]
2660+
UseTypeOfFunction = 1 << 2, // Write typeof instead of function type literal
2661+
NoTruncation = 1 << 3, // Don't truncate typeToString result
2662+
WriteArrowStyleSignature = 1 << 4, // Write arrow style signature
2663+
WriteOwnNameForAnyLike = 1 << 5, // Write symbol's own name instead of 'any' for any like types (eg. unknown, __resolving__ etc)
2664+
WriteTypeArgumentsOfSignature = 1 << 6, // Write the type arguments instead of type parameters of the signature
2665+
InElementType = 1 << 7, // Writing an array or union element type
2666+
UseFullyQualifiedType = 1 << 8, // Write out the fully qualified type name (eg. Module.Type, instead of Type)
2667+
InFirstTypeArgument = 1 << 9, // Writing first type argument of the instantiated type
2668+
InTypeAlias = 1 << 10, // Writing type in type alias declaration
2669+
UseTypeAliasValue = 1 << 11, // Serialize the type instead of using type-alias. This is needed when we emit declaration file.
2670+
SuppressAnyReturnType = 1 << 12, // If the return type is any-like, don't offer a return type.
2671+
AddUndefined = 1 << 13, // Add undefined to types of initialized, non-optional parameters
2672+
WriteClassExpressionAsTypeLiteral = 1 << 14, // Write a type literal instead of (Anonymous class)
26722673
}
26732674

26742675
export const enum SymbolFormatFlags {

src/compiler/utilities.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ namespace ts {
6868
clear: () => str = "",
6969
trackSymbol: noop,
7070
reportInaccessibleThisError: noop,
71-
reportIllegalExtends: noop
71+
reportPrivateInBaseOfClassExpression: noop,
7272
};
7373
}
7474

src/services/utilities.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1136,7 +1136,7 @@ namespace ts {
11361136
clear: resetWriter,
11371137
trackSymbol: noop,
11381138
reportInaccessibleThisError: noop,
1139-
reportIllegalExtends: noop
1139+
reportPrivateInBaseOfClassExpression: noop,
11401140
};
11411141

11421142
function writeIndent() {

tests/baselines/reference/declarationEmitExpressionInExtends4.errors.txt

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,17 @@
1-
tests/cases/compiler/declarationEmitExpressionInExtends4.ts(1,10): error TS4060: Return type of exported function has or is using private name 'D'.
21
tests/cases/compiler/declarationEmitExpressionInExtends4.ts(5,17): error TS2315: Type 'D' is not generic.
3-
tests/cases/compiler/declarationEmitExpressionInExtends4.ts(5,17): error TS4020: 'extends' clause of exported class 'C' has or is using private name 'D'.
42
tests/cases/compiler/declarationEmitExpressionInExtends4.ts(9,18): error TS2304: Cannot find name 'SomeUndefinedFunction'.
53
tests/cases/compiler/declarationEmitExpressionInExtends4.ts(14,18): error TS2304: Cannot find name 'SomeUndefinedFunction'.
64
tests/cases/compiler/declarationEmitExpressionInExtends4.ts(14,18): error TS4020: 'extends' clause of exported class 'C3' has or is using private name 'SomeUndefinedFunction'.
75

86

9-
==== tests/cases/compiler/declarationEmitExpressionInExtends4.ts (6 errors) ====
7+
==== tests/cases/compiler/declarationEmitExpressionInExtends4.ts (4 errors) ====
108
function getSomething() {
11-
~~~~~~~~~~~~
12-
!!! error TS4060: Return type of exported function has or is using private name 'D'.
139
return class D { }
1410
}
1511

1612
class C extends getSomething()<number, string> {
1713
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1814
!!! error TS2315: Type 'D' is not generic.
19-
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
20-
!!! error TS4020: 'extends' clause of exported class 'C' has or is using private name 'D'.
2115

2216
}
2317

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
//// [emitClassExpressionInDeclarationFile.ts]
2+
export var simpleExample = class {
3+
static getTags() { }
4+
tags() { }
5+
}
6+
export var circularReference = class C {
7+
static getTags(c: C): C { return c }
8+
tags(c: C): C { return c }
9+
}
10+
11+
// repro from #15066
12+
export class FooItem {
13+
foo(): void { }
14+
name?: string;
15+
}
16+
17+
export type Constructor<T> = new(...args: any[]) => T;
18+
export function WithTags<T extends Constructor<FooItem>>(Base: T) {
19+
return class extends Base {
20+
static getTags(): void { }
21+
tags(): void { }
22+
}
23+
}
24+
25+
export class Test extends WithTags(FooItem) {}
26+
27+
const test = new Test();
28+
29+
Test.getTags()
30+
test.tags();
31+
32+
33+
//// [emitClassExpressionInDeclarationFile.js]
34+
"use strict";
35+
var __extends = (this && this.__extends) || (function () {
36+
var extendStatics = Object.setPrototypeOf ||
37+
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
38+
function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
39+
return function (d, b) {
40+
extendStatics(d, b);
41+
function __() { this.constructor = d; }
42+
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
43+
};
44+
})();
45+
exports.__esModule = true;
46+
exports.simpleExample = (function () {
47+
function class_1() {
48+
}
49+
class_1.getTags = function () { };
50+
class_1.prototype.tags = function () { };
51+
return class_1;
52+
}());
53+
exports.circularReference = (function () {
54+
function C() {
55+
}
56+
C.getTags = function (c) { return c; };
57+
C.prototype.tags = function (c) { return c; };
58+
return C;
59+
}());
60+
// repro from #15066
61+
var FooItem = (function () {
62+
function FooItem() {
63+
}
64+
FooItem.prototype.foo = function () { };
65+
return FooItem;
66+
}());
67+
exports.FooItem = FooItem;
68+
function WithTags(Base) {
69+
return (function (_super) {
70+
__extends(class_2, _super);
71+
function class_2() {
72+
return _super !== null && _super.apply(this, arguments) || this;
73+
}
74+
class_2.getTags = function () { };
75+
class_2.prototype.tags = function () { };
76+
return class_2;
77+
}(Base));
78+
}
79+
exports.WithTags = WithTags;
80+
var Test = (function (_super) {
81+
__extends(Test, _super);
82+
function Test() {
83+
return _super !== null && _super.apply(this, arguments) || this;
84+
}
85+
return Test;
86+
}(WithTags(FooItem)));
87+
exports.Test = Test;
88+
var test = new Test();
89+
Test.getTags();
90+
test.tags();
91+
92+
93+
//// [emitClassExpressionInDeclarationFile.d.ts]
94+
export declare var simpleExample: {
95+
new (): {
96+
tags(): void;
97+
};
98+
getTags(): void;
99+
};
100+
export declare var circularReference: {
101+
new (): {
102+
tags(c: any): any;
103+
};
104+
getTags(c: {
105+
tags(c: any): any;
106+
}): {
107+
tags(c: any): any;
108+
};
109+
};
110+
export declare class FooItem {
111+
foo(): void;
112+
name?: string;
113+
}
114+
export declare type Constructor<T> = new (...args: any[]) => T;
115+
export declare function WithTags<T extends Constructor<FooItem>>(Base: T): {
116+
new (...args: any[]): {
117+
tags(): void;
118+
foo(): void;
119+
name?: string;
120+
};
121+
getTags(): void;
122+
} & T;
123+
declare const Test_base: {
124+
new (...args: any[]): {
125+
tags(): void;
126+
foo(): void;
127+
name?: string;
128+
};
129+
getTags(): void;
130+
} & typeof FooItem;
131+
export declare class Test extends Test_base {
132+
}

0 commit comments

Comments
 (0)