Skip to content

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

Description

@tjjfvi

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.

Activity

  1. RyanCavanaugh commented on Nov 11, 2020

    @RyanCavanaugh
    Member

    I'm curious what would happen if we did that

  2. tjjfvi commented on Nov 12, 2020

    @tjjfvi
    ContributorAuthor

    Ryan Cavanaugh (@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?

  3. tjjfvi commented on Nov 12, 2020

    @tjjfvi
    ContributorAuthor

    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> { }
  4. bfricka commented on Mar 24, 2021

    @bfricka

    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.

  5. tjjfvi commented on May 28, 2021

    @tjjfvi
    ContributorAuthor

    Brian Frichette (@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

Metadata

Metadata

Assignees

No one assigned

    Labels

    Experimentation NeededSomeone needs to try this out to see what happensSuggestionAn idea for TypeScript

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions