Skip to content

Commit

Permalink
Add support for encoding enum singletons as products.
Browse files Browse the repository at this point in the history
  • Loading branch information
mrdziuban committed Jul 11, 2024
1 parent 1024d69 commit 1a09157
Show file tree
Hide file tree
Showing 5 changed files with 46 additions and 9 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,13 @@ import io.bullet.borer.internal.{ElementDeque, Util}

import scala.annotation.tailrec

sealed abstract class AdtEncodingStrategy:
sealed abstract class AdtEncodingStrategy(private var enumCasesAsProduct0: Boolean = false):
final def enumCasesAsProduct: Boolean = enumCasesAsProduct0
final def withEnumCasesAsProduct(enumCasesAsProduct: Boolean): this.type = {
enumCasesAsProduct0 = enumCasesAsProduct
this
}

def writeAdtEnvelopeOpen(w: Writer, typeName: String): w.type
def writeAdtEnvelopeClose(w: Writer, typeName: String): w.type

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -335,6 +335,8 @@ object ArrayBasedCodecs extends DerivationApi {
case enc: AdtEncoder[A] => enc.write(w, value)
case enc => enc.write(w.writeArrayOpen(2).writeString(typeId), value).writeArrayClose()
}

final def enumCasesAsProduct: Boolean = false
}

abstract class ArrayBasedAdtDecoder[T] extends DerivedAdtDecoder[T]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ abstract class DerivedAdtEncoder[T] extends AdtEncoder[T] {

def writeAdtValue[A](w: Writer, typeId: Long, value: A)(using encoder: Encoder[A]): Writer
def writeAdtValue[A](w: Writer, typeId: String, value: A)(using encoder: Encoder[A]): Writer
def enumCasesAsProduct: Boolean
}

/**
Expand Down
42 changes: 34 additions & 8 deletions derivation/src/main/scala/io/bullet/borer/derivation/Deriver.scala
Original file line number Diff line number Diff line change
Expand Up @@ -242,15 +242,44 @@ abstract private[derivation] class Deriver[F[_]: Type, T: Type, Q <: Quotes](usi
val flattened = flattenedSubs[Encoder](rootNode, deepRecurse = false, includeEnumSingletonCases = true)
val typeIdsAndNodesSorted = extractTypeIdsAndSort(rootNode, flattened)

def writeEnumSingletonTypeId(typeId: Long | String): Expr[Writer] =
typeId match
case x: Long => '{ $w.writeLong(${ Expr(x) }) }
case x: String => '{ $w.writeString(${ Expr(x) }) }

def writeAdtValue[A: Type](
typeId: Long | String,
valueAsA: Expr[A],
encA: Expr[Encoder[A]]): Expr[Writer] =
typeId match
case x: Long => '{ $self.writeAdtValue[A]($w, ${ Expr(x) }, $valueAsA)(using $encA) }
case x: String => '{ $self.writeAdtValue[A]($w, ${ Expr(x) }, $valueAsA)(using $encA) }

def rec(ix: Int): Expr[Writer] =
if (ix < typeIdsAndNodesSorted.length) {
val (typeId, sub) = typeIdsAndNodesSorted(ix)
if (sub.isEnumSingletonCase) {
val enumRef = sub.enumRef.asExprOf[AnyRef]
val writeTypeId = typeId match
case x: Long => '{ $w.writeLong(${ Expr(x) }) }
case x: String => '{ $w.writeString(${ Expr(x) }) }
'{ if ($value.asInstanceOf[AnyRef] eq $enumRef) $writeTypeId else ${ rec(ix + 1) } }
lazy val writeTypeId = writeEnumSingletonTypeId(typeId)
lazy val writeAsProduct = sub.tpe.asType match
case '[a] =>
val valueAsA = '{ $value.asInstanceOf[a] }
val encA = '{
new Encoder[a] {
def write(w: Writer, value: a): Writer = {
w.writeMapStart()
w.writeBreak()
}
}
}
writeAdtValue[a](typeId, valueAsA, encA)

'{
if ($value.asInstanceOf[AnyRef] eq $enumRef)
if ($self.enumCasesAsProduct) $writeAsProduct else $writeTypeId
else
${ rec(ix + 1) }
}
} else {
val testType = sub.tpe match
case AppliedType(x, _) => x
Expand All @@ -259,10 +288,7 @@ abstract private[derivation] class Deriver[F[_]: Type, T: Type, Q <: Quotes](usi
case '[a] =>
val valueAsA = '{ $value.asInstanceOf[a] }
val encA = Expr.summon[Encoder[a]].getOrElse(fail(s"Cannot find given Encoder[${Type.show[a]}]"))
val writeKeyed = typeId match
case x: Long => '{ $self.writeAdtValue[a]($w, ${ Expr(x) }, $valueAsA)(using $encA) }
case x: String =>
'{ $self.writeAdtValue[a]($w, ${ Expr(x) }, $valueAsA)(using $encA) }
val writeKeyed = writeAdtValue[a](typeId, valueAsA, encA)
testType.asType match
case '[b] => '{ if ($value.isInstanceOf[b]) $writeKeyed else ${ rec(ix + 1) } }
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -617,6 +617,8 @@ object MapBasedCodecs extends DerivationApi {
enc.write(w.writeString(typeId), value)
strategy.writeAdtEnvelopeClose(w, typeName)
}

final def enumCasesAsProduct: Boolean = strategy.enumCasesAsProduct
}

abstract class MapBasedAdtDecoder[T] extends DerivedAdtDecoder[T]
Expand Down

0 comments on commit 1a09157

Please sign in to comment.