I have explained the problem by comments below. It is not as complex as the size of the code block would make it seem, just trying to include an easy to follow example.
Bottomline question: Why does the type inference fail at the end?
// Fancy type
type ItemErrors<IT> = {
[P in keyof IT]?: IT[P] extends object ?
ItemErrors<IT[P]>
: IT[P] extends any[] ?
string[]
: string
};
// Purpose of the fancy type is to be the same "shape" as given type but to represent (optional) errors for its properties
// Say there are two types that both have a name property
type WithName = {
name: string
other: number
obj: {
inner: boolean
}
}
type AlsoWithName = {
name: string
cool: boolean
}
// A union of those two types can only say for certain that it has a name
type UnionHasName = WithName | AlsoWithName
// this thing requires a string
function prints(mustbestring: string){
console.log(mustbestring)
}
function works(my: UnionHasName){
// must have a name and that is a string. check.
prints(my.name)
}
function alsoWorks<T extends UnionHasName>(something: T){
// if it extends UnionHasName it still must have a name and that a string. check.
prints(something.name)
}
function stillWorks(something: ItemErrors<WithName>){
// ItemErros for WithName means it either has a string for the name field, or else undefined. check.
prints(something.name ?? "")
}
function evenStillWorks(something: ItemErrors<UnionHasName>){
// ItemErros for UnionHasName means it either has a string for the name field, or else undefined. check.
prints(something.name ?? "")
}
function doesNotWork<T extends UnionHasName>(something: ItemErrors<T>){
// but here it fails...
// T extends UnionHasName, so T must have a "name: string" property
// within itemerrors then, that property of name must be either undefined or a string.
// yet the inference below fails. why?
prints(something.name ?? "")
}
The fundamental issue seems that TS currently won't take into account given generic constraints when evaluating conditional types for the sake of inference.
In my specific case, I realized the requirement represented by the generic constraint was actually tightly coupled (always true) for places where I wanted to use the conditional type, so I just "duplicated" the constraint into the conditional type logic and that seems to work.
Updated Typescript Playground