I've encountered a type error as shown following, based on TS mixin example, need some helps, thank you.
type GConstructor<T = {}> = new (...args: any[]) => T
interface Navigation<T> {
up(n: number): Navigation<T>
down(n: number): Navigation<T>
left(n: number): Navigation<T>
right(n: number): Navigation<T>
move(n: number): Navigation<T>
}
type Movable<T> = GConstructor<{
right: (n: number) => Navigation<T>
down: (n: number) => Navigation<T>
}>
function Movable<T, TBase extends Movable<T>>(Base: TBase) {
return class Movable extends Base {
left(n = 1) {
return this.right(-n)
}
up(n = 1) {
return this.down(-n)
}
move(right = 0, down = 0) {
this.right(right).down(down) // <- this function requires right() return Navigation instead of Movable
}
}
}
class Cursor {
row: number = 0
col: number = 0
down(n: number) {
return new CursorNext(this.row n, this.col) // <- this feels a little off, but return Cursor has same error
}
right(n: number) {
return new CursorNext(this.row, this.col n)
}
}
const CursorNext = Movable<Cursor, Cursor>(Cursor)
^^^^^^
Type 'Cursor' does not satisfy the constraint 'Movable<Cursor>'.
Type 'Cursor' provides no match for the signature 'new (...args: any[]): { right: (n: number) => Navigation<Cursor>; down: (n: number) => Navigation<Cursor>; }'.
I want to achieve derive free implementation from implementing abstract functions, much like this.
abstract class Movable {
abstract right(n: number)
left(n: number) {
return this.right(-n)
}
}
class Cursor extends Movable {
right() { ... }
}
new Cursor().left() // for free from abstract class
I'll have more abstract classes like this to implement in Cursor class so I'm using mixins.
CodePudding user response:
The correct type argument for the TBase
type parameter is typeof Cursor
, which would also be inferred if you were to omit it. However, to typecheck that typeof Cursor
satisfies Movable<T>
, you will also need to break the circular reference and explicitly declare the return types of the Cursor.down
and .right
methods to be compatible with Navigation
.
type GConstructor<T = {}> = new (...args: any[]) => T
interface Navigation<T> {
up(n: number): Navigation<T>
down(n: number): Navigation<T>
left(n: number): Navigation<T>
right(n: number): Navigation<T>
}
type Movable<T> = {
right: (n: number) => Navigation<T>
down: (n: number) => Navigation<T>
}
function Navigable<T, TBase extends GConstructor<Movable<T>>>(Base: TBase) {
return class extends Base implements Navigation<T> {
left(n = 1) {
return this.right(-n)
}
up(n = 1) {
return this.down(-n)
}
move(right = 0, down = 0) {
this.right(right).down(down)
}
}
}
class Cursor {
row: number = 0
col: number = 0
constructor(row: number, col: number) {
this.row = row;
this.col = col;
}
down(n: number): Navigation<never> {
return new CursorNext(this.row n, this.col)
}
right(n: number): Navigation<never> {
return new CursorNext(this.row, this.col n)
}
}
const CursorNext = Navigable(Cursor)
// or
const CursorNext = Navigable<never, typeof Cursor>(Cursor)
Notice I've removed the GConstructor
from the Movable
type and used it only in the type requirement for TBase
, I've renamed the function and made the class anonymous for less ambiguity, and instantiated T
to never
since it is use nowhere and could be completely omitted.