From 4ddbff0bb4e3ffddfeb509c59835b83245fb975e Mon Sep 17 00:00:00 2001 From: Maxim Khramtsov Date: Mon, 29 Jul 2024 11:35:30 +0200 Subject: [PATCH] Support `Refinement` in `Preficate.tuple` and `Predicate.struct` (#3366) Co-authored-by: maksim.khramtsov --- .changeset/purple-onions-drive.md | 5 ++ packages/effect/dtslint/Predicate.ts | 48 ++++++++++++++++++ packages/effect/src/Predicate.ts | 76 +++++++++++++++++++++++++--- 3 files changed, 121 insertions(+), 8 deletions(-) create mode 100644 .changeset/purple-onions-drive.md diff --git a/.changeset/purple-onions-drive.md b/.changeset/purple-onions-drive.md new file mode 100644 index 0000000000..7e948c5304 --- /dev/null +++ b/.changeset/purple-onions-drive.md @@ -0,0 +1,5 @@ +--- +"effect": minor +--- + +Support `Refinement` in `Predicate.tuple` and `Predicate.struct` diff --git a/packages/effect/dtslint/Predicate.ts b/packages/effect/dtslint/Predicate.ts index 4d8a9a12c3..d7d3a4db25 100644 --- a/packages/effect/dtslint/Predicate.ts +++ b/packages/effect/dtslint/Predicate.ts @@ -271,3 +271,51 @@ pipe(Predicate.isString, Predicate.or(Predicate.isNumber)) // $ExpectType Refinement Predicate.or(Predicate.isString, Predicate.isNumber) + +// ------------------------------------------------------------------------------------- +// tuple +// ------------------------------------------------------------------------------------- + +const isA = hole>() +const isTrue = hole>() +const isOdd = hole>() + +// $ExpectType Refinement +Predicate.tuple(isTrue, isA) + +// $ExpectType Refinement +Predicate.tuple(isTrue, isOdd) + +// $ExpectType Predicate +Predicate.tuple(isOdd, isOdd) + +// $ExpectType Predicate +Predicate.tuple(...hole>>()) + +// $ExpectType Refinement +Predicate.tuple(...hole | Predicate.Refinement>>()) + +// $ExpectType Refinement +Predicate.tuple(...hole>>()) + +// ------------------------------------------------------------------------------------- +// struct +// ------------------------------------------------------------------------------------- + +// $ExpectType Refinement<{ readonly a: string; readonly true: boolean; }, { readonly a: "a"; readonly true: true; }> +Predicate.struct({ + a: isA, + true: isTrue +}) + +// $ExpectType Refinement<{ readonly odd: number; readonly true: boolean; }, { readonly odd: number; readonly true: true; }> +Predicate.struct({ + odd: isOdd, + true: isTrue +}) + +// $ExpectType Predicate<{ readonly odd: number; readonly odd1: number; }> +Predicate.struct({ + odd: isOdd, + odd1: isOdd +}) diff --git a/packages/effect/src/Predicate.ts b/packages/effect/src/Predicate.ts index 2d79c29d47..1646a1a9c3 100644 --- a/packages/effect/src/Predicate.ts +++ b/packages/effect/src/Predicate.ts @@ -29,6 +29,45 @@ export interface Refinement { (a: A): a is B } +/** + * @since 3.6.0 + * @category type-level + */ +export declare namespace Predicate { + /** + * @since 3.6.0 + * @category type-level + */ + export type In = [T] extends [Predicate] ? _A : never + /** + * @since 3.6.0 + * @category type-level + */ + export type Any = Predicate +} + +/** + * @since 3.6.0 + * @category type-level + */ +export declare namespace Refinement { + /** + * @since 3.6.0 + * @category type-level + */ + export type In = [T] extends [Refinement] ? _A : never + /** + * @since 3.6.0 + * @category type-level + */ + export type Out = [T] extends [Refinement] ? _B : never + /** + * @since 3.6.0 + * @category type-level + */ + export type Any = Refinement +} + /** * Given a `Predicate` returns a `Predicate` * @@ -686,23 +725,44 @@ export const productMany = ( * Similar to `Promise.all` but operates on `Predicate`s. * * ``` + * [Refinement, Refinement, ...] -> Refinement<[A, C, ...], [B, D, ...]> * [Predicate, Predicate, ...] -> Predicate<[A, B, ...]> + * [Refinement, Predicate, ...] -> Refinement<[A, C, ...], [B, C, ...]> * ``` * * @since 2.0.0 */ -export const tuple = >>( - ...elements: T -): Predicate] ? A : never }>> => all(elements) as any +export const tuple: { + >( + ...elements: T + ): [Extract] extends [never] ? Predicate<{ readonly [I in keyof T]: Predicate.In }> + : Refinement< + { readonly [I in keyof T]: T[I] extends Refinement.Any ? Refinement.In : Predicate.In }, + { readonly [I in keyof T]: T[I] extends Refinement.Any ? Refinement.Out : Predicate.In } + > +} = (...elements: ReadonlyArray) => all(elements) as any /** + * ``` + * { ab: Refinement; cd: Refinement, ... } -> Refinement<{ ab: A; cd: C; ... }, { ab: B; cd: D; ... }> + * { a: Predicate; b: Predicate, ... } -> Predicate<{ a: A; b: B; ... }> + * { ab: Refinement; c: Predicate, ... } -> Refinement<{ ab: A; c: C; ... }, { ab: B; c: ะก; ... }> + * ``` + * * @since 2.0.0 */ -export const struct = >>( - fields: R -): Predicate<{ readonly [K in keyof R]: [R[K]] extends [Predicate] ? A : never }> => { +export const struct: { + >( + fields: R + ): [Extract] extends [never] ? + Predicate<{ readonly [K in keyof R]: Predicate.In }> : + Refinement< + { readonly [K in keyof R]: R[K] extends Refinement.Any ? Refinement.In : Predicate.In }, + { readonly [K in keyof R]: R[K] extends Refinement.Any ? Refinement.Out : Predicate.In } + > +} = (>(fields: R) => { const keys = Object.keys(fields) - return (a) => { + return (a: Record) => { for (const key of keys) { if (!fields[key](a[key])) { return false @@ -710,7 +770,7 @@ export const struct = >>( } return true } -} +}) as any /** * Negates the result of a given predicate.