All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.specs2.matcher.describe.Diffable.scala Maven / Gradle / Ivy

There is a newer version: 5.5.8
Show newest version
package org.specs2
package matcher.describe

import scala.util.{Failure, Try}
import PrimitiveDiffable.primitive
import scala.deriving.*
import scala.compiletime.{erasedValue, error, constValue, summonInline}

/** Typeclass for values which can be compared and return a comparison result
  */
trait Diffable[T]:
  def diff(actual: T, expected: T): ComparisonResult

  def unsafeDiff(actual: Any, expected: Any): ComparisonResult =
    diff(actual.asInstanceOf[T], expected.asInstanceOf[T])

/** Note: variance is not managed by having a Diffable typeclass with a contravariant parameter Diffable[-T] In that the
  * implicit for case classes (see `product`) can not be found.
  *
  * Instead we deal with variance at the level of each implicit.
  *
  * For example `given exceptionDiffable[T <: Throwable]: Diffable[T] = new ThrowableDiffable[T]` will be found for any
  * custom exception extending Throwable. Similarly we can get a diff for Right[String, Int](1) and Right[String,
  * Int](2) with `given eitherDiffable[L: Diffable, R: Diffable, T <: Either[L, R]]` because we unify both Rights with
  * Either[String, Int]
  */
object Diffable extends DiffableLowImplicits:

  def diff[T](actual: T, expected: T)(using di: Diffable[T]): ComparisonResult =
    di.diff(actual, expected)

  // scala collections
  given mapDiffable[K: Diffable, V: Diffable, M <: Map[K, V]]: Diffable[M] = new MapDiffable[K, V, M]
  given seqDiffable[E: Diffable, S <: Seq[E]]: Diffable[S] = new SeqLinesDiffable[E, S]
  given setDiffable[E: Diffable, S <: Set[E]]: Diffable[S] = new SetDiffable[E, S]
  given arrayDiffable[E: Diffable]: Diffable[Array[E]] = new ArrayDiffable

  // Needed to avoid ambiguous implicits with Dotty when looking for a Diffable
  // for `Either[Int, Nothing]` for example.
  given nothingDiffable: Diffable[Nothing] = NothingDiffable

  // instances for primitive types
  given intDiffable: Diffable[Int] = primitive
  given booleanDiffable: Diffable[Boolean] = primitive
  given stringDiffable: Diffable[String] = primitive
  given longDiffable: Diffable[Long] = primitive
  given floatDiffable: Diffable[Float] = primitive
  given doubleDiffable: Diffable[Double] = primitive

  // basic elements
  given stackTraceElementDiffable: Diffable[StackTraceElement] = new StackTraceElementDiffable
  given exceptionDiffable[T <: Throwable]: Diffable[T] = new ThrowableDiffable[T]

  // None type
  given optionNoneDiffable[T <: Option[Nothing]]: Diffable[T] = new OptionNoneDiffable[T]

  given eitherRightDiffable[R: Diffable]: Diffable[Right[Nothing, R]] = new EitherRightDiffable[R]
  given eitherLeftDiffable[L: Diffable]: Diffable[Left[L, Nothing]] = new EitherLeftDiffable[L]

  given tryDiffable[T: Diffable, S <: Try[T]]: Diffable[S] = new TryDiffable[T, S]
  given failureDiffable: Diffable[Failure[Nothing]] = new FailureDiffable

trait DiffableLowImplicits extends DiffableLowImplicits2:
  given optionDiffable[T: Diffable, S <: Option[T]]: Diffable[S] = new OptionDiffable[T, S]
  given eitherDiffable[L: Diffable, R: Diffable, T <: Either[L, R]]: Diffable[T] = new EitherDiffable[L, R, T]

trait DiffableLowImplicits2 extends DiffableLowImplicits3:

  /** this Diff instance addresses case classes differences */
  inline given product[T](using m: Mirror.ProductOf[T]): Diffable[T] =
    derived[T]

  inline def derived[T](using p: Mirror.ProductOf[T]): Diffable[T] =
    lazy val diffables = summonAll[p.MirroredElemTypes]
    lazy val typeName: String = constValue[p.MirroredLabel]
    new ProductDiffable[T](typeName, p, diffables)

  inline def summonAll[T <: Tuple]: List[Diffable[?]] =
    inline erasedValue[T] match
      case _: EmptyTuple => Nil
      // in the case of a large number of fields we summon the diffables 5 by 5 in order
      // to avoid hitting the max inlining limit of 32 by default
      case _: (t1 *: t2 *: t3 *: t4 *: t5 *: ts) =>
        summonInline[Diffable[t1]] :: summonInline[Diffable[t2]] :: summonInline[Diffable[t3]] ::
          summonInline[Diffable[t4]] :: summonInline[Diffable[t5]] :: summonAll[ts]
      case _: (t *: ts) => summonInline[Diffable[t]] :: summonAll[ts]

trait DiffableLowImplicits3:

  given fallbackDiffable[T]: Diffable[T] =
    new FallbackDiffable[T]

trait Diffables:
  extension [T](diffable: Diffable[T])
    def compareWith(compare: (T, T) => Boolean): Diffable[T] =
      new Diffable[T]:
        def diff(actual: T, expected: T): ComparisonResult = new ComparisonResult {
          def identical: Boolean = compare(actual, expected)
          def render: String = diffable.diff(actual, expected).render
          override def render(indent: String): String = diffable.diff(actual, expected).render(indent)
        }

object Diffables extends Diffables




© 2015 - 2024 Weber Informatics LLC | Privacy Policy