From 051c5669665bf44d4b9fa527dc5309b76e17b836 Mon Sep 17 00:00:00 2001
From: pikax <david-181@hotmail.com>
Date: Tue, 16 May 2023 13:52:26 +0100
Subject: [PATCH] types(withDefaults): Allow to be used with generic
 defineProps

---
 packages/dts-test/setupHelpers.test-d.ts     | 34 +++++++++++++
 packages/runtime-core/src/apiSetupHelpers.ts | 52 +++++++++++---------
 2 files changed, 64 insertions(+), 22 deletions(-)

diff --git a/packages/dts-test/setupHelpers.test-d.ts b/packages/dts-test/setupHelpers.test-d.ts
index 9b68b345268..77342590dc6 100644
--- a/packages/dts-test/setupHelpers.test-d.ts
+++ b/packages/dts-test/setupHelpers.test-d.ts
@@ -100,6 +100,40 @@ describe('defineProps w/ union type declaration + withDefaults', () => {
   )
 })
 
+describe('defineProps w/ generic type declaration + withDefaults', <T extends number, TA extends {
+  a: string
+}, TString extends string>() => {
+  const res = withDefaults(
+    defineProps<{
+      n?: number
+      bool?: boolean
+
+      generic1?: T[] | { x: T }
+      generic2?: { x: T }
+      generic3?: TString
+      generic4?: TA
+    }>(),
+    {
+      n: 123,
+
+      generic1: () => [123, 33] as T[],
+      generic2: () => ({ x: 123 } as { x: T }),
+
+      generic3: () => 'test' as TString,
+      generic4: () => ({ a: 'test' } as TA)
+    }
+  )
+
+  res.n + 1
+
+  expectType<T[] | { x: T }>(res.generic1)
+  expectType<{ x: T }>(res.generic2)
+  expectType<TString>(res.generic3)
+  expectType<TA>(res.generic4)
+
+  expectType<boolean>(res.bool)
+})
+
 describe('defineProps w/ runtime declaration', () => {
   // runtime declaration
   const props = defineProps({
diff --git a/packages/runtime-core/src/apiSetupHelpers.ts b/packages/runtime-core/src/apiSetupHelpers.ts
index 3ad330a1c27..76ad08cf63a 100644
--- a/packages/runtime-core/src/apiSetupHelpers.ts
+++ b/packages/runtime-core/src/apiSetupHelpers.ts
@@ -81,7 +81,10 @@ export function defineProps<
   PP extends ComponentObjectPropsOptions = ComponentObjectPropsOptions
 >(props: PP): Prettify<Readonly<ExtractPropTypes<PP>>>
 // overload 3: typed-based declaration
-export function defineProps<TypeProps>(): DefineProps<TypeProps>
+export function defineProps<TypeProps>(): DefineProps<
+  TypeProps,
+  BooleanKey<TypeProps>
+>
 // implementation
 export function defineProps() {
   if (__DEV__) {
@@ -90,8 +93,8 @@ export function defineProps() {
   return null as any
 }
 
-type DefineProps<T> = Readonly<T> & {
-  readonly [K in BooleanKey<T>]-?: boolean
+type DefineProps<T, BKeys extends keyof T> = Readonly<T> & {
+  readonly [K in BKeys]-?: boolean
 }
 
 type BooleanKey<T, K extends keyof T = keyof T> = K extends any
@@ -281,26 +284,27 @@ interface DefineModelOptions {
 type NotUndefined<T> = T extends undefined ? never : T
 
 type InferDefaults<T> = {
-  [K in keyof T]?: InferDefault<T, NotUndefined<T[K]>>
+  [K in keyof T]?: InferDefault<T, T[K]>
 }
 
-type InferDefault<P, T> = T extends
-  | null
-  | number
-  | string
-  | boolean
-  | symbol
-  | Function
-  ? T | ((props: P) => T)
-  : (props: P) => T
-
-type PropsWithDefaults<Base, Defaults> = Base & {
-  [K in keyof Defaults]: K extends keyof Base
+type NativeType = null | number | string | boolean | symbol | Function
+
+type InferDefault<P, T> =
+  | ((props: P) => T & {})
+  | (T extends NativeType ? T : never)
+
+type PropsWithDefaults<
+  T,
+  Defaults extends InferDefaults<T>,
+  BKeys extends keyof T
+> = Omit<T, keyof Defaults> & {
+  [K in keyof Defaults]-?: K extends keyof T
     ? Defaults[K] extends undefined
-      ? Base[K]
-      : NotUndefined<Base[K]>
+      ? T[K]
+      : NotUndefined<T[K]>
     : never
-}
+} & { readonly [K in BKeys]-?: boolean }
+
 /**
  * Vue `<script setup>` compiler macro for providing props default values when
  * using type-based `defineProps` declaration.
@@ -321,10 +325,14 @@ type PropsWithDefaults<Base, Defaults> = Base & {
  *
  * @see {@link https://vuejs.org/guide/typescript/composition-api.html#typing-component-props}
  */
-export function withDefaults<Props, Defaults extends InferDefaults<Props>>(
-  props: Props,
+export function withDefaults<
+  T,
+  BKeys extends keyof T,
+  Defaults extends InferDefaults<T>
+>(
+  props: DefineProps<T, BKeys>,
   defaults: Defaults
-): PropsWithDefaults<Props, Defaults> {
+): PropsWithDefaults<T, Defaults, BKeys> {
   if (__DEV__) {
     warnRuntimeUsage(`withDefaults`)
   }