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

Properties P in generic type U that extends type T should allow use of type T[P] #13547

Closed
DmitryEfimenko opened this issue Jan 17, 2017 · 11 comments
Labels
Working as Intended The behavior described is the intended behavior; this is not a bug

Comments

@DmitryEfimenko
Copy link

TypeScript Version: 2.1.4 / Playground

Code

type shouldCompile<T, U extends T> = {
    [P in keyof U]: T[P];
}

Expected behavior:
Should compile because U extends T, thus has the same properties

Actual behavior:
Does not compile.

Additional comments
I think I saw something similar beeing discussed in one of the previous issues, but I could not find it. Sorry if this is a duplicate.

The type is kept simple for the purposes of demonstration of the issue. It's unlikely it'd be created like that. In a more real situation U would be extending a Partial<T> or some other variation of T

Also, the title of this issue proves once again that naming things is hard!

@RyanCavanaugh
Copy link
Member

U has a superset of the properties of T, so it's not guaranteed that something in U is in T (concretely if T is { a } then U could be { a, b } and there is no b in T) . The reverse declaration works:

type shouldCompile<T, U extends T> = {
    [P in keyof T]: U[P];
}

@DmitryEfimenko
Copy link
Author

DmitryEfimenko commented Jan 17, 2017

You are correct. For the real world scenario, I was thinking about U extends Partial<T>, but even in that case, it's the same situation as you described.

Is there anything that would specify that U can only have props of T?
Something like U subset T

Edit:
I see there was a lot fo discussion what to name a Partial type and subset was one of the options. I agree with the decision there to name it a Partial since it's rather a superset - not a subset. However, I could not find any discussion about a true type subset, in which case this issue becomes a suggestion rather than a bug

Edit2:
For full context, this example shows what I was trying to achieve.
The parameter cols of function getAll() mimics the way you specify columns that you want to return from mongodb.

@NN---
Copy link

NN--- commented Jan 19, 2017

If there was something which produced only common properties between two types.
Such as {a:number;b:number;} @ {b:number;} = {b:number;} it would be easy to achieve:

type shouldCompile<T, U extends (T @ U)> = {
    [P in keyof U]: T[P];
}

@DmitryEfimenko
Copy link
Author

I think the problem is with the word extends. The moment it's used (in example U extends T), it's assumed that U is a superset of T, and we fall back into the problem described by @RyanCavanaugh .

That's why I think there should be a whole separate keyword. For example, subset

@mhegazy mhegazy added the Working as Intended The behavior described is the intended behavior; this is not a bug label Jan 25, 2017
@mhegazy mhegazy closed this as completed Feb 28, 2017
@DmitryEfimenko
Copy link
Author

hey, @mhegazy, I understand that the word extends works as intended, but that does not solve the underlying issue I had. Is there anything in the works, like the word subset I mentioned above that would allow the scenario I was talking about in the first comment?

@mhegazy
Copy link
Contributor

mhegazy commented Feb 28, 2017

As described by @RyanCavanaugh in #13547 (comment), it is not guaranteed that T will have the properties in U. if you can elaborate on the original scenario you are trying to achieve we can think of solution.

@DmitryEfimenko
Copy link
Author

DmitryEfimenko commented Feb 28, 2017

I'm trying to have TypeScript recognize the return type of the function given its parameters.
For example, a function that would query database table of a known type, but only return selected columns:

interface IPerson { id: string; name: string; age: number }
let person = db.getPerson<IPerson>({ id: 1, name: 1 });
// typescript should recognize the type of `person` is `{ id: string; name: string }` - without age property

Here is my best shot at this example in TypeScript playground.
Interestingly enough, typescript figures out types properly but has compile error.

@mhegazy
Copy link
Contributor

mhegazy commented Feb 28, 2017

I am assuming the object literal has number type for properties id and name intentionally.. you could use Record to infer the keys provided, call it K, then use Record again to get the types of IPerson for properties with the keys K specified:

getPerson<K extends keyof IPerson>(obj: Record<K, number>): Record<K, IPerson>;

@DmitryEfimenko
Copy link
Author

DmitryEfimenko commented Feb 28, 2017

yes, type for properties of id and name in the params object is intentionally a number. This is similar to MongoDb's query interface.

returning Record<K, IPerson> isn't right. It would be an object of type.

{ id: IPerson; name: IPerson}

instead of what's needed:

{ id: string; name: string }

@mhegazy
Copy link
Contributor

mhegazy commented Feb 28, 2017

Sorry. In my brain I was thinking of Pick but wrote Record :D..

declare function getPerson<K extends keyof IPerson>(obj: Record<K, number>): Pick<IPerson, K>;

@DmitryEfimenko
Copy link
Author

this is fantastic! Thanks for the help! That alone made my day!

@microsoft microsoft locked and limited conversation to collaborators Jun 19, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Working as Intended The behavior described is the intended behavior; this is not a bug
Projects
None yet
Development

No branches or pull requests

4 participants