diff --git a/source/readonly-deep.d.ts b/source/readonly-deep.d.ts index f4ad9e3ff..9fa397caa 100644 --- a/source/readonly-deep.d.ts +++ b/source/readonly-deep.d.ts @@ -34,8 +34,14 @@ data.foo.push('bar'); @category Set @category Map */ -export type ReadonlyDeep = T extends BuiltIns | ((...arguments: any[]) => unknown) +export type ReadonlyDeep = T extends BuiltIns ? T + : T extends (...arguments: any[]) => unknown + ? {} extends ReadonlyObjectDeep + ? T + : HasMultipleCallSignatures extends true + ? T + : ((...arguments: Parameters) => ReturnType) & ReadonlyObjectDeep : T extends Readonly> ? ReadonlyMapDeep : T extends Readonly> @@ -62,3 +68,18 @@ Same as `ReadonlyDeep`, but accepts only `object`s as inputs. Internal helper fo type ReadonlyObjectDeep = { readonly [KeyType in keyof ObjectType]: ReadonlyDeep }; + +/** +Test if the given function has multiple call signatures. + +Needed to handle the case of a single call signature with properties. + +Multiple call signatures cannot currently be supported due to a TypeScript limitation. +@see https://github.com/microsoft/TypeScript/issues/29732 +*/ +type HasMultipleCallSignatures unknown> = + T extends {(...arguments: infer A): unknown; (...arguments: any[]): unknown} + ? unknown[] extends A + ? false + : true + : false; diff --git a/test-d/readonly-deep.ts b/test-d/readonly-deep.ts index 5dcb2850c..d2c45ae9d 100644 --- a/test-d/readonly-deep.ts +++ b/test-d/readonly-deep.ts @@ -1,11 +1,29 @@ import {expectType, expectError} from 'tsd'; import {ReadonlyDeep} from '../index'; +import {ReadonlyObjectDeep} from '../source/readonly-deep'; + +type Overloaded = { + (foo: number): string; + (foo: string, bar: number): number; +}; + +type Namespace = { + (foo: number): string; + baz: boolean[]; +}; + +type NamespaceWithOverload = Overloaded & { + baz: boolean[]; +}; const data = { object: { foo: 'bar', }, fn: (_: string) => true, + fnWithOverload: ((_: number) => 'foo') as Overloaded, + namespace: {} as unknown as Namespace, + namespaceWithOverload: {} as unknown as NamespaceWithOverload, string: 'foo', number: 1, boolean: false, @@ -28,6 +46,9 @@ const readonlyData: ReadonlyDeep = data; readonlyData.fn('foo'); +readonlyData.fnWithOverload(1); +readonlyData.fnWithOverload('', 1); + expectError(readonlyData.string = 'bar'); expectType<{readonly foo: string}>(readonlyData.object); expectType(readonlyData.string); @@ -46,3 +67,14 @@ expectType>>(readonlyData.readonlyMap); expectType>>(readonlyData.readonlySet); expectType(readonlyData.readonlyArray); expectType(readonlyData.readonlyTuple); + +expectType<((foo: number) => string) & ReadonlyObjectDeep>(readonlyData.namespace); +expectType(readonlyData.namespace(1)); +expectType(readonlyData.namespace.baz); + +// These currently aren't readonly due to TypeScript limitations. +// @see https://github.com/microsoft/TypeScript/issues/29732 +expectType(readonlyData.namespaceWithOverload); +expectType(readonlyData.namespaceWithOverload(1)); +expectType(readonlyData.namespaceWithOverload('foo', 1)); +expectType(readonlyData.namespaceWithOverload.baz);