safety-match
Version:
`safety-match` provides pattern matching for JavaScript, TypeScript, and Flow.
207 lines (175 loc) • 6.54 kB
Flow
// @flow
/* #region Utility Types */
// We have to copy stuff out of flown because other projects that install us
// can't resolve a flown import in our codebase, even if they install flown.
/* #region `Is` from flown */
declare function is<A, B: A>(
...args: [A, B]
): $Call<((B) => true) & (() => false), A>;
declare function is(): false;
type _Is = typeof is;
type Is<A, B> = $Call<_Is, A, B>;
/* #endregion */
/* #region `IsCompat` from flown */
declare function isCompat<A, B: A>(...args: [A, B]): true;
declare function isCompat(): false;
type _IsCompat = typeof isCompat;
type IsCompat<A, B> = $Call<_IsCompat, A, B>;
/* #endregion */
/* #region $If from https://gist.github.com/miyaokamarina/934887ac2aff863b9c73283acfb71cf0 */
type If<X: boolean, Then, Else = empty> = $Call<
((true, Then, Else) => Then) & ((false, Then, Else) => Else),
X,
Then,
Else
>;
/* #endregion */
/* #region Arity checkers by me */
type FuncWith1Arg = <A>(A) => any;
type FuncWith2Args = <A, B>(A, B) => any;
type FuncWith3Args = <A, B, C>(A, B, C) => any;
type FuncWith4Args = <A, B, C, D>(A, B, C, D) => any;
type FuncWith5Args = <A, B, C, D, E>(A, B, C, D, E) => any;
/* #endregion */
/* #region Parameter extractors by me */
type Arg1of1<SomeFunc> = $Call<<A, F: (A) => any>(F) => A & A, SomeFunc>;
type Arg1of2<SomeFunc> = $Call<<A, B, F: (A, B) => any>(F) => A & A, SomeFunc>;
type Arg2of2<SomeFunc> = $Call<<A, B, F: (A, B) => any>(F) => B & B, SomeFunc>;
type Arg1of3<SomeFunc> = $Call<
<A, B, C, F: (A, B, C) => any>(F) => A & A,
SomeFunc
>;
type Arg2of3<SomeFunc> = $Call<
<A, B, C, F: (A, B, C) => any>(F) => B & B,
SomeFunc
>;
type Arg3of3<SomeFunc> = $Call<
<A, B, C, F: (A, B, C) => any>(F) => C & C,
SomeFunc
>;
type Arg1of4<SomeFunc> = $Call<
<A, B, C, D, F: (A, B, C, D) => any>(F) => A & A,
SomeFunc
>;
type Arg2of4<SomeFunc> = $Call<
<A, B, C, D, F: (A, B, C, D) => any>(F) => B & B,
SomeFunc
>;
type Arg3of4<SomeFunc> = $Call<
<A, B, C, D, F: (A, B, C, D) => any>(F) => C & C,
SomeFunc
>;
type Arg4of4<SomeFunc> = $Call<
<A, B, C, D, F: (A, B, C, D) => any>(F) => D & D,
SomeFunc
>;
type Arg1of5<SomeFunc> = $Call<
<A, B, C, D, E, F: (A, B, C, D, E) => any>(F) => A & A,
SomeFunc
>;
type Arg2of5<SomeFunc> = $Call<
<A, B, C, D, E, F: (A, B, C, D, E) => any>(F) => B & B,
SomeFunc
>;
type Arg3of5<SomeFunc> = $Call<
<A, B, C, D, E, F: (A, B, C, D, E) => any>(F) => C & C,
SomeFunc
>;
type Arg4of5<SomeFunc> = $Call<
<A, B, C, D, E, F: (A, B, C, D, E) => any>(F) => D & D,
SomeFunc
>;
type Arg5of5<SomeFunc> = $Call<
<A, B, C, D, E, F: (A, B, C, D, E) => any>(F) => E & E,
SomeFunc
>;
/* #endregion */
/* #endregion */
// HACK: None is actually a Symbol, so it shouldn't be callable, but we're
// going to pretend it's a function so that getting the DataMap is just
// getting the return type of everything in the DefObj.
//
// Otherwise, we would need to use `If` everywhere to "refine" out the None
// case, but it seems like `If` doesn't always refine (couldn't get it
// working).
//
// I wanted it to be a function that returned `void`, so that `void` would be
// the data type of None (since that's what it is), but for some reason, flow
// was comparing every match arm with the return type of this function, so
// every match was failing. So I had to change it to any to make flow shut up.
// This has the unfortunate side-effect that the .data property on a tagged
// union member now has to be any (because a union of anything with any
// behaves just like any). But at least matching works.
//
// The unfortunate side-effect of this all is that flow won't yell at you for
// calling None. So hopefully no one ever does that.
export type None = ((_: number) => any) & {
__none_this_is_only_here_for_flow_not_at_runtime_dont_use_this: true,
};
declare var none: None;
export { none };
export type MemberType<
TaggedUnionT: {
__member_type_this_is_only_here_for_flow_not_at_runtime_dont_use_this: any,
}
> = $PropertyType<
TaggedUnionT,
"__member_type_this_is_only_here_for_flow_not_at_runtime_dont_use_this"
>;
type DefObjSuper = { [key: string]: None | ((...args: any) => any) };
type ExtractReturnType = <V>((...args: any) => V) => V;
type DataMap<DefObj: DefObjSuper> = $ObjMap<DefObj, ExtractReturnType>;
type DataForKey<Key, DefObj: DefObjSuper> = $ElementType<DataMap<DefObj>, Key>;
type CasesObjFull<DefObj: DefObjSuper, Ret> = $ObjMapi<
DefObj,
<K, V>(K, V) => (data: DataForKey<K, DefObj>) => Ret
>;
type Partial<T> = $Rest<T, {}>;
type CasesObjPartialWithDefault<DefObj: DefObjSuper, Ret> = {|
...Partial<CasesObjFull<DefObj, Ret>>,
_: (data: any) => Ret,
|};
interface TaggedUnionMember<DefObj: DefObjSuper> {
match: <
Ret,
// The object you pass in to match must either include a key for every tag
// in the tagged union, or include the key `_` to handle all unspecified tags.
CasesObj:
| CasesObjFull<DefObj, Ret>
| CasesObjPartialWithDefault<DefObj, Ret>
>(
cases: CasesObj
) => Ret;
variant: $Keys<DefObj>;
data: any;
}
opaque type TAGGED_UNIONS_WHOSE_CONSTRUCTORS_TAKE_LESS_THAN_ONE_OR_MORE_THAN_5_ARGUMENTS_ARE_NOT_SUPPORTED_WITH_FLOW = (
...args: any
) => any;
// prettier-ignore
type DefObjToTaggedUnionMapper<DefObj: DefObjSuper> = <V>(V) =>
If<Is<V, None>,
TaggedUnionMember<DefObj>,
If<IsCompat<V, FuncWith1Arg>,
(arg1: Arg1of1<V>) => TaggedUnionMember<DefObj>,
If<IsCompat<V, FuncWith2Args>,
(arg1: Arg1of2<V>, arg2: Arg2of2<V>) => TaggedUnionMember<DefObj>,
If<IsCompat<V, FuncWith3Args>,
(arg1: Arg1of3<V>, arg2: Arg2of3<V>, arg3: Arg3of3<V>) => TaggedUnionMember<DefObj>,
If<IsCompat<V, FuncWith4Args>,
(arg1: Arg1of4<V>, arg2: Arg2of4<V>, arg3: Arg3of4<V>, arg4: Arg4of4<V>) => TaggedUnionMember<DefObj>,
If<IsCompat<V, FuncWith5Args>,
(arg1: Arg1of5<V>, arg2: Arg2of5<V>, arg3: Arg3of5<V>, arg4: Arg4of5<V>, arg5: Arg5of5<V>) => TaggedUnionMember<DefObj>,
TAGGED_UNIONS_WHOSE_CONSTRUCTORS_TAKE_LESS_THAN_ONE_OR_MORE_THAN_5_ARGUMENTS_ARE_NOT_SUPPORTED_WITH_FLOW,
>
>
>
>
>
>;
export type TaggedUnion<DefObj: DefObjSuper> = {
...$ObjMap<DefObj, DefObjToTaggedUnionMapper<DefObj>>,
__member_type_this_is_only_here_for_flow_not_at_runtime_dont_use_this: TaggedUnionMember<DefObj>,
};
declare function makeTaggedUnion<T: DefObjSuper>(defObj: T): TaggedUnion<T>;
export { makeTaggedUnion };