Hone logo
Hone
Problems

Advanced TypeScript Union Type Manipulation

TypeScript's union types are a powerful tool for representing values that can be one of several types. However, manipulating and extracting information from complex union types can sometimes be cumbersome. This challenge focuses on building utility types that simplify common operations on union types, making your TypeScript code more robust and readable.

Problem Description

Your task is to implement a series of TypeScript utility types that help in working with union types. These utilities should allow for flexible transformation and filtering of types within a union.

Key Requirements:

  1. UnionToTuple<T>: Convert a union type T into a tuple where each element of the union appears exactly once as an element in the tuple. The order of elements in the tuple does not strictly matter, but it should be consistent.
  2. LastInUnion<T>: Extract the "last" type from a union T. This is defined as the type that would be returned by repeatedly removing one type from the union until only one remains.
  3. UnionToIntersection<T>: Convert a union type T into an intersection type. This is particularly useful for scenarios where you want to combine properties from all types within a union.
  4. FilterUnion<T, U>: Given a union type T and a specific type U, create a new union type containing only those members of T that are assignable to U.
  5. ExcludeUnion<T, U>: Given a union type T and a specific type U, create a new union type containing only those members of T that are not assignable to U. This is essentially a more generic version of TypeScript's built-in Exclude.

Expected Behavior:

The utility types should behave predictably and correctly for various union types, including primitives, objects, and combinations thereof.

Edge Cases to Consider:

  • Empty union types (though TypeScript often infers never for these).
  • Unions containing any, unknown, null, and undefined.
  • Unions with identical members.
  • Unions with deeply nested types.

Examples

Example 1: UnionToTuple<T>

// Input:
type MyUnion1 = 'a' | 'b' | 'c';
type ResultTuple1 = UnionToTuple<MyUnion1>;
// Expected Output Type:
// ['a', 'b', 'c'] or ['c', 'b', 'a'] (order may vary but should be consistent)

// Input:
type MyUnion2 = 1 | 2 | 3 | 2; // Duplicate '2'
type ResultTuple2 = UnionToTuple<MyUnion2>;
// Expected Output Type:
// [1, 2, 3] or similar permutation

Example 2: LastInUnion<T>

// Input:
type MyUnion3 = 'a' | 'b' | 'c';
type LastType3 = LastInUnion<MyUnion3>;
// Expected Output Type:
// 'c' (or the last one encountered in a specific reduction process)

// Input:
type MyUnion4 = string | number | boolean;
type LastType4 = LastInUnion<MyUnion4>;
// Expected Output Type:
// boolean (or the last one encountered)

Example 3: UnionToIntersection<T>

// Input:
type MyUnion5 = { a: string } | { b: number };
type IntersectionType5 = UnionToIntersection<MyUnion5>;
// Expected Output Type:
// { a: string } & { b: number }

// Input:
type MyUnion6 = { x: number } | { x: string }; // Conflicting properties
type IntersectionType6 = UnionToIntersection<MyUnion6>;
// Expected Output Type:
// { x: number } & { x: string }

Example 4: FilterUnion<T, U>

// Input:
type MyUnion7 = string | number | boolean | null;
type TargetType7 = string | number;
type FilteredUnion7 = FilterUnion<MyUnion7, TargetType7>;
// Expected Output Type:
// string | number

// Input:
type MyUnion8 = { type: 'a', value: number } | { type: 'b', value: string } | { type: 'c', value: boolean };
type TargetType8 = { type: 'a' };
type FilteredUnion8 = FilterUnion<MyUnion8, TargetType8>;
// Expected Output Type:
// { type: 'a', value: number }

Example 5: ExcludeUnion<T, U>

// Input:
type MyUnion9 = 'a' | 'b' | 'c' | 'd';
type ExcludedType9 = 'b' | 'd';
type RemainingUnion9 = ExcludeUnion<MyUnion9, ExcludedType9>;
// Expected Output Type:
// 'a' | 'c'

// Input:
type MyUnion10 = number | string | boolean;
type ExcludedType10 = string;
type RemainingUnion10 = ExcludeUnion<MyUnion10, ExcludedType10>;
// Expected Output Type:
// number | boolean

Constraints

  • All utility types must be implemented using TypeScript's conditional types and other built-in type manipulation features.
  • Do not use external libraries or packages.
  • The solution should be performant enough for typical TypeScript project compilation times. Avoid excessively recursive or computationally expensive type manipulations where simpler alternatives exist.
  • For UnionToTuple, the order of elements in the resulting tuple is not strictly defined but must be consistent for a given input union.

Notes

  • Consider how TypeScript's distributive conditional types work when operating on union types.
  • You might find it helpful to explore the relationship between unions and intersections.
  • For UnionToTuple, you may need a helper type to manage the accumulation of elements.
  • Think about how type assignability plays a role in filtering and exclusion.
  • The goal is to create elegant and robust type-level solutions.
Loading editor...
typescript