Skip to content

Commit

Permalink
Fix declaration/quick info for abstract construct signatures
Browse files Browse the repository at this point in the history
  • Loading branch information
rbuckton committed Jan 7, 2021
1 parent 9beaffd commit cfec2ca
Show file tree
Hide file tree
Showing 9 changed files with 232 additions and 11 deletions.
45 changes: 44 additions & 1 deletion src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3816,6 +3816,23 @@ namespace ts {
members, callSignatures, constructSignatures, stringIndexInfo, numberIndexInfo);
}

function getResolvedTypeWithoutAbstractConstructSignatures(type: ResolvedType) {
if (type.constructSignatures.length === 0) return type;
if (type.objectTypeWithoutAbstractConstructSignatures) return type.objectTypeWithoutAbstractConstructSignatures;
const constructSignatures = filter(type.constructSignatures, signature => !(signature.flags & SignatureFlags.Abstract));
if (type.constructSignatures === constructSignatures) return type;
const typeCopy = createAnonymousType(
type.symbol,
type.members,
type.callSignatures,
some(constructSignatures) ? constructSignatures : emptyArray,
type.stringIndexInfo,
type.numberIndexInfo);
type.objectTypeWithoutAbstractConstructSignatures = typeCopy;
typeCopy.objectTypeWithoutAbstractConstructSignatures = typeCopy;
return typeCopy;
}

function forEachSymbolTableInScope<T>(enclosingDeclaration: Node | undefined, callback: (symbolTable: SymbolTable) => T): T {
let result: T;
for (let location = enclosingDeclaration; location; location = location.parent) {
Expand Down Expand Up @@ -4762,13 +4779,38 @@ namespace ts {
}
}

const abstractSignatures = filter(resolved.constructSignatures, signature => !!(signature.flags & SignatureFlags.Abstract));
if (some(abstractSignatures)) {
const types = map(abstractSignatures, getOrCreateTypeFromSignature);
// count the number of type elements excluding abstract constructors
const typeElementCount =
resolved.callSignatures.length +
(resolved.constructSignatures.length - abstractSignatures.length) +
(resolved.stringIndexInfo ? 1 : 0) +
(resolved.numberIndexInfo ? 1 : 0) +
// exclude `prototype` when writing a class expression as a type literal, as per
// the logic in `createTypeNodesFromResolvedType`.
(context.flags & NodeBuilderFlags.WriteClassExpressionAsTypeLiteral ?
countWhere(resolved.properties, p => !(p.flags & SymbolFlags.Prototype)) :
length(resolved.properties));
// don't include an empty object literal if there were no other static-side
// properties to write, i.e. `abstract class C { }` becomes `abstract new () => {}`
// and not `(abstract new () => {}) & {}`
if (typeElementCount) {
// create a copy of the object type without any abstract construct signatures.
types.push(getResolvedTypeWithoutAbstractConstructSignatures(resolved));
}
return typeToTypeNodeHelper(getIntersectionType(types), context);
}

const savedFlags = context.flags;
context.flags |= NodeBuilderFlags.InObjectTypeLiteral;
const members = createTypeNodesFromResolvedType(resolved);
context.flags = savedFlags;
const typeLiteralNode = factory.createTypeLiteralNode(members);
context.approximateLength += 2;
return setEmitFlags(typeLiteralNode, (context.flags & NodeBuilderFlags.MultilineObjectLiterals) ? 0 : EmitFlags.SingleLine);
setEmitFlags(typeLiteralNode, (context.flags & NodeBuilderFlags.MultilineObjectLiterals) ? 0 : EmitFlags.SingleLine);
return typeLiteralNode;
}

function typeReferenceToTypeNode(type: TypeReference) {
Expand Down Expand Up @@ -4938,6 +4980,7 @@ namespace ts {
typeElements.push(<CallSignatureDeclaration>signatureToSignatureDeclarationHelper(signature, SyntaxKind.CallSignature, context));
}
for (const signature of resolvedType.constructSignatures) {
if (signature.flags & SignatureFlags.Abstract) continue;
typeElements.push(<ConstructSignatureDeclaration>signatureToSignatureDeclarationHelper(signature, SyntaxKind.ConstructSignature, context));
}
if (resolvedType.stringIndexInfo) {
Expand Down
1 change: 1 addition & 0 deletions src/compiler/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5172,6 +5172,7 @@ namespace ts {
/* @internal */ constructSignatures?: readonly Signature[]; // Construct signatures of type
/* @internal */ stringIndexInfo?: IndexInfo; // String indexing info
/* @internal */ numberIndexInfo?: IndexInfo; // Numeric indexing info
/* @internal */ objectTypeWithoutAbstractConstructSignatures?: ObjectType;
}

/** Class and interface types (ObjectFlags.Class and ObjectFlags.Interface). */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -104,12 +104,10 @@ export declare const Mixed: {
bar: number;
};
} & typeof Unmixed;
declare const FilteredThing_base: {
new (...args: any[]): {
match(path: string): boolean;
thing: number;
};
} & typeof Unmixed;
declare const FilteredThing_base: (abstract new (...args: any[]) => {
match(path: string): boolean;
thing: number;
}) & typeof Unmixed;
export declare class FilteredThing extends FilteredThing_base {
match(path: string): boolean;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ export const Mixed = mixin(Unmixed);
>Unmixed : typeof Unmixed

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

abstract class FilterMixin extends ctor {
Expand All @@ -50,13 +50,13 @@ function Filter<C extends Constructor<{}>>(ctor: C) {
>12 : 12
}
return FilterMixin;
>FilterMixin : { new (...args: any[]): FilterMixin; prototype: Filter<any>.FilterMixin; } & C
>FilterMixin : ((abstract new (...args: any[]) => FilterMixin) & { prototype: Filter<any>.FilterMixin; }) & C
}

export class FilteredThing extends Filter(Unmixed) {
>FilteredThing : FilteredThing
>Filter(Unmixed) : Filter<typeof Unmixed>.FilterMixin & Unmixed
>Filter : <C extends Constructor<{}>>(ctor: C) => { new (...args: any[]): FilterMixin; prototype: Filter<any>.FilterMixin; } & C
>Filter : <C extends Constructor<{}>>(ctor: C) => ((abstract new (...args: any[]) => FilterMixin) & { prototype: Filter<any>.FilterMixin; }) & C
>Unmixed : typeof Unmixed

match(path: string) {
Expand Down
2 changes: 1 addition & 1 deletion tests/baselines/reference/mixinAbstractClasses.types
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ function Mixin<TBaseClass extends abstract new (...args: any) => any>(baseClass:
}
}
return MixinClass;
>MixinClass : { new (...args: any): MixinClass; prototype: Mixin<any>.MixinClass; } & TBaseClass
>MixinClass : ((abstract new (...args: any) => MixinClass) & { prototype: Mixin<any>.MixinClass; }) & TBaseClass
}

class ConcreteBase {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
//// [mixinAbstractClassesReturnTypeInference.ts]
interface Mixin1 {
mixinMethod(): void;
}

abstract class AbstractBase {
abstract abstractBaseMethod(): void;
}

function Mixin2<TBase extends abstract new (...args: any[]) => any>(baseClass: TBase) {
// must be `abstract` because we cannot know *all* of the possible abstract members that need to be
// implemented for this to be concrete.
abstract class MixinClass extends baseClass implements Mixin1 {
mixinMethod(): void {}
static staticMixinMethod(): void {}
}
return MixinClass;
}

class DerivedFromAbstract2 extends Mixin2(AbstractBase) {
abstractBaseMethod() {}
}


//// [mixinAbstractClassesReturnTypeInference.js]
class AbstractBase {
}
function Mixin2(baseClass) {
// must be `abstract` because we cannot know *all* of the possible abstract members that need to be
// implemented for this to be concrete.
class MixinClass extends baseClass {
mixinMethod() { }
static staticMixinMethod() { }
}
return MixinClass;
}
class DerivedFromAbstract2 extends Mixin2(AbstractBase) {
abstractBaseMethod() { }
}


//// [mixinAbstractClassesReturnTypeInference.d.ts]
interface Mixin1 {
mixinMethod(): void;
}
declare abstract class AbstractBase {
abstract abstractBaseMethod(): void;
}
declare function Mixin2<TBase extends abstract new (...args: any[]) => any>(baseClass: TBase): ((abstract new (...args: any[]) => {
[x: string]: any;
mixinMethod(): void;
}) & {
staticMixinMethod(): void;
}) & TBase;
declare const DerivedFromAbstract2_base: ((abstract new (...args: any[]) => {
[x: string]: any;
mixinMethod(): void;
}) & {
staticMixinMethod(): void;
}) & typeof AbstractBase;
declare class DerivedFromAbstract2 extends DerivedFromAbstract2_base {
abstractBaseMethod(): void;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
=== tests/cases/conformance/classes/mixinAbstractClassesReturnTypeInference.ts ===
interface Mixin1 {
>Mixin1 : Symbol(Mixin1, Decl(mixinAbstractClassesReturnTypeInference.ts, 0, 0))

mixinMethod(): void;
>mixinMethod : Symbol(Mixin1.mixinMethod, Decl(mixinAbstractClassesReturnTypeInference.ts, 0, 18))
}

abstract class AbstractBase {
>AbstractBase : Symbol(AbstractBase, Decl(mixinAbstractClassesReturnTypeInference.ts, 2, 1))

abstract abstractBaseMethod(): void;
>abstractBaseMethod : Symbol(AbstractBase.abstractBaseMethod, Decl(mixinAbstractClassesReturnTypeInference.ts, 4, 29))
}

function Mixin2<TBase extends abstract new (...args: any[]) => any>(baseClass: TBase) {
>Mixin2 : Symbol(Mixin2, Decl(mixinAbstractClassesReturnTypeInference.ts, 6, 1))
>TBase : Symbol(TBase, Decl(mixinAbstractClassesReturnTypeInference.ts, 8, 16))
>args : Symbol(args, Decl(mixinAbstractClassesReturnTypeInference.ts, 8, 44))
>baseClass : Symbol(baseClass, Decl(mixinAbstractClassesReturnTypeInference.ts, 8, 68))
>TBase : Symbol(TBase, Decl(mixinAbstractClassesReturnTypeInference.ts, 8, 16))

// must be `abstract` because we cannot know *all* of the possible abstract members that need to be
// implemented for this to be concrete.
abstract class MixinClass extends baseClass implements Mixin1 {
>MixinClass : Symbol(MixinClass, Decl(mixinAbstractClassesReturnTypeInference.ts, 8, 87))
>baseClass : Symbol(baseClass, Decl(mixinAbstractClassesReturnTypeInference.ts, 8, 68))
>Mixin1 : Symbol(Mixin1, Decl(mixinAbstractClassesReturnTypeInference.ts, 0, 0))

mixinMethod(): void {}
>mixinMethod : Symbol(MixinClass.mixinMethod, Decl(mixinAbstractClassesReturnTypeInference.ts, 11, 67))

static staticMixinMethod(): void {}
>staticMixinMethod : Symbol(MixinClass.staticMixinMethod, Decl(mixinAbstractClassesReturnTypeInference.ts, 12, 30))
}
return MixinClass;
>MixinClass : Symbol(MixinClass, Decl(mixinAbstractClassesReturnTypeInference.ts, 8, 87))
}

class DerivedFromAbstract2 extends Mixin2(AbstractBase) {
>DerivedFromAbstract2 : Symbol(DerivedFromAbstract2, Decl(mixinAbstractClassesReturnTypeInference.ts, 16, 1))
>Mixin2 : Symbol(Mixin2, Decl(mixinAbstractClassesReturnTypeInference.ts, 6, 1))
>AbstractBase : Symbol(AbstractBase, Decl(mixinAbstractClassesReturnTypeInference.ts, 2, 1))

abstractBaseMethod() {}
>abstractBaseMethod : Symbol(DerivedFromAbstract2.abstractBaseMethod, Decl(mixinAbstractClassesReturnTypeInference.ts, 18, 57))
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
=== tests/cases/conformance/classes/mixinAbstractClassesReturnTypeInference.ts ===
interface Mixin1 {
mixinMethod(): void;
>mixinMethod : () => void
}

abstract class AbstractBase {
>AbstractBase : AbstractBase

abstract abstractBaseMethod(): void;
>abstractBaseMethod : () => void
}

function Mixin2<TBase extends abstract new (...args: any[]) => any>(baseClass: TBase) {
>Mixin2 : <TBase extends abstract new (...args: any[]) => any>(baseClass: TBase) => ((abstract new (...args: any[]) => MixinClass) & { prototype: Mixin2<any>.MixinClass; staticMixinMethod(): void; }) & TBase
>args : any[]
>baseClass : TBase

// must be `abstract` because we cannot know *all* of the possible abstract members that need to be
// implemented for this to be concrete.
abstract class MixinClass extends baseClass implements Mixin1 {
>MixinClass : MixinClass
>baseClass : TBase

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

static staticMixinMethod(): void {}
>staticMixinMethod : () => void
}
return MixinClass;
>MixinClass : ((abstract new (...args: any[]) => MixinClass) & { prototype: Mixin2<any>.MixinClass; staticMixinMethod(): void; }) & TBase
}

class DerivedFromAbstract2 extends Mixin2(AbstractBase) {
>DerivedFromAbstract2 : DerivedFromAbstract2
>Mixin2(AbstractBase) : Mixin2<typeof AbstractBase>.MixinClass & AbstractBase
>Mixin2 : <TBase extends abstract new (...args: any[]) => any>(baseClass: TBase) => ((abstract new (...args: any[]) => MixinClass) & { prototype: Mixin2<any>.MixinClass; staticMixinMethod(): void; }) & TBase
>AbstractBase : typeof AbstractBase

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

Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// @target: esnext
// @declaration: true

interface Mixin1 {
mixinMethod(): void;
}

abstract class AbstractBase {
abstract abstractBaseMethod(): void;
}

function Mixin2<TBase extends abstract new (...args: any[]) => any>(baseClass: TBase) {
// must be `abstract` because we cannot know *all* of the possible abstract members that need to be
// implemented for this to be concrete.
abstract class MixinClass extends baseClass implements Mixin1 {
mixinMethod(): void {}
static staticMixinMethod(): void {}
}
return MixinClass;
}

class DerivedFromAbstract2 extends Mixin2(AbstractBase) {
abstractBaseMethod() {}
}

0 comments on commit cfec2ca

Please sign in to comment.