Suppose I have a bunch of type guard functions and some logic I want to reuse across all of them. Is there any way to propagate the type predicate? I.e. something like:
type TokenType = `NumericLiteral`; // Big union here
interface Token {
type: TokenType;
raw: string;
value?: unknown;
}
interface NumericLiteral extends Token {
type: `NumericLiteral`;
value: number;
}
function isNumericLiteral(token: Token): token is NumericLiteral {
return token.type === `NumericLiteral`;
}
function expectToken(
token: Token | undefined,
type: TokenType,
guardfn: (token: Token) => boolean
) { // Extract the type predicate out of guardfn somehow?
if (!token || !guardfn(token)) {
const received = token ? token.type : `nothing`;
throw new Error(`Expected ${type} but got ${received}`);
}
}
const token = { type: `NumericLiteral`, value: 123 } as Token;
expectToken(token, `NumericLiteral`, isNumericLiteral);
// token should now be narrowed as NumericLiteral
An assertion function with a generic type parameter will allow you to infer the type guard from the guard function.
Note that I didn't only use the generic type on the return type, but also on the type guard parameter (guardFn: (token: Token) => token is T
) and the actual type parameter (type: T['type']
) to ensure consistency:
function expectToken<T extends Token>(
token: Token | undefined,
type: T['type'],
guardFn: (token: Token) => token is T
): asserts token is T {
if (!token || !guardFn(token)) {
const received = token ? token.type : `nothing`;
throw new Error(`Expected ${type} but got ${received}`);
}
}
Complete snippet:
type TokenType = `NumericLiteral`;
interface Token {
type: TokenType;
raw: string;
value?: unknown;
}
interface NumericLiteral extends Token {
type: `NumericLiteral`;
value: number;
}
function isNumericLiteral(token: Token): token is NumericLiteral {
return token.type === `NumericLiteral`;
}
function expectToken<T extends Token>(
token: Token | undefined,
type: T['type'],
guardFn: (token: Token) => token is T
): asserts token is T {
if (!token || !guardFn(token)) {
const received = token ? token.type : `nothing`;
throw new Error(`Expected ${type} but got ${received}`);
}
}
const token = { type: `NumericLiteral`, value: 123 } as Token;
expectToken(token, `NumericLiteral`, isNumericLiteral);
token; // token is narrowed to NumericLiteral