Skip to content

Instantly share code, notes, and snippets.

@OliverJAsh
Created August 9, 2019 18:16
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save OliverJAsh/a6de628277b424573bee28a431182c79 to your computer and use it in GitHub Desktop.
Save OliverJAsh/a6de628277b424573bee28a431182c79 to your computer and use it in GitHub Desktop.
A negate function for type guards
type TypeGuard<T, U extends T> = (value: T) => value is U;
const negate = <T, U extends T>(
typeGuard: TypeGuard<T, U>,
): TypeGuard<T, Exclude<T, U>> => (value): value is Exclude<T, U> =>
typeGuard(value) === false;
// Example
const checkIsArray = <T>(value: T | any[]): value is any[] =>
Array.isArray(value);
const checkIsNotArray = negate(checkIsArray);
declare const value: string | string[];
if (checkIsNotArray(value)) {
value; // type here is `string` 🎉
}
@karol-majewski
Copy link

Note: negate can only work if the input type guard is generic. It won't work in situations like this:

declare function isString(candidate: unknown): candidate is string;

const isNotString = negate(isString); // 🤷‍♂️

@OliverJAsh
Copy link
Author

I think the issue there is that types cannot be excluded from unknown (in this case, Exclude<unknown, string> => unknown).

If you don't use unknown, it works:

declare function isString(candidate: string | number): candidate is string;

… but I guess that's not always an option! I guess Exclude only works for filtering union types, so the parameter has to be a union.

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