I was playing around with "multiple inheritances" in TypeScript or rather getting a good understanding of mixins. After many detours, I found the easiest way is to as few explicit casting as possible and were able to create stuff as found below (the complete examples can be found in this gist).
My question is: Why does TypeScript allow me to build this, but then fails to create declaration files for this?
export function TaggedMixin<Super extends Ctor>(superClass: Super) {
class Tagged extends superClass {
public static TAG_PUBLIC: string;
protected static TAG_PROTECTED: string;
private static TAG_PRIVATE: string;
public tag_public!: string;
protected tag_protected!: number;
private tag_private!: number;
}
return Tagged;
}
const Tagged = TaggedMixin(class {
public static ANON_PUBLIC: string;
protected static ANON_PROTECTED: string;
private static ANON_PRIVATE: string;
public anon_public!: string;
protected anon_protected!: number;
private anon_private!: number;
});
class TaggedClass extends Tagged {
constructor() {
super();
TaggedClass.ANON_PUBLIC;
TaggedClass.ANON_PROTECTED;
TaggedClass.TAG_PUBLIC;
TaggedClass.TAG_PROTECTED;
this.anon_public;
this.anon_protected;
this.tag_public;
this.tag_protected;
}
}
EDIT:
The error with that TS fails to create declaration files:
Property 'tag_protected' of exported class expression may not be private or protected.ts(4094)
Property 'tag_private' of exported class expression may not be private or protected.ts(4094)
Property 'TAG_PROTECTED' of exported class expression may not be private or protected.ts(4094)
Property 'TAG_PRIVATE' of exported class expression may not be private or protected.ts(4094)
There are limitations with emitting declarations and mixins. Class expressions can't have private or protected members because as stated here:
exported anonymous classes can't have private or protected members if declaration emit is enabled, because there's no way to represent that in a .d.ts file.
Typescript will represent the instance type of the mixin as an object type, and object types can't have members marked with private
or protected
hence the error.
Removing the non-public members will fix the issue.
You could also add some manual typing with and a class declaration, that will get you close to your goal:
type Ctor = new (...a: any[]) => any
declare class TaggedMixinInstance { // just a dummy, never call new TaggedMixinInstance() or extend this directly
public tag_public: string;
protected tag_protected: number;
private tag_private: number;
}
export function TaggedMixin<Super extends Ctor>(superClass: Super): {
new(...a: any[]): TaggedMixinInstance;
TAG_PUBLIC: string
} & Super {
class Tagged extends superClass {
public static TAG_PUBLIC: string;
protected static TAG_PROTECTED: string;
private static TAG_PRIVATE: string;
public tag_public!: string;
protected tag_protected!: number;
private tag_private!: number;
}
return Tagged;
}
const Tagged = TaggedMixin(class {
public static ANON_PUBLIC: string;
protected static ANON_PROTECTED: string;
private static ANON_PRIVATE: string;
public anon_public!: string;
protected anon_protected!: number;
private anon_private!: number;
});
class TaggedClass extends Tagged {
constructor() {
super();
TaggedClass.ANON_PUBLIC;
TaggedClass.ANON_PROTECTED;
TaggedClass.TAG_PUBLIC;
TaggedClass.TAG_PROTECTED; // still an error
this.anon_public;
this.anon_protected;
this.tag_public;
this.tag_protected;
}
}