Skip to content

Commit bc7efa3

Browse files
authored
preserve refinement types in Match.when (#4748)
1 parent b74b84f commit bc7efa3

File tree

4 files changed

+90
-44
lines changed

4 files changed

+90
-44
lines changed

.changeset/hot-needles-film.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"effect": patch
3+
---
4+
5+
preserve refinement types in Match.when

packages/effect/dtslint/Match.tst.ts

+44-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
import type { Option } from "effect"
2-
import { Either, hole, Match, pipe, Predicate } from "effect"
1+
import { Either, hole, Match, Option, pipe, Predicate } from "effect"
32
import { describe, expect, it } from "tstyche"
43

54
type Value = { _tag: "A"; a: number } | { _tag: "B"; b: number }
@@ -259,7 +258,7 @@ describe("Match", () => {
259258
pipe(
260259
Match.value(hole<{ readonly a: string | Array<number> }>()),
261260
Match.when({ a: isArray }, (v) => {
262-
expect(v).type.toBe<{ a: ReadonlyArray<number> }>()
261+
expect(v).type.toBe<{ a: Array<number> }>()
263262
return "array"
264263
}),
265264
Match.orElse((v) => {
@@ -559,4 +558,46 @@ describe("Match", () => {
559558
)(value)
560559
).type.toBe<string | number>()
561560
})
561+
562+
it("Option.isSome", () => {
563+
expect(
564+
pipe(
565+
Match.type<{ maybeNumber: Option.Option<number> }>(),
566+
Match.when({ maybeNumber: Option.isSome }, (v) => {
567+
expect(v).type.toBe<{ maybeNumber: Option.Some<number> }>()
568+
return v.maybeNumber.value
569+
}),
570+
Match.orElse((B) => {
571+
expect(B).type.toBe<{ maybeNumber: Option.Option<number> }>()
572+
return undefined
573+
})
574+
)({ maybeNumber: Option.some(1) })
575+
).type.toBe<number | undefined>()
576+
})
577+
578+
it("whenOr refinement with pattern", () => {
579+
class Person {
580+
get contactable() {
581+
return true
582+
}
583+
}
584+
expect(
585+
pipe(
586+
Match.type<{ maybeNumber: Option.Option<number>; person: Person }>(),
587+
Match.whenOr({
588+
maybeNumber: {
589+
_tag: Match.is("Some", "None")
590+
},
591+
person: { contactable: true }
592+
}, ({ person }) => {
593+
expect(person.contactable).type.toBe<true>()
594+
return person.contactable
595+
}),
596+
Match.orElse(({ person }) => {
597+
expect(person).type.toBe<Person>()
598+
return false
599+
})
600+
)({ maybeNumber: Option.some(1), person: new Person() })
601+
).type.toBe<boolean>()
602+
})
562603
})

packages/effect/src/Match.ts

+40-40
Original file line numberDiff line numberDiff line change
@@ -925,7 +925,7 @@ export const not: <
925925
R,
926926
const P extends Types.PatternPrimitive<R> | Types.PatternBase<R>,
927927
Ret,
928-
Fn extends (_: Exclude<R, Types.ExtractMatch<R, Types.PForExclude<P>>>) => Ret
928+
Fn extends (_: Types.NotMatch<R, P>) => Ret
929929
>(
930930
pattern: P,
931931
f: Fn
@@ -956,7 +956,7 @@ export const nonEmptyString: SafeRefinement<string, never> = internal.nonEmptySt
956956
*/
957957
export const is: <
958958
Literals extends ReadonlyArray<string | number | bigint | boolean | null>
959-
>(...literals: Literals) => Predicate.Refinement<unknown, Literals[number]> = internal.is
959+
>(...literals: Literals) => SafeRefinement<Literals[number]> = internal.is
960960

961961
/**
962962
* Matches values of type `string`.
@@ -1275,8 +1275,8 @@ export declare namespace Types {
12751275
*/
12761276
export type WhenMatch<R, P> =
12771277
// check for any
1278-
[0] extends [1 & R] ? PForMatch<P>
1279-
: P extends SafeRefinement<infer SP, never> ? SP
1278+
[0] extends [1 & R] ? ResolvePred<P> :
1279+
P extends SafeRefinement<infer SP, never> ? SP
12801280
: P extends Predicate.Refinement<infer _R, infer RP>
12811281
// try to narrow refinement
12821282
? [Extract<R, RP>] extends [infer X] ? [X] extends [never]
@@ -1285,17 +1285,20 @@ export declare namespace Types {
12851285
: X
12861286
: never
12871287
: P extends PredicateA<infer PP> ? PP
1288-
: ExtractMatch<R, PForMatch<P>>
1288+
: ExtractMatch<R, P>
12891289

12901290
/**
12911291
* @since 1.0.0
12921292
*/
1293-
export type NotMatch<R, P> = Exclude<R, ExtractMatch<R, PForExclude<P>>>
1293+
export type NotMatch<R, P> = Exclude<R, ExtractMatch<R, PForNotMatch<P>>>
1294+
1295+
type PForNotMatch<P> = [ToSafeRefinement<P>] extends [infer X] ? X
1296+
: never
12941297

12951298
/**
12961299
* @since 1.0.0
12971300
*/
1298-
export type PForMatch<P> = [SafeRefinementP<ResolvePred<P>>] extends [infer X] ? X
1301+
export type PForMatch<P> = [ResolvePred<P>] extends [infer X] ? X
12991302
: never
13001303

13011304
/**
@@ -1307,22 +1310,16 @@ export declare namespace Types {
13071310
// utilities
13081311
type PredicateA<A> = Predicate.Predicate<A> | Predicate.Refinement<A, A>
13091312

1310-
type SafeRefinementP<A> = A extends never ? never
1311-
: A extends SafeRefinement<infer S, infer _> ? S
1312-
: A extends Function ? A
1313-
: A extends Record<string, any> ? { [K in keyof A]: SafeRefinementP<A[K]> }
1314-
: A
1315-
13161313
type SafeRefinementR<A> = A extends never ? never
13171314
: A extends SafeRefinement<infer _, infer R> ? R
13181315
: A extends Function ? A
13191316
: A extends Record<string, any> ? { [K in keyof A]: SafeRefinementR<A[K]> }
13201317
: A
13211318

13221319
type ResolvePred<A> = A extends never ? never
1320+
: A extends SafeRefinement<infer _A, infer _R> ? _R
13231321
: A extends Predicate.Refinement<any, infer P> ? P
13241322
: A extends Predicate.Predicate<infer P> ? P
1325-
: A extends SafeRefinement<any> ? A
13261323
: A extends Record<string, any> ? { [K in keyof A]: ResolvePred<A[K]> }
13271324
: A
13281325

@@ -1433,33 +1430,36 @@ export declare namespace Types {
14331430

14341431
type Simplify<A> = { [K in keyof A]: A[K] } & {}
14351432

1436-
type ExtractAndNarrow<Input, P> =
1437-
// unknown is a wildcard pattern
1438-
unknown extends P ? Input
1439-
: Input extends infer I ? Exclude<
1440-
I extends ReadonlyArray<any> ? P extends ReadonlyArray<any> ? {
1441-
readonly [K in keyof I]: K extends keyof P ? ExtractAndNarrow<I[K], P[K]>
1442-
: I[K]
1443-
} extends infer R ? Fail extends R[keyof R] ? never
1444-
: R
1445-
: never
1433+
type ExtractAndNarrow<Input, P> = Input extends infer I ?
1434+
P extends SafeRefinement<infer _In, infer _R> | Predicate.Refinement<infer _In, infer _R> ?
1435+
P extends SafeRefinement<I & {}, infer _R> | Predicate.Refinement<I & {}, infer _R> ?
1436+
[0] extends [1 & _R] ? I : _R
1437+
: Extract<I, _R> :
1438+
P extends Predicate.Predicate<infer _In> ? P extends Predicate.Predicate<I & {}> ? _In : never :
1439+
Exclude<
1440+
I extends ReadonlyArray<any> ? P extends ReadonlyArray<any> ? {
1441+
readonly [K in keyof I]: K extends keyof P ? ExtractAndNarrow<I[K], P[K]>
1442+
: I[K]
1443+
} extends infer R ? Fail extends R[keyof R] ? never
1444+
: R
1445+
: never
1446+
: never
1447+
: IsPlainObject<I> extends true ? string extends keyof I ? I extends P ? I
1448+
: never
1449+
: symbol extends keyof I ? I extends P ? I
14461450
: never
1447-
: IsPlainObject<I> extends true ? string extends keyof I ? I extends P ? I
1448-
: never
1449-
: symbol extends keyof I ? I extends P ? I
1450-
: never
1451-
: Simplify<
1452-
& { [RK in Extract<keyof I, keyof P>]-?: ExtractAndNarrow<I[RK], P[RK]> }
1453-
& Omit<I, keyof P>
1454-
> extends infer R ? keyof P extends NonFailKeys<R> ? R
1455-
: never
1456-
: never
1457-
: MaybeReplace<I, P> extends infer R ? [I] extends [R] ? I
1458-
: R
1459-
: never,
1460-
Fail
1461-
> :
1462-
never
1451+
: Simplify<
1452+
& { [RK in Extract<keyof I, keyof P>]-?: ExtractAndNarrow<I[RK], P[RK]> }
1453+
& Omit<I, keyof P>
1454+
> extends infer R ? keyof P extends NonFailKeys<R> ? R
1455+
: never
1456+
: never
1457+
: MaybeReplace<I, P> extends infer R ? [I] extends [R] ? I
1458+
: R
1459+
: never,
1460+
Fail
1461+
> :
1462+
never
14631463

14641464
type NonFailKeys<A> = keyof A & {} extends infer K ? K extends keyof A ? A[K] extends Fail ? never : K
14651465
: never :

packages/effect/src/internal/matcher.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -509,7 +509,7 @@ export const is: <
509509
Literals extends ReadonlyArray<string | number | boolean | null | bigint>
510510
>(
511511
...literals: Literals
512-
) => Predicate.Refinement<unknown, Literals[number]> = (...literals): any => {
512+
) => SafeRefinement<Literals[number]> = (...literals): any => {
513513
const len = literals.length
514514
return (u: unknown) => {
515515
for (let i = 0; i < len; i++) {

0 commit comments

Comments
 (0)