DEV Community

canalun
canalun

Posted on

Safer Type Predicate in TypeScript

TypeScript has a feature called type predicate.
However, this function is NOT 100% type-safe.
So I'd like to introduce a safer way to use type predicate in this article👶

Summary

One of the problems about TypeScript's type predicate is that it does NOT care whether your type assertion logic is correct.
Actually, even if your function cannot determine type properly, compliers will not give you any errors as long as the function is grammatically correct.

So, when using type predicate, we should take this way; assign the determination target to a variable of type T|null through your type assertion logic👶

//safer type predicate
function isTypeA(x: typeA | typeB): x is TypeA {
    const maybeTypeA: typeA | null =
        if ([logic which is true only when x is TypeA]) ? x : null
    return !!maybeTypeA
}
Enter fullscreen mode Exit fullscreen mode

In the following, I explain what type predicate is like in TypeScript. And then, see the problem and how the above code solves it.

What is type predicate?

In the context of computer science, the word "predicate" means "a function to determine if something is true or false" in a broad sense👶
The following link is easy to understand.
https://stackoverflow.com/questions/3230944/what-does-predicate-mean-in-the-context-of-computer-science

So, it can be said that "type predicate" means "a function that determines type".
Such a function is implemented not only in TypeScript but also in other languages such as LISP.

It's important that, when saying type predicate in the context of TypeScript, it specifically means the following function(official documentation)

//type predicate in TypeScript
function isFish(pet: Fish | Bird): pet is Fish {
  return (pet as Fish).swim !== undefined;
}
Enter fullscreen mode Exit fullscreen mode

As you can see here, you can easily write a type predicate by changing type of the return value of your type asserting function from boolean to X is T.

And type predicate narrows down type of the variable as follows (from official documentation).

//Example usage of type predicate in TypeScript
let pet = getSmallPet();

if (isFish(pet)) {
  pet.swim();
} else {
  pet.fly();
}
Enter fullscreen mode Exit fullscreen mode

Type predicate can perform type assertion which typeof and instanceof cannot.

When is type predicate insecure?

Although type predicate is useful, it'll be unsafe if the logic to determine type is incorrect.

The following example is clearly incorrect, but ignored by compilers.

//Example of unsafe type predicate in TypeScript
function isNumber(x: unknown): x is Number {
  return typeof x === "boolean";
}
Enter fullscreen mode Exit fullscreen mode

That is a really simple example, but you can overlook and/or make this kind of mistake in complicated codes.

How can we write type predicate safely?

So what exactly should we do? Here, the conclusion at the beginning of this article is explained again.

It's the most important to assign the determination target to a variable of type T|null through your type assertion logic👶.
There are many ways to do this, but for now, ternary operator is convenient.

//safer type predicate
function isTypeA(x: unknown): x is TypeA {
    const maybeTypeA: typeA | null =
        if ([logic which is true only when x is TypeA]) ? x : null
    return !!maybeTypeA
}
Enter fullscreen mode Exit fullscreen mode

Remember, the problem is type predicate is seen as CORRECT by compliers even if its logic is incorrect.
So, if you take the above way, if an not-TypeA argument was determined as TypeA by incorrect logic, the variable 'maybeTypeA' would be null by the ternary operator, and the result would settle to false.

The difference is clear when compared to the typical one as follows.

// In the typical way, if the condition is wrong, the type predicate will also return the wrong result.
function isTypeA(x: unknown): x is TypeA {
    return if ([logic which is true only when x is TypeA]).
}

// In this way of writing, if the condition is wrong, type predicate can detect it (i.e. it can return false).
function isTypeA(x: unknown): x is TypeA {
    const maybeTypeA: typeA | null =
        if ([logic which is true only when x is TypeA]) ? x : null
    return !!maybeTypeA
}
Enter fullscreen mode Exit fullscreen mode

May the safe type life be with you👶

Top comments (0)