From 6a0f513eda24c8a39fec76c36cf9b389f2098400 Mon Sep 17 00:00:00 2001 From: Malte Legenhausen Date: Wed, 31 May 2023 10:12:25 +0200 Subject: [PATCH 1/5] Fix `matchXW` failed pattern match when return value is `undefined` --- src/index.ts | 6 ++---- test/unit/index.ts | 13 +++++++++++++ 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/src/index.ts b/src/index.ts index e174bdd..12a37b6 100644 --- a/src/index.ts +++ b/src/index.ts @@ -256,13 +256,11 @@ const mkMatchXW = (x: A): C => { const tag = x[tagKey] as Tag - const g = fs[tag] // eslint-disable-next-line functional/no-conditional-statement, @typescript-eslint/no-unsafe-return - if (g !== undefined) return g as C + if (tag in fs) return fs[tag] as C - const h = (fs as CasesXWildcard)[_] // eslint-disable-next-line functional/no-conditional-statement, @typescript-eslint/no-unsafe-return - if (h !== undefined) return h as C + if (_ in fs) return (fs as CasesXWildcard)[_] as C // eslint-disable-next-line functional/no-throw-statement throw new Error(`Failed to pattern match against tag "${tag}".`) diff --git a/test/unit/index.ts b/test/unit/index.ts index 5a531a8..a9dfe06 100644 --- a/test/unit/index.ts +++ b/test/unit/index.ts @@ -98,6 +98,19 @@ describe("index", () => { expect(f(Weather.mk.Sun)).toBeNull() }) + + it('matchXW allow undefined value', () => { + type T = Member<'A'> | Member<'B'> + const T = create() + + const f = T.matchXW({ + A: undefined, + [_]: undefined + }); + + expect(f(T.mk.A)).toBe(undefined); + expect(f(T.mk.B)).toBe(undefined); + }); }) }) From f67a99644b845307c084936db8c5b15a8eb6a9ed Mon Sep 17 00:00:00 2001 From: Malte Legenhausen Date: Wed, 31 May 2023 10:20:21 +0200 Subject: [PATCH 2/5] Formatting fixed --- test/unit/index.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/test/unit/index.ts b/test/unit/index.ts index a9dfe06..7d120c4 100644 --- a/test/unit/index.ts +++ b/test/unit/index.ts @@ -99,18 +99,18 @@ describe("index", () => { expect(f(Weather.mk.Sun)).toBeNull() }) - it('matchXW allow undefined value', () => { - type T = Member<'A'> | Member<'B'> + it("matchXW allow undefined value", () => { + type T = Member<"A"> | Member<"B"> const T = create() const f = T.matchXW({ A: undefined, - [_]: undefined - }); + [_]: undefined, + }) - expect(f(T.mk.A)).toBe(undefined); - expect(f(T.mk.B)).toBe(undefined); - }); + expect(f(T.mk.A)).toBe(undefined) + expect(f(T.mk.B)).toBe(undefined) + }) }) }) From afd9a761922d43cc3642dcad5cf13ad0aa33744a Mon Sep 17 00:00:00 2001 From: Malte Legenhausen Date: Wed, 31 May 2023 10:41:57 +0200 Subject: [PATCH 3/5] Use `Object.prototype.hasOwnProperty` instead of `in` operator --- src/index.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/index.ts b/src/index.ts index 12a37b6..fdf94d6 100644 --- a/src/index.ts +++ b/src/index.ts @@ -257,10 +257,11 @@ const mkMatchXW = const tag = x[tagKey] as Tag // eslint-disable-next-line functional/no-conditional-statement, @typescript-eslint/no-unsafe-return - if (tag in fs) return fs[tag] as C + if (Object.prototype.hasOwnProperty.call(fs, tag)) return fs[tag] as C // eslint-disable-next-line functional/no-conditional-statement, @typescript-eslint/no-unsafe-return - if (_ in fs) return (fs as CasesXWildcard)[_] as C + if (Object.prototype.hasOwnProperty.call(fs, _)) + return (fs as CasesXWildcard)[_] as C // eslint-disable-next-line functional/no-throw-statement throw new Error(`Failed to pattern match against tag "${tag}".`) From 413c8a2539c8c0cf8144ccaada4022e84905e209 Mon Sep 17 00:00:00 2001 From: Malte Legenhausen Date: Wed, 31 May 2023 10:43:39 +0200 Subject: [PATCH 4/5] `matchX` tests added --- test/unit/index.ts | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/test/unit/index.ts b/test/unit/index.ts index 7d120c4..f3cca72 100644 --- a/test/unit/index.ts +++ b/test/unit/index.ts @@ -84,6 +84,19 @@ describe("index", () => { expect(f(Weather.mk.Sun)).toBe("didn't rain") }) + it("matchX allow undefined value", () => { + type T = Member<"A"> | Member<"B"> + const T = create() + + const f = T.matchX({ + A: undefined, + [_]: undefined, + }) + + expect(f(T.mk.A)).toBe(undefined) + expect(f(T.mk.B)).toBe(undefined) + }) + it("matchXW", () => { const f = Weather.matchXW({ Rain: "rained", From 13c9b3010f65adcba3ea93a806df8ecfe264753a Mon Sep 17 00:00:00 2001 From: Malte Legenhausen Date: Thu, 1 Jun 2023 11:52:19 +0200 Subject: [PATCH 5/5] Make sure object prototype properties are not matched --- test/unit/index.ts | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/test/unit/index.ts b/test/unit/index.ts index f3cca72..2e6182a 100644 --- a/test/unit/index.ts +++ b/test/unit/index.ts @@ -85,7 +85,7 @@ describe("index", () => { }) it("matchX allow undefined value", () => { - type T = Member<"A"> | Member<"B"> + type T = Member<"A"> | Member<"B"> | Member<"toString"> const T = create() const f = T.matchX({ @@ -95,6 +95,9 @@ describe("index", () => { expect(f(T.mk.A)).toBe(undefined) expect(f(T.mk.B)).toBe(undefined) + // Check that `toString` does not match an object prototype function + // This check would fail when the `in` operator is used instead of `hasOwnProperty`. + expect(f(T.mk.toString)).toBe(undefined) }) it("matchXW", () => { @@ -113,7 +116,7 @@ describe("index", () => { }) it("matchXW allow undefined value", () => { - type T = Member<"A"> | Member<"B"> + type T = Member<"A"> | Member<"B"> | Member<"toString"> const T = create() const f = T.matchXW({ @@ -123,6 +126,9 @@ describe("index", () => { expect(f(T.mk.A)).toBe(undefined) expect(f(T.mk.B)).toBe(undefined) + // Check that `toString` does not match an object prototype function. + // This check would fail when the `in` operator is used instead of `hasOwnProperty`. + expect(f(T.mk.toString)).toBe(undefined) }) }) })