Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

added simpleEnum annotation #564

Merged
merged 10 commits into from
Sep 5, 2023
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 @@ -501,23 +501,53 @@ object DeriveSchema {
}
}

val isSimpleEnum: Boolean =
!tpe.typeSymbol.asClass.knownDirectSubclasses.map { subtype =>
subtype.typeSignature.decls.sorted.collect {
case p: TermSymbol if p.isCaseAccessor && !p.isMethod => p
}.size
}.exists(_ > 0)

val hasSimpleEnum: Boolean =
tpe.typeSymbol.annotations.exists(_.tree.tpe =:= typeOf[_root_.zio.schema.annotation.simpleEnum])

@nowarn
val typeAnnotations: List[Tree] =
tpe.typeSymbol.annotations.collect {
case annotation if !(annotation.tree.tpe <:< JavaAnnotationTpe) =>
annotation.tree match {
case q"new $annConstructor(..$annotationArgs)" =>
q"new ${annConstructor.tpe.typeSymbol}(..$annotationArgs)"
case q"new $annConstructor()" =>
q"new ${annConstructor.tpe.typeSymbol}()"
case tree =>
c.warning(c.enclosingPosition, s"Unhandled annotation tree $tree")
EmptyTree
}
case annotation =>
c.warning(c.enclosingPosition, s"Unhandled annotation ${annotation.tree}")
EmptyTree
}.filter(_ != EmptyTree)
val typeAnnotations: List[Tree] = (isSimpleEnum, hasSimpleEnum) match {
case (true, false) =>
tpe.typeSymbol.annotations.collect {
case annotation if !(annotation.tree.tpe <:< JavaAnnotationTpe) =>
annotation.tree match {
case q"new $annConstructor(..$annotationArgs)" =>
q"new ${annConstructor.tpe.typeSymbol}(..$annotationArgs)"
case q"new $annConstructor()" =>
q"new ${annConstructor.tpe.typeSymbol}()"
case tree =>
c.warning(c.enclosingPosition, s"Unhandled annotation tree $tree")
EmptyTree
}
case annotation =>
c.warning(c.enclosingPosition, s"Unhandled annotation ${annotation.tree}")
EmptyTree
}.filter(_ != EmptyTree).+:(q"new _root_.zio.schema.annotation.simpleEnum(true)")
case (false, true) =>
c.abort(c.enclosingPosition, s"${show(tpe)} must be a simple Enum")
case _ =>
tpe.typeSymbol.annotations.collect {
case annotation if !(annotation.tree.tpe <:< JavaAnnotationTpe) =>
annotation.tree match {
case q"new $annConstructor(..$annotationArgs)" =>
q"new ${annConstructor.tpe.typeSymbol}(..$annotationArgs)"
case q"new $annConstructor()" =>
q"new ${annConstructor.tpe.typeSymbol}()"
case tree =>
c.warning(c.enclosingPosition, s"Unhandled annotation tree $tree")
EmptyTree
}
case annotation =>
c.warning(c.enclosingPosition, s"Unhandled annotation ${annotation.tree}")
EmptyTree
}.filter(_ != EmptyTree)
}

val selfRefName = c.freshName("ref")
val selfRefIdent = Ident(TermName(selfRefName))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -293,7 +293,14 @@ private case class DeriveSchema()(using val ctx: Quotes) extends ReflectionUtils

val cases = typesAndLabels.map { case (tpe, label) => deriveCase[T](tpe, label, newStack) }

val annotationExprs = TypeRepr.of[T].typeSymbol.annotations.filter(filterAnnotation).map(_.asExpr)
val isSimpleEnum: Boolean = !TypeRepr.of[T].typeSymbol.children.map(_.declaredFields.length).exists( _ > 0 )
val hasSimpleEnumAnn: Boolean = TypeRepr.of[T].typeSymbol.hasAnnotation(TypeRepr.of[_root_.zio.schema.annotation.simpleEnum].typeSymbol)

val annotationExprs = (isSimpleEnum, hasSimpleEnumAnn) match {
case (true, false) => TypeRepr.of[T].typeSymbol.annotations.filter(filterAnnotation).map(_.asExpr).+:('{_root_.zio.schema.annotation.simpleEnum(true)})
case (false, true) => throw new Exception(s"${TypeRepr.of[T].typeSymbol.name} must be a simple Enum")
case _ => TypeRepr.of[T].typeSymbol.annotations.filter(filterAnnotation).map(_.asExpr)
}
val annotations = '{ zio.Chunk.fromIterable(${Expr.ofSeq(annotationExprs)}) }

val typeInfo = '{TypeId.parse(${Expr(TypeRepr.of[T].show)})}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ package zio.schema
import scala.annotation.Annotation

import zio.Chunk
import zio.schema.annotation.{ fieldName, optionalField }
import zio.schema.annotation.{ fieldName, optionalField, simpleEnum }
import zio.test._

object DeriveSchemaSpec extends ZIOSpecDefault with VersionSpecificDeriveSchemaSpec {
Expand Down Expand Up @@ -243,6 +243,13 @@ object DeriveSchemaSpec extends ZIOSpecDefault with VersionSpecificDeriveSchemaS
implicit val schema: Schema[OptionalField] = DeriveSchema.gen[OptionalField]
}

@simpleEnum
sealed trait SimpleEnum1
case class SimpleClass1() extends SimpleEnum1

sealed trait SimpleEnum2
case class SimpleClass2() extends SimpleEnum2

override def spec: Spec[Environment, Any] = suite("DeriveSchemaSpec")(
suite("Derivation")(
test("correctly derives case class 0") {
Expand Down Expand Up @@ -449,6 +456,14 @@ object DeriveSchemaSpec extends ZIOSpecDefault with VersionSpecificDeriveSchemaS
)
}
assert(derived)(hasSameSchema(expected))
},
test("correctly derives simpleEnum with annotation") {
val derived = DeriveSchema.gen[SimpleEnum1]
assertTrue(derived.annotations == Chunk(simpleEnum(false)))
},
test("correctly derives simpleEnum without annotation") {
val derived = DeriveSchema.gen[SimpleEnum2]
assertTrue(derived.annotations == Chunk(simpleEnum(true)))
}
),
versionSpecificSuite
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -380,7 +380,7 @@ object JsonCodec {

private def enumEncoder[Z](parentSchema: Schema.Enum[Z], cases: Schema.Case[Z, _]*): ZJsonEncoder[Z] =
// if all cases are CaseClass0, encode as a String
if (cases.forall(_.schema.isInstanceOf[Schema.CaseClass0[_]])) {
if (parentSchema.annotations.exists(_.isInstanceOf[simpleEnum])) {
val caseMap: Map[Z, String] = cases
.filterNot(_.annotations.exists(_.isInstanceOf[transientCase]))
.map(
Expand Down
4 changes: 2 additions & 2 deletions zio-schema/shared/src/main/scala/zio/schema/Schema.scala
Original file line number Diff line number Diff line change
Expand Up @@ -161,8 +161,8 @@ object Schema extends SchemaEquality {

def defer[A](schema: => Schema[A]): Schema[A] = Lazy(() => schema)

def enumeration[A, C <: CaseSet.Aux[A]](id: TypeId, caseSet: C): Schema[A] =
EnumN(id, caseSet, Chunk.empty)
def enumeration[A, C <: CaseSet.Aux[A]](id: TypeId, caseSet: C, annotations: Chunk[Any] = Chunk.empty): Schema[A] =
EnumN(id, caseSet, annotations)

def fail[A](message: String): Schema[A] = Fail(message)

Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
package zio.schema.annotation

final case class recordName(name: String) extends scala.annotation.StaticAnnotation
import scala.annotation.StaticAnnotation

final case class recordName(name: String) extends StaticAnnotation
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package zio.schema.annotation

import scala.annotation.StaticAnnotation

/*
* Automatically added in sealed traits with only case objects or case class without parameters.
* Gives error if it used in other types of enumerations.
*/

final case class simpleEnum(automaticallyAdded: Boolean = false) extends StaticAnnotation
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import scala.collection.mutable
import zio.constraintless.TypeList
import zio.prelude._
import zio.schema._
import zio.schema.annotation.simpleEnum
import zio.{ Chunk, ChunkBuilder }

sealed trait ExtensibleMetaSchema[BuiltIn <: TypeList] { self =>
Expand Down Expand Up @@ -636,23 +637,31 @@ object ExtensibleMetaSchema {
materialize(left, refs),
materialize(right, refs)
)
case ExtensibleMetaSchema.Sum(id, _, elems, _) =>
case ExtensibleMetaSchema.Sum(id, _, elems, _) => {
val (cases, isSimple) = elems.foldRight[(CaseSet.Aux[Any], Boolean)]((CaseSet.Empty[Any](), true)) {
case (Labelled(label, ast), (acc, wasSimple)) => {
val _case: Schema.Case[Any, Any] = Schema
.Case[Any, Any](
label,
materialize(ast, refs).asInstanceOf[Schema[Any]],
identity[Any],
identity[Any],
_.isInstanceOf[Any],
Chunk.empty
)
val isSimple: Boolean = _case.schema match {
case _: Schema.CaseClass0[_] => true
case _ => false
}
(CaseSet.Cons(_case, acc), wasSimple && isSimple)
}
}
Schema.enumeration[Any, CaseSet.Aux[Any]](
id,
elems.foldRight[CaseSet.Aux[Any]](CaseSet.Empty[Any]()) {
case (Labelled(label, ast), acc) =>
val _case: Schema.Case[Any, Any] = Schema
.Case[Any, Any](
label,
materialize(ast, refs).asInstanceOf[Schema[Any]],
identity[Any],
identity[Any],
_.isInstanceOf[Any],
Chunk.empty
)
CaseSet.Cons(_case, acc)
}
cases,
if (isSimple) Chunk(new simpleEnum(true)) else Chunk.empty
)
}
case ExtensibleMetaSchema.Either(_, left, right, _) =>
Schema.either(
materialize(left, refs),
Expand Down
Loading