From 3c796467b8c05bca6363585b4eb4f15c5404e9c8 Mon Sep 17 00:00:00 2001 From: gvergnaud Date: Wed, 19 Jul 2023 11:13:39 -0400 Subject: [PATCH] =?UTF-8?q?=F0=9F=90=9B=20fix:=20Accept=20branded=20primit?= =?UTF-8?q?ive=20types=20as=20patterns?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit TS-Pattern v4 used to support branded types, usually defined as an intersection between a literal type (like `number` or `string`) and an object containing a unique symbol. There is a bug in v5 that prevents people from using branded types as patterns and this PR fixes it. This closes issue #167 and #178. --- src/types/Pattern.ts | 2 +- tests/branded-nominal-types.test.ts | 34 +++++++++++++++++++++++++++++ 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/src/types/Pattern.ts b/src/types/Pattern.ts index e66ce02a..1ae6bc5e 100644 --- a/src/types/Pattern.ts +++ b/src/types/Pattern.ts @@ -158,7 +158,7 @@ type KnownPatternInternal< a, objs = Exclude | Set | readonly any[]>, arrays = Extract, - primitives = Exclude + primitives = Extract > = | primitives | PatternMatcher diff --git a/tests/branded-nominal-types.test.ts b/tests/branded-nominal-types.test.ts index b992b3df..1564d40b 100644 --- a/tests/branded-nominal-types.test.ts +++ b/tests/branded-nominal-types.test.ts @@ -23,4 +23,38 @@ describe('Branded strings', () => { .otherwise(() => 'nope') ).toEqual('Match: value'); }); + + it('issue #167', () => { + const tag: unique symbol = Symbol(); + type Tagged = { readonly [tag]: Token }; + type Opaque = Type & Tagged; + + const opaqueString = (Math.random() > 0.5 ? 'A' : 'B') as Opaque<'A' | 'B'>; + + match(opaqueString) + .with('A' as Opaque<'A'>, () => 1) + .with('B' as Opaque<'B'>, () => 2) + .exhaustive(); + }); + + it('issue #178', () => { + const symbol: unique symbol = Symbol(); + + interface Branded { + [symbol]: { [k in key]: true }; + } + + type Brand = a & Branded; + type BrandId = Brand; + + const a: number = 1; + const b: BrandId = 1 as BrandId; + + expect( + match({ a, b }) + .with({ a, b }, () => '1') + .with({ a: P.number, b: P.number }, () => '2') + .exhaustive() + ).toEqual('1'); + }); });