5

This code

declare function fn<T, U>(array: T[], predicates: ((arg: T) => U)[]): [T, U];
let a = fn([1, 2, 3], [x => 2, x => 's']);

leads to this error:

The type argument for type parameter 'U' cannot be inferred from the usage. Consider specifying the type arguments explicitly. Type argument candidate 'number' is not a valid type argument because it is not a supertype of candidate 'string'. function fn(array: T[], predicates: ((arg: T) => U)[]): [T, U]

Why can't be U simply inferred to have the type string | number here?

thorn0
  • 9,362
  • 3
  • 68
  • 96

1 Answers1

9

TypeScript in general will not synthesize a union type during generic inference. The reason, in simplified terms, is that it's not desirable to do inference like this:

function compare<T>(x: T, y: T): number { ... }
// Could infer T: string | number here... but that'd be bad
compare('oops', 42);

If a generic type can't be formed by picking one of the inference candidates, you'll get the error you posted.

Experience informed this choice. In prior versions (before union types existed), {} would be inferred if no inference candidate was a supertype of all candidates. In practice this led to a lot of missed errors that looked like the example above.

Ryan Cavanaugh
  • 209,514
  • 56
  • 272
  • 235
  • Makes sense. But on the other hand, if we make `predicates` just `U[]`, `U` *is* inferred to be a union type. – thorn0 Oct 06 '16 at 21:23
  • Why are `U[]` and `((arg: T) => U)[]` treated differently? – thorn0 Oct 06 '16 at 21:24
  • 1
    `U[]` ends up being a single inference candidate whose type is taken from the element type of the array. Heterogenuous arrays used to be disallowed under roughly the same logic, but are now allowed because union types generally play well with them. – Ryan Cavanaugh Oct 06 '16 at 21:31
  • `((arg: T) => U)[]` is a kind of heterogeneous array too, isn't it? To me it looks more similar to the `U[]` case than to the `compare` function from your initial answer – thorn0 Oct 06 '16 at 22:00
  • [indeed, can someone explain the difference](https://www.typescriptlang.org/play/#src=const%20i%20%3D%20x%20%3D%3E%202%3B%0D%0Aconst%20s%20%3D%20x%20%3D%3E%20's'%3B%0D%0A%0D%0Alet%20b%20%3D%20%5Bi%2C%20s%5D%3B%0D%0A%0D%0Alet%20x%3A%20((arg%3A%20any)%20%3D%3E%20(number%20%7C%20string))%5B%5D%20%3D%20b%3B%0D%0A%0D%0Adeclare%20function%20fn%3CT%2C%20U%3E(array%3A%20T%5B%5D%2C%20predicates%3A%20((arg%3A%20T)%20%3D%3E%20U)%5B%5D)%3A%20%5BT%2C%20U%5D%3B%0D%0A%0D%0Alet%20a%20%3D%20fn(%5B1%2C%202%5D%2C%20x)%3B%0D%0A%0D%0Alet%20a2%20%3D%20fn(%5B1%2C%202%5D%2C%20b)%3B%0D%0A%0D%0A%0D%0A) – artem Oct 06 '16 at 22:36
  • I can :-) `((arg: T) => number) | ((arg: T) => string)` can not be unified into `(arg: T) => number | string` during inference for generic type argument because it will turn return type into a union type. Now the question is, does it make sense to relax this constraint if we already have union type to begin with? – artem Oct 06 '16 at 23:21
  • @RyanCavanaugh That makes a lot of sense as a default, but is there any way to tell TypeScript "hey actually please do synthesize a union type here during generic inference"? (BTW thanks for your work on TypeScript you're awesome!) – Han Seoul-Oh Jul 19 '17 at 19:01
  • @RyanCavanaugh What really surprised me is I couldn't even use type overloading to manually suggest synthesizing a union type: https://git.io/v7eO2 – Han Seoul-Oh Jul 19 '17 at 19:39
  • e.g. `pick(x:T, y:U): T | U` ? – Ryan Cavanaugh Jul 19 '17 at 19:47
  • @RyanCavanaugh How come we don't see the same behaviour when the parameters are objects (instead of primitives like in the example above)? https://typescript-play.js.org/#code/CYUwxgNghgTiAEYD2A7AzgF3gMxQLngB4AFAPgAoAHARgOIBp5KAmOgSngF5T5iBuAFACA3gPjj4AeknwAcknggYMJDAB08AAYxN8AJZp4UeAFcUe1PCTYmsKAFsQGJWjViJydFhhccKcsI4SEgE1PAAvoyBAEZQAF6hEWyC4SLu4p6Y8D6cfuTpEuLU9AWF0vAAosqqBACCMADmJo4oWNbwGACelAgA5ABE2MH9vfqGKEhYUGhoeg0oUNEQCBgKlHaOzj7tXT3wvdS9boVlMhUAHj1gzsDwaA4I0SAAFlAAbhYmPtNG0UhvCHI2l0BiMpnMlna6xgDy2hlymhQzSePgAPncMDA9CgGpo2McTuJekMkL0SoVkgJUkA – Oliver Joseph Ash Jun 12 '19 at 18:53