When defining UglyDeepMerge<T, U>
, the intention is for the result to always be assignable to U
, regardless of what type U
represents. If U
is a generic type constrained to Base
, then UglyDeepMerge<T, U>
will also be constrained to Base
. Unfortunately, due to the complexity of the conditional type used in the implementation of UglyDeepMerge<T, U>
, the compiler may struggle to analyze it properly. Generic conditional types like this are often opaque to the compiler, which means their evaluation is deferred. The compiler can determine that the result is constrained to Base
when specific types for T
and U
are provided, but making the leap from individual cases to the more abstract generic case is beyond its capabilities at this time.
To help the compiler understand that UglyDeepMerge<T, U>
is indeed assignable to U
for any given U
, one possible solution is to refactor the definition by wrapping it with Extract<⋯, U>
:
type UglyDeepMerge<T, U> = Extract<{
[K in (keyof T | keyof U)]:
K extends keyof U ? K extends keyof T ?
T[K] extends Record<string, unknown> ? U[K] extends Record<string, unknown> ?
UglyDeepMerge<T[K], U[K]> :
U[K] : U[K] : U[K] :
K extends keyof T ? T[K] : never
}, U>
type Test<T extends Base, U extends Base> =
DoSomething<UglyDeepMerge<T, U>>; // okay
This approach works because the Extract<T, U>
utility type is designed as a simple conditional type where the compiler can easily see that the result will be assignable to both T
and U
. Another option is to simplify the definition further into something more direct that showcases clear assignability, such as using an intersection type:
type DeepMerge<T, U> = (
T extends object ? {
[K in keyof T]: K extends keyof U ? DeepMerge<T[K], U[K]> : T[K]
} : unknown
) & U;
type Test<T extends Base, U extends Base> =
DoSomething<DeepMerge<T, U>>;
This second method involves intersections and may not fit your exact requirements, but it provides a more straightforward path for the compiler to recognize the assignability. Experimenting with different approaches can help find the best solution for your specific use case.
https://www.typescriptlang.org/play?ssl=17&ssc=71&pln=16&pc=1#code/JYOwLgpgTgZghgYwgAgEJwM4oN4ChnIhwC2EAXMhmFKAOYDcuAvrmAJ4AOKAIgPYDKvUmAAWdADwAVZBAAekEABMMaTBAB8yALzJJjXLiKkMHRCgDqvKAGsVeAuy7IAqrQA2bbhAgcAstFoIKQAaF00dAFF5KEQwcXsCZABtAGlkUGQACmsINl4YXWQAH2QcvILnAEoAXTJ8RLS5BWVS3PyXZAB+ZEb5CCUVMvbpTvqCSVTqmT6B5AAlCAQrRXEqGhBaUIBXEGsQXgB3EE1u50np5pUFpagVtbpt3f2jk7GXd09vPwCgi...manner