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

Add an overload to Object.fromEntries when called with const tuples #50379

Closed
niieani opened this issue Aug 20, 2022 · 7 comments
Closed

Add an overload to Object.fromEntries when called with const tuples #50379

niieani opened this issue Aug 20, 2022 · 7 comments
Labels
Suggestion An idea for TypeScript Too Complex An issue which adding support for may be too complex for the value it adds

Comments

@niieani
Copy link

niieani commented Aug 20, 2022

lib Update Request

Object.fromEntries as is declared today results in a loss of key names and a unionization of all values, or a complete loss of information and type of any.
This change would make it so that it is possible to create strict objects — from const tuples only.

Note that the usage of fromEntries with Arrays and non-readonly tuples cannot be made sound, so this issue only discusses the narrow case of const tuples.

Sample Code

const obj1 = Object.fromEntries([
  ['1', 2],
  ['3', 4],
] as const)
// this should result in an object of type: {1: 2, 3: 4}, but results instead in: { [k: string]: 2 | 4 }

const obj2 = Object.fromEntries([
  ['1', 2],
  ['3', '4'],
] as const)

// should result in an object of type: {1: 2, 3: '4'}, but results in: any

The how & example

This could be achieved by adding a new overload to Object.fromEntries in the case it is called with const tuples only.
This distinction is vital, because not distinguishing between Arrays, tuples and const tuples would make this change unsound. Only properties of a readonly tuple are guaranteed to be there at runtime, since they cannot be reassigned.

Here's a working proof-of-concept playground that should be sound in all cases, including the edge case of using a union type for the key.

The type is somewhat complex right now, so it would be great if we could somehow simplify it. Suggestions are welcome.

Related issues were previously closed, because the change would have been unsound without guarding for const tuples only (#35745, #49305, #43332). Here however, the discussion is around const tuples only.

Configuration Check

My compilation target is es2022 and my lib is es2022.

Missing / Incorrect Definition

  • Object.fromEntries

Note

Opened by request of @sandersn in #50203.

@webstrand
Copy link
Contributor

Another failing test case

const x = fromEntries([] as [["foo", 1]] | Iterable<["bar", 2]>);

@webstrand
Copy link
Contributor

webstrand commented Aug 24, 2022

I really would rather avoid a conditional return type, at least not as the last overload. By supporting a reduced, but still improved, set of features I think this is better:

// Most generally useful
declare function fromEntries<T extends readonly [PropertyKey, unknown]>(
  entries: Iterable<T>
): { [P in T as P[0]]?: P[1] }

// Support wonky union types like `[["foo", 1]] | Iterable<["bar", 2]>`
declare function fromEntries<K extends PropertyKey, V>(
  entries: Iterable<readonly [K, V]>
): { [_ in K]?: V }

// For people destructuring `fromEntries`
declare function fromEntries(entries: Iterable<readonly [PropertyKey, unknown]>): { [key: PropertyKey]: unknown }

I just noticed that this breaks the obj2_fromMixed case

@RyanCavanaugh RyanCavanaugh added Suggestion An idea for TypeScript Too Complex An issue which adding support for may be too complex for the value it adds labels Aug 25, 2022
@RyanCavanaugh
Copy link
Member

This is extremely clever, but far too complex to put into the standard library. Since this can merge in at the top of the overload list, you can still get the same behavior in userland with a custom additional lib file.

@niieani
Copy link
Author

niieani commented Aug 26, 2022

@RyanCavanaugh thanks for taking a look. I can make an NPM library for that, sounds like the way to go, unfortunately.

It would be possible to make this much simpler, if we had these two things built-in in TypeScript:

  • When doing a mapped type, ability to conditionally set optionality.
    Right now, one can set the value using the X extends Y ? ... : ... pattern, but one cannot do it to add in or remove ? conditionally.
    Perhaps a special type like +? or -? to add or remove optionality in mapped types, and similarly to add or remove readonly property +readonly could be added? E.g. { [K in keyof O]: O[K] extends X ? X & +? : X & -? }
  • Ability to refine based on negation. E.g. only const tuples: T extends readonly [...unknown] & not [...unknown]

@webstrand the failing test case you mentioned is failing for a good reason - there's no way to create a predictable object from that type, so it should be handled by another overload and fallback to a less strictly typed type. And as you notice, your simplified version unfortunately isn't sound. Believe me I've spent hours trying to figure it out.

@alexweej
Copy link

Yes, please!

@niieani
Copy link
Author

niieani commented Feb 8, 2023

I've finally gotten around to publishing an NPM package with the strict Object.fromEntries typings.
See nesity-types.

@sandersn
Copy link
Member

sandersn commented Feb 8, 2023

Would it be OK to close this PR now?

@niieani niieani closed this as not planned Won't fix, can't repro, duplicate, stale Feb 14, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Suggestion An idea for TypeScript Too Complex An issue which adding support for may be too complex for the value it adds
Projects
None yet
Development

No branches or pull requests

5 participants