TypeScript Version: 1.8.4
Code
type stringType1 = "foo" | "bar";
type stringType2 = "baz" | "bar";
interface Temp1 {
getValue(name: stringType1);
}
interface Temp2 {
getValue(name: stringType2);
}
function test(t: Temp1 | Temp2) {
var z = t.getValue("bar"); // Error here
}
Expected behavior:
I was hoping everything would go fine (which is the case when I only use one interface in the function test
Actual behavior:
I get an error: "Cannot invoke an expression whose type lacks a call signature". Is this by design?
t
can be Temp2
. Temp2.getValue
accept name
that can be either "bar"
or "baz"
, not "foo"
.
This is currently by design because we don't synthesize an intersectional call signature when getting the members of a union type -- only call signatures which are _identical_ appear on the unioned type.
To make this work, we'd need some plausible algorithm that takes two sets of signatures and produces one (or many?) new signatures that are substitutes for the original.
interface Alpha {
(x: string): void;
(y: number, z: string): void;
}
interface Beta {
(...args: Array<number|string>): boolean;
}
interface Gamma {
(y: string|number, z: any): string;
}
let ab: Alpha | Beta;
let ac: Alpha | Gamma;
let bc: Beta | Gamma;
// What arguments can I invoke ab, ac, and bc with?
@DickvdBrink getting an error (albeit a different one) the way you laid out your example is a completely expected thing:
foo
is only an option for one of 2 possible methods, all equal there is a change that foo
is going to go to an instance of Temp2
which cannot be accepted by its getValue
method, hence the compile error (different from what you got)
@aleksey-bykov, you are correct - I made a mistake when creating the example - updated it.
The point of this issue was that I expected it to work with bar
but it didn't.
@RyanCavanaugh First, "align" all signatures so parameters can be compared on an individual basis. If any signatures contain rest parameters, pad all signatures with rest parameters of type undefined[]
. Next, pad all signatures with non-rest parameters of their rest parameter type until the number of non-rest parameters is the same. Then follow these rules.
Taking your first example (let ab: Alpha | Beta
):
Alpha | Beta
is aligned to:
(
(x: string, pad1: undefined, ...padRest: undefined[]) => void
& (y: number, z: string, ...padRest: undefined[]) => void
)
| (pad1: number | string, pad2: number | string, ...args: (number | string)[]) => boolean
((x: string) => R1 & (y: number) => R2) | (pad1: number) => R3
(x: string) => R1 & (y: number) => R2
may be invoked with string
to produce R1
, number
to produce R2
, or string | number
to produce R1 | R2
((x: X) => R) | (pad1: number) => R3
can only be invoked with an argument of type X & number
to produce a result of type R | R3
string & number
is passed, the remaining signature is R1 | R3
number & number == number
is passed, the remaining signature is R2 | R3
(string | number) & number == (string & number) | number
is passed, the remaining signature is (R1 | R2) | R3 == R1 | R2 | R3
Then, you apply this same algorithm again with the remaining signature and the next argument, until you've eventually exhausted all parameters (the rest parameter is treated as a single array parameter).
Note that your uncertainty about what is being returned and the constraints on what you have to pass both grow very rapidly with the number of parameters and overloads. While it seems to me that this is sound in the general case, it is likely to only be useful for simple function signatures.
Different code, related issue:
export type URI<K extends RouteParams> = string;
export interface RouteParams {
[key: string]: (string | number | boolean)
}
export interface Document {
[key: string]: (string | number | boolean)
}
/**
* Create a URI from a document properties
* @param the props to build the URI from
* @return the URI
*/
export type RouteCreator<K extends RouteParams> = (props: K) => string;
/**
* Parses a URI and returns the props
* @param uri the URI to parse
* @return the params parsed from URI
*/
export type RouteParser<K extends RouteParams> = (uri: string) => K;
export type Route<T extends RouteParams> = RouteParser<T> | RouteCreator<T>;
/**
* Creates a Route which is a function that either parse or stringify object/string
* @param route the route uri
* @return the Route
*/
export type RouteFactory<K extends RouteParams> = (route: string) => Route<K>;
export interface DocURI<K extends RouteParams> {
route: RouteFactory<K>;
}
import {DocURI, Document, RouteParams, URI, RouteFactory} from './Definitions';
const docuri = require('docuri');
function getRoute <T extends Document> (): DocURI<T> {
return (docuri as DocURI<T>);
}
...
const artistURI = getRoute<ArtistParams>().route('artist/name');
const parsed = artistURI(album.artist); // Cannot invoke an expression whose type lacks a call signature. Type 'Route<ArtistParams>' has no compatible call signatures.
I just ran into this with a situation like this:
let promise: Promise<boolean> | PromiseLike<boolean> = this.getPromise();
promise.then((result) {
});
The getPromise has the ability to return either a Promise or PromiseLike, of which both interfaces support the .then(
function. Attempting to use the then function results in a "Cannot invoke an expression whose type lacks a call signature" error as mentioned by others above.
Just thought this was another useful use case for this functionality which was worth sharing.
if you're not going to have type Promise<T> | PromiseLike<T>
as a result of this then
call, you can safely change the type of promise
to just PromiseLike<boolean>
since they are compatible.
let promise: PromiseLike<boolean> = this.getPromise(); // returns Promise<boolean> | PromiseLike<boolean>
promise.then((result) {
});
To add to this issue, I just saw this rear it's ugly head while working on the definition for the 'q' promise library.
We have this definition:
export function all<A, B>(promises: IWhenable<[IPromise<A>, IPromise<B>]>): Promise<[A, B]>;
export function all<A, B>(promises: IWhenable<[A, IPromise<B>]>): Promise<[A, B]>;
export function all<A, B>(promises: IWhenable<[IPromise<A>, B]>): Promise<[A, B]>;
export function all<A, B>(promises: IWhenable<[A, B]>): Promise<[A, B]>;
With this compilation test to make sure all our types are working:
const y1 = Q().then(() => {
let s = Q("hello");
let n = Q(1);
return <[typeof s, typeof n]> [s, n];
});
const y2 = Q().then(() => {
let s = "hello";
let n = Q(1);
return <[typeof s, typeof n]> [s, n];
});
const p2: Q.Promise<[string, number]> = y1.then(val => Q.all(val));
const p3: Q.Promise<[string, number]> = Q.all(y1);
const p5: Q.Promise<[string, number]> = y2.then(val => Q.all(val));
const p6: Q.Promise<[string, number]> = Q.all(y2);
Everything compiles fine, however, TSLint is saying that we can combine the function signature since the only thing that changes is the input. Sounds good, less code, so I modify my 'all' function definition for the different types:
export function all<A, B>(promises: IWhenable<[IPromise<A>, IPromise<B>]> | IWhenable<[A, IPromise<B>]> | IWhenable<[IPromise<A>, B]> | IWhenable<[A, B]>): Promise<[A, B]>;
But when I do so, the same test as above is now giving me an error:
error TS2322: Type 'Promise<[Promise<string>, Promise<number>]>' is not assignable to type 'Promise<[string, number]>'.
Type '[Promise<string>, Promise<number>]' is not assignable to type '[string, number]'.
Type 'Promise<string>' is not assignable to type 'string'.
error TS2322: Type 'Promise<[string, Promise<number>]>' is not assignable to type 'Promise<[string, number]>'.
Type '[string, Promise<number>]' is not assignable to type '[string, number]'.
Type 'Promise<number>' is not assignable to type 'number'.
In the meantime, I can work around it easily by removing the TSLint rule and keeping it as it was, but I'm curious as to why typescript is having problems deciphering the type based on the signature since it works when using overloaded functions.
fml
To make this work, we'd need some plausible algorithm that takes two sets of signatures and produces one (or many?) new signatures that are substitutes for the original.
Huh? Can't you let it compile if it matches any of the call signatures? you don't have to merge them, all you have to do is check if any is matched. I am not seeing the problem.
@oresoftware That's backwards. If you have a union of things, you can only apply operations that all the constituents support (or try to narrow the union by inspecting the value).
How can I get around this basic example: https://www.typescriptlang.org/play/#src=type%20Callback%20%3D%20(((cb%3A%20Function)%20%3D%3E%20void)%20%7C%20((one%3A%20any%2C%20cb%3A%20Function)%20%3D%3E%20void))%3B%0D%0A%0D%0Aconst%20test%20%3D%20(cb%3A%20Callback)%20%3D%3E%20%7B%0D%0A%20%20%20%20cb(()%20%3D%3E%20%7B%7D)%3B%20%20%20%20%20%20%20%2F%2F%20while%20this%20matches%20both%20types%0D%0A%20%20%20%20cb(1%2C%20()%20%3D%3E%20%7B%7D)%3B%20%20%20%20%2F%2F%20this%20one%20clearly%20only%20matches%20the%20second%0D%0A%7D
if the union type is declared in an external module:
https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/passport-oauth2/index.d.ts#L28
Declaration is correct. Hard to understand this. 😕
I'd like to through another example into the ring. This is with the same base type. It is just T[]|T[][]
and it is causing this issue.
https://www.typescriptlang.org/play/#src=export%20type%20PrimitiveValue%20%3D%20number%20%7C%20string%20%7C%20boolean%3B%0D%0Aexport%20type%20PrimitiveOrArray%20%3D%20PrimitiveValue%20%7C%20PrimitiveValue%5B%5D%20%7C%20PrimitiveValue%5B%5D%5B%5D%3B%0D%0A%0D%0Aconst%20x%3A%20PrimitiveOrArray%20%3D%205%20as%20PrimitiveOrArray%3B%0D%0A%0D%0Aif%20(Array.isArray(x))%20%7B%0D%0A%20%20%20%20const%20y%20%3D%20x.map(v%20%3D%3E%20v)%3B%0D%0A%7D%20%0D%0A%0D%0A
I think this is the same problem? If I change it to this then the compiler error goes away.
Broken:
export interface Bug {
buggyFunction(): string[] | number | PromiseLike<string[]> | PromiseLike<number>;
}
Compiles:
export interface Bug {
buggyFunction(): string[] | number | PromiseLike<string[] | number>;
}
PromiseLike<string[] | number>
and PromiseLike<string[]> | PromiseLike<number>
are two very different types and you have to choose the correct one depending on what you mean.
@RyanCavanaugh Thank you very much for your information. So then this compiler error is valid then?
"use strict";
export class Implementation {
public extra(): PromiseLike<void> {
return null;
}
public buggyFunction(): string[] | number | PromiseLike<string[]> | PromiseLike<number> {
return this.extra().then(() => {
return this.trigger();
});
}
public trigger(): string[] | number {
return [];
}
}
To make this work, we'd need some plausible algorithm that takes two sets of signatures and produces one (or many?) new signatures that are substitutes for the original.
I also don't understand this. Synthesizing a new signature seems hard, so why can't typescript just leave the type as the union of the signatures, but then, when the function is called, check that the arguments provided are valid for each constituent of the union and distribute over the union to get the result type?
I.e., from @RyanCavanaugh's example:
// These variables would have the same type as they do today.
let ab: Alpha | Beta;
let ac: Alpha | Gamma;
let bc: Beta | Gamma;
// Calling a variable whose type is a union of call signatures distributes over them,
// or errors if any constituents of the union can't work with the provided arguments.
// The calls below are valid because, in each call, the arguments match a signature
// in both Alpha and Beta. Return type is void | boolean
ab("test");
ab(4, "test");
// This might or might not be ruled valid, depending on the strictness desired.
// It clearly matches the signature in Beta, and might match the first overload in Alpha.
ab("test", 4);
// This is invalid, as it doesn't match any signature from alpha.
ab(4);
Figuring out if a case is valid/invalid for ac
and bc
is similar. Algorithmically, I think verifying this is linear in the number of signatures and overloads, so that doesn't seem like it would be a problem.
Consider this program:
declare function fn1(x: number, cb: (x: number) => void): void;
declare function fn2(x: Date, cb: (x: Date) => void): void;
declare function fn3(x: string, cb: (x: string) => void): void;
type F = typeof fn1 | typeof fn2 | typeof fn3;
declare const f: F;
f(<any>undefined, q => {
f(q, z => f(z, p => p.toString()));
});
The variable q
now has to take on three different values during typechecking. Then the variable z
takes on nine different values during typechecking. You could potentially go as deep as you wanted here, each step being combinatorially explosive with the prior one. The entire checker is built around the principle that once we've determined the type of a node, we don't ever need to do it again during the same lifetime of compilation.
Then we'd need new mechanics around keeping track of what variables were introduced when, rolling back any inferences made there, and repeatedly re-typechecking expressions because we have no idea which results were invalidated. Not simple.
Ok, I think I understand the problem, even though the details of when inference happens and what all the sources are is still a little beyond me.
Maybe this is naive, but... would a simple solution be to apply the approach I proposed above, but only if none of the arguments are function types? (If an argument is a function type, the call site would error, which is no worse than where things stand today.)
Granted, even if that would work (and idk if it does), adding a special case like that is a little ugly. But, if it would solve the error in most cases and a general solution isn't forthcoming, I think it'd be worth it. The workarounds I've had to add to my own code to make things compile are also pretty ugly so, if it's just a question of where to put a hack, I'd rather have it in the compiler than make every user deal with it.
Fwiw (and the implementations may be too different for this to be useful), the OP's example works in Flow after its converted to Flow syntax:
type stringType1 = "foo" | "bar";
type stringType2 = "baz" | "bar";
type Temp1 = { getValue(name: stringType1): string }
type Temp2 = { getValue(name: stringType2): number }
function test(t: Temp1 | Temp2) {
var z = t.getValue("bar"); // typeof z is string | number
}
Also, for the example you (@RyanCavanaugh) gave above, flow gives a clearer error and then works with a single annotation on q:
declare function f(x: number, cb: (x: number) => void): void;
declare function f(x: Date, cb: (x: Date) => void): void;
declare function f(x: string, cb: (x: string) => void): void;
f((undefined: any), q => {
f(q, z => f(z, p => { p.toString() }));
});
// Error: Could not decide which case to select. Since case 1 [1] may work but if it doesn't case 2 [2] looks promising too. To fix add a type annotation to `q` [3].
Again, I know Flow's inference works differently, so there may not be anything to borrow here, but maybe there is?
Is the following just another manifestation of this issue?
Code
const array: number[] | string[] = [];
array.forEach(value => {
console.log(value);
});
Expected behavior:
value
should be of type number | string
Actual behavior:
Cannot invoke an expression whose type lacks a call signature.
Type '((callbackfn: (value: number, index: number, array: number[]) => void, thisArg?: any) => void) | ...'
has no compatible call signatures.
@bradenhs yep, same issue. If you type the array as (number | string)[]
, the code compiles.
Until this is addressed, one workaround is to use something like this to merge disjoint array types into one:
type $Coalesce<T extends any[]> = Array<T[0]>;
type OriginalType = number[] | string[];
const array: $Coalesce<OriginalType> = []; // array: (number | string)[]
Hi, I have a similar issue, read this: why typescript disallow call concat in string | string[] type?
. I think TS can infer type of error
is S
. And if I call const ret = error('foo')
, TS can make a "type sink", infer type of ret
is string
. So why TS cannot support this?
Possibly the same bug:
// ok
type OneAction = { type: 'ACTION_ONE' }
// this will cause bug
type MultipleAction = { type: 'ACTION_ONE' } | { type: 'ACTION_TWO' }
export function createActionCreator<P = void>() {
const withoutPayload = () => null
const withPayload = (payload: P) => null
// works
// type y = typeof withPayload
// causes bug, even if both returns `typeof withPayload`
type y = P extends void ? typeof withoutPayload : typeof withPayload
return withPayload as y
}
// works
const zero = createActionCreator()
zero()
// works
const one = createActionCreator<OneAction>()
one({ type: 'ACTION_ONE' })
// BUG: Cannot invoke an expression whose type lacks a call signature.
const multiple = createActionCreator<MultipleAction>()
multiple({ type: 'ACTION_ONE' })
This should work :(
I was able to fix my issue above:
export function createActionCreator<P = void>() {
- const withoutPayload = () => null
- const withPayload = (payload: P) => null
-
- return withPayload as P extends void
- ? typeof withoutPayload
- : typeof withPayload
+ return (...args: P extends void ? [] : [P]) => args[0]
}
See real app code
export function createAction<T extends string>(type: T): Action<T, void>
export function createAction<T extends string, P>(
type: T,
payload: P,
): Action<T, P>
export function createAction<T extends string, P>(type: T, payload?: P) {
return typeof payload === 'undefined' ? { type } : { type, payload }
}
export function createActionCreator<T extends string, P = void>(type: T) {
return (...args: P extends void ? [] : [P]) => createAction(type, args[0])
}
// just a workaround for a good type checking without having to duplicate code
// while typescript doesnt support partial type argument inference
// see: https://stackoverflow.com/a/45514257/2228575
// see: https://github.com/Microsoft/TypeScript/pull/26349
export function createActionCreatorCreator<T extends string>(type: T) {
return function creator<P = void>() {
return createActionCreator<T, P>(type)
}
}
// USAGE:
export const popModal = createActionCreator('POP_MODAL')
popModal()
export const pushModal = createActionCreatorCreator('PUSH_MODAL')<Modal>()
pushModal({ name: 'THAT_MODAL', params: {} })
There's an issue I keep seeing questions about, having to do with what I call "correlated types" which are somewhat related to existential types (#14466). In short, something like this:
type A = {x: string, f: (x: string)=>void} | {x: number, f: (x: number)=>void};
declare const a: A;
a.f(a.x); // error!
gives people an unexpected error about a union signature type for a.f
. I had opened an issue (#25051) about this, but it was closed as a duplicate of this issue. But I don't think the fix in #29011 will ever address it.
I don't think that my suggestion in #25051 is necessarily the right way to address it, and maybe it doesn't ever need to be addressed (e.g., just use type assertions and move on), but I'd like some canonical place to send people who ask about it, and it doesn't seem to be here. Should I open a new issue?
@jcalz I think the generic values issue covers your case:
type A = <T> {x: T, f: (x: T)=>void};
@Kinrany The point is that you can't just change the signature. In the question @jcalz mentions the signature comes from a map object, where there is a handler for each member of the union that takes as a parameter the corresponding union member.
A minimal version from the question:
type Types = 'baz' | 'bar';
type Foo<T extends Types> = { type: T; }
type AllFoos = Foo<'bar'> | Foo<'baz'>
type Predicate<T extends Types> = (e: Foo<T>) => boolean;
type Policies = {
[P in Types]: Predicate<P>
}
const policies: Policies = {
baz: (e: Foo<'baz'>) => true,
bar: (e: Foo<'bar'>) => true
}
function verify(e: AllFoos) {
const policy3 = policies[e.type]; // type is one of bar | baz
// we are calling with the same type, this is valid for sure
const result3 = policy3(e); // but ts raises an error
}
@Kinrany I think generic values could possibly, if used cleverly, give this kind of functionality, but the type you posted would not be it... A
is not supposed to be <T> {x: T, f: (x: T)=>void}
for all T
(even if you turn T
into T extends string | number
... or maybe something like T in string | number
to indicate that we are iterating through union constituents and not trying to accept string or number literals, as in #17713, which I see is also relevant to this issue), it's actually <T in string | number> {x: T, f: (x: T)=>void}
for some T
, an existential type which might be written as exists T in string | number. {x: T, f(x: T)=>void}
or possibly <∃T in string | number> {x: T, f(x: T)=>void}
. It's like the difference between intersection and union but for quantifiers.
But anyway I don't know that full existential types or generic values are needed here (since A
really isn't generic... it's exactly one of two types). It's more like I want control flow narrowing to happen once for each constituent of a union, which led to my proposal in #25051, which was closed as a duplicate of this, which uh oh I'm just repeating myself. 🤐
I ran into something unexpected and I think it's the same issue. Given a simple sum type:
interface SimpleA {
example(arg: string): string;
}
interface SimpleB {
example(arg: string): number;
}
type Simple =
| SimpleA
| SimpleB;
let test: Simple;
let result = test.example("hi");
Everything works as expected, result
has type string | number
.
However, if all I do is add a generic:
interface SimpleA {
example<T>(arg: string): string;
}
interface SimpleB {
example<T>(arg: string): number;
}
type Simple =
| SimpleA
| SimpleB;
let test: Simple;
let result = test.example("hi");
Suddenly I get cannot invoke an expression whose type lacks a call signature
. I can't really see why adding (the same) generic to both functions would make them incompatible; they are called the same way. The only difference is the ret value.
The only way to make them compatible again is to make them have the same return type, which is not a restriction the non-generic version has, and it was a lot of hair-pulling before I isolated it to the generic. Even more weirdly, if you only add the generic to one of them, it still works fine.
I can't really see why adding (the same) generic to both functions would make them incompatible; they are called the same way. The only difference is the ret value.
Well, it's because we don't really _know_ if the generics are the same (and so because of that we'd opt to make the parameter type T & T'
instead and combine the type parameter lists to <T, T'>
), so we opt to not allow the call. getUnionSignatures
inside checker.ts
is the implementation of this. We're very open to improving it if we can be confident that handling generics is both correct and won't tank performance, we definitely just started with nongeneric signatures since they're conceptually simpler. It's also complicated by explicitly passed type arguments - those make generating an actual new type parameter list dicey.
This is still an issue with TS >3.3 when you cannot change the function signature for 'aligning' because they are defined in an external lib. For instance with Mongoose:
when a union type specifies the schema:
type TUser = TUserAdmin | TUserNormal
that gets intersected with Document
type from mongoose types to create the document instance type:
type TUserDoc = TUser & Document
(note you cannot define this as interface since you cannot extend a type)
now if you create a new document of that type, you won't be able to call any of the methods defined on Document in mongoose library, like doc.save()
This is a must-have for mapping over method-chained / fluent interfaces.
Is this the same case and would be closed as a duplicate?
interface Fizz {
id: number;
fizz: string;
}
interface Buzz {
id: number;
buzz: string;
}
([] as Fizz[] | Buzz[]).map(item => item.id);
https://www.typescriptlang.org/play/#src=interface%20Fizz%20%7B%0D%0A%20%20%20%20id%3A%20number%3B%0D%0A%20%20%20%20fizz%3A%20string%3B%0D%0A%7D%0D%0A%0D%0Ainterface%20Buzz%20%7B%0D%0A%20%20%20%20id%3A%20number%3B%0D%0A%20%20%20%20buzz%3A%20string%3B%0D%0A%7D%0D%0A%0D%0A(%5B%5D%20as%20Fizz%5B%5D%20%7C%20Buzz%5B%5D).map(item%20%3D%3E%20item.id)%3B%20
I came across this issue when trying to choose between graphql response and default initial data for a form.
Each come with their own data format, due to the one being an API response.
type CompoundType = Campaign_result | Campaign
const initialData: CompoundType = props.campaign || emptyCampaign
full snippet and codesandbox example here issue@33591
Is this the same case and would be closed as a duplicate?
interface Fizz { id: number; fizz: string; } interface Buzz { id: number; buzz: string; } ([] as Fizz[] | Buzz[]).map(item => item.id);
https://www.typescriptlang.org/play/#src=interface%20Fizz%20%7B%0D%0A%20%20%20%20id%3A%20number%3B%0D%0A%20%20%20%20fizz%3A%20string%3B%0D%0A%7D%0D%0A%0D%0Ainterface%20Buzz%20%7B%0D%0A%20%20%20%20id%3A%20number%3B%0D%0A%20%20%20%20buzz%3A%20string%3B%0D%0A%7D%0D%0A%0D%0A(%5B%5D%20as%20Fizz%5B%5D%20%7C%20Buzz%5B%5D).map(item%20%3D%3E%20item.id)%3B%20
I ran into this exact problem. Is it the same case?
Most helpful comment
I ran into something unexpected and I think it's the same issue. Given a simple sum type:
Everything works as expected,
result
has typestring | number
.However, if all I do is add a generic:
Suddenly I get
cannot invoke an expression whose type lacks a call signature
. I can't really see why adding (the same) generic to both functions would make them incompatible; they are called the same way. The only difference is the ret value.The only way to make them compatible again is to make them have the same return type, which is not a restriction the non-generic version has, and it was a lot of hair-pulling before I isolated it to the generic. Even more weirdly, if you only add the generic to one of them, it still works fine.