diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index cff0905a4..063b4e56e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -83,7 +83,7 @@ jobs: - name: Test if: matrix.scala == '3' - run: sbt 'project ${{ matrix.project }}' '++ ${{ matrix.scala }}' shared/test test/test + run: sbt 'project ${{ matrix.project }}' '++ ${{ matrix.scala }}' shared/test test/test scalacheck/test guava/test - name: Check binary compatibility if: '!(matrix.scala == ''3'')' diff --git a/build.sbt b/build.sbt index 6a194f8f4..aa2e09e95 100644 --- a/build.sbt +++ b/build.sbt @@ -46,7 +46,7 @@ val tensorflowMetadataVersion = "1.10.0" val tensorflowVersion = "0.5.0" // project -ThisBuild / tlBaseVersion := "0.7" +ThisBuild / tlBaseVersion := "0.8" ThisBuild / tlSonatypeUseLegacyHost := true ThisBuild / organization := "com.spotify" ThisBuild / organizationName := "Spotify AB" @@ -110,7 +110,9 @@ val scala212 = "2.12.19" val scalaDefault = scala213 val scala3Projects = List( "shared", - "test" + "test", + "scalacheck", + "guava" ) // github actions @@ -267,7 +269,9 @@ val commonSettings = Seq( "-Yretain-trees", // tolerate some nested macro expansion "-Xmax-inlines", - "64" + "64", + // silence warnings. dotty doesn't have unused-imports category nor origin support yet + "-Wconf:msg=unused import:s" ) case Some((2, 13)) => Seq( @@ -373,6 +377,7 @@ lazy val scalacheck = project commonSettings, moduleName := "magnolify-scalacheck", description := "Magnolia add-on for ScalaCheck", + crossScalaVersions := Seq(scala3, scala213, scala212), libraryDependencies += "org.scalacheck" %% "scalacheck" % scalacheckVersion % Provided ) @@ -405,6 +410,7 @@ lazy val guava = project commonSettings, moduleName := "magnolify-guava", description := "Magnolia add-on for Guava", + crossScalaVersions := Seq(scala3, scala213, scala212), libraryDependencies ++= Seq( "com.google.guava" % "guava" % guavaVersion % Provided ) diff --git a/docs/scalacheck.md b/docs/scalacheck.md index 2542cf3e2..aae5826bf 100644 --- a/docs/scalacheck.md +++ b/docs/scalacheck.md @@ -25,6 +25,6 @@ import org.scalacheck._ case class Inner(int: Int, str: String) case class Outer(inner: Inner) -val arb: Arbitrary[Outer] = ArbitraryDerivation[Outer] -val cogen: Cogen[Outer] = CogenDerivation[Outer] +val arb: Arbitrary[Outer] = Arbitrary.gen[Outer] +val cogen: Cogen[Outer] = Cogen.gen[Outer] ``` diff --git a/guava/src/main/scala-2/magnolify/guava/FunnelDerivation.scala b/guava/src/main/scala-2/magnolify/guava/FunnelDerivation.scala new file mode 100644 index 000000000..b45f43bb5 --- /dev/null +++ b/guava/src/main/scala-2/magnolify/guava/FunnelDerivation.scala @@ -0,0 +1,59 @@ +/* + * Copyright 2019 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package magnolify.guava + +import com.google.common.base.Charsets +import com.google.common.hash.{Funnel, PrimitiveSink} +import magnolia1.* + +import scala.annotation.nowarn + +object FunnelDerivation { + type Typeclass[T] = Funnel[T] + + def join[T](caseClass: CaseClass[Funnel, T]): Funnel[T] = new Funnel[T] { + override def funnel(from: T, into: PrimitiveSink): Unit = + if (caseClass.isValueClass) { + val p = caseClass.parameters.head + p.typeclass.funnel(p.dereference(from), into) + } else if (caseClass.parameters.isEmpty) { + into.putString(caseClass.typeName.short, Charsets.UTF_8): @nowarn + } else { + caseClass.parameters.foreach { p => + // inject index to distinguish cases like `(Some(1), None)` and `(None, Some(1))` + into.putInt(p.index) + p.typeclass.funnel(p.dereference(from), into) + } + } + } + + def split[T](sealedTrait: SealedTrait[Funnel, T]): Funnel[T] = new Funnel[T] { + override def funnel(from: T, into: PrimitiveSink): Unit = + sealedTrait.split(from)(sub => sub.typeclass.funnel(sub.cast(from), into)) + } + + implicit def gen[T]: Funnel[T] = macro Magnolia.gen[T] + + @deprecated("Use gen instead", "0.7.0") + implicit def apply[T]: Funnel[T] = macro Magnolia.gen[T] + + @deprecated("Use funnel.contramap instead", "0.7.0") + def by[T, S](f: T => S)(implicit fnl: Funnel[S]): Funnel[T] = { + import FunnelImplicits.* + fnl.contramap(f) + } +} diff --git a/guava/src/main/scala-2/magnolify/guava/FunnelImplicits.scala b/guava/src/main/scala-2/magnolify/guava/FunnelImplicits.scala new file mode 100644 index 000000000..69c9e5233 --- /dev/null +++ b/guava/src/main/scala-2/magnolify/guava/FunnelImplicits.scala @@ -0,0 +1,51 @@ +/* + * Copyright 2023 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package magnolify.guava + +import com.google.common.hash.{Funnel, PrimitiveSink} + +trait FunnelImplicits { + import FunnelImplicits.* + def Funnel[T](implicit fnl: Funnel[T]): Funnel[T] = fnl + + implicit val intFunnel: Funnel[Int] = FunnelInstances.intFunnel() + implicit val longFunnel: Funnel[Long] = FunnelInstances.longFunnel() + implicit val bytesFunnel: Funnel[Array[Byte]] = FunnelInstances.bytesFunnel() + implicit val booleanFunnel: Funnel[Boolean] = FunnelInstances.booleanFunnel() + implicit val byteFunnel: Funnel[Byte] = FunnelInstances.byteFunnel() + implicit val charFunnel: Funnel[Char] = FunnelInstances.charFunnel() + implicit val shortFunnel: Funnel[Short] = FunnelInstances.shortFunnel() + + implicit def charSequenceFunnel[T <: CharSequence]: Funnel[T] = + FunnelInstances.charSequenceFunnel[T]() + + // There is an implicit Option[T] => Iterable[T] + implicit def iterableFunnel[T, C[_]](implicit + fnl: Funnel[T], + ti: C[T] => Iterable[T] + ): Funnel[C[T]] = FunnelInstances.iterableFunnel(fnl) + + implicit def funnelOps[T](fnl: Funnel[T]): FunnelOps[T] = new FunnelOps(fnl) +} + +object FunnelImplicits extends FunnelImplicits { + final class FunnelOps[T](val fnl: Funnel[T]) extends AnyVal { + def contramap[U](f: U => T): Funnel[U] = new Funnel[U] { + override def funnel(from: U, into: PrimitiveSink): Unit = fnl.funnel(f(from), into) + } + } +} diff --git a/guava/src/main/scala-2/magnolify/guava/GuavaMacros.scala b/guava/src/main/scala-2/magnolify/guava/GuavaMacros.scala new file mode 100644 index 000000000..cd4814f6c --- /dev/null +++ b/guava/src/main/scala-2/magnolify/guava/GuavaMacros.scala @@ -0,0 +1,39 @@ +/* + * Copyright 2023 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package magnolify.guava + +import com.google.common.hash.Funnel + +import scala.reflect.macros.* + +object GuavaMacros { + + def genFunnelMacro[T: c.WeakTypeTag](c: whitebox.Context): c.Tree = { + import c.universe.* + val wtt = weakTypeTag[T] + q"""_root_.magnolify.guava.FunnelDerivation.gen[$wtt]""" + } + +} + +trait SemiAutoDerivations { + def genFunnel[T]: Funnel[T] = macro GuavaMacros.genFunnelMacro[T] +} + +trait AutoDerivations { + implicit def genFunnel[T]: Funnel[T] = macro GuavaMacros.genFunnelMacro[T] +} diff --git a/guava/src/main/scala-3/magnolify/guava/FunnelDerivation.scala b/guava/src/main/scala-3/magnolify/guava/FunnelDerivation.scala new file mode 100644 index 000000000..6cc23b31c --- /dev/null +++ b/guava/src/main/scala-3/magnolify/guava/FunnelDerivation.scala @@ -0,0 +1,46 @@ +/* + * Copyright 2019 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package magnolify.guava + +import com.google.common.base.Charsets +import com.google.common.hash.{Funnel, PrimitiveSink} +import magnolia1.* + +import scala.annotation.nowarn +import scala.deriving.Mirror + +object FunnelDerivation extends Derivation[Funnel]: + + def join[T](caseClass: CaseClass[Funnel, T]): Funnel[T] = new Funnel[T]: + override def funnel(from: T, into: PrimitiveSink): Unit = + if (caseClass.isValueClass) + val p = caseClass.parameters.head + p.typeclass.funnel(p.deref(from), into) + else if (caseClass.parameters.isEmpty) + into.putString(caseClass.typeInfo.short, Charsets.UTF_8): @nowarn + else + caseClass.parameters.foreach { p => + // inject index to distinguish cases like `(Some(1), None)` and `(None, Some(1))` + into.putInt(p.index) + p.typeclass.funnel(p.deref(from), into) + } + + def split[T](sealedTrait: SealedTrait[Funnel, T]): Funnel[T] = new Funnel[T]: + override def funnel(from: T, into: PrimitiveSink): Unit = + sealedTrait.choose(from)(sub => sub.typeclass.funnel(sub.cast(from), into)) + + inline def gen[T](using Mirror.Of[T]): Funnel[T] = derivedMirror[T] diff --git a/guava/src/main/scala-3/magnolify/guava/FunnelImplicits.scala b/guava/src/main/scala-3/magnolify/guava/FunnelImplicits.scala new file mode 100644 index 000000000..5f4b8482b --- /dev/null +++ b/guava/src/main/scala-3/magnolify/guava/FunnelImplicits.scala @@ -0,0 +1,45 @@ +/* + * Copyright 2023 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package magnolify.guava + +import com.google.common.hash.{Funnel, PrimitiveSink} + +trait FunnelImplicits: + def Funnel[T](using fnl: Funnel[T]): Funnel[T] = fnl + + given intFunnel: Funnel[Int] = FunnelInstances.intFunnel() + given longFunnel: Funnel[Long] = FunnelInstances.longFunnel() + given bytesFunnel: Funnel[Array[Byte]] = FunnelInstances.bytesFunnel() + given booleanFunnel: Funnel[Boolean] = FunnelInstances.booleanFunnel() + given byteFunnel: Funnel[Byte] = FunnelInstances.byteFunnel() + given charFunnel: Funnel[Char] = FunnelInstances.charFunnel() + given shortFunnel: Funnel[Short] = FunnelInstances.shortFunnel() + + given charSequenceFunnel[T <: CharSequence]: Funnel[T] = + FunnelInstances.charSequenceFunnel[T]() + + // There is an implicit Option[T] => Iterable[T] + given iterableFunnel[T, C[_]](using + fnl: Funnel[T], + ti: C[T] => Iterable[T] + ): Funnel[C[T]] = FunnelInstances.iterableFunnel(fnl) + + extension [T](fnl: Funnel[T]) + def contramap[U](f: U => T): Funnel[U] = new Funnel[U]: + override def funnel(from: U, into: PrimitiveSink): Unit = fnl.funnel(f(from), into) + +object FunnelImplicits extends FunnelImplicits diff --git a/guava/src/main/scala/magnolify/guava/GuavaMacros.scala b/guava/src/main/scala-3/magnolify/guava/GuavaMacros.scala similarity index 63% rename from guava/src/main/scala/magnolify/guava/GuavaMacros.scala rename to guava/src/main/scala-3/magnolify/guava/GuavaMacros.scala index 9978fccda..bb123d138 100644 --- a/guava/src/main/scala/magnolify/guava/GuavaMacros.scala +++ b/guava/src/main/scala-3/magnolify/guava/GuavaMacros.scala @@ -1,5 +1,5 @@ /* - * Copyright 2024 Spotify AB + * Copyright 2023 Spotify AB * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,12 +16,12 @@ package magnolify.guava -import scala.reflect.macros.whitebox +import com.google.common.hash.Funnel -private object GuavaMacros { - def genFunnelMacro[T: c.WeakTypeTag](c: whitebox.Context): c.Tree = { - import c.universe._ - val wtt = weakTypeTag[T] - q"""_root_.magnolify.guava.semiauto.FunnelDerivation.apply[$wtt]""" - } -} +import scala.deriving.Mirror + +trait SemiAutoDerivations: + inline def genFunnel[T](using Mirror.Of[T]): Funnel[T] = FunnelDerivation.derivedMirror[T] + +trait AutoDerivations: + inline given genFunnel[T](using Mirror.Of[T]): Funnel[T] = FunnelDerivation.derivedMirror[T] diff --git a/guava/src/main/scala/magnolify/guava/FunnelInstances.scala b/guava/src/main/scala/magnolify/guava/FunnelInstances.scala new file mode 100644 index 000000000..b286bd985 --- /dev/null +++ b/guava/src/main/scala/magnolify/guava/FunnelInstances.scala @@ -0,0 +1,52 @@ +/* + * Copyright 2023 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package magnolify.guava + +import com.google.common.hash.{Funnel, Funnels, PrimitiveSink} + +import scala.annotation.nowarn + +object FunnelInstances { + + private def funnel[T](f: (PrimitiveSink, T) => Any): Funnel[T] = new Funnel[T] { + override def funnel(from: T, into: PrimitiveSink): Unit = f(into, from): @nowarn + } + + // respect naming convention from Funnels + def intFunnel(): Funnel[Int] = Funnels.integerFunnel().asInstanceOf[Funnel[Int]] + def longFunnel(): Funnel[Long] = Funnels.longFunnel().asInstanceOf[Funnel[Long]] + def bytesFunnel(): Funnel[Array[Byte]] = Funnels.byteArrayFunnel() + def booleanFunnel(): Funnel[Boolean] = funnel[Boolean](_.putBoolean(_)) + def byteFunnel(): Funnel[Byte] = funnel[Byte](_.putByte(_)) + def charFunnel(): Funnel[Char] = funnel[Char](_.putChar(_)) + def shortFunnel(): Funnel[Short] = funnel[Short](_.putShort(_)) + + def charSequenceFunnel[T <: CharSequence](): Funnel[T] = + Funnels.unencodedCharsFunnel().asInstanceOf[Funnel[T]] + + // There is an implicit Option[T] => Iterable[T] + def iterableFunnel[T, C[_]](fnl: Funnel[T])(implicit ti: C[T] => Iterable[T]): Funnel[C[T]] = + funnel { (sink, xs) => + var i = 0 + ti(xs).foreach { x => + fnl.funnel(x, sink) + i += 1 + } + // inject size to distinguish `None`, `Some("")`, and `List("", "", ...)` + sink.putInt(i) + } +} diff --git a/guava/src/main/scala/magnolify/guava/auto/package.scala b/guava/src/main/scala/magnolify/guava/auto/package.scala index d66fd51ce..d5d6bd6a6 100644 --- a/guava/src/main/scala/magnolify/guava/auto/package.scala +++ b/guava/src/main/scala/magnolify/guava/auto/package.scala @@ -16,11 +16,4 @@ package magnolify.guava -import com.google.common.hash.Funnel -import magnolify.guava.semiauto.FunnelImplicits - -package object auto extends FunnelImplicits with LowPriorityImplicits - -trait LowPriorityImplicits { - implicit def genFunnel[T]: Funnel[T] = macro GuavaMacros.genFunnelMacro[T] -} +package object auto extends FunnelImplicits with AutoDerivations diff --git a/guava/src/main/scala/magnolify/guava/semiauto/FunnelDerivation.scala b/guava/src/main/scala/magnolify/guava/semiauto/FunnelDerivation.scala deleted file mode 100644 index 8d5eabaab..000000000 --- a/guava/src/main/scala/magnolify/guava/semiauto/FunnelDerivation.scala +++ /dev/null @@ -1,86 +0,0 @@ -/* - * Copyright 2019 Spotify AB - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package magnolify.guava.semiauto - -import com.google.common.base.Charsets -import com.google.common.hash.{Funnel, Funnels, PrimitiveSink} -import magnolia1._ - -import scala.annotation.nowarn - -object FunnelDerivation { - type Typeclass[T] = Funnel[T] - - def join[T](caseClass: ReadOnlyCaseClass[Typeclass, T]): Typeclass[T] = new Funnel[T] { - override def funnel(from: T, into: PrimitiveSink): Unit = - if (caseClass.isValueClass) { - val p = caseClass.parameters.head - p.typeclass.funnel(p.dereference(from), into) - } else if (caseClass.parameters.isEmpty) { - into.putString(caseClass.typeName.short, Charsets.UTF_8): @nowarn - } else { - caseClass.parameters.foreach { p => - // inject index to distinguish cases like `(Some(1), None)` and `(None, Some(1))` - into.putInt(p.index) - p.typeclass.funnel(p.dereference(from), into) - } - } - } - - def split[T](sealedTrait: SealedTrait[Typeclass, T]): Typeclass[T] = new Funnel[T] { - override def funnel(from: T, into: PrimitiveSink): Unit = - sealedTrait.split(from)(sub => sub.typeclass.funnel(sub.cast(from), into)) - } - - implicit def apply[T]: Typeclass[T] = macro Magnolia.gen[T] - - def by[T, S](f: T => S)(implicit fnl: Funnel[S]): Funnel[T] = new Funnel[T] { - override def funnel(from: T, into: PrimitiveSink): Unit = fnl.funnel(f(from), into) - } -} - -trait FunnelImplicits { - private def funnel[T](f: (PrimitiveSink, T) => Unit): Funnel[T] = new Funnel[T] { - override def funnel(from: T, into: PrimitiveSink): Unit = f(into, from) - } - - implicit val intFunnel: Funnel[Int] = Funnels.integerFunnel().asInstanceOf[Funnel[Int]] - implicit val longFunnel: Funnel[Long] = Funnels.longFunnel().asInstanceOf[Funnel[Long]] - implicit val bytesFunnel: Funnel[Array[Byte]] = Funnels.byteArrayFunnel() - implicit val booleanFunnel: Funnel[Boolean] = funnel[Boolean](_.putBoolean(_): @nowarn) - implicit val byteFunnel: Funnel[Byte] = funnel[Byte](_.putByte(_): @nowarn) - implicit val charFunnel: Funnel[Char] = funnel[Char](_.putChar(_): @nowarn) - implicit val shortFunnel: Funnel[Short] = funnel[Short](_.putShort(_): @nowarn) - - implicit def charSequenceFunnel[T <: CharSequence]: Funnel[T] = - Funnels.unencodedCharsFunnel().asInstanceOf[Funnel[T]] - - // There is an implicit Option[T] => Iterable[T] - implicit def iterableFunnel[T, C[_]](implicit - fnl: Funnel[T], - ti: C[T] => Iterable[T] - ): Funnel[C[T]] = - funnel { (sink, from) => - var i = 0 - from.foreach { x => - fnl.funnel(x, sink) - i += 1 - } - // inject size to distinguish `None`, `Some("")`, and `List("", "", ...)` - sink.putInt(i): @nowarn - } -} diff --git a/guava/src/main/scala/magnolify/guava/semiauto/package.scala b/guava/src/main/scala/magnolify/guava/semiauto/package.scala index ab45a50e4..b67c45234 100644 --- a/guava/src/main/scala/magnolify/guava/semiauto/package.scala +++ b/guava/src/main/scala/magnolify/guava/semiauto/package.scala @@ -16,4 +16,8 @@ package magnolify.guava -package object semiauto extends FunnelImplicits +package object semiauto extends FunnelImplicits with SemiAutoDerivations { + + @deprecated("Use genFunnel[T] instead", "0.7.0") + val FunnelDerivation = magnolify.guava.FunnelDerivation +} diff --git a/guava/src/test/scala/magnolify/guava/FunnelDerivationSuite.scala b/guava/src/test/scala/magnolify/guava/FunnelDerivationSuite.scala index 09b4204ad..06ce1e526 100644 --- a/guava/src/test/scala/magnolify/guava/FunnelDerivationSuite.scala +++ b/guava/src/test/scala/magnolify/guava/FunnelDerivationSuite.scala @@ -16,31 +16,27 @@ package magnolify.guava -import com.google.common.hash.Funnel -import com.google.common.hash.PrimitiveSink -import magnolify.guava.auto._ -import magnolify.guava.semiauto.FunnelDerivation -import magnolify.scalacheck.auto._ -import magnolify.scalacheck.TestArbitrary._ -import magnolify.test.ADT._ -import magnolify.test.Simple._ -import magnolify.test._ -import org.scalacheck._ - -import java.io.ByteArrayInputStream -import java.io.ByteArrayOutputStream -import java.io.ObjectInputStream -import java.io.ObjectOutputStream +import com.google.common.hash.{Funnel, PrimitiveSink} +import magnolify.scalacheck.TestArbitrary.* +import magnolify.test.* +import magnolify.test.ADT.* +import magnolify.test.Simple.* +import org.scalacheck.* + +import java.io.{ByteArrayOutputStream, ObjectOutputStream} import java.net.URI import java.nio.ByteBuffer import java.nio.charset.Charset import java.time.Duration -import scala.reflect._ +import scala.reflect.* -class FunnelDerivationSuite extends MagnolifySuite { +class FunnelDerivationSuite extends MagnolifySuite with magnolify.guava.FunnelImplicits { + import magnolify.scalacheck.auto.genArbitrary + import magnolify.guava.auto.genFunnel private def test[T: ClassTag](implicit arb: Arbitrary[T], t: Funnel[T]): Unit = { - val fnl = ensureSerializable(t) + // TODO val fnl = ensureSerializable(t) + val fnl = t val name = className[T] val g = arb.arbitrary property(s"$name.uniqueness") { @@ -59,8 +55,8 @@ class FunnelDerivationSuite extends MagnolifySuite { sink.toBytes.toList } - implicit val fUri: Funnel[URI] = FunnelDerivation.by(_.toString) - implicit val fDuration: Funnel[Duration] = FunnelDerivation.by(_.toMillis) + implicit val fUri: Funnel[URI] = Funnel[String].contramap(_.toString) + implicit val fDuration: Funnel[Duration] = Funnel[Long].contramap(_.toMillis) test[Integers] test[Required] @@ -76,21 +72,27 @@ class FunnelDerivationSuite extends MagnolifySuite { test[List[Int]] test[List[Required]] - test("AnyVal") { - implicit val f: Funnel[HasValueClass] = FunnelDerivation[HasValueClass] - test[HasValueClass] - - val sink = new BytesSink() - f.funnel(HasValueClass(ValueClass("String")), sink) - - val ois = new ObjectInputStream(new ByteArrayInputStream(sink.toBytes)) - assert(ois.readInt() == 0) - "String".foreach(c => assert(ois.readChar() == c)) - assert(ois.available() == 0) - } - + // TODO scala3 value-class are mirrorless and not supported by magnolia + // test("AnyVal") { + // implicit val f: Funnel[HasValueClass] = genFunnel[HasValueClass] + // test[HasValueClass] + // + // val sink = new BytesSink() + // f.funnel(HasValueClass(ValueClass("String")), sink) + // + // val ois = new ObjectInputStream(new ByteArrayInputStream(sink.toBytes)) + // assert(ois.readInt() == 0) + // "String".foreach(c => assert(ois.readChar() == c)) + // assert(ois.available() == 0) + // } + + // magnolia scala3 limitation: + // For a recursive structures it is required to assign the derived value to an implicit variable + implicit val funnelNode: Funnel[Node] = genFunnel + implicit val funnelGNode: Funnel[GNode[Int]] = genFunnel test[Node] test[GNode[Int]] + test[Shape] test[Color] } diff --git a/guava/src/test/scala/magnolify/guava/ScopeTest.scala b/guava/src/test/scala/magnolify/guava/ScopeTest.scala index 2fe3aa846..1d074723e 100644 --- a/guava/src/test/scala/magnolify/guava/ScopeTest.scala +++ b/guava/src/test/scala/magnolify/guava/ScopeTest.scala @@ -17,16 +17,21 @@ package magnolify.guava import com.google.common.hash.Funnel -import magnolify.test.Simple._ +import magnolify.test.Simple.* object ScopeTest { + object Auto { - import magnolify.guava.auto._ + import magnolify.guava.auto.genFunnel + import magnolify.guava.auto.intFunnel + import magnolify.guava.auto.longFunnel implicitly[Funnel[Integers]] } object Semi { - import magnolify.guava.semiauto._ - FunnelDerivation[Integers] + import magnolify.guava.semiauto.genFunnel + import magnolify.guava.semiauto.intFunnel + import magnolify.guava.semiauto.longFunnel + genFunnel[Integers] } } diff --git a/refined/src/test/scala/magnolify/refined/RefinedSuite.scala b/refined/src/test/scala/magnolify/refined/RefinedSuite.scala index 6eff3de7c..e46a82190 100644 --- a/refined/src/test/scala/magnolify/refined/RefinedSuite.scala +++ b/refined/src/test/scala/magnolify/refined/RefinedSuite.scala @@ -22,8 +22,7 @@ import eu.timepit.refined.auto._ import eu.timepit.refined.boolean._ import eu.timepit.refined.numeric._ import eu.timepit.refined.string._ -import magnolify.guava.BytesSink -import magnolify.guava.semiauto.FunnelDerivation +import magnolify.guava.{BytesSink, FunnelDerivation} import magnolify.test._ object RefinedSuite { diff --git a/scalacheck/src/main/scala-2/magnolify/scalacheck/ArbitraryDerivation.scala b/scalacheck/src/main/scala-2/magnolify/scalacheck/ArbitraryDerivation.scala new file mode 100644 index 000000000..9ff13415e --- /dev/null +++ b/scalacheck/src/main/scala-2/magnolify/scalacheck/ArbitraryDerivation.scala @@ -0,0 +1,58 @@ +/* + * Copyright 2024 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package magnolify.scalacheck + +import magnolia1.* +import org.scalacheck.{Arbitrary, Gen} + +object ArbitraryDerivation { + type Typeclass[T] = Arbitrary[T] + + private implicit val monadicGen: Monadic[Gen] = new Monadic[Gen] { + override def point[A](value: A): Gen[A] = Gen.const(value) + override def map[A, B](from: Gen[A])(fn: A => B): Gen[B] = from.map(fn) + override def flatMap[A, B](from: Gen[A])(fn: A => Gen[B]): Gen[B] = from.flatMap(fn) + } + + def join[T](caseClass: CaseClass[Arbitrary, T]): Arbitrary[T] = Arbitrary { + caseClass.constructMonadic(_.typeclass.arbitrary) + } + + def split[T](sealedTrait: SealedTrait[Arbitrary, T]): Arbitrary[T] = Arbitrary { + Gen.lzy { + Gen.sized { size => + val subtypes = sealedTrait.subtypes + for { + i <- + if (size >= 0) { + // pick any subtype + Gen.choose(0, subtypes.size - 1) + } else { + // pick a fixed subtype to have a chance to stop recursion + Gen.const(subtypes.size + size) + } + subtypeGen <- Gen.resize(size - 1, subtypes(i).typeclass.arbitrary) + } yield subtypeGen + } + } + } + + implicit def gen[T]: Arbitrary[T] = macro Magnolia.gen[T] + + @deprecated("Use gen instead", "0.7.0") + def apply[T]: Arbitrary[T] = macro Magnolia.gen[T] +} diff --git a/scalacheck/src/main/scala/magnolify/scalacheck/semiauto/CogenDerivation.scala b/scalacheck/src/main/scala-2/magnolify/scalacheck/CogenDerivation.scala similarity index 61% rename from scalacheck/src/main/scala/magnolify/scalacheck/semiauto/CogenDerivation.scala rename to scalacheck/src/main/scala-2/magnolify/scalacheck/CogenDerivation.scala index 3f29618b2..88e5b4f93 100644 --- a/scalacheck/src/main/scala/magnolify/scalacheck/semiauto/CogenDerivation.scala +++ b/scalacheck/src/main/scala-2/magnolify/scalacheck/CogenDerivation.scala @@ -14,29 +14,29 @@ * limitations under the License. */ -package magnolify.scalacheck.semiauto +package magnolify.scalacheck -import magnolia1._ +import magnolia1.* import org.scalacheck.Cogen object CogenDerivation { type Typeclass[T] = Cogen[T] - def join[T](caseClass: ReadOnlyCaseClass[Typeclass, T]): Typeclass[T] = Cogen { (seed, t) => - caseClass.parameters.foldLeft(seed) { (seed, p) => + def join[T](caseClass: CaseClass[Cogen, T]): Cogen[T] = Cogen { (seed, t) => + caseClass.parameters.foldLeft(seed) { (s, p) => // inject index to distinguish cases like `(Some(false), None)` and `(None, Some(0))` - val s = Cogen.cogenInt.perturb(seed, p.index) - p.typeclass.perturb(s, p.dereference(t)) + p.typeclass.perturb(Cogen.perturb(s, p.index), p.dereference(t)) } } - def split[T](sealedTrait: SealedTrait[Typeclass, T]): Typeclass[T] = Cogen { (seed, t: T) => + def split[T](sealedTrait: SealedTrait[Cogen, T]): Cogen[T] = Cogen { (seed, t) => sealedTrait.split(t) { sub => // inject index to distinguish case objects instances - val s = Cogen.cogenInt.perturb(seed, sub.index) - sub.typeclass.perturb(s, sub.cast(t)) + sub.typeclass.perturb(Cogen.perturb(seed, sub.index), sub.cast(t)) } } - implicit def apply[T]: Typeclass[T] = macro Magnolia.gen[T] + implicit def gen[T]: Cogen[T] = macro Magnolia.gen[T] + @deprecated("Use gen instead", "0.7.0") + def apply[T]: Cogen[T] = macro Magnolia.gen[T] } diff --git a/scalacheck/src/main/scala-2/magnolify/scalacheck/ScalacheckMacros.scala b/scalacheck/src/main/scala-2/magnolify/scalacheck/ScalacheckMacros.scala new file mode 100644 index 000000000..5e97d5417 --- /dev/null +++ b/scalacheck/src/main/scala-2/magnolify/scalacheck/ScalacheckMacros.scala @@ -0,0 +1,40 @@ +/* + * Copyright 2023 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package magnolify.scalacheck + +import org.scalacheck.{Arbitrary, Cogen} + +import scala.reflect.macros.* +object ScalaCheckMacros { + def genArbitraryMacro[T: c.WeakTypeTag](c: whitebox.Context): c.Tree = { + import c.universe._ + val wtt = weakTypeTag[T] + q"""_root_.magnolify.scalacheck.ArbitraryDerivation.gen[$wtt]""" + } + + def genCogenMacro[T: c.WeakTypeTag](c: whitebox.Context): c.Tree = { + import c.universe._ + val wtt = weakTypeTag[T] + q"""_root_.magnolify.scalacheck.CogenDerivation.gen[$wtt]""" + } + +} + +trait AutoDerivations { + implicit def genArbitrary[T]: Arbitrary[T] = macro ScalaCheckMacros.genArbitraryMacro[T] + implicit def genCogen[T]: Cogen[T] = macro ScalaCheckMacros.genCogenMacro[T] +} diff --git a/scalacheck/src/main/scala-3/magnolify/scalacheck/ArbitraryDerivation.scala b/scalacheck/src/main/scala-3/magnolify/scalacheck/ArbitraryDerivation.scala new file mode 100644 index 000000000..0a32e7262 --- /dev/null +++ b/scalacheck/src/main/scala-3/magnolify/scalacheck/ArbitraryDerivation.scala @@ -0,0 +1,54 @@ +/* + * Copyright 2024 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package magnolify.scalacheck + +import magnolia1.* +import org.scalacheck.{Arbitrary, Gen} + +import scala.deriving.Mirror + +object ArbitraryDerivation extends Derivation[Arbitrary]: + + private given Monadic[Gen] with + def point[A](value: A): Gen[A] = Gen.const(value) + def map[A, B](from: Gen[A])(fn: A => B): Gen[B] = from.map(fn) + def flatMap[A, B](from: Gen[A])(fn: A => Gen[B]): Gen[B] = from.flatMap(fn) + + def join[T](caseClass: CaseClass[Arbitrary, T]): Arbitrary[T] = Arbitrary { + caseClass.constructMonadic(_.typeclass.arbitrary) + } + + def split[T](sealedTrait: SealedTrait[Arbitrary, T]): Arbitrary[T] = Arbitrary { + Gen.lzy { + Gen.sized { size => + val subtypes = sealedTrait.subtypes + for { + i <- + if (size >= 0) { + // pick any subtype + Gen.choose(0, subtypes.size - 1) + } else { + // pick a fixed subtype to have a chance to stop recursion + Gen.const(subtypes.size + size) + } + subtypeGen <- Gen.resize(size - 1, subtypes(i).typeclass.arbitrary) + } yield subtypeGen + } + } + } + + inline def gen[T](using Mirror.Of[T]): Arbitrary[T] = derivedMirror[T] diff --git a/scalacheck/src/main/scala-3/magnolify/scalacheck/CogenDerivation.scala b/scalacheck/src/main/scala-3/magnolify/scalacheck/CogenDerivation.scala new file mode 100644 index 000000000..3cb8b226a --- /dev/null +++ b/scalacheck/src/main/scala-3/magnolify/scalacheck/CogenDerivation.scala @@ -0,0 +1,40 @@ +/* + * Copyright 2023 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package magnolify.scalacheck + +import magnolia1.* +import org.scalacheck.Cogen + +import scala.deriving.Mirror + +object CogenDerivation extends Derivation[Cogen]: + + def join[T](caseClass: CaseClass[Cogen, T]): Cogen[T] = Cogen[T] { (seed, t) => + caseClass.params.foldLeft(seed) { (s, p) => + // inject index to distinguish cases like `(Some(false), None)` and `(None, Some(0))` + p.typeclass.perturb(Cogen.perturb(s, p.index), p.deref(t)) + } + } + + def split[T](sealedTrait: SealedTrait[Cogen, T]): Cogen[T] = Cogen[T] { (seed, t) => + sealedTrait.choose(t) { sub => + // inject index to distinguish case objects instances + sub.typeclass.perturb(Cogen.perturb(seed, sub.subtype.index), sub.cast(t)) + } + } + + inline def gen[T](using Mirror.Of[T]): Cogen[T] = derivedMirror[T] diff --git a/scalacheck/src/main/scala-3/magnolify/scalacheck/ScalacheckMacros.scala b/scalacheck/src/main/scala-3/magnolify/scalacheck/ScalacheckMacros.scala new file mode 100644 index 000000000..789fe7ca8 --- /dev/null +++ b/scalacheck/src/main/scala-3/magnolify/scalacheck/ScalacheckMacros.scala @@ -0,0 +1,27 @@ +/* + * Copyright 2024 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package magnolify.scalacheck + +import org.scalacheck.{Arbitrary, Cogen} + +import scala.deriving.Mirror + +trait AutoDerivations: + inline implicit def genArbitrary[T](using Mirror.Of[T]): Arbitrary[T] = + ArbitraryDerivation.derivedMirror[T] + inline implicit def genCogen[T](using Mirror.Of[T]): Cogen[T] = + CogenDerivation.derivedMirror[T] diff --git a/scalacheck/src/main/scala/magnolify/scalacheck/auto/package.scala b/scalacheck/src/main/scala/magnolify/scalacheck/auto/package.scala index e416350b1..fbf9a262e 100644 --- a/scalacheck/src/main/scala/magnolify/scalacheck/auto/package.scala +++ b/scalacheck/src/main/scala/magnolify/scalacheck/auto/package.scala @@ -16,23 +16,4 @@ package magnolify.scalacheck -import org.scalacheck._ - -import scala.reflect.macros._ - -package object auto { - def genArbitraryMacro[T: c.WeakTypeTag](c: whitebox.Context): c.Tree = { - import c.universe._ - val wtt = weakTypeTag[T] - q"""_root_.magnolify.scalacheck.semiauto.ArbitraryDerivation.apply[$wtt]""" - } - - def genCogenMacro[T: c.WeakTypeTag](c: whitebox.Context): c.Tree = { - import c.universe._ - val wtt = weakTypeTag[T] - q"""_root_.magnolify.scalacheck.semiauto.CogenDerivation.apply[$wtt]""" - } - - implicit def genArbitrary[T]: Arbitrary[T] = macro genArbitraryMacro[T] - implicit def genCogen[T]: Cogen[T] = macro genCogenMacro[T] -} +package object auto extends AutoDerivations diff --git a/scalacheck/src/main/scala/magnolify/scalacheck/semiauto/ArbitraryDerivation.scala b/scalacheck/src/main/scala/magnolify/scalacheck/semiauto/ArbitraryDerivation.scala deleted file mode 100644 index e6a882367..000000000 --- a/scalacheck/src/main/scala/magnolify/scalacheck/semiauto/ArbitraryDerivation.scala +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright 2019 Spotify AB - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package magnolify.scalacheck.semiauto - -import magnolia1._ -import org.scalacheck.rng.Seed -import org.scalacheck.{Arbitrary, Gen} - -object ArbitraryDerivation { - type Typeclass[T] = Arbitrary[T] - - def join[T: Fallback](caseClass: CaseClass[Typeclass, T]): Typeclass[T] = Arbitrary { - Gen.lzy(Gen.sized { size => - if (size >= 0) { - Gen.resize(size - 1, caseClass.constructMonadic(_.typeclass.arbitrary)(monadicGen)) - } else { - implicitly[Fallback[T]].get - } - }) - } - - def split[T: Fallback](sealedTrait: SealedTrait[Typeclass, T]): Typeclass[T] = Arbitrary { - if (sealedTrait.typeName.full == classOf[Seed].getCanonicalName) { - // Prevent derivation of invalid seed via `Seed.apply(0, 0, 0, 0)` - // https://github.com/typelevel/scalacheck/pull/674 - Arbitrary.arbLong.arbitrary.map(Seed(_)).asInstanceOf[Gen[T]] - } else { - Gen.sized { size => - if (size > 0) { - Gen.resize( - size - 1, - Gen.oneOf(sealedTrait.subtypes.map(_.typeclass.arbitrary)).flatMap(identity) - ) - } else { - implicitly[Fallback[T]].get - } - } - } - } - - implicit def apply[T]: Typeclass[T] = macro Magnolia.gen[T] - - private val monadicGen: Monadic[Gen] = new Monadic[Gen] { - override def point[A](value: A): Gen[A] = Gen.const(value) - override def map[A, B](from: Gen[A])(fn: A => B): Gen[B] = from.map(fn) - override def flatMap[A, B](from: Gen[A])(fn: A => Gen[B]): Gen[B] = from.flatMap(fn) - } - - sealed trait Fallback[+T] extends Serializable { - def get: Gen[T] - } - - object Fallback { - - object NoFallback extends Fallback[Nothing] { - override def get: Gen[Nothing] = Gen.fail - } - - def apply[T](g: Gen[T]): Fallback[T] = new Fallback[T] { - override def get: Gen[T] = g - } - - def apply[T](v: T): Fallback[T] = Fallback[T](Gen.const(v)) - def apply[T](implicit arb: Arbitrary[T]): Fallback[T] = Fallback[T](arb.arbitrary) - - implicit def defaultFallback[T]: Fallback[T] = NoFallback - } -} diff --git a/scalacheck/src/main/scala/magnolify/scalacheck/semiauto/package.scala b/scalacheck/src/main/scala/magnolify/scalacheck/semiauto/package.scala new file mode 100644 index 000000000..49968882c --- /dev/null +++ b/scalacheck/src/main/scala/magnolify/scalacheck/semiauto/package.scala @@ -0,0 +1,32 @@ +/* + * Copyright 2023 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package magnolify.scalacheck + +import org.scalacheck.{Arbitrary, Cogen} + +package object semiauto { + + @deprecated("Use Arbitrary.gen[T] instead", "0.7.0") + val ArbitraryDerivation = magnolify.scalacheck.ArbitraryDerivation + @deprecated("Use Gogen.gen[T] instead", "0.7.0") + val CogenDerivation = magnolify.scalacheck.CogenDerivation + + implicit def genArbitrary(a: Arbitrary.type): magnolify.scalacheck.ArbitraryDerivation.type = + magnolify.scalacheck.ArbitraryDerivation + implicit def genCogen(c: Cogen.type): magnolify.scalacheck.CogenDerivation.type = + magnolify.scalacheck.CogenDerivation +} diff --git a/scalacheck/src/test/scala-3/magnolify/scalacheck/MoreCollectionsBuildable.scala b/scalacheck/src/test/scala-3/magnolify/scalacheck/MoreCollectionsBuildable.scala new file mode 100644 index 000000000..57404f0ba --- /dev/null +++ b/scalacheck/src/test/scala-3/magnolify/scalacheck/MoreCollectionsBuildable.scala @@ -0,0 +1,19 @@ +/* + * Copyright 2022 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package magnolify.scalacheck + +object MoreCollectionsBuildable diff --git a/scalacheck/src/test/scala/magnolify/scalacheck/ArbitraryDerivationSuite.scala b/scalacheck/src/test/scala/magnolify/scalacheck/ArbitraryDerivationSuite.scala index 59b896ed3..692fafafa 100644 --- a/scalacheck/src/test/scala/magnolify/scalacheck/ArbitraryDerivationSuite.scala +++ b/scalacheck/src/test/scala/magnolify/scalacheck/ArbitraryDerivationSuite.scala @@ -16,7 +16,6 @@ package magnolify.scalacheck -import magnolify.scalacheck.auto._ import magnolify.scalacheck.MoreCollectionsBuildable._ // extra scala 2.12 Buildable import magnolify.scalacheck.TestArbitrary.arbDuration import magnolify.scalacheck.TestArbitrary.arbUri @@ -30,13 +29,13 @@ import org.scalacheck.rng.Seed import scala.reflect._ class ArbitraryDerivationSuite extends MagnolifySuite { + import TestArbitrary.arbSeed + import magnolify.scalacheck.auto.genArbitrary - private def test[T: Arbitrary: ClassTag]: Unit = test[T](None) - private def test[T: Arbitrary: ClassTag](suffix: String): Unit = test[T](Some(suffix)) - - private def test[T: ClassTag](suffix: Option[String])(implicit t: Arbitrary[T]): Unit = { - val g = ensureSerializable(t).arbitrary - val name = className[T] + (if (suffix == null) "" else "." + suffix) + private def test[T: ClassTag](implicit t: Arbitrary[T]): Unit = { + // TODO val g = ensureSerializable(t).arbitrary + val g = t.arbitrary + val name = className[T] val prms = Gen.Parameters.default // `forAll(Gen.listOfN(10, g))` fails for `Repeated` & `Collections` when size parameter <= 1 property(s"$name.uniqueness") { @@ -72,36 +71,13 @@ class ArbitraryDerivationSuite extends MagnolifySuite { test[Custom] - { - import magnolify.scalacheck.semiauto.ArbitraryDerivation.Fallback - implicit val f: Fallback[Node] = Fallback[Leaf] - test[Node] - } - - { - import magnolify.scalacheck.semiauto.ArbitraryDerivation.Fallback - implicit val f: Fallback[GNode[Int]] = Fallback(Gen.const(GLeaf(0))) - test[GNode[Int]]("Fallback(G: Gen[T])") - } - - { - import magnolify.scalacheck.semiauto.ArbitraryDerivation.Fallback - implicit val f: Fallback[GNode[Int]] = Fallback(GLeaf(0)) - test[GNode[Int]]("Fallback(v: T)") - } - - { - import magnolify.scalacheck.semiauto.ArbitraryDerivation.Fallback - implicit val f: Fallback[GNode[Int]] = Fallback[GLeaf[Int]] - test[GNode[Int]]("Fallback[T]") - } + // magnolia scala3 limitation: + // For a recursive structures it is required to assign the derived value to an implicit variable + implicit val arbNode: Arbitrary[Node] = genArbitrary + implicit val arbGNode: Arbitrary[GNode[Int]] = genArbitrary + test[Node] + test[GNode[Int]] test[Shape] test[Color] - - property("Seed") { - Prop.forAll { (seed: Seed) => - seed.next != seed - } - } } diff --git a/scalacheck/src/test/scala/magnolify/scalacheck/CogenDerivationSuite.scala b/scalacheck/src/test/scala/magnolify/scalacheck/CogenDerivationSuite.scala index 8109c0a14..e491eae68 100644 --- a/scalacheck/src/test/scala/magnolify/scalacheck/CogenDerivationSuite.scala +++ b/scalacheck/src/test/scala/magnolify/scalacheck/CogenDerivationSuite.scala @@ -16,20 +16,22 @@ package magnolify.scalacheck -import magnolify.scalacheck.auto._ -import magnolify.test.ADT._ -import magnolify.test.Simple._ -import magnolify.test._ -import org.scalacheck._ +import magnolify.test.* +import magnolify.test.ADT.* +import magnolify.test.Simple.* +import org.scalacheck.* import org.scalacheck.rng.Seed -import scala.reflect._ import java.net.URI +import scala.reflect.* class CogenDerivationSuite extends MagnolifySuite { + import TestArbitrary.arbSeed + import magnolify.scalacheck.auto.genCogen private def test[T: ClassTag](implicit arb: Arbitrary[T], t: Cogen[T]): Unit = { - val co = ensureSerializable(t) + // TODO val co = ensureSerializable(t) + val co = t val name = className[T] implicit val arbList: Arbitrary[List[T]] = Arbitrary(Gen.listOfN(10, arb.arbitrary)) property(s"$name.uniqueness") { @@ -44,7 +46,7 @@ class CogenDerivationSuite extends MagnolifySuite { } } - import magnolify.scalacheck.TestArbitrary._ + import magnolify.scalacheck.TestArbitrary.* implicit val cogenUri: Cogen[URI] = Cogen(_.hashCode().toLong) test[Numbers] @@ -56,6 +58,11 @@ class CogenDerivationSuite extends MagnolifySuite { test[Nested] test[Custom] + // magnolia scala3 limitation: + // For a recursive structures it is required to assign the derived value to an implicit variable + implicit val cogenNode: Cogen[Node] = genCogen + implicit val cogenGNode: Cogen[GNode[Int]] = genCogen + test[Node] test[GNode[Int]] test[Shape] diff --git a/scalacheck/src/test/scala/magnolify/scalacheck/FunctionDerivationSuite.scala b/scalacheck/src/test/scala/magnolify/scalacheck/FunctionDerivationSuite.scala index 7b18cc1b5..6db978569 100644 --- a/scalacheck/src/test/scala/magnolify/scalacheck/FunctionDerivationSuite.scala +++ b/scalacheck/src/test/scala/magnolify/scalacheck/FunctionDerivationSuite.scala @@ -18,18 +18,21 @@ package magnolify.scalacheck import magnolify.test.Simple._ import magnolify.test.ADT._ -import magnolify.scalacheck.auto._ import magnolify.test._ import org.scalacheck._ import scala.reflect._ class FunctionDerivationSuite extends MagnolifySuite { + import magnolify.scalacheck.auto.genCogen + import magnolify.scalacheck.auto.genArbitrary + private def test[A: ClassTag, B: ClassTag](implicit t: Arbitrary[A => B], arbA: Arbitrary[A] ): Unit = { - val gf = ensureSerializable(t).arbitrary + // TODO val gf = ensureSerializable(t).arbitrary + val gf = t.arbitrary val ga = arbA.arbitrary val name = s"${className[A]}.${className[B]}" property(s"$name.consistency") { @@ -45,14 +48,7 @@ class FunctionDerivationSuite extends MagnolifySuite { } test[Numbers, Numbers] - - { - // Gen[A => B] depends on Gen[B] and may run out of size - import magnolify.scalacheck.semiauto.ArbitraryDerivation.Fallback - implicit val f: Fallback[Shape] = Fallback[Circle] - test[Shape, Shape] - test[Numbers, Shape] - } - + test[Shape, Shape] + test[Numbers, Shape] test[Shape, Numbers] } diff --git a/scalacheck/src/test/scala/magnolify/scalacheck/ScopeTest.scala b/scalacheck/src/test/scala/magnolify/scalacheck/ScopeTest.scala index df59d2e07..8687371c0 100644 --- a/scalacheck/src/test/scala/magnolify/scalacheck/ScopeTest.scala +++ b/scalacheck/src/test/scala/magnolify/scalacheck/ScopeTest.scala @@ -16,21 +16,21 @@ package magnolify.scalacheck -import magnolify.test.Simple._ -import org.scalacheck._ +import magnolify.test.Simple.* +import org.scalacheck.* object ScopeTest { object Auto { - import magnolify.scalacheck.auto._ + import magnolify.scalacheck.auto.* implicitly[Arbitrary[Numbers]] implicitly[Cogen[Numbers]] implicitly[Arbitrary[Numbers => Numbers]] } object Semi { - import magnolify.scalacheck.semiauto._ - implicit val arb: Arbitrary[Numbers] = ArbitraryDerivation[Numbers] - implicit val cogen: Cogen[Numbers] = CogenDerivation[Numbers] + import magnolify.scalacheck.semiauto.* + implicit val arb: Arbitrary[Numbers] = Arbitrary.gen[Numbers] + implicit val cogen: Cogen[Numbers] = Cogen.gen[Numbers] // T => T is not a case class, so ArbitraryDerivation.apply won't work implicitly[Arbitrary[Numbers => Numbers]] } diff --git a/scalacheck/src/test/scala/magnolify/scalacheck/TestArbitrary.scala b/scalacheck/src/test/scala/magnolify/scalacheck/TestArbitrary.scala index c0d9de75e..513a1e71c 100644 --- a/scalacheck/src/test/scala/magnolify/scalacheck/TestArbitrary.scala +++ b/scalacheck/src/test/scala/magnolify/scalacheck/TestArbitrary.scala @@ -16,19 +16,25 @@ package magnolify.scalacheck -import magnolify.scalacheck.semiauto.ArbitraryDerivation +import magnolify.scalacheck.semiauto.* import magnolify.shared.UnsafeEnum -import magnolify.test.ADT._ +import magnolify.test.ADT.* import magnolify.test.JavaEnums -import magnolify.test.Simple._ -import org.joda.{time => joda} -import org.scalacheck._ +import magnolify.test.Simple.* +import org.joda.time as joda +import org.scalacheck.* +import org.scalacheck.rng.Seed import java.net.URI import java.nio.ByteBuffer -import java.time._ +import java.time.* object TestArbitrary { + // seed + implicit lazy val arbSeed: Arbitrary[Seed] = Arbitrary( + Arbitrary.arbLong.arbitrary.map(Seed.apply) + ) + // null implicit lazy val arbNull: Arbitrary[Null] = Arbitrary(Gen.const(null)) @@ -93,29 +99,29 @@ object TestArbitrary { } // ADT - implicit lazy val arbNode: Arbitrary[Node] = ArbitraryDerivation[Node] - implicit lazy val arbGNode: Arbitrary[GNode[Int]] = ArbitraryDerivation[GNode[Int]] - implicit lazy val arbShape: Arbitrary[Shape] = ArbitraryDerivation[Shape] - implicit lazy val arbColor: Arbitrary[Color] = ArbitraryDerivation[Color] - implicit lazy val arbPerson: Arbitrary[Person] = ArbitraryDerivation[Person] + implicit lazy val arbNode: Arbitrary[Node] = Arbitrary.gen[Node] + implicit lazy val arbGNode: Arbitrary[GNode[Int]] = Arbitrary.gen[GNode[Int]] + implicit lazy val arbShape: Arbitrary[Shape] = Arbitrary.gen[Shape] + implicit lazy val arbColor: Arbitrary[Color] = Arbitrary.gen[Color] + implicit lazy val arbPerson: Arbitrary[Person] = Arbitrary.gen[Person] // simple - implicit lazy val arbIntegers: Arbitrary[Integers] = ArbitraryDerivation[Integers] - implicit lazy val arbFloats: Arbitrary[Floats] = ArbitraryDerivation[Floats] - implicit lazy val arbNumbers: Arbitrary[Numbers] = ArbitraryDerivation[Numbers] - implicit lazy val arbRequired: Arbitrary[Required] = ArbitraryDerivation[Required] - implicit lazy val arbNullable: Arbitrary[Nullable] = ArbitraryDerivation[Nullable] - implicit lazy val arbRepeated: Arbitrary[Repeated] = ArbitraryDerivation[Repeated] - implicit lazy val arbNested: Arbitrary[Nested] = ArbitraryDerivation[Nested] - implicit lazy val arbCollections: Arbitrary[Collections] = ArbitraryDerivation[Collections] + implicit lazy val arbIntegers: Arbitrary[Integers] = Arbitrary.gen[Integers] + implicit lazy val arbFloats: Arbitrary[Floats] = Arbitrary.gen[Floats] + implicit lazy val arbNumbers: Arbitrary[Numbers] = Arbitrary.gen[Numbers] + implicit lazy val arbRequired: Arbitrary[Required] = Arbitrary.gen[Required] + implicit lazy val arbNullable: Arbitrary[Nullable] = Arbitrary.gen[Nullable] + implicit lazy val arbRepeated: Arbitrary[Repeated] = Arbitrary.gen[Repeated] + implicit lazy val arbNested: Arbitrary[Nested] = Arbitrary.gen[Nested] + implicit lazy val arbCollections: Arbitrary[Collections] = Arbitrary.gen[Collections] implicit lazy val arbMoreCollections: Arbitrary[MoreCollections] = - ArbitraryDerivation[MoreCollections] - implicit lazy val arbEnums: Arbitrary[Enums] = ArbitraryDerivation[Enums] - implicit lazy val arbUnsafeEnums: Arbitrary[UnsafeEnums] = ArbitraryDerivation[UnsafeEnums] - implicit lazy val arbCustom: Arbitrary[Custom] = ArbitraryDerivation[Custom] - implicit lazy val arbLowerCamel: Arbitrary[LowerCamel] = ArbitraryDerivation[LowerCamel] + Arbitrary.gen[MoreCollections] + implicit lazy val arbEnums: Arbitrary[Enums] = Arbitrary.gen[Enums] + implicit lazy val arbUnsafeEnums: Arbitrary[UnsafeEnums] = Arbitrary.gen[UnsafeEnums] + implicit lazy val arbCustom: Arbitrary[Custom] = Arbitrary.gen[Custom] + implicit lazy val arbLowerCamel: Arbitrary[LowerCamel] = Arbitrary.gen[LowerCamel] implicit lazy val arbLowerCamelInner: Arbitrary[LowerCamelInner] = - ArbitraryDerivation[LowerCamelInner] + Arbitrary.gen[LowerCamelInner] // other implicit lazy val arbUri: Arbitrary[URI] = Arbitrary(Gen.alphaNumStr.map(URI.create)) diff --git a/scalacheck/src/test/scala/magnolify/scalacheck/TestCogen.scala b/scalacheck/src/test/scala/magnolify/scalacheck/TestCogen.scala index b32e59f8b..418f21e7f 100644 --- a/scalacheck/src/test/scala/magnolify/scalacheck/TestCogen.scala +++ b/scalacheck/src/test/scala/magnolify/scalacheck/TestCogen.scala @@ -16,11 +16,13 @@ package magnolify.scalacheck -import magnolify.scalacheck.semiauto.CogenDerivation -import magnolify.test.ADT._ +import magnolify.scalacheck.semiauto.* +import magnolify.shared.UnsafeEnum +import magnolify.test.ADT.* import magnolify.test.JavaEnums -import magnolify.test.Simple._ +import magnolify.test.Simple.* import org.scalacheck.Cogen +import org.scalacheck.rng.Seed import java.net.URI @@ -28,29 +30,36 @@ object TestCogen { // enum implicit lazy val coJavaEnum: Cogen[JavaEnums.Color] = Cogen(_.ordinal().toLong) implicit lazy val coScalaEnums: Cogen[ScalaEnums.Color.Type] = Cogen(_.id.toLong) + implicit def coUnsafeEnum[T: Cogen]: Cogen[UnsafeEnum[T]] = + Cogen { (seed: Seed, value: UnsafeEnum[T]) => + value match { + case UnsafeEnum.Known(v) => Cogen[T].perturb(seed, v) + case UnsafeEnum.Unknown(v) => Cogen[String].perturb(seed, v) + } + } // ADT - implicit lazy val coNode: Cogen[Node] = CogenDerivation[Node] - implicit lazy val coGNode: Cogen[GNode[Int]] = CogenDerivation[GNode[Int]] - implicit lazy val coShape: Cogen[Shape] = CogenDerivation[Shape] - implicit lazy val coColor: Cogen[Color] = CogenDerivation[Color] - implicit lazy val coPerson: Cogen[Person] = CogenDerivation[Person] + implicit lazy val coNode: Cogen[Node] = Cogen.gen[Node] + implicit lazy val coGNode: Cogen[GNode[Int]] = Cogen.gen[GNode[Int]] + implicit lazy val coShape: Cogen[Shape] = Cogen.gen[Shape] + implicit lazy val coColor: Cogen[Color] = Cogen.gen[Color] + implicit lazy val coPerson: Cogen[Person] = Cogen.gen[Person] // simple - implicit lazy val coIntegers: Cogen[Integers] = CogenDerivation[Integers] - implicit lazy val coFloats: Cogen[Floats] = CogenDerivation[Floats] - implicit lazy val coNumbers: Cogen[Numbers] = CogenDerivation[Numbers] - implicit lazy val coRequired: Cogen[Required] = CogenDerivation[Required] - implicit lazy val coNullable: Cogen[Nullable] = CogenDerivation[Nullable] - implicit lazy val coRepeated: Cogen[Repeated] = CogenDerivation[Repeated] - implicit lazy val coNested: Cogen[Nested] = CogenDerivation[Nested] - implicit lazy val coCollections: Cogen[Collections] = CogenDerivation[Collections] - // implicit lazy val coMoreCollections: Cogen[MoreCollections] = CogenDerivation[MoreCollections] - implicit lazy val coEnums: Cogen[Enums] = CogenDerivation[Enums] - implicit lazy val coUnsafeEnums: Cogen[UnsafeEnums] = CogenDerivation[UnsafeEnums] - implicit lazy val coCustom: Cogen[Custom] = CogenDerivation[Custom] - implicit lazy val coLowerCamel: Cogen[LowerCamel] = CogenDerivation[LowerCamel] - implicit lazy val coLowerCamelInner: Cogen[LowerCamelInner] = CogenDerivation[LowerCamelInner] + implicit lazy val coIntegers: Cogen[Integers] = Cogen.gen[Integers] + implicit lazy val coFloats: Cogen[Floats] = Cogen.gen[Floats] + implicit lazy val coNumbers: Cogen[Numbers] = Cogen.gen[Numbers] + implicit lazy val coRequired: Cogen[Required] = Cogen.gen[Required] + implicit lazy val coNullable: Cogen[Nullable] = Cogen.gen[Nullable] + implicit lazy val coRepeated: Cogen[Repeated] = Cogen.gen[Repeated] + implicit lazy val coNested: Cogen[Nested] = Cogen.gen[Nested] + implicit lazy val coCollections: Cogen[Collections] = Cogen.gen[Collections] + // implicit lazy val coMoreCollections: Cogen[MoreCollections] = Cogen.gen[MoreCollections] + implicit lazy val coEnums: Cogen[Enums] = Cogen.gen[Enums] + implicit lazy val coUnsafeEnums: Cogen[UnsafeEnums] = Cogen.gen[UnsafeEnums] + implicit lazy val coCustom: Cogen[Custom] = Cogen.gen[Custom] + implicit lazy val coLowerCamel: Cogen[LowerCamel] = Cogen.gen[LowerCamel] + implicit lazy val coLowerCamelInner: Cogen[LowerCamelInner] = Cogen.gen[LowerCamelInner] // other implicit lazy val coUri: Cogen[URI] = Cogen(_.hashCode().toLong) diff --git a/shared/src/main/scala-2/magnolify/shared/EnumTypeDerivation.scala b/shared/src/main/scala-2/magnolify/shared/EnumTypeDerivation.scala index 249030ccf..f2ae647ee 100644 --- a/shared/src/main/scala-2/magnolify/shared/EnumTypeDerivation.scala +++ b/shared/src/main/scala-2/magnolify/shared/EnumTypeDerivation.scala @@ -16,7 +16,7 @@ package magnolify.shared -import magnolia1.{CaseClass, SealedTrait} +import magnolia1.{CaseClass, Magnolia, SealedTrait} import scala.annotation.implicitNotFound @@ -32,7 +32,7 @@ trait EnumTypeDerivation { implicit def genEnumValue[T]: EnumValue[T] = macro EnumTypeMacros.genEnumValueMacro[T] - def join[T: EnumValue](caseClass: CaseClass[Typeclass, T]): Typeclass[T] = { + def join[T: EnumValue](caseClass: CaseClass[EnumType, T]): EnumType[T] = { val n = caseClass.typeName.short val ns = caseClass.typeName.owner EnumType.create( @@ -44,7 +44,7 @@ trait EnumTypeDerivation { ) } - def split[T](sealedTrait: SealedTrait[Typeclass, T]): Typeclass[T] = { + def split[T](sealedTrait: SealedTrait[EnumType, T]): EnumType[T] = { val n = sealedTrait.typeName.short val ns = sealedTrait.typeName.owner val subs = sealedTrait.subtypes.map(_.typeclass) @@ -60,4 +60,6 @@ trait EnumTypeDerivation { v => subs.find(_.name == v).get.from(v) ) } + + implicit def gen[T]: EnumType[T] = macro Magnolia.gen[T] } diff --git a/shared/src/main/scala-2/magnolify/shared/EnumTypeMacros.scala b/shared/src/main/scala-2/magnolify/shared/EnumTypeMacros.scala index 7a9f4f3bf..19646df78 100644 --- a/shared/src/main/scala-2/magnolify/shared/EnumTypeMacros.scala +++ b/shared/src/main/scala-2/magnolify/shared/EnumTypeMacros.scala @@ -16,7 +16,6 @@ package magnolify.shared -import magnolia1.Magnolia import scala.reflect.macros.whitebox object EnumTypeMacros { @@ -51,11 +50,7 @@ object EnumTypeMacros { } } -trait EnumTypeCompanionMacros extends EnumTypeCompanionLowPrioMacros { +trait EnumTypeCompanionMacros extends EnumTypeDerivation { implicit def scalaEnumType[T <: Enumeration#Value: AnnotationType]: EnumType[T] = macro EnumTypeMacros.scalaEnumTypeMacro[T] } - -trait EnumTypeCompanionLowPrioMacros extends EnumTypeDerivation { - implicit def gen[T]: EnumType[T] = macro Magnolia.gen[T] -} diff --git a/shared/src/main/scala-3/magnolify/shared/EnumTypeDerivation.scala b/shared/src/main/scala-3/magnolify/shared/EnumTypeDerivation.scala index 784ff722d..eccf6ba7a 100644 --- a/shared/src/main/scala-3/magnolify/shared/EnumTypeDerivation.scala +++ b/shared/src/main/scala-3/magnolify/shared/EnumTypeDerivation.scala @@ -75,3 +75,5 @@ trait EnumTypeDerivation extends CommonDerivation[EnumType] with SealedTraitDeri v => subs.find(_.name == v).get.from(v) ) end split + + inline implicit def gen[T](using Mirror.Of[T]): EnumType[T] = derivedMirror[T] diff --git a/shared/src/main/scala-3/magnolify/shared/EnumTypeMacros.scala b/shared/src/main/scala-3/magnolify/shared/EnumTypeMacros.scala index ffbf59ecd..fde9a36be 100644 --- a/shared/src/main/scala-3/magnolify/shared/EnumTypeMacros.scala +++ b/shared/src/main/scala-3/magnolify/shared/EnumTypeMacros.scala @@ -17,7 +17,6 @@ package magnolify.shared import scala.quoted.* -import scala.deriving.Mirror object EnumTypeMacros: def scalaEnumTypeMacro[T: Type](annotations: Expr[AnnotationType[T]])(using @@ -43,5 +42,4 @@ trait EnumTypeCompanionMacros0 extends EnumTypeCompanionMacros1: ): EnumType[T] = ${ EnumTypeMacros.scalaEnumTypeMacro[T]('annotations) } -trait EnumTypeCompanionMacros1 extends EnumTypeDerivation: - inline implicit def gen[T](using Mirror.Of[T]): EnumType[T] = derivedMirror[T] +trait EnumTypeCompanionMacros1 extends EnumTypeDerivation diff --git a/test/src/test/scala/magnolify/shared/EnumTypeSuite.scala b/test/src/test/scala/magnolify/shared/EnumTypeSuite.scala index 480534fe4..ddc40853b 100644 --- a/test/src/test/scala/magnolify/shared/EnumTypeSuite.scala +++ b/test/src/test/scala/magnolify/shared/EnumTypeSuite.scala @@ -126,7 +126,7 @@ class EnumTypeSuite extends MagnolifySuite { | ] | ) | - |But method gen in trait EnumTypeCompanionMacros1 does not match type magnolify.shared.EnumType[Option[magnolify.test.ADT.Color]] + |But method gen in trait EnumTypeDerivation does not match type magnolify.shared.EnumType[Option[magnolify.test.ADT.Color]] | |where: MirroredMonoType is a type in an anonymous class locally defined in class EnumTypeSuite which is an alias of Option[magnolify.test.ADT.Color] | MirroredMonoType² is a type in trait Mirror with bounds""".stripMargin + " \n" + """|.