Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 18 additions & 4 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3266,6 +3266,9 @@ namespace ts {
writeTypeList(type.typeArguments.slice(0, getTypeReferenceArity(type)), SyntaxKind.CommaToken);
writePunctuation(writer, SyntaxKind.CloseBracketToken);
}
else if (type.symbol.valueDeclaration && type.symbol.valueDeclaration.kind === SyntaxKind.ClassExpression) {
writeAnonymousType(getDeclaredTypeOfClassOrInterface(type.symbol), flags);
}
else {
// Write the type reference in the format f<A>.g<B>.C<X, Y> where A and B are type arguments
// for outer type parameters, and f and g are the respective declaring containers of those
Expand Down Expand Up @@ -3313,7 +3316,7 @@ namespace ts {
const symbol = type.symbol;
if (symbol) {
// Always use 'typeof T' for type of class, enum, and module objects
if (symbol.flags & SymbolFlags.Class && !getBaseTypeVariableOfClass(symbol) ||
if (symbol.flags & SymbolFlags.Class && !getBaseTypeVariableOfClass(symbol) && symbol.valueDeclaration.kind !== SyntaxKind.ClassExpression ||
symbol.flags & (SymbolFlags.Enum | SymbolFlags.ValueModule)) {
writeTypeOfSymbol(type, flags);
}
Expand All @@ -3335,12 +3338,23 @@ namespace ts {
else {
// Since instantiations of the same anonymous type have the same symbol, tracking symbols instead
// of types allows us to catch circular references to instantiations of the same anonymous type
// However, in case of class expressions, we want to write both the static side and the instance side.
// We skip adding the static side so that the instance side has a chance to be written
// before checking for circular references.
if (!symbolStack) {
symbolStack = [];
}
symbolStack.push(symbol);
writeLiteralType(type, flags);
symbolStack.pop();
const isConstructorObject = type.flags & TypeFlags.Object &&
getObjectFlags(type) & ObjectFlags.Anonymous &&
type.symbol && type.symbol.flags & SymbolFlags.Class;
if (isConstructorObject) {
writeLiteralType(type, flags);
}
else {
symbolStack.push(symbol);
writeLiteralType(type, flags);
symbolStack.pop();
}
}
}
else {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,23 +1,17 @@
tests/cases/compiler/declarationEmitExpressionInExtends4.ts(1,10): error TS4060: Return type of exported function has or is using private name 'D'.
tests/cases/compiler/declarationEmitExpressionInExtends4.ts(5,17): error TS2315: Type 'D' is not generic.
tests/cases/compiler/declarationEmitExpressionInExtends4.ts(5,17): error TS4020: 'extends' clause of exported class 'C' has or is using private name 'D'.
tests/cases/compiler/declarationEmitExpressionInExtends4.ts(9,18): error TS2304: Cannot find name 'SomeUndefinedFunction'.
tests/cases/compiler/declarationEmitExpressionInExtends4.ts(14,18): error TS2304: Cannot find name 'SomeUndefinedFunction'.
tests/cases/compiler/declarationEmitExpressionInExtends4.ts(14,18): error TS4020: 'extends' clause of exported class 'C3' has or is using private name 'SomeUndefinedFunction'.


==== tests/cases/compiler/declarationEmitExpressionInExtends4.ts (6 errors) ====
==== tests/cases/compiler/declarationEmitExpressionInExtends4.ts (4 errors) ====
function getSomething() {
~~~~~~~~~~~~
!!! error TS4060: Return type of exported function has or is using private name 'D'.
return class D { }
}

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

}

Expand Down
148 changes: 148 additions & 0 deletions tests/baselines/reference/emitClassExpressionInDeclarationFile.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
//// [emitClassExpressionInDeclarationFile.ts]
export var simpleExample = class {
static getTags() { }
tags() { }
}
export var circularReference = class C {
static getTags(c: C): C { return c }
tags(c: C): C { return c }
}

// repro from #15066
export class FooItem {
foo(): void { }
name?: string;
}

export type Constructor<T> = new(...args: any[]) => T;
export function WithTags<T extends Constructor<FooItem>>(Base: T) {
return class extends Base {
static getTags(): void { }
tags(): void { }
}
}

export class Test extends WithTags(FooItem) {}

const test = new Test();

Test.getTags()
test.tags();


//// [emitClassExpressionInDeclarationFile.js]
"use strict";
var __extends = (this && this.__extends) || (function () {
var extendStatics = Object.setPrototypeOf ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
return function (d, b) {
extendStatics(d, b);
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
})();
exports.__esModule = true;
exports.simpleExample = (function () {
function class_1() {
}
class_1.getTags = function () { };
class_1.prototype.tags = function () { };
return class_1;
}());
exports.circularReference = (function () {
function C() {
}
C.getTags = function (c) { return c; };
C.prototype.tags = function (c) { return c; };
return C;
}());
// repro from #15066
var FooItem = (function () {
function FooItem() {
}
FooItem.prototype.foo = function () { };
return FooItem;
}());
exports.FooItem = FooItem;
function WithTags(Base) {
return (function (_super) {
__extends(class_2, _super);
function class_2() {
return _super !== null && _super.apply(this, arguments) || this;
}
class_2.getTags = function () { };
class_2.prototype.tags = function () { };
return class_2;
}(Base));
}
exports.WithTags = WithTags;
var Test = (function (_super) {
__extends(Test, _super);
function Test() {
return _super !== null && _super.apply(this, arguments) || this;
}
return Test;
}(WithTags(FooItem)));
exports.Test = Test;
var test = new Test();
Test.getTags();
test.tags();


//// [emitClassExpressionInDeclarationFile.d.ts]
export declare var simpleExample: {
new (): {
tags(): void;
};
prototype: {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

kinda weird that we keep this property. can we filter prototype out?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

tags(): void;
};
getTags(): void;
};
export declare var circularReference: {
new (): {
tags(c: any): any;
};
prototype: {
tags(c: any): any;
};
getTags(c: {
tags(c: any): any;
}): {
tags(c: any): any;
};
};
export declare class FooItem {
foo(): void;
name?: string;
}
export declare type Constructor<T> = new (...args: any[]) => T;
export declare function WithTags<T extends Constructor<FooItem>>(Base: T): {
new (...args: any[]): {
tags(): void;
foo(): void;
name?: string;
};
prototype: {
tags(): void;
foo(): void;
name?: string;
};
getTags(): void;
} & T;
declare const Test_base: {
new (...args: any[]): {
tags(): void;
foo(): void;
name?: string;
};
prototype: {
tags(): void;
foo(): void;
name?: string;
};
getTags(): void;
} & typeof FooItem;
export declare class Test extends Test_base {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
=== tests/cases/compiler/emitClassExpressionInDeclarationFile.ts ===
export var simpleExample = class {
>simpleExample : Symbol(simpleExample, Decl(emitClassExpressionInDeclarationFile.ts, 0, 10))

static getTags() { }
>getTags : Symbol(simpleExample.getTags, Decl(emitClassExpressionInDeclarationFile.ts, 0, 34))

tags() { }
>tags : Symbol(simpleExample.tags, Decl(emitClassExpressionInDeclarationFile.ts, 1, 24))
}
export var circularReference = class C {
>circularReference : Symbol(circularReference, Decl(emitClassExpressionInDeclarationFile.ts, 4, 10))
>C : Symbol(C, Decl(emitClassExpressionInDeclarationFile.ts, 4, 30))

static getTags(c: C): C { return c }
>getTags : Symbol(C.getTags, Decl(emitClassExpressionInDeclarationFile.ts, 4, 40))
>c : Symbol(c, Decl(emitClassExpressionInDeclarationFile.ts, 5, 19))
>C : Symbol(C, Decl(emitClassExpressionInDeclarationFile.ts, 4, 30))
>C : Symbol(C, Decl(emitClassExpressionInDeclarationFile.ts, 4, 30))
>c : Symbol(c, Decl(emitClassExpressionInDeclarationFile.ts, 5, 19))

tags(c: C): C { return c }
>tags : Symbol(C.tags, Decl(emitClassExpressionInDeclarationFile.ts, 5, 40))
>c : Symbol(c, Decl(emitClassExpressionInDeclarationFile.ts, 6, 9))
>C : Symbol(C, Decl(emitClassExpressionInDeclarationFile.ts, 4, 30))
>C : Symbol(C, Decl(emitClassExpressionInDeclarationFile.ts, 4, 30))
>c : Symbol(c, Decl(emitClassExpressionInDeclarationFile.ts, 6, 9))
}

// repro from #15066
export class FooItem {
>FooItem : Symbol(FooItem, Decl(emitClassExpressionInDeclarationFile.ts, 7, 1))

foo(): void { }
>foo : Symbol(FooItem.foo, Decl(emitClassExpressionInDeclarationFile.ts, 10, 22))

name?: string;
>name : Symbol(FooItem.name, Decl(emitClassExpressionInDeclarationFile.ts, 11, 19))
}

export type Constructor<T> = new(...args: any[]) => T;
>Constructor : Symbol(Constructor, Decl(emitClassExpressionInDeclarationFile.ts, 13, 1))
>T : Symbol(T, Decl(emitClassExpressionInDeclarationFile.ts, 15, 24))
>args : Symbol(args, Decl(emitClassExpressionInDeclarationFile.ts, 15, 33))
>T : Symbol(T, Decl(emitClassExpressionInDeclarationFile.ts, 15, 24))

export function WithTags<T extends Constructor<FooItem>>(Base: T) {
>WithTags : Symbol(WithTags, Decl(emitClassExpressionInDeclarationFile.ts, 15, 54))
>T : Symbol(T, Decl(emitClassExpressionInDeclarationFile.ts, 16, 25))
>Constructor : Symbol(Constructor, Decl(emitClassExpressionInDeclarationFile.ts, 13, 1))
>FooItem : Symbol(FooItem, Decl(emitClassExpressionInDeclarationFile.ts, 7, 1))
>Base : Symbol(Base, Decl(emitClassExpressionInDeclarationFile.ts, 16, 57))
>T : Symbol(T, Decl(emitClassExpressionInDeclarationFile.ts, 16, 25))

return class extends Base {
>Base : Symbol(Base, Decl(emitClassExpressionInDeclarationFile.ts, 16, 57))

static getTags(): void { }
>getTags : Symbol((Anonymous class).getTags, Decl(emitClassExpressionInDeclarationFile.ts, 17, 31))

tags(): void { }
>tags : Symbol((Anonymous class).tags, Decl(emitClassExpressionInDeclarationFile.ts, 18, 34))
}
}

export class Test extends WithTags(FooItem) {}
>Test : Symbol(Test, Decl(emitClassExpressionInDeclarationFile.ts, 21, 1))
>WithTags : Symbol(WithTags, Decl(emitClassExpressionInDeclarationFile.ts, 15, 54))
>FooItem : Symbol(FooItem, Decl(emitClassExpressionInDeclarationFile.ts, 7, 1))

const test = new Test();
>test : Symbol(test, Decl(emitClassExpressionInDeclarationFile.ts, 25, 5))
>Test : Symbol(Test, Decl(emitClassExpressionInDeclarationFile.ts, 21, 1))

Test.getTags()
>Test.getTags : Symbol((Anonymous class).getTags, Decl(emitClassExpressionInDeclarationFile.ts, 17, 31))
>Test : Symbol(Test, Decl(emitClassExpressionInDeclarationFile.ts, 21, 1))
>getTags : Symbol((Anonymous class).getTags, Decl(emitClassExpressionInDeclarationFile.ts, 17, 31))

test.tags();
>test.tags : Symbol((Anonymous class).tags, Decl(emitClassExpressionInDeclarationFile.ts, 18, 34))
>test : Symbol(test, Decl(emitClassExpressionInDeclarationFile.ts, 25, 5))
>tags : Symbol((Anonymous class).tags, Decl(emitClassExpressionInDeclarationFile.ts, 18, 34))

Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
=== tests/cases/compiler/emitClassExpressionInDeclarationFile.ts ===
export var simpleExample = class {
>simpleExample : typeof simpleExample
>class { static getTags() { } tags() { }} : typeof simpleExample

static getTags() { }
>getTags : () => void

tags() { }
>tags : () => void
}
export var circularReference = class C {
>circularReference : typeof C
>class C { static getTags(c: C): C { return c } tags(c: C): C { return c }} : typeof C
>C : typeof C

static getTags(c: C): C { return c }
>getTags : (c: C) => C
>c : C
>C : C
>C : C
>c : C

tags(c: C): C { return c }
>tags : (c: C) => C
>c : C
>C : C
>C : C
>c : C
}

// repro from #15066
export class FooItem {
>FooItem : FooItem

foo(): void { }
>foo : () => void

name?: string;
>name : string
}

export type Constructor<T> = new(...args: any[]) => T;
>Constructor : Constructor<T>
>T : T
>args : any[]
>T : T

export function WithTags<T extends Constructor<FooItem>>(Base: T) {
>WithTags : <T extends Constructor<FooItem>>(Base: T) => { new (...args: any[]): (Anonymous class); prototype: WithTags<any>.(Anonymous class); getTags(): void; } & T
>T : T
>Constructor : Constructor<T>
>FooItem : FooItem
>Base : T
>T : T

return class extends Base {
>class extends Base { static getTags(): void { } tags(): void { } } : { new (...args: any[]): (Anonymous class); prototype: WithTags<any>.(Anonymous class); getTags(): void; } & T
>Base : FooItem

static getTags(): void { }
>getTags : () => void

tags(): void { }
>tags : () => void
}
}

export class Test extends WithTags(FooItem) {}
>Test : Test
>WithTags(FooItem) : WithTags<typeof FooItem>.(Anonymous class) & FooItem
>WithTags : <T extends Constructor<FooItem>>(Base: T) => { new (...args: any[]): (Anonymous class); prototype: WithTags<any>.(Anonymous class); getTags(): void; } & T
>FooItem : typeof FooItem

const test = new Test();
>test : Test
>new Test() : Test
>Test : typeof Test

Test.getTags()
>Test.getTags() : void
>Test.getTags : () => void
>Test : typeof Test
>getTags : () => void

test.tags();
>test.tags() : void
>test.tags : () => void
>test : Test
>tags : () => void

Loading