Home > Blockchain >  Types that can only be achieved via a type guard?
Types that can only be achieved via a type guard?

Time:01-23

I'm trying to enforce type safety on strings that are too complex for a template literal by using a regex type guard instead. This is a model of what I have:

type Foo = string;
function isFoo(str: string): str is Foo {
    return /.../.test(str);
}
function useFoo(foo: Foo) {...}

The problem is that plain strings can be passed to useFoo. I know I can just call isFoo inside useFoo and handle mismatches somehow, but it seems like it should be possible to tell Typescript that a type T can be used as a U but can only become a T through a type guard. I've tried looking for something like that but without formal names it's hard to search for something this specific. Is this possible?

CodePudding user response:

Unfortunately, TypeScript does structural typing, so when you do type Foo = string, Foo and string can be used interchangeably.

But there is still a possibility to achieve your objective, by using a technique called "branding" (or "tagging").

E.g. with the ts-brand library:

import { Brand } from 'ts-brand';

type Foo2 = Brand<string, "Foo2">
function isFoo2(str: string): str is Foo2 {
    return /.../.test(str);
}
function useFoo2(foo: Foo2) { }

// Fail on string as expected
useFoo2("bar"); // Error: Type 'string' is not assignable to type '{ __type__: "Foo2"; }'.

declare const foo2: string | Foo2;
// Fail on string | Foo2 as expected
useFoo2(foo2); // Error: Type 'string' is not assignable to type '{ __type__: "Foo2"; }'.

if (isFoo2(foo2)) {
    // Okay on Foo2 only (e.g. here after type guard)
    useFoo2(foo2); // Okay!
    //      ^? Foo2
}

Playground Link

CodePudding user response:

That's seems to be a use case for a branded type : use an intersectionnal type too add contraint ! Therefore bare strings won't be able to be used !

type Foo = string & { __brand: 'Foo' } ;

There a complete example on the Playground

  • Related