Skip to content

Commit

Permalink
Fix/interface effect propagation (#2495)
Browse files Browse the repository at this point in the history
* fix: abstract effect not propagated to interface

* refactor: share abstract type detection between interface and object

* fmt
  • Loading branch information
i10416 authored Dec 21, 2024
1 parent 66816c0 commit aa97e09
Show file tree
Hide file tree
Showing 5 changed files with 102 additions and 16 deletions.
10 changes: 7 additions & 3 deletions core/src/main/scala/caliban/parsing/adt/Definition.scala
Original file line number Diff line number Diff line change
Expand Up @@ -77,14 +77,18 @@ object Definition {
}
}

sealed trait TypeDefinition extends TypeSystemDefinition {
sealed trait TypeDefinition extends TypeSystemDefinition {
def name: String
def description: Option[String]
def directives: List[Directive]

final def isDeprecated: Boolean = Directives.isDeprecated(directives)
final def deprecationReason: Option[String] = Directives.deprecationReason(directives)
}
private[caliban] sealed trait AggregationTypeDefinition extends TypeDefinition {
def fields: List[FieldDefinition]
def implements: List[NamedType]
}

object TypeDefinition {

Expand All @@ -94,7 +98,7 @@ object Definition {
implements: List[NamedType],
directives: List[Directive],
fields: List[FieldDefinition]
) extends TypeDefinition {
) extends AggregationTypeDefinition {
override def toString: String = "Object"
}

Expand All @@ -104,7 +108,7 @@ object Definition {
implements: List[NamedType],
directives: List[Directive],
fields: List[FieldDefinition]
) extends TypeDefinition {
) extends AggregationTypeDefinition {
override def toString: String = "Interface"
}

Expand Down
4 changes: 3 additions & 1 deletion core/src/main/scala/caliban/parsing/adt/Document.scala
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ case class Document(definitions: List[Definition], sourceMapper: SourceMapper) {
@transient lazy val subscriptionDefinitions: List[OperationDefinition] =
definitions.collect { case od: OperationDefinition if od.operationType == Subscription => od }

def objectTypeDefinition(name: String): Option[ObjectTypeDefinition] =
def objectTypeDefinition(name: String): Option[ObjectTypeDefinition] =
objectTypeDefinitions.find(t => t.name == name)
def interfaceTypeDefinition(name: String): Option[InterfaceTypeDefinition] =
interfaceTypeDefinitions.find(t => t.name == name)
}
43 changes: 31 additions & 12 deletions tools/src/main/scala/caliban/tools/SchemaWriter.scala
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package caliban.tools

import caliban.parsing.adt.Definition.TypeSystemDefinition.TypeDefinition
import caliban.parsing.adt.Definition.TypeSystemDefinition.AggregationTypeDefinition
import caliban.parsing.adt.Definition.TypeSystemDefinition.TypeDefinition._
import caliban.parsing.adt.Directives.{ LazyDirective, NewtypeDirective }
import caliban.parsing.adt.Type.{ ListType, NamedType }
Expand Down Expand Up @@ -89,10 +90,10 @@ object SchemaWriter {
s"${safeName(field.name)} :$argsTypeName $effect[$fieldType]"
}

def isAbstractEffectful(typedef: ObjectTypeDefinition): Boolean =
def isAbstractEffectful(typedef: AggregationTypeDefinition): Boolean =
isEffectTypeAbstract && isEffectful(typedef)

def isEffectful(typedef: ObjectTypeDefinition): Boolean =
def isEffectful(typedef: AggregationTypeDefinition): Boolean =
isLocalEffectful(typedef) || isNestedEffectful(typedef)

def isUnionSiblingAbstractEffectful(typedef: ObjectTypeDefinition): Boolean =
Expand All @@ -102,19 +103,27 @@ object SchemaWriter {
union.memberTypes.flatMap(schema.objectTypeDefinition).exists(isAbstractEffectful)
)

def isLocalEffectful(typedef: ObjectTypeDefinition): Boolean =
def isLocalEffectful(typedef: AggregationTypeDefinition): Boolean =
hasFieldWithDirective(typedef, LazyDirective)

def isNestedEffectful(typedef: ObjectTypeDefinition): Boolean =
def isNestedEffectful(typedef: AggregationTypeDefinition): Boolean =
typeNameToNestedFields
.getOrElse(typedef.name, List.empty)
.exists(t => hasFieldWithDirective(t, LazyDirective))

def generic(op: ObjectTypeDefinition, isRootDefinition: Boolean = false): String =
if ((isRootDefinition && isEffectTypeAbstract) || isAbstractEffectful(op) || isUnionSiblingAbstractEffectful(op))
s"[${effect}[_]]"
else
s""
def generic(tpeDef: AggregationTypeDefinition, isRootDefinition: Boolean = false): String =
tpeDef match {
case op: ObjectTypeDefinition =>
if (
(isRootDefinition && isEffectTypeAbstract) || isAbstractEffectful(op) || isUnionSiblingAbstractEffectful(op)
)
s"[${effect}[_]]"
else
""
case interface: InterfaceTypeDefinition =>
if (isAbstractEffectful(interface)) s"[${effect}[_]]"
else ""
}

def writeRootQueryOrMutationDef(op: ObjectTypeDefinition): String =
s"""
Expand Down Expand Up @@ -209,7 +218,9 @@ object SchemaWriter {
s"""@GQLInterface
${writeTypeAnnotations(
interface
)}sealed trait ${interface.name} extends scala.Product with scala.Serializable $derivesEnvSchema {
)}sealed trait ${interface.name}${generic(
interface
)} extends scala.Product with scala.Serializable $derivesEnvSchema {
${interface.fields.map(field => writeField(field, interface, isMethod = true)).mkString("\n")}
}
"""
Expand Down Expand Up @@ -449,8 +460,16 @@ object SchemaWriter {
schemaDef.exists(_.subscription.getOrElse("Subscription") == obj.name)
)
.map { obj =>
val extendsInterfaces = obj.implements.map(name => name.name)
val partOfUnionTypes = unionTypes.collect {
val extendsInterfaces = obj.implements.flatMap { case NamedType(name, _) =>
schema
.interfaceTypeDefinition(name)
.map(interface =>
if (isAbstractEffectful(interface)) s"$name[F]"
else name
)
}

val partOfUnionTypes = unionTypes.collect {
case (u, members) if members.exists(_.name == obj.name) =>
if (members.exists(isAbstractEffectful)) s"${u.name}[F]" else u.name
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import Types._

import caliban.schema.Annotations._

object Types {

final case class T0[F[_]](i: F[Int]) extends Direct[F]
final case class T1[F[_]](i: scala.Option[T2[F]]) extends Transitive[F]
final case class T2[F[_]](i: F[scala.Option[Int]])

@GQLInterface
sealed trait Direct[F[_]] extends scala.Product with scala.Serializable {
def i: F[Int]
}

@GQLInterface
sealed trait Transitive[F[_]] extends scala.Product with scala.Serializable {
def i: scala.Option[T2[F]]
}

}

object Operations {

final case class Query[F[_]](
t0: F[T0[F]],
t1: F[T1[F]]
)

}
31 changes: 31 additions & 0 deletions tools/src/test/scala/caliban/tools/SchemaWriterSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,37 @@ object SchemaWriterSpec extends SnapshotTest {
isEffectTypeAbstract = true
)
),
snapshotTest("interface, abstracted effect and lazy combination")(
gen(
"""
directive @lazy on FIELD_DEFINITION
type Query {
t0: T0!
t1: T1!
}
interface Direct {
i: Int! @lazy
}
interface Transitive {
i: T2
}
type T0 implements Direct {
i: Int! @lazy
}
type T1 implements Transitive {
i: T2
}
type T2 {
i: Int @lazy
}
""",
effect = "F",
isEffectTypeAbstract = true
)
),
snapshotTest("simple mutation with abstracted effect type")(
gen(
"""
Expand Down

0 comments on commit aa97e09

Please sign in to comment.