diff --git a/src/types/__tests__/indices.spec-d.ts b/src/types/__tests__/indices.spec-d.ts index 6a836818..349fce9e 100644 --- a/src/types/__tests__/indices.spec-d.ts +++ b/src/types/__tests__/indices.spec-d.ts @@ -25,14 +25,14 @@ describe('unit-d:types/Indices', () => { : never : never - it('should equal number if T is any', () => { - expectTypeOf>().toBeNumber() - }) - it('should equal never if T is never', () => { expectTypeOf>().toBeNever() }) + it('should equal number if T is any', () => { + expectTypeOf>().toBeNumber() + }) + describe('T extends string', () => { it('should equal never if T extends EmptyString', () => { expectTypeOf>().toBeNever() @@ -64,7 +64,7 @@ describe('unit-d:types/Indices', () => { describe('T extends readonly [T[0], ...T[number][]]', () => { it('should construct union of negative and positive indices', () => { // Arrange - type T = ['a', 'b'?] + type T = readonly ['a', 'b'?] // Expect expectTypeOf>().toEqualTypeOf>() @@ -73,7 +73,7 @@ describe('unit-d:types/Indices', () => { describe('number extends Length', () => { it('should equal number', () => { - expectTypeOf>().toEqualTypeOf() + expectTypeOf>().toEqualTypeOf() }) }) }) diff --git a/src/types/__tests__/spread.spec-d.ts b/src/types/__tests__/spread.spec-d.ts new file mode 100644 index 00000000..1662261f --- /dev/null +++ b/src/types/__tests__/spread.spec-d.ts @@ -0,0 +1,201 @@ +/** + * @file Type Tests - Spread + * @module tutils/types/tests/unit-d/Spread + */ + +import type Vehicle from '#fixtures/types/vehicle' +import type At from '../at' +import type EmptyObject from '../empty-object' +import type Fn from '../fn' +import type Nilable from '../nilable' +import type Nullable from '../nullable' +import type Objectify from '../objectify' +import type Optional from '../optional' +import type TestSubject from '../spread' +import type Writable from '../writable' + +describe('unit-d:types/Spread', () => { + it('should equal Objectify if T is any', () => { + // Arrange + type T = any + + // Expect + expectTypeOf>().toEqualTypeOf>() + }) + + it('should equal Objectify if T is never', () => { + // Arrange + type T = never + + // Expect + expectTypeOf>().toEqualTypeOf>() + }) + + it('should equal Objectify if T is unknown', () => { + // Arrange + type T = unknown + + // Expect + expectTypeOf>().toEqualTypeOf>() + }) + + describe('T extends ObjectCurly', () => { + describe('EmptyObject extends T', () => { + it('should equal {}', () => { + expectTypeOf>().toEqualTypeOf<{}>() + expectTypeOf>().toEqualTypeOf<{}>() + }) + }) + + describe('Simplify extends ObjectPlain', () => { + it('should equal Writable', () => { + // Arrange + type T = Readonly + + // Expect + expectTypeOf>().toEqualTypeOf>() + }) + }) + + describe('T extends Readonly', () => { + it('should enumerate properties of T', () => { + // Arrange + type T = Map & { readonly id: string } + + // Expect + expectTypeOf>().toEqualTypeOf<{ id: string }>() + }) + }) + }) + + describe('T extends Primitive', () => { + describe('T extends NIL', () => { + it('should equal Objectify', () => { + // Arrange + type T1 = null + type T2 = undefined + + // Expect + expectTypeOf>().toEqualTypeOf>() + expectTypeOf>().toEqualTypeOf>() + }) + }) + + describe('T extends bigint', () => { + it('should spread T into object', () => { + // Arrange + type T = bigint & { readonly id: string } + + // Expect + expectTypeOf>().toEqualTypeOf<{ id: string }>() + }) + }) + + describe('T extends boolean', () => { + it('should spread T into object', () => { + // Arrange + type T = false & { readonly id: string } + + // Expect + expectTypeOf>().toEqualTypeOf<{ id: string }>() + }) + }) + + describe('T extends number', () => { + it('should spread T into object', () => { + // Arrange + type T = number & { readonly id: string } + + // Expect + expectTypeOf>().toEqualTypeOf<{ id: string }>() + }) + }) + + describe('T extends string', () => { + describe('IsLiteral extends true', () => { + it('should spread T into object', () => { + // Arrange + type T = 'vin' + + // Expect + expectTypeOf>().toEqualTypeOf<{ + [x: number]: At + '0': At + '1': At + '2': At + }>() + }) + }) + + describe('number extends Length', () => { + it('should spread T into object', () => { + // Arrange + type T = Vehicle['vin'] & { readonly id: string } + type Expect = { [x: number]: Optional; id: string } + + // Expect + expectTypeOf>().toEqualTypeOf() + }) + }) + }) + + describe('T extends symbol', () => { + it('should spread T into object', () => { + // Arrange + type T = symbol & { readonly id: string } + + // Expect + expectTypeOf>().toEqualTypeOf<{ id: string }>() + }) + }) + }) + + describe('T extends Readonly', () => { + it('should spread T into object', () => { + // Arrange + type T = Readonly + + // Expect + expectTypeOf>().toEqualTypeOf<{ id: string }>() + }) + }) + + describe('T extends readonly unknown[]', () => { + describe('IsTuple extends true', () => { + it('should spread T into object', () => { + // Arrange + type T = readonly [Vehicle, Nilable, Nullable?] + + // Expect + expectTypeOf>().toEqualTypeOf<{ + [x: number]: At + '0': At + '1': At + '2'?: At + }>() + }) + }) + + describe('number extends Length', () => { + it('should spread T into object', () => { + // Arrange + type T = readonly Vehicle[] & { readonly id: string } + type Expect = { [x: number]: Optional; id: string } + + // Expect + expectTypeOf>().toEqualTypeOf() + }) + }) + }) + + describe('unions', () => { + it('should distribute over unions', () => { + // Arrange + type T = Nullable<['vin']> + type Expect = { [x: number]: 'vin'; '0': 'vin' } | {} + + // Expect + expectTypeOf>().toEqualTypeOf() + }) + }) +}) diff --git a/src/types/index.ts b/src/types/index.ts index c75637e4..605d24c4 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -165,6 +165,7 @@ export type { default as Shake } from './shake' export type { default as Sift } from './sift' export type { default as Simplify } from './simplify' export type { default as Split } from './split' +export type { default as Spread } from './spread' export type { default as Stringafiable } from './stringafiable' export type { default as Stringify } from './stringify' export type { default as Tail } from './tail' diff --git a/src/types/indices.ts b/src/types/indices.ts index 385d1888..81abfe2c 100644 --- a/src/types/indices.ts +++ b/src/types/indices.ts @@ -42,22 +42,22 @@ import type UnwrapNumeric from './unwrap-numeric' * * @template T - Type to evaluate */ -type Indices = ( - T extends EmptyString | Readonly - ? never - : Length extends infer L extends number - ? number extends L - ? L - : NaturalRange extends infer R extends number - ? Length> extends infer L extends number - ? { - [K in R]: K | UnwrapNumeric}`, '-0'>> - }[R] - : never +type Indices = T extends + | EmptyString + | Readonly + ? never + : Length extends infer L extends number + ? number extends L + ? L + : NaturalRange extends infer R extends number + ? { + [K in R]: + | K + | UnwrapNumeric>, K>}`, '-0'>> + }[R] extends infer I extends number + ? I : never : never -) extends infer I extends number - ? I : never export type { Indices as default } diff --git a/src/types/length.ts b/src/types/length.ts index c84bac34..4cbadb32 100644 --- a/src/types/length.ts +++ b/src/types/length.ts @@ -40,7 +40,7 @@ import type Split from './split' * * @template T - Type to evaluate */ -type Length = +type Length = T['length'] extends infer L extends number ? IfAny['length'] : L> : never diff --git a/src/types/spread.ts b/src/types/spread.ts new file mode 100644 index 00000000..f90d5c4e --- /dev/null +++ b/src/types/spread.ts @@ -0,0 +1,58 @@ +/** + * @file Type Definitions - Spread + * @module tutils/types/Spread + */ + +import type At from './at' +import type BuiltIn from './built-in' +import type IfIndexSignature from './if-index-signature' +import type IfNegativeInteger from './if-negative' +import type IfNumber from './if-number' +import type Indices from './indices' +import type Intersection from './intersection' +import type IsAnyOrNever from './is-any-or-never' +import type Keyof from './keyof' +import type Objectify from './objectify' +import type Stringify from './stringify' +import type Writable from './writable' + +/** + * Construct a type representing the result of [spreading][1] `T`. + * + * [1]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Operators/Spread_syntax + * + * @todo examples + * + * @template T - Type to evaluate + */ +type Spread = IsAnyOrNever extends true + ? Objectify + : T extends unknown + ? Objectify & + (T extends string + ? { [N in Indices as IfNegativeInteger]: At } + : Objectify) extends infer U + ? Writable<{ + [H in keyof U as IfIndexSignature< + U, + H, + H, + IfNumber< + H, + Stringify, + T extends Readonly + ? H extends Intersection> + ? never + : H + : H + > + >]: T extends string | readonly unknown[] + ? number extends H + ? At + : U[H] + : U[H] + }> + : never + : never + +export type { Spread as default }