import * as Either from "../Either.js" import { dual, identity } from "../Function.js" import type { Case, Matcher, MatcherTypeId, Not, SafeRefinement, TypeMatcher, Types, ValueMatcher, When } from "../Match.js" import * as Option from "../Option.js" import { pipeArguments } from "../Pipeable.js" import type * as Predicate from "../Predicate.js" import type { Unify } from "../Unify.js" /** @internal */ export const TypeId: MatcherTypeId = Symbol.for( "@effect/matcher/Matcher" ) as MatcherTypeId const TypeMatcherProto: Omit, "cases"> = { [TypeId]: { _input: identity, _filters: identity, _remaining: identity, _result: identity, _return: identity }, _tag: "TypeMatcher", add( this: TypeMatcher, _case: Case ): TypeMatcher { return makeTypeMatcher([...this.cases, _case]) }, pipe() { return pipeArguments(this, arguments) } } function makeTypeMatcher( cases: ReadonlyArray ): TypeMatcher { const matcher = Object.create(TypeMatcherProto) matcher.cases = cases return matcher } const ValueMatcherProto: Omit< ValueMatcher, "provided" | "value" > = { [TypeId]: { _input: identity, _filters: identity, _remaining: identity, _result: identity, _provided: identity, _return: identity }, _tag: "ValueMatcher", add( this: ValueMatcher, _case: Case ): ValueMatcher { if (this.value._tag === "Right") { return this } if (_case._tag === "When" && _case.guard(this.provided) === true) { return makeValueMatcher( this.provided, Either.right(_case.evaluate(this.provided)) ) } else if (_case._tag === "Not" && _case.guard(this.provided) === false) { return makeValueMatcher( this.provided, Either.right(_case.evaluate(this.provided)) ) } return this }, pipe() { return pipeArguments(this, arguments) } } function makeValueMatcher( provided: Pr, value: Either.Either ): ValueMatcher { const matcher = Object.create(ValueMatcherProto) matcher.provided = provided matcher.value = value return matcher } const makeWhen = ( guard: (u: unknown) => boolean, evaluate: (input: unknown) => any ): When => ({ _tag: "When", guard, evaluate }) const makeNot = ( guard: (u: unknown) => boolean, evaluate: (input: unknown) => any ): Not => ({ _tag: "Not", guard, evaluate }) const makePredicate = (pattern: unknown): Predicate.Predicate => { if (typeof pattern === "function") { return pattern as Predicate.Predicate } else if (Array.isArray(pattern)) { const predicates = pattern.map(makePredicate) const len = predicates.length return (u: unknown) => { if (!Array.isArray(u)) { return false } for (let i = 0; i < len; i++) { if (predicates[i](u[i]) === false) { return false } } return true } } else if (pattern !== null && typeof pattern === "object") { const keysAndPredicates = Object.entries(pattern).map( ([k, p]) => [k, makePredicate(p)] as const ) const len = keysAndPredicates.length return (u: unknown) => { if (typeof u !== "object" || u === null) { return false } for (let i = 0; i < len; i++) { const [key, predicate] = keysAndPredicates[i] if (!(key in u) || predicate((u as any)[key]) === false) { return false } } return true } } return (u: unknown) => u === pattern } const makeOrPredicate = ( patterns: ReadonlyArray ): Predicate.Predicate => { const predicates = patterns.map(makePredicate) const len = predicates.length return (u: unknown) => { for (let i = 0; i < len; i++) { if (predicates[i](u) === true) { return true } } return false } } const makeAndPredicate = ( patterns: ReadonlyArray ): Predicate.Predicate => { const predicates = patterns.map(makePredicate) const len = predicates.length return (u: unknown) => { for (let i = 0; i < len; i++) { if (predicates[i](u) === false) { return false } } return true } } /** @internal */ export const type = (): Matcher< I, Types.Without, I, never, never > => makeTypeMatcher([]) /** @internal */ export const value = ( i: I ): Matcher, I, never, I> => makeValueMatcher(i, Either.left(i)) /** @internal */ export const valueTags: { < const I, P extends & { readonly [Tag in Types.Tags<"_tag", I> & string]: (_: Extract) => any } & { readonly [Tag in Exclude>]: never } >(fields: P): (input: I) => Unify> < const I, P extends & { readonly [Tag in Types.Tags<"_tag", I> & string]: (_: Extract) => any } & { readonly [Tag in Exclude>]: never } >(input: I, fields: P): Unify> } = dual( 2, < const I, P extends & { readonly [Tag in Types.Tags<"_tag", I> & string]: (_: Extract) => any } & { readonly [Tag in Exclude>]: never } >(input: I, fields: P): Unify> => { const match: any = tagsExhaustive(fields as any)(makeTypeMatcher([])) return match(input) } ) /** @internal */ export const typeTags = () => < P extends { readonly [Tag in Types.Tags<"_tag", I> & string]: ( _: Extract ) => any } >( fields: P ) => { const match: any = tagsExhaustive(fields as any)(makeTypeMatcher([])) return (input: I): Unify> => match(input) } /** @internal */ export const withReturnType = () => (self: Matcher): [Ret] extends [ [A] extends [never] ? any : A ] ? Matcher : "withReturnType constraint does not extend Result type" => self as any /** @internal */ export const when = < R, const P extends Types.PatternPrimitive | Types.PatternBase, Ret, Fn extends (_: Types.WhenMatch) => Ret >( pattern: P, f: Fn ) => ( self: Matcher ): Matcher< I, Types.AddWithout>, Types.ApplyFilters>>, A | ReturnType, Pr, Ret > => (self as any).add(makeWhen(makePredicate(pattern), f as any)) /** @internal */ export const whenOr = < R, const P extends ReadonlyArray< Types.PatternPrimitive | Types.PatternBase >, Ret, Fn extends (_: Types.WhenMatch) => Ret >( ...args: [...patterns: P, f: Fn] ) => ( self: Matcher ): Matcher< I, Types.AddWithout>, Types.ApplyFilters>>, A | ReturnType, Pr, Ret > => { const onMatch = args[args.length - 1] as any const patterns = args.slice(0, -1) as unknown as P return (self as any).add(makeWhen(makeOrPredicate(patterns), onMatch)) } /** @internal */ export const whenAnd = < R, const P extends ReadonlyArray< Types.PatternPrimitive | Types.PatternBase >, Ret, Fn extends (_: Types.WhenMatch>) => Ret >( ...args: [...patterns: P, f: Fn] ) => ( self: Matcher ): Matcher< I, Types.AddWithout>>, Types.ApplyFilters< I, Types.AddWithout>> >, A | ReturnType, Pr > => { const onMatch = args[args.length - 1] as any const patterns = args.slice(0, -1) as unknown as P return (self as any).add(makeWhen(makeAndPredicate(patterns), onMatch)) } /** @internal */ export const discriminator = (field: D) => < R, P extends Types.Tags & string, Ret, Fn extends (_: Extract>) => Ret >( ...pattern: [ first: P, ...values: Array

, f: Fn ] ) => { const f = pattern[pattern.length - 1] const values: Array

= pattern.slice(0, -1) as any const pred = values.length === 1 ? (_: any) => _ != null && _[field] === values[0] : (_: any) => _ != null && values.includes(_[field]) return ( self: Matcher ): Matcher< I, Types.AddWithout>>, Types.ApplyFilters>>>, A | ReturnType, Pr, Ret > => (self as any).add(makeWhen(pred, f as any)) as any } /** @internal */ export const discriminatorStartsWith = (field: D) => < R, P extends string, Ret, Fn extends (_: Extract>) => Ret >( pattern: P, f: Fn ) => { const pred = (_: any) => _ != null && typeof _[field] === "string" && _[field].startsWith(pattern) return ( self: Matcher ): Matcher< I, Types.AddWithout>>, Types.ApplyFilters< I, Types.AddWithout>> >, A | ReturnType, Pr, Ret > => (self as any).add(makeWhen(pred, f as any)) as any } /** @internal */ export const discriminators = (field: D) => < R, Ret, P extends & { readonly [Tag in Types.Tags & string]?: | ((_: Extract>) => Ret) | undefined } & { readonly [Tag in Exclude>]: never } >( fields: P ) => { const predicate = makeWhen( (arg: any) => arg != null && arg[field] in fields, (data: any) => (fields as any)[data[field]](data) ) return ( self: Matcher ): Matcher< I, Types.AddWithout>>, Types.ApplyFilters>>>, A | ReturnType, Pr, Ret > => (self as any).add(predicate) } /** @internal */ export const discriminatorsExhaustive: ( field: D ) => < R, Ret, P extends & { readonly [Tag in Types.Tags & string]: ( _: Extract> ) => Ret } & { readonly [Tag in Exclude>]: never } >( fields: P ) => ( self: Matcher ) => [Pr] extends [never] ? (u: I) => Unify> : Unify> = (field: string) => (fields: object) => { const addCases = discriminators(field)(fields) return (matcher: any) => exhaustive(addCases(matcher)) } /** @internal */ export const tag: < R, P extends Types.Tags<"_tag", R> & string, Ret, Fn extends (_: Extract>) => Ret >( ...pattern: [ first: P, ...values: Array

, f: Fn ] ) => ( self: Matcher ) => Matcher< I, Types.AddWithout>>, Types.ApplyFilters>>>, ReturnType | A, Pr, Ret > = discriminator("_tag") /** @internal */ export const tagStartsWith = discriminatorStartsWith("_tag") /** @internal */ export const tags = discriminators("_tag") /** @internal */ export const tagsExhaustive = discriminatorsExhaustive("_tag") /** @internal */ export const not = < R, const P extends Types.PatternPrimitive | Types.PatternBase, Ret, Fn extends (_: Types.NotMatch) => Ret >( pattern: P, f: Fn ) => ( self: Matcher ): Matcher< I, Types.AddOnly>, Types.ApplyFilters>>, A | ReturnType, Pr, Ret > => (self as any).add(makeNot(makePredicate(pattern), f as any)) /** @internal */ export const nonEmptyString: SafeRefinement = ((u: unknown) => typeof u === "string" && u.length > 0) as any /** @internal */ export const is: < Literals extends ReadonlyArray >( ...literals: Literals ) => SafeRefinement = (...literals): any => { const len = literals.length return (u: unknown) => { for (let i = 0; i < len; i++) { if (u === literals[i]) { return true } } return false } } /** @internal */ export const any: SafeRefinement = (() => true) as any /** @internal */ export const defined = (u: A): u is A & {} => (u !== undefined && u !== null) as any /** @internal */ export const instanceOf = any>( constructor: A ): SafeRefinement, never> => ((u: unknown) => u instanceof constructor) as any /** @internal */ export const instanceOfUnsafe: any>( constructor: A ) => SafeRefinement, InstanceType> = instanceOf /** @internal */ export const orElse = Ret>(f: F) => (self: Matcher): [Pr] extends [never] ? (input: I) => Unify | A> : Unify | A> => { const result = either(self) if (Either.isEither(result)) { // @ts-expect-error return result._tag === "Right" ? result.right : f(result.left) } // @ts-expect-error return (input: I) => { const a = result(input) return a._tag === "Right" ? a.right : f(a.left) } } /** @internal */ export const orElseAbsurd = ( self: Matcher ): [Pr] extends [never] ? (input: I) => Unify : Unify => orElse(() => { throw new Error("effect/Match/orElseAbsurd: absurd") })(self) /** @internal */ export const either: ( self: Matcher ) => [Pr] extends [never] ? (input: I) => Either.Either, R> : Either.Either, R> = ((self: Matcher) => { if (self._tag === "ValueMatcher") { return self.value } const len = self.cases.length if (len === 1) { const _case = self.cases[0] return (input: I): Either.Either => { if (_case._tag === "When" && _case.guard(input) === true) { return Either.right(_case.evaluate(input)) } else if (_case._tag === "Not" && _case.guard(input) === false) { return Either.right(_case.evaluate(input)) } return Either.left(input as any) } } return (input: I): Either.Either => { for (let i = 0; i < len; i++) { const _case = self.cases[i] if (_case._tag === "When" && _case.guard(input) === true) { return Either.right(_case.evaluate(input)) } else if (_case._tag === "Not" && _case.guard(input) === false) { return Either.right(_case.evaluate(input)) } } return Either.left(input as any) } }) as any /** @internal */ export const option: ( self: Matcher ) => [Pr] extends [never] ? (input: I) => Option.Option> : Option.Option> = ((self: Matcher) => { const toEither = either(self) if (Either.isEither(toEither)) { return Either.match(toEither, { onLeft: () => Option.none(), onRight: Option.some }) } return (input: I): Option.Option => Either.match((toEither as any)(input), { onLeft: () => Option.none(), onRight: Option.some as any }) }) as any const getExhaustiveAbsurdErrorMessage = "effect/Match/exhaustive: absurd" /** @internal */ export const exhaustive: ( self: Matcher ) => [Pr] extends [never] ? (u: I) => Unify : Unify = (( self: Matcher ) => { const toEither = either(self as any) if (Either.isEither(toEither)) { if (toEither._tag === "Right") { return toEither.right } throw new Error(getExhaustiveAbsurdErrorMessage) } return (u: I): A => { // @ts-expect-error const result = toEither(u) if (result._tag === "Right") { return result.right as any } throw new Error(getExhaustiveAbsurdErrorMessage) } }) as any