Repository: apollographql/apollo-rs
Pull request #511 (2023-04-04 09:42:21)

feat(compiler): validate fragment spread is possible
Part of #310

Hopefully it doesn't collide too much with #483, lets merge that PR before this one if possible.

This PR checks that fragment spreads `... frag` and inline spreads `... { }` can be applied to the selection set they are used in.

I added a `FragmentSelection` HIR node which gets created on the fly and represents either type of fragment (named or inline). This way the validation is only implemented once.

This is ready for review but there are a few more things to check before merging:
- [x] Recursion in the type system should not cause `get_possible_types` to loop
- [x] Need some tests for inline spreads without a type condition, like when it's only used to apply a directive `... @skip(if: $bool) {}`

lrlna: @goto-bus-stop i merged #483 just now ^__^ I really like this `FragmentSelection` thing you have going here, would be good to get that with some of that with the type condition validation.

apologies for all the test file conflicts 🙈 we were _really_ not use semantically correct graphql with _any_ of our fragments 
goto-bus-stop: I think this PR also reports errors when you use a fragment on a non-composite type, need to address that. It's not meant to happen for the validation in this PR and already done by the validations in Camille's PR.
goto-bus-stop: OK I'm gonna keep the "double" errors for now. It's not that trivial to avoid. Ideally we would also silence errors if you have something like this:

```graphql
fragment wrong on Int {
  fieldA
  fieldB
}
```

Currently we'll report that you can't put a fragment on an Int, and then also report that the fields `fieldA` and `fieldB` do not exist, but this is noise for the end user. So it would be better to not emit those diagnostics if something else is already wrong. But that's a bit complicated to attack I think and it's mostly about UI which we can tweak and improve after finishing everything else.

Patch:
diff --git a/crates/apollo-compiler/README.md b/crates/apollo-compiler/README.md
index bff6183..c9bafcf 100644
--- a/crates/apollo-compiler/README.md
+++ b/crates/apollo-compiler/README.md
@@ -98,29 +98,18 @@ use miette::Result;
 
 fn main() -> Result<()> {
     let schema_input = r#"
-    type Query {
-      topProducts: Product
-      customer: User
-    }
-
-    type Product {
-      type: String
-      price(setPrice: Int): Int
-    }
-
     type User {
       id: ID
       name: String
       profilePic(size: Int): URL
     }
 
+    schema { query: User }
+
     scalar URL @specifiedBy(url: "https://tools.ietf.org/html/rfc3986")
     "#;
     let query_input = r#"
-    query getProduct {
-      topProducts {
-          type
-      }
+    query getUser {
       ... vipCustomer
     }
 
@@ -142,8 +131,8 @@ fn main() -> Result<()> {
     }
     assert!(diagnostics.is_empty());
 
-    let op = compiler.db.find_operation(query_id, Some("getProduct".into()))
-        .expect("getProduct query does not exist");
+    let op = compiler.db.find_operation(query_id, Some("getUser".into()))
+        .expect("getUser query does not exist");
     let fragment_in_op: Vec<hir::FragmentDefinition> = op.selection_set().selection().iter().filter_map(|sel| match sel {
         hir::Selection::FragmentSpread(frag) => {
             Some(frag.fragment(&compiler.db)?.as_ref().clone())
diff --git a/crates/apollo-compiler/src/database/hir.rs b/crates/apollo-compiler/src/database/hir.rs
index c6fa110..f2edc1b 100644
--- a/crates/apollo-compiler/src/database/hir.rs
+++ b/crates/apollo-compiler/src/database/hir.rs
@@ -1354,6 +1354,73 @@ impl Selection {
     }
 }
 
+/// Represent both kinds of fragment selections: named and inline fragments.
+#[derive(Clone, Debug, Hash, PartialEq, Eq)]
+pub enum FragmentSelection {
+    FragmentSpread(Arc<FragmentSpread>),
+    InlineFragment(Arc<InlineFragment>),
+}
+
+impl FragmentSelection {
+    /// Get the name of this fragment's type condition.
+    ///
+    /// This returns `None` on the following invalid inputs:
+    /// - `self` is a named fragment spread, but the fragment it refers to is not defined
+    /// - `self` is an inline fragment without an explicit type condition, used in a selection set
+    ///   with a declared parent type that is not defined in the schema
+    pub fn type_condition(&self, db: &dyn HirDatabase) -> Option<String> {
+        match self {
+            FragmentSelection::FragmentSpread(spread) => spread
+                .fragment(db)
+                .map(|frag| frag.type_condition().to_string()),
+            FragmentSelection::InlineFragment(inline) => inline
+                .type_condition()
+                .or(inline.parent_obj.as_deref())
+                .map(ToString::to_string),
+        }
+    }
+
+    /// Get this fragment's selection set. This may be `None` if the fragment spread refers to an
+    /// undefined fragment.
+    pub fn selection_set(&self, db: &dyn HirDatabase) -> Option<SelectionSet> {
+        match self {
+            FragmentSelection::FragmentSpread(spread) => {
+                spread.fragment(db).map(|frag| frag.selection_set().clone())
+            }
+            FragmentSelection::InlineFragment(inline) => Some(inline.selection_set().clone()),
+        }
+    }
+
+    /// Get the type that this fragment is being spread onto.
+    ///
+    /// Returns `None` if the fragment is spread into a selection of an undefined field or type,
+    /// like in:
+    /// ```graphql
+    /// type Query {
+    ///   field: Int
+    /// }
+    /// query {
+    ///   nonExistentField {
+    ///     ... spreadToUnknownType
+    ///   }
+    /// }
+    /// ```
+    pub fn parent_type(&self, db: &dyn HirDatabase) -> Option<TypeDefinition> {
+        match self {
+            FragmentSelection::FragmentSpread(spread) => spread.parent_type(db),
+            FragmentSelection::InlineFragment(inline) => inline.parent_type(db),
+        }
+    }
+
+    /// Get the AST location information for this HIR node.
+    pub fn loc(&self) -> HirNodeLocation {
+        match self {
+            FragmentSelection::FragmentSpread(fragment_spread) => fragment_spread.loc(),
+            FragmentSelection::InlineFragment(inline_fragment) => inline_fragment.loc(),
+        }
+    }
+}
+
 #[derive(Clone, Debug, Hash, PartialEq, Eq)]
 pub struct Field {
     pub(crate) alias: Option<Arc<Alias>>,
@@ -1507,6 +1574,7 @@ pub struct InlineFragment {
     pub(crate) type_condition: Option<Name>,
     pub(crate) directives: Arc<Vec<Directive>>,
     pub(crate) selection_set: SelectionSet,
+    pub(crate) parent_obj: Option<String>,
     pub(crate) loc: HirNodeLocation,
 }
 
@@ -1516,6 +1584,26 @@ impl InlineFragment {
         self.type_condition.as_ref().map(|t| t.src())
     }
 
+    /// Get the type this fragment is spread onto.
+    ///
+    /// ## Examples
+    /// ```graphql
+    /// type Query {
+    ///     field: X
+    /// }
+    /// query {
+    ///     ... on Query { field } # spread A
+    ///     field {
+    ///         ... on X { subField } # spread B
+    ///     }
+    /// }
+    /// ```
+    /// `A.parent_type()` is `Query`.
+    /// `B.parent_type()` is `X`.
+    pub fn parent_type(&self, db: &dyn HirDatabase) -> Option<TypeDefinition> {
+        db.find_type_definition_by_name(self.parent_obj.as_ref()?.to_string())
+    }
+
     /// Get inline fragments's type definition.
     pub fn type_def(&self, db: &dyn HirDatabase) -> Option<TypeDefinition> {
         db.find_type_definition_by_name(self.type_condition()?.to_string())
@@ -1584,6 +1672,7 @@ impl InlineFragment {
 pub struct FragmentSpread {
     pub(crate) name: Name,
     pub(crate) directives: Arc<Vec<Directive>>,
+    pub(crate) parent_obj: Option<String>,
     pub(crate) loc: HirNodeLocation,
 }
 
@@ -1598,6 +1687,24 @@ impl FragmentSpread {
         db.find_fragment_by_name(self.loc.file_id(), self.name().to_string())
     }
 
+    /// Get the type this fragment is spread onto.
+    ///
+    /// ## Examples
+    /// ```graphql
+    /// type Query {
+    ///     field: X
+    /// }
+    /// query {
+    ///     ...fragment
+    ///     field { ...subFragment }
+    /// }
+    /// ```
+    /// `fragment.parent_type()` is `Query`.
+    /// `subFragment.parent_type()` is `X`.
+    pub fn parent_type(&self, db: &dyn HirDatabase) -> Option<TypeDefinition> {
+        db.find_type_definition_by_name(self.parent_obj.as_ref()?.to_string())
+    }
+
     /// Return an iterator over the variables used in directives on this spread.
     ///
     /// Variables used by the fragment definition are not included. For that, use
diff --git a/crates/apollo-compiler/src/database/hir_db.rs b/crates/apollo-compiler/src/database/hir_db.rs
index b3d20b8..c5ae4e6 100644
--- a/crates/apollo-compiler/src/database/hir_db.rs
+++ b/crates/apollo-compiler/src/database/hir_db.rs
@@ -1517,7 +1517,7 @@ fn selection(
             field(db, sel_field, parent_obj_ty, file_id).map(Selection::Field)
         }
         ast::Selection::FragmentSpread(fragment) => {
-            fragment_spread(fragment, file_id).map(Selection::FragmentSpread)
+            fragment_spread(db, fragment, parent_obj_ty, file_id).map(Selection::FragmentSpread)
         }
         ast::Selection::InlineFragment(fragment) => Some(Selection::InlineFragment(
             inline_fragment(db, fragment, parent_obj_ty, file_id),
@@ -1538,7 +1538,7 @@ fn inline_fragment(
     let directives = directives(fragment.directives(), file_id);
     let new_parent_obj = type_condition
         .clone()
-        .map_or(parent_obj.clone(), |tc| Some(tc.src().to_string()));
+        .map_or_else(|| parent_obj.clone(), |tc| Some(tc.src().to_string()));
     let selection_set: SelectionSet =
         selection_set(db, fragment.selection_set(), new_parent_obj, file_id);
     let loc = location(file_id, fragment.syntax());
@@ -1546,15 +1546,21 @@ fn inline_fragment(
     let fragment_data = InlineFragment {
         // for implicit inline fragments, the type condition is implied to be
         // that of the current scope
-        type_condition: type_condition.or(parent_obj.map(|o| Name { src: o, loc: None })),
+        type_condition: type_condition.or(parent_obj.clone().map(|o| Name { src: o, loc: None })),
         directives,
         selection_set,
+        parent_obj,
         loc,
     };
     Arc::new(fragment_data)
 }
 
-fn fragment_spread(fragment: ast::FragmentSpread, file_id: FileId) -> Option<Arc<FragmentSpread>> {
+fn fragment_spread(
+    _db: &dyn HirDatabase,
+    fragment: ast::FragmentSpread,
+    parent_obj: Option<String>,
+    file_id: FileId,
+) -> Option<Arc<FragmentSpread>> {
     let name = name(fragment.fragment_name()?.name(), file_id)?;
     let directives = directives(fragment.directives(), file_id);
     let loc = location(file_id, fragment.syntax());
@@ -1562,6 +1568,7 @@ fn fragment_spread(fragment: ast::FragmentSpread, file_id: FileId) -> Option<Arc
     let fragment_data = FragmentSpread {
         name,
         directives,
+        parent_obj,
         loc,
     };
     Some(Arc::new(fragment_data))
diff --git a/crates/apollo-compiler/src/diagnostics.rs b/crates/apollo-compiler/src/diagnostics.rs
index 3092f42..335b605 100644
--- a/crates/apollo-compiler/src/diagnostics.rs
+++ b/crates/apollo-compiler/src/diagnostics.rs
@@ -306,6 +306,13 @@ pub enum DiagnosticData {
         /// Name of the type on which the fragment is declared
         ty: String,
     },
+    #[error("fragment cannot be applied to this type")]
+    InvalidFragmentSpread {
+        /// Fragment name or None if it's an inline fragment
+        name: Option<String>,
+        /// Type name the fragment is being applied to
+        type_name: String,
+    },
     #[error("fragment `{name}` must be used in an operation")]
     UnusedFragment {
         /// Name of the fragment
diff --git a/crates/apollo-compiler/src/lib.rs b/crates/apollo-compiler/src/lib.rs
index 16ac6ec..102c05e 100644
--- a/crates/apollo-compiler/src/lib.rs
+++ b/crates/apollo-compiler/src/lib.rs
@@ -297,7 +297,7 @@ query ExampleQuery($definedVariable: Int, $definedVariable2: Int) {
   topProducts(first: $definedVariable) {
     type
   }
-  ... vipCustomer
+  customer { ... vipCustomer }
 }
 
 fragment vipCustomer on User {
@@ -647,7 +647,9 @@ query getProduct {
   topProducts {
     type
   }
-  ... vipCustomer
+  customer {
+    ... vipCustomer
+  }
 }
 
 fragment vipCustomer on User {
@@ -671,6 +673,10 @@ fragment vipCustomer on User {
             .db
             .find_operation(query_id, Some("getProduct".into()));
         let fragment_in_op: Vec<crate::hir::FragmentDefinition> = op
+            .unwrap()
+            .fields(&compiler.db)
+            .iter()
+            .find(|field| field.name() == "customer")
             .unwrap()
             .selection_set()
             .selection()
diff --git a/crates/apollo-compiler/src/validation/fragment.rs b/crates/apollo-compiler/src/validation/fragment.rs
index c0e7f4d..a3b6a0c 100644
--- a/crates/apollo-compiler/src/validation/fragment.rs
+++ b/crates/apollo-compiler/src/validation/fragment.rs
@@ -4,7 +4,178 @@ use crate::{
     validation::RecursionStack,
     FileId, ValidationDatabase,
 };
-use std::sync::Arc;
+use std::{collections::HashSet, sync::Arc};
+
+/// Given a type definition, find all the types that can be used for fragment spreading.
+///
+/// Spec: https://spec.graphql.org/October2021/#GetPossibleTypes()
+pub fn get_possible_types(
+    db: &dyn ValidationDatabase,
+    ty: hir::TypeDefinition,
+) -> Vec<hir::TypeDefinition> {
+    fn get_possible_types_impl(
+        db: &dyn ValidationDatabase,
+        ty: hir::TypeDefinition,
+        seen: &mut RecursionStack<'_>,
+        output: &mut Vec<hir::TypeDefinition>,
+    ) {
+        match &ty {
+            // 1. If `type` is an object type, return a set containing `type`.
+            hir::TypeDefinition::ObjectTypeDefinition(_) => output.push(ty),
+            // 2. If `type` is an interface type, return the set of types implementing `type`.
+            hir::TypeDefinition::InterfaceTypeDefinition(intf) => {
+                // Prevent stack overflow if interface implements itself
+                if seen.contains(intf.name()) {
+                    return;
+                }
+
+                let subtype_map = db.subtype_map();
+                if let Some(names) = subtype_map.get(intf.name()) {
+                    let mut seen = seen.push(intf.name().to_string());
+                    names
+                        .iter()
+                        .filter_map(|name| db.find_type_definition_by_name(name.to_string()))
+                        .for_each(|ty| get_possible_types_impl(db, ty, &mut seen, output))
+                }
+                output.push(ty);
+            }
+            // 3. If `type` is a union type, return the set of possible types of `type`.
+            hir::TypeDefinition::UnionTypeDefinition(union_) => {
+                // Prevent stack overflow if union is a member of itself
+                if seen.contains(union_.name()) {
+                    return;
+                }
+
+                let subtype_map = db.subtype_map();
+                if let Some(names) = subtype_map.get(union_.name()) {
+                    let mut seen = seen.push(union_.name().to_string());
+                    names
+                        .iter()
+                        .filter_map(|name| db.find_type_definition_by_name(name.to_string()))
+                        .for_each(|ty| get_possible_types_impl(db, ty, &mut seen, output))
+                }
+
+                output.push(ty);
+            }
+            _ => (),
+        }
+    }
+
+    let mut output = vec![];
+    get_possible_types_impl(db, ty, &mut RecursionStack(&mut vec![]), &mut output);
+    output
+}
+
+pub fn validate_fragment_selection(
+    db: &dyn ValidationDatabase,
+    spread: hir::FragmentSelection,
+) -> Vec<ApolloDiagnostic> {
+    let mut diagnostics = Vec::new();
+
+    let Some(cond) = spread.type_condition(db.upcast()) else {
+        // Returns None on invalid documents only
+        return diagnostics;
+    };
+    let Some(parent_type) = spread.parent_type(db.upcast()) else {
+        // We cannot check anything if the parent type is unknown.
+        return diagnostics;
+    };
+    let Some(cond_type) = db.find_type_definition_by_name(cond.clone()) else {
+        // We cannot check anything if the type condition refers to an unknown type.
+        return diagnostics;
+    };
+
+    let concrete_parent_types = db
+        .get_possible_types(parent_type.clone())
+        .into_iter()
+        .map(|ty| ty.name().to_string())
+        .collect::<HashSet<_>>();
+    let concrete_condition_types = db
+        .get_possible_types(cond_type)
+        .into_iter()
+        .map(|ty| ty.name().to_string())
+        .collect::<HashSet<_>>();
+
+    let mut applicable_types = concrete_parent_types.intersection(&concrete_condition_types);
+    if applicable_types.next().is_none() {
+        // Report specific errors for the different kinds of fragments.
+        let diagnostic = match spread {
+            hir::FragmentSelection::FragmentSpread(spread) => {
+                // This unwrap is safe because the fragment definition must exist for `cond_type` to be `Some()`.
+                let fragment_definition = spread.fragment(db.upcast()).unwrap();
+
+                ApolloDiagnostic::new(
+                    db,
+                    spread.loc().into(),
+                    DiagnosticData::InvalidFragmentSpread {
+                        name: Some(spread.name().to_string()),
+                        type_name: parent_type.name().to_string(),
+                    },
+                )
+                .label(Label::new(
+                    spread.loc(),
+                    format!("fragment `{}` cannot be applied", spread.name()),
+                ))
+                .label(Label::new(
+                    fragment_definition.loc(),
+                    format!("fragment declared with type condition `{cond}` here"),
+                ))
+                .label(Label::new(
+                    parent_type.loc(),
+                    format!("type condition `{cond}` is not assignable to this type"),
+                ))
+            }
+            hir::FragmentSelection::InlineFragment(inline) => ApolloDiagnostic::new(
+                db,
+                inline.loc().into(),
+                DiagnosticData::InvalidFragmentSpread {
+                    name: None,
+                    type_name: parent_type.name().to_string(),
+                },
+            )
+            .label(Label::new(
+                inline.loc(),
+                format!("fragment applied with type condition `{cond}` here"),
+            ))
+            .label(Label::new(
+                parent_type.loc(),
+                format!("type condition `{cond}` is not assignable to this type"),
+            )),
+        };
+
+        diagnostics.push(diagnostic);
+    }
+
+    diagnostics
+}
+
+pub fn validate_inline_fragment(
+    db: &dyn ValidationDatabase,
+    inline: Arc<hir::InlineFragment>,
+) -> Vec<ApolloDiagnostic> {
+    let mut diagnostics = Vec::new();
+
+    diagnostics.extend(db.validate_directives(
+        inline.directives().to_vec(),
+        hir::DirectiveLocation::InlineFragment,
+    ));
+
+    let type_cond_diagnostics = db.validate_fragment_type_condition(
+        inline.type_condition().map(|t| t.to_string()),
+        inline.loc(),
+    );
+    let has_type_error = !type_cond_diagnostics.is_empty();
+    diagnostics.extend(type_cond_diagnostics);
+
+    // If there was an error with the type condition, it makes no sense to validate the selection,
+    // as every field would be an error.
+    if !has_type_error {
+        diagnostics
+            .extend(db.validate_fragment_selection(hir::FragmentSelection::InlineFragment(inline)));
+    }
+
+    diagnostics
+}
 
 pub fn validate_fragment_spread(
     db: &dyn ValidationDatabase,
@@ -17,7 +188,10 @@ pub fn validate_fragment_spread(
         hir::DirectiveLocation::FragmentSpread,
     ));
 
-    if spread.fragment(db.upcast()).is_none() {
+    if spread.fragment(db.upcast()).is_some() {
+        diagnostics
+            .extend(db.validate_fragment_selection(hir::FragmentSelection::FragmentSpread(spread)))
+    } else {
         diagnostics.push(
             ApolloDiagnostic::new(
                 db,
@@ -133,7 +307,7 @@ pub fn validate_fragment_type_condition(
         Some(type_cond) => {
             let type_def = db.find_type_definition_by_name(type_cond.clone());
             let is_composite = type_def
-                .clone()
+                .as_ref()
                 .map_or(false, |ty| ty.is_composite_definition());
 
             if !is_composite {
@@ -144,6 +318,10 @@ pub fn validate_fragment_type_condition(
                         ty: type_cond.clone(),
                     },
                 )
+                .label(Label::new(
+                    loc,
+                    "fragment declares unsupported type condition `{type_cond}`",
+                ))
                 .help("fragments cannot be defined on enums, scalars and input object");
                 if let Some(def) = type_def {
                     diagnostic = diagnostic.label(Label::new(
@@ -165,15 +343,10 @@ pub fn validate_fragment_type_condition(
                     )
                     .label(Label::new(
                         loc,
-                        format!(
-                            "`{}` is defined here but not declared in the schema",
-                            &type_cond
-                        ),
+                        format!("`{type_cond}` is defined here but not declared in the schema"),
                     ))
-                    .help(
-                        "fragments must be specified on types that exist in the schema".to_string(),
-                    )
-                    .help(format!("consider defining `{}` in the schema", &type_cond)),
+                    .help("fragments must be specified on types that exist in the schema")
+                    .help(format!("consider defining `{type_cond}` in the schema")),
                 );
             }
         }
diff --git a/crates/apollo-compiler/src/validation/selection.rs b/crates/apollo-compiler/src/validation/selection.rs
index e28e4c7..9ca9fe5 100644
--- a/crates/apollo-compiler/src/validation/selection.rs
+++ b/crates/apollo-compiler/src/validation/selection.rs
@@ -312,23 +312,11 @@ pub fn validate_selection(
             hir::Selection::Field(field) => {
                 diagnostics.extend(db.validate_field(field.clone()));
             }
-            // TODO handle fragment spreads on invalid parent types
-            hir::Selection::FragmentSpread(frag) => {
-                diagnostics.extend(db.validate_fragment_spread(Arc::clone(frag)));
-                diagnostics.extend(db.validate_directives(
-                    frag.directives().to_vec(),
-                    hir::DirectiveLocation::FragmentSpread,
-                ));
+            hir::Selection::FragmentSpread(spread) => {
+                diagnostics.extend(db.validate_fragment_spread(Arc::clone(spread)));
             }
             hir::Selection::InlineFragment(inline) => {
-                diagnostics.extend(db.validate_directives(
-                    inline.directives().to_vec(),
-                    hir::DirectiveLocation::InlineFragment,
-                ));
-                diagnostics.extend(db.validate_fragment_type_condition(
-                    inline.type_condition().map(|t| t.to_string()),
-                    inline.loc(),
-                ));
+                diagnostics.extend(db.validate_inline_fragment(Arc::clone(inline)));
             }
         }
     }
diff --git a/crates/apollo-compiler/src/validation/validation_db.rs b/crates/apollo-compiler/src/validation/validation_db.rs
index 159d986..7285f83 100644
--- a/crates/apollo-compiler/src/validation/validation_db.rs
+++ b/crates/apollo-compiler/src/validation/validation_db.rs
@@ -167,9 +167,21 @@ pub trait ValidationDatabase:
     #[salsa::invoke(operation::validate_operation_definitions)]
     fn validate_operation_definitions(&self, file_id: FileId) -> Vec<ApolloDiagnostic>;
 
+    /// Given a type definition, find all the types that can be used for fragment spreading.
+    ///
+    /// Spec: https://spec.graphql.org/October2021/#GetPossibleTypes()
+    #[salsa::invoke(fragment::get_possible_types)]
+    fn get_possible_types(&self, ty: TypeDefinition) -> Vec<TypeDefinition>;
+
+    #[salsa::invoke(fragment::validate_fragment_selection)]
+    fn validate_fragment_selection(&self, spread: FragmentSelection) -> Vec<ApolloDiagnostic>;
+
     #[salsa::invoke(fragment::validate_fragment_spread)]
     fn validate_fragment_spread(&self, spread: Arc<FragmentSpread>) -> Vec<ApolloDiagnostic>;
 
+    #[salsa::invoke(fragment::validate_inline_fragment)]
+    fn validate_inline_fragment(&self, inline: Arc<InlineFragment>) -> Vec<ApolloDiagnostic>;
+
     #[salsa::invoke(fragment::validate_fragment_definitions)]
     fn validate_fragment_definitions(&self, file_id: FileId) -> Vec<ApolloDiagnostic>;
 
diff --git a/crates/apollo-compiler/src/validation/variable.rs b/crates/apollo-compiler/src/validation/variable.rs
index babde96..fb6ad1d 100644
--- a/crates/apollo-compiler/src/validation/variable.rs
+++ b/crates/apollo-compiler/src/validation/variable.rs
@@ -149,13 +149,14 @@ query ExampleQuery {
     name
   }
 
-  ... on User {
-    id
-    name
-    profilePic(size: $dimensions)
-    status
+  me {
+    ... on User {
+      id
+      name
+      profilePic(size: $dimensions)
+      status
+    }
   }
-
 }
 
 type Query {
@@ -226,10 +227,12 @@ query ExampleQuery {
     name
   }
 
-  ... on User {
-    id
-    name
-    status(membership: $goldStatus)
+  me {
+    ... on User {
+      id
+      name
+      status(membership: $goldStatus)
+    }
   }
 
   ... fragmentOne


Test patch:
diff --git a/crates/apollo-compiler/test_data/diagnostics/0008_operation_with_undefined_variables_in_inline_fragment.graphql b/crates/apollo-compiler/test_data/diagnostics/0008_operation_with_undefined_variables_in_inline_fragment.graphql
index 6cdbe99..435ef69 100644
--- a/crates/apollo-compiler/test_data/diagnostics/0008_operation_with_undefined_variables_in_inline_fragment.graphql
+++ b/crates/apollo-compiler/test_data/diagnostics/0008_operation_with_undefined_variables_in_inline_fragment.graphql
@@ -1,9 +1,9 @@
 query ExampleQuery {
   topProducts {
     name
-  }
-  ... on Product {
-      price(setPrice: $value)
+    ... on Product {
+        price(setPrice: $value)
+    }
   }
 }
 
@@ -14,4 +14,4 @@ type Query {
 type Product {
   name: String
   price(setPrice: Int): Int
-}
\ No newline at end of file
+}
diff --git a/crates/apollo-compiler/test_data/diagnostics/0009_operation_with_undefined_variables_in_fragment.graphql b/crates/apollo-compiler/test_data/diagnostics/0009_operation_with_undefined_variables_in_fragment.graphql
index 3aab3f1..6dca24f 100644
--- a/crates/apollo-compiler/test_data/diagnostics/0009_operation_with_undefined_variables_in_fragment.graphql
+++ b/crates/apollo-compiler/test_data/diagnostics/0009_operation_with_undefined_variables_in_fragment.graphql
@@ -1,14 +1,13 @@
 query ExampleQuery($variable: Int) {
-  topProducts {
+  topProducts(first: $variable) {
     name
   }
-  ... multipleSubscriptions
+  ... subFrag
 }
 
-fragment multipleSubscriptions on Review {
-  message {
-    body
-    sender(attribute: $value)
+fragment subFrag on Query {
+  topProducts(first: $variable) {
+    price(setPrice: $value)
   }
 }
 
@@ -29,4 +28,4 @@ type Message {
 type Product {
   name: String
   price(setPrice: Int): Int
-}
\ No newline at end of file
+}
diff --git a/crates/apollo-compiler/test_data/diagnostics/0009_operation_with_undefined_variables_in_fragment.txt b/crates/apollo-compiler/test_data/diagnostics/0009_operation_with_undefined_variables_in_fragment.txt
index 3e15d89..6394463 100644
--- a/crates/apollo-compiler/test_data/diagnostics/0009_operation_with_undefined_variables_in_fragment.txt
+++ b/crates/apollo-compiler/test_data/diagnostics/0009_operation_with_undefined_variables_in_fragment.txt
@@ -28,33 +28,4 @@
             name: "value",
         },
     },
-    ApolloDiagnostic {
-        cache: {
-            0: "built_in_types.graphql",
-            9: "0009_operation_with_undefined_variables_in_fragment.graphql",
-        },
-        location: DiagnosticLocation {
-            file_id: FileId {
-                id: 9,
-            },
-            offset: 19,
-            length: 14,
-        },
-        labels: [
-            Label {
-                location: DiagnosticLocation {
-                    file_id: FileId {
-                        id: 9,
-                    },
-                    offset: 19,
-                    length: 14,
-                },
-                text: "this variable is never used",
-            },
-        ],
-        help: None,
-        data: UnusedVariable {
-            name: "variable",
-        },
-    },
 ]
diff --git a/crates/apollo-compiler/test_data/diagnostics/0045_operation_with_unused_variable_in_fragment.graphql b/crates/apollo-compiler/test_data/diagnostics/0045_operation_with_unused_variable_in_fragment.graphql
index 3aab3f1..1daa4c3 100644
--- a/crates/apollo-compiler/test_data/diagnostics/0045_operation_with_unused_variable_in_fragment.graphql
+++ b/crates/apollo-compiler/test_data/diagnostics/0045_operation_with_unused_variable_in_fragment.graphql
@@ -2,13 +2,11 @@ query ExampleQuery($variable: Int) {
   topProducts {
     name
   }
-  ... multipleSubscriptions
 }
 
-fragment multipleSubscriptions on Review {
-  message {
-    body
-    sender(attribute: $value)
+fragment unusedFrag on Query {
+  topProducts {
+    price(setPrice: $variable)
   }
 }
 
@@ -29,4 +27,4 @@ type Message {
 type Product {
   name: String
   price(setPrice: Int): Int
-}
\ No newline at end of file
+}
diff --git a/crates/apollo-compiler/test_data/diagnostics/0045_operation_with_unused_variable_in_fragment.txt b/crates/apollo-compiler/test_data/diagnostics/0045_operation_with_unused_variable_in_fragment.txt
index b669d8b..a56db44 100644
--- a/crates/apollo-compiler/test_data/diagnostics/0045_operation_with_unused_variable_in_fragment.txt
+++ b/crates/apollo-compiler/test_data/diagnostics/0045_operation_with_unused_variable_in_fragment.txt
@@ -8,8 +8,8 @@
             file_id: FileId {
                 id: 45,
             },
-            offset: 183,
-            length: 6,
+            offset: 19,
+            length: 14,
         },
         labels: [
             Label {
@@ -17,15 +17,15 @@
                     file_id: FileId {
                         id: 45,
                     },
-                    offset: 183,
-                    length: 6,
+                    offset: 19,
+                    length: 14,
                 },
-                text: "not found in this scope",
+                text: "this variable is never used",
             },
         ],
         help: None,
-        data: UndefinedDefinition {
-            name: "value",
+        data: UnusedVariable {
+            name: "variable",
         },
     },
     ApolloDiagnostic {
@@ -37,8 +37,8 @@
             file_id: FileId {
                 id: 45,
             },
-            offset: 19,
-            length: 14,
+            offset: 69,
+            length: 83,
         },
         labels: [
             Label {
@@ -46,15 +46,17 @@
                     file_id: FileId {
                         id: 45,
                     },
-                    offset: 19,
-                    length: 14,
+                    offset: 69,
+                    length: 83,
                 },
-                text: "this variable is never used",
+                text: "`unusedFrag` is defined here",
             },
         ],
-        help: None,
-        data: UnusedVariable {
-            name: "variable",
+        help: Some(
+            "fragment `unusedFrag` must be used in an operation",
+        ),
+        data: UnusedFragment {
+            name: "unusedFrag",
         },
     },
 ]
diff --git a/crates/apollo-compiler/test_data/diagnostics/0050_directives_in_invalid_locations.graphql b/crates/apollo-compiler/test_data/diagnostics/0050_directives_in_invalid_locations.graphql
index 210459c..fd57923 100644
--- a/crates/apollo-compiler/test_data/diagnostics/0050_directives_in_invalid_locations.graphql
+++ b/crates/apollo-compiler/test_data/diagnostics/0050_directives_in_invalid_locations.graphql
@@ -2,11 +2,11 @@ query queryA($status: String @skip(if: true)) @skip(if: $foo){
   field
   response(status: $status) @deprecated
   human {
-    ... pet @directiveB
+    pets { ... pet @directiveB }
   }
 }
 
-fragment pet on Cat @directiveB{
+fragment pet on Cat @directiveB {
   meowVolume
   ... on Pet @directiveA {
     name
diff --git a/crates/apollo-compiler/test_data/diagnostics/0050_directives_in_invalid_locations.txt b/crates/apollo-compiler/test_data/diagnostics/0050_directives_in_invalid_locations.txt
index 90dff60..c5bf3ff 100644
--- a/crates/apollo-compiler/test_data/diagnostics/0050_directives_in_invalid_locations.txt
+++ b/crates/apollo-compiler/test_data/diagnostics/0050_directives_in_invalid_locations.txt
@@ -8,7 +8,7 @@
             file_id: FileId {
                 id: 50,
             },
-            offset: 1145,
+            offset: 1155,
             length: 18,
         },
         labels: [
@@ -17,7 +17,7 @@
                     file_id: FileId {
                         id: 50,
                     },
-                    offset: 1145,
+                    offset: 1155,
                     length: 18,
                 },
                 text: "SCHEMA is not a valid location",
@@ -47,7 +47,7 @@
             file_id: FileId {
                 id: 50,
             },
-            offset: 1296,
+            offset: 1306,
             length: 11,
         },
         labels: [
@@ -56,7 +56,7 @@
                     file_id: FileId {
                         id: 50,
                     },
-                    offset: 1296,
+                    offset: 1306,
                     length: 11,
                 },
                 text: "SCALAR is not a valid location",
@@ -66,7 +66,7 @@
                     file_id: FileId {
                         id: 50,
                     },
-                    offset: 1387,
+                    offset: 1397,
                     length: 29,
                 },
                 text: "consider adding SCALAR directive location here",
@@ -82,7 +82,7 @@
                 file_id: FileId {
                     id: 50,
                 },
-                offset: 1387,
+                offset: 1397,
                 length: 29,
             },
         },
@@ -96,7 +96,7 @@
             file_id: FileId {
                 id: 50,
             },
-            offset: 831,
+            offset: 841,
             length: 11,
         },
         labels: [
@@ -105,7 +105,7 @@
                     file_id: FileId {
                         id: 50,
                     },
-                    offset: 831,
+                    offset: 841,
                     length: 11,
                 },
                 text: "ENUM is not a valid location",
@@ -115,7 +115,7 @@
                     file_id: FileId {
                         id: 50,
                     },
-                    offset: 1356,
+                    offset: 1366,
                     length: 30,
                 },
                 text: "consider adding ENUM directive location here",
@@ -131,7 +131,7 @@
                 file_id: FileId {
                     id: 50,
                 },
-                offset: 1356,
+                offset: 1366,
                 length: 30,
             },
         },
@@ -145,7 +145,7 @@
             file_id: FileId {
                 id: 50,
             },
-            offset: 853,
+            offset: 863,
             length: 11,
         },
         labels: [
@@ -154,7 +154,7 @@
                     file_id: FileId {
                         id: 50,
                     },
-                    offset: 853,
+                    offset: 863,
                     length: 11,
                 },
                 text: "ENUM_VALUE is not a valid location",
@@ -164,7 +164,7 @@
                     file_id: FileId {
                         id: 50,
                     },
-                    offset: 1356,
+                    offset: 1366,
                     length: 30,
                 },
                 text: "consider adding ENUM_VALUE directive location here",
@@ -180,7 +180,7 @@
                 file_id: FileId {
                     id: 50,
                 },
-                offset: 1356,
+                offset: 1366,
                 length: 30,
             },
         },
@@ -194,7 +194,7 @@
             file_id: FileId {
                 id: 50,
             },
-            offset: 749,
+            offset: 759,
             length: 11,
         },
         labels: [
@@ -203,7 +203,7 @@
                     file_id: FileId {
                         id: 50,
                     },
-                    offset: 749,
+                    offset: 759,
                     length: 11,
                 },
                 text: "UNION is not a valid location",
@@ -213,7 +213,7 @@
                     file_id: FileId {
                         id: 50,
                     },
-                    offset: 1387,
+                    offset: 1397,
                     length: 29,
                 },
                 text: "consider adding UNION directive location here",
@@ -229,7 +229,7 @@
                 file_id: FileId {
                     id: 50,
                 },
-                offset: 1387,
+                offset: 1397,
                 length: 29,
             },
         },
@@ -243,7 +243,7 @@
             file_id: FileId {
                 id: 50,
             },
-            offset: 435,
+            offset: 445,
             length: 15,
         },
         labels: [
@@ -252,7 +252,7 @@
                     file_id: FileId {
                         id: 50,
                     },
-                    offset: 435,
+                    offset: 445,
                     length: 15,
                 },
                 text: "INTERFACE is not a valid location",
@@ -282,7 +282,7 @@
             file_id: FileId {
                 id: 50,
             },
-            offset: 659,
+            offset: 669,
             length: 18,
         },
         labels: [
@@ -291,7 +291,7 @@
                     file_id: FileId {
                         id: 50,
                     },
-                    offset: 659,
+                    offset: 669,
                     length: 18,
                 },
                 text: "INPUT_OBJECT is not a valid location",
@@ -321,7 +321,7 @@
             file_id: FileId {
                 id: 50,
             },
-            offset: 696,
+            offset: 706,
             length: 18,
         },
         labels: [
@@ -330,7 +330,7 @@
                     file_id: FileId {
                         id: 50,
                     },
-                    offset: 696,
+                    offset: 706,
                     length: 18,
                 },
                 text: "INPUT_FIELD_DEFINITION is not a valid location",
@@ -360,7 +360,7 @@
             file_id: FileId {
                 id: 50,
             },
-            offset: 512,
+            offset: 522,
             length: 11,
         },
         labels: [
@@ -369,7 +369,7 @@
                     file_id: FileId {
                         id: 50,
                     },
-                    offset: 512,
+                    offset: 522,
                     length: 11,
                 },
                 text: "FIELD_DEFINITION is not a valid location",
@@ -379,7 +379,7 @@
                     file_id: FileId {
                         id: 50,
                     },
-                    offset: 1387,
+                    offset: 1397,
                     length: 29,
                 },
                 text: "consider adding FIELD_DEFINITION directive location here",
@@ -395,7 +395,7 @@
                 file_id: FileId {
                     id: 50,
                 },
-                offset: 1387,
+                offset: 1397,
                 length: 29,
             },
         },
@@ -409,7 +409,7 @@
             file_id: FileId {
                 id: 50,
             },
-            offset: 896,
+            offset: 906,
             length: 11,
         },
         labels: [
@@ -418,7 +418,7 @@
                     file_id: FileId {
                         id: 50,
                     },
-                    offset: 896,
+                    offset: 906,
                     length: 11,
                 },
                 text: "OBJECT is not a valid location",
@@ -448,7 +448,7 @@
             file_id: FileId {
                 id: 50,
             },
-            offset: 968,
+            offset: 978,
             length: 56,
         },
         labels: [
@@ -457,7 +457,7 @@
                     file_id: FileId {
                         id: 50,
                     },
-                    offset: 968,
+                    offset: 978,
                     length: 56,
                 },
                 text: "ARGUMENT_DEFINITION is not a valid location",
@@ -604,7 +604,7 @@
             file_id: FileId {
                 id: 50,
             },
-            offset: 133,
+            offset: 140,
             length: 11,
         },
         labels: [
@@ -613,7 +613,7 @@
                     file_id: FileId {
                         id: 50,
                     },
-                    offset: 133,
+                    offset: 140,
                     length: 11,
                 },
                 text: "FRAGMENT_SPREAD is not a valid location",
@@ -623,7 +623,7 @@
                     file_id: FileId {
                         id: 50,
                     },
-                    offset: 1387,
+                    offset: 1397,
                     length: 29,
                 },
                 text: "consider adding FRAGMENT_SPREAD directive location here",
@@ -639,7 +639,7 @@
                 file_id: FileId {
                     id: 50,
                 },
-                offset: 1387,
+                offset: 1397,
                 length: 29,
             },
         },
@@ -653,7 +653,7 @@
             file_id: FileId {
                 id: 50,
             },
-            offset: 133,
+            offset: 278,
             length: 11,
         },
         labels: [
@@ -662,56 +662,7 @@
                     file_id: FileId {
                         id: 50,
                     },
-                    offset: 133,
-                    length: 11,
-                },
-                text: "FRAGMENT_SPREAD is not a valid location",
-            },
-            Label {
-                location: DiagnosticLocation {
-                    file_id: FileId {
-                        id: 50,
-                    },
-                    offset: 1387,
-                    length: 29,
-                },
-                text: "consider adding FRAGMENT_SPREAD directive location here",
-            },
-        ],
-        help: Some(
-            "the directive must be used in a location that the service has declared support for",
-        ),
-        data: UnsupportedLocation {
-            name: "directiveB",
-            dir_loc: FragmentSpread,
-            directive_def: DiagnosticLocation {
-                file_id: FileId {
-                    id: 50,
-                },
-                offset: 1387,
-                length: 29,
-            },
-        },
-    },
-    ApolloDiagnostic {
-        cache: {
-            0: "built_in_types.graphql",
-            50: "0050_directives_in_invalid_locations.graphql",
-        },
-        location: DiagnosticLocation {
-            file_id: FileId {
-                id: 50,
-            },
-            offset: 268,
-            length: 11,
-        },
-        labels: [
-            Label {
-                location: DiagnosticLocation {
-                    file_id: FileId {
-                        id: 50,
-                    },
-                    offset: 268,
+                    offset: 278,
                     length: 11,
                 },
                 text: "SUBSCRIPTION is not a valid location",
@@ -721,7 +672,7 @@
                     file_id: FileId {
                         id: 50,
                     },
-                    offset: 1356,
+                    offset: 1366,
                     length: 30,
                 },
                 text: "consider adding SUBSCRIPTION directive location here",
@@ -737,7 +688,7 @@
                 file_id: FileId {
                     id: 50,
                 },
-                offset: 1356,
+                offset: 1366,
                 length: 30,
             },
         },
@@ -751,7 +702,7 @@
             file_id: FileId {
                 id: 50,
             },
-            offset: 344,
+            offset: 354,
             length: 15,
         },
         labels: [
@@ -760,7 +711,7 @@
                     file_id: FileId {
                         id: 50,
                     },
-                    offset: 344,
+                    offset: 354,
                     length: 15,
                 },
                 text: "MUTATION is not a valid location",
@@ -790,7 +741,7 @@
             file_id: FileId {
                 id: 50,
             },
-            offset: 172,
+            offset: 181,
             length: 11,
         },
         labels: [
@@ -799,7 +750,7 @@
                     file_id: FileId {
                         id: 50,
                     },
-                    offset: 172,
+                    offset: 181,
                     length: 11,
                 },
                 text: "FRAGMENT_DEFINITION is not a valid location",
@@ -809,7 +760,7 @@
                     file_id: FileId {
                         id: 50,
                     },
-                    offset: 1387,
+                    offset: 1397,
                     length: 29,
                 },
                 text: "consider adding FRAGMENT_DEFINITION directive location here",
@@ -825,7 +776,7 @@
                 file_id: FileId {
                     id: 50,
                 },
-                offset: 1387,
+                offset: 1397,
                 length: 29,
             },
         },
@@ -839,7 +790,7 @@
             file_id: FileId {
                 id: 50,
             },
-            offset: 211,
+            offset: 221,
             length: 11,
         },
         labels: [
@@ -848,7 +799,7 @@
                     file_id: FileId {
                         id: 50,
                     },
-                    offset: 211,
+                    offset: 221,
                     length: 11,
                 },
                 text: "INLINE_FRAGMENT is not a valid location",
@@ -858,7 +809,7 @@
                     file_id: FileId {
                         id: 50,
                     },
-                    offset: 1356,
+                    offset: 1366,
                     length: 30,
                 },
                 text: "consider adding INLINE_FRAGMENT directive location here",
@@ -874,7 +825,7 @@
                 file_id: FileId {
                     id: 50,
                 },
-                offset: 1356,
+                offset: 1366,
                 length: 30,
             },
         },
diff --git a/crates/apollo-compiler/test_data/diagnostics/0053_argument_name_is_not_defined.graphql b/crates/apollo-compiler/test_data/diagnostics/0053_argument_name_is_not_defined.graphql
index 6758931..1de3d72 100644
--- a/crates/apollo-compiler/test_data/diagnostics/0053_argument_name_is_not_defined.graphql
+++ b/crates/apollo-compiler/test_data/diagnostics/0053_argument_name_is_not_defined.graphql
@@ -7,8 +7,8 @@ type Field {
   field(arg4: Int): Int
 }
 
-fragment X on Query {
-  field(arg1: 1, arg2: 2, arg3: 3)
+fragment X on Field {
+  field(arg4: 1, arg2: 2, arg3: 3)
 }
 
 query field {
@@ -19,4 +19,4 @@ query fieldWithFragment {
   item {
     ...X
   }
-}
\ No newline at end of file
+}
diff --git a/crates/apollo-compiler/test_data/diagnostics/0053_argument_name_is_not_defined.txt b/crates/apollo-compiler/test_data/diagnostics/0053_argument_name_is_not_defined.txt
index 2719a16..41902da 100644
--- a/crates/apollo-compiler/test_data/diagnostics/0053_argument_name_is_not_defined.txt
+++ b/crates/apollo-compiler/test_data/diagnostics/0053_argument_name_is_not_defined.txt
@@ -66,7 +66,7 @@
                     file_id: FileId {
                         id: 53,
                     },
-                    offset: 15,
+                    offset: 69,
                     length: 21,
                 },
                 text: "field declared here",
@@ -105,7 +105,7 @@
                     file_id: FileId {
                         id: 53,
                     },
-                    offset: 15,
+                    offset: 69,
                     length: 21,
                 },
                 text: "field declared here",
diff --git a/crates/apollo-compiler/test_data/diagnostics/0087_fragment_type_condition_on_composite_types.graphql b/crates/apollo-compiler/test_data/diagnostics/0087_fragment_type_condition_on_composite_types.graphql
index 9a178a0..ef0609d 100644
--- a/crates/apollo-compiler/test_data/diagnostics/0087_fragment_type_condition_on_composite_types.graphql
+++ b/crates/apollo-compiler/test_data/diagnostics/0087_fragment_type_condition_on_composite_types.graphql
@@ -2,7 +2,7 @@ query Query {
   products {
     inStock
     name
-    ...fragOnScalar
+    price { ...fragOnScalar }
     ...inlineFragOnScalar
     ... on Int {
       name
@@ -13,6 +13,7 @@ type Query {
   products: Product
 }
 type Product {
+  price: Int
   inStock: Boolean
   name: String
 }
@@ -26,4 +27,4 @@ fragment inlineFragOnScalar on Product {
     name
   }
   name
-}
\ No newline at end of file
+}
diff --git a/crates/apollo-compiler/test_data/diagnostics/0087_fragment_type_condition_on_composite_types.txt b/crates/apollo-compiler/test_data/diagnostics/0087_fragment_type_condition_on_composite_types.txt
index cd27d83..467b1e3 100644
--- a/crates/apollo-compiler/test_data/diagnostics/0087_fragment_type_condition_on_composite_types.txt
+++ b/crates/apollo-compiler/test_data/diagnostics/0087_fragment_type_condition_on_composite_types.txt
@@ -8,10 +8,57 @@
             file_id: FileId {
                 id: 84,
             },
-            offset: 98,
+            offset: 52,
+            length: 25,
+        },
+        labels: [
+            Label {
+                location: DiagnosticLocation {
+                    file_id: FileId {
+                        id: 84,
+                    },
+                    offset: 52,
+                    length: 25,
+                },
+                text: "field `price` of type `Int` is a scalar and cannot select any fields",
+            },
+            Label {
+                location: DiagnosticLocation {
+                    file_id: FileId {
+                        id: 0,
+                    },
+                    offset: 2916,
+                    length: 150,
+                },
+                text: "`Int` declared here",
+            },
+        ],
+        help: None,
+        data: DisallowedSubselection,
+    },
+    ApolloDiagnostic {
+        cache: {
+            0: "built_in_types.graphql",
+            84: "0087_fragment_type_condition_on_composite_types.graphql",
+        },
+        location: DiagnosticLocation {
+            file_id: FileId {
+                id: 84,
+            },
+            offset: 108,
             length: 29,
         },
         labels: [
+            Label {
+                location: DiagnosticLocation {
+                    file_id: FileId {
+                        id: 84,
+                    },
+                    offset: 108,
+                    length: 29,
+                },
+                text: "fragment declares unsupported type condition `{type_cond}`",
+            },
             Label {
                 location: DiagnosticLocation {
                     file_id: FileId {
@@ -39,10 +86,20 @@
             file_id: FileId {
                 id: 84,
             },
-            offset: 221,
+            offset: 244,
             length: 39,
         },
         labels: [
+            Label {
+                location: DiagnosticLocation {
+                    file_id: FileId {
+                        id: 84,
+                    },
+                    offset: 244,
+                    length: 39,
+                },
+                text: "fragment declares unsupported type condition `{type_cond}`",
+            },
             Label {
                 location: DiagnosticLocation {
                     file_id: FileId {
@@ -70,7 +127,7 @@
             file_id: FileId {
                 id: 84,
             },
-            offset: 254,
+            offset: 277,
             length: 4,
         },
         labels: [
@@ -79,7 +136,7 @@
                     file_id: FileId {
                         id: 84,
                     },
-                    offset: 254,
+                    offset: 277,
                     length: 4,
                 },
                 text: "`name` field is not defined",
@@ -111,10 +168,20 @@
             file_id: FileId {
                 id: 84,
             },
-            offset: 305,
+            offset: 328,
             length: 25,
         },
         labels: [
+            Label {
+                location: DiagnosticLocation {
+                    file_id: FileId {
+                        id: 84,
+                    },
+                    offset: 328,
+                    length: 25,
+                },
+                text: "fragment declares unsupported type condition `{type_cond}`",
+            },
             Label {
                 location: DiagnosticLocation {
                     file_id: FileId {
diff --git a/crates/apollo-compiler/test_data/diagnostics/0089_fragment_type_condition_on_non_existent_types.txt b/crates/apollo-compiler/test_data/diagnostics/0089_fragment_type_condition_on_non_existent_types.txt
index c6eed4e..0613afb 100644
--- a/crates/apollo-compiler/test_data/diagnostics/0089_fragment_type_condition_on_non_existent_types.txt
+++ b/crates/apollo-compiler/test_data/diagnostics/0089_fragment_type_condition_on_non_existent_types.txt
@@ -11,7 +11,18 @@
             offset: 97,
             length: 44,
         },
-        labels: [],
+        labels: [
+            Label {
+                location: DiagnosticLocation {
+                    file_id: FileId {
+                        id: 86,
+                    },
+                    offset: 97,
+                    length: 44,
+                },
+                text: "fragment declares unsupported type condition `{type_cond}`",
+            },
+        ],
         help: Some(
             "fragments cannot be defined on enums, scalars and input object",
         ),
@@ -64,7 +75,18 @@
             offset: 275,
             length: 30,
         },
-        labels: [],
+        labels: [
+            Label {
+                location: DiagnosticLocation {
+                    file_id: FileId {
+                        id: 86,
+                    },
+                    offset: 275,
+                    length: 30,
+                },
+                text: "fragment declares unsupported type condition `{type_cond}`",
+            },
+        ],
         help: Some(
             "fragments cannot be defined on enums, scalars and input object",
         ),
@@ -117,7 +139,18 @@
             offset: 309,
             length: 55,
         },
-        labels: [],
+        labels: [
+            Label {
+                location: DiagnosticLocation {
+                    file_id: FileId {
+                        id: 86,
+                    },
+                    offset: 309,
+                    length: 55,
+                },
+                text: "fragment declares unsupported type condition `{type_cond}`",
+            },
+        ],
         help: Some(
             "fragments cannot be defined on enums, scalars and input object",
         ),
diff --git a/crates/apollo-compiler/test_data/diagnostics/0090_fragment_spread_impossible.graphql b/crates/apollo-compiler/test_data/diagnostics/0090_fragment_spread_impossible.graphql
new file mode 100644
index 0000000..e555186
--- /dev/null
+++ b/crates/apollo-compiler/test_data/diagnostics/0090_fragment_spread_impossible.graphql
@@ -0,0 +1,57 @@
+interface Sentient {
+  name: String!
+}
+
+interface Pet {
+  name: String!
+}
+
+type Dog implements Pet {
+  name: String!
+  barkVolume: Int
+}
+
+type Cat implements Pet {
+  name: String!
+  meowVolume: Int
+}
+
+union CatOrDog = Cat | Dog
+
+type Human implements Sentient {
+  name: String!
+  pets: [Pet!]
+}
+
+type Alien implements Sentient {
+  name: String!
+  homePlanet: String!
+}
+
+union HumanOrAlien = Human | Alien
+
+fragment catInDogFragmentInvalid on Dog {
+  ... on Cat {
+    meowVolume
+  }
+}
+
+fragment sentientFragment on Sentient {
+  ... on Dog {
+    barkVolume
+  }
+}
+
+fragment humanOrAlienFragment on HumanOrAlien {
+  ... on Cat {
+    meowVolume
+  }
+}
+
+fragment nonIntersectingInterfaces on Pet {
+  ...sentientFragment2
+}
+
+fragment sentientFragment2 on Sentient {
+  name
+}
diff --git a/crates/apollo-compiler/test_data/diagnostics/0090_fragment_spread_impossible.txt b/crates/apollo-compiler/test_data/diagnostics/0090_fragment_spread_impossible.txt
new file mode 100644
index 0000000..0ec7c82
--- /dev/null
+++ b/crates/apollo-compiler/test_data/diagnostics/0090_fragment_spread_impossible.txt
@@ -0,0 +1,298 @@
+[
+    ApolloDiagnostic {
+        cache: {
+            0: "built_in_types.graphql",
+            87: "0090_fragment_spread_impossible.graphql",
+        },
+        location: DiagnosticLocation {
+            file_id: FileId {
+                id: 87,
+            },
+            offset: 450,
+            length: 31,
+        },
+        labels: [
+            Label {
+                location: DiagnosticLocation {
+                    file_id: FileId {
+                        id: 87,
+                    },
+                    offset: 450,
+                    length: 31,
+                },
+                text: "fragment applied with type condition `Cat` here",
+            },
+            Label {
+                location: DiagnosticLocation {
+                    file_id: FileId {
+                        id: 87,
+                    },
+                    offset: 75,
+                    length: 61,
+                },
+                text: "type condition `Cat` is not assignable to this type",
+            },
+        ],
+        help: None,
+        data: InvalidFragmentSpread {
+            name: None,
+            type_name: "Dog",
+        },
+    },
+    ApolloDiagnostic {
+        cache: {
+            0: "built_in_types.graphql",
+            87: "0090_fragment_spread_impossible.graphql",
+        },
+        location: DiagnosticLocation {
+            file_id: FileId {
+                id: 87,
+            },
+            offset: 406,
+            length: 77,
+        },
+        labels: [
+            Label {
+                location: DiagnosticLocation {
+                    file_id: FileId {
+                        id: 87,
+                    },
+                    offset: 406,
+                    length: 77,
+                },
+                text: "`catInDogFragmentInvalid` is defined here",
+            },
+        ],
+        help: Some(
+            "fragment `catInDogFragmentInvalid` must be used in an operation",
+        ),
+        data: UnusedFragment {
+            name: "catInDogFragmentInvalid",
+        },
+    },
+    ApolloDiagnostic {
+        cache: {
+            0: "built_in_types.graphql",
+            87: "0090_fragment_spread_impossible.graphql",
+        },
+        location: DiagnosticLocation {
+            file_id: FileId {
+                id: 87,
+            },
+            offset: 527,
+            length: 31,
+        },
+        labels: [
+            Label {
+                location: DiagnosticLocation {
+                    file_id: FileId {
+                        id: 87,
+                    },
+                    offset: 527,
+                    length: 31,
+                },
+                text: "fragment applied with type condition `Dog` here",
+            },
+            Label {
+                location: DiagnosticLocation {
+                    file_id: FileId {
+                        id: 87,
+                    },
+                    offset: 0,
+                    length: 38,
+                },
+                text: "type condition `Dog` is not assignable to this type",
+            },
+        ],
+        help: None,
+        data: InvalidFragmentSpread {
+            name: None,
+            type_name: "Sentient",
+        },
+    },
+    ApolloDiagnostic {
+        cache: {
+            0: "built_in_types.graphql",
+            87: "0090_fragment_spread_impossible.graphql",
+        },
+        location: DiagnosticLocation {
+            file_id: FileId {
+                id: 87,
+            },
+            offset: 485,
+            length: 75,
+        },
+        labels: [
+            Label {
+                location: DiagnosticLocation {
+                    file_id: FileId {
+                        id: 87,
+                    },
+                    offset: 485,
+                    length: 75,
+                },
+                text: "`sentientFragment` is defined here",
+            },
+        ],
+        help: Some(
+            "fragment `sentientFragment` must be used in an operation",
+        ),
+        data: UnusedFragment {
+            name: "sentientFragment",
+        },
+    },
+    ApolloDiagnostic {
+        cache: {
+            0: "built_in_types.graphql",
+            87: "0090_fragment_spread_impossible.graphql",
+        },
+        location: DiagnosticLocation {
+            file_id: FileId {
+                id: 87,
+            },
+            offset: 612,
+            length: 31,
+        },
+        labels: [
+            Label {
+                location: DiagnosticLocation {
+                    file_id: FileId {
+                        id: 87,
+                    },
+                    offset: 612,
+                    length: 31,
+                },
+                text: "fragment applied with type condition `Cat` here",
+            },
+            Label {
+                location: DiagnosticLocation {
+                    file_id: FileId {
+                        id: 87,
+                    },
+                    offset: 370,
+                    length: 34,
+                },
+                text: "type condition `Cat` is not assignable to this type",
+            },
+        ],
+        help: None,
+        data: InvalidFragmentSpread {
+            name: None,
+            type_name: "HumanOrAlien",
+        },
+    },
+    ApolloDiagnostic {
+        cache: {
+            0: "built_in_types.graphql",
+            87: "0090_fragment_spread_impossible.graphql",
+        },
+        location: DiagnosticLocation {
+            file_id: FileId {
+                id: 87,
+            },
+            offset: 562,
+            length: 83,
+        },
+        labels: [
+            Label {
+                location: DiagnosticLocation {
+                    file_id: FileId {
+                        id: 87,
+                    },
+                    offset: 562,
+                    length: 83,
+                },
+                text: "`humanOrAlienFragment` is defined here",
+            },
+        ],
+        help: Some(
+            "fragment `humanOrAlienFragment` must be used in an operation",
+        ),
+        data: UnusedFragment {
+            name: "humanOrAlienFragment",
+        },
+    },
+    ApolloDiagnostic {
+        cache: {
+            0: "built_in_types.graphql",
+            87: "0090_fragment_spread_impossible.graphql",
+        },
+        location: DiagnosticLocation {
+            file_id: FileId {
+                id: 87,
+            },
+            offset: 693,
+            length: 20,
+        },
+        labels: [
+            Label {
+                location: DiagnosticLocation {
+                    file_id: FileId {
+                        id: 87,
+                    },
+                    offset: 693,
+                    length: 20,
+                },
+                text: "fragment `sentientFragment2` cannot be applied",
+            },
+            Label {
+                location: DiagnosticLocation {
+                    file_id: FileId {
+                        id: 87,
+                    },
+                    offset: 717,
+                    length: 49,
+                },
+                text: "fragment declared with type condition `Sentient` here",
+            },
+            Label {
+                location: DiagnosticLocation {
+                    file_id: FileId {
+                        id: 87,
+                    },
+                    offset: 40,
+                    length: 33,
+                },
+                text: "type condition `Sentient` is not assignable to this type",
+            },
+        ],
+        help: None,
+        data: InvalidFragmentSpread {
+            name: Some(
+                "sentientFragment2",
+            ),
+            type_name: "Pet",
+        },
+    },
+    ApolloDiagnostic {
+        cache: {
+            0: "built_in_types.graphql",
+            87: "0090_fragment_spread_impossible.graphql",
+        },
+        location: DiagnosticLocation {
+            file_id: FileId {
+                id: 87,
+            },
+            offset: 647,
+            length: 68,
+        },
+        labels: [
+            Label {
+                location: DiagnosticLocation {
+                    file_id: FileId {
+                        id: 87,
+                    },
+                    offset: 647,
+                    length: 68,
+                },
+                text: "`nonIntersectingInterfaces` is defined here",
+            },
+        ],
+        help: Some(
+            "fragment `nonIntersectingInterfaces` must be used in an operation",
+        ),
+        data: UnusedFragment {
+            name: "nonIntersectingInterfaces",
+        },
+    },
+]
diff --git a/crates/apollo-compiler/test_data/diagnostics/0091_recursive_interface_definition.graphql b/crates/apollo-compiler/test_data/diagnostics/0091_recursive_interface_definition.graphql
new file mode 100644
index 0000000..602f7bf
--- /dev/null
+++ b/crates/apollo-compiler/test_data/diagnostics/0091_recursive_interface_definition.graphql
@@ -0,0 +1,16 @@
+interface A implements B {
+  name: String
+}
+interface B implements A {
+  name: String
+}
+fragment recursive on A {
+  name
+}
+
+type Query {
+  get: A
+}
+query {
+  get { ...recursive }
+}
diff --git a/crates/apollo-compiler/test_data/diagnostics/0091_recursive_interface_definition.txt b/crates/apollo-compiler/test_data/diagnostics/0091_recursive_interface_definition.txt
new file mode 100644
index 0000000..8ea6d3e
--- /dev/null
+++ b/crates/apollo-compiler/test_data/diagnostics/0091_recursive_interface_definition.txt
@@ -0,0 +1,60 @@
+[
+    ApolloDiagnostic {
+        cache: {
+            0: "built_in_types.graphql",
+            88: "0091_recursive_interface_definition.graphql",
+        },
+        location: DiagnosticLocation {
+            file_id: FileId {
+                id: 88,
+            },
+            offset: 23,
+            length: 1,
+        },
+        labels: [
+            Label {
+                location: DiagnosticLocation {
+                    file_id: FileId {
+                        id: 88,
+                    },
+                    offset: 23,
+                    length: 1,
+                },
+                text: "A must also be implemented here",
+            },
+        ],
+        help: None,
+        data: TransitiveImplementedInterfaces {
+            missing_interface: "A",
+        },
+    },
+    ApolloDiagnostic {
+        cache: {
+            0: "built_in_types.graphql",
+            88: "0091_recursive_interface_definition.graphql",
+        },
+        location: DiagnosticLocation {
+            file_id: FileId {
+                id: 88,
+            },
+            offset: 67,
+            length: 1,
+        },
+        labels: [
+            Label {
+                location: DiagnosticLocation {
+                    file_id: FileId {
+                        id: 88,
+                    },
+                    offset: 67,
+                    length: 1,
+                },
+                text: "B must also be implemented here",
+            },
+        ],
+        help: None,
+        data: TransitiveImplementedInterfaces {
+            missing_interface: "B",
+        },
+    },
+]
diff --git a/crates/apollo-compiler/test_data/diagnostics/0092_recursive_fragment_spread.txt b/crates/apollo-compiler/test_data/diagnostics/0092_recursive_fragment_spread.txt
index 0fd22ac..c7ee38e 100644
--- a/crates/apollo-compiler/test_data/diagnostics/0092_recursive_fragment_spread.txt
+++ b/crates/apollo-compiler/test_data/diagnostics/0092_recursive_fragment_spread.txt
@@ -2,11 +2,11 @@
     ApolloDiagnostic {
         cache: {
             0: "built_in_types.graphql",
-            87: "0092_recursive_fragment_spread.graphql",
+            89: "0092_recursive_fragment_spread.graphql",
         },
         location: DiagnosticLocation {
             file_id: FileId {
-                id: 87,
+                id: 89,
             },
             offset: 104,
             length: 42,
@@ -15,7 +15,7 @@
             Label {
                 location: DiagnosticLocation {
                     file_id: FileId {
-                        id: 87,
+                        id: 89,
                     },
                     offset: 104,
                     length: 14,
@@ -25,7 +25,7 @@
             Label {
                 location: DiagnosticLocation {
                     file_id: FileId {
-                        id: 87,
+                        id: 89,
                     },
                     offset: 222,
                     length: 8,
@@ -41,11 +41,11 @@
     ApolloDiagnostic {
         cache: {
             0: "built_in_types.graphql",
-            87: "0092_recursive_fragment_spread.graphql",
+            89: "0092_recursive_fragment_spread.graphql",
         },
         location: DiagnosticLocation {
             file_id: FileId {
-                id: 87,
+                id: 89,
             },
             offset: 147,
             length: 42,
@@ -54,7 +54,7 @@
             Label {
                 location: DiagnosticLocation {
                     file_id: FileId {
-                        id: 87,
+                        id: 89,
                     },
                     offset: 147,
                     length: 14,
@@ -64,7 +64,7 @@
             Label {
                 location: DiagnosticLocation {
                     file_id: FileId {
-                        id: 87,
+                        id: 89,
                     },
                     offset: 136,
                     length: 8,
@@ -80,11 +80,11 @@
     ApolloDiagnostic {
         cache: {
             0: "built_in_types.graphql",
-            87: "0092_recursive_fragment_spread.graphql",
+            89: "0092_recursive_fragment_spread.graphql",
         },
         location: DiagnosticLocation {
             file_id: FileId {
-                id: 87,
+                id: 89,
             },
             offset: 190,
             length: 42,
@@ -93,7 +93,7 @@
             Label {
                 location: DiagnosticLocation {
                     file_id: FileId {
-                        id: 87,
+                        id: 89,
                     },
                     offset: 190,
                     length: 14,
@@ -103,7 +103,7 @@
             Label {
                 location: DiagnosticLocation {
                     file_id: FileId {
-                        id: 87,
+                        id: 89,
                     },
                     offset: 179,
                     length: 8,
diff --git a/crates/apollo-compiler/test_data/diagnostics/0093_fragment_validation_with_recursive_type_system.graphql b/crates/apollo-compiler/test_data/diagnostics/0093_fragment_validation_with_recursive_type_system.graphql
new file mode 100644
index 0000000..740ec4e
--- /dev/null
+++ b/crates/apollo-compiler/test_data/diagnostics/0093_fragment_validation_with_recursive_type_system.graphql
@@ -0,0 +1,27 @@
+# ensure we don't get a stack overflow when the type system is recursive
+interface A implements B {
+  a: A
+  b: B
+}
+interface B implements A {
+  a: A
+  b: B
+}
+
+type C implements A & B {
+  a: A
+  b: B
+  value: Int!
+}
+
+type Query {
+  c: C
+}
+
+fragment b on C {
+  value
+}
+
+query {
+  c { ... b }
+}
diff --git a/crates/apollo-compiler/test_data/diagnostics/0093_fragment_validation_with_recursive_type_system.txt b/crates/apollo-compiler/test_data/diagnostics/0093_fragment_validation_with_recursive_type_system.txt
new file mode 100644
index 0000000..6b831d2
--- /dev/null
+++ b/crates/apollo-compiler/test_data/diagnostics/0093_fragment_validation_with_recursive_type_system.txt
@@ -0,0 +1,60 @@
+[
+    ApolloDiagnostic {
+        cache: {
+            0: "built_in_types.graphql",
+            90: "0093_fragment_validation_with_recursive_type_system.graphql",
+        },
+        location: DiagnosticLocation {
+            file_id: FileId {
+                id: 90,
+            },
+            offset: 96,
+            length: 1,
+        },
+        labels: [
+            Label {
+                location: DiagnosticLocation {
+                    file_id: FileId {
+                        id: 90,
+                    },
+                    offset: 96,
+                    length: 1,
+                },
+                text: "A must also be implemented here",
+            },
+        ],
+        help: None,
+        data: TransitiveImplementedInterfaces {
+            missing_interface: "A",
+        },
+    },
+    ApolloDiagnostic {
+        cache: {
+            0: "built_in_types.graphql",
+            90: "0093_fragment_validation_with_recursive_type_system.graphql",
+        },
+        location: DiagnosticLocation {
+            file_id: FileId {
+                id: 90,
+            },
+            offset: 139,
+            length: 1,
+        },
+        labels: [
+            Label {
+                location: DiagnosticLocation {
+                    file_id: FileId {
+                        id: 90,
+                    },
+                    offset: 139,
+                    length: 1,
+                },
+                text: "B must also be implemented here",
+            },
+        ],
+        help: None,
+        data: TransitiveImplementedInterfaces {
+            missing_interface: "B",
+        },
+    },
+]
diff --git a/crates/apollo-compiler/test_data/ok/0013_operation_with_used_variable_in_fragment.graphql b/crates/apollo-compiler/test_data/ok/0013_operation_with_used_variable_in_fragment.graphql
index 43ffba3..4d2ac6e 100644
--- a/crates/apollo-compiler/test_data/ok/0013_operation_with_used_variable_in_fragment.graphql
+++ b/crates/apollo-compiler/test_data/ok/0013_operation_with_used_variable_in_fragment.graphql
@@ -2,13 +2,12 @@ query ExampleQuery($variable: Int) {
   topProducts {
     name
   }
-  ...multipleSubscriptions
+  ... subFrag
 }
 
-fragment multipleSubscriptions on Review {
-  message {
-    body
-    sender(attribute: $variable)
+fragment subFrag on Query {
+  topProducts {
+    price(setPrice: $variable)
   }
 }
 
@@ -29,4 +28,4 @@ type Message {
 type Product {
   name: String
   price(setPrice: Int): Int
-}
\ No newline at end of file
+}
diff --git a/crates/apollo-compiler/test_data/ok/0013_operation_with_used_variable_in_fragment.txt b/crates/apollo-compiler/test_data/ok/0013_operation_with_used_variable_in_fragment.txt
index 6ae5537..d0be8d8 100644
--- a/crates/apollo-compiler/test_data/ok/0013_operation_with_used_variable_in_fragment.txt
+++ b/crates/apollo-compiler/test_data/ok/0013_operation_with_used_variable_in_fragment.txt
@@ -1,5 +1,5 @@
-- DOCUMENT@0..434
-    - OPERATION_DEFINITION@0..94
+- DOCUMENT@0..400
+    - OPERATION_DEFINITION@0..81
         - OPERATION_TYPE@0..5
             - query_KW@0..5 "query"
         - WHITESPACE@5..6 " "
@@ -19,7 +19,7 @@
                         - IDENT@30..33 "Int"
             - R_PAREN@33..34 ")"
         - WHITESPACE@34..35 " "
-        - SELECTION_SET@35..94
+        - SELECTION_SET@35..81
             - L_CURLY@35..36 "{"
             - WHITESPACE@36..39 "\n  "
             - FIELD@39..65
@@ -35,200 +35,198 @@
                     - WHITESPACE@61..64 "\n  "
                     - R_CURLY@64..65 "}"
             - WHITESPACE@65..68 "\n  "
-            - FRAGMENT_SPREAD@68..92
+            - FRAGMENT_SPREAD@68..79
                 - SPREAD@68..71 "..."
-                - FRAGMENT_NAME@71..92
-                    - NAME@71..92
-                        - IDENT@71..92 "multipleSubscriptions"
-            - WHITESPACE@92..93 "\n"
-            - R_CURLY@93..94 "}"
-    - WHITESPACE@94..96 "\n\n"
-    - FRAGMENT_DEFINITION@96..198
-        - fragment_KW@96..104 "fragment"
-        - WHITESPACE@104..105 " "
-        - FRAGMENT_NAME@105..126
-            - NAME@105..126
-                - IDENT@105..126 "multipleSubscriptions"
-        - WHITESPACE@126..127 " "
-        - TYPE_CONDITION@127..136
-            - on_KW@127..129 "on"
-            - WHITESPACE@129..130 " "
-            - NAMED_TYPE@130..136
-                - NAME@130..136
-                    - IDENT@130..136 "Review"
-        - WHITESPACE@136..137 " "
-        - SELECTION_SET@137..198
-            - L_CURLY@137..138 "{"
-            - WHITESPACE@138..141 "\n  "
-            - FIELD@141..196
-                - NAME@141..148
-                    - IDENT@141..148 "message"
-                - WHITESPACE@148..149 " "
-                - SELECTION_SET@149..196
-                    - L_CURLY@149..150 "{"
-                    - WHITESPACE@150..155 "\n    "
-                    - FIELD@155..159
-                        - NAME@155..159
-                            - IDENT@155..159 "body"
-                    - WHITESPACE@159..164 "\n    "
-                    - FIELD@164..192
-                        - NAME@164..170
-                            - IDENT@164..170 "sender"
-                        - ARGUMENTS@170..192
-                            - L_PAREN@170..171 "("
-                            - ARGUMENT@171..191
-                                - NAME@171..180
-                                    - IDENT@171..180 "attribute"
-                                - COLON@180..181 ":"
-                                - WHITESPACE@181..182 " "
-                                - VARIABLE@182..191
-                                    - DOLLAR@182..183 "$"
-                                    - NAME@183..191
-                                        - IDENT@183..191 "variable"
-                            - R_PAREN@191..192 ")"
-                    - WHITESPACE@192..195 "\n  "
-                    - R_CURLY@195..196 "}"
-            - WHITESPACE@196..197 "\n"
-            - R_CURLY@197..198 "}"
-    - WHITESPACE@198..200 "\n\n"
-    - OBJECT_TYPE_DEFINITION@200..268
-        - type_KW@200..204 "type"
-        - WHITESPACE@204..205 " "
-        - NAME@205..210
-            - IDENT@205..210 "Query"
-        - WHITESPACE@210..211 " "
-        - FIELDS_DEFINITION@211..268
-            - L_CURLY@211..212 "{"
-            - WHITESPACE@212..215 "\n  "
-            - FIELD_DEFINITION@215..247
-                - NAME@215..226
-                    - IDENT@215..226 "topProducts"
-                - ARGUMENTS_DEFINITION@226..238
-                    - L_PAREN@226..227 "("
-                    - INPUT_VALUE_DEFINITION@227..237
-                        - NAME@227..232
-                            - IDENT@227..232 "first"
-                        - COLON@232..233 ":"
-                        - WHITESPACE@233..234 " "
-                        - NAMED_TYPE@234..237
-                            - NAME@234..237
-                                - IDENT@234..237 "Int"
-                    - R_PAREN@237..238 ")"
-                - COLON@238..239 ":"
-                - WHITESPACE@239..240 " "
-                - NAMED_TYPE@240..247
-                    - NAME@240..247
-                        - IDENT@240..247 "Product"
-            - COMMA@247..248 ","
+                - WHITESPACE@71..72 " "
+                - FRAGMENT_NAME@72..79
+                    - NAME@72..79
+                        - IDENT@72..79 "subFrag"
+            - WHITESPACE@79..80 "\n"
+            - R_CURLY@80..81 "}"
+    - WHITESPACE@81..83 "\n\n"
+    - FRAGMENT_DEFINITION@83..163
+        - fragment_KW@83..91 "fragment"
+        - WHITESPACE@91..92 " "
+        - FRAGMENT_NAME@92..99
+            - NAME@92..99
+                - IDENT@92..99 "subFrag"
+        - WHITESPACE@99..100 " "
+        - TYPE_CONDITION@100..108
+            - on_KW@100..102 "on"
+            - WHITESPACE@102..103 " "
+            - NAMED_TYPE@103..108
+                - NAME@103..108
+                    - IDENT@103..108 "Query"
+        - WHITESPACE@108..109 " "
+        - SELECTION_SET@109..163
+            - L_CURLY@109..110 "{"
+            - WHITESPACE@110..113 "\n  "
+            - FIELD@113..161
+                - NAME@113..124
+                    - IDENT@113..124 "topProducts"
+                - WHITESPACE@124..125 " "
+                - SELECTION_SET@125..161
+                    - L_CURLY@125..126 "{"
+                    - WHITESPACE@126..131 "\n    "
+                    - FIELD@131..157
+                        - NAME@131..136
+                            - IDENT@131..136 "price"
+                        - ARGUMENTS@136..157
+                            - L_PAREN@136..137 "("
+                            - ARGUMENT@137..156
+                                - NAME@137..145
+                                    - IDENT@137..145 "setPrice"
+                                - COLON@145..146 ":"
+                                - WHITESPACE@146..147 " "
+                                - VARIABLE@147..156
+                                    - DOLLAR@147..148 "$"
+                                    - NAME@148..156
+                                        - IDENT@148..156 "variable"
+                            - R_PAREN@156..157 ")"
+                    - WHITESPACE@157..160 "\n  "
+                    - R_CURLY@160..161 "}"
+            - WHITESPACE@161..162 "\n"
+            - R_CURLY@162..163 "}"
+    - WHITESPACE@163..165 "\n\n"
+    - OBJECT_TYPE_DEFINITION@165..233
+        - type_KW@165..169 "type"
+        - WHITESPACE@169..170 " "
+        - NAME@170..175
+            - IDENT@170..175 "Query"
+        - WHITESPACE@175..176 " "
+        - FIELDS_DEFINITION@176..233
+            - L_CURLY@176..177 "{"
+            - WHITESPACE@177..180 "\n  "
+            - FIELD_DEFINITION@180..212
+                - NAME@180..191
+                    - IDENT@180..191 "topProducts"
+                - ARGUMENTS_DEFINITION@191..203
+                    - L_PAREN@191..192 "("
+                    - INPUT_VALUE_DEFINITION@192..202
+                        - NAME@192..197
+                            - IDENT@192..197 "first"
+                        - COLON@197..198 ":"
+                        - WHITESPACE@198..199 " "
+                        - NAMED_TYPE@199..202
+                            - NAME@199..202
+                                - IDENT@199..202 "Int"
+                    - R_PAREN@202..203 ")"
+                - COLON@203..204 ":"
+                - WHITESPACE@204..205 " "
+                - NAMED_TYPE@205..212
+                    - NAME@205..212
+                        - IDENT@205..212 "Product"
+            - COMMA@212..213 ","
+            - WHITESPACE@213..216 "\n  "
+            - FIELD_DEFINITION@216..231
+                - NAME@216..223
+                    - IDENT@216..223 "reviews"
+                - COLON@223..224 ":"
+                - WHITESPACE@224..225 " "
+                - NAMED_TYPE@225..231
+                    - NAME@225..231
+                        - IDENT@225..231 "Review"
+            - WHITESPACE@231..232 "\n"
+            - R_CURLY@232..233 "}"
+    - WHITESPACE@233..235 "\n\n"
+    - OBJECT_TYPE_DEFINITION@235..269
+        - type_KW@235..239 "type"
+        - WHITESPACE@239..240 " "
+        - NAME@240..246
+            - IDENT@240..246 "Review"
+        - WHITESPACE@246..247 " "
+        - FIELDS_DEFINITION@247..269
+            - L_CURLY@247..248 "{"
             - WHITESPACE@248..251 "\n  "
-            - FIELD_DEFINITION@251..266
+            - FIELD_DEFINITION@251..267
                 - NAME@251..258
-                    - IDENT@251..258 "reviews"
+                    - IDENT@251..258 "message"
                 - COLON@258..259 ":"
                 - WHITESPACE@259..260 " "
-                - NAMED_TYPE@260..266
-                    - NAME@260..266
-                        - IDENT@260..266 "Review"
-            - WHITESPACE@266..267 "\n"
-            - R_CURLY@267..268 "}"
-    - WHITESPACE@268..270 "\n\n"
-    - OBJECT_TYPE_DEFINITION@270..304
-        - type_KW@270..274 "type"
-        - WHITESPACE@274..275 " "
-        - NAME@275..281
-            - IDENT@275..281 "Review"
-        - WHITESPACE@281..282 " "
-        - FIELDS_DEFINITION@282..304
-            - L_CURLY@282..283 "{"
-            - WHITESPACE@283..286 "\n  "
-            - FIELD_DEFINITION@286..302
-                - NAME@286..293
-                    - IDENT@286..293 "message"
-                - COLON@293..294 ":"
-                - WHITESPACE@294..295 " "
-                - NAMED_TYPE@295..302
-                    - NAME@295..302
-                        - IDENT@295..302 "Message"
-            - WHITESPACE@302..303 "\n"
-            - R_CURLY@303..304 "}"
-    - WHITESPACE@304..306 "\n\n"
-    - OBJECT_TYPE_DEFINITION@306..373
-        - type_KW@306..310 "type"
-        - WHITESPACE@310..311 " "
-        - NAME@311..318
-            - IDENT@311..318 "Message"
-        - WHITESPACE@318..319 " "
-        - FIELDS_DEFINITION@319..373
-            - L_CURLY@319..320 "{"
-            - WHITESPACE@320..323 "\n  "
-            - FIELD_DEFINITION@323..335
-                - NAME@323..327
-                    - IDENT@323..327 "body"
-                - COLON@327..328 ":"
-                - WHITESPACE@328..329 " "
-                - NAMED_TYPE@329..335
-                    - NAME@329..335
-                        - IDENT@329..335 "String"
-            - WHITESPACE@335..338 "\n  "
-            - FIELD_DEFINITION@338..371
-                - NAME@338..344
-                    - IDENT@338..344 "sender"
-                - ARGUMENTS_DEFINITION@344..363
-                    - L_PAREN@344..345 "("
-                    - INPUT_VALUE_DEFINITION@345..362
-                        - NAME@345..354
-                            - IDENT@345..354 "attribute"
-                        - COLON@354..355 ":"
-                        - WHITESPACE@355..356 " "
-                        - NAMED_TYPE@356..362
-                            - NAME@356..362
-                                - IDENT@356..362 "String"
-                    - R_PAREN@362..363 ")"
-                - COLON@363..364 ":"
-                - WHITESPACE@364..365 " "
-                - NAMED_TYPE@365..371
-                    - NAME@365..371
-                        - IDENT@365..371 "String"
-            - WHITESPACE@371..372 "\n"
-            - R_CURLY@372..373 "}"
-    - WHITESPACE@373..375 "\n\n"
-    - OBJECT_TYPE_DEFINITION@375..434
-        - type_KW@375..379 "type"
-        - WHITESPACE@379..380 " "
-        - NAME@380..387
-            - IDENT@380..387 "Product"
-        - WHITESPACE@387..388 " "
-        - FIELDS_DEFINITION@388..434
-            - L_CURLY@388..389 "{"
-            - WHITESPACE@389..392 "\n  "
-            - FIELD_DEFINITION@392..404
-                - NAME@392..396
-                    - IDENT@392..396 "name"
-                - COLON@396..397 ":"
-                - WHITESPACE@397..398 " "
-                - NAMED_TYPE@398..404
-                    - NAME@398..404
-                        - IDENT@398..404 "String"
-            - WHITESPACE@404..407 "\n  "
-            - FIELD_DEFINITION@407..432
-                - NAME@407..412
-                    - IDENT@407..412 "price"
-                - ARGUMENTS_DEFINITION@412..427
-                    - L_PAREN@412..413 "("
-                    - INPUT_VALUE_DEFINITION@413..426
-                        - NAME@413..421
-                            - IDENT@413..421 "setPrice"
-                        - COLON@421..422 ":"
-                        - WHITESPACE@422..423 " "
-                        - NAMED_TYPE@423..426
-                            - NAME@423..426
-                                - IDENT@423..426 "Int"
-                    - R_PAREN@426..427 ")"
-                - COLON@427..428 ":"
-                - WHITESPACE@428..429 " "
-                - NAMED_TYPE@429..432
-                    - NAME@429..432
-                        - IDENT@429..432 "Int"
-            - WHITESPACE@432..433 "\n"
-            - R_CURLY@433..434 "}"
+                - NAMED_TYPE@260..267
+                    - NAME@260..267
+                        - IDENT@260..267 "Message"
+            - WHITESPACE@267..268 "\n"
+            - R_CURLY@268..269 "}"
+    - WHITESPACE@269..271 "\n\n"
+    - OBJECT_TYPE_DEFINITION@271..338
+        - type_KW@271..275 "type"
+        - WHITESPACE@275..276 " "
+        - NAME@276..283
+            - IDENT@276..283 "Message"
+        - WHITESPACE@283..284 " "
+        - FIELDS_DEFINITION@284..338
+            - L_CURLY@284..285 "{"
+            - WHITESPACE@285..288 "\n  "
+            - FIELD_DEFINITION@288..300
+                - NAME@288..292
+                    - IDENT@288..292 "body"
+                - COLON@292..293 ":"
+                - WHITESPACE@293..294 " "
+                - NAMED_TYPE@294..300
+                    - NAME@294..300
+                        - IDENT@294..300 "String"
+            - WHITESPACE@300..303 "\n  "
+            - FIELD_DEFINITION@303..336
+                - NAME@303..309
+                    - IDENT@303..309 "sender"
+                - ARGUMENTS_DEFINITION@309..328
+                    - L_PAREN@309..310 "("
+                    - INPUT_VALUE_DEFINITION@310..327
+                        - NAME@310..319
+                            - IDENT@310..319 "attribute"
+                        - COLON@319..320 ":"
+                        - WHITESPACE@320..321 " "
+                        - NAMED_TYPE@321..327
+                            - NAME@321..327
+                                - IDENT@321..327 "String"
+                    - R_PAREN@327..328 ")"
+                - COLON@328..329 ":"
+                - WHITESPACE@329..330 " "
+                - NAMED_TYPE@330..336
+                    - NAME@330..336
+                        - IDENT@330..336 "String"
+            - WHITESPACE@336..337 "\n"
+            - R_CURLY@337..338 "}"
+    - WHITESPACE@338..340 "\n\n"
+    - OBJECT_TYPE_DEFINITION@340..399
+        - type_KW@340..344 "type"
+        - WHITESPACE@344..345 " "
+        - NAME@345..352
+            - IDENT@345..352 "Product"
+        - WHITESPACE@352..353 " "
+        - FIELDS_DEFINITION@353..399
+            - L_CURLY@353..354 "{"
+            - WHITESPACE@354..357 "\n  "
+            - FIELD_DEFINITION@357..369
+                - NAME@357..361
+                    - IDENT@357..361 "name"
+                - COLON@361..362 ":"
+                - WHITESPACE@362..363 " "
+                - NAMED_TYPE@363..369
+                    - NAME@363..369
+                        - IDENT@363..369 "String"
+            - WHITESPACE@369..372 "\n  "
+            - FIELD_DEFINITION@372..397
+                - NAME@372..377
+                    - IDENT@372..377 "price"
+                - ARGUMENTS_DEFINITION@377..392
+                    - L_PAREN@377..378 "("
+                    - INPUT_VALUE_DEFINITION@378..391
+                        - NAME@378..386
+                            - IDENT@378..386 "setPrice"
+                        - COLON@386..387 ":"
+                        - WHITESPACE@387..388 " "
+                        - NAMED_TYPE@388..391
+                            - NAME@388..391
+                                - IDENT@388..391 "Int"
+                    - R_PAREN@391..392 ")"
+                - COLON@392..393 ":"
+                - WHITESPACE@393..394 " "
+                - NAMED_TYPE@394..397
+                    - NAME@394..397
+                        - IDENT@394..397 "Int"
+            - WHITESPACE@397..398 "\n"
+            - R_CURLY@398..399 "}"
+    - WHITESPACE@399..400 "\n"
 recursion limit: 4096, high: 2
diff --git a/crates/apollo-compiler/test_data/ok/0031_fragment_spread_possible.graphql b/crates/apollo-compiler/test_data/ok/0031_fragment_spread_possible.graphql
new file mode 100644
index 0000000..18c81af
--- /dev/null
+++ b/crates/apollo-compiler/test_data/ok/0031_fragment_spread_possible.graphql
@@ -0,0 +1,112 @@
+interface Pet {
+  name: String!
+}
+
+type Dog implements Pet {
+  name: String!
+  barkVolume: Int
+}
+
+type Cat implements Pet {
+  name: String!
+  meowVolume: Int
+}
+
+union CatOrDog = Cat | Dog
+
+type Human {
+  pets: [Pet!]
+}
+
+union DogOrHuman = Dog | Human
+
+# https://spec.graphql.org/October2021/#sec-Object-Spreads-In-Object-Scope
+fragment dogFragment on Dog {
+  ... on Dog {
+    barkVolume
+  }
+}
+
+# https://spec.graphql.org/October2021/#sec-Abstract-Spreads-in-Object-Scope
+fragment petNameFragment on Pet {
+  name
+}
+
+fragment interfaceWithinObjectFragment on Dog {
+  ...petNameFragment
+}
+
+fragment catOrDogNameFragment on CatOrDog {
+  ... on Cat {
+    meowVolume
+  }
+}
+
+fragment unionWithObjectFragment on Dog {
+  ...catOrDogNameFragment
+}
+
+# https://spec.graphql.org/October2021/#sec-Object-Spreads-In-Abstract-Scope
+fragment petFragment on Pet {
+  name
+  ... on Dog {
+    barkVolume
+  }
+}
+
+fragment catOrDogFragment on CatOrDog {
+  ... on Cat {
+    meowVolume
+  }
+}
+
+# https://spec.graphql.org/October2021/#sec-Abstract-Spreads-in-Abstract-Scope
+fragment unionWithInterface on Pet {
+  ...dogOrHumanFragment
+}
+
+fragment dogOrHumanFragment on DogOrHuman {
+  ... on Dog {
+    barkVolume
+  }
+}
+
+# https://spec.graphql.org/October2021/#sec-Abstract-Spreads-in-Abstract-Scope.Interface-Spreads-in-implemented-Interface-Scope
+interface Node {
+  id: ID!
+}
+
+interface Resource implements Node {
+  id: ID!
+  url: String
+}
+
+fragment interfaceWithInterface on Node {
+  ...resourceFragment
+}
+
+fragment resourceFragment on Resource {
+  url
+}
+
+# Make sure the above fragments are used
+type Query {
+  pet: Pet
+  resource: Resource
+}
+query UseAllFragments {
+  pet {
+    ... dogFragment
+    ... interfaceWithinObjectFragment
+    ... unionWithObjectFragment
+    ... petFragment
+    ... catOrDogFragment
+    ... unionWithInterface
+  }
+  resource {
+    ...interfaceWithInterface
+    ... @skip(if: true) {
+      urlInInlineFragment: url
+    }
+  }
+}
diff --git a/crates/apollo-compiler/test_data/ok/0031_fragment_spread_possible.txt b/crates/apollo-compiler/test_data/ok/0031_fragment_spread_possible.txt
new file mode 100644
index 0000000..9ab9315
--- /dev/null
+++ b/crates/apollo-compiler/test_data/ok/0031_fragment_spread_possible.txt
@@ -0,0 +1,715 @@
+- DOCUMENT@0..1927
+    - INTERFACE_TYPE_DEFINITION@0..33
+        - interface_KW@0..9 "interface"
+        - WHITESPACE@9..10 " "
+        - NAME@10..13
+            - IDENT@10..13 "Pet"
+        - WHITESPACE@13..14 " "
+        - FIELDS_DEFINITION@14..33
+            - L_CURLY@14..15 "{"
+            - WHITESPACE@15..18 "\n  "
+            - FIELD_DEFINITION@18..31
+                - NAME@18..22
+                    - IDENT@18..22 "name"
+                - COLON@22..23 ":"
+                - WHITESPACE@23..24 " "
+                - NON_NULL_TYPE@24..31
+                    - NAMED_TYPE@24..30
+                        - NAME@24..30
+                            - IDENT@24..30 "String"
+                    - BANG@30..31 "!"
+            - WHITESPACE@31..32 "\n"
+            - R_CURLY@32..33 "}"
+    - WHITESPACE@33..35 "\n\n"
+    - OBJECT_TYPE_DEFINITION@35..96
+        - type_KW@35..39 "type"
+        - WHITESPACE@39..40 " "
+        - NAME@40..43
+            - IDENT@40..43 "Dog"
+        - WHITESPACE@43..44 " "
+        - IMPLEMENTS_INTERFACES@44..58
+            - implements_KW@44..54 "implements"
+            - WHITESPACE@54..55 " "
+            - NAMED_TYPE@55..58
+                - NAME@55..58
+                    - IDENT@55..58 "Pet"
+        - WHITESPACE@58..59 " "
+        - FIELDS_DEFINITION@59..96
+            - L_CURLY@59..60 "{"
+            - WHITESPACE@60..63 "\n  "
+            - FIELD_DEFINITION@63..76
+                - NAME@63..67
+                    - IDENT@63..67 "name"
+                - COLON@67..68 ":"
+                - WHITESPACE@68..69 " "
+                - NON_NULL_TYPE@69..76
+                    - NAMED_TYPE@69..75
+                        - NAME@69..75
+                            - IDENT@69..75 "String"
+                    - BANG@75..76 "!"
+            - WHITESPACE@76..79 "\n  "
+            - FIELD_DEFINITION@79..94
+                - NAME@79..89
+                    - IDENT@79..89 "barkVolume"
+                - COLON@89..90 ":"
+                - WHITESPACE@90..91 " "
+                - NAMED_TYPE@91..94
+                    - NAME@91..94
+                        - IDENT@91..94 "Int"
+            - WHITESPACE@94..95 "\n"
+            - R_CURLY@95..96 "}"
+    - WHITESPACE@96..98 "\n\n"
+    - OBJECT_TYPE_DEFINITION@98..159
+        - type_KW@98..102 "type"
+        - WHITESPACE@102..103 " "
+        - NAME@103..106
+            - IDENT@103..106 "Cat"
+        - WHITESPACE@106..107 " "
+        - IMPLEMENTS_INTERFACES@107..121
+            - implements_KW@107..117 "implements"
+            - WHITESPACE@117..118 " "
+            - NAMED_TYPE@118..121
+                - NAME@118..121
+                    - IDENT@118..121 "Pet"
+        - WHITESPACE@121..122 " "
+        - FIELDS_DEFINITION@122..159
+            - L_CURLY@122..123 "{"
+            - WHITESPACE@123..126 "\n  "
+            - FIELD_DEFINITION@126..139
+                - NAME@126..130
+                    - IDENT@126..130 "name"
+                - COLON@130..131 ":"
+                - WHITESPACE@131..132 " "
+                - NON_NULL_TYPE@132..139
+                    - NAMED_TYPE@132..138
+                        - NAME@132..138
+                            - IDENT@132..138 "String"
+                    - BANG@138..139 "!"
+            - WHITESPACE@139..142 "\n  "
+            - FIELD_DEFINITION@142..157
+                - NAME@142..152
+                    - IDENT@142..152 "meowVolume"
+                - COLON@152..153 ":"
+                - WHITESPACE@153..154 " "
+                - NAMED_TYPE@154..157
+                    - NAME@154..157
+                        - IDENT@154..157 "Int"
+            - WHITESPACE@157..158 "\n"
+            - R_CURLY@158..159 "}"
+    - WHITESPACE@159..161 "\n\n"
+    - UNION_TYPE_DEFINITION@161..187
+        - union_KW@161..166 "union"
+        - WHITESPACE@166..167 " "
+        - NAME@167..175
+            - IDENT@167..175 "CatOrDog"
+        - WHITESPACE@175..176 " "
+        - UNION_MEMBER_TYPES@176..187
+            - EQ@176..177 "="
+            - WHITESPACE@177..178 " "
+            - NAMED_TYPE@178..181
+                - NAME@178..181
+                    - IDENT@178..181 "Cat"
+            - WHITESPACE@181..182 " "
+            - PIPE@182..183 "|"
+            - WHITESPACE@183..184 " "
+            - NAMED_TYPE@184..187
+                - NAME@184..187
+                    - IDENT@184..187 "Dog"
+    - WHITESPACE@187..189 "\n\n"
+    - OBJECT_TYPE_DEFINITION@189..218
+        - type_KW@189..193 "type"
+        - WHITESPACE@193..194 " "
+        - NAME@194..199
+            - IDENT@194..199 "Human"
+        - WHITESPACE@199..200 " "
+        - FIELDS_DEFINITION@200..218
+            - L_CURLY@200..201 "{"
+            - WHITESPACE@201..204 "\n  "
+            - FIELD_DEFINITION@204..216
+                - NAME@204..208
+                    - IDENT@204..208 "pets"
+                - COLON@208..209 ":"
+                - WHITESPACE@209..210 " "
+                - LIST_TYPE@210..216
+                    - L_BRACK@210..211 "["
+                    - NON_NULL_TYPE@211..215
+                        - NAMED_TYPE@211..214
+                            - NAME@211..214
+                                - IDENT@211..214 "Pet"
+                        - BANG@214..215 "!"
+                    - R_BRACK@215..216 "]"
+            - WHITESPACE@216..217 "\n"
+            - R_CURLY@217..218 "}"
+    - WHITESPACE@218..220 "\n\n"
+    - UNION_TYPE_DEFINITION@220..250
+        - union_KW@220..225 "union"
+        - WHITESPACE@225..226 " "
+        - NAME@226..236
+            - IDENT@226..236 "DogOrHuman"
+        - WHITESPACE@236..237 " "
+        - UNION_MEMBER_TYPES@237..250
+            - EQ@237..238 "="
+            - WHITESPACE@238..239 " "
+            - NAMED_TYPE@239..242
+                - NAME@239..242
+                    - IDENT@239..242 "Dog"
+            - WHITESPACE@242..243 " "
+            - PIPE@243..244 "|"
+            - WHITESPACE@244..245 " "
+            - NAMED_TYPE@245..250
+                - NAME@245..250
+                    - IDENT@245..250 "Human"
+    - WHITESPACE@250..252 "\n\n"
+    - COMMENT@252..326 "# https://spec.graphql.org/October2021/#sec-Object-Spreads-In-Object-Scope"
+    - WHITESPACE@326..327 "\n"
+    - FRAGMENT_DEFINITION@327..392
+        - fragment_KW@327..335 "fragment"
+        - WHITESPACE@335..336 " "
+        - FRAGMENT_NAME@336..347
+            - NAME@336..347
+                - IDENT@336..347 "dogFragment"
+        - WHITESPACE@347..348 " "
+        - TYPE_CONDITION@348..354
+            - on_KW@348..350 "on"
+            - WHITESPACE@350..351 " "
+            - NAMED_TYPE@351..354
+                - NAME@351..354
+                    - IDENT@351..354 "Dog"
+        - WHITESPACE@354..355 " "
+        - SELECTION_SET@355..392
+            - L_CURLY@355..356 "{"
+            - WHITESPACE@356..359 "\n  "
+            - INLINE_FRAGMENT@359..390
+                - SPREAD@359..362 "..."
+                - WHITESPACE@362..363 " "
+                - TYPE_CONDITION@363..369
+                    - on_KW@363..365 "on"
+                    - WHITESPACE@365..366 " "
+                    - NAMED_TYPE@366..369
+                        - NAME@366..369
+                            - IDENT@366..369 "Dog"
+                - WHITESPACE@369..370 " "
+                - SELECTION_SET@370..390
+                    - L_CURLY@370..371 "{"
+                    - WHITESPACE@371..376 "\n    "
+                    - FIELD@376..386
+                        - NAME@376..386
+                            - IDENT@376..386 "barkVolume"
+                    - WHITESPACE@386..389 "\n  "
+                    - R_CURLY@389..390 "}"
+            - WHITESPACE@390..391 "\n"
+            - R_CURLY@391..392 "}"
+    - WHITESPACE@392..394 "\n\n"
+    - COMMENT@394..470 "# https://spec.graphql.org/October2021/#sec-Abstract-Spreads-in-Object-Scope"
+    - WHITESPACE@470..471 "\n"
+    - FRAGMENT_DEFINITION@471..513
+        - fragment_KW@471..479 "fragment"
+        - WHITESPACE@479..480 " "
+        - FRAGMENT_NAME@480..495
+            - NAME@480..495
+                - IDENT@480..495 "petNameFragment"
+        - WHITESPACE@495..496 " "
+        - TYPE_CONDITION@496..502
+            - on_KW@496..498 "on"
+            - WHITESPACE@498..499 " "
+            - NAMED_TYPE@499..502
+                - NAME@499..502
+                    - IDENT@499..502 "Pet"
+        - WHITESPACE@502..503 " "
+        - SELECTION_SET@503..513
+            - L_CURLY@503..504 "{"
+            - WHITESPACE@504..507 "\n  "
+            - FIELD@507..511
+                - NAME@507..511
+                    - IDENT@507..511 "name"
+            - WHITESPACE@511..512 "\n"
+            - R_CURLY@512..513 "}"
+    - WHITESPACE@513..515 "\n\n"
+    - FRAGMENT_DEFINITION@515..585
+        - fragment_KW@515..523 "fragment"
+        - WHITESPACE@523..524 " "
+        - FRAGMENT_NAME@524..553
+            - NAME@524..553
+                - IDENT@524..553 "interfaceWithinObjectFragment"
+        - WHITESPACE@553..554 " "
+        - TYPE_CONDITION@554..560
+            - on_KW@554..556 "on"
+            - WHITESPACE@556..557 " "
+            - NAMED_TYPE@557..560
+                - NAME@557..560
+                    - IDENT@557..560 "Dog"
+        - WHITESPACE@560..561 " "
+        - SELECTION_SET@561..585
+            - L_CURLY@561..562 "{"
+            - WHITESPACE@562..565 "\n  "
+            - FRAGMENT_SPREAD@565..583
+                - SPREAD@565..568 "..."
+                - FRAGMENT_NAME@568..583
+                    - NAME@568..583
+                        - IDENT@568..583 "petNameFragment"
+            - WHITESPACE@583..584 "\n"
+            - R_CURLY@584..585 "}"
+    - WHITESPACE@585..587 "\n\n"
+    - FRAGMENT_DEFINITION@587..666
+        - fragment_KW@587..595 "fragment"
+        - WHITESPACE@595..596 " "
+        - FRAGMENT_NAME@596..616
+            - NAME@596..616
+                - IDENT@596..616 "catOrDogNameFragment"
+        - WHITESPACE@616..617 " "
+        - TYPE_CONDITION@617..628
+            - on_KW@617..619 "on"
+            - WHITESPACE@619..620 " "
+            - NAMED_TYPE@620..628
+                - NAME@620..628
+                    - IDENT@620..628 "CatOrDog"
+        - WHITESPACE@628..629 " "
+        - SELECTION_SET@629..666
+            - L_CURLY@629..630 "{"
+            - WHITESPACE@630..633 "\n  "
+            - INLINE_FRAGMENT@633..664
+                - SPREAD@633..636 "..."
+                - WHITESPACE@636..637 " "
+                - TYPE_CONDITION@637..643
+                    - on_KW@637..639 "on"
+                    - WHITESPACE@639..640 " "
+                    - NAMED_TYPE@640..643
+                        - NAME@640..643
+                            - IDENT@640..643 "Cat"
+                - WHITESPACE@643..644 " "
+                - SELECTION_SET@644..664
+                    - L_CURLY@644..645 "{"
+                    - WHITESPACE@645..650 "\n    "
+                    - FIELD@650..660
+                        - NAME@650..660
+                            - IDENT@650..660 "meowVolume"
+                    - WHITESPACE@660..663 "\n  "
+                    - R_CURLY@663..664 "}"
+            - WHITESPACE@664..665 "\n"
+            - R_CURLY@665..666 "}"
+    - WHITESPACE@666..668 "\n\n"
+    - FRAGMENT_DEFINITION@668..737
+        - fragment_KW@668..676 "fragment"
+        - WHITESPACE@676..677 " "
+        - FRAGMENT_NAME@677..700
+            - NAME@677..700
+                - IDENT@677..700 "unionWithObjectFragment"
+        - WHITESPACE@700..701 " "
+        - TYPE_CONDITION@701..707
+            - on_KW@701..703 "on"
+            - WHITESPACE@703..704 " "
+            - NAMED_TYPE@704..707
+                - NAME@704..707
+                    - IDENT@704..707 "Dog"
+        - WHITESPACE@707..708 " "
+        - SELECTION_SET@708..737
+            - L_CURLY@708..709 "{"
+            - WHITESPACE@709..712 "\n  "
+            - FRAGMENT_SPREAD@712..735
+                - SPREAD@712..715 "..."
+                - FRAGMENT_NAME@715..735
+                    - NAME@715..735
+                        - IDENT@715..735 "catOrDogNameFragment"
+            - WHITESPACE@735..736 "\n"
+            - R_CURLY@736..737 "}"
+    - WHITESPACE@737..739 "\n\n"
+    - COMMENT@739..815 "# https://spec.graphql.org/October2021/#sec-Object-Spreads-In-Abstract-Scope"
+    - WHITESPACE@815..816 "\n"
+    - FRAGMENT_DEFINITION@816..888
+        - fragment_KW@816..824 "fragment"
+        - WHITESPACE@824..825 " "
+        - FRAGMENT_NAME@825..836
+            - NAME@825..836
+                - IDENT@825..836 "petFragment"
+        - WHITESPACE@836..837 " "
+        - TYPE_CONDITION@837..843
+            - on_KW@837..839 "on"
+            - WHITESPACE@839..840 " "
+            - NAMED_TYPE@840..843
+                - NAME@840..843
+                    - IDENT@840..843 "Pet"
+        - WHITESPACE@843..844 " "
+        - SELECTION_SET@844..888
+            - L_CURLY@844..845 "{"
+            - WHITESPACE@845..848 "\n  "
+            - FIELD@848..852
+                - NAME@848..852
+                    - IDENT@848..852 "name"
+            - WHITESPACE@852..855 "\n  "
+            - INLINE_FRAGMENT@855..886
+                - SPREAD@855..858 "..."
+                - WHITESPACE@858..859 " "
+                - TYPE_CONDITION@859..865
+                    - on_KW@859..861 "on"
+                    - WHITESPACE@861..862 " "
+                    - NAMED_TYPE@862..865
+                        - NAME@862..865
+                            - IDENT@862..865 "Dog"
+                - WHITESPACE@865..866 " "
+                - SELECTION_SET@866..886
+                    - L_CURLY@866..867 "{"
+                    - WHITESPACE@867..872 "\n    "
+                    - FIELD@872..882
+                        - NAME@872..882
+                            - IDENT@872..882 "barkVolume"
+                    - WHITESPACE@882..885 "\n  "
+                    - R_CURLY@885..886 "}"
+            - WHITESPACE@886..887 "\n"
+            - R_CURLY@887..888 "}"
+    - WHITESPACE@888..890 "\n\n"
+    - FRAGMENT_DEFINITION@890..965
+        - fragment_KW@890..898 "fragment"
+        - WHITESPACE@898..899 " "
+        - FRAGMENT_NAME@899..915
+            - NAME@899..915
+                - IDENT@899..915 "catOrDogFragment"
+        - WHITESPACE@915..916 " "
+        - TYPE_CONDITION@916..927
+            - on_KW@916..918 "on"
+            - WHITESPACE@918..919 " "
+            - NAMED_TYPE@919..927
+                - NAME@919..927
+                    - IDENT@919..927 "CatOrDog"
+        - WHITESPACE@927..928 " "
+        - SELECTION_SET@928..965
+            - L_CURLY@928..929 "{"
+            - WHITESPACE@929..932 "\n  "
+            - INLINE_FRAGMENT@932..963
+                - SPREAD@932..935 "..."
+                - WHITESPACE@935..936 " "
+                - TYPE_CONDITION@936..942
+                    - on_KW@936..938 "on"
+                    - WHITESPACE@938..939 " "
+                    - NAMED_TYPE@939..942
+                        - NAME@939..942
+                            - IDENT@939..942 "Cat"
+                - WHITESPACE@942..943 " "
+                - SELECTION_SET@943..963
+                    - L_CURLY@943..944 "{"
+                    - WHITESPACE@944..949 "\n    "
+                    - FIELD@949..959
+                        - NAME@949..959
+                            - IDENT@949..959 "meowVolume"
+                    - WHITESPACE@959..962 "\n  "
+                    - R_CURLY@962..963 "}"
+            - WHITESPACE@963..964 "\n"
+            - R_CURLY@964..965 "}"
+    - WHITESPACE@965..967 "\n\n"
+    - COMMENT@967..1045 "# https://spec.graphql.org/October2021/#sec-Abstract-Spreads-in-Abstract-Scope"
+    - WHITESPACE@1045..1046 "\n"
+    - FRAGMENT_DEFINITION@1046..1108
+        - fragment_KW@1046..1054 "fragment"
+        - WHITESPACE@1054..1055 " "
+        - FRAGMENT_NAME@1055..1073
+            - NAME@1055..1073
+                - IDENT@1055..1073 "unionWithInterface"
+        - WHITESPACE@1073..1074 " "
+        - TYPE_CONDITION@1074..1080
+            - on_KW@1074..1076 "on"
+            - WHITESPACE@1076..1077 " "
+            - NAMED_TYPE@1077..1080
+                - NAME@1077..1080
+                    - IDENT@1077..1080 "Pet"
+        - WHITESPACE@1080..1081 " "
+        - SELECTION_SET@1081..1108
+            - L_CURLY@1081..1082 "{"
+            - WHITESPACE@1082..1085 "\n  "
+            - FRAGMENT_SPREAD@1085..1106
+                - SPREAD@1085..1088 "..."
+                - FRAGMENT_NAME@1088..1106
+                    - NAME@1088..1106
+                        - IDENT@1088..1106 "dogOrHumanFragment"
+            - WHITESPACE@1106..1107 "\n"
+            - R_CURLY@1107..1108 "}"
+    - WHITESPACE@1108..1110 "\n\n"
+    - FRAGMENT_DEFINITION@1110..1189
+        - fragment_KW@1110..1118 "fragment"
+        - WHITESPACE@1118..1119 " "
+        - FRAGMENT_NAME@1119..1137
+            - NAME@1119..1137
+                - IDENT@1119..1137 "dogOrHumanFragment"
+        - WHITESPACE@1137..1138 " "
+        - TYPE_CONDITION@1138..1151
+            - on_KW@1138..1140 "on"
+            - WHITESPACE@1140..1141 " "
+            - NAMED_TYPE@1141..1151
+                - NAME@1141..1151
+                    - IDENT@1141..1151 "DogOrHuman"
+        - WHITESPACE@1151..1152 " "
+        - SELECTION_SET@1152..1189
+            - L_CURLY@1152..1153 "{"
+            - WHITESPACE@1153..1156 "\n  "
+            - INLINE_FRAGMENT@1156..1187
+                - SPREAD@1156..1159 "..."
+                - WHITESPACE@1159..1160 " "
+                - TYPE_CONDITION@1160..1166
+                    - on_KW@1160..1162 "on"
+                    - WHITESPACE@1162..1163 " "
+                    - NAMED_TYPE@1163..1166
+                        - NAME@1163..1166
+                            - IDENT@1163..1166 "Dog"
+                - WHITESPACE@1166..1167 " "
+                - SELECTION_SET@1167..1187
+                    - L_CURLY@1167..1168 "{"
+                    - WHITESPACE@1168..1173 "\n    "
+                    - FIELD@1173..1183
+                        - NAME@1173..1183
+                            - IDENT@1173..1183 "barkVolume"
+                    - WHITESPACE@1183..1186 "\n  "
+                    - R_CURLY@1186..1187 "}"
+            - WHITESPACE@1187..1188 "\n"
+            - R_CURLY@1188..1189 "}"
+    - WHITESPACE@1189..1191 "\n\n"
+    - COMMENT@1191..1318 "# https://spec.graphql.org/October2021/#sec-Abstract-Spreads-in-Abstract-Scope.Interface-Spreads-in-implemented-Interface-Scope"
+    - WHITESPACE@1318..1319 "\n"
+    - INTERFACE_TYPE_DEFINITION@1319..1347
+        - interface_KW@1319..1328 "interface"
+        - WHITESPACE@1328..1329 " "
+        - NAME@1329..1333
+            - IDENT@1329..1333 "Node"
+        - WHITESPACE@1333..1334 " "
+        - FIELDS_DEFINITION@1334..1347
+            - L_CURLY@1334..1335 "{"
+            - WHITESPACE@1335..1338 "\n  "
+            - FIELD_DEFINITION@1338..1345
+                - NAME@1338..1340
+                    - IDENT@1338..1340 "id"
+                - COLON@1340..1341 ":"
+                - WHITESPACE@1341..1342 " "
+                - NON_NULL_TYPE@1342..1345
+                    - NAMED_TYPE@1342..1344
+                        - NAME@1342..1344
+                            - IDENT@1342..1344 "ID"
+                    - BANG@1344..1345 "!"
+            - WHITESPACE@1345..1346 "\n"
+            - R_CURLY@1346..1347 "}"
+    - WHITESPACE@1347..1349 "\n\n"
+    - INTERFACE_TYPE_DEFINITION@1349..1411
+        - interface_KW@1349..1358 "interface"
+        - WHITESPACE@1358..1359 " "
+        - NAME@1359..1367
+            - IDENT@1359..1367 "Resource"
+        - WHITESPACE@1367..1368 " "
+        - IMPLEMENTS_INTERFACES@1368..1383
+            - implements_KW@1368..1378 "implements"
+            - WHITESPACE@1378..1379 " "
+            - NAMED_TYPE@1379..1383
+                - NAME@1379..1383
+                    - IDENT@1379..1383 "Node"
+        - WHITESPACE@1383..1384 " "
+        - FIELDS_DEFINITION@1384..1411
+            - L_CURLY@1384..1385 "{"
+            - WHITESPACE@1385..1388 "\n  "
+            - FIELD_DEFINITION@1388..1395
+                - NAME@1388..1390
+                    - IDENT@1388..1390 "id"
+                - COLON@1390..1391 ":"
+                - WHITESPACE@1391..1392 " "
+                - NON_NULL_TYPE@1392..1395
+                    - NAMED_TYPE@1392..1394
+                        - NAME@1392..1394
+                            - IDENT@1392..1394 "ID"
+                    - BANG@1394..1395 "!"
+            - WHITESPACE@1395..1398 "\n  "
+            - FIELD_DEFINITION@1398..1409
+                - NAME@1398..1401
+                    - IDENT@1398..1401 "url"
+                - COLON@1401..1402 ":"
+                - WHITESPACE@1402..1403 " "
+                - NAMED_TYPE@1403..1409
+                    - NAME@1403..1409
+                        - IDENT@1403..1409 "String"
+            - WHITESPACE@1409..1410 "\n"
+            - R_CURLY@1410..1411 "}"
+    - WHITESPACE@1411..1413 "\n\n"
+    - FRAGMENT_DEFINITION@1413..1478
+        - fragment_KW@1413..1421 "fragment"
+        - WHITESPACE@1421..1422 " "
+        - FRAGMENT_NAME@1422..1444
+            - NAME@1422..1444
+                - IDENT@1422..1444 "interfaceWithInterface"
+        - WHITESPACE@1444..1445 " "
+        - TYPE_CONDITION@1445..1452
+            - on_KW@1445..1447 "on"
+            - WHITESPACE@1447..1448 " "
+            - NAMED_TYPE@1448..1452
+                - NAME@1448..1452
+                    - IDENT@1448..1452 "Node"
+        - WHITESPACE@1452..1453 " "
+        - SELECTION_SET@1453..1478
+            - L_CURLY@1453..1454 "{"
+            - WHITESPACE@1454..1457 "\n  "
+            - FRAGMENT_SPREAD@1457..1476
+                - SPREAD@1457..1460 "..."
+                - FRAGMENT_NAME@1460..1476
+                    - NAME@1460..1476
+                        - IDENT@1460..1476 "resourceFragment"
+            - WHITESPACE@1476..1477 "\n"
+            - R_CURLY@1477..1478 "}"
+    - WHITESPACE@1478..1480 "\n\n"
+    - FRAGMENT_DEFINITION@1480..1527
+        - fragment_KW@1480..1488 "fragment"
+        - WHITESPACE@1488..1489 " "
+        - FRAGMENT_NAME@1489..1505
+            - NAME@1489..1505
+                - IDENT@1489..1505 "resourceFragment"
+        - WHITESPACE@1505..1506 " "
+        - TYPE_CONDITION@1506..1517
+            - on_KW@1506..1508 "on"
+            - WHITESPACE@1508..1509 " "
+            - NAMED_TYPE@1509..1517
+                - NAME@1509..1517
+                    - IDENT@1509..1517 "Resource"
+        - WHITESPACE@1517..1518 " "
+        - SELECTION_SET@1518..1527
+            - L_CURLY@1518..1519 "{"
+            - WHITESPACE@1519..1522 "\n  "
+            - FIELD@1522..1525
+                - NAME@1522..1525
+                    - IDENT@1522..1525 "url"
+            - WHITESPACE@1525..1526 "\n"
+            - R_CURLY@1526..1527 "}"
+    - WHITESPACE@1527..1529 "\n\n"
+    - COMMENT@1529..1569 "# Make sure the above fragments are used"
+    - WHITESPACE@1569..1570 "\n"
+    - OBJECT_TYPE_DEFINITION@1570..1616
+        - type_KW@1570..1574 "type"
+        - WHITESPACE@1574..1575 " "
+        - NAME@1575..1580
+            - IDENT@1575..1580 "Query"
+        - WHITESPACE@1580..1581 " "
+        - FIELDS_DEFINITION@1581..1616
+            - L_CURLY@1581..1582 "{"
+            - WHITESPACE@1582..1585 "\n  "
+            - FIELD_DEFINITION@1585..1593
+                - NAME@1585..1588
+                    - IDENT@1585..1588 "pet"
+                - COLON@1588..1589 ":"
+                - WHITESPACE@1589..1590 " "
+                - NAMED_TYPE@1590..1593
+                    - NAME@1590..1593
+                        - IDENT@1590..1593 "Pet"
+            - WHITESPACE@1593..1596 "\n  "
+            - FIELD_DEFINITION@1596..1614
+                - NAME@1596..1604
+                    - IDENT@1596..1604 "resource"
+                - COLON@1604..1605 ":"
+                - WHITESPACE@1605..1606 " "
+                - NAMED_TYPE@1606..1614
+                    - NAME@1606..1614
+                        - IDENT@1606..1614 "Resource"
+            - WHITESPACE@1614..1615 "\n"
+            - R_CURLY@1615..1616 "}"
+    - WHITESPACE@1616..1617 "\n"
+    - OPERATION_DEFINITION@1617..1926
+        - OPERATION_TYPE@1617..1622
+            - query_KW@1617..1622 "query"
+        - WHITESPACE@1622..1623 " "
+        - NAME@1623..1638
+            - IDENT@1623..1638 "UseAllFragments"
+        - WHITESPACE@1638..1639 " "
+        - SELECTION_SET@1639..1926
+            - L_CURLY@1639..1640 "{"
+            - WHITESPACE@1640..1643 "\n  "
+            - FIELD@1643..1814
+                - NAME@1643..1646
+                    - IDENT@1643..1646 "pet"
+                - WHITESPACE@1646..1647 " "
+                - SELECTION_SET@1647..1814
+                    - L_CURLY@1647..1648 "{"
+                    - WHITESPACE@1648..1653 "\n    "
+                    - FRAGMENT_SPREAD@1653..1668
+                        - SPREAD@1653..1656 "..."
+                        - WHITESPACE@1656..1657 " "
+                        - FRAGMENT_NAME@1657..1668
+                            - NAME@1657..1668
+                                - IDENT@1657..1668 "dogFragment"
+                    - WHITESPACE@1668..1673 "\n    "
+                    - FRAGMENT_SPREAD@1673..1706
+                        - SPREAD@1673..1676 "..."
+                        - WHITESPACE@1676..1677 " "
+                        - FRAGMENT_NAME@1677..1706
+                            - NAME@1677..1706
+                                - IDENT@1677..1706 "interfaceWithinObjectFragment"
+                    - WHITESPACE@1706..1711 "\n    "
+                    - FRAGMENT_SPREAD@1711..1738
+                        - SPREAD@1711..1714 "..."
+                        - WHITESPACE@1714..1715 " "
+                        - FRAGMENT_NAME@1715..1738
+                            - NAME@1715..1738
+                                - IDENT@1715..1738 "unionWithObjectFragment"
+                    - WHITESPACE@1738..1743 "\n    "
+                    - FRAGMENT_SPREAD@1743..1758
+                        - SPREAD@1743..1746 "..."
+                        - WHITESPACE@1746..1747 " "
+                        - FRAGMENT_NAME@1747..1758
+                            - NAME@1747..1758
+                                - IDENT@1747..1758 "petFragment"
+                    - WHITESPACE@1758..1763 "\n    "
+                    - FRAGMENT_SPREAD@1763..1783
+                        - SPREAD@1763..1766 "..."
+                        - WHITESPACE@1766..1767 " "
+                        - FRAGMENT_NAME@1767..1783
+                            - NAME@1767..1783
+                                - IDENT@1767..1783 "catOrDogFragment"
+                    - WHITESPACE@1783..1788 "\n    "
+                    - FRAGMENT_SPREAD@1788..1810
+                        - SPREAD@1788..1791 "..."
+                        - WHITESPACE@1791..1792 " "
+                        - FRAGMENT_NAME@1792..1810
+                            - NAME@1792..1810
+                                - IDENT@1792..1810 "unionWithInterface"
+                    - WHITESPACE@1810..1813 "\n  "
+                    - R_CURLY@1813..1814 "}"
+            - WHITESPACE@1814..1817 "\n  "
+            - FIELD@1817..1924
+                - NAME@1817..1825
+                    - IDENT@1817..1825 "resource"
+                - WHITESPACE@1825..1826 " "
+                - SELECTION_SET@1826..1924
+                    - L_CURLY@1826..1827 "{"
+                    - WHITESPACE@1827..1832 "\n    "
+                    - FRAGMENT_SPREAD@1832..1857
+                        - SPREAD@1832..1835 "..."
+                        - FRAGMENT_NAME@1835..1857
+                            - NAME@1835..1857
+                                - IDENT@1835..1857 "interfaceWithInterface"
+                    - WHITESPACE@1857..1862 "\n    "
+                    - INLINE_FRAGMENT@1862..1920
+                        - SPREAD@1862..1865 "..."
+                        - WHITESPACE@1865..1866 " "
+                        - DIRECTIVES@1866..1881
+                            - DIRECTIVE@1866..1881
+                                - AT@1866..1867 "@"
+                                - NAME@1867..1871
+                                    - IDENT@1867..1871 "skip"
+                                - ARGUMENTS@1871..1881
+                                    - L_PAREN@1871..1872 "("
+                                    - ARGUMENT@1872..1880
+                                        - NAME@1872..1874
+                                            - IDENT@1872..1874 "if"
+                                        - COLON@1874..1875 ":"
+                                        - WHITESPACE@1875..1876 " "
+                                        - BOOLEAN_VALUE@1876..1880
+                                            - true_KW@1876..1880 "true"
+                                    - R_PAREN@1880..1881 ")"
+                        - WHITESPACE@1881..1882 " "
+                        - SELECTION_SET@1882..1920
+                            - L_CURLY@1882..1883 "{"
+                            - WHITESPACE@1883..1890 "\n      "
+                            - FIELD@1890..1914
+                                - ALIAS@1890..1910
+                                    - NAME@1890..1909
+                                        - IDENT@1890..1909 "urlInInlineFragment"
+                                    - COLON@1909..1910 ":"
+                                - WHITESPACE@1910..1911 " "
+                                - NAME@1911..1914
+                                    - IDENT@1911..1914 "url"
+                            - WHITESPACE@1914..1919 "\n    "
+                            - R_CURLY@1919..1920 "}"
+                    - WHITESPACE@1920..1923 "\n  "
+                    - R_CURLY@1923..1924 "}"
+            - WHITESPACE@1924..1925 "\n"
+            - R_CURLY@1925..1926 "}"
+    - WHITESPACE@1926..1927 "\n"
+recursion limit: 4096, high: 3