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 typeTand 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 returnunknown. 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 typeTand return a new type whereTis made nullable. This means thatTcan now be either its original type ornull.MakeOptional<T>: This function should take a typeTand return a new type whereTis made optional. This means thatTcan now be either its original type orundefined.
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 returnnumber.ExtractType<string[]>should returnstring.ExtractType<{ name: string }>should returnunknown.ExtractType<[number, string]>should return[number, string].MakeNullable<string>should returnstring | null.MakeNullable<number[]>should returnnumber[] | null.MakeOptional<number>should returnnumber | undefined.MakeOptional<{ name: string }>should return{ name?: string }.
Edge Cases to Consider:
ExtractType<void>should returnvoid.ExtractType<never>should returnnever.ExtractType<unknown>should returnunknown.- Handling union types correctly (e.g.,
ExtractType<string | number>should returnstring | number). - Nested types (e.g.,
ExtractType<string[][]>should returnstring).
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
unknowntype 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.