-
Notifications
You must be signed in to change notification settings - Fork 146
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: implement Scala 3 Constant and Union support for string-based l…
…iterals as enums (#1125) * feat: implement Scala 3 Constant and Union support for string-based literals as enums * add JsonCodec test * format * add non-mdoc examples (needs Scala 3)
- Loading branch information
1 parent
df9c147
commit 608bb6e
Showing
11 changed files
with
147 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
2 changes: 2 additions & 0 deletions
2
zio-json/shared/src/main/scala-2.x/zio/json/JsonDecoderVersionSpecific.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,5 @@ | ||
package zio.json | ||
|
||
private[json] trait JsonDecoderVersionSpecific | ||
|
||
private[json] trait DecoderLowPriorityVersionSpecific |
2 changes: 2 additions & 0 deletions
2
zio-json/shared/src/main/scala-2.x/zio/json/JsonEncoderVersionSpecific.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,5 @@ | ||
package zio.json | ||
|
||
private[json] trait JsonEncoderVersionSpecific | ||
|
||
private[json] trait EncoderLowPriorityVersionSpecific |
23 changes: 23 additions & 0 deletions
23
zio-json/shared/src/main/scala-3/zio/json/JsonDecoderVersionSpecific.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,28 @@ | ||
package zio.json | ||
|
||
import scala.compiletime.* | ||
import scala.compiletime.ops.any.IsConst | ||
|
||
private[json] trait JsonDecoderVersionSpecific { | ||
inline def derived[A: deriving.Mirror.Of]: JsonDecoder[A] = DeriveJsonDecoder.gen[A] | ||
} | ||
|
||
trait DecoderLowPriorityVersionSpecific { | ||
|
||
inline given unionOfStringEnumeration[T](using IsUnionOf[String, T]): JsonDecoder[T] = | ||
val values = UnionDerivation.constValueUnionTuple[String, T] | ||
JsonDecoder.string.mapOrFail( | ||
{ | ||
case raw if values.toList.contains(raw) => Right(raw.asInstanceOf[T]) | ||
case _ => Left("expected one of: " + values.toList.mkString(", ")) | ||
} | ||
) | ||
|
||
inline given constStringToEnum[T <: String](using IsConst[T] =:= true): JsonDecoder[T] = | ||
JsonDecoder.string.mapOrFail( | ||
{ | ||
case raw if raw == constValue[T] => Right(constValue[T]) | ||
case _ => Left("expected one of: " + constValue[T]) | ||
} | ||
) | ||
} |
11 changes: 11 additions & 0 deletions
11
zio-json/shared/src/main/scala-3/zio/json/JsonEncoderVersionSpecific.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,16 @@ | ||
package zio.json | ||
|
||
import scala.compiletime.ops.any.IsConst | ||
|
||
private[json] trait JsonEncoderVersionSpecific { | ||
inline def derived[A: deriving.Mirror.Of]: JsonEncoder[A] = DeriveJsonEncoder.gen[A] | ||
} | ||
|
||
private[json] trait EncoderLowPriorityVersionSpecific { | ||
|
||
inline given unionOfStringEnumeration[T](using IsUnionOf[String, T]): JsonEncoder[T] = | ||
JsonEncoder.string.asInstanceOf[JsonEncoder[T]] | ||
|
||
inline given constStringToEnum[T <: String](using IsConst[T] =:= true): JsonEncoder[T] = | ||
JsonEncoder.string.narrow[T] | ||
} |
74 changes: 74 additions & 0 deletions
74
zio-json/shared/src/main/scala-3/zio/json/union_derivation.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
package zio.json | ||
|
||
import scala.compiletime.* | ||
import scala.deriving.* | ||
import scala.quoted.* | ||
|
||
@scala.annotation.implicitNotFound("${A} is not a union type") | ||
sealed trait IsUnion[A] | ||
|
||
object IsUnion: | ||
|
||
private val singleton: IsUnion[Any] = new IsUnion[Any] {} | ||
|
||
transparent inline given derived[A]: IsUnion[A] = ${ deriveImpl[A] } | ||
|
||
private def deriveImpl[A](using quotes: Quotes, t: Type[A]): Expr[IsUnion[A]] = | ||
import quotes.reflect.* | ||
val tpe: TypeRepr = TypeRepr.of[A] | ||
tpe.dealias match | ||
case o: OrType => ('{ IsUnion.singleton.asInstanceOf[IsUnion[A]] }).asExprOf[IsUnion[A]] | ||
case other => report.errorAndAbort(s"${tpe.show} is not a Union") | ||
|
||
@scala.annotation.implicitNotFound("${A} is not a union of ${T}") | ||
sealed trait IsUnionOf[T, A] | ||
|
||
object IsUnionOf: | ||
|
||
private val singleton: IsUnionOf[Any, Any] = new IsUnionOf[Any, Any] {} | ||
|
||
transparent inline given derived[T, A]: IsUnionOf[T, A] = ${ deriveImpl[T, A] } | ||
|
||
private def deriveImpl[T, A](using quotes: Quotes, t: Type[T], a: Type[A]): Expr[IsUnionOf[T, A]] = | ||
import quotes.reflect.* | ||
val tpe: TypeRepr = TypeRepr.of[A] | ||
val bound: TypeRepr = TypeRepr.of[T] | ||
|
||
def validateTypes(tpe: TypeRepr): Unit = | ||
tpe.dealias match | ||
case o: OrType => | ||
validateTypes(o.left) | ||
validateTypes(o.right) | ||
case o => | ||
if o <:< bound then () | ||
else report.errorAndAbort(s"${o.show} is not a subtype of ${bound.show}") | ||
|
||
tpe.dealias match | ||
case o: OrType => | ||
validateTypes(o) | ||
('{ IsUnionOf.singleton.asInstanceOf[IsUnionOf[T, A]] }).asExprOf[IsUnionOf[T, A]] | ||
case other => report.errorAndAbort(s"${tpe.show} is not a Union") | ||
|
||
object UnionDerivation: | ||
transparent inline def constValueUnionTuple[T, A](using IsUnionOf[T, A]): Tuple = ${ constValueUnionTupleImpl[T, A] } | ||
|
||
private def constValueUnionTupleImpl[T: Type, A: Type](using Quotes): Expr[Tuple] = | ||
Expr.ofTupleFromSeq(constTypes[T, A]) | ||
|
||
private def constTypes[T: Type, A: Type](using Quotes): List[Expr[Any]] = | ||
import quotes.reflect.* | ||
val tpe: TypeRepr = TypeRepr.of[A] | ||
val bound: TypeRepr = TypeRepr.of[T] | ||
|
||
def transformTypes(tpe: TypeRepr): List[TypeRepr] = | ||
tpe.dealias match | ||
case o: OrType => | ||
transformTypes(o.left) ::: transformTypes(o.right) | ||
case o: Constant if o <:< bound && o.isSingleton => | ||
o :: Nil | ||
case o => | ||
report.errorAndAbort(s"${o.show} is not a subtype of ${bound.show}") | ||
|
||
transformTypes(tpe).distinct.map(_.asType match | ||
case '[t] => '{ constValue[t] } | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters