From d448b28f7bc2ab21b8bbedc348f2d1902af8fa18 Mon Sep 17 00:00:00 2001 From: Uma Date: Wed, 10 Apr 2024 08:59:33 +0100 Subject: [PATCH] Fragment applicability by subtype intersection As outlined in https://spec.graphql.org/October2021/#sec-Fragment-spread-is-possible --- modules/core/src/main/scala/compiler.scala | 19 ++++++++----------- modules/core/src/main/scala/schema.scala | 9 ++++++++- 2 files changed, 16 insertions(+), 12 deletions(-) diff --git a/modules/core/src/main/scala/compiler.scala b/modules/core/src/main/scala/compiler.scala index 80241477..6836f044 100644 --- a/modules/core/src/main/scala/compiler.scala +++ b/modules/core/src/main/scala/compiler.scala @@ -977,7 +977,7 @@ object QueryCompiler { f <- Elab.fragment(nme) ctpe = c.tpe.underlyingNamed subtpe <- Elab.liftR(s.definition(f.tpnme).toResult(s"Unknown type '${f.tpnme}' in type condition of fragment '$nme'")) - _ <- Elab.failure(s"Fragment '$nme' is not compatible with type '${c.tpe}'").whenA(!fragmentApplies(subtpe, ctpe)) + _ <- Elab.failure(s"Fragment '$nme' is not compatible with type '${c.tpe}'").whenA(!fragmentApplies(s, subtpe, ctpe)) _ <- Elab.push(c.asType(subtpe), f.child) ec <- transform(f.child) _ <- Elab.pop @@ -999,7 +999,7 @@ object QueryCompiler { case Some(tpnme) => Elab.liftR(s.definition(tpnme).toResult(s"Unknown type '$tpnme' in type condition inline fragment")) } - _ <- Elab.failure(s"Inline fragment with type condition '$subtpe' is not compatible with type '$ctpe'").whenA(!fragmentApplies(subtpe, ctpe)) + _ <- Elab.failure(s"Inline fragment with type condition '$subtpe' is not compatible with type '$ctpe'").whenA(!fragmentApplies(s, subtpe, ctpe)) _ <- Elab.push(c.asType(subtpe), child) ec <- transform(child) _ <- Elab.pop @@ -1013,16 +1013,13 @@ object QueryCompiler { /** * Tests the supplied type condition is satisfied by the supplied context type + * https://spec.graphql.org/October2021/#sec-Fragment-spread-is-possible */ - def fragmentApplies(typeCond: NamedType, ctpe: NamedType): Boolean = - (typeCond.dealias, ctpe.dealias) match { - case (u: UnionType, _) => - u.members.exists(m => fragmentApplies(m, ctpe)) - case (_, u: UnionType) => - u.members.exists(m => fragmentApplies(typeCond, m)) - case _ => - typeCond <:< ctpe || ctpe <:< typeCond - } + def fragmentApplies(schema: Schema, typeCond: NamedType, ctpe: NamedType): Boolean = { + val typeCondPoss = schema.subtypes(typeCond.dealias) + val ctpePoss = schema.subtypes(ctpe.dealias) + typeCondPoss.exists(x => ctpePoss.exists(y => x =:= y)) + } def elaborateBinding(b: Binding, vars: Vars): Elab[Binding] = Elab.liftR(Value.elaborateValue(b.value, vars).map(ev => b.copy(value = ev))) diff --git a/modules/core/src/main/scala/schema.scala b/modules/core/src/main/scala/schema.scala index 2b619bc6..dfc4941d 100644 --- a/modules/core/src/main/scala/schema.scala +++ b/modules/core/src/main/scala/schema.scala @@ -257,6 +257,9 @@ trait Schema { case _ => schemaType } } + + /** Returns all subtypes of `tpe` */ + def subtypes(tpe: NamedType): Set[NamedType] = types.filter(_ <:< tpe).toSet } object Schema { @@ -575,6 +578,8 @@ sealed trait Type extends Product { def isUnion: Boolean = false + def isObject: Boolean = false + def /(pathElement: String): Path = Path.from(this) / pathElement @@ -784,7 +789,9 @@ case class ObjectType( fields: List[Field], interfaces: List[NamedType], directives: List[Directive] -) extends TypeWithFields +) extends TypeWithFields { + override def isObject: Boolean = true +} /** * Object extensions allow additional fields to be added to a pre-existing object type