The primary issue at hand is that the current compiler lacks the capability to conduct higher-order type analysis required to recognize an equivalence between an unresolved generic object type and intersections of complementary property sets within that type. It remains uncertain whether such analysis will be implemented in the future as indicated by the status of the linked GitHub issue. While it may be feasible to modify the compiler to accommodate these scenarios, there are concerns about potential performance degradation.
Another contributing factor is the fact that the two types in question are not truly equivalent. Consider the interfaces Beta
and Alpha
:
interface Beta {
x: string | number,
y: boolean,
z?: object
}
interface Alpha extends Beta {
x: string,
y: true,
z: undefined
}
func<Alpha, Beta>(); // no error, but problematic
declare const beta: Beta;
declare const alpha: Alpha;
const notAlpha: Alpha = { ...alpha, ...beta }; // triggers an error!
Although Alpha
extends Beta
, combining an instance of Alpha
with an instance of Beta
does not produce a valid Alpha
. Therefore, calling func<Alpha, Beta>()
may not be appropriate.
Your focus may have been on expanding a type by introducing new properties rather than refining existing property types.
An approach to address this would involve enforcing stricter constraints on A
and
B</code so that shared keys between them possess mutually assignable property types. This can be achieved by specifying that <code>A extends B
and
B extends Pick<A, keyof A & keyof B>
. For any key
K
in
keyof A & keyof B
, both
A extends B
and
B extends Pick<A, keyof A & keyof B>
should hold true.
If you are convinced (more than the compiler) that Omit<A, keyof B> & B
actually equates to A
, you can utilize a type assertion to assert your reasoning over the compiler's doubts.
Here is a practical demonstration:
// implementing a stricter constraint
const func = <A extends B, B extends Pick<A, keyof A & keyof B>>() => {
const aWithoutB: Omit<A, keyof B> = {} as any;
const b: B = {} as any;
const a = {
...aWithoutB,
...b
} as A; // Outsmarting the compiler 🤓
};
func<Alpha, Beta>(); // now triggers an error, which is desired
func<Beta & { extraProp: string }, Beta>(); // permissible
Hopefully, this explanation proves helpful. Best of luck!