diff --git a/packages/runtime-core/src/componentProps.ts b/packages/runtime-core/src/componentProps.ts index f672446446b..89baa373314 100644 --- a/packages/runtime-core/src/componentProps.ts +++ b/packages/runtime-core/src/componentProps.ts @@ -60,7 +60,7 @@ type PropConstructor = | { (): T } | PropMethod -type PropMethod = T extends (...args: any) => any // if is function with args +type PropMethod = [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 = { : never }[keyof T] -type InferPropType = T extends null +type InferPropType = [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 - : 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 ? (unknown extends V ? D : V) : T + : [T] extends [Prop] + ? (unknown extends V ? D : V) + : T export type ExtractPropTypes = O extends object ? { [K in RequiredKeys]: InferPropType } & diff --git a/test-dts/defineComponent.test-d.tsx b/test-dts/defineComponent.test-d.tsx index 949654b1b9e..7a07fef2fe1 100644 --- a/test-dts/defineComponent.test-d.tsx +++ b/test-dts/defineComponent.test-d.tsx @@ -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(props.hhh) expectType(props.ggg) expectType(props.ffff) + if (typeof props.iii !== 'function') { + expectType(props.iii) + } + expectType(props.iii) + expectType>(true) + expectType(props.jjj) + expectType(props.kkk) expectType(props.validated) expectType(props.date) @@ -160,6 +181,13 @@ describe('with object props', () => { expectType(props.fff) expectType(props.hhh) expectType(props.ggg) + if (typeof props.iii !== 'function') { + expectType(props.iii) + } + expectType(props.iii) + expectType>(true) + expectType(props.jjj) + expectType(props.kkk) // @ts-expect-error props should be readonly expectError((props.a = 1)) @@ -180,6 +208,14 @@ describe('with object props', () => { expectType(this.fff) expectType(this.hhh) expectType(this.ggg) + if (typeof this.iii !== 'function') { + expectType(this.iii) + } + expectType(this.iii) + const { jjj } = this + expectType>(true) + expectType(this.jjj) + expectType(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={() => ''} /> ) diff --git a/test-dts/index.d.ts b/test-dts/index.d.ts index be3143f4773..8376d5a842a 100644 --- a/test-dts/index.d.ts +++ b/test-dts/index.d.ts @@ -11,3 +11,9 @@ export function describe(_name: string, _fn: () => void): void export function expectType(value: T): void export function expectError(value: T): void export function expectAssignable(value: T2): void + +export type IsUnion = (T extends any + ? (U extends T ? false : true) + : never) extends false + ? false + : true