]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
fix(types): union function prop (#3119)
author07akioni <07akioni2@gmail.com>
Thu, 25 Mar 2021 15:27:54 +0000 (23:27 +0800)
committerGitHub <noreply@github.com>
Thu, 25 Mar 2021 15:27:54 +0000 (11:27 -0400)
fix #3357

packages/runtime-core/src/componentProps.ts
test-dts/defineComponent.test-d.tsx
test-dts/index.d.ts

index f672446446bf25b9c7de2f65d201c1d7787aa3b5..89baa373314325ac06eb2ff52aa2ff798804cd9f 100644 (file)
@@ -60,7 +60,7 @@ type PropConstructor<T = any> =
   | { (): T }
   | PropMethod<T>
 
-type PropMethod<T, TConstructor = any> = T extends (...args: any) => any // if is function with args
+type PropMethod<T, TConstructor = any> = [T] extends [(...args: any) => any] // if is function with args
   ? { new (): TConstructor; (): T; readonly prototype: TConstructor } // Create Function like constructor
   : never
 
@@ -89,17 +89,19 @@ type DefaultKeys<T> = {
     : never
 }[keyof T]
 
-type InferPropType<T> = T extends null
+type InferPropType<T> = [T] extends [null]
   ? any // null & true would fail to infer
-  : T extends { type: null | true }
+  : [T] extends [{ type: null | true }]
     ? any // As TS issue https://github.com/Microsoft/TypeScript/issues/14829 // somehow `ObjectConstructor` when inferred from { (): T } becomes `any` // `BooleanConstructor` when inferred from PropConstructor(with PropMethod) becomes `Boolean`
-    : T extends ObjectConstructor | { type: ObjectConstructor }
+    : [T] extends [ObjectConstructor | { type: ObjectConstructor }]
       ? Record<string, any>
-      : T extends BooleanConstructor | { type: BooleanConstructor }
+      : [T] extends [BooleanConstructor | { type: BooleanConstructor }]
         ? boolean
-        : T extends DateConstructor | { type: DateConstructor }
+        : [T] extends [DateConstructor | { type: DateConstructor }]
           ? Date
-          : T extends Prop<infer V, infer D> ? (unknown extends V ? D : V) : T
+          : [T] extends [Prop<infer V, infer D>]
+            ? (unknown extends V ? D : V)
+            : T
 
 export type ExtractPropTypes<O> = O extends object
   ? { [K in RequiredKeys<O>]: InferPropType<O[K]> } &
index 949654b1b9ec1f76fe4476431a0c619dd0fff4d5..7a07fef2fe1f57a89f152a9d0020ad0d17648cce 100644 (file)
@@ -11,6 +11,7 @@ import {
   ComponentPublicInstance,
   ComponentOptions,
   SetupContext,
+  IsUnion,
   h
 } from './index'
 
@@ -33,6 +34,9 @@ describe('with object props', () => {
     hhh: boolean
     ggg: 'foo' | 'bar'
     ffff: (a: number, b: string) => { a: boolean }
+    iii?: (() => string) | (() => number)
+    jjj: ((arg1: string) => string) | ((arg1: string, arg2: string) => string)
+    kkk?: any
     validated?: string
     date?: Date
   }
@@ -100,6 +104,16 @@ describe('with object props', () => {
         type: Function as PropType<(a: number, b: string) => { a: boolean }>,
         default: (a: number, b: string) => ({ a: a > +b })
       },
+      // union + function with different return types
+      iii: Function as PropType<(() => string) | (() => number)>,
+      // union + function with different args & same return type
+      jjj: {
+        type: Function as PropType<
+          ((arg1: string) => string) | ((arg1: string, arg2: string) => string)
+        >,
+        required: true
+      },
+      kkk: null,
       validated: {
         type: String,
         // validator requires explicit annotation
@@ -126,6 +140,13 @@ describe('with object props', () => {
       expectType<ExpectedProps['hhh']>(props.hhh)
       expectType<ExpectedProps['ggg']>(props.ggg)
       expectType<ExpectedProps['ffff']>(props.ffff)
+      if (typeof props.iii !== 'function') {
+        expectType<undefined>(props.iii)
+      }
+      expectType<ExpectedProps['iii']>(props.iii)
+      expectType<IsUnion<typeof props.jjj>>(true)
+      expectType<ExpectedProps['jjj']>(props.jjj)
+      expectType<ExpectedProps['kkk']>(props.kkk)
       expectType<ExpectedProps['validated']>(props.validated)
       expectType<ExpectedProps['date']>(props.date)
 
@@ -160,6 +181,13 @@ describe('with object props', () => {
       expectType<ExpectedProps['fff']>(props.fff)
       expectType<ExpectedProps['hhh']>(props.hhh)
       expectType<ExpectedProps['ggg']>(props.ggg)
+      if (typeof props.iii !== 'function') {
+        expectType<undefined>(props.iii)
+      }
+      expectType<ExpectedProps['iii']>(props.iii)
+      expectType<IsUnion<typeof props.jjj>>(true)
+      expectType<ExpectedProps['jjj']>(props.jjj)
+      expectType<ExpectedProps['kkk']>(props.kkk)
 
       // @ts-expect-error props should be readonly
       expectError((props.a = 1))
@@ -180,6 +208,14 @@ describe('with object props', () => {
       expectType<ExpectedProps['fff']>(this.fff)
       expectType<ExpectedProps['hhh']>(this.hhh)
       expectType<ExpectedProps['ggg']>(this.ggg)
+      if (typeof this.iii !== 'function') {
+        expectType<undefined>(this.iii)
+      }
+      expectType<ExpectedProps['iii']>(this.iii)
+      const { jjj } = this
+      expectType<IsUnion<typeof jjj>>(true)
+      expectType<ExpectedProps['jjj']>(this.jjj)
+      expectType<ExpectedProps['kkk']>(this.kkk)
 
       // @ts-expect-error props on `this` should be readonly
       expectError((this.a = 1))
@@ -214,6 +250,7 @@ describe('with object props', () => {
       fff={(a, b) => ({ a: a > +b })}
       hhh={false}
       ggg="foo"
+      jjj={() => ''}
       // should allow class/style as attrs
       class="bar"
       style={{ color: 'red' }}
@@ -232,6 +269,7 @@ describe('with object props', () => {
       eee={() => ({ a: 'eee' })}
       fff={(a, b) => ({ a: a > +b })}
       hhh={false}
+      jjj={() => ''}
     />
   )
 
index be3143f4773c8bba645b6e1b0d4d265970dbdb4c..8376d5a842a2fc73170e20cbc6275fbeaf61cf5c 100644 (file)
@@ -11,3 +11,9 @@ export function describe(_name: string, _fn: () => void): void
 export function expectType<T>(value: T): void
 export function expectError<T>(value: T): void
 export function expectAssignable<T, T2 extends T = T>(value: T2): void
+
+export type IsUnion<T, U extends T = T> = (T extends any
+  ? (U extends T ? false : true)
+  : never) extends false
+  ? false
+  : true