Getting the complement of a type from a type parameter

Viewed 133

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>;

TS playground of the above

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'.

TS playground of that

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;
}

TS playground of what the endgame is

0 Answers
Related