I have written my own implementation of the build-in utility-type InstanceType:
type MyInstanceType<C extends abstract new (...args: any) => any>
= C extends abstract new (...args: any) => infer R ? R : never
It is very close to how it is actually implemented:
type BuiltInInstanceType<C extends abstract new (...args: any) => any>
= C extends abstract new (...args: any) => infer R ? R : any
the only difference is that I'm having last word never while actual implementation has there any
It results in a difference in how it treats any:
type MyWithAny = MyInstanceType<any> // unknown
type BuiltInWithAny = BuiltInInstanceType<any> // any
I assume that it means that it is somehow getting to the falsy branch of the conditional type
So the question is, how is this possible? Considering that conditional type is the same as type constraint and so anything that is falsy to the conditional type should also be falsy to type constraint and so should result in a type constraint failed error
And the second question, why MyWithAny is exactly unknown and not e.g. never?
UPD: based on the answer and discussion, this is how it's getting evaluated (more details in the accepted answer):
any as an argument to the generic function when evaluating MyWithAny and BuiltInWithAny has completely disabled the type constraints. So MyInstanceType has been evaluated like it'stype MyInstanceType<C> = C extends abstract new (...args: any) => infer R ? R : never
and BuiltInInstanceType has been evaluated like it's
type BuiltInInstanceType<C> = C extends abstract new (...args: any) => infer R ? R : any
that makes falsy branches possible
any includes instances that may or may not satisfy the conditional type, the type of MyWithAny is becoming R | never and the type of BuiltInWithAny is becoming R | anyR it considers as unknown. So, the result type of MyWithAny is unknown | never that is unkonown, and the result type of BuiltInWithAny is unknown | any that is anyYour example can be reduced to smth like this:
type WithNever = <T>(t: T) => T | never
type WithAny = <T>(t: T) => T | any
type NeverRetutn = ReturnType<WithNever>
type AnyRetutn = ReturnType<WithAny>
We are interested in T | never and T | any.
If TypeScript is unable to infer type of T, TS will assign unknown type.
See example:
type ReturnUnknown = ReturnType< <T>(t: T) => T> // unknown
What is never? (docs)
The never type is a subtype of, and assignable to, every type
In fact, you should treat never as empty union.
type Check = 1 | never // 1
type Check2 = unknown | never // unknown
type Check3 = any | never // any
As you might have noticed, never is assigned to every type.
Let's go back to your question. In fact, you are receiving union of types.
type MyWithAny = MyInstanceType<any> // R | never
type BuiltInWithAny = BuiltInInstanceType<any> // R | any
I hope, it is clear now, that R | never will be resolved as unknown, because R is unknown and never is assignable to unknown.
R | any is the same as unknown | any, and because any type is assignable to any you are getting just any.
But why you are receiving the union of types? It is clear that conditional type should return true or false branch, but not both. This is not how it works in TS.
Because you have provided any as an argument every branch is applied. I think it is expected, because I don't have enough arguments to say that only true branch should be applied, or only false branch.
Considering that conditional type is the same as type constraint and so anything that is falsy to the conditional type should also be falsy to type constraint and so should result in a type constraint failed error
Since any type is in fact any type (every type), why do you think false branch should be applied ? Keep in mind, you are working with types, not the values.
What is confusing to me about falsy branch is that how I understand it, it should always be evacuated to never, no matter what we put there
It can't be always evaluated to false branch. Such assumption is not correct.
Consider next example:
type CheckAny = any extends abstract new (...args: any) => any ? true : false
Above condition evaluates to true | false. And true | false union is just a built in boolean type.
I don't think typescript is not smart enough. TS just tells you that any type can extend constructor type but in the same time it can not extend.
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