3
const s: string = 'foo';

const pass1 = (origin: string) => origin.concat(s);
const pass2 = (origin: string[]) => origin.concat(s);

type S = string | string[];

const error = (origin: S) => origin.concat(s);

The code above. I can call concat in a string or string[] type. So why TypeScript disallow call concat in string | string[] type?

The error is:

Cannot invoke an expression whose type lacks a call signature.
Type '((...strings: string[]) => string) | { (...items: ConcatArray<string>[]): string[]; (...items: (s...'
has no compatible call signatures.

Because they have different return type? But I think TS can infer error's type is S. Is it a intentional design? If it is, why?

1
  • The good news is we can do this from TS 3.3. Also it need some improvement. Such as, const ret = error('msg'), what's the type of ret? TS infer it's S, but it should be string. Commented Feb 28, 2019 at 4:36

1 Answer 1

5

Because while the concat method is common between the two types it has a very different signature between the two, so Typescript can't really merge the declarations of the methods. While not ideal you can use a type guard to discern between the two types:

const s: string = 'foo';
type S = string | string[];

const error = (origin: S) => typeof origin === 'string' ?
    origin.concat(s) :
    origin.concat(s);

Or just assert as any :

const s: string = 'foo';
type S = string | string[];

const error = (origin: S) =>  (origin as any).concat(s) as S

There is also the option of transforming the union of signatures into an intersection of signatures. This may work well in some cases but not in others:

const s: string = 'foo';
type S = string | string[];

type UnionToIntersection<U> = 
(U extends any ? (k: U)=>void : never) extends ((k: infer I)=>void) ? I : never

function mergeSignature<T, K extends keyof T> (value: T, method: K) : UnionToIntersection<T[K]>{
    return ((...args: any[]) => (value[method] as any as Function).apply(value, args)) as any;
}

const error = (origin: S) => mergeSignature(origin, 'concat')(s); 
Sign up to request clarification or add additional context in comments.

6 Comments

I prefer type guard way. Thank you. Why TS cannot merge the result type in this case? I can write a function which may return T1 or T2, but I do not need declare the function return type as T1 | T2 explicitly, TS can infer it.
There is a discussion here as to why they don't do this github.com/Microsoft/TypeScript/issues/7294
I think they know about that issue, after all, they commented there ;)
Yes, I have read that issue before I ask this question in SOF. I understand it's difficult to handle deep call in a union type, it's hand to track what the union type consists of, such as curry function. But in my case, it's just a one level calling. It's a design trade-off, but can implement. Right?
@merito added a new workaround that transforms the signature union to an intersection of sigantures making it callable
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.