Home > Enterprise >  Typescript Generics and not assignable error
Typescript Generics and not assignable error

Time:02-11

I seem to have a understanding problem of how typescript types works. I have the following situation. I have a class which provides a function which can be overriden:

    /**
     * The current log method. Can be overridden to redirect output.
     */
    log: (...args: any[]) => void;

I have the following type

type AzureTransport = {
  (...args: any[]): void
}

No I have a kind of facade which looks like:

export interface DataChampLogger {
  info(...args: any[]): void
  error(...args: any[]): void
  warn(...args: any[]): void
  verbose(...args: any[]): void
  setLogLevel(level: DataChampLogLevel): void
  setTransport<T>(transport: T): void

// implementation
class .... {
 setTransport<AzureTransport>(transport: AzureTransport): void {
    AzureLogger.log = transport
  }
}
}

this does not work, I get the following error Type 'AzureTransport' is not assignable to type '(...args: any[]) => void'.ts(2322)

when I refactor the code to:

export interface DataChampLogger {
  info(...args: any[]): void
  error(...args: any[]): void
  warn(...args: any[]): void
  verbose(...args: any[]): void
  setLogLevel(level: DataChampLogLevel): void
  setTransport(transport: unknown): void

// implementation
class .... {
 setTransport(transport: AzureTransport): void {
    AzureLogger.log = transport
  }
}
}

everything works fine, but it is not really clear to why I get an error in the first version of the implementation.

[edit] Playground Link

Thanks for your time and help

Regards Mathias

CodePudding user response:

It's because Typescript cannot know that the AzureTransport type that you use in the class will be assignable to T, since T could be something else.

You can add a generic constraint like that:

export interface DataChampLogger {
  setTransport<T extends AzureTransport>(transport: T): void; // "T extends AzureTransport" is the constraint
}

class MyClass implements DataChampLogger {
  setTransport(transport: AzureTransport): void {
  }
}

Alternatively, you can move the generic to the interface itself, like that:

export interface DataChampLogger<T> // generic is here  {
  setTransport(transport: T): void;
}

class MyClass implements DataChampLogger<AzureTransport> {
  setTransport(transport: AzureTransport): void { // no complaints since T is AzureTransport
  }
}


On a side note, this

type AzureTransport = {
  (...args: any[]): void
}

is the same as

type AzureTransport = (...args: any[]) => void;

You don't need the curly brackets.

  • Related