Skip to content

Commit

Permalink
CaseClass1Rep support for Scala 3. Enabling kebs-instances. (#187)
Browse files Browse the repository at this point in the history
* Fixing `enum` and some macro warnings.

* Adding basic CaseClass1Rep support for Scala 3. Enabling kebs-instances.

* Creating EnumOf typeclass (with a macro) to support Scala 3's enums.
  • Loading branch information
luksow committed Dec 30, 2021
1 parent 3330cf7 commit 017360d
Show file tree
Hide file tree
Showing 19 changed files with 172 additions and 13 deletions.
1 change: 0 additions & 1 deletion build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -426,7 +426,6 @@ lazy val instances = project
.in(file("instances"))
.settings(instancesSettings: _*)
.settings(publishSettings: _*)
.settings(disableScala3)
.settings(
name := "instances",
description := "Standard type mappings",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
package pl.iterators.kebs.instances.net

import org.scalatest.funsuite.AnyFunSuite
import org.scalatest.matchers.should.Matchers.convertToAnyShouldWrapper
import org.scalatest.matchers.should.Matchers
import pl.iterators.kebs.instances.InstanceConverter
import pl.iterators.kebs.instances.InstanceConverter.DecodeErrorException

import java.net.URI

class NetInstancesTests extends AnyFunSuite with URIString {
class NetInstancesTests extends AnyFunSuite with Matchers with URIString {

test("URI to String") {
val ico = implicitly[InstanceConverter[URI, String]]
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
package pl.iterators.kebs.macros

import scala.deriving.Mirror

final class CaseClass1Rep[CC, F1](val apply: F1 => CC, val unapply: CC => F1)

object CaseClass1Rep {
inline given[T <: Product, F1](using m: Mirror.ProductOf[T], teq: m.MirroredElemTypes =:= F1 *: EmptyTuple.type): CaseClass1Rep[T, F1] = {
new CaseClass1Rep[T, F1](f1 => m.fromProduct(Tuple1(f1)), _.productElement(0).asInstanceOf[F1])
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package pl.iterators.kebs.macros.enums

import scala.quoted._

trait EnumLike[T] {
def values: Array[T]
def valueOf(name: String): T
def fromOrdinal(ordinal: Int): T
}

class EnumOf[E](val `enum`: EnumLike[E])

object EnumOf {
inline given [E]: EnumOf[E] = ${EnumOf.impl[E]()}

private def impl[T]()(using Quotes, Type[T]): Expr[EnumOf[T]] = {
import quotes.reflect._
val companion = Ref(TypeRepr.of[T].typeSymbol.companionModule)
'{
new EnumOf(
new EnumLike[T] {
def values: Array[T] = ${Select.unique(companion, "values").asExprOf[Array[T]]}
def valueOf(name: String): T = ${Apply(Select.unique(companion, "valueOf"), List('{name}.asTerm)).asExprOf[T]}
def fromOrdinal(ordinal: Int): T = ${Apply(Select.unique(companion, "fromOrdinal"), List('{ordinal}.asTerm)).asExprOf[T]}
}
)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package pl.iterators.kebs.support

import pl.iterators.kebs.macros.CaseClass1Rep

trait EquivSupport {

implicit def equivFromCaseClass1Rep[A, Rep](implicit cc1Rep: CaseClass1Rep[A, Rep], equivRep: Equiv[Rep]): Equiv[A] =
(x: A, y: A) => equivRep.equiv(cc1Rep.unapply(x), cc1Rep.unapply(y))

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package pl.iterators.kebs.support

import pl.iterators.kebs.macros.CaseClass1Rep

trait FractionalSupport {

implicit def fractionalFromCaseClass1Rep[A, Rep](implicit cc1Rep: CaseClass1Rep[A, Rep],
fractionalRep: Fractional[Rep],
numeric: Numeric[A]): Fractional[A] =
new Fractional[A] {
override def div(x: A, y: A): A = cc1Rep.apply(fractionalRep.div(cc1Rep.unapply(x), cc1Rep.unapply(y)))
override def plus(x: A, y: A): A = numeric.plus(x, y)
override def minus(x: A, y: A): A = numeric.minus(x, y)
override def times(x: A, y: A): A = numeric.times(x, y)
override def negate(x: A): A = numeric.negate(x)
override def fromInt(x: Int): A = numeric.fromInt(x)
override def parseString(str: String): Option[A] = numeric.parseString(str)
override def toInt(x: A): Int = numeric.toInt(x)
override def toLong(x: A): Long = numeric.toLong(x)
override def toFloat(x: A): Float = numeric.toFloat(x)
override def toDouble(x: A): Double = numeric.toDouble(x)
override def compare(x: A, y: A): Int = numeric.compare(x, y)
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package pl.iterators.kebs.support

import pl.iterators.kebs.macros.CaseClass1Rep

trait IntegralSupport {

implicit def integralFromCaseClass1Rep[A, Rep](implicit cc1Rep: CaseClass1Rep[A, Rep],
integralRep: Integral[Rep],
numeric: Numeric[A]): Integral[A] =
new Integral[A] {
override def quot(x: A, y: A): A = cc1Rep.apply(integralRep.quot(cc1Rep.unapply(x), cc1Rep.unapply(y)))
override def rem(x: A, y: A): A = cc1Rep.apply(integralRep.rem(cc1Rep.unapply(x), cc1Rep.unapply(y)))
override def plus(x: A, y: A): A = numeric.plus(x, y)
override def minus(x: A, y: A): A = numeric.minus(x, y)
override def times(x: A, y: A): A = numeric.times(x, y)
override def negate(x: A): A = numeric.negate(x)
override def fromInt(x: Int): A = numeric.fromInt(x)
override def parseString(str: String): Option[A] = numeric.parseString(str)
override def toInt(x: A): Int = numeric.toInt(x)
override def toLong(x: A): Long = numeric.toLong(x)
override def toFloat(x: A): Float = numeric.toFloat(x)
override def toDouble(x: A): Double = numeric.toDouble(x)
override def compare(x: A, y: A): Int = numeric.compare(x, y)
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package pl.iterators.kebs.support

import pl.iterators.kebs.macros.CaseClass1Rep

trait NumericSupport {

implicit def numericFromCaseClass1Rep[A, Rep](implicit cc1Rep: CaseClass1Rep[A, Rep], numericRep: Numeric[Rep]): Numeric[A] = {
new Numeric[A] {
override def plus(x: A, y: A): A = cc1Rep.apply(numericRep.plus(cc1Rep.unapply(x), cc1Rep.unapply(y)))
override def minus(x: A, y: A): A = cc1Rep.apply(numericRep.minus(cc1Rep.unapply(x), cc1Rep.unapply(y)))
override def times(x: A, y: A): A = cc1Rep.apply(numericRep.times(cc1Rep.unapply(x), cc1Rep.unapply(y)))
override def negate(x: A): A = cc1Rep.apply(numericRep.negate(cc1Rep.unapply(x)))
override def fromInt(x: Int): A = cc1Rep.apply(numericRep.fromInt(x))
override def toInt(x: A): Int = numericRep.toInt(cc1Rep.unapply(x))
override def toLong(x: A): Long = numericRep.toLong(cc1Rep.unapply(x))
override def toFloat(x: A): Float = numericRep.toFloat(cc1Rep.unapply(x))
override def toDouble(x: A): Double = numericRep.toDouble(cc1Rep.unapply(x))
override def compare(x: A, y: A): Int = numericRep.compare(cc1Rep.unapply(x), cc1Rep.unapply(y))
override def parseString(str: String): Option[A] = numericRep.parseString(str).map(cc1Rep.apply)
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package pl.iterators.kebs.support

import pl.iterators.kebs.macros.CaseClass1Rep

trait PartialOrderingSupport {

implicit def partialOrderingFromCaseClass1Rep[A, Rep](implicit cc1Rep: CaseClass1Rep[A, Rep],
partialOrderingRep: PartialOrdering[Rep]): PartialOrdering[A] =
new PartialOrdering[A] {
override def tryCompare(x: A, y: A): Option[Int] = partialOrderingRep.tryCompare(cc1Rep.unapply(x), cc1Rep.unapply(y))
override def lteq(x: A, y: A): Boolean = partialOrderingRep.lteq(cc1Rep.unapply(x), cc1Rep.unapply(y))
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package pl.iterators.kebs

package object support extends FractionalSupport with IntegralSupport with NumericSupport with PartialOrderingSupport with EquivSupport
22 changes: 22 additions & 0 deletions macro-utils/src/test/scala-3/DerivingSpecification.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import org.scalacheck.Prop.forAll
import org.scalacheck.{Gen, Properties}
import pl.iterators.kebs.macros.CaseClass1Rep
import pl.iterators.kebs.macros.enums.EnumOf

object DerivingSpecification extends Properties("Deriving") {
case class CC1Ex(whatever: String)

property("CaseClass1Rep derives properly from 1-element case class") = forAll { (stringValue: String) =>
val tc = implicitly[CaseClass1Rep[CC1Ex, String]]
tc.apply(stringValue) == CC1Ex(stringValue) && tc.unapply(CC1Ex(stringValue)) == stringValue
}

enum Color {
case Red, Green, Blue
}

property("EnumOf derives properly for an enum") = forAll(Gen.oneOf(Color.values.toList)) { (color: Color) =>
val tc = implicitly[EnumOf[Color]]
tc.`enum`.values.contains(color) && tc.`enum`.valueOf(color.toString) == color && tc.`enum`.fromOrdinal(color.ordinal) == color
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,28 +11,28 @@ object EquivSupportSpecification extends Properties("EquivSupport") {

implicit private val equiv: Equiv[String] = Equiv.reference[String]

property("tagged string should be equivalent to reference of non tagged string") = forAll { stringValue: String =>
property("tagged string should be equivalent to reference of non tagged string") = forAll { (stringValue: String) =>
val string = new String(stringValue)
val stringTagged = TaggedString(string)

areEquiv(stringTagged, TaggedString(string))
}

property("tagged string should not be equivalent to new instance of non tagged string") = forAll { stringValue: String =>
property("tagged string should not be equivalent to new instance of non tagged string") = forAll { (stringValue: String) =>
val string = new String(stringValue)
val stringTagged = TaggedString(string)

!areEquiv(stringTagged, TaggedString(new String(stringValue)))
}

property("boxed string should be equivalent to reference of non boxed string") = forAll { stringValue: String =>
property("boxed string should be equivalent to reference of non boxed string") = forAll { (stringValue: String) =>
val string = new String(stringValue)
val stringBoxed = BoxedString(string)

areEquiv(stringBoxed, BoxedString(string))
}

property("boxed string should not be equivalent to new instance of non boxed string") = forAll { stringValue: String =>
property("boxed string should not be equivalent to new instance of non boxed string") = forAll { (stringValue: String) =>
val string = new String(stringValue)
val stringBoxed = BoxedString(string)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@ object NumericSupportSpecification extends Properties("NumericSupport") {
import NumbersDomain._
import pl.iterators.kebs.support._

property("sum of List[TaggedBigDecimal]") = forAll { bigDecimalList: List[BigDecimal] =>
property("sum of List[TaggedBigDecimal]") = forAll { (bigDecimalList: List[BigDecimal]) =>
bigDecimalList.map(TaggedBigDecimal(_)).sum == TaggedBigDecimal(bigDecimalList.sum)
}

property("sum of List[BoxedBigDecimal]") = forAll { bigDecimalList: List[BigDecimal] =>
property("sum of List[BoxedBigDecimal]") = forAll { (bigDecimalList: List[BigDecimal]) =>
bigDecimalList.map(BoxedBigDecimal(_)).sum == BoxedBigDecimal(bigDecimalList.sum)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,19 +15,19 @@ object OrderingSupportSpecification extends Properties("OrderingSupport") {
private def sortedReverse[A: Ordering](list: List[A]): List[A] =
list.sorted(Ordering[A].reverse)

property("ordering should be available for numeric tagged types") = forAll { bigDecimalList: List[BigDecimal] =>
property("ordering should be available for numeric tagged types") = forAll { (bigDecimalList: List[BigDecimal]) =>
toTagged(bigDecimalList).sorted == toTagged(bigDecimalList.sorted)
}

property("reverse ordering should be available for numeric tagged types") = forAll { bigDecimalList: List[BigDecimal] =>
property("reverse ordering should be available for numeric tagged types") = forAll { (bigDecimalList: List[BigDecimal]) =>
sortedReverse(toTagged(bigDecimalList)) == toTagged(sortedReverse(bigDecimalList))
}

property("ordering should be available for numeric boxed types") = forAll { bigDecimalList: List[BigDecimal] =>
property("ordering should be available for numeric boxed types") = forAll { (bigDecimalList: List[BigDecimal]) =>
toBoxed(bigDecimalList).sorted == toBoxed(bigDecimalList.sorted)
}

property("reverse ordering should be available for numeric boxed types") = forAll { bigDecimalList: List[BigDecimal] =>
property("reverse ordering should be available for numeric boxed types") = forAll { (bigDecimalList: List[BigDecimal]) =>
sortedReverse(toBoxed(bigDecimalList)) == toBoxed(sortedReverse(bigDecimalList))
}
}

0 comments on commit 017360d

Please sign in to comment.