I want to create a type predicate so that when I use filter on array of Union of generics, it would return the correct type.
I wrote a type to indicate the input/output type of the convert function for the corresponding id:
type ConverterGroupIdToFunc = {
text: [string, string]
image: [File, string]
}
type ConverterGroupId = keyof ConverterGroupIdToFunc
type ConverterGroup<T extends ConverterGroupId> = {
id: T
convertFunc: (s: ConverterGroupIdToFunc[T][0]) => (ConverterGroupIdToFunc[T][1]);
}
And I also need a ConverterGroup array to store 2 different types of generics (ConverterGroup<"text">|ConverterGroup<"image">)[]
type ConverterGroupUnion = ConverterGroup<"text"> | ConverterGroup<"image">
For converterGroups: (ConverterGroup<"text">|ConverterGroup<"image">)[], I'd like find() to return type ConverterGroup<"text"> if the converterGroup's id is also "text".
I am trying to use a type predicate to tell find() to return the correct type but couldn't find how:
function isConverterGroup<T extends ConverterGroupId>(converterGroupId: T) {
return (converterGroup: ConverterGroupUnion): converterGroup is ConverterGroup<T> => {
return converterGroup.id === converterGroupId;
}
}
const convertFunc = converterGroups.find(isConverterGroup("text"))!.convertFunc;
The above code gives me the error:
A type predicate's type must be assignable to its parameter's type.
Type 'ConverterGroup<T>' is not assignable to type 'ConverterGroupUnion'.
Type 'ConverterGroup<T>' is not assignable to type 'ConverterGroup<"text">'.
Types of property 'id' are incompatible.
Type 'T' is not assignable to type '"text"'.ts(2677)
Here is the Typescript Playground Link
Sometimes you know that a type
Xis assignable to a typeY, but TypeScript can't see it because eitherXorYdepend on some generic type. In your example,ConverterGroup<T>is generic and the compiler can't figure out that it's assignable toConverterGroupUnion. And since a custom type guard function requires that fory is Xthe typeXis assignable totypeof y, it complains withconverterGroup is ConverterGroup<T>.In situations like this, you can usually fix it by changing
Xto eitherX & YorExtract<X, Y>orExtract<Y, X>depending on the use case. An intersection is always assignable to its members, and so is the result of the union-filteringExtractutility type. If you're right thatXis assignable toY, then eventuallyX & YandExtract<X, Y>(and maybeExtract<Y, X>, depending on the form ofYandX) will be justXonce the generic is specified, and the resulting behavior doesn't change. If you're wrong about the assignability then you'll end up with something else, possiblynever, so you should be careful that you know what you're doing with the types.Anyway since in your case it looks like you just want to filter the
ConverterGroupUnionto the member that is assignable toConverterGroup<T>, you can useExtract<ConverterGroupUnion, ConverterGroup<T>>:Now there's no error, and your other code works as expected:
Note that if you did something weird like
You get that
neverI was mentioning, becauseConverterGroup<"text" | "image">is not related to eitherConverterGroup<"text">orConverterGroup<"image">, sinceConverterGroup<T>is invariant inT(see Difference between Variance, Covariance, Contravariance, Bivariance and Invariance in TypeScript). Which is part of the reason for the error in the first place.Playground link to code