Skip to content

Commit d88027d

Browse files
committed
Fix declaration/quick info for abstract construct signatures
1 parent 9beaffd commit d88027d

9 files changed

+232
-11
lines changed

src/compiler/checker.ts

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3816,6 +3816,23 @@ namespace ts {
38163816
members, callSignatures, constructSignatures, stringIndexInfo, numberIndexInfo);
38173817
}
38183818

3819+
function getResolvedTypeWithoutAbstractConstructSignatures(type: ResolvedType) {
3820+
if (type.constructSignatures.length === 0) return type;
3821+
if (type.objectTypeWithoutAbstractConstructSignatures) return type.objectTypeWithoutAbstractConstructSignatures;
3822+
const constructSignatures = filter(type.constructSignatures, signature => !(signature.flags & SignatureFlags.Abstract));
3823+
if (type.constructSignatures === constructSignatures) return type;
3824+
const typeCopy = createAnonymousType(
3825+
type.symbol,
3826+
type.members,
3827+
type.callSignatures,
3828+
some(constructSignatures) ? constructSignatures : emptyArray,
3829+
type.stringIndexInfo,
3830+
type.numberIndexInfo);
3831+
type.objectTypeWithoutAbstractConstructSignatures = typeCopy;
3832+
typeCopy.objectTypeWithoutAbstractConstructSignatures = typeCopy;
3833+
return typeCopy;
3834+
}
3835+
38193836
function forEachSymbolTableInScope<T>(enclosingDeclaration: Node | undefined, callback: (symbolTable: SymbolTable) => T): T {
38203837
let result: T;
38213838
for (let location = enclosingDeclaration; location; location = location.parent) {
@@ -4762,13 +4779,38 @@ namespace ts {
47624779
}
47634780
}
47644781

4782+
const abstractSignatures = filter(resolved.constructSignatures, signature => !!(signature.flags & SignatureFlags.Abstract));
4783+
if (some(abstractSignatures)) {
4784+
const types = map(abstractSignatures, getOrCreateTypeFromSignature);
4785+
// count the number of type elements excluding abstract constructors
4786+
const typeElementCount =
4787+
resolved.callSignatures.length +
4788+
(resolved.constructSignatures.length - abstractSignatures.length) +
4789+
(resolved.stringIndexInfo ? 1 : 0) +
4790+
(resolved.numberIndexInfo ? 1 : 0) +
4791+
// exclude `prototype` when writing a class expression as a type literal, as per
4792+
// the logic in `createTypeNodesFromResolvedType`.
4793+
(context.flags & NodeBuilderFlags.WriteClassExpressionAsTypeLiteral ?
4794+
countWhere(resolved.properties, p => !(p.flags & SymbolFlags.Prototype)) :
4795+
length(resolved.properties));
4796+
// don't include an empty object literal if there were no other static-side
4797+
// properties to write, i.e. `abstract class C { }` becomes `abstract new () => {}`
4798+
// and not `(abstract new () => {}) & {}`
4799+
if (typeElementCount) {
4800+
// create a copy of the object type without any abstract construct signatures.
4801+
types.push(getResolvedTypeWithoutAbstractConstructSignatures(resolved));
4802+
}
4803+
return typeToTypeNodeHelper(getIntersectionType(types), context);
4804+
}
4805+
47654806
const savedFlags = context.flags;
47664807
context.flags |= NodeBuilderFlags.InObjectTypeLiteral;
47674808
const members = createTypeNodesFromResolvedType(resolved);
47684809
context.flags = savedFlags;
47694810
const typeLiteralNode = factory.createTypeLiteralNode(members);
47704811
context.approximateLength += 2;
4771-
return setEmitFlags(typeLiteralNode, (context.flags & NodeBuilderFlags.MultilineObjectLiterals) ? 0 : EmitFlags.SingleLine);
4812+
setEmitFlags(typeLiteralNode, (context.flags & NodeBuilderFlags.MultilineObjectLiterals) ? 0 : EmitFlags.SingleLine);
4813+
return typeLiteralNode;
47724814
}
47734815

47744816
function typeReferenceToTypeNode(type: TypeReference) {
@@ -4938,6 +4980,7 @@ namespace ts {
49384980
typeElements.push(<CallSignatureDeclaration>signatureToSignatureDeclarationHelper(signature, SyntaxKind.CallSignature, context));
49394981
}
49404982
for (const signature of resolvedType.constructSignatures) {
4983+
if (signature.flags & SignatureFlags.Abstract) continue;
49414984
typeElements.push(<ConstructSignatureDeclaration>signatureToSignatureDeclarationHelper(signature, SyntaxKind.ConstructSignature, context));
49424985
}
49434986
if (resolvedType.stringIndexInfo) {

src/compiler/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5172,6 +5172,7 @@ namespace ts {
51725172
/* @internal */ constructSignatures?: readonly Signature[]; // Construct signatures of type
51735173
/* @internal */ stringIndexInfo?: IndexInfo; // String indexing info
51745174
/* @internal */ numberIndexInfo?: IndexInfo; // Numeric indexing info
5175+
/* @internal */ objectTypeWithoutAbstractConstructSignatures?: ObjectType;
51755176
}
51765177

51775178
/** Class and interface types (ObjectFlags.Class and ObjectFlags.Interface). */

tests/baselines/reference/declarationEmitLocalClassDeclarationMixin.js

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -104,12 +104,10 @@ export declare const Mixed: {
104104
bar: number;
105105
};
106106
} & typeof Unmixed;
107-
declare const FilteredThing_base: {
108-
new (...args: any[]): {
109-
match(path: string): boolean;
110-
thing: number;
111-
};
112-
} & typeof Unmixed;
107+
declare const FilteredThing_base: (abstract new (...args: any[]) => {
108+
match(path: string): boolean;
109+
thing: number;
110+
}) & typeof Unmixed;
113111
export declare class FilteredThing extends FilteredThing_base {
114112
match(path: string): boolean;
115113
}

tests/baselines/reference/declarationEmitLocalClassDeclarationMixin.types

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ export const Mixed = mixin(Unmixed);
3333
>Unmixed : typeof Unmixed
3434

3535
function Filter<C extends Constructor<{}>>(ctor: C) {
36-
>Filter : <C extends Constructor<{}>>(ctor: C) => { new (...args: any[]): FilterMixin; prototype: Filter<any>.FilterMixin; } & C
36+
>Filter : <C extends Constructor<{}>>(ctor: C) => ((abstract new (...args: any[]) => FilterMixin) & { prototype: Filter<any>.FilterMixin; }) & C
3737
>ctor : C
3838

3939
abstract class FilterMixin extends ctor {
@@ -50,13 +50,13 @@ function Filter<C extends Constructor<{}>>(ctor: C) {
5050
>12 : 12
5151
}
5252
return FilterMixin;
53-
>FilterMixin : { new (...args: any[]): FilterMixin; prototype: Filter<any>.FilterMixin; } & C
53+
>FilterMixin : ((abstract new (...args: any[]) => FilterMixin) & { prototype: Filter<any>.FilterMixin; }) & C
5454
}
5555

5656
export class FilteredThing extends Filter(Unmixed) {
5757
>FilteredThing : FilteredThing
5858
>Filter(Unmixed) : Filter<typeof Unmixed>.FilterMixin & Unmixed
59-
>Filter : <C extends Constructor<{}>>(ctor: C) => { new (...args: any[]): FilterMixin; prototype: Filter<any>.FilterMixin; } & C
59+
>Filter : <C extends Constructor<{}>>(ctor: C) => ((abstract new (...args: any[]) => FilterMixin) & { prototype: Filter<any>.FilterMixin; }) & C
6060
>Unmixed : typeof Unmixed
6161

6262
match(path: string) {

tests/baselines/reference/mixinAbstractClasses.types

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ function Mixin<TBaseClass extends abstract new (...args: any) => any>(baseClass:
1919
}
2020
}
2121
return MixinClass;
22-
>MixinClass : { new (...args: any): MixinClass; prototype: Mixin<any>.MixinClass; } & TBaseClass
22+
>MixinClass : ((abstract new (...args: any) => MixinClass) & { prototype: Mixin<any>.MixinClass; }) & TBaseClass
2323
}
2424

2525
class ConcreteBase {
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
//// [mixinAbstractClassesReturnTypeInference.ts]
2+
interface Mixin1 {
3+
mixinMethod(): void;
4+
}
5+
6+
abstract class AbstractBase {
7+
abstract abstractBaseMethod(): void;
8+
}
9+
10+
function Mixin2<TBase extends abstract new (...args: any[]) => any>(baseClass: TBase) {
11+
// must be `abstract` because we cannot know *all* of the possible abstract members that need to be
12+
// implemented for this to be concrete.
13+
abstract class MixinClass extends baseClass implements Mixin1 {
14+
mixinMethod(): void {}
15+
static staticMixinMethod(): void {}
16+
}
17+
return MixinClass;
18+
}
19+
20+
class DerivedFromAbstract2 extends Mixin2(AbstractBase) {
21+
abstractBaseMethod() {}
22+
}
23+
24+
25+
//// [mixinAbstractClassesReturnTypeInference.js]
26+
class AbstractBase {
27+
}
28+
function Mixin2(baseClass) {
29+
// must be `abstract` because we cannot know *all* of the possible abstract members that need to be
30+
// implemented for this to be concrete.
31+
class MixinClass extends baseClass {
32+
mixinMethod() { }
33+
static staticMixinMethod() { }
34+
}
35+
return MixinClass;
36+
}
37+
class DerivedFromAbstract2 extends Mixin2(AbstractBase) {
38+
abstractBaseMethod() { }
39+
}
40+
41+
42+
//// [mixinAbstractClassesReturnTypeInference.d.ts]
43+
interface Mixin1 {
44+
mixinMethod(): void;
45+
}
46+
declare abstract class AbstractBase {
47+
abstract abstractBaseMethod(): void;
48+
}
49+
declare function Mixin2<TBase extends abstract new (...args: any[]) => any>(baseClass: TBase): ((abstract new (...args: any[]) => {
50+
[x: string]: any;
51+
mixinMethod(): void;
52+
}) & {
53+
staticMixinMethod(): void;
54+
}) & TBase;
55+
declare const DerivedFromAbstract2_base: ((abstract new (...args: any[]) => {
56+
[x: string]: any;
57+
mixinMethod(): void;
58+
}) & {
59+
staticMixinMethod(): void;
60+
}) & typeof AbstractBase;
61+
declare class DerivedFromAbstract2 extends DerivedFromAbstract2_base {
62+
abstractBaseMethod(): void;
63+
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
=== tests/cases/conformance/classes/mixinAbstractClassesReturnTypeInference.ts ===
2+
interface Mixin1 {
3+
>Mixin1 : Symbol(Mixin1, Decl(mixinAbstractClassesReturnTypeInference.ts, 0, 0))
4+
5+
mixinMethod(): void;
6+
>mixinMethod : Symbol(Mixin1.mixinMethod, Decl(mixinAbstractClassesReturnTypeInference.ts, 0, 18))
7+
}
8+
9+
abstract class AbstractBase {
10+
>AbstractBase : Symbol(AbstractBase, Decl(mixinAbstractClassesReturnTypeInference.ts, 2, 1))
11+
12+
abstract abstractBaseMethod(): void;
13+
>abstractBaseMethod : Symbol(AbstractBase.abstractBaseMethod, Decl(mixinAbstractClassesReturnTypeInference.ts, 4, 29))
14+
}
15+
16+
function Mixin2<TBase extends abstract new (...args: any[]) => any>(baseClass: TBase) {
17+
>Mixin2 : Symbol(Mixin2, Decl(mixinAbstractClassesReturnTypeInference.ts, 6, 1))
18+
>TBase : Symbol(TBase, Decl(mixinAbstractClassesReturnTypeInference.ts, 8, 16))
19+
>args : Symbol(args, Decl(mixinAbstractClassesReturnTypeInference.ts, 8, 44))
20+
>baseClass : Symbol(baseClass, Decl(mixinAbstractClassesReturnTypeInference.ts, 8, 68))
21+
>TBase : Symbol(TBase, Decl(mixinAbstractClassesReturnTypeInference.ts, 8, 16))
22+
23+
// must be `abstract` because we cannot know *all* of the possible abstract members that need to be
24+
// implemented for this to be concrete.
25+
abstract class MixinClass extends baseClass implements Mixin1 {
26+
>MixinClass : Symbol(MixinClass, Decl(mixinAbstractClassesReturnTypeInference.ts, 8, 87))
27+
>baseClass : Symbol(baseClass, Decl(mixinAbstractClassesReturnTypeInference.ts, 8, 68))
28+
>Mixin1 : Symbol(Mixin1, Decl(mixinAbstractClassesReturnTypeInference.ts, 0, 0))
29+
30+
mixinMethod(): void {}
31+
>mixinMethod : Symbol(MixinClass.mixinMethod, Decl(mixinAbstractClassesReturnTypeInference.ts, 11, 67))
32+
33+
static staticMixinMethod(): void {}
34+
>staticMixinMethod : Symbol(MixinClass.staticMixinMethod, Decl(mixinAbstractClassesReturnTypeInference.ts, 12, 30))
35+
}
36+
return MixinClass;
37+
>MixinClass : Symbol(MixinClass, Decl(mixinAbstractClassesReturnTypeInference.ts, 8, 87))
38+
}
39+
40+
class DerivedFromAbstract2 extends Mixin2(AbstractBase) {
41+
>DerivedFromAbstract2 : Symbol(DerivedFromAbstract2, Decl(mixinAbstractClassesReturnTypeInference.ts, 16, 1))
42+
>Mixin2 : Symbol(Mixin2, Decl(mixinAbstractClassesReturnTypeInference.ts, 6, 1))
43+
>AbstractBase : Symbol(AbstractBase, Decl(mixinAbstractClassesReturnTypeInference.ts, 2, 1))
44+
45+
abstractBaseMethod() {}
46+
>abstractBaseMethod : Symbol(DerivedFromAbstract2.abstractBaseMethod, Decl(mixinAbstractClassesReturnTypeInference.ts, 18, 57))
47+
}
48+
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
=== tests/cases/conformance/classes/mixinAbstractClassesReturnTypeInference.ts ===
2+
interface Mixin1 {
3+
mixinMethod(): void;
4+
>mixinMethod : () => void
5+
}
6+
7+
abstract class AbstractBase {
8+
>AbstractBase : AbstractBase
9+
10+
abstract abstractBaseMethod(): void;
11+
>abstractBaseMethod : () => void
12+
}
13+
14+
function Mixin2<TBase extends abstract new (...args: any[]) => any>(baseClass: TBase) {
15+
>Mixin2 : <TBase extends abstract new (...args: any[]) => any>(baseClass: TBase) => ((abstract new (...args: any[]) => MixinClass) & { prototype: Mixin2<any>.MixinClass; staticMixinMethod(): void; }) & TBase
16+
>args : any[]
17+
>baseClass : TBase
18+
19+
// must be `abstract` because we cannot know *all* of the possible abstract members that need to be
20+
// implemented for this to be concrete.
21+
abstract class MixinClass extends baseClass implements Mixin1 {
22+
>MixinClass : MixinClass
23+
>baseClass : TBase
24+
25+
mixinMethod(): void {}
26+
>mixinMethod : () => void
27+
28+
static staticMixinMethod(): void {}
29+
>staticMixinMethod : () => void
30+
}
31+
return MixinClass;
32+
>MixinClass : ((abstract new (...args: any[]) => MixinClass) & { prototype: Mixin2<any>.MixinClass; staticMixinMethod(): void; }) & TBase
33+
}
34+
35+
class DerivedFromAbstract2 extends Mixin2(AbstractBase) {
36+
>DerivedFromAbstract2 : DerivedFromAbstract2
37+
>Mixin2(AbstractBase) : Mixin2<typeof AbstractBase>.MixinClass & AbstractBase
38+
>Mixin2 : <TBase extends abstract new (...args: any[]) => any>(baseClass: TBase) => ((abstract new (...args: any[]) => MixinClass) & { prototype: Mixin2<any>.MixinClass; staticMixinMethod(): void; }) & TBase
39+
>AbstractBase : typeof AbstractBase
40+
41+
abstractBaseMethod() {}
42+
>abstractBaseMethod : () => void
43+
}
44+
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
// @target: esnext
2+
// @declaration: true
3+
4+
interface Mixin1 {
5+
mixinMethod(): void;
6+
}
7+
8+
abstract class AbstractBase {
9+
abstract abstractBaseMethod(): void;
10+
}
11+
12+
function Mixin2<TBase extends abstract new (...args: any[]) => any>(baseClass: TBase) {
13+
// must be `abstract` because we cannot know *all* of the possible abstract members that need to be
14+
// implemented for this to be concrete.
15+
abstract class MixinClass extends baseClass implements Mixin1 {
16+
mixinMethod(): void {}
17+
static staticMixinMethod(): void {}
18+
}
19+
return MixinClass;
20+
}
21+
22+
class DerivedFromAbstract2 extends Mixin2(AbstractBase) {
23+
abstractBaseMethod() {}
24+
}

0 commit comments

Comments
 (0)