Reputation: 5611
I have a set of classes which can be "naively" represented as a diamond:
OneBase
/ \
OneDer TwoBase
\ /
TwoDer
[NB -- due to @mitch comment have changed names.]
In fact, One
and Two
are "base" classes, and there are several different classes besides OneDer
and TwoDer
that derive from them using the same pattern. Lets say that (OneBase,TwoBase)
are in module base
, and (OneDer, TwoDer)
are in module derived
.
Also OneBase
s contain a structure Part
which contains a TwoBase
instance; in OneDer
s the equivalent structure should contain a TwoDer
instance.
I converted TwoBase
into a mixin, and the following code does compile:
Module base
:
export type Constructor<T> = new(...args: any[]) => T
export class One {
p: Part
constructor (readonly a: number) {}
}
export class TwoPre extends One {
constructor (readonly a: number, readonly c: M[]) {
super(a)
}
}
export function TwoMix<
T extends Constructor<TwoPre>> (Base: T): Constructor<TwoPre> {
class Two extends Base {
}
return Two
}
export const Two = TwoMix(TwoPre)
export interface Part {
u: typeof Two
}
Module derived
:
import { Part, One, TwoMix } from './base'
export class OneDer extends One {
p: Part
}
export class TwoDerPre extends OneDer {
constructor (readonly a: number, readonly c: M[]) {
super(a)
}
}
export const TwoDer = TwoMix(TwoDerPre)
Note that TwoPre
and TwoDerPre
are necessary because the "naive" Two
has a different constructor signature than One
and mixin functions can't define
a constructor. This is a bit of a pain, as it adds unnecessarily to the prototype chain -- so workarounds appreciated. Other than this, the mixin
does enforce the method resolution order I would like.
The real problem comes when I try to enforce the difference of PartDer
from Part
; this version of module derived
doesn't compile:
import { Part, One, TwoMix } from './base'
export class OneDer extends One {
p: PartDer
}
export class TwoDerPre extends OneDer {
constructor (readonly a: number, readonly c: M[]) {
super(a)
}
}
export const TwoDer = TwoMix(TwoDerPre)
export interface PartDer extends Part {
u: typeof TwoDer
}
I get the errors:
src/derived.ts(13,14): error TS7022: 'TwoDer' implicitly has type 'any' because it does not have a type annotation and is referenced directly or indirectly in its own initializer.
src/derived.ts(15,3): error TS2502: 'u' is referenced directly or indirectly in its own type annotation.
I'm wondering why the compiler complains about this cycle when it doesn't complain about it in base
, and what I can do about it?
NOTE This is the shortest way I could think of to express the problem. Of course, TwoDer
is really not just the instantiation of the mixin, so the code at the end of derived
looks more along the lines of:
export class TwoDer extends TwoMix(TwoDerPre) {
}
export interface PartDer extends Part {
u: TwoDer
}
With this version I get slightly different errors:
src/derived.ts(17,6): error TS2304: Cannot find name 'TwoDer'.
src/derived.ts(17,6): error TS4033: Property 'u' of exported interface has or is using private name 'TwoDer'.
If I convert PartDer
to use typeof TwoDer
I get the original errors:
export interface PartDer extends Part {
u: typeof TwoDer
}
Upvotes: 0
Views: 527
Reputation: 250972
I can't quite fathom what you are trying to do, but TypeScript loses a bit of type information here:
export const TwoDer = TwoMix(TwoDerPre)
And this is causing a knock on effect. You can solve this specific issue by adding some type information:
export const TwoDer: Constructor<TwoPre> = TwoMix(TwoDerPre)
This clears the problem with 'u' is referenced directly or indirectly in its own type annotation.
confusion. I have no idea if it makes everything work for you.
Upvotes: 1