Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -813,6 +813,12 @@ object JsonCodecMaker {

def isOpaque(tpe: TypeRepr): Boolean = tpe.typeSymbol.flags.is(Flags.Opaque)

@tailrec
def opaqueDealias(tpe: TypeRepr): TypeRepr = tpe match {
case trTpe @ TypeRef(_, _) if trTpe.isOpaqueAlias => opaqueDealias(trTpe.translucentSuperType.dealias)
case _ => tpe
}

def isSealedClass(tpe: TypeRepr): Boolean = tpe.typeSymbol.flags.is(Flags.Sealed)

def hasSealedParent(tpe: TypeRepr): Boolean =
Expand Down Expand Up @@ -1345,6 +1351,11 @@ object JsonCodecMaker {
case ConstantType(DoubleConstant(v)) =>
'{ if ($in.readKeyAsDouble() != ${Expr(v)}) $in.decodeError(${Expr(s"expected key: \"$v\"")}); ${Expr(v)} }
case _ => cannotFindKeyCodecError(tpe)
} else if (isOpaque(tpe)) {
val sTpe = opaqueDealias(tpe)
sTpe.asType match { case '[s] =>
'{ ${genReadKey[s](sTpe :: types.tail, in)}.asInstanceOf[T] }
}
} else cannotFindKeyCodecError(tpe)
}.asExprOf[T]

Expand Down Expand Up @@ -1560,6 +1571,12 @@ object JsonCodecMaker {
case ConstantType(FloatConstant(v)) => '{ $out.writeKey(${Expr(v)}) }
case ConstantType(DoubleConstant(v)) => '{ $out.writeKey(${Expr(v)}) }
case _ => cannotFindKeyCodecError(tpe)
} else if (isOpaque(tpe)) {
val sTpe = opaqueDealias(tpe)
sTpe.asType match { case '[s] =>
val newX = '{ $x.asInstanceOf[s] }
genWriteKey[s](newX, sTpe :: types.tail, out)
}
} else cannotFindKeyCodecError(tpe)

def genWriteConstantKey(name: String, out: Expr[JsonWriter])(using Quotes): Expr[Unit] =
Expand Down Expand Up @@ -1891,7 +1908,10 @@ object JsonCodecMaker {
tpe1.asType match
case '[t1] => getClassInfo(tpe).genNew(List(List(genNullValue[t1](tpe1 :: types).asTerm))).asExprOf[T]
} else if (TypeRepr.of[Null] <:< tpe) '{ null }.asExprOf[T]
else '{ null.asInstanceOf[T] }.asExprOf[T]
else if (isOpaque(tpe) && !isNamedTuple(tpe)) {
val sTpe = opaqueDealias(tpe)
sTpe.asType match { case '[st] => '{ ${genNullValue[st](sTpe :: types.tail)}.asInstanceOf[T] }.asExprOf[T] }
} else '{ null.asInstanceOf[T] }.asExprOf[T]

case class ReadDiscriminator(valDefOpt: Option[ValDef]) {
def skip(in: Expr[JsonReader], l: Expr[Int])(using Quotes): Expr[Unit] = valDefOpt match {
Expand Down Expand Up @@ -2743,7 +2763,13 @@ object JsonCodecMaker {
} else if (isNonAbstractScalaClass(tpe)) withDecoderFor(methodKey, default, in) { (in, default) =>
genReadNonAbstractScalaClass(getClassInfo(tpe), types, useDiscriminator, in, default)
} else if (isConstType(tpe)) genReadConstType(tpe, isStringified, in)
else cannotFindValueCodecError(tpe)
else if (isOpaque(tpe)) {
val sTpe = opaqueDealias(tpe)
sTpe.asType match { case '[s] =>
val newDefault = '{ $default.asInstanceOf[s] }.asExprOf[s]
'{ ${genReadVal[s](sTpe :: types.tail, newDefault, isStringified, useDiscriminator, in)}.asInstanceOf[T] }
}
} else cannotFindValueCodecError(tpe)

def genWriteNonAbstractScalaClass[T: Type](x: Expr[T], typeInfo: TypeInfo, types: List[TypeRepr],
optDiscriminator: Option[WriteDiscriminator],
Expand Down Expand Up @@ -3249,7 +3275,12 @@ object JsonCodecMaker {
} else if (isNonAbstractScalaClass(tpe)) withEncoderFor(methodKey, m, out) { (out, x) =>
genWriteNonAbstractScalaClass(x, getClassInfo(tpe), types, optWriteDiscriminator, out)
} else if (isConstType(tpe)) getWriteConstType(tpe, isStringified, out)
else cannotFindValueCodecError(tpe)
else if (isOpaque(tpe)) {
val sTpe = opaqueDealias(tpe)
sTpe.asType match { case '[s] =>
genWriteVal[s]('{ $m.asInstanceOf[s] }, sTpe :: types.tail, isStringified, optWriteDiscriminator, out)
}
} else cannotFindValueCodecError(tpe)

val codecDef = '{ // FIXME: generate a type class instance using `ClassDef.apply` and `Symbol.newClass` calls after graduating from experimental API: https://www.scala-lang.org/blog/2022/06/21/scala-3.1.3-released.html
new JsonValueCodec[A] {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,27 @@ import scala.jdk.CollectionConverters._
import scala.language.implicitConversions
import scala.util.hashing.MurmurHash3

opaque type Gram = Double

object Gram {
inline def apply(x: Double): Gram = x

extension (x: Gram)
inline def toDouble: Double = x
}

opaque type Meter <: Double = Double

object Meter {
inline def apply(x: Double): Meter = x
}

opaque type Year = Int

object Year {
def apply(x: Int): Option[Year] = if (x > 1900) Some(x) else None

inline def from(inline x: Int): Year =
inline def of(inline x: Int): Year =
requireConst(x)
inline if x > 1900 then x else error("expected year > 1900")

Expand Down Expand Up @@ -123,6 +138,16 @@ class JsonCodecMakerNewTypeSpec extends VerifyingSpec {
"""No implicit 'com.github.plokhotnyuk.jsoniter_scala.core.JsonValueCodec[_ >: scala.Nothing <: scala.Any]' defined for '"A" | "B" | "C"'."""
})
}
"serialize and deserialize Scala3 opaque types" in {
case class Planet(@stringified radius: Meter, mass: Gram)

verifySerDeser(make[Meter], Meter(6.37814e6), "6378140.0")
verifySerDeser(make[Gram](CodecMakerConfig.withIsStringified(true)), Gram(5.976e+27), """"5.976E27"""")
verifySerDeser(make[Array[Meter]], Array(Meter(6.37814e6)), "[6378140.0]")
verifySerDeser(make[Array[Gram]], Array(Gram(5.976e+27)), "[5.976E27]")
verifySerDeser(make[Map[Meter, Gram]], Map(Meter(6.37814e6) -> Gram(5.976e+27)), """{"6378140.0":5.976E27}""")
verifySerDeser(make[Planet], Planet(Meter(6.37814e6), Gram(5.976e+27)), """{"radius":"6378140.0","mass":5.976E27}""")
}
"serialize and deserialize Scala3 opaque types using custom value codecs" in {
case class Period(start: Year, end: Year)

Expand All @@ -136,8 +161,8 @@ class JsonCodecMakerNewTypeSpec extends VerifyingSpec {

val nullValue: Year = null.asInstanceOf[Year]
}
verifySerDeser(make[Period], Period(Year.from(1976), Year.from(2022)), """{"start":1976,"end":2022}""")
verifySerDeser(make[Array[Year]], Array(Year(1976).get), "[1976]")
verifySerDeser(make[Period], Period(Year.of(1976), Year.of(2022)), """{"start":1976,"end":2022}""")
verifySerDeser(make[Array[Year]], Array(Year.of(1976)), "[1976]")
}
"serialize and deserialize a Scala3 union type using a custom codec" in {
type Value = String | Boolean | Double
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -943,7 +943,7 @@ class JsonCodecMakerSpec extends VerifyingSpec {
verifySerDeser(codecOfApp, App("Skype", Version.`Current`), """{"name":"Skype","version":"8.10"}""")
verifyDeserError(codecOfApp, """{"name":"Skype","version":"9.0"}""", "illegal version, offset: 0x0000001e")
}
"serialize and deserialize outer types using custom value codecs for opaque types" in {
"serialize and deserialize outer types using custom value codecs for newtypes" in {
abstract class Foo {
type Bar
}
Expand Down