Reputation: 2336
While working on a library, I discovered what to me looks like a bug when using Generics:
type R<A> = A extends Bottom ? A : A
type Bottom = { test: number }
const f = <A extends Bottom>(a: A) => {
useIt(a) // type error here
}
const useIt = <A extends Bottom>(a: R<A>) => console.log(a)
As you can also see in the
Playground example, for some unclear reason a
cannot be used as R<A>
, even though this type is equivalent to A
.
The type error is:
Argument of type 'A' is not assignable to parameter of type 'R<A>'.
Type 'Bottom' is not assignable to type 'R<A>'.
Using a concrete type instead of a generic will work as expected, eg:
type X = {test: 1}
const x: R<X> = {test: 1} // all good
const noX: R<X> = {test: 2} // error
Having a better restriction type will also work as expected for concrete types:
type R<A> = A extends Bottom ? A : never
const x: R<X> = {test: 1} // all good
const error: R<{}> = {} // type error as expected given that {} doesn't extend Bottom
So, is there any way to make it work with Generics?
Upvotes: 0
Views: 72
Reputation: 2336
After a lot of tinkering, I solved this problem by explicitly adding the restriction:
const f = <A extends Bottom>(a: R<A>) => {
useIt(a) // works
}
const useIt = <A extends Bottom>(a: R<A>) => console.log(a)
Please note that now f
argument has the same constraint of useIt
, which will make the compiler happy. With hindsight, this actually makes sense, so that we are 100% sure the type is usable for useIt
too :)
Upvotes: 0
Reputation: 328196
This is more of a design limitation than a bug; unresolved conditional types (ones which depend on a yet-to-be-specified generic type parameter) are more or less deferred completely by the compiler, and almost nothing is seen as assignable to them.
There's an open issue, microsoft/TypeScript#23132, that suggests using generic constraints to determine assignability to unresolved conditional types; I think if this suggestion were implemented your example code would work (because A extends Bottom
would be seen as true)... so you might want to go that issue and give it a 👍 and possibly explain your use case if you think it's more compelling than what's there.
There's also microsoft/TypeScript#33912, which proposes using control flow analysis to determine assignability to unresolved conditional types, which might also help if it were to be implemented.
Right now I think the only way to "make it work" is either to use type assertions, as in:
useIt(a as R<A>)
or to express your type so that it is no longer an unresolved conditional type, if possible; in your example code, R<A>
is unconditionally A
, so
// type R<A> = A extends Bottom ? A : A
type R<A> = A
would solve it.
Actually I see you changed R<A>
in another part of your code to be essentially Extract<A, Bottom>
. In some instances, Extract<T, U>
can be replaced by the intersection T & U
without ill effects; you might try that instead:
// type R<A> = A extends Bottom ? A : never
type R<A> = A & Bottom
That might also work.
Okay, hope that helps; good luck!
Upvotes: 1