Is this possible to have types restricted without if
by function calls that never
return for e.g undefined
like assert
in Typescript?
Example code:
interface Foo { bar(): void }
function getFoo(): Foo | undefined { }
function test() {
const foo = someService.getFoo();
assert(foo);
if (!foo) { // now mandatory because without this foo may be still undefined even if assert protects us from this
return;
}
foo.bar(); // , here foo may be undefined
}
I would like to be able to write assert
in such way that i can skip following if (!foo)
clause and have foo
type restricted to plain Foo
.
Is this possible in Typescript?
I've tried adding overloads with never
for types that throw:
function assertGuard(v: undefined | null | '' | 0 | false): never;
function assertGuard(v: any): void; // i'm not sure which one is captured by TS typesystem here
function assertGuard<T>(v: T | undefined) {
if (v === undefined || v === null || v === '' || v === 0 || v === false) {
throw new AssertionError({message: 'foo'})
}
}
This one compiles, but call to assertGuard(foo)
doesn't recognize that for undefined
it will return never
so doesn't restrict foo
to Foo
.
I've found possible workarounds but i consider classical assert
a cleaner approach:
function assertResultDefined<T>(v: T|undefined): T | never {
if (v === undefined) {
throw new Error('foo');
}
return v;
}
function die(): never { throw new Error('value expected)}
const foo = assertResultDefined(getFoo()) // foo is Foo, undefined is erased
const foo = getFoo() || die();
// undefined is erased from foo
/ CONS: doesn't play well with types that interpolate to `false` like 0, ''
A type guard is a TypeScript technique used to get information about the type of a variable, usually within a conditional block. Type guards are regular functions that return a boolean, taking a type and telling TypeScript if it can be narrowed down to something more specific.
What does ?: mean in TypeScript? Using a question mark followed by a colon ( ?: ) means a property is optional. That said, a property can either have a value based on the type defined or its value can be undefined .
TypeScript follows possible paths of execution that our programs can take to analyze the most specific possible type of a value at a given position. It looks at these special checks (called type guards) and assignments, and the process of refining types to more specific types than declared is called narrowing.
In Typescript, Type aliases give a type a new name. They are similar to interfaces in that they can be used to name primitives and any other kinds that you'd have to define by hand otherwise. Aliasing doesn't truly create a new type; instead, it gives that type a new name.
Typescript 3.7 adds assertions in control flow analysis.
An
asserts
return type predicate indicates that the function returns only when the assertion holds and otherwise throws an exception
Hacks on consumer side are not needed anymore.
interface Foo { bar(): void }
declare function getFoo(): Foo | undefined;
function assert(value: unknown): asserts value {
if (value === undefined) {
throw new Error('value must be defined');
}
}
function test() {
const foo = getFoo();
// foo is Foo | undefined here
assert(foo);
// foo narrowed to Foo
foo.bar();
}
Playground
Additionally one can assert that provided parameter is of required type:
declare function assertIsArrayOfStrings(obj: unknown): asserts obj is string[];
function foo(x: unknown) {
assertIsArrayOfStrings(x);
return x[0].length; // x has type string[] here
}
Playground
There is an issue in the typescript backlog for this https://github.com/Microsoft/TypeScript/issues/8655. So for now you can't do this.
What you can do, is to use the assertion operator "!". Adding ! after value will assert that the value is neither undefined nor null. Use this is case where you're absolutely sure it cannot lead to a null or undefined reference.
function test() {
const foo: (FooType|null) = getFoo();
foo!.bar(); // "!" - asserts that foo is not null nor undefined
}
Source: https://www.typescriptlang.org/docs/handbook/advanced-types.html#type-guards-and-type-assertions
Since foo
is Foo | undefined
, its type should be changed to Foo
somehow.
In the code above, this reasonably can be done with:
let foo = getFoo(); // Foo | undefined
foo = assertResultDefined(foo); // Foo
foo.bar();
Another option is to use non-null assertion (as another answer suggests):
let foo = getFoo();
foo = assertResultDefined(foo);
foo = foo!;
foo.bar();
this should work for you:
const foo = (a: number | null) => {
a = shouldBe(_.isNumber, a)
a // TADA! a: number
}
const shouldBe = <T>(fn: (t1) => t1 is T, t) => (fn(t) ? t : throwError(fn, t))
const throwError = (fn:Function, t) => {
throw new Error(`not valid, ${fn.name} failed on ${t}`)
}
where _.isNumber
has a type guard x is number
This can be used with any function with a type guard.
the key is you must reassign the variable, so effectively assert
is an identity function that throws an error on failed type assertion
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With