diff --git a/source/internal/type.d.ts b/source/internal/type.d.ts index c1f53a2b9..7038aa766 100644 --- a/source/internal/type.d.ts +++ b/source/internal/type.d.ts @@ -2,6 +2,8 @@ import type {If} from '../if.d.ts'; import type {IsAny} from '../is-any.d.ts'; import type {IsNever} from '../is-never.d.ts'; import type {Primitive} from '../primitive.d.ts'; +import type {IsEqual} from '../is-equal.d.ts'; +import type {UnionToIntersection} from '../union-to-intersection.d.ts'; import type {UnknownArray} from '../unknown-array.d.ts'; /** @@ -161,4 +163,50 @@ export type IsExactOptionalPropertyTypesEnabled = [(string | undefined)?] extend ? false : true; +/** +Returns the last element of a union type; otherwise `never` if `never` passed. +Note that this is non-deterministic because the order of union type is not guaranteed. + +@see https://github.com/microsoft/TypeScript/issues/13298#issuecomment-468375328 + +This can be used to implement a recursive type function that accepts a union type. +It can detect a termination case using {@link IsNever `IsNever`}. + +@example +``` +type RecursionType = + LastOfUnion extends infer L + ? IsNever extends false + ? RecursionType, [...R, L]> + : R + : never; + +type RecursionTest = RecursionType; +//=> [string, number] +``` + +@example +``` +export type UnionToTuple> = + IsNever extends false + ? [...UnionToTuple>, L] + : []; +``` + +@example +``` +type Last = LastOfUnion<1 | 2 | 3>; +//=> 3 + +type LastNever = LastOfUnion; +//=> never +``` +*/ +export type LastOfUnion = + IsEqual extends true + ? never + : UnionToIntersection T : never> extends () => (infer R) + ? R + : never; + export {}; diff --git a/source/pick-deep.d.ts b/source/pick-deep.d.ts index 4c8e0d60c..789b2c693 100644 --- a/source/pick-deep.d.ts +++ b/source/pick-deep.d.ts @@ -1,10 +1,169 @@ import type {TupleOf} from './tuple-of.d.ts'; -import type {BuildObject, NonRecursiveType, ObjectValue} from './internal/index.d.ts'; -import type {IsNever} from './is-never.d.ts'; +import type {BuildObject, LastOfUnion, StringToNumber} from './internal/index.d.ts'; import type {Paths} from './paths.d.ts'; import type {Simplify} from './simplify.d.ts'; -import type {UnionToIntersection} from './union-to-intersection.d.ts'; +import type {GreaterThan} from './greater-than.d.ts'; +import type {IsEqual} from './is-equal.d.ts'; +import type {Or} from './or.d.ts'; +import type {IsNegative} from './numeric.d.ts'; +import type {IsTuple} from './is-tuple.d.ts'; +import type {KeysOfUnion} from './keys-of-union.d.ts'; import type {UnknownArray} from './unknown-array.d.ts'; +import type {IsNever} from './is-never.d.ts'; + +/** +`Pick<{0: 0}, '0'>` does not work as expected in some cases, but `ForcePick` handles both `string` and `number` keys correctly. + +type Test_ForcePick_0 = ForcePick<[0,1], 1>; // 1 +type Test_ForcePick_1 = ForcePick<{0: 'aa'}, '0'>; // {0: 'aa'} +type Test_ForcePick_2 = ForcePick<{'0': 'aa'}, '0'>; // {'0': 'aa'} +type Test_ForcePick_3 = ForcePick<{0: 'aa'}, 0>; // {0: 'aa'} +type Test_ForcePick_4 = ForcePick<{'0': 'aa'}, 0>; // {'0': 'aa'} +*/ +type ForcePick = + Collection extends UnknownArray + ? Collection[Key extends keyof Collection ? Key : never] + : {[K in keyof Collection as `${K extends (string | number) ? K : never}` extends `${Key}` ? K : never]: Collection[K]}; + +/** +Forcefully access the property value of `T` by Key if it exists, supporting both `string` and `number` representations; otherwise, return `never`. + +type TestForceGet_0 = ForceGet<{2: 'x'}, '2'>; // 'x' +type TestForceGet_1 = ForceGet<{'2': 'x'}, '2'>; // 'x' +type TestForceGet_2 = ForceGet<{2: 'x'}, 2>; // 'x' +type TestForceGet_3 = ForceGet<{'2': 'x'}, 2>; // 'x' +type TestForceGet_4 = ForceGet<['x', 'xx'], 0>; // 'x' +type TestForceGet_5 = ForceGet<['x', 'xx'], '0'>; // 'x' +type TestForceGet_6 = ForceGet<['x', 'xx'], '2'>; // never +type TestForceGet_7 = ForceGet<{a: 'x'}, 'b'>; // never +type TestForceGet_8 = ForceGet<[0,1,2], `${number}`>; // 0 | 1 | 2 +type TestForceGet_9 = ForceGet // string +*/ +type ForceGet = + Key extends keyof T + ? T[Key] + : Key extends string | number + ? StringToNumber<`${Key}`> extends infer Number_ extends number + ? IsNever extends false + ? `${Key}` extends `${keyof T extends (string | number) ? keyof T : never}` + ? T[(`${Key}` extends infer K extends keyof T ? K : never)] | T[(Number_ extends infer K extends keyof T ? K : never)] + /* `${number}` case */ + : `${Key}` extends `${infer N extends number}` + ? [IsEqual, T] extends [true, ReadonlyArray] + ? ArrayType + : never + : never + : never + : never + : never; + +/** +Returns `true` if the key is theoretically accessible on the value `A`; otherwise, returns `false`. + +type Test_IsKeyOf_0 = IsKeyOf<[0, 1], 1>; // true +type Test_IsKeyOf_1 = IsKeyOf<[0, 1], 3>; // false +type Test_IsKeyOf_2 = IsKeyOf<[0, 1], -1>; // false +type Test_IsKeyOf_3 = IsKeyOf<[0, 1], 's'>; // false +type Test_IsKeyOf_4 = IsKeyOf<[0, 1], '1'>; // true +type Test_IsKeyOf_5 = IsKeyOf<[0, 1], '3'>; // false +type Test_IsKeyOf_6 = IsKeyOf<[0, 1], '-1'>; // false +type Test_IsKeyOf_7 = IsKeyOf<[0, 1], `${number}`>; // true +type Test_IsKeyOf_8 = IsKeyOf, `${number}`>; // true +type Test_IsKeyOf_9 = IsKeyOf<{0: 'a0'}, 0>; // true +type Test_IsKeyOf_10 = IsKeyOf<{'0': 'a0'}, 0>; // true +type Test_IsKeyOf_11 = IsKeyOf<{0: 'a0'}, '0'>; // true +type Test_IsKeyOf_12 = IsKeyOf<{'0': 'a0'}, 0>; // true +type Test_IsKeyOf_13 = IsKeyOf<{'0': 'a0'}, never>; // false +*/ +type IsKeyOf = + IsEqual extends true + ? false + : A extends UnknownArray + ? IsEqual, true> extends false + ? K extends `${number}` + ? true + : K extends (string | number) + ? IsEqual>> + : never + : K extends (string | number) + ? [A['length'], IsEqual, IsEqual>>, GreaterThan>] extends [number, false, true, true] + ? true + : false + : false + : A extends object + ? K extends (string | number) + ? `${K}` extends `${keyof A extends (string | number) ? keyof A : never}` + ? true + : false + : false + : false; + +/** +Returns `A` itself if it is not an `object`. +If `A` is an `object`, behaves like `Pick`. +If `A` is an `UnknownArray`, returns `A[K]`. +If `K` is `never`, returns `never`. + +This fixes https://github.com/sindresorhus/type-fest/issues/1224. + +type Test_PickOrSelf_0 = PickOrSelf; // string +type Test_PickOrSelf_1 = PickOrSelf<{readonly a?: 0}, 'a'>; // {readonly a?: 0} +type Test_PickOrSelf_2 = PickOrSelf<{2?: 0}, '2'>; // {2?: 0} +type Test_PickOrSelf_3 = PickOrSelf<{2?: 0}, 2>; // {2?: 0} +type Test_PickOrSelf_4 = PickOrSelf<{'2': 0}, 2>; // {'2': 0} +type Test_PickOrSelf_5 = PickOrSelf<{2?: 0}, never>; // never +*/ +type PickOrSelf = + A extends UnknownArray + ? ForcePick + : A extends object + ? IsEqual> extends true + ? ForcePick + : never + : A; + +/** +Merges only the `object` types from a union; otherwise, returns the value as-is. + +type Test_MergeOnlyObjectUnion_0 = MergeOnlyObjectUnion<0 | string | {readonly a: 0} | {b?: 2} | [0] | [1]>; // string | 0 | [0] | [1] | {readonly a: 0; b?: 2} +*/ +type MergeOnlyObjectUnion = _MergeOnlyObjectUnion; +type _MergeOnlyObjectUnion = + LastOfUnion extends infer L + ? IsNever extends false + ? L extends UnknownArray + ? _MergeOnlyObjectUnion, ObjectStack, UnionStack | L> + : L extends object + ? _MergeOnlyObjectUnion, ObjectStack & L, UnionStack> + : _MergeOnlyObjectUnion, ObjectStack, UnionStack | L> + : UnionStack | Simplify extends false ? ObjectStack : never> + : never; + +/** +This doesn't fail with non-object type, to safely support `keyof` with union types including `object`s. + +type Test_CoerceKeyof_0 = CoerceKeyof; // "x" +type Test_CoerceKeyof_1 = CoerceKeyof; // "x" | "y" +type Test_CoerceKeyof_2 = CoerceKeyof; // never +*/ +type CoerceKeyof = R extends object ? keyof R extends (string | number) ? keyof R : never : never; + +/* `BuildObject` supporting `${number}`. */ +type Build = + `${K extends string ? K : never}` extends `${infer N extends number}` + ? IsEqual extends true + /* Preserve readonly for arrays */ + ? M extends unknown[] + ? L[] + : M extends readonly unknown[] + ? readonly L[] + : never + : M extends unknown[] + ? [...TupleOf, L] + : M extends readonly unknown[] + ? readonly [...TupleOf, L] + : BuildObject + : BuildObject; /** Pick properties from a deeply-nested object. @@ -77,76 +236,114 @@ type Street = PickDeep; @category Object @category Array */ -export type PickDeep> = - T extends NonRecursiveType - ? never - : T extends UnknownArray - ? UnionToIntersection<{ - [P in PathUnion]: InternalPickDeep; - }[PathUnion] - > - : T extends object - ? Simplify; - }[PathUnion]>> - : never; +export type PickDeep> = InternalPickDeep> extends infer M extends PathTreeType ? M : never>; -/** -Pick an object/array from the given object/array by one path. -*/ -type InternalPickDeep = - T extends NonRecursiveType - ? never - : T extends UnknownArray ? PickDeepArray - : T extends object ? Simplify> - : never; +type InternalPickDeep = + Parent extends UnknownArray + ? _PickDeep + : Parent extends object + ? _PickDeep + : Parent; -/** -Pick an object from the given object by one path. -*/ -type PickDeepObject = - P extends `${infer RecordKeyInPath}.${infer SubPath}` - ? ObjectValue extends infer ObjectV - ? IsNever extends false - ? BuildObject, SubPath>, RecordType> +type RecursionPickDeep = + NextParent extends infer NextParentArray extends UnknownArray + /* NextParent: array */ + ? IsEqual, false> extends true + ? NextParentArray extends readonly unknown[] + /* If end */ + ? ForceGet> extends LeafMarker + ? IsEqual<`${number}`, `${CoerceKeyof}`> extends true + /* `leadingSpreadArray2_Actual` in `test-d/pick-deep.ts` */ + ? NextParent + /* `tailingSpreadArray1_Actual` in `test-d/pick-deep.ts` */ + : [...TupleOf}`>>, PickOrSelf>] + /* Not end */ + : InternalPickDeep>, Extract>, PathTreeType>> extends infer Result + ? IsEqual<`${number}`, `${CoerceKeyof}`> extends true + /* `leadingSpreadArray1_Actual` in `test-d/pick-deep.ts` */ + ? NextParentArray extends unknown[] + ? Result[] + : readonly Result[] + /* `tailingSpreadArray2_Actual` in `test-d/pick-deep.ts`. */ + : [...TupleOf}`>>, Result] + : never : never - : never - : ObjectValue extends infer ObjectV - ? IsNever extends false - ? BuildObject + /* NextParent: tuple */ + : InternalPickDeep + /* NextParent: object */ + : InternalPickDeep>>>>>, NextPathTree>; + +type _PickDeep = + LastOfUnion extends infer L extends keyof PathTree + ? IsNever extends false + ? L extends number | string + ? IsEqual> extends true + /* Detect an end of path. */ + ? IsEqual extends true + ? PickOrSelf extends infer PickResult + ? PickResult extends UnknownArray + ? _PickDeep, U | Simplify<[...TupleOf>, PickResult]>> + : _PickDeep, U | Simplify> + : never + /* Not a leaf - recurse deeper */ + : ForceGet extends infer SubTree + ? _PickDeep, U | Simplify, Extract>, Extract>>> + : never + : never : never - : never; + : MergeOnlyObjectUnion + : never; /** -Pick an array from the given array by one path. +Converts a dot-delimited path string into a nested object tree structure. +Example: `'a.b.c'` becomes `{a: {b: {c: ''}}}` + +type Test_PathToTree = MergeTree>; +// {d: {b: {d: ''; c: ''}}; +// a: {b: {d: {x: ''}; c: {x: ''}}}} */ -type PickDeepArray = - // Handle paths that are `${number}.${string}` - P extends `${infer ArrayIndex extends number}.${infer SubPath}` - // When `ArrayIndex` is equal to `number` - ? number extends ArrayIndex - ? ArrayType extends unknown[] - ? Array, SubPath>> - : ArrayType extends readonly unknown[] - ? ReadonlyArray, SubPath>> - : never - // When `ArrayIndex` is a number literal - : ArrayType extends unknown[] - ? [...TupleOf, InternalPickDeep, SubPath>] - : ArrayType extends readonly unknown[] - ? readonly [...TupleOf, InternalPickDeep, SubPath>] - : never - // When the path is equal to `number` - : P extends `${infer ArrayIndex extends number}` - // When `ArrayIndex` is `number` - ? number extends ArrayIndex - ? ArrayType - // When `ArrayIndex` is a number literal - : ArrayType extends unknown[] - ? [...TupleOf, ArrayType[ArrayIndex]] - : ArrayType extends readonly unknown[] - ? readonly [...TupleOf, ArrayType[ArrayIndex]] - : never - : never; +type PathToTree = + S extends `${infer F}.${infer Next}` + ? Next extends `${infer _}.${infer __}` + ? {[K in F]: PathToTree} + : {[K in F]: {[L in Next]: LeafMarker}} + : {[K in S extends string ? S : never]: LeafMarker}; + +type LeafMarker = ''; +type PathTreeType = {[K in string]: PathTreeType | LeafMarker}; + +/** +Merges nested `object` trees from a union into a single tree structure. + +type Test_MergeTree_0 = MergeTree<{a: {b: ''}} | {a: {c: ''}}>; // {a: {b: ''; c: '';}} +*/ +type MergeTree = + LastOfUnion extends infer L extends object + ? IsNever extends false + ? MergeTree, MergeTreeObject> + : IsEqual extends true + ? never + : M + : never; + +type _MergeTreeObject = + LastOfUnion extends infer K + ? IsNever extends false + ? K extends (keyof A) & (keyof B) + ? _MergeTreeObject, Simplify, Extract>, A | B>>> + : K extends keyof A + ? _MergeTreeObject, Simplify>> + : K extends keyof B + ? _MergeTreeObject, Simplify>> + : R + : R + : never; + +type MergeTreeObject = + Or, IsEqual> extends true + ? B + : Or, IsEqual> extends true + ? A + : _MergeTreeObject | KeysOfUnion), (keyof A | keyof B)>>; export {}; diff --git a/source/union-to-tuple.d.ts b/source/union-to-tuple.d.ts index 5a6d13ae2..ada4b9d18 100644 --- a/source/union-to-tuple.d.ts +++ b/source/union-to-tuple.d.ts @@ -1,19 +1,5 @@ import type {IsNever} from './is-never.d.ts'; -import type {UnionToIntersection} from './union-to-intersection.d.ts'; - -/** -Returns the last element of a union type. - -@example -``` -type Last = LastOfUnion<1 | 2 | 3>; -//=> 3 -``` -*/ -type LastOfUnion = -UnionToIntersection T : never> extends () => (infer R) - ? R - : never; +import type {LastOfUnion} from './internal/index.d.ts'; /** Convert a union type into an unordered tuple type of its elements. @@ -50,9 +36,11 @@ const petList = Object.keys(pets) as UnionToTuple; @category Array */ -export type UnionToTuple> = -IsNever extends false - ? [...UnionToTuple>, L] - : []; +export type UnionToTuple = _UnionToTuple; + +type _UnionToTuple> = + IsNever extends false + ? [..._UnionToTuple>, L] + : []; export {}; diff --git a/test-d/pick-deep.ts b/test-d/pick-deep.ts index 556fefc81..6ab2c7152 100644 --- a/test-d/pick-deep.ts +++ b/test-d/pick-deep.ts @@ -1,5 +1,5 @@ import {expectType} from 'tsd'; -import type {PickDeep} from '../index.d.ts'; +import type {IsEqual, PickDeep} from '../index.d.ts'; declare class ClassA { a: string; @@ -32,8 +32,9 @@ type Testing = BaseType & { 2?: BaseType; }; -declare const normal: PickDeep; -expectType<{string: string}>(normal); +type normal_Actual = PickDeep; +type normal_Expected = {string: string}; +expectType({} as IsEqual); type DeepType = { nested: { @@ -48,8 +49,8 @@ type DeepType = { }; type DepthType = {nested: {deep: {deeper: {value: string}}}}; -declare const deep: PickDeep; -expectType(deep); +type deep_Actual = PickDeep; +expectType({} as IsEqual); // Test interface // eslint-disable-next-line @typescript-eslint/consistent-type-definitions @@ -59,70 +60,172 @@ interface DeepInterface extends DeepType { string: string; }; } -declare const deepInterface: PickDeep; -expectType(deepInterface); -declare const deepInterface2: PickDeep; -expectType<{bar: {number: number}}>(deepInterface2); + +type deepInterface_Actual = PickDeep; +expectType({} as IsEqual); + +type deepInterface2_Actual = PickDeep; +type deepInterface2_Expected = {bar: {number: number}}; +expectType({} as IsEqual); type GenericType = { genericKey: T; }; -declare const genericTest: PickDeep, 'genericKey'>; -expectType<{genericKey: number}>(genericTest); - -declare const union: PickDeep; -expectType<{object: {number: number} & {string: string}}>(union); - -declare const optional: PickDeep; -expectType<{optionalObject?: {optionalString?: string}}>(optional); - -declare const optionalUnion: PickDeep; -expectType<{optionalObject?: {string?: string}; object: {number: number}}>(optionalUnion); - -declare const readonlyTest: PickDeep; -expectType<{readonly readonlyObject: {a: 1}}>(readonlyTest); - -declare const array: PickDeep; -expectType<{object: {array: number[]}}>(array); - -declare const readonlyArray: PickDeep; -expectType<{object: {readonlyArray: readonly number[]}}>(readonlyArray); - -declare const tuple: PickDeep; -expectType<{object: {tuples: ['foo', 'bar']}}>(tuple); - -declare const objectArray1: PickDeep; -expectType<{object: {objectArray: Array<{a: 1; b: 2}>}}>(objectArray1); - -declare const objectArray2: PickDeep; -expectType<{object: {objectArray: Array<{a: 1}>}}>(objectArray2); - -declare const leadingSpreadArray1: PickDeep; -expectType<{object: {leadingSpreadArray: [...Array<{a: 1}>]}}>(leadingSpreadArray1); - -declare const leadingSpreadArray2: PickDeep; -expectType<{object: {leadingSpreadArray: [...Array<{a: 1}>, {b: 2}]}}>(leadingSpreadArray2); - -declare const tailingSpreadArray1: PickDeep; -expectType<{object: {tailingSpreadArray: [unknown, {b: {c: 2; other: 2}}]}}>(tailingSpreadArray1); - -declare const tailingSpreadArray2: PickDeep; -expectType<{object: {tailingSpreadArray: [unknown, {b: {c: 2}}]}}>(tailingSpreadArray2); - -declare const date: PickDeep; -expectType<{object: {date: Date}}>(date); - -declare const instance: PickDeep; -expectType<{object: {instance: ClassA}}>(instance); - -declare const classTest: PickDeep; -expectType<{object: {Class: typeof ClassA}}>(classTest); - -declare const numberTest: PickDeep; -expectType<{1: BaseType}>(numberTest); - -declare const numberTest2: PickDeep; -expectType<{1: {0: number}}>(numberTest2); +type genericTest_Actual = PickDeep, 'genericKey'>; +type genericTest_Expected = {genericKey: number}; +expectType({} as IsEqual); + +type union_Actual = PickDeep; +type union_Expected = {object: {number: number; string: string}}; +expectType({} as IsEqual); + +type optional_Actual = PickDeep; +type optional_Expected = {optionalObject?: {optionalString?: string}}; +expectType({} as IsEqual); + +type optionalUnion_Actual = PickDeep; +type optionalUnion_Expected = { + optionalObject?: {string?: string}; + object: {number: number}; +}; +expectType({} as IsEqual); + +type readonlyTest_Actual = PickDeep; +type readonlyTest_Expected = {readonly readonlyObject: {a: 1}}; +expectType({} as IsEqual); + +type array_Actual = PickDeep; +type array_Expected = {object: {array: number[]}}; +expectType({} as IsEqual); + +type readonlyArray_Actual = PickDeep; +type readonlyArray_Expected = {object: {readonlyArray: readonly number[]}}; +expectType({} as IsEqual); + +type tuple_Actual = PickDeep; +type tuple_Expected = {object: {tuples: ['foo', 'bar']}}; +expectType({} as IsEqual); + +type objectArray1_Actual = PickDeep; +type objectArray1_Expected = {object: {objectArray: Array<{a: 1; b: 2}>}}; +expectType({} as IsEqual); + +type objectArray2_Actual = PickDeep; +type objectArray2_Expected = {object: {objectArray: Array<{a: 1}>}}; +expectType({} as IsEqual); + +type leadingSpreadArray1_Actual = PickDeep; +type leadingSpreadArray1_Expected = {object: {leadingSpreadArray: [...Array<{a: 1}>]}}; +expectType({} as IsEqual); + +type leadingSpreadArray2_Actual = PickDeep; +type leadingSpreadArray2_Expected = {object: {leadingSpreadArray: [...Array<{a: 1}>, {b: 2}]}}; +expectType({} as IsEqual); + +type tailingSpreadArray1_Actual = PickDeep; +type tailingSpreadArray1_Expected = {object: {tailingSpreadArray: [unknown, {b: {c: 2; other: 2}}]}}; +expectType({} as IsEqual); + +type tailingSpreadArray2_Actual = PickDeep; +type tailingSpreadArray2_Expected = {object: {tailingSpreadArray: [unknown, {b: {c: 2}}]}}; +expectType({} as IsEqual); + +type date_Actual = PickDeep; +type date_Expected = {object: {date: Date}}; +expectType({} as IsEqual); + +// The `PickDeep` test for a property containing a class instance (ClassA). +type instance_Actual = PickDeep; +type instance_Expected = {object: {instance: ClassA}}; +expectType({} as IsEqual); + +type classTest_Actual = PickDeep; +type classTest_Expected = {object: {Class: typeof ClassA}}; +expectType({} as IsEqual); + +type numberTest_Actual = PickDeep; +type numberTest_Expected = {1: BaseType}; +expectType({} as IsEqual); + +type numberTest2_Actual = PickDeep; +type numberTest2_Expected = {1: {0: number}}; +expectType({} as IsEqual); + +type notEqual_NumberTest2_Actual0 = PickDeep; +type notEqual_NumberTest2_Expected0 = PickDeep; +expectType({} as IsEqual); + +type numberTest3_Actual = PickDeep; +type numberTest3_Expected = {2?: {0: number}}; +expectType({} as IsEqual); + +// Test for https://github.com/sindresorhus/type-fest/issues/1224 +// Ensures `null` and `undefined` are preserved: https://github.com/sindresorhus/type-fest/issues/880#issuecomment-2115864014 +type unionElement0_Actual = PickDeep<{obj: string | {a: string; b: number; c: boolean} | null | undefined}, 'obj'>; +type unionElement0_Expected = {obj: string | {a: string; b: number; c: boolean} | null | undefined}; +expectType({} as IsEqual); + +// Test for https://github.com/sindresorhus/type-fest/issues/1224 +type unionElement1_Actual = PickDeep<{obj: string | {a: string; b: number; c: {d: 'result'}} | null | undefined}, 'obj.b'>; +type unionElement1_Expected = {obj: string | null | undefined | {b: number}}; +expectType({} as IsEqual); + +// Test for https://github.com/sindresorhus/type-fest/issues/1224 +type unionElement2_Actual = PickDeep<{obj: string | {a: string; b: number; c: {readonly d?: 'result'}} | null | undefined}, 'obj.c.d'>; +type unionElement2_Expected = {obj: string | null | undefined | {c: {readonly d?: 'result'}}}; +expectType({} as IsEqual); + +// Test for https://github.com/sindresorhus/type-fest/issues/1224 +type unionElement3_Actual = PickDeep< + {obj: string | {a: string; b: number; c?: {readonly d?: 'result' | 'is'}} | null | undefined}, 'obj.c.d'>; +type unionElement3_Expected = {obj: string | null | undefined | {c?: {readonly d?: 'result' | 'is'}}}; +expectType({} as IsEqual); + +// Test for https://github.com/sindresorhus/type-fest/issues/1224 +type unionElement4_Actual = PickDeep< + {obj: string | {a: string; b: number; c?: {readonly d?: 'result' | 'is'}} | null | undefined}, 'obj.c.d' | 'obj.b'>; +type unionElement4_Expected = {obj: string | null | undefined | {c?: {readonly d?: 'result' | 'is'}; b: number}}; +expectType({} as IsEqual); + +// Test for https://github.com/sindresorhus/type-fest/issues/1140#issuecomment-2881382244 +type unionObject0_Actual = PickDeep<{foo: string} | {foo: number}, 'foo'>; +type unionObject0_Expected = {foo: string} | {foo: number}; +expectType({} as IsEqual); + +// Test for https://github.com/sindresorhus/type-fest/issues/1223 +type unionKeyObjectArray_Actual = PickDeep<{arr: Array<{a: string; b: number; c: boolean}>}, `arr.${number}.${'b' | 'c'}`>; +type unionKeyObjectArray_Expected = {arr: Array<{b: number; c: boolean}>}; +expectType({} as IsEqual); + +type unionKeyObjectArrayArray_Actual = PickDeep<{arr: Array>}, `arr.${number}.${number}.${'b' | 'c'}`>; +type unionKeyObjectArrayArray_Expected = {arr: Array>}; +expectType({} as IsEqual); + +type unionSameKeysObject_Actual = PickDeep<{a: string | {b: 1 | true; c: 2; d: {g: {f: 9; h: 10}}} | {b: '1'; c: '2'}; x: 10 | 11; y: [[0, 1], 2, 3]}, `a.${'b' | 'c'}` | 'x'>; +type unionSameKeysObject_Expected = {x: 10 | 11; a: string | {b: 1 | true; c: 2} | {b: '1'; c: '2'}}; +expectType({} as IsEqual); + +type unionTupleTuple_Actual = PickDeep<[0, string | [1, [2]]], '1.1'>; +type unionTupleTuple_Expected = [unknown, string | [unknown, [2]]]; +expectType({} as IsEqual); + +// Test: readonly array should preserve readonly modifier when picking into elements +type ReadonlyArrayContainer = { + items: ReadonlyArray<{a: 1; b: 2}>; +}; +type readonlyArrayPick_Actual = PickDeep; +type readonlyArrayPick_Expected = {items: ReadonlyArray<{a: 1}>}; +expectType({} as IsEqual); -declare const numberTest3: PickDeep; -expectType<{2?: {0: number}}>(numberTest3); +// Test: mutable array should stay mutable +type MutableArrayContainer = { + items: Array<{a: 1; b: 2}>; +}; +type mutableArrayPick_Actual = PickDeep; +type mutableArrayPick_Expected = {items: Array<{a: 1}>}; +expectType({} as IsEqual); + +// Test: https://github.com/sindresorhus/type-fest/issues/880#issuecomment-2115864014 +type Nested = {name: string; age: number}; +type RootNestedNullable = {name: string; age: number; nested: Nested | null}; +expectType({} as IsEqual<{name: string; nested: {name: string} | null}, PickDeep>); diff --git a/test-d/union-to-tuple.ts b/test-d/union-to-tuple.ts index 12de3cf2b..6c5392272 100644 --- a/test-d/union-to-tuple.ts +++ b/test-d/union-to-tuple.ts @@ -11,3 +11,5 @@ expectType({} as (1 | 2 | 3)); type Options2 = UnionToTuple; expectType({} as (1 | false | true)); + +expectType<[]>({} as UnionToTuple);