UNPKG

safety-match

Version:

`safety-match` provides pattern matching for JavaScript, TypeScript, and Flow.

207 lines (175 loc) 6.54 kB
// @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 };