Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(types): fix function prop type inference #11223

Open
wants to merge 1 commit into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions types/options.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -150,12 +150,12 @@ export type PropType<T> = Prop<T> | Prop<T>[];

export type PropValidator<T> = PropOptions<T> | PropType<T>;

export interface PropOptions<T=any> {
type?: PropType<T>;
export type PropOptions<T=any> = (<TypeAsAccessed extends T, TypeAsDefined extends PropType<T>>() => {
type?: TypeAsDefined;
required?: boolean;
default?: T | null | undefined | (() => T | null | undefined);
validator?(value: T): boolean;
}
default?: TypeAsDefined extends PropType<(...args: any[]) => any> ? TypeAsAccessed | null | undefined : TypeAsAccessed | null | undefined | (() => TypeAsAccessed | null | undefined);
validator?(value: TypeAsAccessed): boolean;
}) extends () => infer Opts ? Opts : never;

export type RecordPropsDefinition<T> = {
[K in keyof T]: PropValidator<T[K]>
Expand Down
41 changes: 40 additions & 1 deletion types/test/vue-test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import Vue, { VNode } from "../index";
import { ComponentOptions } from "../options";
import { ComponentOptions, PropType } from "../options";

class Test extends Vue {
a: number = 0;
Expand Down Expand Up @@ -154,6 +154,45 @@ const FunctionalScopedSlotsComponent = Vue.extend({
}
});

declare function assertBoolean<A>(value: string extends A ? never : (A extends boolean ? A : never)): true;
Copy link
Author

@kjleitz kjleitz Mar 20, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To explain a little: calling assertBoolean(value) will only compile if value is precisely boolean, and not if value is boolean | AnotherType, nor if value is any (the latter is why ...lue: string extends A ? nev... is there—it's just a way to throw if value is any).


declare const val1: boolean;
// declare const val2: boolean | (() => boolean);
// declare const val3: any;

assertBoolean(val1); //=> compiles (good)
// assertBoolean(val2); //=> does not compile (good)
// assertBoolean(val3); //=> does not compile (good)

const ComponentWithFunctionProps = Vue.extend({
props: {
functionProp: {
type: Function,
default: () => true,
},
functionPropWithBooleanReturnType: {
type: Function as PropType<() => boolean>,
default: () => true,
},
booleanProp: {
type: Boolean,
default: true,
},
booleanPropWithFunctionDefault: {
type: Boolean,
default: () => true,
},
},
methods: {
test(): void {
this.functionProp(); // callable (good)
assertBoolean(this.functionPropWithBooleanReturnType())
Comment on lines +188 to +189
Copy link
Author

@kjleitz kjleitz Mar 20, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For reference...

Before the change, these lines were failing with:

this.functionProp();

(property) functionProp: boolean | Function
This expression is not callable.
No constituent of type 'boolean | Function' is callable. ts(2349)

assertBoolean(this.functionPropWithBooleanReturnType());

(property) functionPropWithBooleanReturnType: boolean | (() => boolean)
This expression is not callable.
Not all constituents of type 'boolean | (() => boolean)' are callable.
Type 'false' has no call signatures. ts(2349)

assertBoolean(this.booleanProp);
assertBoolean(this.booleanPropWithFunctionDefault);
},
},
});

const Parent = Vue.extend({
data() {
return { greeting: 'Hello' }
Expand Down