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

[Scala 3] Fix derivation of case objects / parameterless case classes that contain @GQLField methods #2305

Merged
merged 3 commits into from
Jun 25, 2024
Merged
Changes from 1 commit
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
Prev Previous commit
Next Next commit
Cleanup
kyri-petrou committed Jun 25, 2024

Verified

This commit was signed with the committer’s verified signature.
commit e9be7d16faa70e4368c5e06a6575a41d8f2ec4f4
47 changes: 23 additions & 24 deletions core/src/main/scala-3/caliban/schema/macros/Macros.scala
Original file line number Diff line number Diff line change
@@ -5,7 +5,7 @@ import caliban.schema.Schema

import scala.quoted.*

export magnolia1.{ Macro as MMacro, TypeInfo }
export magnolia1.TypeInfo

object Macros {
inline def isFieldExcluded[P, T]: Boolean = ${ isFieldExcludedImpl[P, T] }
@@ -16,9 +16,8 @@ object Macros {
transparent inline def hasFieldsFromMethods[T]: Boolean =
${ hasFieldsFromMethodsImpl[T] }

transparent inline def fieldsFromMethods[R, T]: List[(String, List[Any], Schema[R, ?])] = ${
fieldsFromMethodsImpl[R, T]
}
transparent inline def fieldsFromMethods[R, T]: List[(String, List[Any], Schema[R, ?])] =
${ fieldsFromMethodsImpl[R, T] }

/**
* Tests whether type argument [[FieldT]] in [[Parent]] is annotated with [[GQLExcluded]]
@@ -52,6 +51,15 @@ object Macros {
Expr(TypeRepr.of[P].typeSymbol.flags.is(Flags.Enum) && TypeRepr.of[T].typeSymbol.flags.is(Flags.Enum))
}

private def hasFieldsFromMethodsImpl[T: Type](using q: Quotes): Expr[Boolean] = {
import q.reflect.*
val targetSym = TypeTree.of[T].symbol
val annType = TypeRepr.of[GQLField]
val annSym = annType.typeSymbol

Expr(targetSym.declaredMethods.exists(_.getAnnotation(annSym).isDefined))
}

private def fieldsFromMethodsImpl[R: Type, T: Type](using
q: Quotes
): Expr[List[(String, List[Any], Schema[R, ?])]] = {
@@ -80,8 +88,18 @@ object Macros {
if (methodSym.signature.paramSigs.size > 0)
report.errorAndAbort(s"Method '${methodSym.name}' annotated with @GQLField must be parameterless")

// Unfortunately we can't reuse Magnolias filtering so we copy the implementation
def filterAnnotation(ann: Term): Boolean = {
val tpe = ann.tpe

tpe != annType && // No need to include the GQLField annotation
(tpe.typeSymbol.maybeOwner.isNoSymbol ||
(tpe.typeSymbol.owner.fullName != "scala.annotation.internal" &&
tpe.typeSymbol.owner.fullName != "jdk.internal"))
}

def extractAnnotations(methodSym: Symbol): List[Expr[Any]] =
methodSym.annotations.filter(filterAnnotation(_, annType)).map(_.asExpr.asInstanceOf[Expr[Any]])
methodSym.annotations.filter(filterAnnotation).map(_.asExpr.asInstanceOf[Expr[Any]])

Expr.ofList {
targetSym.declaredMethods
@@ -99,25 +117,6 @@ object Macros {
}
}

private def hasFieldsFromMethodsImpl[T: Type](using q: Quotes): Expr[Boolean] = {
import q.reflect.*
val targetSym = TypeTree.of[T].symbol
val annType = TypeRepr.of[GQLField]
val annSym = annType.typeSymbol

Expr(targetSym.declaredMethods.exists(_.getAnnotation(annSym).isDefined))
}

// Unfortunately we can't reuse Magnolias filtering so we copy the implementation
private def filterAnnotation(using q: Quotes)(ann: q.reflect.Term, annType: q.reflect.TypeRepr): Boolean = {
val tpe = ann.tpe

tpe != annType && // No need to include the GQLField annotation
(tpe.typeSymbol.maybeOwner.isNoSymbol ||
(tpe.typeSymbol.owner.fullName != "scala.annotation.internal" &&
tpe.typeSymbol.owner.fullName != "jdk.internal"))
}

// Copied from Schema so that we have the same compiler error message
private inline def schemaNotFound(tpe: String) =
s"""Cannot find a Schema for type $tpe.