Skip to content

Commit a7b066f

Browse files
authored
Allow local class declarations to be returned as mixins (#22807)
1 parent 874dd25 commit a7b066f

5 files changed

+294
-1
lines changed

src/compiler/checker.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -3185,7 +3185,9 @@ namespace ts {
31853185
}
31863186
else if (context.flags & NodeBuilderFlags.WriteClassExpressionAsTypeLiteral &&
31873187
type.symbol.valueDeclaration &&
3188-
type.symbol.valueDeclaration.kind === SyntaxKind.ClassExpression) {
3188+
isClassLike(type.symbol.valueDeclaration) &&
3189+
!isValueSymbolAccessible(type.symbol, context.enclosingDeclaration)
3190+
) {
31893191
return createAnonymousTypeNode(type);
31903192
}
31913193
else {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
//// [declarationEmitLocalClassDeclarationMixin.ts]
2+
interface Constructor<C> { new (...args: any[]): C; }
3+
4+
function mixin<B extends Constructor<{}>>(Base: B) {
5+
class PrivateMixed extends Base {
6+
bar = 2;
7+
}
8+
return PrivateMixed;
9+
}
10+
11+
export class Unmixed {
12+
foo = 1;
13+
}
14+
15+
export const Mixed = mixin(Unmixed);
16+
17+
function Filter<C extends Constructor<{}>>(ctor: C) {
18+
abstract class FilterMixin extends ctor {
19+
abstract match(path: string): boolean;
20+
// other concrete methods, fields, constructor
21+
thing = 12;
22+
}
23+
return FilterMixin;
24+
}
25+
26+
export class FilteredThing extends Filter(Unmixed) {
27+
match(path: string) {
28+
return false;
29+
}
30+
}
31+
32+
33+
//// [declarationEmitLocalClassDeclarationMixin.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+
function mixin(Base) {
47+
var PrivateMixed = /** @class */ (function (_super) {
48+
__extends(PrivateMixed, _super);
49+
function PrivateMixed() {
50+
var _this = _super !== null && _super.apply(this, arguments) || this;
51+
_this.bar = 2;
52+
return _this;
53+
}
54+
return PrivateMixed;
55+
}(Base));
56+
return PrivateMixed;
57+
}
58+
var Unmixed = /** @class */ (function () {
59+
function Unmixed() {
60+
this.foo = 1;
61+
}
62+
return Unmixed;
63+
}());
64+
exports.Unmixed = Unmixed;
65+
exports.Mixed = mixin(Unmixed);
66+
function Filter(ctor) {
67+
var FilterMixin = /** @class */ (function (_super) {
68+
__extends(FilterMixin, _super);
69+
function FilterMixin() {
70+
var _this = _super !== null && _super.apply(this, arguments) || this;
71+
// other concrete methods, fields, constructor
72+
_this.thing = 12;
73+
return _this;
74+
}
75+
return FilterMixin;
76+
}(ctor));
77+
return FilterMixin;
78+
}
79+
var FilteredThing = /** @class */ (function (_super) {
80+
__extends(FilteredThing, _super);
81+
function FilteredThing() {
82+
return _super !== null && _super.apply(this, arguments) || this;
83+
}
84+
FilteredThing.prototype.match = function (path) {
85+
return false;
86+
};
87+
return FilteredThing;
88+
}(Filter(Unmixed)));
89+
exports.FilteredThing = FilteredThing;
90+
91+
92+
//// [declarationEmitLocalClassDeclarationMixin.d.ts]
93+
export declare class Unmixed {
94+
foo: number;
95+
}
96+
export declare const Mixed: {
97+
new (...args: any[]): {
98+
bar: number;
99+
};
100+
} & typeof Unmixed;
101+
declare const FilteredThing_base: {
102+
new (...args: any[]): {
103+
match(path: string): boolean;
104+
thing: number;
105+
};
106+
} & typeof Unmixed;
107+
export declare class FilteredThing extends FilteredThing_base {
108+
match(path: string): boolean;
109+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
=== tests/cases/compiler/declarationEmitLocalClassDeclarationMixin.ts ===
2+
interface Constructor<C> { new (...args: any[]): C; }
3+
>Constructor : Symbol(Constructor, Decl(declarationEmitLocalClassDeclarationMixin.ts, 0, 0))
4+
>C : Symbol(C, Decl(declarationEmitLocalClassDeclarationMixin.ts, 0, 22))
5+
>args : Symbol(args, Decl(declarationEmitLocalClassDeclarationMixin.ts, 0, 32))
6+
>C : Symbol(C, Decl(declarationEmitLocalClassDeclarationMixin.ts, 0, 22))
7+
8+
function mixin<B extends Constructor<{}>>(Base: B) {
9+
>mixin : Symbol(mixin, Decl(declarationEmitLocalClassDeclarationMixin.ts, 0, 53))
10+
>B : Symbol(B, Decl(declarationEmitLocalClassDeclarationMixin.ts, 2, 15))
11+
>Constructor : Symbol(Constructor, Decl(declarationEmitLocalClassDeclarationMixin.ts, 0, 0))
12+
>Base : Symbol(Base, Decl(declarationEmitLocalClassDeclarationMixin.ts, 2, 42))
13+
>B : Symbol(B, Decl(declarationEmitLocalClassDeclarationMixin.ts, 2, 15))
14+
15+
class PrivateMixed extends Base {
16+
>PrivateMixed : Symbol(PrivateMixed, Decl(declarationEmitLocalClassDeclarationMixin.ts, 2, 52))
17+
>Base : Symbol(Base, Decl(declarationEmitLocalClassDeclarationMixin.ts, 2, 42))
18+
19+
bar = 2;
20+
>bar : Symbol(PrivateMixed.bar, Decl(declarationEmitLocalClassDeclarationMixin.ts, 3, 37))
21+
}
22+
return PrivateMixed;
23+
>PrivateMixed : Symbol(PrivateMixed, Decl(declarationEmitLocalClassDeclarationMixin.ts, 2, 52))
24+
}
25+
26+
export class Unmixed {
27+
>Unmixed : Symbol(Unmixed, Decl(declarationEmitLocalClassDeclarationMixin.ts, 7, 1))
28+
29+
foo = 1;
30+
>foo : Symbol(Unmixed.foo, Decl(declarationEmitLocalClassDeclarationMixin.ts, 9, 22))
31+
}
32+
33+
export const Mixed = mixin(Unmixed);
34+
>Mixed : Symbol(Mixed, Decl(declarationEmitLocalClassDeclarationMixin.ts, 13, 12))
35+
>mixin : Symbol(mixin, Decl(declarationEmitLocalClassDeclarationMixin.ts, 0, 53))
36+
>Unmixed : Symbol(Unmixed, Decl(declarationEmitLocalClassDeclarationMixin.ts, 7, 1))
37+
38+
function Filter<C extends Constructor<{}>>(ctor: C) {
39+
>Filter : Symbol(Filter, Decl(declarationEmitLocalClassDeclarationMixin.ts, 13, 36))
40+
>C : Symbol(C, Decl(declarationEmitLocalClassDeclarationMixin.ts, 15, 16))
41+
>Constructor : Symbol(Constructor, Decl(declarationEmitLocalClassDeclarationMixin.ts, 0, 0))
42+
>ctor : Symbol(ctor, Decl(declarationEmitLocalClassDeclarationMixin.ts, 15, 43))
43+
>C : Symbol(C, Decl(declarationEmitLocalClassDeclarationMixin.ts, 15, 16))
44+
45+
abstract class FilterMixin extends ctor {
46+
>FilterMixin : Symbol(FilterMixin, Decl(declarationEmitLocalClassDeclarationMixin.ts, 15, 53))
47+
>ctor : Symbol(ctor, Decl(declarationEmitLocalClassDeclarationMixin.ts, 15, 43))
48+
49+
abstract match(path: string): boolean;
50+
>match : Symbol(FilterMixin.match, Decl(declarationEmitLocalClassDeclarationMixin.ts, 16, 45))
51+
>path : Symbol(path, Decl(declarationEmitLocalClassDeclarationMixin.ts, 17, 23))
52+
53+
// other concrete methods, fields, constructor
54+
thing = 12;
55+
>thing : Symbol(FilterMixin.thing, Decl(declarationEmitLocalClassDeclarationMixin.ts, 17, 46))
56+
}
57+
return FilterMixin;
58+
>FilterMixin : Symbol(FilterMixin, Decl(declarationEmitLocalClassDeclarationMixin.ts, 15, 53))
59+
}
60+
61+
export class FilteredThing extends Filter(Unmixed) {
62+
>FilteredThing : Symbol(FilteredThing, Decl(declarationEmitLocalClassDeclarationMixin.ts, 22, 1))
63+
>Filter : Symbol(Filter, Decl(declarationEmitLocalClassDeclarationMixin.ts, 13, 36))
64+
>Unmixed : Symbol(Unmixed, Decl(declarationEmitLocalClassDeclarationMixin.ts, 7, 1))
65+
66+
match(path: string) {
67+
>match : Symbol(FilteredThing.match, Decl(declarationEmitLocalClassDeclarationMixin.ts, 24, 52))
68+
>path : Symbol(path, Decl(declarationEmitLocalClassDeclarationMixin.ts, 25, 10))
69+
70+
return false;
71+
}
72+
}
73+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
=== tests/cases/compiler/declarationEmitLocalClassDeclarationMixin.ts ===
2+
interface Constructor<C> { new (...args: any[]): C; }
3+
>Constructor : Constructor<C>
4+
>C : C
5+
>args : any[]
6+
>C : C
7+
8+
function mixin<B extends Constructor<{}>>(Base: B) {
9+
>mixin : <B extends Constructor<{}>>(Base: B) => { new (...args: any[]): PrivateMixed; prototype: mixin<any>.PrivateMixed; } & B
10+
>B : B
11+
>Constructor : Constructor<C>
12+
>Base : B
13+
>B : B
14+
15+
class PrivateMixed extends Base {
16+
>PrivateMixed : PrivateMixed
17+
>Base : {}
18+
19+
bar = 2;
20+
>bar : number
21+
>2 : 2
22+
}
23+
return PrivateMixed;
24+
>PrivateMixed : { new (...args: any[]): PrivateMixed; prototype: mixin<any>.PrivateMixed; } & B
25+
}
26+
27+
export class Unmixed {
28+
>Unmixed : Unmixed
29+
30+
foo = 1;
31+
>foo : number
32+
>1 : 1
33+
}
34+
35+
export const Mixed = mixin(Unmixed);
36+
>Mixed : { new (...args: any[]): mixin<typeof Unmixed>.PrivateMixed; prototype: mixin<any>.PrivateMixed; } & typeof Unmixed
37+
>mixin(Unmixed) : { new (...args: any[]): mixin<typeof Unmixed>.PrivateMixed; prototype: mixin<any>.PrivateMixed; } & typeof Unmixed
38+
>mixin : <B extends Constructor<{}>>(Base: B) => { new (...args: any[]): PrivateMixed; prototype: mixin<any>.PrivateMixed; } & B
39+
>Unmixed : typeof Unmixed
40+
41+
function Filter<C extends Constructor<{}>>(ctor: C) {
42+
>Filter : <C extends Constructor<{}>>(ctor: C) => { new (...args: any[]): FilterMixin; prototype: Filter<any>.FilterMixin; } & C
43+
>C : C
44+
>Constructor : Constructor<C>
45+
>ctor : C
46+
>C : C
47+
48+
abstract class FilterMixin extends ctor {
49+
>FilterMixin : FilterMixin
50+
>ctor : {}
51+
52+
abstract match(path: string): boolean;
53+
>match : (path: string) => boolean
54+
>path : string
55+
56+
// other concrete methods, fields, constructor
57+
thing = 12;
58+
>thing : number
59+
>12 : 12
60+
}
61+
return FilterMixin;
62+
>FilterMixin : { new (...args: any[]): FilterMixin; prototype: Filter<any>.FilterMixin; } & C
63+
}
64+
65+
export class FilteredThing extends Filter(Unmixed) {
66+
>FilteredThing : FilteredThing
67+
>Filter(Unmixed) : Filter<typeof Unmixed>.FilterMixin & Unmixed
68+
>Filter : <C extends Constructor<{}>>(ctor: C) => { new (...args: any[]): FilterMixin; prototype: Filter<any>.FilterMixin; } & C
69+
>Unmixed : typeof Unmixed
70+
71+
match(path: string) {
72+
>match : (path: string) => boolean
73+
>path : string
74+
75+
return false;
76+
>false : false
77+
}
78+
}
79+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
// @declaration: true
2+
interface Constructor<C> { new (...args: any[]): C; }
3+
4+
function mixin<B extends Constructor<{}>>(Base: B) {
5+
class PrivateMixed extends Base {
6+
bar = 2;
7+
}
8+
return PrivateMixed;
9+
}
10+
11+
export class Unmixed {
12+
foo = 1;
13+
}
14+
15+
export const Mixed = mixin(Unmixed);
16+
17+
function Filter<C extends Constructor<{}>>(ctor: C) {
18+
abstract class FilterMixin extends ctor {
19+
abstract match(path: string): boolean;
20+
// other concrete methods, fields, constructor
21+
thing = 12;
22+
}
23+
return FilterMixin;
24+
}
25+
26+
export class FilteredThing extends Filter(Unmixed) {
27+
match(path: string) {
28+
return false;
29+
}
30+
}

0 commit comments

Comments
 (0)