Skip to content

Commit

Permalink
Support Refinement in Preficate.tuple and Predicate.struct (#3366)
Browse files Browse the repository at this point in the history
Co-authored-by: maksim.khramtsov <maksim.khramtsov@btsdigital.kz>
  • Loading branch information
2 people authored and tim-smart committed Jul 30, 2024
1 parent 8135294 commit 4ddbff0
Show file tree
Hide file tree
Showing 3 changed files with 121 additions and 8 deletions.
5 changes: 5 additions & 0 deletions .changeset/purple-onions-drive.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"effect": minor
---

Support `Refinement` in `Predicate.tuple` and `Predicate.struct`
48 changes: 48 additions & 0 deletions packages/effect/dtslint/Predicate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -271,3 +271,51 @@ pipe(Predicate.isString, Predicate.or(Predicate.isNumber))

// $ExpectType Refinement<unknown, string | number>
Predicate.or(Predicate.isString, Predicate.isNumber)

// -------------------------------------------------------------------------------------
// tuple
// -------------------------------------------------------------------------------------

const isA = hole<Predicate.Refinement<string, "a">>()
const isTrue = hole<Predicate.Refinement<boolean, true>>()
const isOdd = hole<Predicate.Predicate<number>>()

// $ExpectType Refinement<readonly [boolean, string], readonly [true, "a"]>
Predicate.tuple(isTrue, isA)

// $ExpectType Refinement<readonly [boolean, number], readonly [true, number]>
Predicate.tuple(isTrue, isOdd)

// $ExpectType Predicate<readonly [number, number]>
Predicate.tuple(isOdd, isOdd)

// $ExpectType Predicate<readonly number[]>
Predicate.tuple(...hole<Array<Predicate.Predicate<number>>>())

// $ExpectType Refinement<readonly never[], readonly never[]>
Predicate.tuple(...hole<Array<Predicate.Predicate<number> | Predicate.Refinement<boolean, true>>>())

// $ExpectType Refinement<readonly boolean[], readonly true[]>
Predicate.tuple(...hole<Array<Predicate.Refinement<boolean, true>>>())

// -------------------------------------------------------------------------------------
// 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
})
76 changes: 68 additions & 8 deletions packages/effect/src/Predicate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,45 @@ export interface Refinement<in A, out B extends A> {
(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 Any> = [T] extends [Predicate<infer _A>] ? _A : never
/**
* @since 3.6.0
* @category type-level
*/
export type Any = Predicate<any>
}

/**
* @since 3.6.0
* @category type-level
*/
export declare namespace Refinement {
/**
* @since 3.6.0
* @category type-level
*/
export type In<T extends Any> = [T] extends [Refinement<infer _A, infer _>] ? _A : never
/**
* @since 3.6.0
* @category type-level
*/
export type Out<T extends Any> = [T] extends [Refinement<infer _, infer _B>] ? _B : never
/**
* @since 3.6.0
* @category type-level
*/
export type Any = Refinement<any, any>
}

/**
* Given a `Predicate<A>` returns a `Predicate<B>`
*
Expand Down Expand Up @@ -686,31 +725,52 @@ export const productMany = <A>(
* Similar to `Promise.all` but operates on `Predicate`s.
*
* ```
* [Refinement<A, B>, Refinement<C, D>, ...] -> Refinement<[A, C, ...], [B, D, ...]>
* [Predicate<A>, Predicate<B>, ...] -> Predicate<[A, B, ...]>
* [Refinement<A, B>, Predicate<C>, ...] -> Refinement<[A, C, ...], [B, C, ...]>
* ```
*
* @since 2.0.0
*/
export const tuple = <T extends ReadonlyArray<Predicate<any>>>(
...elements: T
): Predicate<Readonly<{ [I in keyof T]: [T[I]] extends [Predicate<infer A>] ? A : never }>> => all(elements) as any
export const tuple: {
<T extends ReadonlyArray<Predicate.Any>>(
...elements: T
): [Extract<T[number], Refinement.Any>] extends [never] ? Predicate<{ readonly [I in keyof T]: Predicate.In<T[I]> }>
: Refinement<
{ readonly [I in keyof T]: T[I] extends Refinement.Any ? Refinement.In<T[I]> : Predicate.In<T[I]> },
{ readonly [I in keyof T]: T[I] extends Refinement.Any ? Refinement.Out<T[I]> : Predicate.In<T[I]> }
>
} = (...elements: ReadonlyArray<Predicate.Any>) => all(elements) as any

/**
* ```
* { ab: Refinement<A, B>; cd: Refinement<C, D>, ... } -> Refinement<{ ab: A; cd: C; ... }, { ab: B; cd: D; ... }>
* { a: Predicate<A, B>; b: Predicate<B>, ... } -> Predicate<{ a: A; b: B; ... }>
* { ab: Refinement<A, B>; c: Predicate<C>, ... } -> Refinement<{ ab: A; c: C; ... }, { ab: B; c: С; ... }>
* ```
*
* @since 2.0.0
*/
export const struct = <R extends Record<string, Predicate<any>>>(
fields: R
): Predicate<{ readonly [K in keyof R]: [R[K]] extends [Predicate<infer A>] ? A : never }> => {
export const struct: {
<R extends Record<string, Predicate.Any>>(
fields: R
): [Extract<R[keyof R], Refinement.Any>] extends [never] ?
Predicate<{ readonly [K in keyof R]: Predicate.In<R[K]> }> :
Refinement<
{ readonly [K in keyof R]: R[K] extends Refinement.Any ? Refinement.In<R[K]> : Predicate.In<R[K]> },
{ readonly [K in keyof R]: R[K] extends Refinement.Any ? Refinement.Out<R[K]> : Predicate.In<R[K]> }
>
} = (<R extends Record<string, Predicate.Any>>(fields: R) => {
const keys = Object.keys(fields)
return (a) => {
return (a: Record<string, unknown>) => {
for (const key of keys) {
if (!fields[key](a[key])) {
return false
}
}
return true
}
}
}) as any

/**
* Negates the result of a given predicate.
Expand Down

0 comments on commit 4ddbff0

Please sign in to comment.