Home > OS >  TS mixin error not match constructor type constraint
TS mixin error not match constructor type constraint

Time:11-07

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)

(Playground demo)

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.

  • Related