Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support Scala 3 for module derivation #1275

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 9 additions & 2 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -322,10 +322,17 @@ lazy val derivation = projectMatrix
.in(modules / "derivation")
.settings(
defaultSettings,
libraryDependencies ++= Seq(magnolia2, derevo, catsTaglessMacros),
scala3MigratedModuleOptions,
libraryDependencies ++= {
CrossVersion.partialVersion(scalaVersion.value) match {
case Some((2, _)) => Seq(derevo, magnolia2, catsTaglessMacros)
case Some((3, _)) => Seq(magnolia3)
case _ => Seq.empty
}
},
name := "tofu-derivation",
)
.jvmPlatform(scala2Versions)
.jvmPlatform(scalaVersions = scala2And3Versions)
.dependsOn(kernel)

val zioInterop = modules / "interop" / "zio1"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,20 @@ import cats.Eval
import derevo.Derivation
import magnolia1.{CaseClass, Magnolia, SealedTrait}
import tofu.common.Display
import tofu.magnolia.compat

/** Derivation of [[Display]] typeclass for case classes and sealed traits
*
* @note
* Derived [[Display]] instances will indent nested structures if those are supposed to be on newline. You can see
* examples in the tests.
*/
object display extends Derivation[Display] {
trait DisplayDerivationImpl extends Derivation[Display] {

private type Typeclass[T] = Display[T]

def join[T](ctx: CaseClass[Typeclass, T]): Display[T] = (cfg: Display.Config, a: T) => {
import cfg.{fieldSeparator, indent, brackets, fieldAssign, newline}
import cfg._

val nestedIndent = indent + indent

Expand Down Expand Up @@ -46,7 +47,7 @@ object display extends Derivation[Display] {
alreadyDisplayed <- acc
nestedCfg = cfg.copy(indent = nestedIndent, brackets = brackets.copy(right = indent + brackets.right))
label = if (cfg.showFieldLabels) current.label + fieldAssign else ""
displayedParameterValue <- current.typeclass.displayBuild(nestedCfg, current.dereference(a))
displayedParameterValue <- current.typeclass.displayBuild(nestedCfg, compat.deref[Typeclass, T](current)(a))
// this value has at least one element in it by construction,
// but we avoid using NEVector here due to performance and simplicity
adapted :+ value = adaptDisplayedParameter(label, displayedParameterValue)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
package tofu.data.derived

import cats.Monad
import derevo.Derivation
import magnolia1.{CaseClass, Magnolia}
import magnolia1.Monadic
import magnolia1.{CaseClass, Magnolia, Monadic}
import tofu.Init

class InitDerivation[F[_]: Monad] extends Derivation[Init[F, *]] {
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package tofu.data
package derived

import derevo.Derivation
import magnolia1.{CaseClass, Magnolia, SealedTrait}
import tofu.magnolia.compat

trait MergeInstances1 extends Derivation[Merge] {
type Typeclass[A] = Merge[A]

def join[T](caseClass: CaseClass[Typeclass, T]): Typeclass[T] =
(a, b) =>
caseClass.construct(p => p.typeclass.merge(compat.deref[Typeclass, T](p)(a), compat.deref[Typeclass, T](p)(b)))

def split[T](sealedTrait: SealedTrait[Typeclass, T]): Typeclass[T] =
(a, b) => sealedTrait.split(a) { h => if (h.cast.isDefinedAt(b)) h.typeclass.merge(h.cast(a), h.cast(b)) else a }

implicit def instance[A]: Merge[A] = macro Magnolia.gen[A]
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
package tofu.higherKind.derived
import derevo.DerivationK2

import derevo.{DerivationK2, DerivationKN11}
import tofu.higherKind.bi.{EmbedBK, RepresentableB}
import tofu.higherKind.{Embed, RepresentableK}
import derevo.DerivationKN11
import tofu.higherKind.bi.RepresentableB
import tofu.higherKind.bi.EmbedBK

object representableK extends DerivationK2[RepresentableK] {
def instance[T[_[_]]]: RepresentableK[T] = macro HigherKindedMacros.representableK[T]
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package tofu.magnolia

object compat {
type Param[Typeclass[_], Type] = magnolia1.Param[Typeclass, Type]

def deref[Typeclass[_], Type](p: Param[Typeclass, Type]): Type => p.PType = tpe => p.dereference(tpe)

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package tofu.common.derived

import cats.Eval
import magnolia1.*
import tofu.common.Display
import tofu.magnolia.compat
import scala.deriving.Mirror

/** Derivation of [[Display]] typeclass for case classes and sealed traits
*
* @note
* Derived [[Display]] instances will indent nested structures if those are supposed to be on newline. You can see
* examples in the tests.
*/
trait DisplayDerivationImpl extends AutoDerivation[Display] {

private type Typeclass[T] = Display[T]

def join[T](ctx: CaseClass[Typeclass, T]): Display[T] = (cfg: Display.Config, a: T) => {
import cfg.*

val nestedIndent = indent + indent

def adaptDisplayedParameter(label: String, displayedParameterValue: Vector[String]): Vector[String] = {
displayedParameterValue match {
case value +: Vector() =>
Vector(indent + label + value)
case typeHeader +: innerValueParams =>
val labeledTypeHeader =
indent + label + typeHeader
labeledTypeHeader +: innerValueParams // .map(indent + _)
case _ => Vector(indent + label)
}
}

val shortName: String = ctx.typeInfo.short

ctx.parameters.zipWithIndex
.foldLeft(
Eval.now(
Vector(
s"$shortName ${brackets.left}$newline",
)
)
) { case (acc, (current, index)) =>
for {
alreadyDisplayed <- acc
nestedCfg = cfg.copy(indent = nestedIndent, brackets = brackets.copy(right = indent + brackets.right))
label = if (cfg.showFieldLabels) current.label + fieldAssign else ""
displayedParameterValue <- current.typeclass.displayBuild(nestedCfg, compat.deref[Typeclass, T](current)(a))
// this value has at least one element in it by construction,
// but we avoid using NEVector here due to performance and simplicity
adapted :+ value = adaptDisplayedParameter(label, displayedParameterValue): @unchecked
separator = if (index + 1 < ctx.parameters.size) fieldSeparator else ""
adaptedIndentedValueWithSeparator = value + separator + newline
separatedLabelValue = adapted :+ adaptedIndentedValueWithSeparator
} yield alreadyDisplayed ++: separatedLabelValue
}
.map(s => s :+ brackets.right)
}
def split[T](ctx: SealedTrait[Typeclass, T]): Display[T] = (cfg: Display.Config, a: T) =>
ctx.choose(a)(adtCase => adtCase.typeclass.displayBuild(cfg, adtCase.cast(a)))

inline def instance[T](using Mirror.Of[T]): Display[T] = autoDerived[T]

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package tofu.common

import scala.deriving.Mirror

package object derived {
extension (x: Display.type) inline def derived[A](using Mirror.Of[A]): Display[A] = display.derived[A]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package tofu.data.derived

import magnolia1.*
import tofu.magnolia.compat
import scala.deriving.Mirror

trait MergeInstances1 extends AutoDerivation[Merge] {
type Typeclass[A] = Merge[A]

def join[T](caseClass: CaseClass[Typeclass, T]): Typeclass[T] =
(a, b) =>
caseClass.construct(p => p.typeclass.merge(compat.deref[Typeclass, T](p)(a), compat.deref[Typeclass, T](p)(b)))

def split[T](sealedTrait: SealedTrait[Typeclass, T]): Typeclass[T] =
(a, b) => sealedTrait.choose(a) { h => if (h.cast.isDefinedAt(b)) h.typeclass.merge(h.cast(a), h.cast(b)) else a }

inline def instance[A](using Mirror.Of[A]): Merge[A] = autoDerived[A]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package tofu.magnolia

object compat {
type Param[Typeclass[_], Type] = magnolia1.CaseClass.Param[Typeclass, Type]

def deref[Typeclass[_], Type](p: Param[Typeclass, Type]): Type => p.PType = tpe => p.deref(tpe)

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package tofu.common.derived

object display extends DisplayDerivationImpl
53 changes: 53 additions & 0 deletions modules/derivation/src/main/scala/tofu/data/derived/Merge.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package tofu.data.derived

import cats.kernel.Semigroup
import tofu.compat.unused
import tofu.internal.DataComp

import java.time.{Instant, LocalDate, LocalDateTime, ZonedDateTime}

trait Merge[A] {
def merge(a: A, b: A): A
}

object Merge extends MergeInstances1 with DataComp[Merge] {
implicit def optionInstance[A](implicit m: Merge[A]): Merge[Option[A]] =
(ao, bo) => ao.fold(bo)(a => bo.fold(ao)(b => Some(m.merge(a, b))))

implicit def primitiveInstance[A](implicit @unused ev: Primitive[A]): Merge[A] = (a: A, _: A) => a

val ops: tofu.syntax.merge.type = tofu.syntax.merge

sealed class Primitive[A]
implicit object primitiveByte extends Primitive[Byte]
implicit object primitiveShort extends Primitive[Short]
implicit object primitiveInt extends Primitive[Int]
implicit object primitiveLong extends Primitive[Long]
implicit object primitiveChar extends Primitive[Char]
implicit object primitiveFloat extends Primitive[Float]
implicit object primitiveDouble extends Primitive[Double]
implicit object primitiveUnit extends Primitive[Unit]
implicit object primitiveBigDecimal extends Primitive[BigDecimal]
implicit object primitiveBigInt extends Primitive[BigInt]
implicit object primitiveLocalDateTime extends Primitive[LocalDateTime]
implicit object primitiveZonedDateTime extends Primitive[ZonedDateTime]
implicit object primitiveLocalDate extends Primitive[LocalDate]
implicit object primitiveInstant extends Primitive[Instant]
implicit object primitiveString extends Primitive[String]
}

object Merged {
trait OpaqueTag extends Any
type Base = Any { type MergedOpaque }

type Mer[A] <: Base with OpaqueTag

def apply[A](value: A): Mer[A] = value.asInstanceOf[Mer[A]]

implicit final class MergedOps[A](private val mer: Mer[A]) extends AnyVal {
def value: A = mer.asInstanceOf[A]
}

implicit def mergedSemigroup[A: Merge]: Semigroup[Merged[A]] =
(x, y) => apply(Merge[A].merge(x.value, y.value))
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package tofu.common.derived

import cats.Eval
import derevo.derive
import tofu.common.Display
import tofu.common.Display.Config

object DisplayData {
@derive(display)
case class Bar(value: Int, another: String)

@derive(display)
case class Foo(bar: Bar, field: Double, xs: List[Int])

@derive(display)
sealed trait FooBar
object FooBar {
case class Barn(i: Int) extends FooBar
case class Darn(j: Double) extends FooBar
}

case class Emptiness()
object Emptiness {
implicit val displayEmptiness: Display[Emptiness] =
(_: Config, _: Emptiness) => Eval.now(Vector.empty[String])
}

@derive(display)
case class Bjarn(i: Emptiness)

case class Nested(a: Int, b: Int, c: Int)
object Nested {
implicit val displayNested: Display[Nested] =
(_: Config, ns: Nested) => Eval.now(Vector("Nested {", s"a = ${ns.a}, ", s"b = ${ns.b}, ", s"c = ${ns.c}", "}"))
}

@derive(display)
case class Cont(a: Int, b: Nested)

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package tofu.data.derived

import derevo.derive

object MergeData {
@derive(Merge)
final case class Foo(a: Int, b: Option[String], c: Option[Double])

@derive(Merge)
final case class Bar(x: Foo, y: Option[Foo])
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import cats.instances.either._
import cats.instances.option._
import cats.syntax.traverse._
import cats.syntax.either._

import scala.util.Try

class EmbedSuite extends AnyFlatSpec with Matchers {
Expand Down
Loading
Loading