Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Conditional Mapped Types and Generic Type Parameters #23207

Closed
gamli opened this issue Apr 6, 2018 · 3 comments
Closed

Conditional Mapped Types and Generic Type Parameters #23207

gamli opened this issue Apr 6, 2018 · 3 comments
Labels
Question An issue which isn't directly actionable in code

Comments

@gamli
Copy link

gamli commented Apr 6, 2018

TypeScript Version: 2.8.1 and 2.9.0-dev.20180406

Search Terms: Conditional Mapped Types Generic Type Parameter

Code

// 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 Link: http://www.typescriptlang.org/play/#src=%0A%2F%2F%20defines%20a%20type%20that%20contains%20all%20properties%20of%20Super%20exactly%20as%20they%20are%20defined%20in%20Super%0A%2F%2F%20and%20all%20properties%20of%20Sub%20that%20are%20not%20already%20defined%20in%20Super.%0Atype%20SubAddsNewPropsToSuper%3CSub%2C%20Super%3E%20%3D%20%7B%20%5BKeyOfNewPropsInSub%20in%20Exclude%3Ckeyof%20Sub%2C%20keyof%20Super%3E%5D%3A%20Sub%5BKeyOfNewPropsInSub%5D%20%7D%20%26%20Super%3B%0A%0Aclass%20Controller%3CTModel%3E%20%7B%0A%0A%20%20%20%20constructor(public%20model%3A%20SubAddsNewPropsToSuper%3CTModel%2C%20%7B%20Title%3A%20string%20%7D%3E)%20%7B%20%7D%0A%0A%20%20%20%20setFancyTitle()%20%7B%20%20%20%20%20%20%20%20%0A%20%20%20%20%20%20%20%20this.model.Title%20%3D%20%22FANCY%22%3B%20%2F%2F%20works%0A%20%20%20%20%20%20%20%20this.updateModel(%7B%20Title%3A%20%22FANCY%22%20%7D)%3B%20%2F%2F%20does%20not%20work%20-%20why%3F%0A%20%20%20%20%20%20%20%20const%20x%3A%20Partial%3CSubAddsNewPropsToSuper%3C%7B%20Title%3A%20%22Foo%22%20%7D%2C%20%7B%20Title%3A%20string%20%7D%3E%3E%20%3D%20%7B%20Title%3A%20%22FANCY%22%20%7D%3B%20%2F%2F%20works%0A%20%20%20%20%20%20%20%20const%20y%3A%20Partial%3CSubAddsNewPropsToSuper%3CTModel%2C%20%7B%20Title%3A%20string%20%7D%3E%3E%20%3D%20%7B%20Title%3A%20%22FANCY%22%20%7D%3B%20%2F%2F%20does%20not%20work%20-%20why%3F%0A%20%20%20%20%7D%0A%0A%20%20%20%20updateModel(changes%3A%20Partial%3CSubAddsNewPropsToSuper%3CTModel%2C%20%7B%20Title%3A%20string%20%7D%3E%3E)%20%7B%0A%20%20%20%20%20%20%20%20%2F%2F%20merge%20%22changes%22%20into%20%22this.model%22%0A%20%20%20%20%7D%0A%7D%0A

Related Issues:

@krryan
Copy link

krryan commented Apr 6, 2018

I believe this is related to a problem I had where I was attempting to use Omit<Foo, 'bar'> & { bar: AltBar; }, which worked perfectly when Foo was some defined type, but when Foo was a generic parameter that extends { bar: Bar; baz: Baz; }, the baz property was not available, only bar (as an AltBar) was.

And Omit doesn't even use conditions, it's just

type Diff<T extends string, U extends string> = ({ [P in T]: P } & { [P in U]: never } & { [x: string]: never })[T];
type Omit<T, K extends keyof T> = { [P in Diff<keyof T, K>]: T[P] };

In short, it doesn't seem like TypeScript can apply these sorts of modifications to generic/partially known structures?

@gamli
Copy link
Author

gamli commented May 23, 2018

Since the issue doesn't have a tag, nor is it closed it might be considered just a question?! I asked the question on StackOverflow.

@RyanCavanaugh RyanCavanaugh added the Question An issue which isn't directly actionable in code label May 29, 2018
@typescript-bot
Copy link
Collaborator

Automatically closing this issue for housekeeping purposes. The issue labels indicate that it is unactionable at the moment or has already been addressed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Question An issue which isn't directly actionable in code
Projects
None yet
Development

No branches or pull requests

4 participants