Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use TypeScript template literal types to check and generate better types #1

Open
VitorLuizC opened this issue Sep 12, 2021 · 0 comments
Assignees
Labels
enhancement New feature or request

Comments

@VitorLuizC
Copy link
Owner

VitorLuizC commented Sep 12, 2021

There's no way to use tagged template string with template literal types:
microsoft/TypeScript#33304

But right after TS implement them we can use it to check and generate better refinement functions. I've written a small PoC to test this possibility and it works really well.

PoC source code
type Not<T, U> = T extends U ? never : T;

type PrefixOperators<T, A> = {
  'not': Not<T, A>;
  'nullish': null | void | undefined | A;
};

type PrefixOperatorName = keyof PrefixOperators<any, any>;

type InfixOperators<_T, A, B> = {
  'or': A | B;
  'and': A & B;
};

type InfixOperatorName = keyof InfixOperators<any, any, any>;

type Predicates = {
  'binary': 0 | 1;
  'nullish': null | void | undefined;
  'string': string;
  'number': number;
  'bigint': bigint;
  'boolean': boolean;
  'symbol': symbol;
  'undefined': undefined;
  'object': null | object;
  'function': (...args: unknown[]) => unknown;
};

type PredicateName = keyof Predicates;

type EvalType<E extends string, T> = (
  E extends PredicateName
    ? Predicates[E]
    : E extends `${PrefixOperatorName} ${infer A}`
      ? E extends `${infer O} ${A}`
        ? O extends PrefixOperatorName
          ? PrefixOperators<T, EvalType<A, T>>[O]
          : never
        : never
      : E extends `${infer A} ${InfixOperatorName} ${infer B}`
        ? E extends `${A} ${infer O} ${B}`
          ? O extends InfixOperatorName
            ? InfixOperators<T, EvalType<A, T>, EvalType<B, T>>[O]
            : never
          : never
        : never
);

type Eval<E extends string> = (
  E extends PredicateName
    ? ((value: unknown) => value is EvalType<E, unknown>)
    : E extends `${PrefixOperatorName} ${string}`
      ? (<T = unknown>(value: T) => value is EvalType<E, T>)
      : E extends `${string} ${InfixOperatorName} ${string}`
        ? (<T = unknown>(value: T) => value is EvalType<E, T>)
        : never
);

declare function is<E extends string>(expression: E): Eval<E>;

// ---
// USAGE EXAMPLE
// ---

const isObj = is('object and not nullish');

let user: undefined | null | {
  name: string;
};

if (isObj(user)) {
  // 'user' here is an object with name property.
  console.log(user.name.toUpperCase());
} else {
  // @ts-expect-error because because 'user' here is null or undefined.
  console.log(user.name.toUpperCase());
}
@VitorLuizC VitorLuizC added the enhancement New feature or request label Sep 12, 2021
@VitorLuizC VitorLuizC self-assigned this Sep 12, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

1 participant