Hone logo
Hone
Problems

Advanced Type-Level Programming in TypeScript

TypeScript's powerful type system allows for sophisticated compile-time computations. This challenge focuses on building generic type utilities that can manipulate and transform types, enabling more robust and expressive code. Mastering these techniques leads to safer APIs, better developer tooling, and more predictable application behavior.

Problem Description

Your task is to implement several generic TypeScript type utilities that perform common type-level operations. These utilities will operate solely on type definitions and will not involve any runtime JavaScript code. You need to ensure that your solutions are correct, efficient, and handle various type scenarios, including primitive types, object types, array types, and union/intersection types.

Key Requirements

  1. DeepReadonly<T>: Create a type that makes all properties of an object type, and recursively all nested object properties, readonly.
  2. DeepPartial<T>: Create a type that makes all properties of an object type, and recursively all nested object properties, optional.
  3. TupleToObject<T>: Given a tuple type T where each element is a two-element tuple [Key, Value], convert it into an object type where the first element of each tuple is the key and the second is the value.
  4. Unpack<T>: Create a type that takes a nested array or tuple type and flattens it into a single-level union of its elements.

Expected Behavior

Your implemented types should behave as described in the examples below. They should correctly infer and transform types at compile time.

Edge Cases to Consider

  • Handling of primitive types (string, number, boolean, null, undefined, symbol, bigint) within nested structures.
  • Handling of arrays and tuples.
  • Handling of union and intersection types within the structures being transformed.
  • Ensuring that DeepReadonly and DeepPartial do not affect already readonly or optional properties in unintended ways (e.g., making an optional readonly property ?readonly).

Examples

Example 1: DeepReadonly<T>

type MyObject = {
  a: number;
  b: {
    c: string;
    d: boolean;
  };
  e: Array<{ f: number }>;
};

type DeepReadonlyMyObject = DeepReadonly<MyObject>;

// Expected type:
// {
//   readonly a: number;
//   readonly b: {
//     readonly c: string;
//     readonly d: boolean;
//   };
//   readonly e: readonly Array<{ readonly f: number }>;
// }

Example 2: DeepPartial<T>

type MyObject = {
  a: number;
  b: {
    c: string;
    d: boolean;
  };
};

type PartialMyObject = DeepPartial<MyObject>;

// Expected type:
// {
//   a?: number;
//   b?: {
//     c?: string;
//     d?: boolean;
//   };
// }

Example 3: TupleToObject<T>

type MyTuple = [['name', string], ['age', number], ['isActive', boolean]];

type MyObjectFromTuple = TupleToObject<MyTuple>;

// Expected type:
// {
//   name: string;
//   age: number;
//   isActive: boolean;
// }

Example 4: Unpack<T>

type NestedArray = Array<number | Array<string | Array<boolean>>>;
type UnpackedArray = Unpack<NestedArray>;

// Expected type:
// string | number | boolean

type NestedTuple = [1, [2, ['hello', [true]]]];
type UnpackedTuple = Unpack<NestedTuple>;

// Expected type:
// 1 | 2 | 'hello' | true

Constraints

  • All solutions must be implemented using TypeScript's conditional types, mapped types, and infer keywords.
  • No runtime JavaScript code is allowed. The solution must be entirely type-level.
  • The solution should aim for reasonable compile-time performance. Extremely complex or deeply recursive types that cause excessive compilation time should be avoided if simpler alternatives exist.
  • Each utility should be a single generic type alias.

Notes

  • Consider how to correctly handle arrays and tuples differently from plain objects.
  • The DeepReadonly and DeepPartial utilities should be able to handle primitive types gracefully by returning them unchanged.
  • For TupleToObject, you might need to iterate over the tuple elements and extract the key and value from each pair.
  • For Unpack, you'll likely need recursion to handle arbitrarily nested arrays/tuples.
  • Pay attention to the difference between keyof T and iterating over tuple elements.
Loading editor...
typescript