Skip to content

WritableKeysOf / ReadonlyKeysOf: Fix behaviour with unions and optional properties#1088

Merged
sindresorhus merged 7 commits intomainfrom
fix/writable-keys-of-with-optional-props
Mar 27, 2025
Merged

WritableKeysOf / ReadonlyKeysOf: Fix behaviour with unions and optional properties#1088
sindresorhus merged 7 commits intomainfrom
fix/writable-keys-of-with-optional-props

Conversation

@som-sm
Copy link
Copy Markdown
Collaborator

@som-sm som-sm commented Mar 27, 2025

Fixes #1087

Looks like there's some issue in TS v5.4 and later, because it works fine in v5.3 and below (playground link). Regardless, the updated implementation should work fine across all versions.

Also, fixes behaviour with unions.


The updated implementation also improves behaviour with arrays:

type CurrentBehaviour = WritableKeysOf<[string, number]>;
//   ^? type CurrentBehaviour = 2 | "0" | "1" | (() => ArrayIterator<"0" | "1">) | {
//          [x: number]: boolean | undefined;
//          length?: boolean | undefined;
//          toString?: boolean | undefined;
//          toLocaleString?: boolean | undefined;
//          ... 28 more ...;
//          readonly [Symbol.unscopables]?: boolean | undefined;
//      } | ... 28 more ... | ((searchElement: "0" | "1", fromIndex?: number) => boolean)

type UpdatedBehaviour = WritableKeysOf<[string, number]>;
//   ^? type T = number | typeof Symbol.iterator | "0" | "1" | "length" | "toString" | "toLocaleString" | "pop" | "push" | "concat" | "join" | "reverse" | "shift" | "slice" | "sort" | "splice" | ... 26 more ... | "with"

However, I had to comment out the array cases because looks like there's some issue with readonly arrays in v5.1 and v5.2.

In v5.1 and v5.2, the number key loses it's readonly modifier while mapping, causing WritableKeys<readonly [string]> to incorrectly include number in its result.

type Test<T> = {
	[P in keyof T as number extends P ? P : never]: {[Q in P]: T[P]}
};

type T1 = Test<readonly [string]>;
//   ^? type T1 = {
//          [x: number]: {
//              [x: number]: string;
//          };
//      }

In v5.3 and higher, the number key correctly preserves it's readonly modifier while mapping, so WritableKeys<readonly [string]> no longer has number in it's result.

type Test<T> = {
	[P in keyof T as number extends P ? P : never]: {[Q in P]: T[P]}
};

type T1 = Test<readonly [string]>;
//   ^? type T1 = {
//          readonly [x: number]: {
//              readonly [x: number]: string;
//          };
//      }

We'll probably have to figure out a way in which we can run certain test cases only for certain specific versions.

@som-sm som-sm requested a review from sindresorhus March 27, 2025 08:36
@som-sm som-sm marked this pull request as draft March 27, 2025 08:50
@som-sm som-sm force-pushed the fix/writable-keys-of-with-optional-props branch from d8ee0ec to 2443b48 Compare March 27, 2025 09:13
@som-sm som-sm force-pushed the fix/writable-keys-of-with-optional-props branch from 2443b48 to 0587ca9 Compare March 27, 2025 09:28
@som-sm som-sm changed the title WritableKeysOf: Fix behaviour with optional properties WritableKeysOf / ReadonlyKeysOf: Fix behaviour with unions and optional properties Mar 27, 2025
@som-sm som-sm marked this pull request as ready for review March 27, 2025 09:30
@som-sm
Copy link
Copy Markdown
Collaborator Author

som-sm commented Mar 27, 2025

We'll probably have to figure out a way in which we can run certain test cases only for certain specific versions.

I had to do a similar thing in ts-essentials as well.

@som-sm
Copy link
Copy Markdown
Collaborator Author

som-sm commented Mar 27, 2025

Also, @sindresorhus in v5, I feel we should add a constraint of object to T for WritableKeysOf and ReadonlyKeysOf, just like we do in OptionalKeysOf and RequiredKeysOf.

export type OptionalKeysOf<BaseType extends object> = Exclude<{

@sindresorhus
Copy link
Copy Markdown
Owner

I feel we should add a constraint of object to T for WritableKeysOf and ReadonlyKeysOf

Agreed. Added to the list.

@sindresorhus sindresorhus merged commit bbf9137 into main Mar 27, 2025
13 checks passed
@sindresorhus sindresorhus deleted the fix/writable-keys-of-with-optional-props branch March 27, 2025 10:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

WritableKeysOf return type has undefined

2 participants