From 0dee4f81834ff4da60d2bb27cd8480b6d6bbd27e Mon Sep 17 00:00:00 2001 From: Michel Davit Date: Wed, 8 Nov 2023 18:44:37 +0100 Subject: [PATCH 01/20] Cross-build scalacheck module on scala 3 --- .github/workflows/ci.yml | 2 +- build.sbt | 4 +- .../scalacheck/ArbitraryDerivation.scala | 58 +++++++++++++ .../scalacheck}/CogenDerivation.scala | 20 ++--- .../scalacheck/ScalacheckMacros.scala | 24 ++++++ .../scalacheck/ArbitraryDerivation.scala | 54 ++++++++++++ .../scalacheck/CogenDerivation.scala | 40 +++++++++ .../scalacheck/ScalacheckMacros.scala | 9 ++ .../magnolify/scalacheck/auto/package.scala | 21 +---- .../semiauto/ArbitraryDerivation.scala | 82 ------------------- .../scalacheck/semiauto/package.scala | 16 ++++ .../scalacheck/MoreCollectionsBuildable.scala | 19 +++++ .../scalacheck/ArbitraryDerivationSuite.scala | 48 +++-------- .../scalacheck/CogenDerivationSuite.scala | 33 +++++--- .../scalacheck/FunctionDerivationSuite.scala | 17 ++-- .../magnolify/scalacheck/ScopeTest.scala | 13 ++- .../magnolify/scalacheck/TestArbitrary.scala | 56 +++++++------ .../magnolify/scalacheck/TestCogen.scala | 46 ++++++----- .../magnolify/shared/EnumTypeDerivation.scala | 8 +- .../magnolify/shared/EnumTypeMacros.scala | 7 +- .../magnolify/shared/EnumTypeDerivation.scala | 2 + .../magnolify/shared/EnumTypeMacros.scala | 4 +- 22 files changed, 345 insertions(+), 238 deletions(-) create mode 100644 scalacheck/src/main/scala-2/magnolify/scalacheck/ArbitraryDerivation.scala rename scalacheck/src/main/{scala/magnolify/scalacheck/semiauto => scala-2/magnolify/scalacheck}/CogenDerivation.scala (61%) create mode 100644 scalacheck/src/main/scala-2/magnolify/scalacheck/ScalacheckMacros.scala create mode 100644 scalacheck/src/main/scala-3/magnolify/scalacheck/ArbitraryDerivation.scala create mode 100644 scalacheck/src/main/scala-3/magnolify/scalacheck/CogenDerivation.scala create mode 100644 scalacheck/src/main/scala-3/magnolify/scalacheck/ScalacheckMacros.scala delete mode 100644 scalacheck/src/main/scala/magnolify/scalacheck/semiauto/ArbitraryDerivation.scala create mode 100644 scalacheck/src/main/scala/magnolify/scalacheck/semiauto/package.scala create mode 100644 scalacheck/src/test/scala-3/magnolify/scalacheck/MoreCollectionsBuildable.scala diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 94e2c7963..502490a82 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 a7394381e..ad25098e9 100644 --- a/build.sbt +++ b/build.sbt @@ -108,7 +108,8 @@ val scala212 = "2.12.18" val scalaDefault = scala213 val scala3Projects = List( "shared", - "test" + "test", + "scalacheck" ) // github actions @@ -361,6 +362,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/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..9ef51531f --- /dev/null +++ b/scalacheck/src/main/scala-2/magnolify/scalacheck/ArbitraryDerivation.scala @@ -0,0 +1,58 @@ +/* + * 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 + +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, sealedTrait.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..3e503f5bc --- /dev/null +++ b/scalacheck/src/main/scala-2/magnolify/scalacheck/ScalacheckMacros.scala @@ -0,0 +1,24 @@ +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..722898a0a --- /dev/null +++ b/scalacheck/src/main/scala-3/magnolify/scalacheck/ArbitraryDerivation.scala @@ -0,0 +1,54 @@ +/* + * 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.{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, sealedTrait.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..d21dc6c83 --- /dev/null +++ b/scalacheck/src/main/scala-3/magnolify/scalacheck/ScalacheckMacros.scala @@ -0,0 +1,9 @@ +package magnolify.scalacheck + +import org.scalacheck.{Arbitrary, Cogen} + +import scala.deriving.Mirror +trait AutoDerivations: + + inline given genArbitrary[T](using Mirror.Of[T]): Arbitrary[T] = ArbitraryDerivation.derivedMirror[T] + inline given 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..be5ec64e2 --- /dev/null +++ b/scalacheck/src/main/scala/magnolify/scalacheck/semiauto/package.scala @@ -0,0 +1,16 @@ +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..8cbe0e9b0 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 @@ -29,14 +28,14 @@ import org.scalacheck.rng.Seed import scala.reflect._ -class ArbitraryDerivationSuite extends MagnolifySuite { +class ArbitraryDerivationSuite extends MagnolifySuite with magnolify.scalacheck.AutoDerivations { - private def test[T: Arbitrary: ClassTag]: Unit = test[T](None) - private def test[T: Arbitrary: ClassTag](suffix: String): Unit = test[T](Some(suffix)) + import TestArbitrary.arbSeed - 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..b619c2874 100644 --- a/scalacheck/src/test/scala/magnolify/scalacheck/CogenDerivationSuite.scala +++ b/scalacheck/src/test/scala/magnolify/scalacheck/CogenDerivationSuite.scala @@ -16,25 +16,33 @@ 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 { +class CogenDerivationSuite extends MagnolifySuite with magnolify.scalacheck.AutoDerivations { + + import TestArbitrary.arbSeed 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") { Prop.forAll { (seed: Seed, xs: List[T]) => - xs.map(co.perturb(seed, _)).toSet.size == xs.toSet.size + val coper = xs.map(co.perturb(seed, _)) + val result = coper.toSet.size == xs.toSet.size + if (!result) { + println("coper: " + coper) + println("origin: " + xs) + } + result } } property(s"$name.consistency") { @@ -44,7 +52,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 +64,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..1745711a0 100644 --- a/scalacheck/src/test/scala/magnolify/scalacheck/FunctionDerivationSuite.scala +++ b/scalacheck/src/test/scala/magnolify/scalacheck/FunctionDerivationSuite.scala @@ -18,18 +18,18 @@ 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 { +class FunctionDerivationSuite extends MagnolifySuite with magnolify.scalacheck.AutoDerivations { 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 +45,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..498a54692 100644 --- a/scalacheck/src/test/scala/magnolify/scalacheck/ScopeTest.scala +++ b/scalacheck/src/test/scala/magnolify/scalacheck/ScopeTest.scala @@ -16,21 +16,20 @@ 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._ + object Auto extends magnolify.scalacheck.AutoDerivations { 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..82ca6e678 100644 --- a/scalacheck/src/test/scala/magnolify/scalacheck/TestCogen.scala +++ b/scalacheck/src/test/scala/magnolify/scalacheck/TestCogen.scala @@ -16,10 +16,11 @@ 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 java.net.URI @@ -28,29 +29,30 @@ 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.gen[UnsafeEnum[T]] // 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 + implicit lazy val coGNode: Cogen[GNode[Int]] = Cogen.gen + implicit lazy val coShape: Cogen[Shape] = Cogen.gen + implicit lazy val coColor: Cogen[Color] = Cogen.gen + implicit lazy val coPerson: Cogen[Person] = Cogen.gen // 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 3d0bf9f33..5ff99e9d9 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, nowarn} @@ -33,7 +33,7 @@ trait EnumTypeDerivation { implicit def genEnumValue[T]: EnumValue[T] = macro EnumTypeMacros.genEnumValueMacro[T] @nowarn - 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( @@ -45,7 +45,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) @@ -61,4 +61,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..18fc199b8 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 given 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 From fb9958a9d77d8bb13a3d487508810d203e622e2b Mon Sep 17 00:00:00 2001 From: Michel Davit Date: Wed, 8 Nov 2023 18:52:25 +0100 Subject: [PATCH 02/20] Fix error message after shared refactoring --- test/src/test/scala/magnolify/shared/EnumTypeSuite.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/src/test/scala/magnolify/shared/EnumTypeSuite.scala b/test/src/test/scala/magnolify/shared/EnumTypeSuite.scala index 480534fe4..cc74de4ae 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 given instance 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" + """|. From 63b7b8e2eca8de6642860e1e333b3ad5f7904ad6 Mon Sep 17 00:00:00 2001 From: Michel Davit Date: Wed, 8 Nov 2023 18:54:58 +0100 Subject: [PATCH 03/20] Keep type param --- .../test/scala/magnolify/scalacheck/TestCogen.scala | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/scalacheck/src/test/scala/magnolify/scalacheck/TestCogen.scala b/scalacheck/src/test/scala/magnolify/scalacheck/TestCogen.scala index 82ca6e678..61da28e17 100644 --- a/scalacheck/src/test/scala/magnolify/scalacheck/TestCogen.scala +++ b/scalacheck/src/test/scala/magnolify/scalacheck/TestCogen.scala @@ -32,11 +32,11 @@ object TestCogen { implicit def coUnsafeEnum[T: Cogen]: Cogen[UnsafeEnum[T]] = Cogen.gen[UnsafeEnum[T]] // ADT - implicit lazy val coNode: Cogen[Node] = Cogen.gen - implicit lazy val coGNode: Cogen[GNode[Int]] = Cogen.gen - implicit lazy val coShape: Cogen[Shape] = Cogen.gen - implicit lazy val coColor: Cogen[Color] = Cogen.gen - implicit lazy val coPerson: Cogen[Person] = Cogen.gen + 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] = Cogen.gen[Integers] From b3eecd34e554ff5d7ba37e0e2f86cfcd3eebd8d2 Mon Sep 17 00:00:00 2001 From: Michel Davit Date: Thu, 9 Nov 2023 15:19:02 +0100 Subject: [PATCH 04/20] Add missing headers --- .../magnolify/scalacheck/ScalacheckMacros.scala | 16 ++++++++++++++++ .../magnolify/scalacheck/semiauto/package.scala | 16 ++++++++++++++++ 2 files changed, 32 insertions(+) diff --git a/scalacheck/src/main/scala-2/magnolify/scalacheck/ScalacheckMacros.scala b/scalacheck/src/main/scala-2/magnolify/scalacheck/ScalacheckMacros.scala index 3e503f5bc..5e97d5417 100644 --- a/scalacheck/src/main/scala-2/magnolify/scalacheck/ScalacheckMacros.scala +++ b/scalacheck/src/main/scala-2/magnolify/scalacheck/ScalacheckMacros.scala @@ -1,3 +1,19 @@ +/* + * 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} diff --git a/scalacheck/src/main/scala/magnolify/scalacheck/semiauto/package.scala b/scalacheck/src/main/scala/magnolify/scalacheck/semiauto/package.scala index be5ec64e2..49968882c 100644 --- a/scalacheck/src/main/scala/magnolify/scalacheck/semiauto/package.scala +++ b/scalacheck/src/main/scala/magnolify/scalacheck/semiauto/package.scala @@ -1,3 +1,19 @@ +/* + * 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} From 99adcc0a76ac39298413055098e5da6ff10049a5 Mon Sep 17 00:00:00 2001 From: Michel Davit Date: Mon, 13 Nov 2023 14:58:00 +0100 Subject: [PATCH 05/20] Prefer explicit imports instead of trait mixin --- .../magnolify/scalacheck/ArbitraryDerivationSuite.scala | 4 ++-- .../scala/magnolify/scalacheck/CogenDerivationSuite.scala | 4 ++-- .../scala/magnolify/scalacheck/FunctionDerivationSuite.scala | 5 ++++- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/scalacheck/src/test/scala/magnolify/scalacheck/ArbitraryDerivationSuite.scala b/scalacheck/src/test/scala/magnolify/scalacheck/ArbitraryDerivationSuite.scala index 8cbe0e9b0..692fafafa 100644 --- a/scalacheck/src/test/scala/magnolify/scalacheck/ArbitraryDerivationSuite.scala +++ b/scalacheck/src/test/scala/magnolify/scalacheck/ArbitraryDerivationSuite.scala @@ -28,9 +28,9 @@ import org.scalacheck.rng.Seed import scala.reflect._ -class ArbitraryDerivationSuite extends MagnolifySuite with magnolify.scalacheck.AutoDerivations { - +class ArbitraryDerivationSuite extends MagnolifySuite { import TestArbitrary.arbSeed + import magnolify.scalacheck.auto.genArbitrary private def test[T: ClassTag](implicit t: Arbitrary[T]): Unit = { // TODO val g = ensureSerializable(t).arbitrary diff --git a/scalacheck/src/test/scala/magnolify/scalacheck/CogenDerivationSuite.scala b/scalacheck/src/test/scala/magnolify/scalacheck/CogenDerivationSuite.scala index b619c2874..4c3362d0b 100644 --- a/scalacheck/src/test/scala/magnolify/scalacheck/CogenDerivationSuite.scala +++ b/scalacheck/src/test/scala/magnolify/scalacheck/CogenDerivationSuite.scala @@ -25,9 +25,9 @@ import org.scalacheck.rng.Seed import java.net.URI import scala.reflect.* -class CogenDerivationSuite extends MagnolifySuite with magnolify.scalacheck.AutoDerivations { - +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 = { // TODO val co = ensureSerializable(t) diff --git a/scalacheck/src/test/scala/magnolify/scalacheck/FunctionDerivationSuite.scala b/scalacheck/src/test/scala/magnolify/scalacheck/FunctionDerivationSuite.scala index 1745711a0..6db978569 100644 --- a/scalacheck/src/test/scala/magnolify/scalacheck/FunctionDerivationSuite.scala +++ b/scalacheck/src/test/scala/magnolify/scalacheck/FunctionDerivationSuite.scala @@ -23,7 +23,10 @@ import org.scalacheck._ import scala.reflect._ -class FunctionDerivationSuite extends MagnolifySuite with magnolify.scalacheck.AutoDerivations { +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] From 60de9b84db4c122e805f733b45352dfc905ecdec Mon Sep 17 00:00:00 2001 From: Michel Davit Date: Wed, 3 Jan 2024 11:56:10 +0100 Subject: [PATCH 06/20] Revert to implicit in shared --- .../src/main/scala-3/magnolify/shared/EnumTypeDerivation.scala | 2 +- test/src/test/scala/magnolify/shared/EnumTypeSuite.scala | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/shared/src/main/scala-3/magnolify/shared/EnumTypeDerivation.scala b/shared/src/main/scala-3/magnolify/shared/EnumTypeDerivation.scala index 18fc199b8..eccf6ba7a 100644 --- a/shared/src/main/scala-3/magnolify/shared/EnumTypeDerivation.scala +++ b/shared/src/main/scala-3/magnolify/shared/EnumTypeDerivation.scala @@ -76,4 +76,4 @@ trait EnumTypeDerivation extends CommonDerivation[EnumType] with SealedTraitDeri ) end split - inline given gen[T](using Mirror.Of[T]): EnumType[T] = derivedMirror[T] + inline implicit def gen[T](using Mirror.Of[T]): EnumType[T] = derivedMirror[T] diff --git a/test/src/test/scala/magnolify/shared/EnumTypeSuite.scala b/test/src/test/scala/magnolify/shared/EnumTypeSuite.scala index cc74de4ae..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 given instance gen in trait EnumTypeDerivation 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" + """|. From cd3f0c6f98009863e1d6c11804ea8fb5e220f830 Mon Sep 17 00:00:00 2001 From: Michel Davit Date: Wed, 3 Jan 2024 12:01:15 +0100 Subject: [PATCH 07/20] Use implicit instead of given --- .../scala-3/magnolify/scalacheck/ScalacheckMacros.scala | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/scalacheck/src/main/scala-3/magnolify/scalacheck/ScalacheckMacros.scala b/scalacheck/src/main/scala-3/magnolify/scalacheck/ScalacheckMacros.scala index d21dc6c83..14aa04286 100644 --- a/scalacheck/src/main/scala-3/magnolify/scalacheck/ScalacheckMacros.scala +++ b/scalacheck/src/main/scala-3/magnolify/scalacheck/ScalacheckMacros.scala @@ -3,7 +3,9 @@ package magnolify.scalacheck import org.scalacheck.{Arbitrary, Cogen} import scala.deriving.Mirror -trait AutoDerivations: - inline given genArbitrary[T](using Mirror.Of[T]): Arbitrary[T] = ArbitraryDerivation.derivedMirror[T] - inline given genCogen[T](using Mirror.Of[T]): Cogen[T] = CogenDerivation.derivedMirror[T] +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] From 27461d1b004866f2cc149f7ce94db00b60d730d2 Mon Sep 17 00:00:00 2001 From: Michel Davit Date: Wed, 3 Jan 2024 12:16:31 +0100 Subject: [PATCH 08/20] Add missing header --- .../magnolify/scalacheck/ScalacheckMacros.scala | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/scalacheck/src/main/scala-3/magnolify/scalacheck/ScalacheckMacros.scala b/scalacheck/src/main/scala-3/magnolify/scalacheck/ScalacheckMacros.scala index 14aa04286..789fe7ca8 100644 --- a/scalacheck/src/main/scala-3/magnolify/scalacheck/ScalacheckMacros.scala +++ b/scalacheck/src/main/scala-3/magnolify/scalacheck/ScalacheckMacros.scala @@ -1,3 +1,19 @@ +/* + * 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} From 67e8de451bfcd929315292a22e2572b1183e9f5e Mon Sep 17 00:00:00 2001 From: Michel Davit Date: Wed, 3 Jan 2024 17:48:40 +0100 Subject: [PATCH 09/20] Fix scala3 warning --- build.sbt | 4 +++- .../src/test/scala/magnolify/scalacheck/TestCogen.scala | 9 ++++++++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/build.sbt b/build.sbt index ad25098e9..d59f17756 100644 --- a/build.sbt +++ b/build.sbt @@ -259,7 +259,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( diff --git a/scalacheck/src/test/scala/magnolify/scalacheck/TestCogen.scala b/scalacheck/src/test/scala/magnolify/scalacheck/TestCogen.scala index 61da28e17..418f21e7f 100644 --- a/scalacheck/src/test/scala/magnolify/scalacheck/TestCogen.scala +++ b/scalacheck/src/test/scala/magnolify/scalacheck/TestCogen.scala @@ -22,6 +22,7 @@ import magnolify.test.ADT.* import magnolify.test.JavaEnums import magnolify.test.Simple.* import org.scalacheck.Cogen +import org.scalacheck.rng.Seed import java.net.URI @@ -29,7 +30,13 @@ 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.gen[UnsafeEnum[T]] + 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] = Cogen.gen[Node] From d26bc502fd85962e6e6d06f48c2b6b10bf54f9c9 Mon Sep 17 00:00:00 2001 From: Michel Davit Date: Fri, 5 Jan 2024 09:46:07 +0100 Subject: [PATCH 10/20] Remove trace --- .../scala/magnolify/scalacheck/CogenDerivationSuite.scala | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/scalacheck/src/test/scala/magnolify/scalacheck/CogenDerivationSuite.scala b/scalacheck/src/test/scala/magnolify/scalacheck/CogenDerivationSuite.scala index 4c3362d0b..e491eae68 100644 --- a/scalacheck/src/test/scala/magnolify/scalacheck/CogenDerivationSuite.scala +++ b/scalacheck/src/test/scala/magnolify/scalacheck/CogenDerivationSuite.scala @@ -36,13 +36,7 @@ class CogenDerivationSuite extends MagnolifySuite { implicit val arbList: Arbitrary[List[T]] = Arbitrary(Gen.listOfN(10, arb.arbitrary)) property(s"$name.uniqueness") { Prop.forAll { (seed: Seed, xs: List[T]) => - val coper = xs.map(co.perturb(seed, _)) - val result = coper.toSet.size == xs.toSet.size - if (!result) { - println("coper: " + coper) - println("origin: " + xs) - } - result + xs.map(co.perturb(seed, _)).toSet.size == xs.toSet.size } } property(s"$name.consistency") { From 564076797be86b645ff4726685577acf13ffc518 Mon Sep 17 00:00:00 2001 From: Michel Davit Date: Tue, 9 Jan 2024 16:43:05 +0100 Subject: [PATCH 11/20] Change year in header --- .../main/scala-2/magnolify/scalacheck/ArbitraryDerivation.scala | 2 +- .../main/scala-3/magnolify/scalacheck/ArbitraryDerivation.scala | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/scalacheck/src/main/scala-2/magnolify/scalacheck/ArbitraryDerivation.scala b/scalacheck/src/main/scala-2/magnolify/scalacheck/ArbitraryDerivation.scala index 9ef51531f..06d61566b 100644 --- a/scalacheck/src/main/scala-2/magnolify/scalacheck/ArbitraryDerivation.scala +++ b/scalacheck/src/main/scala-2/magnolify/scalacheck/ArbitraryDerivation.scala @@ -1,5 +1,5 @@ /* - * Copyright 2019 Spotify AB + * 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. diff --git a/scalacheck/src/main/scala-3/magnolify/scalacheck/ArbitraryDerivation.scala b/scalacheck/src/main/scala-3/magnolify/scalacheck/ArbitraryDerivation.scala index 722898a0a..f64ae6dc6 100644 --- a/scalacheck/src/main/scala-3/magnolify/scalacheck/ArbitraryDerivation.scala +++ b/scalacheck/src/main/scala-3/magnolify/scalacheck/ArbitraryDerivation.scala @@ -1,5 +1,5 @@ /* - * Copyright 2023 Spotify AB + * 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. From a166751155e1b39096f6b9a6cdc921d574a73c41 Mon Sep 17 00:00:00 2001 From: Michel Davit Date: Tue, 9 Jan 2024 16:55:39 +0100 Subject: [PATCH 12/20] Update doc --- site/src/main/paradox/scalacheck.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/site/src/main/paradox/scalacheck.md b/site/src/main/paradox/scalacheck.md index 2542cf3e2..aae5826bf 100644 --- a/site/src/main/paradox/scalacheck.md +++ b/site/src/main/paradox/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] ``` From 49de35d607f16cb38a985e9bad4c67a70c6f6f43 Mon Sep 17 00:00:00 2001 From: Michel Davit Date: Wed, 22 May 2024 14:04:16 +0200 Subject: [PATCH 13/20] Simplify arbitrary derivation --- .../main/scala-2/magnolify/scalacheck/ArbitraryDerivation.scala | 2 +- .../main/scala-3/magnolify/scalacheck/ArbitraryDerivation.scala | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/scalacheck/src/main/scala-2/magnolify/scalacheck/ArbitraryDerivation.scala b/scalacheck/src/main/scala-2/magnolify/scalacheck/ArbitraryDerivation.scala index 06d61566b..9ff13415e 100644 --- a/scalacheck/src/main/scala-2/magnolify/scalacheck/ArbitraryDerivation.scala +++ b/scalacheck/src/main/scala-2/magnolify/scalacheck/ArbitraryDerivation.scala @@ -45,7 +45,7 @@ object ArbitraryDerivation { // pick a fixed subtype to have a chance to stop recursion Gen.const(subtypes.size + size) } - subtypeGen <- Gen.resize(size - 1, sealedTrait.subtypes(i).typeclass.arbitrary) + subtypeGen <- Gen.resize(size - 1, subtypes(i).typeclass.arbitrary) } yield subtypeGen } } diff --git a/scalacheck/src/main/scala-3/magnolify/scalacheck/ArbitraryDerivation.scala b/scalacheck/src/main/scala-3/magnolify/scalacheck/ArbitraryDerivation.scala index f64ae6dc6..0a32e7262 100644 --- a/scalacheck/src/main/scala-3/magnolify/scalacheck/ArbitraryDerivation.scala +++ b/scalacheck/src/main/scala-3/magnolify/scalacheck/ArbitraryDerivation.scala @@ -45,7 +45,7 @@ object ArbitraryDerivation extends Derivation[Arbitrary]: // pick a fixed subtype to have a chance to stop recursion Gen.const(subtypes.size + size) } - subtypeGen <- Gen.resize(size - 1, sealedTrait.subtypes(i).typeclass.arbitrary) + subtypeGen <- Gen.resize(size - 1, subtypes(i).typeclass.arbitrary) } yield subtypeGen } } From 9f286cec0646b2f73658f79d5bf39472694b2812 Mon Sep 17 00:00:00 2001 From: Michel Davit Date: Wed, 22 May 2024 14:17:28 +0200 Subject: [PATCH 14/20] Update to next minor version --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index b407572c1..a6df65b6a 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" From 11d4bd9ba76d8dd26b33d23f7e873dd369d7c889 Mon Sep 17 00:00:00 2001 From: Michel Davit Date: Wed, 22 May 2024 14:29:04 +0200 Subject: [PATCH 15/20] Use import instead of mixin trait --- scalacheck/src/test/scala/magnolify/scalacheck/ScopeTest.scala | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/scalacheck/src/test/scala/magnolify/scalacheck/ScopeTest.scala b/scalacheck/src/test/scala/magnolify/scalacheck/ScopeTest.scala index 498a54692..8687371c0 100644 --- a/scalacheck/src/test/scala/magnolify/scalacheck/ScopeTest.scala +++ b/scalacheck/src/test/scala/magnolify/scalacheck/ScopeTest.scala @@ -20,7 +20,8 @@ import magnolify.test.Simple.* import org.scalacheck.* object ScopeTest { - object Auto extends magnolify.scalacheck.AutoDerivations { + object Auto { + import magnolify.scalacheck.auto.* implicitly[Arbitrary[Numbers]] implicitly[Cogen[Numbers]] implicitly[Arbitrary[Numbers => Numbers]] From a7fe9f5f43be3d8d13de88b3713b168f0c217dd8 Mon Sep 17 00:00:00 2001 From: Michel Davit Date: Fri, 10 Nov 2023 12:05:49 +0100 Subject: [PATCH 16/20] Cross-build guava module on scala 3 --- .github/workflows/ci.yml | 2 +- build.sbt | 4 +- .../magnolify/guava/FunnelDerivation.scala | 59 +++++++++++++ .../scala-2/magnolify/guava/GuavaMacros.scala | 23 +++++ .../magnolify/guava/FunnelDerivation.scala | 46 ++++++++++ .../scala-3/magnolify/guava/GuavaMacros.scala | 11 +++ .../magnolify/guava/FunnelImplicits.scala | 50 +++++++++++ .../scala/magnolify/guava/auto/package.scala | 9 +- .../guava/semiauto/FunnelDerivation.scala | 86 ------------------- .../magnolify/guava/semiauto/package.scala | 6 +- .../guava/FunnelDerivationSuite.scala | 70 ++++++++------- .../scala/magnolify/guava/ScopeTest.scala | 11 +-- .../magnolify/refined/RefinedSuite.scala | 3 +- 13 files changed, 243 insertions(+), 137 deletions(-) create mode 100644 guava/src/main/scala-2/magnolify/guava/FunnelDerivation.scala create mode 100644 guava/src/main/scala-2/magnolify/guava/GuavaMacros.scala create mode 100644 guava/src/main/scala-3/magnolify/guava/FunnelDerivation.scala create mode 100644 guava/src/main/scala-3/magnolify/guava/GuavaMacros.scala create mode 100644 guava/src/main/scala/magnolify/guava/FunnelImplicits.scala delete mode 100644 guava/src/main/scala/magnolify/guava/semiauto/FunnelDerivation.scala diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0eb1171ed..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 scalacheck/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 a6df65b6a..aa2e09e95 100644 --- a/build.sbt +++ b/build.sbt @@ -111,7 +111,8 @@ val scalaDefault = scala213 val scala3Projects = List( "shared", "test", - "scalacheck" + "scalacheck", + "guava" ) // github actions @@ -409,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/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/GuavaMacros.scala b/guava/src/main/scala-2/magnolify/guava/GuavaMacros.scala new file mode 100644 index 000000000..96f6822f7 --- /dev/null +++ b/guava/src/main/scala-2/magnolify/guava/GuavaMacros.scala @@ -0,0 +1,23 @@ +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/GuavaMacros.scala b/guava/src/main/scala-3/magnolify/guava/GuavaMacros.scala new file mode 100644 index 000000000..322916002 --- /dev/null +++ b/guava/src/main/scala-3/magnolify/guava/GuavaMacros.scala @@ -0,0 +1,11 @@ +package magnolify.guava + +import com.google.common.hash.Funnel + +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/FunnelImplicits.scala b/guava/src/main/scala/magnolify/guava/FunnelImplicits.scala new file mode 100644 index 000000000..98c2b9757 --- /dev/null +++ b/guava/src/main/scala/magnolify/guava/FunnelImplicits.scala @@ -0,0 +1,50 @@ +package magnolify.guava + +import com.google.common.hash.{Funnel, Funnels, PrimitiveSink} + +import scala.annotation.nowarn +trait FunnelImplicits { + import FunnelImplicits.* + + 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 + } + + 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(_)) + implicit val byteFunnel: Funnel[Byte] = funnel[Byte](_.putByte(_)) + implicit val charFunnel: Funnel[Char] = funnel[Char](_.putChar(_)) + implicit val shortFunnel: Funnel[Short] = funnel[Short](_.putShort(_)) + + 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 + ti(from).foreach { x => + fnl.funnel(x, sink) + i += 1 + } + // inject size to distinguish `None`, `Some("")`, and `List("", "", ...)` + sink.putInt(i) + } + + def Funnel[T](implicit fnl: Funnel[T]): Funnel[T] = 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/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..b19410928 100644 --- a/guava/src/test/scala/magnolify/guava/FunnelDerivationSuite.scala +++ b/guava/src/test/scala/magnolify/guava/FunnelDerivationSuite.scala @@ -16,31 +16,29 @@ 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.scalacheck.AutoDerivations + with magnolify.guava.FunnelImplicits + with magnolify.guava.AutoDerivations { 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 +57,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 +74,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..db2daabc0 100644 --- a/guava/src/test/scala/magnolify/guava/ScopeTest.scala +++ b/guava/src/test/scala/magnolify/guava/ScopeTest.scala @@ -17,16 +17,17 @@ 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.FunnelImplicits.* + + object Auto extends magnolify.guava.AutoDerivations { implicitly[Funnel[Integers]] } object Semi { - import magnolify.guava.semiauto._ - FunnelDerivation[Integers] + import magnolify.guava.semiauto.* + 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 { From 07bd77d7db7c229d860dbe68ac64398b03c1681f Mon Sep 17 00:00:00 2001 From: Michel Davit Date: Tue, 14 Nov 2023 17:15:37 +0100 Subject: [PATCH 17/20] Favor imports instead of mixin --- .../scala/magnolify/guava/FunnelDerivationSuite.scala | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/guava/src/test/scala/magnolify/guava/FunnelDerivationSuite.scala b/guava/src/test/scala/magnolify/guava/FunnelDerivationSuite.scala index b19410928..06ce1e526 100644 --- a/guava/src/test/scala/magnolify/guava/FunnelDerivationSuite.scala +++ b/guava/src/test/scala/magnolify/guava/FunnelDerivationSuite.scala @@ -30,11 +30,9 @@ import java.nio.charset.Charset import java.time.Duration import scala.reflect.* -class FunnelDerivationSuite - extends MagnolifySuite - with magnolify.scalacheck.AutoDerivations - with magnolify.guava.FunnelImplicits - with magnolify.guava.AutoDerivations { +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 = { // TODO val fnl = ensureSerializable(t) From 2acc69c479e77eb41d6cee48e6249530a07d550d Mon Sep 17 00:00:00 2001 From: Michel Davit Date: Thu, 16 Nov 2023 13:57:50 +0100 Subject: [PATCH 18/20] Idiomatic scala 3 implicits --- .../magnolify/guava/FunnelImplicits.scala | 51 +++++++++++++++++++ .../scala-2/magnolify/guava/GuavaMacros.scala | 16 ++++++ .../magnolify/guava/FunnelImplicits.scala | 45 ++++++++++++++++ .../scala-3/magnolify/guava/GuavaMacros.scala | 16 ++++++ .../magnolify/guava/FunnelImplicits.scala | 50 ------------------ .../magnolify/guava/FunnelInstances.scala | 50 ++++++++++++++++++ .../scala/magnolify/guava/ScopeTest.scala | 10 ++-- 7 files changed, 185 insertions(+), 53 deletions(-) create mode 100644 guava/src/main/scala-2/magnolify/guava/FunnelImplicits.scala create mode 100644 guava/src/main/scala-3/magnolify/guava/FunnelImplicits.scala delete mode 100644 guava/src/main/scala/magnolify/guava/FunnelImplicits.scala create mode 100644 guava/src/main/scala/magnolify/guava/FunnelInstances.scala 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..6cd690603 --- /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).contramap(ti) + + 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 index 96f6822f7..cd4814f6c 100644 --- a/guava/src/main/scala-2/magnolify/guava/GuavaMacros.scala +++ b/guava/src/main/scala-2/magnolify/guava/GuavaMacros.scala @@ -1,3 +1,19 @@ +/* + * 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 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..28f4e1eab --- /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).contramap(ti) + + 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-3/magnolify/guava/GuavaMacros.scala b/guava/src/main/scala-3/magnolify/guava/GuavaMacros.scala index 322916002..bb123d138 100644 --- a/guava/src/main/scala-3/magnolify/guava/GuavaMacros.scala +++ b/guava/src/main/scala-3/magnolify/guava/GuavaMacros.scala @@ -1,3 +1,19 @@ +/* + * 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 diff --git a/guava/src/main/scala/magnolify/guava/FunnelImplicits.scala b/guava/src/main/scala/magnolify/guava/FunnelImplicits.scala deleted file mode 100644 index 98c2b9757..000000000 --- a/guava/src/main/scala/magnolify/guava/FunnelImplicits.scala +++ /dev/null @@ -1,50 +0,0 @@ -package magnolify.guava - -import com.google.common.hash.{Funnel, Funnels, PrimitiveSink} - -import scala.annotation.nowarn -trait FunnelImplicits { - import FunnelImplicits.* - - 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 - } - - 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(_)) - implicit val byteFunnel: Funnel[Byte] = funnel[Byte](_.putByte(_)) - implicit val charFunnel: Funnel[Char] = funnel[Char](_.putChar(_)) - implicit val shortFunnel: Funnel[Short] = funnel[Short](_.putShort(_)) - - 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 - ti(from).foreach { x => - fnl.funnel(x, sink) - i += 1 - } - // inject size to distinguish `None`, `Some("")`, and `List("", "", ...)` - sink.putInt(i) - } - - def Funnel[T](implicit fnl: Funnel[T]): Funnel[T] = 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/magnolify/guava/FunnelInstances.scala b/guava/src/main/scala/magnolify/guava/FunnelInstances.scala new file mode 100644 index 000000000..0a360479d --- /dev/null +++ b/guava/src/main/scala/magnolify/guava/FunnelInstances.scala @@ -0,0 +1,50 @@ +/* + * 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]] + def iterableFunnel[T](fnl: Funnel[T]): Funnel[Iterable[T]] = + funnel { (sink, xs) => + var i = 0 + 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/test/scala/magnolify/guava/ScopeTest.scala b/guava/src/test/scala/magnolify/guava/ScopeTest.scala index db2daabc0..1d074723e 100644 --- a/guava/src/test/scala/magnolify/guava/ScopeTest.scala +++ b/guava/src/test/scala/magnolify/guava/ScopeTest.scala @@ -20,14 +20,18 @@ import com.google.common.hash.Funnel import magnolify.test.Simple.* object ScopeTest { - import magnolify.guava.FunnelImplicits.* - object Auto extends magnolify.guava.AutoDerivations { + object 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.* + import magnolify.guava.semiauto.genFunnel + import magnolify.guava.semiauto.intFunnel + import magnolify.guava.semiauto.longFunnel genFunnel[Integers] } } From e5c83c2563153b2abb7cd9287a0f567780919485 Mon Sep 17 00:00:00 2001 From: Michel Davit Date: Thu, 16 Nov 2023 15:22:50 +0100 Subject: [PATCH 19/20] Better instance definition --- .../main/scala-2/magnolify/guava/FunnelImplicits.scala | 4 ++-- .../main/scala-3/magnolify/guava/FunnelImplicits.scala | 4 ++-- .../src/main/scala/magnolify/guava/FunnelInstances.scala | 8 +++++--- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/guava/src/main/scala-2/magnolify/guava/FunnelImplicits.scala b/guava/src/main/scala-2/magnolify/guava/FunnelImplicits.scala index 6cd690603..69c9e5233 100644 --- a/guava/src/main/scala-2/magnolify/guava/FunnelImplicits.scala +++ b/guava/src/main/scala-2/magnolify/guava/FunnelImplicits.scala @@ -31,13 +31,13 @@ trait FunnelImplicits { implicit val shortFunnel: Funnel[Short] = FunnelInstances.shortFunnel() implicit def charSequenceFunnel[T <: CharSequence]: Funnel[T] = - FunnelInstances.charSequenceFunnel[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).contramap(ti) + ): Funnel[C[T]] = FunnelInstances.iterableFunnel(fnl) implicit def funnelOps[T](fnl: Funnel[T]): FunnelOps[T] = new FunnelOps(fnl) } diff --git a/guava/src/main/scala-3/magnolify/guava/FunnelImplicits.scala b/guava/src/main/scala-3/magnolify/guava/FunnelImplicits.scala index 28f4e1eab..5f4b8482b 100644 --- a/guava/src/main/scala-3/magnolify/guava/FunnelImplicits.scala +++ b/guava/src/main/scala-3/magnolify/guava/FunnelImplicits.scala @@ -30,13 +30,13 @@ trait FunnelImplicits: given shortFunnel: Funnel[Short] = FunnelInstances.shortFunnel() given charSequenceFunnel[T <: CharSequence]: Funnel[T] = - FunnelInstances.charSequenceFunnel[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).contramap(ti) + ): Funnel[C[T]] = FunnelInstances.iterableFunnel(fnl) extension [T](fnl: Funnel[T]) def contramap[U](f: U => T): Funnel[U] = new Funnel[U]: diff --git a/guava/src/main/scala/magnolify/guava/FunnelInstances.scala b/guava/src/main/scala/magnolify/guava/FunnelInstances.scala index 0a360479d..b286bd985 100644 --- a/guava/src/main/scala/magnolify/guava/FunnelInstances.scala +++ b/guava/src/main/scala/magnolify/guava/FunnelInstances.scala @@ -35,12 +35,14 @@ object FunnelInstances { def charFunnel(): Funnel[Char] = funnel[Char](_.putChar(_)) def shortFunnel(): Funnel[Short] = funnel[Short](_.putShort(_)) - def charSequenceFunnel[T <: CharSequence]: Funnel[T] = + def charSequenceFunnel[T <: CharSequence](): Funnel[T] = Funnels.unencodedCharsFunnel().asInstanceOf[Funnel[T]] - def iterableFunnel[T](fnl: Funnel[T]): Funnel[Iterable[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 - xs.foreach { x => + ti(xs).foreach { x => fnl.funnel(x, sink) i += 1 } From 388dee98caf2df8b59f3c292f6efa8892c890eaf Mon Sep 17 00:00:00 2001 From: Michel Davit Date: Wed, 22 May 2024 14:36:27 +0200 Subject: [PATCH 20/20] Delete remaining file --- .../scala/magnolify/guava/GuavaMacros.scala | 27 ------------------- 1 file changed, 27 deletions(-) delete mode 100644 guava/src/main/scala/magnolify/guava/GuavaMacros.scala diff --git a/guava/src/main/scala/magnolify/guava/GuavaMacros.scala b/guava/src/main/scala/magnolify/guava/GuavaMacros.scala deleted file mode 100644 index 9978fccda..000000000 --- a/guava/src/main/scala/magnolify/guava/GuavaMacros.scala +++ /dev/null @@ -1,27 +0,0 @@ -/* - * 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.guava - -import scala.reflect.macros.whitebox - -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]""" - } -}