Skip to content

Update Pick to use new key remapping in mapped types #41383

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

Open
5 tasks done
tjjfvi opened this issue Nov 3, 2020 · 5 comments
Open
5 tasks done

Update Pick to use new key remapping in mapped types #41383

tjjfvi opened this issue Nov 3, 2020 · 5 comments
Labels
Experimentation Needed Someone needs to try this out to see what happens Suggestion An idea for TypeScript

Comments

@tjjfvi
Copy link
Contributor

tjjfvi commented Nov 3, 2020

Search Terms

pick omit key remapping

Suggestion

Update Pick in lib.d.ts to be:

type Pick<T, K extends keyof T> = {
    [P in keyof T as Extract<P, K>]: T[P];
};

It is currently defined as:

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

Use Cases

With this new definition, typescript can statically analyze the keys, which allows interfaces to extends these types.

Examples

interface Base {
    a: number,
    b: string,
    c: boolean,
}

// Errors: "An interface can only extend an object type or intersection of object types with statically known members"
interface PickBase0<K extends keyof Base> extends Pick<Base, K> {
    pickedKeys: K
}

type Pick1<T, K extends keyof T> = { [L in keyof T as Extract<L, K>]: T[L] }

// Works
interface PickBase1<K extends keyof Base> extends Pick1<Base, K> {
    pickedKeys: K
}

Playground Link

Checklist

My suggestion meets these guidelines:

  • This wouldn't be a breaking change in existing TypeScript/JavaScript code
  • This wouldn't change the runtime behavior of existing JavaScript code
  • This could be implemented without emitting different JS based on the types of the expressions
  • This isn't a runtime feature (e.g. library functionality, non-ECMAScript syntax with JavaScript output, etc.)
  • This feature would agree with the rest of TypeScript's Design Goals.
@RyanCavanaugh RyanCavanaugh added Experimentation Needed Someone needs to try this out to see what happens Suggestion An idea for TypeScript labels Nov 11, 2020
@RyanCavanaugh
Copy link
Member

I'm curious what would happen if we did that

@tjjfvi
Copy link
Contributor Author

tjjfvi commented Nov 12, 2020

@RyanCavanaugh I ran the tests with the updated definition; 17 failed (11 conformance and 6 compiler). Looking at the baselines, I was able to distill one problem with the new definition to:

type Pick<T, K extends keyof T> = { [P in keyof T as Extract<P, K>]: T[P] };
type Assert<T, U extends T> = U;
type X<T, K extends keyof T> = Assert<Pick<T, K>, T>;
//                                                ^
// Errors, "Type 'T' does not satisfy the constraint 'Pick<T, K>'"

I haven't looked through all of the baselines in detail to see if there are errors beyond that, though I suspect there are.

Perhaps Pick should be left as-is, and people who want interfaces involving it can define their own. Maybe add a PickInterface?

@tjjfvi
Copy link
Contributor Author

tjjfvi commented Nov 12, 2020

Also, what's the reasoning behind error 2312 (an interface can only extend an object type or intersection of object types with statically known members)? With the new as clause, many types that would appear to not have statically known members are allowed (like below). If types like these can have seemingly dynamic members, why can't interfaces extend the more general format of { [K in X]: Y }?

type Record1<K extends keyof any, V> = { [_K in "" as K]: V }
interface RecordInterface<K extends keyof any, V> extends Record1<K, V> { }

@bfricka
Copy link

bfricka commented Mar 24, 2021

For one thing I noticed this fixes issues with Omit or Pick + intersection types in some cases. I'm not smart enough to know what this is called, but when a Pick or Omit type is fed into certain generics, it gets reduced or simplified in ways I don't fully understand. I've seen it in various cases, but here's a playground example.

You can see that passing an omit type into a remapped type causes an issue, but this new way of doing it fixes the issue. I wish I understood why, but perhaps someone could shed some light on it.

@tjjfvi
Copy link
Contributor Author

tjjfvi commented May 28, 2021

@bfricka The problem in that scenario boils down to Omit's definition:

type Omit<T, K> = Pick<T, Exclude<keyof T, K>>

Because A has an index signature, that becomes Pick<A, string | number>. Homomorphic mapped types, on the other hand, have special casing for index signatures that maps them correctly.

#41966 looks like it might be relevant to your scenario.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Experimentation Needed Someone needs to try this out to see what happens Suggestion An idea for TypeScript
Projects
None yet
Development

No branches or pull requests

3 participants