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 typed overloads to Object.create #39882

Open
4 of 5 tasks
ExE-Boss opened this issue Aug 3, 2020 · 3 comments
Open
4 of 5 tasks

Add typed overloads to Object.create #39882

ExE-Boss opened this issue Aug 3, 2020 · 3 comments
Labels
Awaiting More Feedback This means we'd like to hear from more people who would be helped by this feature Suggestion An idea for TypeScript

Comments

@ExE-Boss
Copy link
Contributor

ExE-Boss commented Aug 3, 2020

Search Terms

  • object.create
  • object create
  • typed object.create
  • typed object create

Suggestion

Change the signatures of Object.create to:

interface ObjectConstructor {
	create<P extends object | null>(proto: P): null extends P ? {} : P;

	create<
		P extends object | null,
		M extends PropertyDescriptorMap = {}
	>(proto: P, properties?: M & ThisType<any>): {
		readonly [
			K in keyof M as M[K] extends { readonly writable: false }
				? K
				: never
		]: M[K] extends Readonly<TypedPropertyDescriptor<infer V>>
			? V
			: any;
	} & {
		-readonly [
			K in keyof M as M[K] extends { readonly writable: true }
				? K
				: never
		]: M[K] extends Readonly<TypedPropertyDescriptor<infer V>>
			? V
			: any;
	} & (null extends P ? {} : P);
}

Use Cases

Getting correct types in code that does:

// `foo` is `any`, because `T` of Object.assign<T, U> is `any`:
const foo = Object.assign(
	// `T` is currently typed as `any`:
	Object.create(null),

	// Only `U` is typed correctly as { bar: number; baz: string }:
	{
		bar: 123,
		baz: 'biz',
	},
);

Examples

Checklist

My suggestion meets these guidelines:

  • This wouldn't be a breaking change in existing TypeScript/JavaScript code
    • This might need a flag similar to strictBindCallApply, or just done in a separate lib file.
  • 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.

Relevant issues

@DanKaplanSES
Copy link

DanKaplanSES commented Sep 20, 2023

#3865 predates this one but was closed. I'm not sure why it was closed, but it might provide useful information.

@RyanCavanaugh replied:

Mostly we see Object.create being used in code that doesn't really benefit from typing, or with Object.create(null) to create a map object, which is going to require a type assertion anyway.

At present I'd like to create many objects (via literals) of type X, but given that many of the properties are shared, using Object.create makes a lot of sense

Can you post some examples of what this looks like? I can't think of any code that it would meaningfully benefit from a different declaration of Object.create in the scenario you describe.

I'd like to contribute my own use case (Stack Overflow link):

"I'm working on some legacy code I didn't write, trying to refactor it to typescript. ... writing the type definition by hand ... is quite painstaking, especially when the Object.create [property] values ... are complex"

@DanKaplanSES
Copy link

DanKaplanSES commented Sep 20, 2023

@lionel-rowe
Copy link
Contributor

lionel-rowe commented Apr 26, 2024

Surely this kind of use case is pretty common?

declare const arr: {
    keyLike: string,
    prop1: string,
    prop2: number,
}[]

declare const arbitraryString: string

const lookupObj = Object.assign(
    Object.create(null),
    Object.fromEntries(arr.map(({ keyLike, ...rest }) => [keyLike, rest])),
)

const val = lookupObj[arbitraryString]
// expected type: `{ prop1: string, prop2: number } | undefined`
// actual type: `any`

I guess typing Object.create(null) as {} still wouldn't fix the undefined issue, but that's a separate issue with TS's typing of Object.fromEntries.

Notably, simply defining lookupObj as Object.fromEntries(arr.map(({ keyLike, ...rest }) => [keyLike, rest])) gives val the type { prop1: string, prop2: number }, which is even less correct if arbitraryString happens to be "constructor" or something.

Edit: I know generally Maps are better for this kind of use case, but sometimes a POJO is more convenient, e.g. easy JSON serialization or adding properties with nullish coalescing assignment ??=.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Awaiting More Feedback This means we'd like to hear from more people who would be helped by this feature Suggestion An idea for TypeScript
Projects
None yet
Development

No branches or pull requests

4 participants