Hone logo
Hone
Problems

Implementing TypeScript typeof Utilities

TypeScript's typeof operator is a powerful tool for inspecting the type of a variable or expression. This challenge asks you to implement your own utility functions that mimic some of the core functionalities of typeof, focusing on extracting type information and creating new types based on existing ones. This is valuable for creating more robust and flexible type-safe code, especially when dealing with dynamic or generic scenarios.

Problem Description

You are tasked with creating three TypeScript utility functions: ExtractType, MakeNullable, and MakeOptional.

  • ExtractType<T>: This function should take a type T and return its underlying type. Essentially, it should strip away any container types (like arrays, objects, tuples) to reveal the base type. For primitive types (number, string, boolean, symbol, bigint, void, never, undefined), it should return the type itself. For object types, it should return unknown. For arrays, it should return the type of the array elements. For tuples, it should return a tuple with the same types.
  • MakeNullable<T>: This function should take a type T and return a new type where T is made nullable. This means that T can now be either its original type or null.
  • MakeOptional<T>: This function should take a type T and return a new type where T is made optional. This means that T can now be either its original type or undefined.

Key Requirements:

  • The functions must be implemented using TypeScript's type system (conditional types, mapped types, etc.).
  • The functions should be generic, accepting any type T.
  • The functions should handle primitive types, object types, arrays, tuples, and potentially more complex types correctly.

Expected Behavior:

  • ExtractType<number> should return number.
  • ExtractType<string[]> should return string.
  • ExtractType<{ name: string }> should return unknown.
  • ExtractType<[number, string]> should return [number, string].
  • MakeNullable<string> should return string | null.
  • MakeNullable<number[]> should return number[] | null.
  • MakeOptional<number> should return number | undefined.
  • MakeOptional<{ name: string }> should return { name?: string }.

Edge Cases to Consider:

  • ExtractType<void> should return void.
  • ExtractType<never> should return never.
  • ExtractType<unknown> should return unknown.
  • Handling union types correctly (e.g., ExtractType<string | number> should return string | number).
  • Nested types (e.g., ExtractType<string[][]> should return string).

Examples

Example 1:

type MyType = number[];
type Extracted = ExtractType<MyType>; // Expected: string
type NullableMyType = MakeNullable<MyType>; // Expected: number[] | null
type OptionalMyType = MakeOptional<MyType>; // Expected: number[] | undefined

Example 2:

type ObjectType = { name: string; age?: number };
type ExtractedObjectType = ExtractType<ObjectType>; // Expected: unknown
type NullableObjectType = MakeNullable<ObjectType>; // Expected: { name: string; age?: number } | null
type OptionalObjectType = MakeOptional<ObjectType>; // Expected: { name?: string; age?: number }

Example 3:

type TupleType = [string, number, boolean];
type ExtractedTupleType = ExtractType<TupleType>; // Expected: [string, number, boolean]
type NullableTupleType = MakeNullable<TupleType>; // Expected: [string, number, boolean] | null
type OptionalTupleType = MakeOptional<TupleType>; // Expected: [string, number, boolean] | undefined

Constraints

  • The solutions must be written in TypeScript.
  • The functions must be type-safe and avoid runtime errors.
  • The functions should be reasonably performant for type manipulation. While performance isn't the primary concern, avoid excessively complex or inefficient type transformations.
  • The code should be well-formatted and readable.

Notes

  • This challenge focuses on TypeScript's type system and conditional types. Understanding how to manipulate types at compile time is crucial.
  • Consider using mapped types and conditional types to achieve the desired behavior.
  • The unknown type is often used as a placeholder when the exact type cannot be determined.
  • Start with simpler types (primitive types, arrays) and gradually move to more complex types (objects, tuples).
  • Test your functions thoroughly with various types to ensure they behave as expected.
Loading editor...
typescript