Skip to content

The evaluation of the Keyof Type Operator produces different results to the Pick built-in tool.Β #48173

Description

@someBrown

Bug Report

πŸ”Ž Search Terms

Pick

πŸ•— Version & Regression Information

v4.5.4

⏯ Playground Link

Link

πŸ’» Code

interface User {
  name?: string
  age?: number
  address?: string
}

type Foo<T, U = keyof T> = {
  [P in Extract<U, keyof T>]: T[P]
}

type Bar<T,U=keyof T> = Required<Pick<T, Extract<U, keyof T>>>

type Res = Foo<User, 'name'>     //name: string | undefined;
type Res2 = Bar<User, 'name'>   //name: string
// We can quickly address your report if:
//  - The code sample is short. Nearly all TypeScript bugs can be demonstrated in 20-30 lines of code!
//  - It doesn't use external libraries. These are often issues with the type definitions rather than TypeScript bugs.
//  - The incorrectness of the behavior is readily apparent from reading the sample.
// Reports are slower to investigate if:
//  - We have to pare too much extraneous code.
//  - We have to clone a large repo and validate that the problem isn't elsewhere.
//  - The sample is confusing or doesn't clearly demonstrate what's wrong.

πŸ™ Actual behavior

type Res = Foo<User, 'name'>     //name: string | undefined;

The value evaluated for the name contains undefined

πŸ™‚ Expected behavior

type Foo<T, U = keyof T> = {
  [P in Extract<U, keyof T>]: T[P]
}

[P in Extract<U, keyof T>]: T[P] should not return undefined because Extract only narrows down the range of the keys

Activity

  1. jcalz commented on Mar 8, 2022

    @jcalz
    Contributor

    I don’t see a bug here. The type User["name"] is string | undefined, so that’s the type of the name property of Foo<User, "name">. Homomorphic mapped types and mapped type modifiers can complicate this, but neither of those are happening in Foo.

  2. RyanCavanaugh commented on Mar 8, 2022

    @RyanCavanaugh
    Member

    +1 to jcalz; this behaves exactly the same as a step-by-step manual evaluation of the type

  3. someBrown commented on Mar 8, 2022

    @someBrown
    Author

    new playground link

    type Foo<T, U = keyof T> = {
      [P in Extract<U, keyof T>]-?: T[P]
    }
    type Res = Foo<User, 'name'>     //name: string | undefined;
    

    Sorry, I forgot to add the '-?' operator in type Foo. I changed the name attribute to required, why does it still return undefined?

  4. fatcerberus commented on Mar 8, 2022

    @fatcerberus

    User["name"] evaluates to string | undefined because the property is optional. -? removes optionality in the new type, but this is separate from the evaluation of T[P], so you end up with name: string | undefined. You might consider NonNullable<T[P]>.

  5. ben-lau commented on Mar 9, 2022

    @ben-lau

    But there is another question: I think the ? symbol means this property is optional. So in this case, the name which is the keyof Users should be optional, but if you got this property , it has to be string not undefined as well.

  6. jcalz commented on Mar 9, 2022

    @jcalz
    Contributor

    If you have a value u of type User, then in const n = u.name, the type of n is string | undefined. That's just how it works. Whether or not the key is present vs absent and whether or not you can actually write undefined to u.name has to do with --exactOptionalPropertyTypes (#43947) but that doesn't change things here.

  7. ben-lau commented on Mar 9, 2022

    @ben-lau

    All right, I missed this, thanks.

  8. locked as resolved and limited conversation to collaborators on Oct 22, 2025
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

    QuestionAn issue which isn't directly actionable in code

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions