Mastering Literal Types in TypeScript
TypeScript's literal types allow you to represent values that are exactly a specific string, number, boolean, or enum member. This offers a powerful way to enhance type safety by restricting variables to a predefined set of allowed values. This challenge will guide you through implementing custom utility types that leverage literal types, enabling you to create more precise and robust type definitions.
Problem Description
Your task is to create a set of TypeScript utility types that operate on literal types. These helpers will allow you to perform common operations on literal types, such as creating union types from arrays of literals, extracting specific literal types, and checking for their presence.
Key Requirements:
ArrayToLiteralUnion<T>: This type should take an arrayTand infer a union of its elements as literal types. For example, ifTis['a', 'b', 'c'], the resulting type should be'a' | 'b' | 'c'.FilterLiteralUnion<Union, Filter>: This type should take a union typeUnionand a literal typeFilter. It should return a new union type containing only the members ofUnionthat are assignable toFilter.IsLiteral<T>: This type should returntrueifTis a literal type (string, number, boolean, or enum member) andfalseotherwise.
Expected Behavior:
- The utility types should be generic and work with various literal types and array structures.
- They should handle nested arrays or complex object structures gracefully by focusing on the direct elements of the input array for
ArrayToLiteralUnion. - The
IsLiteraltype should be precise in identifying primitive literal types.
Edge Cases:
- Empty arrays for
ArrayToLiteralUnion. - Non-literal types within an array for
ArrayToLiteralUnion. FilterLiteralUnionwith aFilterthat doesn't match any members.IsLiteralwith types likeobject,any,unknown, or union types themselves.
Examples
Example 1: ArrayToLiteralUnion
type Colors = ['red', 'green', 'blue'];
type ColorUnion = ArrayToLiteralUnion<Colors>;
// Expected: 'red' | 'green' | 'blue'
Example 2: FilterLiteralUnion
type Status = 'pending' | 'processing' | 'completed' | 'failed';
type PendingOrFailed = FilterLiteralUnion<Status, 'pending' | 'failed'>;
// Expected: 'pending' | 'failed'
type OnlyStrings = FilterLiteralUnion<Status | 123 | true, string>;
// Expected: 'pending' | 'processing' | 'completed' | 'failed'
Example 3: IsLiteral
type IsRedLiteral = IsLiteral<'red'>;
// Expected: true
type IsNumberLiteral = IsLiteral<42>;
// Expected: true
type IsBooleanLiteral = IsLiteral<false>;
// Expected: true
type IsAnyLiteral = IsLiteral<any>;
// Expected: false
type IsStringLiteral = IsLiteral<string>;
// Expected: false
type IsUnionLiteral = IsLiteral<'a' | 'b'>;
// Expected: false
Constraints
- The solution must be written entirely in TypeScript.
- You should not use runtime type checking or JavaScript code. All logic must be within the TypeScript type system.
- The utility types should be efficient and not lead to excessive type instantiation overhead.
IsLiteralshould correctly identify primitive literal types and differentiate them from their corresponding primitive types (e.g.,'a'vsstring).
Notes
- Consider using mapped types and conditional types extensively.
- For
ArrayToLiteralUnion, you'll need to infer the elements of the array type. - For
FilterLiteralUnion, think about how to check assignability between types. - For
IsLiteral, consider the primitive types in TypeScript and how to distinguish their literal forms. You might need to create specific checks forstring,number, andbooleanliterals.