All files / src/laws/typeclass/parameterized compose.ts

100% Statements 35/35
100% Branches 6/6
100% Functions 4/4
100% Lines 35/35

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181  1x   1x         1x     1x 1x                     98x               98x       98x         98x   98x     98x 98x 98x 98x 294x 98x 98x   98x 98x                                     1x                               98x           98x       98x   98x 98x 98x 98x 98x 98x 98x 98x 98x 98x 98x 98x                                                                                                                                        
import type {LiftArbitrary} from '#arbitrary'
import {option} from '#arbitrary'
import type {LiftEquivalence} from '#law'
import {Equivalence as EQ, Option as OP, pipe} from 'effect'
import type {Kind, TypeLambda} from 'effect/HKT'
import type {OptionTypeLambda} from 'effect/Option'
import fc from 'fast-check'
import type {ComposeKey, ComposeTypeLambda} from '../../../compose.js'
import {composeMap} from '../../../compose.js'
import type {ParameterizedGiven} from './given.js'
 
const liftGiven =
  <
    Class extends TypeLambda,
    F extends TypeLambda,
    G extends TypeLambda,
    A,
    B = A,
    C = A,
    R = never,
    O = unknown,
    E = unknown,
  >() =>
  <
    K extends ComposeKey,
    Os extends ParameterizedGiven<Class, F, A, B, C, R, O, E>,
  >(
    /**
     * Type of composition requested: `Of`, `Invariant`, `Covariant`,
     * `Applicative`, `Traversable`, or `Foldable`.
     */
    key: K,
    /**
     * Suffix test label to indicate this is a composition test.
     */
    suffix: string,
    /**
     * Original options for testing the datatype known by the type lambda `F`,
     * encoding the _inner_ type of the composition.
     */
    {F, getEquivalence, getArbitrary, ...given}: Os,
  ) =>
  ({G, getEquivalenceG, getArbitraryG}: FromGiven<Class, F, G, Os>) => {
    type Composed = ComposeGiven<Class, F, G, Os>
 
    const composedGiven = {
      ...given,
      F: composeMap[key](G as never, F as never) as Composed['instance'],
      getEquivalence: <T>(equalA: EQ.Equivalence<T>) =>
        getEquivalenceG(getEquivalence(equalA)),
      getArbitrary: <T>(a: fc.Arbitrary<T>) => getArbitraryG(getArbitrary(a)),
    } as Composed['given']
 
    return [`${key}Composition.${suffix}`, composedGiven] as const
  }
 
/**
 * Return the given options transformed into options for a composed
 * typeclass test, where the outer composed datatype is an `Option`.
 *
 * For example if we are testing `Covariant` laws on `MyTuple`, and
 * the underlying types are all `number`, then the correct `given`
 * type required for these tests, is
 * `ParameterizedGiven<CovariantTypeLambda, MyTupleLambda, number>`.
 *
 * If we wanted to run the same law test but on a _composed instance_
 * of `MyTuple` inside an `Option`, then we could use this function
 * to convert the options to the required type. Then we can run these
 * new options to test typeclass laws on the composed instance.
 * @returns Typeclass test options for the `F` datatype when it is
 * wrapped in an `Option`.
 * @category composition
 */
export const withOuterOption = <
  K extends ComposeKey,
  Class extends TypeLambda,
  F extends TypeLambda,
  A,
  B = A,
  C = A,
  R = never,
  O = unknown,
  E = unknown,
>(
  /**
   * Type of composition requested: `Of`, `Invariant`, `Covariant`,
   * `Applicative`, or `Traversable`. The `Option` datatype can do
   * all of them.
   */
  key: K,
  /**
   * The original {@link ParameterizedGiven} for the typeclass under test as
   * it is _before_ composition.
   *
   */
  given: ParameterizedGiven<Class, F, A, B, C, R, O, E>,
  /**
   * The instance of `Option` for the typeclass under test.
   */
  optionInstance: Kind<Class, R, O, E, OptionTypeLambda>,
) =>
  pipe(
    {
      G: optionInstance,
      getEquivalenceG: OP.getEquivalence,
      getArbitraryG: option,
    },
    liftGiven<Class, F, OptionTypeLambda, A, B, C, R, O, E>()(
      key,
      'Option<F>',
      given,
    ),
  )
 
/**
 * Composed typeclass law test options.
 * @category composition
 */
export type ComposeGiven<
  Class extends TypeLambda,
  F extends TypeLambda,
  G extends TypeLambda,
  Os extends ParameterizedGiven<Class, F, any, any, any, any, any, any>,
> =
  Os extends ParameterizedGiven<
    Class,
    F,
    infer A,
    infer B,
    infer C,
    infer R,
    infer O,
    infer E
  >
    ? {
        instance: Kind<Class, R, O, E, ComposeTypeLambda<G, F, R, O, E>>
        given: ParameterizedGiven<
          Class,
          ComposeTypeLambda<G, F, R, O, E>,
          A,
          B,
          C,
          R,
          O,
          E
        >
      }
    : never
 
type FromGiven<
  Class extends TypeLambda,
  F extends TypeLambda,
  G extends TypeLambda,
  Os extends ParameterizedGiven<Class, F, any, any, any, any, any, any>,
> =
  Os extends ParameterizedGiven<
    Class,
    F,
    any,
    any,
    any,
    infer R,
    infer O,
    infer E
  >
    ? {
        /**
         * An instance of `Class` for the _outer_ value.
         */
        G: Kind<Class, R, O, E, G>
        /**
         * A function that will lift equivalences into the outer value `G`.
         */
        getEquivalenceG: LiftEquivalence<G, R, O, E>
        /**
         * A function that will lift arbitraries into the outer value `G`.
         */
        getArbitraryG: LiftArbitrary<G, R, O, E>
      }
    : never