Hone logo
Hone
Problems

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:

  1. ArrayToLiteralUnion<T>: This type should take an array T and infer a union of its elements as literal types. For example, if T is ['a', 'b', 'c'], the resulting type should be 'a' | 'b' | 'c'.
  2. FilterLiteralUnion<Union, Filter>: This type should take a union type Union and a literal type Filter. It should return a new union type containing only the members of Union that are assignable to Filter.
  3. IsLiteral<T>: This type should return true if T is a literal type (string, number, boolean, or enum member) and false otherwise.

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 IsLiteral type should be precise in identifying primitive literal types.

Edge Cases:

  • Empty arrays for ArrayToLiteralUnion.
  • Non-literal types within an array for ArrayToLiteralUnion.
  • FilterLiteralUnion with a Filter that doesn't match any members.
  • IsLiteral with types like object, 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.
  • IsLiteral should correctly identify primitive literal types and differentiate them from their corresponding primitive types (e.g., 'a' vs string).

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 for string, number, and boolean literals.
Loading editor...
typescript