Skip to content

Commit bbf9137

Browse files
authored
WritableKeysOf / ReadonlyKeysOf: Fix behavior with unions and optional properties (#1088)
1 parent a6590b9 commit bbf9137

File tree

4 files changed

+33
-7
lines changed

4 files changed

+33
-7
lines changed

source/readonly-keys-of.d.ts

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
import type {IsEqual} from './is-equal';
1+
import type {KeysOfUnion} from './keys-of-union';
2+
import type {WritableKeysOf} from './writable-keys-of';
23

34
/**
45
Extract all readonly keys from the given type.
@@ -24,6 +25,4 @@ const update1: UpdateResponse<User> = {
2425
2526
@category Utilities
2627
*/
27-
export type ReadonlyKeysOf<T> = NonNullable<{
28-
[P in keyof T]: IsEqual<{[Q in P]: T[P]}, {readonly [Q in P]: T[P]}> extends true ? P : never
29-
}[keyof T]>;
28+
export type ReadonlyKeysOf<T> = Exclude<KeysOfUnion<T>, WritableKeysOf<T>>;

source/writable-keys-of.d.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import type {IsEqual} from './is-equal';
2+
import type {KeysOfUnion} from './keys-of-union';
23

34
/**
45
Extract all writable keys from the given type.
@@ -25,6 +26,6 @@ const update1: UpdateRequest<User> = {
2526
2627
@category Utilities
2728
*/
28-
export type WritableKeysOf<T> = NonNullable<{
29-
[P in keyof T]: IsEqual<{[Q in P]: T[P]}, {readonly [Q in P]: T[P]}> extends false ? P : never
30-
}[keyof T]>;
29+
export type WritableKeysOf<T> = KeysOfUnion<{
30+
[P in keyof T as IsEqual<{[Q in P]: T[P]}, {readonly [Q in P]: T[P]}> extends false ? P : never]: never
31+
}>;

test-d/readonly-keys-of.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,3 +27,16 @@ declare const test3: ReadonlyKeysOf3;
2727
expectType<'b'>(test1);
2828
expectType<'a' | 'b'>(test2);
2929
expectType<never>(test3);
30+
31+
expectType<'a' | 'c'>({} as ReadonlyKeysOf<{readonly a?: string; b: number; readonly c: boolean}>);
32+
expectType<'c'>({} as ReadonlyKeysOf<{a?: string; b: number; readonly c: boolean}>);
33+
34+
// Unions
35+
expectType<'b' | 'c'>({} as ReadonlyKeysOf<{a: string; readonly b: number} | {readonly c?: string; d?: number}>);
36+
37+
// TODO: Uncomment when targeting TypeScript 5.3 or later.
38+
// Arrays
39+
// expectType<never>({} as Extract<ReadonlyKeysOf<[string, number, boolean]>, number | `${number}`>);
40+
// expectType<number | '0' | '1' | '2'>({} as Extract<ReadonlyKeysOf<readonly [string, number, boolean]>, number | `${number}`>);
41+
// expectType<never>({} as Extract<ReadonlyKeysOf<string[]>, number | `${number}`>);
42+
// expectType<number>({} as Extract<ReadonlyKeysOf<readonly string[]>, number | `${number}`>);

test-d/writable-keys-of.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,3 +27,16 @@ declare const test3: WritableKeysOf3;
2727
expectType<'b'>(test1);
2828
expectType<'a' | 'b'>(test2);
2929
expectType<never>(test3);
30+
31+
expectType<'a' | 'c'>({} as WritableKeysOf<{a?: string; readonly b: number; c: boolean}>);
32+
expectType<'c'>({} as WritableKeysOf<{readonly a?: string; readonly b: number; c: boolean}>);
33+
34+
// Unions
35+
expectType<'b' | 'c'>({} as WritableKeysOf<{readonly a: string; b: number} | {c?: string; readonly d?: number}>);
36+
37+
// TODO: Uncomment when targeting TypeScript 5.3 or later.
38+
// Arrays
39+
// expectType<number | '0' | '1' | '2'>({} as Extract<WritableKeysOf<[string, number, boolean]>, number | `${number}`>);
40+
// expectType<never>({} as Extract<WritableKeysOf<readonly [string, number, boolean]>, number | `${number}`>);
41+
// expectType<number>({} as Extract<WritableKeysOf<string[]>, number | `${number}`>);
42+
// expectType<never>({} as Extract<WritableKeysOf<readonly string[]>, number | `${number}`>);

0 commit comments

Comments
 (0)