Repository: remeda/remeda
Pull request #635 (2024-04-12 05:49:32)

fix(isDeepEqual): Prevent redundant narrowing
Fixes: #634

Narrowing the result of isDeepEqual is redundant when the type isn't effectively narrowed because it breaks the rejected/negated path which would be `never`.

This means that the common use case where both objects are of the same type and narrowing was meaningless, will now act as a regular boolean check.

codesandbox-ci[bot]: This pull request is automatically built and testable in [CodeSandbox](https://codesandbox.io).

  To see build info of the built libraries, click [here](https://ci.codesandbox.io/status/remeda/remeda/pr/635/builds/493956) or the icon next to each commit SHA.

Latest deployment of this branch, based on commit b1681011d1e34ea66881d2647b7d94a9a4948d84:

|Sandbox| Source |
|--|--|
|[remeda-example-vanilla](https://codesandbox.io/s/remeda-example-vanilla-hrlj5v)| Configuration |


Patch:
diff --git a/src/isDeepEqual.ts b/src/isDeepEqual.ts
index 20b2f98..32382ba 100644
--- a/src/isDeepEqual.ts
+++ b/src/isDeepEqual.ts
@@ -25,7 +25,11 @@ import { purry } from "./purry";
  * @dataFirst
  * @category Guard
  */
-export function isDeepEqual<T, S extends T = T>(data: T, other: S): data is S;
+export function isDeepEqual<T, S extends T>(
+  data: T,
+  other: T extends Exclude<T, S> ? S : never,
+): data is S;
+export function isDeepEqual<T, S extends T = T>(data: T, other: S): boolean;
 
 /**
  * Performs a deep *semantic* comparison between two values to determine if they
@@ -51,9 +55,10 @@ export function isDeepEqual<T, S extends T = T>(data: T, other: S): data is S;
  * @dataLast
  * @category Guard
  */
+export function isDeepEqual<T, S extends T>(other: S): (data: T) => data is S;
 export function isDeepEqual<T, S extends T = T>(
-  other: S,
-): (data: T) => data is S;
+  other: T extends Exclude<T, S> ? S : never,
+): (data: T) => boolean;
 
 export function isDeepEqual(): unknown {
   return purry(isDeepEqualImplementation, arguments);


Test patch:
diff --git a/src/isDeepEqual.test.ts b/src/isDeepEqual.test.ts
index 36b9177..68bd93a 100644
--- a/src/isDeepEqual.test.ts
+++ b/src/isDeepEqual.test.ts
@@ -257,10 +257,14 @@ describe("typing", () => {
 
     if (isDeepEqual(data, 1)) {
       expectTypeOf(data).toEqualTypeOf<number>();
+    } else {
+      expectTypeOf(data).toEqualTypeOf<number | string>();
     }
 
     if (isDeepEqual(data, "hello")) {
       expectTypeOf(data).toEqualTypeOf<string>();
+    } else {
+      expectTypeOf(data).toEqualTypeOf<number | string>();
     }
   });
 
@@ -268,6 +272,8 @@ describe("typing", () => {
     const data = 1 as number;
     if (isDeepEqual(data, 1 as const)) {
       expectTypeOf(data).toEqualTypeOf<1>();
+    } else {
+      expectTypeOf(data).toEqualTypeOf<number>();
     }
   });
 
@@ -282,6 +288,27 @@ describe("typing", () => {
     >;
     if (isDeepEqual(data, [{ a: [1] }])) {
       expectTypeOf(data).toEqualTypeOf<Array<{ a: Array<number> }>>();
+    } else {
+      expectTypeOf(data).toEqualTypeOf<
+        Array<
+          | {
+              a: Array<number> | Array<string>;
+            }
+          | {
+              b: Array<boolean>;
+            }
+        >
+      >();
+    }
+  });
+
+  it("doesn't narrow when comparing objects of the same type", () => {
+    const data1 = { a: 1 } as { a: number };
+    const data2 = { a: 2 } as { a: number };
+    if (isDeepEqual(data1, data2)) {
+      expectTypeOf(data1).toEqualTypeOf<{ a: number }>();
+    } else {
+      expectTypeOf(data1).toEqualTypeOf<{ a: number }>();
     }
   });
 });