Skip to content

Commit

Permalink
fix(types): union function prop (#3119)
Browse files Browse the repository at this point in the history
fix #3357
  • Loading branch information
07akioni authored Mar 25, 2021
1 parent 41e02f0 commit 3755e60
Show file tree
Hide file tree
Showing 3 changed files with 53 additions and 7 deletions.
16 changes: 9 additions & 7 deletions packages/runtime-core/src/componentProps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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]> } &
Expand Down
38 changes: 38 additions & 0 deletions test-dts/defineComponent.test-d.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
ComponentPublicInstance,
ComponentOptions,
SetupContext,
IsUnion,
h
} from './index'

Expand All @@ -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
}
Expand Down Expand Up @@ -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
Expand All @@ -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)

Expand Down Expand Up @@ -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))
Expand All @@ -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))
Expand Down Expand Up @@ -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' }}
Expand All @@ -232,6 +269,7 @@ describe('with object props', () => {
eee={() => ({ a: 'eee' })}
fff={(a, b) => ({ a: a > +b })}
hhh={false}
jjj={() => ''}
/>
)

Expand Down
6 changes: 6 additions & 0 deletions test-dts/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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

0 comments on commit 3755e60

Please sign in to comment.