TypeScript: Conditional Mapped Types and Generic Type Parameters

348 Views Asked by At

I opened an issue in the TypeScript repo about conditional mapped types and generic type parameters. I was trying to define a mapped type SubAddsNewPropsToSuper<Sub, Super> which is kind of like the extends constraint for generic types except it only allows Sub to add new properties to Super while not changing the type of any existing property.

The question is if it is possible to define such a type. First goes my code example, second is the expected behaviour and third is the actual behaviour.

// defines a type that contains all properties of Super exactly as they are defined in Super
// and all properties of Sub that are not already defined in Super.
type SubAddsNewPropsToSuper<Sub, Super> = { [KeyOfNewPropsInSub in Exclude<keyof Sub, keyof Super>]: Sub[KeyOfNewPropsInSub] } & Super;

class Controller<TModel> {

    constructor(public model: SubAddsNewPropsToSuper<TModel, { Title: string }>) { }

    setFancyTitle() { 

        this.model.Title = "FANCY"; // works
        this.updateModel({ Title: "FANCY" }); // does not work - why?
        const x: Partial<SubAddsNewPropsToSuper<{ Title: "Foo" }, { Title: string }>> = { Title: "FANCY" }; // works
        const y: Partial<SubAddsNewPropsToSuper<TModel, { Title: string }>> = { Title: "FANCY" }; // does not work - why?
    }

    updateModel(changes: Partial<SubAddsNewPropsToSuper<TModel, { Title: string }>>) {
        // merge "changes" into "this.model"
    }
}

Expected behavior: The type SubAddsNewPropsToSuper<A, B> should define a type that contains all properties of type B exactly as they are defined in B even if type A defines the same property with a different type (e.g. changes the type of a property from string to "OnlyThisString"). The idea behind this is to circumvent the "problem" if Controller was defined as class Controller<TModel extends { Title: string }>{ ... } in which case TModel might actually change the type of property Title.

Actual behavior: It works as expected if A and B are both non-generic types but it does not work if A is a generic type.

Playground

0

There are 0 best solutions below