Skip to content

Commit

Permalink
Fix type narrowing of branded string types
Browse files Browse the repository at this point in the history
  • Loading branch information
gvergnaud committed May 31, 2021
1 parent b4dcc2b commit 2857f7a
Show file tree
Hide file tree
Showing 3 changed files with 68 additions and 1 deletion.
5 changes: 4 additions & 1 deletion src/types/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,10 @@ type BuiltInObjects =
| { readonly [Symbol.toStringTag]: string };

export type IsPlainObject<o> = o extends object
? o extends BuiltInObjects
? // to excluded branded string types,
// like `string & { __brand: "id" }`
// and built-in objects
o extends string | BuiltInObjects
? false
: true
: false;
Expand Down
26 changes: 26 additions & 0 deletions tests/branded-nominal-types.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { match, when } from '../src';

describe('Branded strings', () => {
type BrandedId = string & { __brand: 'brandId' };
type FooBar = { type: 'foo'; id: BrandedId; value: string } | { type: 'bar' };
type State = {
fooBar: FooBar;
fooBarId: BrandedId;
};

it('should treat branded strings as regular string, and not as objects', () => {
const state: State = {
fooBar: { type: 'foo', id: '' as BrandedId, value: 'value' },
fooBarId: '' as BrandedId,
};

expect(
match(state)
.with(
{ fooBar: { type: 'foo' }, fooBarId: when((id) => id === '') },
(x) => `Match: ${x.fooBar.value}`
)
.otherwise(() => 'nope')
).toEqual('Match: value');
});
});
38 changes: 38 additions & 0 deletions tests/extract-precise-value.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -222,4 +222,42 @@ describe('ExtractPreciseValue', () => {
];
});
});

describe('Branded strings', () => {
it('Type narrowing should correctly work on branded strings', () => {
// Branded strings is a commonly used way of implementing
// nominal types in typescript.

type BrandedId = string & { __brand: 'brandId' };

type FooBar =
| { type: 'foo'; id: BrandedId; value: string }
| { type: 'bar' };

type cases = [
Expect<
Equal<
ExtractPreciseValue<
{
fooBar: FooBar;
fooBarId: BrandedId;
},
{
fooBar: { type: 'foo' };
fooBarId: BrandedId;
}
>,
{
fooBar: {
type: 'foo';
id: BrandedId;
value: string;
};
fooBarId: BrandedId;
}
>
>
];
});
});
});

0 comments on commit 2857f7a

Please sign in to comment.