diff --git a/build.sbt b/build.sbt index 9d10fff55..9ec29c065 100644 --- a/build.sbt +++ b/build.sbt @@ -48,7 +48,9 @@ val projectsCommon = List( experimentalLaws, experimentalTests, laws, - macros + macros, + magnolia, + magnoliaTests ) val projectsJvmOnly = List[ProjectReference]( @@ -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) diff --git a/magnolia-tests/shared/src/test/scala/zio/debug/magnolia/DeriveDebugSpec.scala b/magnolia-tests/shared/src/test/scala/zio/debug/magnolia/DeriveDebugSpec.scala new file mode 100644 index 000000000..5b6f8ac94 --- /dev/null +++ b/magnolia-tests/shared/src/test/scala/zio/debug/magnolia/DeriveDebugSpec.scala @@ -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)))" + ) + ) + } + ) +} diff --git a/magnolia/shared/src/main/scala-2/zio/debug/magnolia/DeriveDebug.scala b/magnolia/shared/src/main/scala-2/zio/debug/magnolia/DeriveDebug.scala new file mode 100644 index 000000000..e6f83a54d --- /dev/null +++ b/magnolia/shared/src/main/scala-2/zio/debug/magnolia/DeriveDebug.scala @@ -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] +} diff --git a/magnolia/shared/src/main/scala-3/zio/debug/magnolia/DeriveDebug.scala b/magnolia/shared/src/main/scala-3/zio/debug/magnolia/DeriveDebug.scala new file mode 100644 index 000000000..68b2f0cb9 --- /dev/null +++ b/magnolia/shared/src/main/scala-3/zio/debug/magnolia/DeriveDebug.scala @@ -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)) + } + } + +} diff --git a/project/BuildHelper.scala b/project/BuildHelper.scala index 5cf60df60..49938e34d 100644 --- a/project/BuildHelper.scala +++ b/project/BuildHelper.scala @@ -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._