Skip to content

Commit

Permalink
fix: improve types further for even more accuracy and readability 🛠
Browse files Browse the repository at this point in the history
  • Loading branch information
mesqueeb committed Nov 14, 2022
1 parent 36669e1 commit 0b7f1bd
Show file tree
Hide file tree
Showing 3 changed files with 71 additions and 52 deletions.
5 changes: 4 additions & 1 deletion src/merge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,10 @@ import { concatArrays } from './extensions'
*
* This TS Utility can be used as standalone as well
*/
export type Merge<T extends object, Ts extends List<object>> = PrettyPrint<Assign<T, Ts>>
export type Merge<
T extends Record<string | number | symbol, unknown>,
Ts extends List<Record<string | number | symbol, unknown>>
> = PrettyPrint<Assign<T, Ts>>

function assignProp(
carry: Record<string | number | symbol, unknown>,
Expand Down
28 changes: 16 additions & 12 deletions src/typeUtils/Assign.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,14 +38,15 @@ type Cast<A1, A2> = A1 extends A2 ? A1 : A2
type Extends<A1, A2> = [A1] extends [never] ? 0 : A1 extends A2 ? 1 : 0

type __Assign<
O extends object,
Os extends List<object>,
O extends Record<string | number | symbol, unknown>,
Os extends List<Record<string | number | symbol, unknown>>,
I extends Iteration = IterationOf<0>
> = Extends<Pos<I>, Length<Os>> extends 1 ? O : __Assign<MergeDeep<O, Os[Pos<I>]>, Os, Next<I>>

type _Assign<O extends object, Os extends List<object>> = __Assign<O, Os> extends infer X
? Cast<X, object>
: never
type _Assign<
O extends Record<string | number | symbol, unknown>,
Os extends List<Record<string | number | symbol, unknown>>
> = __Assign<O, Os> extends infer X ? Cast<X, Record<string | number | symbol, unknown>> : never

/**
* Assign a list of [[Object]] into `O` with [[MergeDeep]]. Merges from right to
Expand All @@ -57,18 +58,21 @@ type _Assign<O extends object, Os extends List<object>> = __Assign<O, Os> extend
* ```ts
* ```
*/
export type Assign<O extends object, Os extends List<object>> = O extends unknown
? Os extends unknown
? _Assign<O, Os>
: never
: never
export type Assign<
O extends Record<string | number | symbol, unknown>,
Os extends List<Record<string | number | symbol, unknown>>
> = O extends unknown ? (Os extends unknown ? _Assign<O, Os> : never) : never

// type A1 = { arr: string[] }
// type A2 = { arr: number[] }
// type A3 = { arr: boolean[] }
// type Test = Assign<A1, [A2, A3]>
// type TestA = Assign<A1, [A2, A3]>

// type B1 = { arr: number[] }
// type B2 = { arr?: number[] }
// type TestB = Assign<B1, [B2]>

// import { Timestamp } from 'firebase/firestore'
// type T1 = { date: Timestamp }
// type T2 = { date: Timestamp }
// type Test1 = Assign<T1, [T2]>
// type TestT = Assign<T1, [T2]>
90 changes: 51 additions & 39 deletions src/typeUtils/MergeDeep.ts
Original file line number Diff line number Diff line change
@@ -1,48 +1,57 @@
import { PrettyPrint } from './PrettyPrint'

/**
* Make an object properties (all) `never`. We use this to intersect `object`s and
* preserve the combine modifiers like `+readonly` and `?optional`.
* Get the keys of `O` that are optional
* @param O
* @returns [[Key]]
* @example
* ```ts
* ```
*/
type Anyfy<O extends object> = {
[K in keyof O]: any
}
type OptionalKeys<O extends object> = O extends unknown
? {
// eslint-disable-next-line @typescript-eslint/ban-types
[K in keyof O]-?: {} extends Pick<O, K> ? K : never
}[keyof O]
: never

/**
* Get in `O` the type of a field of key `K`
* @param O to extract from
* @param K to extract at
* @returns [[Any]]
* Get the keys of `O` that are required
* @param O
* @returns [[Key]]
* @example
* ```ts
* type User = {
* info: { name: string; age: number; payment: {} }
* id: number
* }
*
* type test0 = At<User, 'id'> // number
* ```
*/
type At<A, K extends string | number | symbol> = unknown extends A
? unknown
: K extends keyof A
? A[K]
: undefined
type RequiredKeys<O extends object> = O extends unknown
? {
// eslint-disable-next-line @typescript-eslint/ban-types
[K in keyof O]-?: {} extends Pick<O, K> ? never : K
}[keyof O]
: never

type MergeObjectDeeply<
O extends Record<string | number | symbol, unknown>,
O1 extends Record<string | number | symbol, unknown>
> = {
[K in keyof (Anyfy<O> & O1)]: K extends keyof O1
? MergeObjectOrReturnValue<At<O, K>, At<O1, K>>
: O[K]
[K in keyof (O & O1)]: K extends RequiredKeys<O1> // second prop is non-optional
? O1[K] // return second prop
: K extends OptionalKeys<O1> // second prop is optional
? K extends OptionalKeys<O> // first prop is optional (second prop also)
? MergeObjectOrReturnUnion<Exclude<O[K], undefined>, Exclude<O1[K], undefined>> // return union
: K extends RequiredKeys<O> // first prop required (second prop optional)
? Exclude<O1[K], undefined> extends O[K] // (optional) second prop has the same type as the (required) first prop
? O[K] // return only the first one
: MergeObjectOrReturnUnion<O[K], Exclude<O1[K], undefined>> // (optional) second prop has a different type as the (required) first prop, so return union without `undefined` in the second
: O1[K] // first prop inexistent, so return second prop
: O[K] // second prop inexistent, so return first prop
}

type MergeObjectOrReturnValue<OK, O1K> = [O1K] extends [never]
? OK
: OK extends Record<string | number | symbol, unknown>
? O1K extends Record<string | number | symbol, unknown>
? MergeObjectDeeply<OK, O1K>
: O1K
: O1K
type MergeObjectOrReturnUnion<Val0, Val1> = Val0 extends Record<string | number | symbol, unknown>
? Val1 extends Record<string | number | symbol, unknown>
? MergeObjectDeeply<Val0, Val1>
: Val0 | Val1
: Val0 | Val1

/**
* Accurately merge the fields of `O` with the ones of `O1`. It is
Expand Down Expand Up @@ -82,11 +91,10 @@ type MergeObjectOrReturnValue<OK, O1K> = [O1K] extends [never]
* // }
* ```
*/
export type MergeDeep<O extends object, O1 extends object> = O extends unknown
? O1 extends unknown
? MergeObjectOrReturnValue<O, O1>
: never
: never
export type MergeDeep<
O extends Record<string | number | symbol, unknown>,
O1 extends Record<string | number | symbol, unknown>
> = O extends unknown ? (O1 extends unknown ? MergeObjectDeeply<O, O1> : never) : never

// type O = {
// name?: string
Expand Down Expand Up @@ -114,11 +122,15 @@ export type MergeDeep<O extends object, O1 extends object> = O extends unknown
// city: string;
// }

// type A1 = { arr: string[] }
// type A2 = { arr?: number[] }
// type Test = MergeDeep<A1, A2>
// type A1 = { arr: string[]; barr?: { b: number } }
// type A2 = { arr?: number[]; barr?: { b: number } }
// type TestA = PrettyPrint<MergeDeep<A1, A2>>

// type B1 = { a: number; b?: number; d?: number; e?: number; x: string; y?: number; z: string; } // prettier-ignore
// type B2 = { a?: number; c?: number; d?: number; e: number; x: number | undefined; y?: string; z?: number; } // prettier-ignore
// type TestB = PrettyPrint<MergeDeep<B1, B2>>

// import { Timestamp } from 'firebase/firestore'
// type T1 = { date: Timestamp }
// type T2 = { date: Timestamp }
// type Test1 = MergeDeep<T1, T2>
// type TestT = MergeDeep<T1, T2>

0 comments on commit 0b7f1bd

Please # to comment.