diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index cff0905a4..0eb1171ed 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 - name: Check binary compatibility if: '!(matrix.scala == ''3'')' diff --git a/build.sbt b/build.sbt index 9fa0c7bed..35c986bf1 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,8 @@ val scala212 = "2.12.19" val scalaDefault = scala213 val scala3Projects = List( "shared", - "test" + "test", + "scalacheck" ) // github actions @@ -267,7 +268,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 +376,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 ) 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/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..a8482a20d --- /dev/null +++ b/scalacheck/src/main/scala-2/magnolify/scalacheck/ScalacheckMacros.scala @@ -0,0 +1,41 @@ +/* + * 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 derivationArbitraryMacro[T: c.WeakTypeTag](c: whitebox.Context): c.Tree = { + import c.universe._ + val wtt = weakTypeTag[T] + q"""_root_.magnolify.scalacheck.ArbitraryDerivation.gen[$wtt]""" + } + + def derivationCogenMacro[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 autoDerivationArbitrary[T]: Arbitrary[T] = + macro ScalaCheckMacros.derivationArbitraryMacro[T] + implicit def autoDerivationCogen[T]: Cogen[T] = + macro ScalaCheckMacros.derivationCogenMacro[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..d699de7da --- /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 autoDerivationArbitrary[T](using Mirror.Of[T]): Arbitrary[T] = + ArbitraryDerivation.derivedMirror[T] + inline implicit def autoDerivationCogen[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..50cbf3028 --- /dev/null +++ b/scalacheck/src/main/scala/magnolify/scalacheck/semiauto/package.scala @@ -0,0 +1,34 @@ +/* + * 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 semiautoDerivationArbitrary( + a: Arbitrary.type + ): magnolify.scalacheck.ArbitraryDerivation.type = + magnolify.scalacheck.ArbitraryDerivation + implicit def semiautoDerivationCogen(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..f2ebbb9ba 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.autoDerivationArbitrary - 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,14 @@ 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 + import magnolify.scalacheck.semiauto.semiautoDerivationArbitrary + implicit val arbNode: Arbitrary[Node] = Arbitrary.gen + implicit val arbGNode: Arbitrary[GNode[Int]] = Arbitrary.gen + 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..73858da15 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.autoDerivationCogen 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,12 @@ 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 + import magnolify.scalacheck.semiauto.semiautoDerivationCogen + implicit val cogenNode: Cogen[Node] = Cogen.gen + implicit val cogenGNode: Cogen[GNode[Int]] = Cogen.gen + 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..63cce1116 100644 --- a/scalacheck/src/test/scala/magnolify/scalacheck/FunctionDerivationSuite.scala +++ b/scalacheck/src/test/scala/magnolify/scalacheck/FunctionDerivationSuite.scala @@ -18,18 +18,20 @@ 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._ + 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 +47,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