The main issue is in export type ActionReducerX<T> = () => T;
.
Seem to be that Map2<WithBase<T>>
and Map2<T> & Map2<Base>
are assignable to each other:
declare var returnType: Map2<WithBase<Value>>
declare var returnValue: Map2<Value> & Map2<Base>
returnType = returnValue
returnValue = returnType
But this is not the case when we use them with function argument type.
export function mergeAll2<T>(t: Map2<T>): Map2<WithBase<T>> {
const b: Map2<Base> = {
a: () => ({ aa: '' })
};
let x: Map2<T> & Map2<Base> = null as any as Map2<T> & Map2<Base>;
let y: Map2<WithBase<T>> = null as any as Map2<WithBase<T>>
x = y // ok
y = x // error
const result = {
...t,
...b
};
return result // error
}
Because function arguments are contravariant, we are no more allowed to assign x
to y
(Map2<T> & Map2<Base>
to Map2<WithBase<T>>
), The arrow of inheritance points in the opposite direction
.
There is a workaround:
export type ActionReducerX<T> = <U extends T>() => U;
export type Map2<T> = {
[key in keyof T]: ActionReducerX<T[key]>;
};
interface Base {
a: { aa: string };
}
type WithBase<T> = T & Base;
export function mergeAll2<T>(t: Map2<T>): Map2<WithBase<T>> {
// remove explicit type
const b = {
a: () => ({ aa: '' })
};
return {
...t,
...b
}
}
We can make this function contravariant
from the beginning.
export type ActionReducerX<T> = <U extends T>() => U;
type Animal = {
name: string
}
type Dog = {
paws: 4
} & Animal
type Check = Animal extends Dog ? true : false // false
type Check2 = ActionReducerX<Animal> extends ActionReducerX<Dog> ? true : false // true
Try to remove extra U
generic argument, and you will see that inheritance behavior will be changed in opposite direction.
I'm open to any sort of critics since this topic (variance) is still hard for me.
Here, here and here you can find more about this topic
UPDATE Custom wrapper for ActionReducer
Sadly it is not possible for me to extend the ActionReducer
You can just create your own custom type wrapper. See Wrapper
export type ActionReducerX<T> = () => T;
type Wrapper<T extends (...args: any) => any> = <U extends ReturnType<T>>() => U
export type Map2<T> = {
[key in keyof T]: Wrapper<ActionReducerX<T[key]>>;
};
interface Base {
a: { aa: string };
}
type WithBase<T> = T & Base;
export function mergeAll2<T>(t: Map2<T>): Map2<WithBase<T>> {
// remove explicit type
const b = {
a: () => ({ aa: '' })
};
return {
...t,
...b
}
}
type Animal = {
name: string
}
type Dog = {
paws: 4
} & Animal
type Check = Animal extends Dog ? true : false // false
type Check2 = Wrapper<ActionReducerX<Animal>> extends Wrapper<ActionReducerX<Dog>> ? true : false // true
Playground