Skip to content

Commit 9baccc3

Browse files
authored
WritableDeep: Fix array handling (#724)
1 parent 6c76e4a commit 9baccc3

2 files changed

Lines changed: 61 additions & 15 deletions

File tree

source/writable-deep.d.ts

Lines changed: 29 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -38,27 +38,47 @@ export type WritableDeep<T> = T extends BuiltIns
3838
: HasMultipleCallSignatures<T> extends true
3939
? T
4040
: ((...arguments_: Parameters<T>) => ReturnType<T>) & WritableObjectDeep<T>
41-
: T extends Readonly<ReadonlyMap<infer KeyType, infer ValueType>>
42-
? WritableMapDeep<KeyType, ValueType>
43-
: T extends Readonly<ReadonlySet<infer ItemType>>
44-
? WritableSetDeep<ItemType>
45-
: T extends object
46-
? WritableObjectDeep<T>
47-
: unknown;
41+
: T extends ReadonlyMap<unknown, unknown>
42+
? WritableMapDeep<T>
43+
: T extends ReadonlySet<unknown>
44+
? WritableSetDeep<T>
45+
: T extends readonly unknown[]
46+
? WritableArrayDeep<T>
47+
: T extends object
48+
? WritableObjectDeep<T>
49+
: unknown;
4850

4951
/**
5052
Same as `WritableDeep`, but accepts only `Map`s as inputs. Internal helper for `WritableDeep`.
5153
*/
52-
type WritableMapDeep<KeyType, ValueType> = {} & Writable<Map<WritableDeep<KeyType>, WritableDeep<ValueType>>>;
54+
type WritableMapDeep<MapType extends ReadonlyMap<unknown, unknown>> =
55+
MapType extends ReadonlyMap<infer KeyType, infer ValueType>
56+
? Map<WritableDeep<KeyType>, WritableDeep<ValueType>>
57+
: MapType; // Should not heppen
5358

5459
/**
5560
Same as `WritableDeep`, but accepts only `Set`s as inputs. Internal helper for `WritableDeep`.
5661
*/
57-
type WritableSetDeep<ItemType> = {} & Writable<Set<WritableDeep<ItemType>>>;
62+
type WritableSetDeep<SetType extends ReadonlySet<unknown>> =
63+
SetType extends ReadonlySet<infer ItemType>
64+
? Set<WritableDeep<ItemType>>
65+
: SetType; // Should not heppen
5866

5967
/**
6068
Same as `WritableDeep`, but accepts only `object`s as inputs. Internal helper for `WritableDeep`.
6169
*/
6270
type WritableObjectDeep<ObjectType extends object> = {
6371
-readonly [KeyType in keyof ObjectType]: WritableDeep<ObjectType[KeyType]>
6472
};
73+
74+
/**
75+
Same as `WritableDeep`, but accepts only `Array`s as inputs. Internal helper for `WritableDeep`.
76+
*/
77+
type WritableArrayDeep<ArrayType extends readonly unknown[]> =
78+
ArrayType extends readonly [] ? []
79+
: ArrayType extends readonly [...infer U, infer V] ? [...WritableArrayDeep<U>, WritableDeep<V>]
80+
: ArrayType extends readonly [infer U, ...infer V] ? [WritableDeep<U>, ...WritableArrayDeep<V>]
81+
: ArrayType extends ReadonlyArray<infer U> ? Array<WritableDeep<U>>
82+
: ArrayType extends Array<infer U> ? Array<WritableDeep<U>>
83+
: ArrayType;
84+

test-d/writable-deep.ts

Lines changed: 32 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import {expectType, expectError, expectAssignable} from 'tsd';
2-
import type {ReadonlyDeep, Writable, WritableDeep} from '../index';
2+
import type {JsonValue, Opaque, ReadonlyDeep, WritableDeep} from '../index';
33
import type {WritableObjectDeep} from '../source/writable-deep';
4+
import {type tag} from '../source/opaque';
45

56
type Overloaded = {
67
(foo: number): string;
@@ -16,6 +17,17 @@ type NamespaceWithOverload = Overloaded & {
1617
readonly baz: readonly boolean[];
1718
};
1819

20+
type OpaqueObjectData = {readonly a: number[]} | {readonly b: string};
21+
type OpaqueObject = Opaque<OpaqueObjectData, {readonly token: unknown}>;
22+
23+
type ReadonlyJsonValue =
24+
| {readonly [k: string]: ReadonlyJsonValue}
25+
| readonly ReadonlyJsonValue[]
26+
| number
27+
| string
28+
| boolean
29+
| null;
30+
1931
const data = {
2032
object: {
2133
foo: 'bar',
@@ -35,14 +47,21 @@ const data = {
3547
map: new Map<string, string>(),
3648
set: new Set<string>(),
3749
array: ['foo'],
50+
emptyTuple: [] as [],
3851
tuple: ['foo'] as ['foo'],
52+
multiItemTuple: [{a: ''}, {b: 1}] as [{a: string}, {b: number}],
53+
spreadTuple: ['foo'] as [...string[]],
54+
trailingSpreadTuple: ['foo', 1] as [string, ...number[]],
55+
leadingSpreadTuple: ['foo', 1] as [...string[], number],
3956
readonlyMap: new Map<string, string>() as ReadonlyMap<string, string>,
4057
readonlySet: new Set<string>() as ReadonlySet<string>,
4158
readonlyArray: ['foo'] as readonly string[],
4259
readonlyTuple: ['foo'] as const,
60+
json: [{x: true}] as JsonValue,
61+
opaqueObj: {a: [3]} as OpaqueObject, // eslint-disable-line @typescript-eslint/consistent-type-assertions
4362
};
4463

45-
const readonlyData: Readonly<typeof data> = data;
64+
const readonlyData: ReadonlyDeep<typeof data> = data;
4665

4766
let writableData: WritableDeep<typeof readonlyData>;
4867
expectError(writableData = readonlyData);
@@ -63,14 +82,21 @@ expectType<null>(writableData.null);
6382
expectType<undefined>(writableData.undefined);
6483
expectType<Date>(writableData.date);
6584
expectType<RegExp>(writableData.regExp);
66-
expectType<Writable<Map<string, string>>>(writableData.map);
67-
expectType<Writable<Set<string>>>(writableData.set);
85+
expectType<Map<string, string>>(writableData.map);
86+
expectType<Set<string>>(writableData.set);
6887
expectType<string[]>(writableData.array);
88+
expectType<[]>(writableData.emptyTuple);
6989
expectType<['foo']>(writableData.tuple);
70-
expectType<Writable<Map<string, string>>>(writableData.readonlyMap);
71-
expectType<Writable<Set<string>>>(writableData.readonlySet);
90+
expectType<[{a: string}, {b: number}]>(writableData.multiItemTuple);
91+
expectType<[...string[]]>(writableData.spreadTuple);
92+
expectType<[string, ...number[]]>(writableData.trailingSpreadTuple);
93+
expectType<[...string[], number]>(writableData.leadingSpreadTuple);
94+
expectType<Map<string, string>>(writableData.readonlyMap);
95+
expectType<Set<string>>(writableData.readonlySet);
7296
expectType<string[]>(writableData.readonlyArray);
7397
expectType<['foo']>(writableData.readonlyTuple);
98+
expectAssignable<ReadonlyJsonValue>(writableData.json);
99+
expectAssignable<Opaque<WritableDeep<OpaqueObjectData>, WritableDeep<OpaqueObject[typeof tag]>>>(writableData.opaqueObj);
74100

75101
expectType<((foo: number) => string) & WritableObjectDeep<Namespace>>(writableData.namespace);
76102
expectType<string>(writableData.namespace(1));

0 commit comments

Comments
 (0)