Skip to content

Commit

Permalink
Save info about generically applied types in new annotation (#695)
Browse files Browse the repository at this point in the history
  • Loading branch information
987Nabil committed Jun 29, 2024
1 parent 91899ff commit 499a565
Show file tree
Hide file tree
Showing 4 changed files with 70 additions and 8 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -232,7 +232,20 @@ object DeriveSchema {

val typeId = q"_root_.zio.schema.TypeId.parse(${tpe.typeSymbol.fullName})"

val typeAnnotations: List[Tree] = collectTypeAnnotations(tpe)
val genericAnnotations: List[Tree] =
if (tpe.typeArgs.isEmpty) Nil
else {
val typeMembers = tpe.typeSymbol.asClass.typeParams.map(_.name.toString)
val typeArgs = tpe.typeArgs
.map(_.typeSymbol.fullName)
.map(t => q"_root_.zio.schema.TypeId.parse(${t}).asInstanceOf[_root_.zio.schema.TypeId.Nominal]")
val typeMembersWithArgs = typeMembers.zip(typeArgs).map { case (m, a) => q"($m, $a)" }
List(
q"new _root_.zio.schema.annotation.genericTypeInfo(_root_.scala.collection.immutable.ListMap(..$typeMembersWithArgs))"
)
}

val typeAnnotations: List[Tree] = collectTypeAnnotations(tpe) ++ genericAnnotations

val defaultConstructorValues =
tpe.typeSymbol.asClass.primaryConstructor.asMethod.paramLists.head
Expand Down Expand Up @@ -557,7 +570,20 @@ object DeriveSchema {
val hasSimpleEnum: Boolean =
tpe.typeSymbol.annotations.exists(_.tree.tpe =:= typeOf[_root_.zio.schema.annotation.simpleEnum])

val typeAnnotations: List[Tree] = (isSimpleEnum, hasSimpleEnum) match {
val genericAnnotations: List[Tree] =
if (tpe.typeArgs.isEmpty) Nil
else {
val typeMembers = tpe.typeSymbol.asClass.typeParams.map(_.name.toString)
val typeArgs = tpe.typeArgs
.map(_.typeSymbol.fullName)
.map(t => q"_root_.zio.schema.TypeId.parse(${t}).asInstanceOf[_root_.zio.schema.TypeId.Nominal]")
val typeMembersWithArgs = typeMembers.zip(typeArgs).map { case (m, a) => q"($m, $a)" }
List(
q"new _root_.zio.schema.annotation.genericTypeInfo(_root_.scala.collection.immutable.ListMap(..$typeMembersWithArgs))"
)
}

val typeAnnotations: List[Tree] = ((isSimpleEnum, hasSimpleEnum) match {
case (true, false) =>
tpe.typeSymbol.annotations.collect {
case annotation if !(annotation.tree.tpe <:< JavaAnnotationTpe) =>
Expand Down Expand Up @@ -592,7 +618,7 @@ object DeriveSchema {
c.warning(c.enclosingPosition, s"Unhandled annotation ${annotation.tree}")
EmptyTree
}.filter(_ != EmptyTree)
}
}) ++ genericAnnotations

val selfRefName = c.freshName("ref")
val selfRefIdent = Ident(TermName(selfRefName))
Expand All @@ -602,6 +628,19 @@ object DeriveSchema {
val typeArgs = subtypes ++ Iterable(tpe)

val cases = subtypes.map { (subtype: Type) =>
val genericAnnotations: List[Tree] =
if (subtype.typeArgs.isEmpty) Nil
else {
val typeMembers = subtype.typeSymbol.asClass.typeParams.map(_.name.toString)
val typeArgs = subtype.typeArgs
.map(_.typeSymbol.fullName)
.map(t => q"_root_.zio.schema.TypeId.parse(${t}).asInstanceOf[_root_.zio.schema.TypeId.Nominal]")
val typeMembersWithArgs = typeMembers.zip(typeArgs).map { case (m, a) => q"($m, $a)" }
List(
q"new _root_.zio.schema.annotation.genericTypeInfo(_root_.scala.collection.immutable.ListMap(..$typeMembersWithArgs))"
)
}

val typeAnnotations: List[Tree] =
subtype.typeSymbol.annotations.collect {
case annotation if !(annotation.tree.tpe <:< JavaAnnotationTpe) =>
Expand All @@ -617,7 +656,7 @@ object DeriveSchema {
case annotation =>
c.warning(c.enclosingPosition, s"Unhandled annotation ${annotation.tree}")
EmptyTree
}.filter(_ != EmptyTree)
}.filter(_ != EmptyTree) ++ genericAnnotations

val caseLabel = subtype.typeSymbol.name.toString.trim
val caseSchema = directInferSchema(tpe, concreteType(tpe, subtype), currentFrame +: stack)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,12 @@ private case class DeriveSchema()(using val ctx: Quotes) {
then TypeRepr.of[T].typeSymbol.children.map(_.annotations).flatten.filter (filterAnnotation).map (_.asExpr)
else TypeRepr.of[T].typeSymbol.annotations.filter (filterAnnotation).map (_.asExpr)

val annotations = '{ zio.Chunk.fromIterable(${Expr.ofSeq(annotationExprs)}) ++ zio.Chunk.fromIterable(${Expr.ofSeq(docAnnotationExpr)}) }
val genericAnnotations = if (TypeRepr.of[T].classSymbol.exists(_.typeMembers.nonEmpty)){
val typeMembersExpr = Expr.ofSeq(TypeRepr.of[T].classSymbol.get.typeMembers.map { t => Expr(t.name) })
val typeArgsExpr = Expr.ofSeq(TypeRepr.of[T].typeArgs.map { t => Expr(t.typeSymbol.fullName) })
List('{zio.schema.annotation.genericTypeInfo(ListMap.from(${typeMembersExpr}.zip(${typeArgsExpr}.map(name => TypeId.parse(name).asInstanceOf[TypeId.Nominal]))))})
} else List.empty
val annotations = '{ zio.Chunk.fromIterable(${Expr.ofSeq(annotationExprs)}) ++ zio.Chunk.fromIterable(${Expr.ofSeq(docAnnotationExpr)}) ++ zio.Chunk.fromIterable(${Expr.ofSeq(genericAnnotations)}) }
val typeInfo = '{TypeId.parse(${Expr(TypeRepr.of[T].show)})}

val applied = if (labels.length <= 22) {
Expand Down Expand Up @@ -372,7 +377,12 @@ private case class DeriveSchema()(using val ctx: Quotes) {
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)}) ++ zio.Chunk.fromIterable(${Expr.ofSeq(docAnnotationExpr.toList)}) }
val genericAnnotations = if (TypeRepr.of[T].classSymbol.exists(_.typeMembers.nonEmpty)){
val typeMembersExpr = Expr.ofSeq(TypeRepr.of[T].classSymbol.get.typeMembers.map { t => Expr(t.name) })
val typeArgsExpr = Expr.ofSeq(TypeRepr.of[T].typeArgs.map { t => Expr(t.typeSymbol.fullName) })
List('{zio.schema.annotation.genericTypeInfo(ListMap.from(${typeMembersExpr}.zip(${typeArgsExpr}.map(name => TypeId.parse(name).asInstanceOf[TypeId.Nominal]))))})
} else List.empty
val annotations = '{ zio.Chunk.fromIterable(${Expr.ofSeq(annotationExprs)}) ++ zio.Chunk.fromIterable(${Expr.ofSeq(docAnnotationExpr.toList)}) ++ zio.Chunk.fromIterable(${Expr.ofSeq(genericAnnotations)}) }

val typeInfo = '{TypeId.parse(${Expr(TypeRepr.of[T].show)})}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,14 @@ package zio.schema

import scala.annotation.nowarn
import scala.reflect.ClassTag

import zio.schema.Deriver.WrappedF
import zio.schema.Schema.Field
import zio.schema.annotation.fieldDefaultValue
import zio.schema.annotation.{ fieldDefaultValue, genericTypeInfo }
import zio.test.{ Spec, TestEnvironment, ZIOSpecDefault, assertTrue }
import zio.{ Chunk, Scope }

import scala.collection.immutable.ListMap

@nowarn object DeriveSpec extends ZIOSpecDefault with VersionSpecificDeriveSpec {
override def spec: Spec[TestEnvironment with Scope, Any] =
suite("Derive")(
Expand Down Expand Up @@ -183,7 +184,11 @@ import zio.{ Chunk, Scope }
.asInstanceOf[Schema.Record[GenericRecordWithDefaultValue[Int]]]
.fields(0)
.annotations
val maybeTypeInfo = capturedSchema.schema.annotations.collectFirst { case gt @ genericTypeInfo(_) => gt }
assertTrue(
maybeTypeInfo.contains(
genericTypeInfo(ListMap("T" -> TypeId.parse("scala.Int").asInstanceOf[TypeId.Nominal]))
),
annotations.exists { a =>
a.isInstanceOf[fieldDefaultValue[_]] &&
a.asInstanceOf[fieldDefaultValue[Option[Int]]].value == None
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package zio.schema.annotation

import zio.schema.TypeId

import scala.collection.immutable.ListMap

final case class genericTypeInfo(appliedTypes: ListMap[String, TypeId.Nominal])
extends scala.annotation.StaticAnnotation

0 comments on commit 499a565

Please sign in to comment.