Skip to content

Inconsistencies within types for ObjectConstructor methods, appeal to revisit the Object.keys discussion #45835

Closed
@dpchamps

Description

@dpchamps

lib Update Request

@jcalz has enumerated a non-exhaustive list here, the issue template links to the SO Answer. I'm aware of these resources, I know that this comes up quite frequently, but it would be greatly appreciated if you would hear me out 🙏

It's instructive to go way back to 2016 with this comment in a follow-up PR to #12207 -- which will be relevant in a few seconds.

Once you move to the instantiated type world it degenerates because an object can (and often does) have more properties at run-time than are statically known at compile time.

@RyanCavanaugh sort of codifies these hesitations in the SO answer. The crux of this argument is that a more-precisely typed Object.keys is potentially unsound. We get a first hand demonstration of this by observing some well-typed program that would produce runtime errors.

But TS is already in this state where very similar code, is A) well-typed, but B) will produce runtime errors. This comes down to how Object.values and Object.entries are typed.

See two examples here, using Object.values:

Note: this was known way back in 2016, where @ahejlsberg called this out in the same comment:

BTW, I have the same reservations about Object.entries which I notice is now typed this way.


Within a structural type-system and in the absence of Exact Types, it would appear that most Object reflection methods must be typed imprecisely wide, including Object.entries and Object.values.

But this kind of feels like it violates Typescripts third non-goal: Precise types for object methods are incredibly useful, which is clear by the number of requests, questions and PRs around Object.keys type signature.

Given the state of things it feels like one of two things should happen:

  1. Remove these two type signatures, and revisit when Exact Types land.
  2. Add a more precise Object.keys type definition override, and maintain at least the same level of unsoundness that already exists.

Sample Code

Something like this maintains parity with the status-quo:

interface ObjectConstructor {
    keys<T extends Record<string, unknown>>(o: T): (keyof T)[];
}

Which is not a valid program in the SO example, but demonstrates the same level of unsoundness that already exists

Thanks for listening, appreciate y'all 😄 . I realize the team is full of experts and this is likely to have been discussed. Happy to close if thats the case, but am interested to read more about it!

Metadata

Metadata

Assignees

No one assigned

    Labels

    DuplicateAn existing issue was already created

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions