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

Fixing lodash defs for flow 0.88.0+ #3020

Merged
merged 9 commits into from Jan 3, 2019
Merged

Fixing lodash defs for flow 0.88.0+ #3020

merged 9 commits into from Jan 3, 2019

Conversation

beaucollins
Copy link
Contributor

@beaucollins beaucollins commented Dec 19, 2018

Now that Function and Object are equivalent to any type many Lodash typedefs no longer work as expected.

For example this is no longer valid:

isObject(value: Object): true;
isObject(value: any): boolean;

Also, now that Function is the same as any, many of the null tests failed because with v0.88.0 and onward this:

unary(Function): Function;

becomes the same as:

unary(any): any;

We can fix some of these by defining the types a bit better. For instance this better describes what .unary actually does.

unary<T, R>((T, ...any[]) => R): (T) => R;

@@ -158,7 +158,7 @@ declare module "lodash" {

declare type NestedArray<T> = Array<Array<T>>;

declare type matchesIterateeShorthand = Object;
declare type matchesIterateeShorthand = {[id: any]: any};
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Since Object now means any we can require a very liberal indexed object definition instead.

@@ -746,8 +746,8 @@ declare module "lodash" {
shuffle<T>(array: ?Array<T>): Array<T>;
shuffle<V, T: Object>(object: T): Array<V>;
size(collection: $ReadOnlyArray<any> | Object | string): number;
some<T>(array: ?$ReadOnlyArray<T>, predicate?: Predicate<T>): boolean;
Copy link
Contributor Author

Choose a reason for hiding this comment

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

By making the null case overload first, the null case test passes successfully.

@@ -776,22 +776,22 @@ declare module "lodash" {
after(n: number, fn: Function): Function;
ary(func: Function, n?: number): Function;
before(n: number, fn: Function): Function;
bind(func: Function, thisArg: any, ...partials: Array<any>): Function;
bind<R>(func: (...any[]) => R, thisArg: any, ...partials: Array<any>): (...any[]) => R;
Copy link
Contributor Author

Choose a reason for hiding this comment

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

lodash.bind returns a partially applied function with the this set with thisArg. Since Function is now any we can instead use (...any[]) => R as the function type.

It does not seem there are any Flow utility types that will allow us to manipulate the argument types in a way that will allow us to correctly type the arguments and partials beyond the use of any.

bindKey(obj?: ?Object, key?: ?string, ...partials?: Array<?any>): Function;
curry: Curry;
curry(func: Function, arity?: number): Function;
curryRight(func: Function, arity?: number): Function;
debounce<F: Function>(func: F, wait?: number, options?: DebounceOptions): F;
defer(func: Function, ...args?: Array<any>): TimeoutID;
defer(func: (...any[]) => any, ...args?: Array<any>): TimeoutID;
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Since Function is now any type, (...any[]) => any will at least require func to be a function. This allows the null case to correctly raise a type error.

// $ExpectError
defer(null);

flip<R>(func: (...any[]) => R): (...any[]) => R;
memoize<A, R>(func: (...A) => R, resolver?: (...A) => mixed): (...A) => R;
negate<A, R>(predicate: (...A) => R): (...A) => boolean;
once<A, R>(func: (...A) => R): (...A) => R;
Copy link
Contributor Author

Choose a reason for hiding this comment

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

More cases where Function is being replaced with a () => {} type. Arguments and return values are made generic where possible.

partial(func: Function, ...partials: any[]): Function;
partialRight(func: Function, ...partials: Array<any>): Function;
partial<R>(func: (...any[]) => R, ...partials: any[]): (...any[]) => R;
partialRight(func: (...any[]) => any, ...partials: Array<any>): Function;
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Function is now any, this at least requires a valid function.

@@ -891,12 +891,10 @@ declare module "lodash" {
isNull(value: any): false;
isNumber(value: number): true;
isNumber(value: any): false;
isObject(value: Object): true;
isObject(value: any): false;
isObject(value: any): boolean;
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Object is now any so isObject(value: Object): true no longer works.

@@ -1066,6 +1064,7 @@ declare module "lodash" {
): Object;
at(object?: ?Object, ...paths: Array<string>): Array<any>;
at(object?: ?Object, paths: Array<string>): Array<any>;
create(prototype: void | null, properties: void | null): {};
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Explicitly adding the null case for create which allows the test to pass.

unset(object: void | null, path?: ?Array<string> | ?string): true;
unset(object: Object, path?: ?Array<string> | ?string): boolean;
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Moving the null case first allows the null test to pass.

@@ -1393,13 +1392,13 @@ declare module "lodash" {
cond(pairs?: ?NestedArray<Function>): Function;
conforms(source?: ?Object): Function;
constant<T>(value: T): () => T;
defaultTo<T1: string | boolean | Object, T2>(
defaultTo<T1: void | null, T2>(value: T1, defaultValue: T2): T2;
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Move the null case to appear first and remove Object from the non-null case since it means any.

@@ -1608,7 +1607,7 @@ declare module "lodash/fp" {

declare type NestedArray<T> = Array<Array<T>>;

declare type matchesIterateeShorthand = Object;
declare type matchesIterateeShorthand = {[string | number]: any};
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Now that Object means any an indexed object type more accurately describes that the matchesIterateeShorthand type can be.

@@ -2358,7 +2357,7 @@ declare module "lodash/fp" {
delay(wait: number, func: Function): TimeoutID;
flip(func: Function): Function;
memoize<F: Function>(func: F): F;
negate(predicate: Function): Function;
negate<A, R>(predicate: (...A) => R): (...A) => R;
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Should be:

Suggested change
negate<A, R>(predicate: (...A) => R): (...A) => R;
negate<A, R>(predicate: (...A) => R): (...A) => boolean;

@@ -3154,18 +3152,18 @@ declare module "lodash/fp" {
cond(pairs: NestedArray<Function>): Function;
constant<T>(value: T): () => T;
always<T>(value: T): () => T;
defaultTo<T1: string | boolean | Object, T2>(
defaultTo<T1: void | null, T2>(defaultValue: T2): (value: T1) => T2;
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Place the null cases first and remove Object since it means any.

@@ -106,9 +106,6 @@ opaque type O = string;
const v: { [O]: number } = { x: 1, y: 2 };
find(v, { x: 3 });

// $ExpectError undefined. This type is incompatible with object type.
var result: Object = find(users, "active");
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Since Object now means any this will never cause an error.

@villesau
Copy link
Member

@beaucollins what's the status of this? Good to go from your side?

@villesau
Copy link
Member

@beaucollins some feedback from quick test:
rather than debounce<A, R>(func: (...A) => R, wait?: number, options?: DebounceOptions): (...A) => R;

this seems to work better:

debounce<F: (...any[]) => any>(func: F, wait?: number, options?: DebounceOptions): F

Same apply for others where function is restructured. Rather have it as a single type. That way Flow does not require type parameters.

@beaucollins
Copy link
Contributor Author

@villesau perhaps do both. I believe the typed parameters and return values should carry through and then it can fall back to the F: (...any[]) => any) one.

@villesau
Copy link
Member

@beaucollins F: (...any[]) => any) carries trough. F is type parameter that carries the type, and : (...any[]) => any) restricts it. It carries the function parameters and return value, so it does everything you would expect (...A) => R to do. Is there some reason why you would want to use (...A) => R rather than single type parameter for the function?

@beaucollins
Copy link
Contributor Author

@villesau you're suggestion is implemented. These changes should be "good enough" to keep us going.

There are a lot of definitions in this library that still use Function and Object so there's more work to do but I'll leave them for future PR's.

@villesau villesau merged commit 0c27d3b into flow-typed:master Jan 3, 2019
@villesau
Copy link
Member

villesau commented Jan 3, 2019

Nice, thanks for the contribution @beaucollins !

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants