Skip to content

Commit 3755e60

Browse files
authored
fix(types): union function prop (#3119)
fix #3357
1 parent 41e02f0 commit 3755e60

File tree

3 files changed

+53
-7
lines changed

3 files changed

+53
-7
lines changed

packages/runtime-core/src/componentProps.ts

+9-7
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ type PropConstructor<T = any> =
6060
| { (): T }
6161
| PropMethod<T>
6262

63-
type PropMethod<T, TConstructor = any> = T extends (...args: any) => any // if is function with args
63+
type PropMethod<T, TConstructor = any> = [T] extends [(...args: any) => any] // if is function with args
6464
? { new (): TConstructor; (): T; readonly prototype: TConstructor } // Create Function like constructor
6565
: never
6666

@@ -89,17 +89,19 @@ type DefaultKeys<T> = {
8989
: never
9090
}[keyof T]
9191

92-
type InferPropType<T> = T extends null
92+
type InferPropType<T> = [T] extends [null]
9393
? any // null & true would fail to infer
94-
: T extends { type: null | true }
94+
: [T] extends [{ type: null | true }]
9595
? 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`
96-
: T extends ObjectConstructor | { type: ObjectConstructor }
96+
: [T] extends [ObjectConstructor | { type: ObjectConstructor }]
9797
? Record<string, any>
98-
: T extends BooleanConstructor | { type: BooleanConstructor }
98+
: [T] extends [BooleanConstructor | { type: BooleanConstructor }]
9999
? boolean
100-
: T extends DateConstructor | { type: DateConstructor }
100+
: [T] extends [DateConstructor | { type: DateConstructor }]
101101
? Date
102-
: T extends Prop<infer V, infer D> ? (unknown extends V ? D : V) : T
102+
: [T] extends [Prop<infer V, infer D>]
103+
? (unknown extends V ? D : V)
104+
: T
103105

104106
export type ExtractPropTypes<O> = O extends object
105107
? { [K in RequiredKeys<O>]: InferPropType<O[K]> } &

test-dts/defineComponent.test-d.tsx

+38
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {
1111
ComponentPublicInstance,
1212
ComponentOptions,
1313
SetupContext,
14+
IsUnion,
1415
h
1516
} from './index'
1617

@@ -33,6 +34,9 @@ describe('with object props', () => {
3334
hhh: boolean
3435
ggg: 'foo' | 'bar'
3536
ffff: (a: number, b: string) => { a: boolean }
37+
iii?: (() => string) | (() => number)
38+
jjj: ((arg1: string) => string) | ((arg1: string, arg2: string) => string)
39+
kkk?: any
3640
validated?: string
3741
date?: Date
3842
}
@@ -100,6 +104,16 @@ describe('with object props', () => {
100104
type: Function as PropType<(a: number, b: string) => { a: boolean }>,
101105
default: (a: number, b: string) => ({ a: a > +b })
102106
},
107+
// union + function with different return types
108+
iii: Function as PropType<(() => string) | (() => number)>,
109+
// union + function with different args & same return type
110+
jjj: {
111+
type: Function as PropType<
112+
((arg1: string) => string) | ((arg1: string, arg2: string) => string)
113+
>,
114+
required: true
115+
},
116+
kkk: null,
103117
validated: {
104118
type: String,
105119
// validator requires explicit annotation
@@ -126,6 +140,13 @@ describe('with object props', () => {
126140
expectType<ExpectedProps['hhh']>(props.hhh)
127141
expectType<ExpectedProps['ggg']>(props.ggg)
128142
expectType<ExpectedProps['ffff']>(props.ffff)
143+
if (typeof props.iii !== 'function') {
144+
expectType<undefined>(props.iii)
145+
}
146+
expectType<ExpectedProps['iii']>(props.iii)
147+
expectType<IsUnion<typeof props.jjj>>(true)
148+
expectType<ExpectedProps['jjj']>(props.jjj)
149+
expectType<ExpectedProps['kkk']>(props.kkk)
129150
expectType<ExpectedProps['validated']>(props.validated)
130151
expectType<ExpectedProps['date']>(props.date)
131152

@@ -160,6 +181,13 @@ describe('with object props', () => {
160181
expectType<ExpectedProps['fff']>(props.fff)
161182
expectType<ExpectedProps['hhh']>(props.hhh)
162183
expectType<ExpectedProps['ggg']>(props.ggg)
184+
if (typeof props.iii !== 'function') {
185+
expectType<undefined>(props.iii)
186+
}
187+
expectType<ExpectedProps['iii']>(props.iii)
188+
expectType<IsUnion<typeof props.jjj>>(true)
189+
expectType<ExpectedProps['jjj']>(props.jjj)
190+
expectType<ExpectedProps['kkk']>(props.kkk)
163191

164192
// @ts-expect-error props should be readonly
165193
expectError((props.a = 1))
@@ -180,6 +208,14 @@ describe('with object props', () => {
180208
expectType<ExpectedProps['fff']>(this.fff)
181209
expectType<ExpectedProps['hhh']>(this.hhh)
182210
expectType<ExpectedProps['ggg']>(this.ggg)
211+
if (typeof this.iii !== 'function') {
212+
expectType<undefined>(this.iii)
213+
}
214+
expectType<ExpectedProps['iii']>(this.iii)
215+
const { jjj } = this
216+
expectType<IsUnion<typeof jjj>>(true)
217+
expectType<ExpectedProps['jjj']>(this.jjj)
218+
expectType<ExpectedProps['kkk']>(this.kkk)
183219

184220
// @ts-expect-error props on `this` should be readonly
185221
expectError((this.a = 1))
@@ -214,6 +250,7 @@ describe('with object props', () => {
214250
fff={(a, b) => ({ a: a > +b })}
215251
hhh={false}
216252
ggg="foo"
253+
jjj={() => ''}
217254
// should allow class/style as attrs
218255
class="bar"
219256
style={{ color: 'red' }}
@@ -232,6 +269,7 @@ describe('with object props', () => {
232269
eee={() => ({ a: 'eee' })}
233270
fff={(a, b) => ({ a: a > +b })}
234271
hhh={false}
272+
jjj={() => ''}
235273
/>
236274
)
237275

test-dts/index.d.ts

+6
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,9 @@ export function describe(_name: string, _fn: () => void): void
1111
export function expectType<T>(value: T): void
1212
export function expectError<T>(value: T): void
1313
export function expectAssignable<T, T2 extends T = T>(value: T2): void
14+
15+
export type IsUnion<T, U extends T = T> = (T extends any
16+
? (U extends T ? false : true)
17+
: never) extends false
18+
? false
19+
: true

0 commit comments

Comments
 (0)