Skip to content

Commit

Permalink
Fragment applicability by subtype intersection
Browse files Browse the repository at this point in the history
  • Loading branch information
umazalakain committed Apr 10, 2024
1 parent 830bfc5 commit d448b28
Show file tree
Hide file tree
Showing 2 changed files with 16 additions and 12 deletions.
19 changes: 8 additions & 11 deletions modules/core/src/main/scala/compiler.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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)))
Expand Down
9 changes: 8 additions & 1 deletion modules/core/src/main/scala/schema.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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
Expand Down

0 comments on commit d448b28

Please sign in to comment.