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

Add branch awareness to TypeScript types (strict) #438

Draft
wants to merge 7 commits into
base: master
Choose a base branch
from
Draft
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
82 changes: 70 additions & 12 deletions index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,33 @@ export interface Functor<A> {

type Mapped<F extends Functor<unknown>, B> = ReturnType<(F & { [$T]: B })['fantasy-land/map']>

export type ConcurrentNever = ConcurrentFutureInstance<never, never>;

export type ConcurrentRejected<T> = ConcurrentFutureInstance<T, never>;

export type ConcurrentResolved<T> = ConcurrentFutureInstance<never, T>;

export type ConcurrentUncertain<L, R> = ConcurrentFutureInstance<L, R>;

export type AnyConcurrent = ConcurrentFutureInstance<unknown, unknown>;

export interface ConcurrentFutureInstance<L, R> extends Functor<R> {
sequential: FutureInstance<L, R>
'fantasy-land/ap'<A, B>(this: ConcurrentFutureInstance<L, (value: A) => B>, right: ConcurrentFutureInstance<L, A>): ConcurrentFutureInstance<L, B>
'fantasy-land/map'<RB extends this[typeof $T]>(mapper: (value: R) => RB): ConcurrentFutureInstance<L, RB>
'fantasy-land/alt'(right: ConcurrentFutureInstance<L, R>): ConcurrentFutureInstance<L, R>
}

export type Never = FutureInstance<never, never>;

export type Rejected<T> = FutureInstance<T, never>;

export type Resolved<T> = FutureInstance<never, T>;

export type Uncertain<L, R> = FutureInstance<L, R>;

export type AnyFuture = FutureInstance<unknown, unknown>;

export interface FutureInstance<L, R> extends Functor<R> {

/** The Future constructor */
Expand All @@ -60,16 +80,54 @@ export interface FutureInstance<L, R> extends Functor<R> {
}

/** Creates a Future which resolves after the given duration with the given value. See https://github.com/fluture-js/Fluture#after */
export function after(duration: number): <R>(value: R) => FutureInstance<never, R>
export function after(duration: number): <R>(value: R) => Resolved<R>

/** Logical and for Futures. See https://github.com/fluture-js/Fluture#and */
export function and<L, R>(left: FutureInstance<L, R>): (right: FutureInstance<L, any>) => FutureInstance<L, R>
export const and: {
<F extends AnyFuture, S extends AnyFuture>(second: F extends Never ? S : never): (first: F) => Never
<F extends AnyFuture, S extends AnyFuture>(second: F extends Resolved<unknown> ? S : never): (first: F) => S
<F extends AnyFuture, S extends AnyFuture>(second: F extends Rejected<unknown> ? S : never): (first: F) => F

<L, R>(second: Uncertain<L, R>): {
<T>(first: Rejected<T>): Rejected<T>
(first: Resolved<any>): Uncertain<L, R>
(first: Uncertain<L, any>): Uncertain<L, R>
}
}

/** Logical or for Futures. See https://github.com/fluture-js/Fluture#alt */
export function alt<L, R>(left: FutureInstance<L, R>): (right: FutureInstance<L, R>) => FutureInstance<L, R>

/** Race two ConcurrentFutures. See https://github.com/fluture-js/Fluture#alt */
export function alt<L, R>(left: ConcurrentFutureInstance<L, R>): (right: ConcurrentFutureInstance<L, R>) => ConcurrentFutureInstance<L, R>
export const alt: {
<F extends AnyFuture, S extends AnyFuture>(second: F extends Never ? S : never): (first: F) => Never
<F extends AnyFuture, S extends AnyFuture>(second: F extends Rejected<unknown> ? S : never): (first: F) => S
<F extends AnyFuture, S extends AnyFuture>(second: F extends Resolved<unknown> ? S : never): (first: F) => F

<L>(second: Rejected<L>): {
(first: Never): Never
(first: Rejected<any>): Rejected<L>
<R>(first: Resolved<R>): Resolved<R>
<R>(first: Uncertain<any, R>): Uncertain<L, R>
}

<L, R>(second: Uncertain<L, R>): {
<T>(first: Resolved<T>): Resolved<T>
(first: Rejected<any>): Uncertain<L, R>
(first: Uncertain<any, R>): Uncertain<L, R>
}

(second: ConcurrentNever): <L, R>(first: ConcurrentUncertain<L, R>) => ConcurrentUncertain<L, R>

<L>(second: ConcurrentRejected<L>): {
<R>(first: ConcurrentResolved<R>): ConcurrentUncertain<L, R>
<R>(first: ConcurrentUncertain<L, R>): ConcurrentUncertain<L, R>
}

<R>(second: ConcurrentResolved<R>): {
<L>(first: ConcurrentRejected<L>): ConcurrentUncertain<L, R>
<L>(first: ConcurrentUncertain<L, R>): ConcurrentUncertain<L, R>
}

<L, R>(second: ConcurrentUncertain<L, R>): (first: ConcurrentUncertain<L, R>) => ConcurrentUncertain<L, R>
}

/** Apply the function in the right Future to the value in the left Future. See https://github.com/fluture-js/Fluture#ap */
export function ap<L, RA>(value: FutureInstance<L, RA>): <RB>(apply: FutureInstance<L, (value: RA) => RB>) => FutureInstance<L, RB>
Expand Down Expand Up @@ -120,7 +178,7 @@ export function extractLeft<L, R>(source: FutureInstance<L, R>): Array<L>
export function extractRight<L, R>(source: FutureInstance<L, R>): Array<R>

/** Coalesce both branches into the resolution branch. See https://github.com/fluture-js/Fluture#coalesce */
export function coalesce<LA, R>(lmapper: (left: LA) => R): <RA>(rmapper: (right: RA) => R) => (source: FutureInstance<LA, RA>) => FutureInstance<never, R>
export function coalesce<LA, R>(lmapper: (left: LA) => R): <RA>(rmapper: (right: RA) => R) => (source: FutureInstance<LA, RA>) => Resolved<R>

/** Fork the given Future into the given continuations. See https://github.com/fluture-js/Fluture#fork */
export function fork<L>(reject: RejectFunction<L>): <R>(resolve: ResolveFunction<R>) => (source: FutureInstance<L, R>) => Cancel
Expand Down Expand Up @@ -153,13 +211,13 @@ export const map: {
export function mapRej<LA, LB>(mapper: (reason: LA) => LB): <R>(source: FutureInstance<LA, R>) => FutureInstance<LB, R>

/** A Future that never settles. See https://github.com/fluture-js/Fluture#never */
export var never: FutureInstance<never, never>
export var never: Never

/** Create a Future using a provided Node-style callback. See https://github.com/fluture-js/Fluture#node */
export function node<L, R>(fn: (done: Nodeback<L, R>) => void): FutureInstance<L, R>

/** Create a Future with the given resolution value. See https://github.com/fluture-js/Fluture#of */
export function resolve<R>(value: R): FutureInstance<never, R>
export function resolve<R>(value: R): Resolved<R>

/** Run an Array of Futures in parallel, under the given concurrency limit. See https://github.com/fluture-js/Fluture#parallel */
export function parallel(concurrency: number): <L, R>(futures: Array<FutureInstance<L, R>>) => FutureInstance<L, Array<R>>
Expand All @@ -171,10 +229,10 @@ export function promise<R>(source: FutureInstance<Error, R>): Promise<R>
export function race<L, R>(left: FutureInstance<L, R>): (right: FutureInstance<L, R>) => FutureInstance<L, R>

/** Create a Future with the given rejection reason. See https://github.com/fluture-js/Fluture#reject */
export function reject<L>(reason: L): FutureInstance<L, never>
export function reject<L>(reason: L): Rejected<L>

/** Creates a Future which rejects after the given duration with the given reason. See https://github.com/fluture-js/Fluture#rejectafter */
export function rejectAfter(duration: number): <L>(reason: L) => FutureInstance<L, never>
export function rejectAfter(duration: number): <L>(reason: L) => Rejected<L>

/** Convert a ConcurrentFuture to a regular Future. See https://github.com/fluture-js/Fluture#concurrentfuture */
export function seq<L, R>(source: ConcurrentFutureInstance<L, R>): FutureInstance<L, R>
Expand All @@ -183,7 +241,7 @@ export function seq<L, R>(source: ConcurrentFutureInstance<L, R>): FutureInstanc
export function swap<L, R>(source: FutureInstance<L, R>): FutureInstance<R, L>

/** Fork the Future into the given continuation. See https://github.com/fluture-js/Fluture#value */
export function value<R>(resolve: ResolveFunction<R>): (source: FutureInstance<never, R>) => Cancel
export function value<R>(resolve: ResolveFunction<R>): (source: Resolved<R>) => Cancel

/** Enable or disable debug mode. See https://github.com/fluture-js/Fluture#debugmode */
export function debugMode(debug: boolean): void;
Expand Down
9 changes: 9 additions & 0 deletions test/types/after.test-d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import {expectType} from 'tsd';

import * as fl from '../../index.js';

expectType<fl.Resolved<number>> (fl.after (1) (42));
expectType<fl.Resolved<string>> (fl.after (1) ('a'));

// https://github.com/microsoft/TypeScript/issues/32277
// expectType<fl.Never> (fl.after (Infinity) ('Finally!'));
54 changes: 54 additions & 0 deletions test/types/alt.test-d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import {expectType, expectError} from 'tsd';

import * as fl from '../../index.js';

const fsn: fl.FutureInstance<string, number> = fl.resolve (42);
const fns: fl.FutureInstance<number, string> = fl.resolve ('a');

// Standard usage on Future instances.
expectType<fl.Never> (fl.alt (fl.never) (fl.never));
expectType<fl.Never> (fl.alt (fl.reject ('a')) (fl.never));
expectType<fl.Never> (fl.alt (fl.resolve ('a')) (fl.never));
expectType<fl.Never> (fl.alt (fl.never) (fl.reject ('a')));
expectType<fl.Resolved<string>> (fl.alt (fl.never) (fl.resolve ('a')));
expectType<fl.Resolved<number>> (fl.alt (fl.reject ('a')) (fl.resolve (42)));
expectType<fl.Resolved<number>> (fl.alt (fl.resolve (42)) (fl.reject ('a')));
expectType<fl.Resolved<number>> (fl.alt (fl.resolve (42)) (fl.resolve (42)));
expectType<fl.Rejected<number>> (fl.alt (fl.reject (42)) (fl.reject (42)));
expectType<fl.Rejected<string>> (fl.alt (fl.reject ('a')) (fl.reject (42)));
expectType<fl.Uncertain<string, number>> (fl.alt (fsn) (fsn));
expectType<fl.Resolved<number>> (fl.alt (fl.resolve ('a')) (fl.resolve (42)));
expectError (fl.alt (fsn) (fns));

// Usage with pipe on Future instances (https://git.io/JLx3F).
expectType<fl.Never> ((fl.never) .pipe (fl.alt (fl.never)));
expectType<fl.Never> ((fl.never) .pipe (fl.alt (fl.reject ('a'))));
expectType<fl.Never> ((fl.never) .pipe (fl.alt (fl.resolve ('a'))));
expectType<fl.Never> ((fl.reject ('a')) .pipe (fl.alt (fl.never)));
expectType<fl.Resolved<string>> ((fl.resolve ('a')) .pipe (fl.alt (fl.never)));
expectType<fl.Resolved<number>> ((fl.resolve (42)) .pipe (fl.alt (fl.reject ('a'))));
expectType<fl.Resolved<number>> ((fl.reject ('a')) .pipe (fl.alt (fl.resolve (42))));
expectType<fl.Resolved<number>> ((fl.resolve (42)) .pipe (fl.alt (fl.resolve (42))));
expectType<fl.Rejected<number>> ((fl.reject (42)) .pipe (fl.alt (fl.reject (42))));
expectType<fl.Rejected<string>> ((fl.reject (42)) .pipe (fl.alt (fl.reject ('a'))));
expectType<fl.Uncertain<string, number>> ((fsn) .pipe (fl.alt (fsn)));
expectType<fl.Resolved<number>> ((fl.resolve (42)) .pipe (fl.alt (fl.resolve ('a'))));
expectError ((fns) .pipe (fl.alt (fsn)));

const csn: fl.ConcurrentFutureInstance<string, number> = fl.Par (fl.resolve (42));
const cns: fl.ConcurrentFutureInstance<number, string> = fl.Par (fl.resolve ('a'));

// Standard usage on ConcurrentFuture instances.
expectType<fl.ConcurrentNever> (fl.alt (fl.Par (fl.never)) (fl.Par (fl.never)));
expectType<fl.ConcurrentRejected<string>> (fl.alt (fl.Par (fl.reject ('a'))) (fl.Par (fl.never)));
expectType<fl.ConcurrentResolved<string>> (fl.alt (fl.Par (fl.resolve ('a'))) (fl.Par (fl.never)));
expectType<fl.ConcurrentRejected<string>> (fl.alt (fl.Par (fl.never)) (fl.Par (fl.reject ('a'))));
expectType<fl.ConcurrentResolved<string>> (fl.alt (fl.Par (fl.never)) (fl.Par (fl.resolve ('a'))));
expectType<fl.ConcurrentFutureInstance<string, number>> (fl.alt (fl.Par (fl.reject ('a'))) (fl.Par (fl.resolve (42))));
expectType<fl.ConcurrentFutureInstance<string, number>> (fl.alt (fl.Par (fl.resolve (42))) (fl.Par (fl.reject ('a'))));
expectType<fl.ConcurrentResolved<number>> (fl.alt (fl.Par (fl.resolve (42))) (fl.Par (fl.resolve (42))));
expectType<fl.ConcurrentRejected<number>> (fl.alt (fl.Par (fl.reject (42))) (fl.Par (fl.reject (42))));
expectType<fl.ConcurrentUncertain<string, number>> (fl.alt (csn) (csn));
expectError (fl.alt (fl.Par (fl.resolve ('a'))) (fl.Par (fl.resolve (42))));
expectError (fl.alt (fl.Par (fl.reject ('a'))) (fl.Par (fl.reject (42))));
expectError (fl.alt (csn) (cns));
34 changes: 34 additions & 0 deletions test/types/and.test-d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import {expectType, expectError} from 'tsd';

import * as fl from '../../index.js';

const fsn: fl.Uncertain<string, number> = fl.resolve (42);
const fns: fl.Uncertain<number, string> = fl.resolve ('a');

// Standard usage on Future instances.
expectType<fl.Never> (fl.and (fl.never) (fl.never));
expectType<fl.Never> (fl.and (fl.never) (fl.resolve ('a')));
expectType<fl.Never> (fl.and (fl.reject ('a')) (fl.never));
expectType<fl.Never> (fl.and (fl.resolve ('a')) (fl.never));
expectType<fl.Rejected<string>> (fl.and (fl.reject ('a')) (fl.resolve (42)));
expectType<fl.Resolved<number>> (fl.and (fl.resolve (42)) (fl.resolve (42)));
expectType<fl.Rejected<number>> (fl.and (fl.reject (42)) (fl.reject (42)));
expectType<fl.Uncertain<string, number>> (fl.and (fsn) (fsn));
expectType<fl.Rejected<string>> (fl.and (fl.never) (fl.reject ('a')));
expectType<fl.Rejected<string>> (fl.and (fl.resolve (42)) (fl.reject ('a')));
expectType<fl.Rejected<number>> (fl.and (fl.reject ('a')) (fl.reject (42)));
expectError (fl.and (fsn) (fns));

// Usage with pipe on Future instances (https://git.io/JLx3F).
expectType<fl.Never> ((fl.never) .pipe (fl.and (fl.never)));
const workaround = (fl.resolve ('a')) .pipe (fl.and (fl.never)); expectType<fl.Never> (workaround);
expectType<fl.Never> ((fl.never) .pipe (fl.and (fl.reject ('a'))));
expectType<fl.Never> ((fl.never) .pipe (fl.and (fl.resolve ('a'))));
expectType<fl.Rejected<string>> ((fl.resolve (42)) .pipe (fl.and (fl.reject ('a'))));
expectType<fl.Resolved<number>> ((fl.resolve (42)) .pipe (fl.and (fl.resolve (42))));
expectType<fl.Rejected<number>> ((fl.reject (42)) .pipe (fl.and (fl.reject (42))));
expectType<fl.Uncertain<string, number>> ((fsn) .pipe (fl.and (fsn)));
expectType<fl.Rejected<string>> ((fl.reject ('a')) .pipe (fl.and (fl.never)));
expectType<fl.Rejected<string>> ((fl.reject ('a')) .pipe (fl.and (fl.resolve (42))));
expectType<fl.Rejected<number>> ((fl.reject (42)) .pipe (fl.and (fl.reject ('a'))));
expectError ((fns) .pipe (fl.and (fsn)));