Skip to content

Commit

Permalink
Support scala 3 sum type EnumType in scala3
Browse files Browse the repository at this point in the history
  • Loading branch information
RustedBones committed May 24, 2024
1 parent 8600d52 commit 0f20d2e
Show file tree
Hide file tree
Showing 6 changed files with 79 additions and 97 deletions.
2 changes: 1 addition & 1 deletion build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -267,7 +267,7 @@ val commonSettings = Seq(
"-Yretain-trees",
// tolerate some nested macro expansion
"-Xmax-inlines",
"64"
"128"
)
case Some((2, 13)) =>
Seq(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ trait EnumTypeDerivation {
val ns = sealedTrait.typeName.owner
val subs = sealedTrait.subtypes.map(_.typeclass)
val values = subs.flatMap(_.values).sorted.toList
val annotations = (sealedTrait.annotations ++ subs.flatMap(_.annotations)).toList
val annotations = sealedTrait.inheritedAnnotations.toList ++ sealedTrait.annotations.toList
EnumType.create(
n,
ns,
Expand Down
77 changes: 0 additions & 77 deletions shared/src/main/scala-3/magnolify/shared/EnumTypeDerivation.scala

This file was deleted.

39 changes: 37 additions & 2 deletions shared/src/main/scala-3/magnolify/shared/EnumTypeMacros.scala
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@

package magnolify.shared

import magnolia1.Macro

import scala.compiletime.*
import scala.quoted.*
import scala.deriving.Mirror

Expand Down Expand Up @@ -43,5 +46,37 @@ 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:

private transparent inline def values[A, S <: Tuple](m: Mirror.SumOf[A]): List[A] =
inline erasedValue[S] match
case _: EmptyTuple =>
Nil
case _: (s *: tail) =>
val infos = summonFrom {
case mm: Mirror.SumOf[`s`] =>
values[A, mm.MirroredElemTypes](mm.asInstanceOf[m.type])
case mm: Mirror.ProductOf[`s`] if Macro.isObject[`s`] =>
List(mm.fromProduct(EmptyTuple).asInstanceOf[A])
case _ =>
error("Cannot derive EnumType for non sum type")
}
infos ::: values[A, tail](m)

inline implicit def gen[T](using mirror: Mirror.Of[T]): EnumType[T] =
val s = inline mirror match
case m: Mirror.SumOf[T] =>
values[T, m.MirroredElemTypes](m).distinct.sortBy(_.toString)
case m: Mirror.ProductOf[T] if Macro.isObject[T] =>
List(m.fromProduct(EmptyTuple))

val it = Macro.typeInfo[T]
val annotations = Macro.inheritedAnns[T] ++ Macro.anns[T]

EnumType.create(
it.short,
it.owner,
s.map(_.toString),
annotations,
name => s.find(_.toString == name).get
)
40 changes: 28 additions & 12 deletions test/src/test/scala/magnolify/shared/EnumTypeSuite.scala
Original file line number Diff line number Diff line change
Expand Up @@ -48,15 +48,31 @@ class EnumTypeSuite extends MagnolifySuite {
}

test("ADT") {
val et = ensureSerializable(EnumType[ADT.Color])
assertEquals(et.name, "Color")
assertEquals(et.namespace, "magnolify.test.ADT")
assertEquals(et.values, List("Blue", "Green", "Red")) // ADTs are ordered alphabetically
assertEquals(et.from("Red"), ADT.Red)
assertEquals(et.to(ADT.Red), "Red")
val etPrimaryColor = ensureSerializable(EnumType[ADT.PrimaryColor])
assertEquals(etPrimaryColor.name, "PrimaryColor")
assertEquals(etPrimaryColor.namespace, "magnolify.test.ADT")
assertEquals(
etPrimaryColor.values,
List("Blue", "Green", "Red")
) // ADTs are ordered alphabetically
assertEquals(etPrimaryColor.from("Red"), ADT.Red)
assertEquals(etPrimaryColor.to(ADT.Red), "Red")
// Magnolia does not capture Java annotations
val as = et.annotations.collect { case a: ScalaAnnotation => a.value }
assertEquals(as, List("Color", "Red"))
val annPrimaryColor = etPrimaryColor.annotations.collect { case a: ScalaAnnotation => a.value }
assertEquals(annPrimaryColor, List("Color", "PrimaryColor"))

val etColor = ensureSerializable(EnumType[ADT.Color])
assertEquals(etColor.name, "Color")
assertEquals(etColor.namespace, "magnolify.test.ADT")
assertEquals(
etColor.values,
List("Blue", "Cyan", "Green", "Magenta", "Red", "Yellow")
) // ADTs are ordered alphabetically
assertEquals(etColor.from("Magenta"), ADT.Magenta)
assertEquals(etColor.to(ADT.Magenta), "Magenta")
// Magnolia does not capture Java annotations
val as = etColor.annotations.collect { case a: ScalaAnnotation => a.value }
assertEquals(as, List("Color"))
}

test("ADT No Default Constructor") {
Expand All @@ -78,9 +94,9 @@ class EnumTypeSuite extends MagnolifySuite {
| ^
|""".stripMargin
val scala3Error =
"""|error: Cannot prove that Some[magnolify.test.ADT.Color] <:< Singleton.
|
| ^
"""|error: Cannot derive EnumType for non sum type
| val error = compileErrors("EnumType.gen[Option[ADT.Color]]")
| ^
|""".stripMargin
if (Properties.versionNumberString.startsWith("2.12")) {
assertNoDiff(error, scala2Error)
Expand Down Expand Up @@ -160,7 +176,7 @@ class EnumTypeSuite extends MagnolifySuite {
}

test("ADT CaseMapper") {
val et = ensureSerializable(EnumType[ADT.Color](CaseMapper(_.toLowerCase)))
val et = ensureSerializable(EnumType[ADT.PrimaryColor](CaseMapper(_.toLowerCase)))
assertEquals(et.values, List("blue", "green", "red")) // ADTs are ordered alphabetically
assertEquals(et.from("red"), ADT.Red)
assertEquals(et.to(ADT.Red), "red")
Expand Down
16 changes: 12 additions & 4 deletions test/src/test/scala/magnolify/test/ADT.scala
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,18 @@ object ADT {

@ScalaAnnotation("Color")
sealed trait Color
@ScalaAnnotation("Red")
case object Red extends Color
case object Green extends Color
case object Blue extends Color
@ScalaAnnotation("PrimaryColor")
sealed trait PrimaryColor extends Color
case object Red extends PrimaryColor
case object Green extends PrimaryColor
case object Blue extends PrimaryColor
@ScalaAnnotation("SecondaryColor")
sealed abstract class SecondaryColor(p1: PrimaryColor, p2: PrimaryColor) extends Color {
def primaryColors: Set[PrimaryColor] = Set(p1, p2)
}
case object Yellow extends SecondaryColor(Red, Green)
case object Cyan extends SecondaryColor(Green, Blue)
case object Magenta extends SecondaryColor(Red, Blue)

// This is needed to simulate an error with "no valid constructor"
// exception on attempt to deserialize a case object implementing an abstract class without
Expand Down

0 comments on commit 0f20d2e

Please sign in to comment.