Skip to content

Commit

Permalink
Construct enum variant from ordinal, not Java Class[T]
Browse files Browse the repository at this point in the history
  • Loading branch information
deusaquilus committed Aug 7, 2023
1 parent 4b3f795 commit f9fa34d
Show file tree
Hide file tree
Showing 4 changed files with 74 additions and 34 deletions.
22 changes: 17 additions & 5 deletions morphir/src-3/org/finos/morphir/datamodel/Deriver.scala
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,6 @@ object Deriver {
// since this is actually a string that will be spliced in at runtime
inline erasedValue[Elems] match {
case _: (head *: tail) =>
val ct = summonClassTagOrFail[head].asInstanceOf[Class[Any]]
// need to make sure that ALL of the matches inside here (including the `unionType` match)
// is inline otherwise very strange things happen! Macros will run for even variants that shouldn't be matching
// (i.e. because the other side of the case match branch is also running)
Expand All @@ -66,20 +65,23 @@ object Deriver {
if (isCaseClass[head]) {
summonProductDeriver[head] match {
case deriver: GenericProductDeriver[Product] @unchecked =>
SumBuilder.EnumProduct(fieldName, ct, deriver)
println(
s"field ${typeName[head]}: ${fieldName} - ${deriver.builder.name.localName}(${deriver.builder.fields})"
)
SumBuilder.EnumProduct(fieldName, deriver)
case other =>
throw new IllegalArgumentException(
"Illegal state, should not be possible, summonProductDeriver always returns a GenericProductDeriver"
)
}
} // enum case without fields
else {
SumBuilder.EnumSingleton(fieldName, ct)
SumBuilder.EnumSingleton(fieldName)
}
// for the sum-case just do regular recursive derivation
case UnionType.Sum =>
val deriver = summonDeriver[head].asInstanceOf[Deriver[Any]]
SumBuilder.SumVariant(fieldName, ct, deriver)
SumBuilder.SumVariant(fieldName, deriver)
}

// return the variant and recurse
Expand Down Expand Up @@ -139,6 +141,7 @@ object Deriver {
inline def deriveSumFromMirror[T](m: Mirror.SumOf[T]): GenericSumDeriver[T] =
inline if (isEnumOrSealedTrait[T]) {
val sumTypeName = summonQualifiedName[T]
val enumName = typeName[T]

// The clause `inferUnionType` NEEDs to be a macro otherwise we can't get the value
// coming out if it to work with inline matches/ifs and if our matches/ifs are not inline
Expand All @@ -152,7 +155,16 @@ object Deriver {
val builder =
inline inferUnionType[T] match {
case UnionType.Enum | UnionType.SealedTrait =>
SumBuilder(SumBuilder.SumType.Enum(sumTypeName), variants)
val ordinalGetter: Any => Int =
(v: Any) =>
v match {
case t: T => m.ordinal(t)
case _ =>
throw new IllegalArgumentException(
s"The value `$v` is not an instance of the needed enum class ${enumName}"
)
}
SumBuilder(SumBuilder.SumType.Enum(sumTypeName), ordinalGetter, variants)
case UnionType.Sum =>
error("Simple union types not allowed yet in builder synthesis")
}
Expand Down
11 changes: 0 additions & 11 deletions morphir/src-3/org/finos/morphir/datamodel/DeriverMacros.scala
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import org.finos.morphir.datamodel.Deriver.UnionType
import org.finos.morphir.datamodel.namespacing.{LocalName, Namespace, PartialName}

import scala.quoted.*
import scala.reflect.ClassTag
import scala.deriving.Mirror

trait GlobalDatamodelContext {
Expand Down Expand Up @@ -129,16 +128,6 @@ object DeriverMacros {
Expr(flags.is(Flags.Case) && !flags.is(Flags.Module) && !(TypeRepr.of[T] <:< TypeRepr.of[List[Any]]))
}

inline def summonClassTagOrFail[T]: Class[T] = ${ summonClassTagOrFailImpl[T] }
def summonClassTagOrFailImpl[T: Type](using Quotes): Expr[Class[T]] = {
import quotes.reflect._
Expr.summon[ClassTag[T]] match {
case Some(value) => '{ $value.runtimeClass.asInstanceOf[Class[T]] }
case None =>
report.errorAndAbort(s"A classTag for the type ${TypeRepr.of[T].show} could not be found!")
}
}

inline def showType[T]: String = ${ showTypeImpl[T] }
def showTypeImpl[T: Type](using Quotes): Expr[String] = {
import quotes.reflect._
Expand Down
28 changes: 10 additions & 18 deletions morphir/src-3/org/finos/morphir/datamodel/SumBuilder.scala
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,11 @@ import org.finos.morphir.datamodel.namespacing.{LocalName, Namespace, QualifiedN

import scala.reflect.ClassTag

private[datamodel] case class SumBuilder(tpe: SumBuilder.SumType, variants: List[SumBuilder.Variant]) {
private[datamodel] case class SumBuilder(
tpe: SumBuilder.SumType,
ordinalGetter: Any => Int,
variants: List[SumBuilder.Variant]
) {
private def failInsideNotProduct(derivedAs: Any) =
throw new IllegalArgumentException(
s"Inner enum data (for: ${tpe}) is only allowed to come from a Scala product but it was derived as: $derivedAs"
Expand All @@ -13,7 +17,6 @@ private[datamodel] case class SumBuilder(tpe: SumBuilder.SumType, variants: List
lazy val enumType =
tpe match {
case SumBuilder.SumType.Enum(name) =>
// TODO variants shuold be constructed from ordinal
val enumCases =
variants.map { v =>
v match {
Expand All @@ -40,22 +43,12 @@ private[datamodel] case class SumBuilder(tpe: SumBuilder.SumType, variants: List
}

def run(value: Any): Data = {
val usedVariant = {
val cls = value.getClass
variants.find(v => cls.isAssignableFrom(v.tag)) match {
case Some(value) => value
case None =>
val varNames = variants.map(v => s"${v.enumLabel}:${v.tag.getName}")
throw new IllegalArgumentException(
s"Cannot decode instance of ${value}:${cls.getName} because it was not a sub-type of any of the possibilities: ${varNames}"
)
}
}
val usedVariant = variants(ordinalGetter(value))

val enumValues =
usedVariant match {
// for a enum case object, data-type is just a 'unit'
case SumBuilder.EnumSingleton(enumLabel, tag) =>
case SumBuilder.EnumSingleton(enumLabel) =>
List()

case v: SumBuilder.EnumProduct =>
Expand Down Expand Up @@ -85,19 +78,18 @@ private[datamodel] case class SumBuilder(tpe: SumBuilder.SumType, variants: List
}
object SumBuilder {
sealed trait Variant {
def tag: Class[Any]
def enumLabel: java.lang.String
}
sealed trait EnumVariant extends Variant
// case object variant of a sealed trait or a enum case with no fields
case class EnumSingleton(enumLabel: java.lang.String, tag: Class[Any])
case class EnumSingleton(enumLabel: java.lang.String)
extends EnumVariant
// case class variant of sealed trait or enum case with fields
case class EnumProduct(enumLabel: java.lang.String, tag: Class[Any], deriver: GenericProductDeriver[Product])
case class EnumProduct(enumLabel: java.lang.String, deriver: GenericProductDeriver[Product])
extends EnumVariant

// for generic sums
case class SumVariant(enumLabel: java.lang.String, tag: Class[Any], deriver: Deriver[Any]) extends Variant
case class SumVariant(enumLabel: java.lang.String, deriver: Deriver[Any]) extends Variant

sealed trait SumType
object SumType {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package org.finos.morphir.datamodel

import org.finos.morphir.datamodel.{*, given}
import org.finos.morphir.datamodel.Concept.Enum
import org.finos.morphir.datamodel.Data
import org.finos.morphir.datamodel.Data.Case
import org.finos.morphir.datamodel.Util.*
import org.finos.morphir.datamodel.namespacing.*
import org.finos.morphir.datamodel.namespacing.PackageName.root
import org.finos.morphir.datamodel.namespacing.Namespace.ns

object EnumData4 {
import EnumGns._
implicit val gnsImpl: GlobalDatamodelContext = new GlobalDatamodelContext {
def value = gns
}

enum Foo {
case Bar
case Baz
}

val deriver = Deriver.gen[Foo]
}

class ToDataEnumsSimple extends munit.FunSuite {
import EnumGns._

val concept =
Enum(
gns :: ("Foo"),
Enum.Case(l"Bar"),
Enum.Case(l"Baz")
)

test("Enum Data 4") {
import EnumData4._
assertEquals(deriver.derive(Foo.Bar), Case()("Bar", concept))
}

test("Enum Data 4.1") {
import EnumData4._
interceptMessage[IllegalArgumentException]("The value `null` is not an instance of the needed enum class Foo") {
deriver.derive(null)
}
}
}

0 comments on commit f9fa34d

Please sign in to comment.