Skip to content

Commit

Permalink
fix(readonlydeep): don't incorrectly map types with multiple call sig…
Browse files Browse the repository at this point in the history
…natures

instead just leave them as be (mutable)
  • Loading branch information
RebeccaStevens committed Jan 31, 2022
1 parent 7e9ff22 commit d179825
Show file tree
Hide file tree
Showing 2 changed files with 31 additions and 5 deletions.
13 changes: 13 additions & 0 deletions source/readonly-deep.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ export type ReadonlyDeep<T> = T extends BuiltIns
: T extends (...arguments: any[]) => unknown
? {} extends ReadonlyObjectDeep<T>
? T
: HasMultipleCallSignatures<T> extends true
? T // Limitation - can't be made readonly
: ((...arguments: Parameters<T>) => ReturnType<T>) & ReadonlyObjectDeep<T>
: T extends Readonly<ReadonlyMap<infer KeyType, infer ValueType>>
? ReadonlyMapDeep<KeyType, ValueType>
Expand Down Expand Up @@ -66,3 +68,14 @@ Same as `ReadonlyDeep`, but accepts only `object`s as inputs. Internal helper fo
type ReadonlyObjectDeep<ObjectType extends object> = {
readonly [KeyType in keyof ObjectType]: ReadonlyDeep<ObjectType[KeyType]>
};

/**
Test if the given function has multiple call signatures. Internal helper for `ReadonlyDeep`.
Maybe wants to me exported as it's own until type.
*/
type HasMultipleCallSignatures<T extends (...arguments: any[]) => unknown> =
T extends {(...arguments: infer A): unknown; (...arguments: any[]): unknown}
? unknown[] extends A
? false
: true
: false;
23 changes: 18 additions & 5 deletions test-d/readonly-deep.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,20 +7,29 @@ type Overloaded = {
(foo: string, bar: number): number;
};

type Namespace = Overloaded & {
type Namespace = {
(foo: number): string;
baz: boolean[];
};

type NamespaceWithOverload = Overloaded & {
baz: boolean[];
};

const namespace = (() => 1) as unknown as Namespace;
namespace.baz = [true];

const namespaceWithOverload = (() => 1) as unknown as NamespaceWithOverload;
namespace.baz = [true];

const data = {
object: {
foo: 'bar',
},
fn: (_: string) => true,
fnWithOverload: ((_: number) => 'foo') as Overloaded,
namespace,
namespaceWithOverload,
string: 'foo',
number: 1,
boolean: false,
Expand Down Expand Up @@ -65,9 +74,13 @@ expectType<Readonly<ReadonlySet<string>>>(readonlyData.readonlySet);
expectType<readonly string[]>(readonlyData.readonlyArray);
expectType<readonly ['foo']>(readonlyData.readonlyTuple);

expectType<((foo: string, bar: number) => number) & ReadonlyObjectDeep<Namespace>>(readonlyData.namespace);
expectType<number>(readonlyData.namespace('foo', 1));
expectType<((foo: number) => string) & ReadonlyObjectDeep<Namespace>>(readonlyData.namespace);
expectType<string>(readonlyData.namespace(1));
expectType<readonly boolean[]>(readonlyData.namespace.baz);

// Currently on last call signature works.
// expectType<string>(readonlyData.namespace(1));
// These currently aren't readonly due to TypeScript limitations.
// @see https://github.com/microsoft/TypeScript/issues/29732
expectType<NamespaceWithOverload>(readonlyData.namespaceWithOverload);
expectType<string>(readonlyData.namespaceWithOverload(1));
expectType<number>(readonlyData.namespaceWithOverload('foo', 1));
expectType<boolean[]>(readonlyData.namespaceWithOverload.baz);

0 comments on commit d179825

Please sign in to comment.