Enforce good typescript compile-time validation error

113 Views Asked by At

Typescript language allows perform compile-time validations. Let's walk though following example (Playground also available)

// Some complex compile-time type check, for instance, ariphmetic expression validator
type NumericSymbols = `0` | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9'
type OperationSymbols =  '+' | '-' | '/' | '*'
type Integers<T extends string> = T extends '' ? never : T extends NumericSymbols ? T : T extends `${infer _FirstLetter}${infer Rest}` ? _FirstLetter extends NumericSymbols 
  ? `${_FirstLetter}${Integers<Rest>}` : never : never
type Operation<T extends string> = T extends `${infer leftExpression}${OperationSymbols}${infer rightExpression}` | `(${infer leftExpression}${OperationSymbols}${infer rightExpression})` 
    ? Operation<leftExpression> | Integers<leftExpression> extends never ? never :
      Operation<rightExpression> | Integers<rightExpression> extends never ? never :
    T
:never
type Expressions<T> = T extends string ? T extends Integers<T> ? T : T extends Operation<T> ? T : never : never

// Utility functions
type IfEquals<X, Y, A = unknown, B = never> = (<T>() => T extends X ? 1 : 2) extends (<T>() => T extends Y ? 1 : 2) ? A : B;
type Undefined<T> = IfEquals<T, never, never, T> extends never ? Undefined<T> : T


// Public type validation genetic
type IsValidOrNever<T> = IfEquals<Expressions<T>, T, T, never>
type IsValidOrUndefined<T> = Undefined<IfEquals<Expressions<T>, T, T, never>>

// Tests
type TestNeverOkay = IsValidOrNever<'(1+2)*3'> // '(1+2)*3'
type TestNeverFail = IsValidOrNever<'1++2'>    // never

type TestUndefinedOkay = IsValidOrUndefined<'(1+2)*3'> // '(1+2)*3'
type TestUndefinedFail = IsValidOrUndefined<'1++2'>    // compile-time error "Type instantiation is excessively deep and possibly infinite"

There is some compile-time validation, in current example it is simple brute-force ariphmetic expression validator, but can be arbitary type expression

Goal is provide export for public generic type, which accepts customer type with validation, and it best, perform some type calculations with good type, and throw pretty compile-time error for bad type

Unfortunately there is only two well-known options: return never-like type (IsValidOrNever) or call divergent function in haskell-manner like undefined (IsValidOrUndefined)

These solutions work well, but have own pitfalls: IsValidOrNever just returns never type, which can be used in circumstances that does not emit compile-time error; and IsValidOrUndefined emits strange error for customer (Type instantiation is excessively deep and possibly infinite)

Question: maybe is there third solution allows emitting pretty and understandable compile-time error for this case? Something like mention instantination of IsValidOrX will be applicable

0

There are 0 best solutions below