Alright, so first off, this is the simplified/generic version of a problem I'm encountering with a project. This project is to type some old JS code that was most definitely not built with types in mind (lots of mutations etc). That being said, I'm not at liberty to change the structures.
We essentially have some base structure on which we can have various "extensions" that we need to feed into functions that will add other extensions on top and we wanna keep everything generic.
I thought I had it pretty much working except Typescript said "nope" with the following error stack:
Type 'WrapperType' is not assignable to type 'Wrapper<ExtensionOf<WrapperType>>'.
Type 'Wrapper<{}>' is not assignable to type 'Wrapper<ExtensionOf<WrapperType>>'.
Type '{}' is not assignable to type 'ExtensionOf<WrapperType>'.
Here is the code:
function fn<WrapperType extends Wrapper>(wrapper: WrapperType):
Wrapper<
ExtensionOf<WrapperType> ///////////////////////////////////////////// This isn't working, but the goal is to add more extensions within this function
> {
return wrapper; // breaking here
}
type BaseObj = {
prop: number;
};
type Wrapper<
Extension = {} ///////////////////////////////////////////// try any instead of {}
> = {
obj: Extension & BaseObj; ///////////////////////////////////////////// try Partial around Extension
}
type ExtensionOf<WrapperType extends Wrapper> = Complement<BaseObj, WrapperType['obj']>; // We can swap BaseObj and WrapperType['obj'] because of the default Extension
type Complement<ExcludePropertiesFromThisType, Type extends ExcludePropertiesFromThisType> = Omit<Type, keyof ExcludePropertiesFromThisType>;
Edit: I think things get interesting when we change the function to
function fn<WrapperType extends Wrapper>(wrapper: Wrapper):
Wrapper<ExtensionOf<WrapperType>> {
return {
...wrapper,
};
}
And get the error:
Type '{ obj: BaseObj; }' is not assignable to type 'Wrapper<ExtensionOf<WrapperType>>'.
Types of property 'obj' are incompatible.
Type 'BaseObj' is not assignable to type 'ExtensionOf<WrapperType> & BaseObj'.
Type 'BaseObj' is not assignable to type 'ExtensionOf<WrapperType>'.
more specifically Type 'BaseObj' is not assignable to type 'ExtensionOf<WrapperType> & BaseObj'.
In the end this is the sort of things I'm trying to do:
const myWrapper: Wrapper<{ otherProp: number }> = {
obj: {
prop: 1,
otherProp: 2,
},
};
const myModifiedWrapper: Wrapper<{ otherProp: number } & WithOtherExtension> = fn(myWrapper);
console.log(myModifiedWrapper.obj.prop, myModifiedWrapper.obj.yetAnotherProp);
function fn<WrapperType extends Wrapper>(wrapper: WrapperType):
Wrapper<
ExtensionOf<WrapperType> & WithOtherExtension
> {
// @ts-ignore ///////////////////////////////////////////// we don't want to have to ignore, obviously
const newWrapper: Wrapper<ExtensionOf<WrapperType> & WithOtherExtension> = wrapper;
newWrapper.obj.yetAnotherProp = true; // this is the goal
return newWrapper;
}