Repository: marpple/FxTS (ts)

Issue:
Incorrect type inference of ReturnPipeType
# Bug Report

<!--
  Please fill in each section completely. Thank you!
-->

### 💻 Code

<!-- Please post the relevant code sample here as well-->
<!-- How to reproduce it -->

```ts
type res1 = ReturnPipeType<[string, { name: 1 }, number, { key: string }]>;

type res2 = ReturnPipeType<
      [string, { name: 1 }, Promise<number>, { key: string }]
    >;

type res3 = ReturnPipeType<
      ["a" | "b" | "c", { name: 1 }, number, { key: string }]
    >;

type res4 = ReturnPipeType<
      ["a" | "b" | "c", string, Promise<number>, { key: string }]
    >;

type res5 = ReturnPipeType<[{ name: 1 }, string, number, { key: string }]>;
```

### 🙁 Actual behavior

<!-- What happened, and why it was wrong -->

```ts
type res1 = { key: string};
type res2 = Promise<{ key: string }>;
type res3 = { name: 1 }; // incorrect
type res4 = string; // incorrect
type res5 = { key: string };
```

### 🙂 Expected behavior

<!-- What you expected to happen instead, and why -->
```ts
type res1 = { key: string};
type res2 = Promise<{ key: string }>;
type res3 = { key: string };
type res4 = Promise<{ key: string }>;
type res5 = { key: string };
```
---

### Version Information

<!-- Please let me know your environment -->

- Operating system: MacOS 13.4
- Typescript: 5.1.3
- Nodejs: 18


Pull request description:
fix: fix the incorrect return type of the pipe function
Fixes #212 


Patch:
diff --git a/src/types/ReturnPipeType.ts b/src/types/ReturnPipeType.ts
index 57bc753b..43dcc47b 100644
--- a/src/types/ReturnPipeType.ts
+++ b/src/types/ReturnPipeType.ts
@@ -1,15 +1,31 @@
 import type Awaited from "./Awaited";
-import type Drop from "./Drop";
-import type Prepend from "./Prepend";
-
-type ReturnPipeType<T extends any[]> = T extends [
-  a: infer A,
-  b: infer B,
-  ...args: any[],
-]
-  ? A extends Promise<any>
-    ? ReturnPipeType<Prepend<Drop<2, T>, Promise<Awaited<B>>>>
-    : ReturnPipeType<Prepend<Drop<2, T>, B>>
-  : T[0];
+import type { TuplifyUnion } from "./ExcludeObject";
+import type Last from "./Last";
+import type Tail from "./Tail";
+
+type IsAny<T> = 0 extends 1 & T ? true : false;
+
+type HasPromise<T extends any[]> = T extends []
+  ? false
+  : IsAny<T[0]> extends true
+  ? HasPromise<Tail<T>>
+  : T extends [Promise<any>, ...any]
+  ? true
+  : HasPromise<Tail<T>>;
+
+type PossiblyHasPromise<T extends any[]> = T extends []
+  ? false
+  : HasPromise<TuplifyUnion<T[0]>> extends true
+  ? true
+  : PossiblyHasPromise<Tail<T>>;
+
+type ReturnPipeType<
+  T extends any[],
+  R = Awaited<Last<T>>,
+> = HasPromise<T> extends true
+  ? Promise<R>
+  : PossiblyHasPromise<T> extends true
+  ? Promise<R> | R
+  : R;
 
 export default ReturnPipeType;


Test patch:
diff --git a/type-check/pipe.test.ts b/type-check/pipe.test.ts
index 59d91780..fd411c44 100644
--- a/type-check/pipe.test.ts
+++ b/type-check/pipe.test.ts
@@ -15,6 +15,36 @@ const res6 = pipe("1", (a) => Promise.resolve(Number(a)));
 const res7 = pipe(Promise.resolve(1), (a) => Promise.resolve(String(a)));
 const res8 = pipe(Promise.resolve("1"), (a) => Promise.resolve(Number(a)));
 
+const res9 = pipe(
+  "a" as "a" | "b",
+  (a) => a.toUpperCase(),
+  () => 1,
+);
+
+const res10 = pipe(
+  Promise.resolve("a") as Promise<"a"> | "a",
+  (a) => a.toUpperCase(),
+  () => 1,
+);
+
+const res11 = pipe(
+  Promise.resolve("a") as Promise<"a"> | "a",
+  (a) => Promise.resolve(a.toUpperCase()),
+  () => 1,
+);
+
+const res12 = pipe(
+  Promise.resolve("a") as Promise<"a"> | "a",
+  (a) => a.toUpperCase(),
+  () => Promise.resolve(1),
+);
+
+const res13 = pipe(
+  Promise.resolve("a") as Promise<"a"> | "a",
+  (a) => (Math.random() > 0.5 ? a.toUpperCase() : Promise.resolve("hi")),
+  () => 1,
+);
+
 checks([
   check<typeof res1, string, Test.Pass>(),
   check<typeof res2, number, Test.Pass>(),
@@ -24,4 +54,9 @@ checks([
   check<typeof res6, Promise<number>, Test.Pass>(),
   check<typeof res7, Promise<string>, Test.Pass>(),
   check<typeof res8, Promise<number>, Test.Pass>(),
+  check<typeof res9, number, Test.Pass>(),
+  check<typeof res10, number | Promise<number>, Test.Pass>(),
+  check<typeof res11, Promise<number>, Test.Pass>(),
+  check<typeof res12, Promise<number>, Test.Pass>(),
+  check<typeof res13, number | Promise<number>, Test.Pass>(),
 ]);