Reputation: 433
Please, help me to figure out the logic of the TS compiler here.
I don't see any strong difference between LookUp and LookUpWrong and don't understand why LookUpWrong always returns never
.
type LookUp<U, T extends string> = U extends { type: string } ? U["type"] extends T ? U : never : never
type LookUpWrong<U extends { type: string }, T extends string> = U["type"] extends T ? U : never
type Animal = { type: 'dog' } | { type: 'cat' }
type ValidResult = LookUp<Animal, 'dog'> // { type: 'dog' }
type WrongResult = LookUpWrong<Animal, 'dog'> // never
Upvotes: 1
Views: 62
Reputation: 8316
I have less know how of typescript but gave it a shot on the playground based on @jcalz input.
From what I can see is that :-
type LookUp<U, T extends string> = U extends { type: string } ? U["type"] extends T ? U : never : never
type LookUpWrong<U extends { type: string }, T extends string> = U extends { type: string }? U["type"] extends T ? U : never : never
type Animal = { type: 'dog' } | { type: 'cat' }
type Banimal = { type : 6};
type ValidResultLookUp = LookUp<Animal, 'dog'> // { type: 'dog' }
type ValidResult2LookUpWrong = LookUpWrong<Animal, 'dog'> // {type:'dog'}
const obj:ValidResultLookUp = {type:'dog'};
const obj2:ValidResult2LookUpWrong = {type:'dog'};;
type invalidResultLookUp = LookUp<Banimal,'dog'> // doesn't give error since it's just U which Banimal satisfies
type invalidResultLookUpWrong = LookUpWrong<Banimal,'dog'> // gives error before hand since Banimal doesn't extend {type:string}
If you reformat LookUpWrong
same as LookUp
with the additional U extends {type:string}
conditional check as I did above, both the types when used to build ValidResultLookUp
and ValidResult2LookUpWrong
work as expected because the Animal
type satisfies the extends {type:string}
bit of the conditional.
Introducing that conditional check to LookUpWrong
kicks in the distributive conditional types property and instead of below :-
type someType = 'dog' | 'cat' extends 'dog' ? 'desiredType' : never;
someType
will be never
the below happens :-
type someType = 'dog' extends 'dog' ? 'desiredType' : never;
and also the cat
bit happens but is not relevant since dog
extends dog
due to distribution makes the conditional truthy and we get desiredType
.
Now consider Banimal
which is {type:6}
. LookUp
in this case will not give you an error when building invalidResultLookUp
since U
doesn't extends on {type:string}
unlike LookUpWrong
which does (the generics portion).
So you still need that conditional check(for distributive conditional effect) in case of LookUpWrong
but it prevents wrong assignment of type when building another type due to additional U extends {type:string}
safeguard.
Here is the playground link
Upvotes: 1