No Overload Matches This Call. Type 'String' Is Not Assignable to Type 'Signals'

How to repair a 'TS2769: No overload matches this call'

My colleague eventually taught me a workaround:

  1. Exclude the corresponding action, e.g. takeEvery from the original import statement.
  2. Import all action with as Eff where Eff is an alias.
  3. Define a new constant with the same name as the original action.
import { put, fork, select, call } from 'redux-saga/effects' // <-- modified
import * as Eff from 'redux-saga/effects' // <-- new

// ...

const takeEvery: any = Eff.takeEvery; // <-- new
const takeLatest: any = Eff.takeLatest; // <-- new

As far as I understand him, the idea is to explicity allow any type.

Edit: I am aware that Facebook (as developer of React) does explicitly state that one should not use inheritance, see https://reactjs.org/docs/composition-vs-inheritance.html

Overload signatures, union types and "No overload matches this call" error

Passing Unions to An Overload

Typescript is not able to "split up" the union before checking it against your overload signatures. It is checking your variable of type Promise<string> | string against each overload individually. The union is not assignable to either of its members so there is no overload signature that accepts Promise<string> | string.

This is a known behavior and some of the GitHub issues about this date back years.

  • Support overload resolution with type union arguments #14107
  • Union types not working with old-style overloads #1805

The argument against changing the typescript behavior seems to be that the number of possible combinations can explode quickly when you have a function with multiple arguments that each accept multiple types.

Since typescript doesn't support this on its own, you have to manually add an overload that accepts the union and returns the union, like you have already done. Note that the implementation signature (the last line of the overload) does not count as one of the overloads. So just having the union in the implementation signature is not enough.

function trim(text: Promise<string>): Promise<string>;
function trim(text: string): string;
function trim(text: Promise<string> | string): Promise<string> | string;
function trim(text: Promise<string> | string): Promise<string> | string {
if (typeof text === "string") {
return text.trim();
} else {
return text.then(result => result.trim());
}
}

Generics

We don't have this problem when using generics instead of overloads because extends includes the union. T extends A | B means that T can be A, B, or the union A | B (or any more refined version of these, which I'll get into in a bit).

function trim<T extends Promise<string> | string>(text: T): T {

However generics raise their own issues when it comes to the implementation of the function because refining the type of the variable text doesn't refine the type of the generic T. That behavior makes sense in general, given that the type T could be a union.

It's frustrating here especially because we are returning the same type as the input. So if we know that text is a string then we know that T must include string so it should be fine to return a string, right? Nope.

It feels like we only have three possibilities (string, Promise<string>, Promise<string> | string). But extends opens infinite possible types for T that are refinements of those. If T is the literal string " A " then the string "A" that we return from text.trim() really isn't assignable to T. So we've solved one issue but created another. We have to make as assertions to avoid errors like:

Type 'string' is not assignable to type 'T'. 'string' is assignable to the constraint of type 'T', but 'T' could be instantiated with a different subtype of constraint 'string | Promise'.(2322)

And there are some weird edge cases where those assertions might be incorrect.

function trim<T extends Promise<string> | string>(text: T): T {
if (text instanceof Promise) {
return text.then((result) => result.trim()) as T;
}
return (text as string).trim() as T;
}

const a = trim(' ' as Promise<string> | string); // type: string | Promise<string>
const b = trim(' '); // type: ' ' -- actually wrong!
const c = trim(' ' as string); // type: string
const d = trim(new Promise<string>(() => ' ')); // type: Promise<string>
const e = trim(new Promise<' '>(() => ' ')); // type: Promise<' '> -- wrong again!

Typescript Playground Link

So I would prefer the overloaded version even though you need that extra line.



Related Topics



Leave a reply



Submit