Reputation: 178
This is a distilled example based on much more complicated legacy code. Suppose you have a function like this:
function foo(val: string | number): void {
if (typeof(val) !== 'number') {
throw new Error('val must be a number');
}
// do something with number
}
Is it possible to write the signature of foo
such that after calling it, the compiler infers that val
must be a number?
const val: string | number = getVal();
foo(val);
// Compiler shouldn't complain here since a string would have cause foo to throw
const x = Math.sqrt(val);
Upvotes: 0
Views: 54
Reputation: 329248
If you've got a void
-returning function which has the effect of narrowing the type of one of its parameters, and if you are running TypeScript 3.7 or above, you can make it an assertion function and the compiler will take that assertion into account. An assertion function's return type starts with the asserts
modifier and is followed by the condition we'd like the function to assert. This condition can either be the name of one of the function's inputs, or a type predicate that says one of the function's inputs is
of a narrower type than its declared type. Here's how we'd do it with foo()
:
function foo(val: string | number): asserts val is number {
if (typeof (val) !== 'number') {
throw new Error('val must be a number');
}
// do something with number
}
So we've changed void
to asserts val is number
. If the function does return, the type of val
will be narrowed from string | number
to number
, as desired:
const val: string | number = Math.random() < 0.5 ? "str" : 123;
foo(val);
const x = Math.sqrt(val); // okay
Note that assertion functions only work on void
-returning functions, so you can't have a function that acts both as a type assertion and returns some value. And there are other caveats (such as they require explicit annotations to be called). But your question is so perfectly suited to assertion functions that if I didn't know better I'd think this was asked by one of the language designers in order to showcase the power of assertion functions! 😜
Okay, hope that helps; good luck!
Upvotes: 0
Reputation: 4317
Function overloads will do this. For example,
function foo(val: string): void;
function foo(val: number): number;
function foo(val: string|number) {
if (typeof(val) !== 'number') {
throw new Error('val must be a number');
}
return Math.sqrt(val);
}
Math.sqrt(foo(123)); // ok
Math.sqrt(foo('asdf')); // not ok
See on the Typescript Playground.
Upvotes: 1