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
}
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' } ;