Skip to content

Commit

Permalink
Magnolia derivation for Debug typeclass
Browse files Browse the repository at this point in the history
  • Loading branch information
cheleb committed Sep 4, 2024
1 parent 35b048e commit 10a448e
Show file tree
Hide file tree
Showing 5 changed files with 177 additions and 1 deletion.
35 changes: 34 additions & 1 deletion build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,9 @@ val projectsCommon = List(
experimentalLaws,
experimentalTests,
laws,
macros
macros,
magnolia,
magnoliaTests
)

val projectsJvmOnly = List[ProjectReference](
Expand Down Expand Up @@ -161,6 +163,37 @@ lazy val macros = crossProject(JSPlatform, JVMPlatform, NativePlatform)
.nativeSettings(nativeSettings)
.enablePlugins(BuildInfoPlugin)

lazy val magnolia = crossProject(JSPlatform, JVMPlatform, NativePlatform)
.in(file("magnolia"))
.dependsOn(core)
.settings(stdSettings("zio-prelude-magnolia"))
.settings(crossProjectSettings)
.settings(macroDefinitionSettings)
.settings(Compile / console / scalacOptions ~= { _.filterNot(Set("-Xfatal-warnings")) })
.settings(buildInfoSettings("zio.prelude.magnolia"))
.settings(testFrameworks := Seq(new TestFramework("zio.test.sbt.ZTestFramework")))
.settings(dottySettings)
.settings(magnoliaSettings)
.settings(libraryDependencies += "dev.zio" %%% "zio-test-sbt" % zioVersion % Test)
.jvmSettings(scalaReflectTestSettings)
.jsSettings(jsSettings)
.nativeSettings(nativeSettings)
.enablePlugins(BuildInfoPlugin)

lazy val magnoliaTests = crossProject(JSPlatform, JVMPlatform, NativePlatform)
.in(file("magnolia-tests"))
.dependsOn(magnolia)
.settings(stdSettings("zio-prelude-magnolia-tests"))
.settings(crossProjectSettings)
.settings(buildInfoSettings("zio.prelude.magnolia.tests"))
.settings(testFrameworks := Seq(new TestFramework("zio.test.sbt.ZTestFramework")))
.settings(dottySettings)
.settings(libraryDependencies += "dev.zio" %%% "zio-test-sbt" % zioVersion % Test)
.jvmSettings(scalaReflectTestSettings)
.jsSettings(jsSettings)
.nativeSettings(nativeSettings)
.enablePlugins(BuildInfoPlugin)

lazy val experimental = crossProject(JSPlatform, JVMPlatform, NativePlatform)
.in(file("experimental"))
.dependsOn(core)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package zio.debug.magnolia

import zio.Scope
import zio.prelude._
import zio.test.Assertion._
import zio.test.{ZIOSpecDefault, _}

case class Lair(name: String, animal: Animal)

object Lair {
implicit val debug: Debug[Lair] = DeriveDebug.derived[Lair]
}

case class Animal(name: String, age: Int)

object Animal {
implicit val debug: Debug[Animal] = DeriveDebug.derived[Animal]
}

case class Adult(name: String, age: Int, chidren: List[Child])
case class Child(name: String, age: Int)

object Adult {
implicit val debug: Debug[Adult] = DeriveDebug.derived[Adult]
}

object Child {
implicit val debug: Debug[Child] = DeriveDebug.derived[Child]
}

object Test extends ZIOSpecDefault {

override def spec: Spec[TestEnvironment with Scope, Any] =
suite("DeriveDebug")(
test("should derive Debug for case class") {
val animal = Animal("tiger", 10)

assert(animal.debug.render)(equalTo("Animal(name = \"tiger\", age = 10)"))

},
test("should derive Debug for nested case class") {
val lair = Lair("jungle", Animal("tiger", 10))

assert(lair.debug.render)(equalTo("Lair(name = \"jungle\", animal = Animal(name = \"tiger\", age = 10))"))
},
test("should derive Debug for case class with list") {
val adult = Adult("John", 30, List(Child("Alice", 5), Child("Bob", 10)))

assert(adult.debug.render)(
equalTo(
"Adult(name = \"John\", age = 30, chidren = List(Child(name = \"Alice\", age = 5), Child(name = \"Bob\", age = 10)))"
)
)
}
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package zio.debug.magnolia

import magnolia1._
import zio.prelude.Debug
import zio.prelude.Debug.Repr

import scala.collection.immutable.ListMap

object DeriveDebug {

type Typeclass[T] = Debug[T]

def join[T](ctx: CaseClass[Debug, T]): Debug[T] =
if (ctx.isValueClass) { (a: T) =>
Repr.VConstructor(
ctx.typeName.owner.split('.').toList,
ctx.typeName.short,
ctx.parameters.map(p => p.typeclass.debug(p.dereference(a))).toList
)
} else if (ctx.isObject) { (_: T) =>
Repr.Object(ctx.typeName.owner.split('.').toList, ctx.typeName.short)
} else { (a: T) =>
Repr.Constructor(
ctx.typeName.owner.split('.').toList,
ctx.typeName.short,
ListMap.apply(ctx.parameters.map(p => p.label -> p.typeclass.debug(p.dereference(a))): _*)
)
}

def split[T](ctx: SealedTrait[Debug, T]): Debug[T] =
new Debug[T] { self =>
def debug(a: T): Repr = ctx.split(a) { sub =>
sub.typeclass.debug(sub.cast(a))
}
}

def derived[T]: Debug[T] = macro Magnolia.gen[T]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package zio.debug.magnolia

import magnolia1._

import scala.collection.immutable.ListMap
import scala.language.experimental.macros
import zio.prelude.Debug
import zio.prelude.Debug.Repr

object DeriveDebug extends AutoDerivation[Debug] {

type Typeclass[T] = Debug[T]

def join[T](ctx: CaseClass[Debug, T]): Debug[T] =
if (ctx.isValueClass) { (a: T) =>
Repr.VConstructor(
ctx.typeInfo.owner.split('.').toList,
ctx.typeInfo.short,
ctx.parameters.map(p => p.typeclass.debug(p.deref(a))).toList
)
} else if (ctx.isObject) { (_: T) =>
Repr.Object(ctx.typeInfo.owner.split('.').toList, ctx.typeInfo.short)
} else { (a: T) =>
Repr.Constructor(
ctx.typeInfo.owner.split('.').toList,
ctx.typeInfo.short,
ListMap.from(ctx.parameters.map(p => p.label -> p.typeclass.debug(p.deref(a))))
)
}

def split[T](ctx: SealedTrait[Debug, T]): Debug[T] =
new Debug[T] { self =>
def debug(a: T): Repr = ctx.choose(a) { sub =>
sub.typeclass.debug(sub.cast(a))
}
}

}
11 changes: 11 additions & 0 deletions project/BuildHelper.scala
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,17 @@ object BuildHelper {
Test / parallelExecution := false
)

val magnoliaSettings =
libraryDependencies += {
scalaVersion.value match {
case Scala3 =>
"com.softwaremill.magnolia1_3" %% "magnolia" % "1.3.7"
case _ =>
"com.softwaremill.magnolia1_2" %% "magnolia" % "1.1.10"
// libraryDependencies += compilerPlugin("org.typelevel" %% "kind-projector" % "0.13.3" cross CrossVersion.full)
}
}

// Keep this consistent with the version in .core-tests/shared/src/test/scala/REPLSpec.scala
val replSettings = makeReplSettings {
"""|import zio._
Expand Down

0 comments on commit 10a448e

Please sign in to comment.