Skip to content

Instantly share code, notes, and snippets.

@karol-majewski
Created September 8, 2019 22:24
Show Gist options
  • Save karol-majewski/237b73aaa34c7a38e44c20a3bb18a069 to your computer and use it in GitHub Desktop.
Save karol-majewski/237b73aaa34c7a38e44c20a3bb18a069 to your computer and use it in GitHub Desktop.
Composing TypeScript type guards
type TypeGuard<T = any, U extends T = any> = (candidate: T) => candidate is U;
type From<T extends TypeGuard> =
T extends TypeGuard<infer U, any>
? U
: never;
type To<T extends TypeGuard> =
T extends TypeGuard<any, infer U>
? U
: never;
declare function or<T extends TypeGuard, U extends TypeGuard>(f: T, g: U): TypeGuard<From<T | U>, To<T> | To<U>>;
declare function isString(candidate: any): candidate is string;
declare function isNumber(candidate: any): candidate is number;
const isStringOrNumber = or(isString, isNumber);
declare const input: any;
if (isStringOrNumber(input)) {
console.log(input.toString());
}
@karol-majewski
Copy link
Author

Better names?

  • Input<T> and Output<T>
  • CandidateOf<T> and Refinement<T>?

@karol-majewski
Copy link
Author

Implementation:

type TypeGuard<T = any, U extends T = any> = (candidate: T) => candidate is U;

type From<T extends TypeGuard> =
  T extends TypeGuard<infer U, any>
   ? U
   : never;

type To<T extends TypeGuard> =
  T extends TypeGuard<any, infer U>
   ? U
   : never;

function or<T extends TypeGuard, U extends TypeGuard>(f: T, g: U) {
  return (candidate => f(candidate) || g(candidate)) as TypeGuard<From<T | U>, To<T> | To<U>>
}

function isString(candidate: any): candidate is string {
  return typeof candidate === 'string';
}

function isNumber(candidate: any): candidate is number {
  return typeof candidate === 'number'
}

const isStringOrNumber = or(isString, isNumber);

const input = 'foo';

if (isStringOrNumber(input)) {
    console.log(`"${input}" is a string or a number`);
}

@karol-majewski
Copy link
Author

type TypeGuard<T, U extends T> = (candidate: T) => candidate is U;

type GetInputType<T extends TypeGuard<any, any>> =
  T extends TypeGuard<infer U, any>
   ? U
   : never;

type GetRefinementType<T extends TypeGuard<any, any>> =
  T extends TypeGuard<any, infer U>
   ? U
   : never;

declare function or<T extends TypeGuard<any, any>, U extends TypeGuard<any, any>>(
  f: T, g: U
): TypeGuard<GetInputType<T | U>, GetRefinementType<T> | GetRefinementType<U>>;

declare function isString(candidate: any): candidate is string;
declare function isNumber(candidate: any): candidate is number;

const isStringOrNumber = or(isString, isNumber);

declare const input: any;

if (isStringOrNumber(input)) {
    console.log(input.toString());
}

@safareli
Copy link

safareli commented Jun 2, 2021

Here is another alternative if someone is interested https://gist.github.com/safareli/8e480d72f7e2665b6030b9a08fe93f40

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment