Skip to content

Commit 58fc597

Browse files
author
Michel Davit
committed
Cross-build scalacheck module on scala 3
Keep type param Add missing headers Prefer explicit imports instead of trait mixin Revert to implicit in shared Use implicit instead of given Add missing header Fix scala3 warning Remove trace Change year in header Update doc Simplify arbitrary derivation Update to next minor version Use import instead of mixin trait Avoid implicit name conflict Streamline macro naming Change macro naming
1 parent 71df285 commit 58fc597

File tree

23 files changed

+406
-238
lines changed

23 files changed

+406
-238
lines changed

.github/workflows/ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ jobs:
8383

8484
- name: Test
8585
if: matrix.scala == '3'
86-
run: sbt 'project ${{ matrix.project }}' '++ ${{ matrix.scala }}' shared/test test/test
86+
run: sbt 'project ${{ matrix.project }}' '++ ${{ matrix.scala }}' shared/test test/test scalacheck/test
8787

8888
- name: Check binary compatibility
8989
if: '!(matrix.scala == ''3'')'

build.sbt

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ val tensorflowMetadataVersion = "1.10.0"
4646
val tensorflowVersion = "0.5.0"
4747

4848
// project
49-
ThisBuild / tlBaseVersion := "0.7"
49+
ThisBuild / tlBaseVersion := "0.8"
5050
ThisBuild / tlSonatypeUseLegacyHost := true
5151
ThisBuild / organization := "com.spotify"
5252
ThisBuild / organizationName := "Spotify AB"
@@ -110,7 +110,8 @@ val scala212 = "2.12.19"
110110
val scalaDefault = scala213
111111
val scala3Projects = List(
112112
"shared",
113-
"test"
113+
"test",
114+
"scalacheck"
114115
)
115116

116117
// github actions
@@ -267,7 +268,9 @@ val commonSettings = Seq(
267268
"-Yretain-trees",
268269
// tolerate some nested macro expansion
269270
"-Xmax-inlines",
270-
"64"
271+
"64",
272+
// silence warnings. dotty doesn't have unused-imports category nor origin support yet
273+
"-Wconf:msg=unused import:s"
271274
)
272275
case Some((2, 13)) =>
273276
Seq(
@@ -373,6 +376,7 @@ lazy val scalacheck = project
373376
commonSettings,
374377
moduleName := "magnolify-scalacheck",
375378
description := "Magnolia add-on for ScalaCheck",
379+
crossScalaVersions := Seq(scala3, scala213, scala212),
376380
libraryDependencies += "org.scalacheck" %% "scalacheck" % scalacheckVersion % Provided
377381
)
378382

docs/scalacheck.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,6 @@ import org.scalacheck._
2525
case class Inner(int: Int, str: String)
2626
case class Outer(inner: Inner)
2727

28-
val arb: Arbitrary[Outer] = ArbitraryDerivation[Outer]
29-
val cogen: Cogen[Outer] = CogenDerivation[Outer]
28+
val arb: Arbitrary[Outer] = Arbitrary.gen[Outer]
29+
val cogen: Cogen[Outer] = Cogen.gen[Outer]
3030
```
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
/*
2+
* Copyright 2024 Spotify AB
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package magnolify.scalacheck
18+
19+
import magnolia1.*
20+
import org.scalacheck.{Arbitrary, Gen}
21+
22+
object ArbitraryDerivation {
23+
type Typeclass[T] = Arbitrary[T]
24+
25+
private implicit val monadicGen: Monadic[Gen] = new Monadic[Gen] {
26+
override def point[A](value: A): Gen[A] = Gen.const(value)
27+
override def map[A, B](from: Gen[A])(fn: A => B): Gen[B] = from.map(fn)
28+
override def flatMap[A, B](from: Gen[A])(fn: A => Gen[B]): Gen[B] = from.flatMap(fn)
29+
}
30+
31+
def join[T](caseClass: CaseClass[Arbitrary, T]): Arbitrary[T] = Arbitrary {
32+
caseClass.constructMonadic(_.typeclass.arbitrary)
33+
}
34+
35+
def split[T](sealedTrait: SealedTrait[Arbitrary, T]): Arbitrary[T] = Arbitrary {
36+
Gen.lzy {
37+
Gen.sized { size =>
38+
val subtypes = sealedTrait.subtypes
39+
for {
40+
i <-
41+
if (size >= 0) {
42+
// pick any subtype
43+
Gen.choose(0, subtypes.size - 1)
44+
} else {
45+
// pick a fixed subtype to have a chance to stop recursion
46+
Gen.const(subtypes.size + size)
47+
}
48+
subtypeGen <- Gen.resize(size - 1, subtypes(i).typeclass.arbitrary)
49+
} yield subtypeGen
50+
}
51+
}
52+
}
53+
54+
implicit def gen[T]: Arbitrary[T] = macro Magnolia.gen[T]
55+
56+
@deprecated("Use gen instead", "0.7.0")
57+
def apply[T]: Arbitrary[T] = macro Magnolia.gen[T]
58+
}

scalacheck/src/main/scala/magnolify/scalacheck/semiauto/CogenDerivation.scala renamed to scalacheck/src/main/scala-2/magnolify/scalacheck/CogenDerivation.scala

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -14,29 +14,29 @@
1414
* limitations under the License.
1515
*/
1616

17-
package magnolify.scalacheck.semiauto
17+
package magnolify.scalacheck
1818

19-
import magnolia1._
19+
import magnolia1.*
2020
import org.scalacheck.Cogen
2121

2222
object CogenDerivation {
2323
type Typeclass[T] = Cogen[T]
2424

25-
def join[T](caseClass: ReadOnlyCaseClass[Typeclass, T]): Typeclass[T] = Cogen { (seed, t) =>
26-
caseClass.parameters.foldLeft(seed) { (seed, p) =>
25+
def join[T](caseClass: CaseClass[Cogen, T]): Cogen[T] = Cogen { (seed, t) =>
26+
caseClass.parameters.foldLeft(seed) { (s, p) =>
2727
// inject index to distinguish cases like `(Some(false), None)` and `(None, Some(0))`
28-
val s = Cogen.cogenInt.perturb(seed, p.index)
29-
p.typeclass.perturb(s, p.dereference(t))
28+
p.typeclass.perturb(Cogen.perturb(s, p.index), p.dereference(t))
3029
}
3130
}
3231

33-
def split[T](sealedTrait: SealedTrait[Typeclass, T]): Typeclass[T] = Cogen { (seed, t: T) =>
32+
def split[T](sealedTrait: SealedTrait[Cogen, T]): Cogen[T] = Cogen { (seed, t) =>
3433
sealedTrait.split(t) { sub =>
3534
// inject index to distinguish case objects instances
36-
val s = Cogen.cogenInt.perturb(seed, sub.index)
37-
sub.typeclass.perturb(s, sub.cast(t))
35+
sub.typeclass.perturb(Cogen.perturb(seed, sub.index), sub.cast(t))
3836
}
3937
}
4038

41-
implicit def apply[T]: Typeclass[T] = macro Magnolia.gen[T]
39+
implicit def gen[T]: Cogen[T] = macro Magnolia.gen[T]
40+
@deprecated("Use gen instead", "0.7.0")
41+
def apply[T]: Cogen[T] = macro Magnolia.gen[T]
4242
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
/*
2+
* Copyright 2023 Spotify AB
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package magnolify.scalacheck
18+
19+
import org.scalacheck.{Arbitrary, Cogen}
20+
21+
import scala.reflect.macros.*
22+
object ScalaCheckMacros {
23+
def derivationArbitraryMacro[T: c.WeakTypeTag](c: whitebox.Context): c.Tree = {
24+
import c.universe._
25+
val wtt = weakTypeTag[T]
26+
q"""_root_.magnolify.scalacheck.ArbitraryDerivation.gen[$wtt]"""
27+
}
28+
29+
def derivationCogenMacro[T: c.WeakTypeTag](c: whitebox.Context): c.Tree = {
30+
import c.universe._
31+
val wtt = weakTypeTag[T]
32+
q"""_root_.magnolify.scalacheck.CogenDerivation.gen[$wtt]"""
33+
}
34+
}
35+
36+
trait AutoDerivations {
37+
implicit def autoDerivationArbitrary[T]: Arbitrary[T] =
38+
macro ScalaCheckMacros.derivationArbitraryMacro[T]
39+
implicit def autoDerivationCogen[T]: Cogen[T] =
40+
macro ScalaCheckMacros.derivationCogenMacro[T]
41+
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
/*
2+
* Copyright 2024 Spotify AB
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package magnolify.scalacheck
18+
19+
import magnolia1.*
20+
import org.scalacheck.{Arbitrary, Gen}
21+
22+
import scala.deriving.Mirror
23+
24+
object ArbitraryDerivation extends Derivation[Arbitrary]:
25+
26+
private given Monadic[Gen] with
27+
def point[A](value: A): Gen[A] = Gen.const(value)
28+
def map[A, B](from: Gen[A])(fn: A => B): Gen[B] = from.map(fn)
29+
def flatMap[A, B](from: Gen[A])(fn: A => Gen[B]): Gen[B] = from.flatMap(fn)
30+
31+
def join[T](caseClass: CaseClass[Arbitrary, T]): Arbitrary[T] = Arbitrary {
32+
caseClass.constructMonadic(_.typeclass.arbitrary)
33+
}
34+
35+
def split[T](sealedTrait: SealedTrait[Arbitrary, T]): Arbitrary[T] = Arbitrary {
36+
Gen.lzy {
37+
Gen.sized { size =>
38+
val subtypes = sealedTrait.subtypes
39+
for {
40+
i <-
41+
if (size >= 0) {
42+
// pick any subtype
43+
Gen.choose(0, subtypes.size - 1)
44+
} else {
45+
// pick a fixed subtype to have a chance to stop recursion
46+
Gen.const(subtypes.size + size)
47+
}
48+
subtypeGen <- Gen.resize(size - 1, subtypes(i).typeclass.arbitrary)
49+
} yield subtypeGen
50+
}
51+
}
52+
}
53+
54+
inline def gen[T](using Mirror.Of[T]): Arbitrary[T] = derivedMirror[T]
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
/*
2+
* Copyright 2023 Spotify AB
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package magnolify.scalacheck
18+
19+
import magnolia1.*
20+
import org.scalacheck.Cogen
21+
22+
import scala.deriving.Mirror
23+
24+
object CogenDerivation extends Derivation[Cogen]:
25+
26+
def join[T](caseClass: CaseClass[Cogen, T]): Cogen[T] = Cogen[T] { (seed, t) =>
27+
caseClass.params.foldLeft(seed) { (s, p) =>
28+
// inject index to distinguish cases like `(Some(false), None)` and `(None, Some(0))`
29+
p.typeclass.perturb(Cogen.perturb(s, p.index), p.deref(t))
30+
}
31+
}
32+
33+
def split[T](sealedTrait: SealedTrait[Cogen, T]): Cogen[T] = Cogen[T] { (seed, t) =>
34+
sealedTrait.choose(t) { sub =>
35+
// inject index to distinguish case objects instances
36+
sub.typeclass.perturb(Cogen.perturb(seed, sub.subtype.index), sub.cast(t))
37+
}
38+
}
39+
40+
inline def gen[T](using Mirror.Of[T]): Cogen[T] = derivedMirror[T]
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
/*
2+
* Copyright 2024 Spotify AB
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package magnolify.scalacheck
18+
19+
import org.scalacheck.{Arbitrary, Cogen}
20+
21+
import scala.deriving.Mirror
22+
23+
trait AutoDerivations:
24+
inline implicit def autoDerivationArbitrary[T](using Mirror.Of[T]): Arbitrary[T] =
25+
ArbitraryDerivation.derivedMirror[T]
26+
inline implicit def autoDerivationCogen[T](using Mirror.Of[T]): Cogen[T] =
27+
CogenDerivation.derivedMirror[T]

scalacheck/src/main/scala/magnolify/scalacheck/auto/package.scala

Lines changed: 1 addition & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -16,23 +16,4 @@
1616

1717
package magnolify.scalacheck
1818

19-
import org.scalacheck._
20-
21-
import scala.reflect.macros._
22-
23-
package object auto {
24-
def genArbitraryMacro[T: c.WeakTypeTag](c: whitebox.Context): c.Tree = {
25-
import c.universe._
26-
val wtt = weakTypeTag[T]
27-
q"""_root_.magnolify.scalacheck.semiauto.ArbitraryDerivation.apply[$wtt]"""
28-
}
29-
30-
def genCogenMacro[T: c.WeakTypeTag](c: whitebox.Context): c.Tree = {
31-
import c.universe._
32-
val wtt = weakTypeTag[T]
33-
q"""_root_.magnolify.scalacheck.semiauto.CogenDerivation.apply[$wtt]"""
34-
}
35-
36-
implicit def genArbitrary[T]: Arbitrary[T] = macro genArbitraryMacro[T]
37-
implicit def genCogen[T]: Cogen[T] = macro genCogenMacro[T]
38-
}
19+
package object auto extends AutoDerivations

0 commit comments

Comments
 (0)