I've understood that something like:
type GenericExample<T> = T extends (infer U) ? U : 'bar';
is equal to:
type GenericExample<T> = T extends T ? T : 'bar';
But when stuff becomes more elaborate, TypeScript complains:
type Types = 'text' | 'date' | 'articles' | 'params';
type MyExperiment<Type extends Types> = { t : Type };
type MyExperimentsUnion = Types extends (infer U) ? MyExperiment<U> : never;
// Type 'U' does not satisfy the constraint 'Types'.
// Type 'U' is not assignable to type '"params"'.
So I'd like to ask why this is wrong: in this particular case distribution over union should take place, so the inferred U
type should be text
, then date
and so on.
So, what does T extends (infer U)
really mean and when it would be appropriate to use it?
I don't think it was meant to be used the way you are using it - basically infer
should be used in order to "infer" (or to resolve maybe better naming?) type, most commonly from a generic.
The way you are using it, you are creating a type that doesn't have any "dynamic" part (basically it's not generic), meaning it's always the same, and therefore inferring from something that is always the same doesn't make sense. Because at compile time you already know that Types
extends only Types & '...anything else'
, and since you can't define that other part in your MyExperimentsUnion
type, infer
doesn't have much of a use.
Example usage
interface Action<T> {
payload: T
}
type ExtractGeneric<T> = T extends Action<infer X> ? X : never
function getPayload<T extends Action<any>>(action: T): ExtractGeneric<T> {
return action.payload;
}
const myAction = { payload: 'Test' };
const myPayloadWithResolvedType = getPayload(myAction);
In the example above myPayloadWithResolvedType
would have string
as resolved type, because if you weren't using infer
, you'd have to pass that return type as second parameter probably like this:
function getPayloadNonExtract<T extends Action<U>, U>(action: T): U {
return action.payload;
}
Here is the link to the playground.
Cheers.
I know that this was asked 1 year ago, but for anyone who is late to the party, here it is.
I can't figure out how I missed this since it's right there in the docs, but as all of us here are in the same boat, let's see what's going on (starting from your code).
type Types = 'text' | 'date' | 'articles' | 'params';
type MyExperiment<Type extends Types> = { t : Type };
// We can drop the parentheses around "infer U" as they do nothing here
type MyExperimentsUnion = Types extends infer U ? MyExperiment<U> : never;
// Type 'U' does not satisfy the constraint 'Types'.
In your code, you only capture Types
using the infer
keyword, but that doesn't tell typescript anything about U
, so even though you will see this:
type foo = Types extends infer U ? U : never;
// foo will show up as 'text' | 'date' | 'articles' | 'params'
And wonder "Well, isn't U
then of type 'text' | 'date' | 'articles' | 'params'
? Why is it not assignable to MyExperiment<>
?". My guess* is that the union type is only resolved at the end of the conditional, so it is not technically available when you assign it as a type parameter to MyExperiment<>
.
If you wanted to do this with infer
and distribute the type, you would have to add an extra condition, to constrain U
at that time, to ensure that it is available as the correct type when you use it as a type parameter in MyExperiment<>
.
type MyExperimentsUnion =
Types extends infer U ?
U extends Types ?
MyExperiment<U>
: never
: never;
// MyExperiment<"text"> | MyExperiment<"date"> | MyExperiment<"articles"> | MyExperiment<"params">
Your example, however, could be also done like this
type MyExperimentsUnion<T extends Types = Types> = T extends any ? MyExperiment<T> : never;
// When you use MyExperimentsUnion with no type parameter, it will be
// MyExperiment<"text"> | MyExperiment<"date"> | MyExperiment<"articles"> | MyExperiment<"params">
* I am explicitly stating that it is my guess because I haven't really studied how TypeScript evaluates this.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With