Repository: apollographql/apollo-rs (rust)

Issue:
Validation: extensions of undefined type
Two scenarios where validation should emit a diagnostic if they don’t already:

* The AST contains a type extension, but there is no type definition with that name
* There is one, but of the wrong kind (e.g. enum instead of union)

Pull request:
Validate extension existence & kind
Extensions referencing nonexistent definitions produce a diagnostic:
![image](https://user-images.githubusercontent.com/1006268/218747250-64c1beff-11b0-4d46-873d-97ba354a1904.png)

Extensions referencing some other kind of definition produce a diagnostic:
![image](https://user-images.githubusercontent.com/1006268/218747348-6b1f3bb5-11ed-498e-8f58-9f1a54bbc511.png)

This adds an `extensions()` salsa query that creates HIR nodes for _all_ declared extensions. The new validation uses that to iterate over all the extensions. The existing queries that used to collect extensions from the AST now also use this new query so we don't create the nodes in two places.

Closes #408

Patch:
diff --git a/crates/apollo-compiler/src/database/hir.rs b/crates/apollo-compiler/src/database/hir.rs
index f360146..f22be3c 100644
--- a/crates/apollo-compiler/src/database/hir.rs
+++ b/crates/apollo-compiler/src/database/hir.rs
@@ -190,6 +190,81 @@ impl TypeDefinition {
     }
 }
 
+#[derive(Clone, Debug, PartialEq, Eq, Hash)]
+pub enum TypeExtension {
+    ScalarTypeExtension(Arc<ScalarTypeExtension>),
+    ObjectTypeExtension(Arc<ObjectTypeExtension>),
+    InterfaceTypeExtension(Arc<InterfaceTypeExtension>),
+    UnionTypeExtension(Arc<UnionTypeExtension>),
+    EnumTypeExtension(Arc<EnumTypeExtension>),
+    InputObjectTypeExtension(Arc<InputObjectTypeExtension>),
+}
+
+impl TypeExtension {
+    pub fn name(&self) -> &str {
+        match self {
+            Self::ScalarTypeExtension(def) => def.name(),
+            Self::ObjectTypeExtension(def) => def.name(),
+            Self::InterfaceTypeExtension(def) => def.name(),
+            Self::UnionTypeExtension(def) => def.name(),
+            Self::EnumTypeExtension(def) => def.name(),
+            Self::InputObjectTypeExtension(def) => def.name(),
+        }
+    }
+
+    pub fn name_src(&self) -> &Name {
+        match self {
+            Self::ScalarTypeExtension(def) => def.name_src(),
+            Self::ObjectTypeExtension(def) => def.name_src(),
+            Self::InterfaceTypeExtension(def) => def.name_src(),
+            Self::UnionTypeExtension(def) => def.name_src(),
+            Self::EnumTypeExtension(def) => def.name_src(),
+            Self::InputObjectTypeExtension(def) => def.name_src(),
+        }
+    }
+
+    pub fn kind(&self) -> &'static str {
+        match self {
+            Self::ScalarTypeExtension(_) => "ScalarTypeExtension",
+            Self::ObjectTypeExtension(_) => "ObjectTypeExtension",
+            Self::InterfaceTypeExtension(_) => "InterfaceTypeExtension",
+            Self::UnionTypeExtension(_) => "UnionTypeExtension",
+            Self::EnumTypeExtension(_) => "EnumTypeExtension",
+            Self::InputObjectTypeExtension(_) => "InputObjectTypeExtension",
+        }
+    }
+
+    pub fn directives(&self) -> &[Directive] {
+        match self {
+            Self::ScalarTypeExtension(def) => def.directives(),
+            Self::ObjectTypeExtension(def) => def.directives(),
+            Self::InterfaceTypeExtension(def) => def.directives(),
+            Self::UnionTypeExtension(def) => def.directives(),
+            Self::EnumTypeExtension(def) => def.directives(),
+            Self::InputObjectTypeExtension(def) => def.directives(),
+        }
+    }
+
+    pub fn field(&self, name: &str) -> Option<&FieldDefinition> {
+        match self {
+            Self::ObjectTypeExtension(def) => def.field(name),
+            Self::InterfaceTypeExtension(def) => def.field(name),
+            _ => None,
+        }
+    }
+
+    pub fn loc(&self) -> HirNodeLocation {
+        match self {
+            Self::ObjectTypeExtension(def) => def.loc(),
+            Self::InterfaceTypeExtension(def) => def.loc(),
+            Self::UnionTypeExtension(def) => def.loc(),
+            Self::EnumTypeExtension(def) => def.loc(),
+            Self::InputObjectTypeExtension(def) => def.loc(),
+            Self::ScalarTypeExtension(def) => def.loc(),
+        }
+    }
+}
+
 #[derive(Clone, Debug, Hash, PartialEq, Eq)]
 pub struct FragmentDefinition {
     pub(crate) name: Name,
@@ -1219,7 +1294,7 @@ pub struct SchemaDefinition {
     pub(crate) directives: Arc<Vec<Directive>>,
     pub(crate) root_operation_type_definition: Arc<Vec<RootOperationTypeDefinition>>,
     pub(crate) loc: Option<HirNodeLocation>,
-    pub(crate) extensions: Vec<SchemaExtension>,
+    pub(crate) extensions: Vec<Arc<SchemaExtension>>,
 }
 
 impl SchemaDefinition {
@@ -1246,7 +1321,7 @@ impl SchemaDefinition {
     }
 
     /// Extensions that apply to this definition
-    pub fn extensions(&self) -> &[SchemaExtension] {
+    pub fn extensions(&self) -> &[Arc<SchemaExtension>] {
         &self.extensions
     }
 
@@ -1337,7 +1412,7 @@ pub struct ObjectTypeDefinition {
     pub(crate) directives: Arc<Vec<Directive>>,
     pub(crate) fields_definition: Arc<Vec<FieldDefinition>>,
     pub(crate) loc: HirNodeLocation,
-    pub(crate) extensions: Vec<ObjectTypeExtension>,
+    pub(crate) extensions: Vec<Arc<ObjectTypeExtension>>,
 }
 
 impl ObjectTypeDefinition {
@@ -1382,7 +1457,7 @@ impl ObjectTypeDefinition {
     }
 
     /// Extensions that apply to this definition
-    pub fn extensions(&self) -> &[ObjectTypeExtension] {
+    pub fn extensions(&self) -> &[Arc<ObjectTypeExtension>] {
         &self.extensions
     }
 }
@@ -1528,7 +1603,7 @@ pub struct ScalarTypeDefinition {
     pub(crate) directives: Arc<Vec<Directive>>,
     pub(crate) built_in: bool,
     pub(crate) loc: Option<HirNodeLocation>,
-    pub(crate) extensions: Vec<ScalarTypeExtension>,
+    pub(crate) extensions: Vec<Arc<ScalarTypeExtension>>,
 }
 
 impl ScalarTypeDefinition {
@@ -1565,7 +1640,7 @@ impl ScalarTypeDefinition {
     }
 
     /// Extensions that apply to this definition
-    pub fn extensions(&self) -> &[ScalarTypeExtension] {
+    pub fn extensions(&self) -> &[Arc<ScalarTypeExtension>] {
         &self.extensions
     }
 }
@@ -1577,7 +1652,7 @@ pub struct EnumTypeDefinition {
     pub(crate) directives: Arc<Vec<Directive>>,
     pub(crate) enum_values_definition: Arc<Vec<EnumValueDefinition>>,
     pub(crate) loc: HirNodeLocation,
-    pub(crate) extensions: Vec<EnumTypeExtension>,
+    pub(crate) extensions: Vec<Arc<EnumTypeExtension>>,
 }
 
 impl EnumTypeDefinition {
@@ -1612,7 +1687,7 @@ impl EnumTypeDefinition {
     }
 
     /// Extensions that apply to this definition
-    pub fn extensions(&self) -> &[EnumTypeExtension] {
+    pub fn extensions(&self) -> &[Arc<EnumTypeExtension>] {
         &self.extensions
     }
 }
@@ -1649,7 +1724,7 @@ pub struct UnionTypeDefinition {
     pub(crate) directives: Arc<Vec<Directive>>,
     pub(crate) union_members: Arc<Vec<UnionMember>>,
     pub(crate) loc: HirNodeLocation,
-    pub(crate) extensions: Vec<UnionTypeExtension>,
+    pub(crate) extensions: Vec<Arc<UnionTypeExtension>>,
 }
 
 impl UnionTypeDefinition {
@@ -1684,7 +1759,7 @@ impl UnionTypeDefinition {
     }
 
     /// Extensions that apply to this definition
-    pub fn extensions(&self) -> &[UnionTypeExtension] {
+    pub fn extensions(&self) -> &[Arc<UnionTypeExtension>] {
         &self.extensions
     }
 }
@@ -1720,7 +1795,7 @@ pub struct InterfaceTypeDefinition {
     pub(crate) directives: Arc<Vec<Directive>>,
     pub(crate) fields_definition: Arc<Vec<FieldDefinition>>,
     pub(crate) loc: HirNodeLocation,
-    pub(crate) extensions: Vec<InterfaceTypeExtension>,
+    pub(crate) extensions: Vec<Arc<InterfaceTypeExtension>>,
 }
 
 impl InterfaceTypeDefinition {
@@ -1765,7 +1840,7 @@ impl InterfaceTypeDefinition {
     }
 
     /// Extensions that apply to this definition
-    pub fn extensions(&self) -> &[InterfaceTypeExtension] {
+    pub fn extensions(&self) -> &[Arc<InterfaceTypeExtension>] {
         &self.extensions
     }
 }
@@ -1777,7 +1852,7 @@ pub struct InputObjectTypeDefinition {
     pub(crate) directives: Arc<Vec<Directive>>,
     pub(crate) input_fields_definition: Arc<Vec<InputValueDefinition>>,
     pub(crate) loc: HirNodeLocation,
-    pub(crate) extensions: Vec<InputObjectTypeExtension>,
+    pub(crate) extensions: Vec<Arc<InputObjectTypeExtension>>,
 }
 
 impl InputObjectTypeDefinition {
@@ -1812,7 +1887,7 @@ impl InputObjectTypeDefinition {
     }
 
     /// Extensions that apply to this definition
-    pub fn extensions(&self) -> &[InputObjectTypeExtension] {
+    pub fn extensions(&self) -> &[Arc<InputObjectTypeExtension>] {
         &self.extensions
     }
 }
diff --git a/crates/apollo-compiler/src/database/hir_db.rs b/crates/apollo-compiler/src/database/hir_db.rs
index af797bf..4fbece8 100644
--- a/crates/apollo-compiler/src/database/hir_db.rs
+++ b/crates/apollo-compiler/src/database/hir_db.rs
@@ -28,6 +28,9 @@ pub trait HirDatabase: InputDatabase + AstDatabase {
     /// on another compiler.
     fn type_system(&self) -> Arc<TypeSystem>;
 
+    /// Return all the extensions defined in the type system.
+    fn extensions(&self) -> Arc<Vec<TypeExtension>>;
+
     /// Return all the operations defined in a file.
     fn operations(&self, file_id: FileId) -> Arc<Vec<Arc<OperationDefinition>>>;
 
@@ -242,6 +245,22 @@ fn type_system(db: &dyn HirDatabase) -> Arc<TypeSystem> {
     })
 }
 
+fn extensions(db: &dyn HirDatabase) -> Arc<Vec<TypeExtension>> {
+    let mut extensions = vec![];
+    for file_id in db.type_definition_files() {
+        extensions.extend(
+            db.ast(file_id)
+                .document()
+                .syntax()
+                .children()
+                .filter_map(ast::Definition::cast)
+                .filter_map(|def| extension(db, def, file_id)),
+        );
+    }
+
+    Arc::new(extensions)
+}
+
 fn operations(db: &dyn HirDatabase, file_id: FileId) -> Arc<Vec<Arc<OperationDefinition>>> {
     Arc::new(
         db.ast(file_id)
@@ -357,13 +376,14 @@ macro_rules! by_name {
 }
 
 macro_rules! by_name_extensible {
-    ($db: ident, $convert: expr, $convert_extension: expr) => {{
+    ($db: ident, $convert: expr, $extension_type: ident) => {{
         let mut map = by_name!($db, $convert);
-        for ext in type_definitions($db, $convert_extension) {
-            if let Some(def) = map.get_mut(ext.name()) {
-                Arc::get_mut(def).unwrap().extensions.push(ext)
-            } else {
-                // TODO: record orphan extensions for validation purpose?
+        for ext in $db.extensions().iter() {
+            // Orphan or incorrect extensions are reported by validation.
+            if let TypeExtension::$extension_type(ext) = ext {
+                if let Some(def) = map.get_mut(ext.name()) {
+                    Arc::get_mut(def).unwrap().extensions.push(Arc::clone(ext))
+                }
             }
         }
         map
@@ -378,7 +398,7 @@ fn object_types(db: &dyn HirDatabase) -> ByName<ObjectTypeDefinition> {
     Arc::new(by_name_extensible!(
         db,
         object_type_definition,
-        object_type_extension
+        ObjectTypeExtension
     ))
 }
 
@@ -390,7 +410,7 @@ fn scalars(db: &dyn HirDatabase) -> ByName<ScalarTypeDefinition> {
     Arc::new(built_in_scalars(by_name_extensible!(
         db,
         scalar_definition,
-        scalar_extension
+        ScalarTypeExtension
     )))
 }
 
@@ -399,7 +419,7 @@ fn enums(db: &dyn HirDatabase) -> ByName<EnumTypeDefinition> {
         // Panics in `ApolloCompiler` methods ensure `type_definition_files().is_empty()`
         return precomputed.definitions.enums.clone();
     }
-    Arc::new(by_name_extensible!(db, enum_definition, enum_extension))
+    Arc::new(by_name_extensible!(db, enum_definition, EnumTypeExtension))
 }
 
 fn unions(db: &dyn HirDatabase) -> ByName<UnionTypeDefinition> {
@@ -407,14 +427,18 @@ fn unions(db: &dyn HirDatabase) -> ByName<UnionTypeDefinition> {
         // Panics in `ApolloCompiler` methods ensure `type_definition_files().is_empty()`
         return precomputed.definitions.unions.clone();
     }
-    Arc::new(by_name_extensible!(db, union_definition, union_extension))
+    Arc::new(by_name_extensible!(
+        db,
+        union_definition,
+        UnionTypeExtension
+    ))
 }
 
 fn interfaces(db: &dyn HirDatabase) -> ByName<InterfaceTypeDefinition> {
     Arc::new(by_name_extensible!(
         db,
         interface_definition,
-        interface_extension
+        InterfaceTypeExtension
     ))
 }
 
@@ -426,7 +450,7 @@ fn input_objects(db: &dyn HirDatabase) -> ByName<InputObjectTypeDefinition> {
     Arc::new(by_name_extensible!(
         db,
         input_object_definition,
-        input_object_extension
+        InputObjectTypeExtension
     ))
 }
 
@@ -510,14 +534,14 @@ fn schema_definition(
     file_id: FileId,
 ) -> Option<SchemaDefinition> {
     let extensions = type_definitions(db, |_db, def: ast::SchemaExtension, file_id| {
-        Some(SchemaExtension {
+        Some(Arc::new(SchemaExtension {
             directives: directives(def.directives(), file_id),
             root_operation_type_definition: root_operation_type_definition(
                 def.root_operation_type_definitions(),
                 file_id,
             ),
             loc: location(file_id, def.syntax()),
-        })
+        }))
     })
     .collect();
 
@@ -565,14 +589,14 @@ fn object_type_extension(
     _db: &dyn HirDatabase,
     def: ast::ObjectTypeExtension,
     file_id: FileId,
-) -> Option<ObjectTypeExtension> {
-    Some(ObjectTypeExtension {
+) -> Option<Arc<ObjectTypeExtension>> {
+    Some(Arc::new(ObjectTypeExtension {
         directives: directives(def.directives(), file_id),
         name: name(def.name(), file_id)?,
         implements_interfaces: implements_interfaces(def.implements_interfaces(), file_id),
         fields_definition: fields_definition(def.fields_definition(), file_id),
         loc: location(file_id, def.syntax()),
-    })
+    }))
 }
 
 fn scalar_definition(
@@ -601,12 +625,12 @@ fn scalar_extension(
     _db: &dyn HirDatabase,
     def: ast::ScalarTypeExtension,
     file_id: FileId,
-) -> Option<ScalarTypeExtension> {
-    Some(ScalarTypeExtension {
+) -> Option<Arc<ScalarTypeExtension>> {
+    Some(Arc::new(ScalarTypeExtension {
         directives: directives(def.directives(), file_id),
         name: name(def.name(), file_id)?,
         loc: location(file_id, def.syntax()),
-    })
+    }))
 }
 
 fn enum_definition(
@@ -636,13 +660,13 @@ fn enum_extension(
     _db: &dyn HirDatabase,
     def: ast::EnumTypeExtension,
     file_id: FileId,
-) -> Option<EnumTypeExtension> {
-    Some(EnumTypeExtension {
+) -> Option<Arc<EnumTypeExtension>> {
+    Some(Arc::new(EnumTypeExtension {
         directives: directives(def.directives(), file_id),
         name: name(def.name(), file_id)?,
         enum_values_definition: enum_values_definition(def.enum_values_definition(), file_id),
         loc: location(file_id, def.syntax()),
-    })
+    }))
 }
 
 fn enum_values_definition(
@@ -706,13 +730,13 @@ fn union_extension(
     _db: &dyn HirDatabase,
     def: ast::UnionTypeExtension,
     file_id: FileId,
-) -> Option<UnionTypeExtension> {
-    Some(UnionTypeExtension {
+) -> Option<Arc<UnionTypeExtension>> {
+    Some(Arc::new(UnionTypeExtension {
         directives: directives(def.directives(), file_id),
         name: name(def.name(), file_id)?,
         union_members: union_members(def.union_member_types(), file_id),
         loc: location(file_id, def.syntax()),
-    })
+    }))
 }
 
 fn union_members(
@@ -769,14 +793,14 @@ fn interface_extension(
     _db: &dyn HirDatabase,
     def: ast::InterfaceTypeExtension,
     file_id: FileId,
-) -> Option<InterfaceTypeExtension> {
-    Some(InterfaceTypeExtension {
+) -> Option<Arc<InterfaceTypeExtension>> {
+    Some(Arc::new(InterfaceTypeExtension {
         directives: directives(def.directives(), file_id),
         name: name(def.name(), file_id)?,
         implements_interfaces: implements_interfaces(def.implements_interfaces(), file_id),
         fields_definition: fields_definition(def.fields_definition(), file_id),
         loc: location(file_id, def.syntax()),
-    })
+    }))
 }
 
 fn directive_definition(
@@ -831,13 +855,37 @@ fn input_object_extension(
     _db: &dyn HirDatabase,
     def: ast::InputObjectTypeExtension,
     file_id: FileId,
-) -> Option<InputObjectTypeExtension> {
-    Some(InputObjectTypeExtension {
+) -> Option<Arc<InputObjectTypeExtension>> {
+    Some(Arc::new(InputObjectTypeExtension {
         directives: directives(def.directives(), file_id),
         name: name(def.name(), file_id)?,
         input_fields_definition: input_fields_definition(def.input_fields_definition(), file_id),
         loc: location(file_id, def.syntax()),
-    })
+    }))
+}
+
+fn extension(db: &dyn HirDatabase, def: ast::Definition, file_id: FileId) -> Option<TypeExtension> {
+    match def {
+        ast::Definition::ScalarTypeExtension(def) => {
+            scalar_extension(db, def, file_id).map(TypeExtension::ScalarTypeExtension)
+        }
+        ast::Definition::ObjectTypeExtension(def) => {
+            object_type_extension(db, def, file_id).map(TypeExtension::ObjectTypeExtension)
+        }
+        ast::Definition::InterfaceTypeExtension(def) => {
+            interface_extension(db, def, file_id).map(TypeExtension::InterfaceTypeExtension)
+        }
+        ast::Definition::UnionTypeExtension(def) => {
+            union_extension(db, def, file_id).map(TypeExtension::UnionTypeExtension)
+        }
+        ast::Definition::EnumTypeExtension(def) => {
+            enum_extension(db, def, file_id).map(TypeExtension::EnumTypeExtension)
+        }
+        ast::Definition::InputObjectTypeExtension(def) => {
+            input_object_extension(db, def, file_id).map(TypeExtension::InputObjectTypeExtension)
+        }
+        _ => None,
+    }
 }
 
 fn add_object_type_id_to_schema(db: &dyn HirDatabase) -> Arc<Vec<RootOperationTypeDefinition>> {
diff --git a/crates/apollo-compiler/src/diagnostics.rs b/crates/apollo-compiler/src/diagnostics.rs
index 0768158..f1aa73f 100644
--- a/crates/apollo-compiler/src/diagnostics.rs
+++ b/crates/apollo-compiler/src/diagnostics.rs
@@ -184,6 +184,15 @@ pub enum DiagnosticData {
         /// Name of the type not in scope
         name: String,
     },
+    #[error("type extension for `{name}` is the wrong kind")]
+    WrongTypeExtension {
+        /// Name of the type being extended
+        name: String,
+        /// Location of the original definition. This may be None when extending a builtin GraphQL type.
+        definition: Option<DiagnosticLocation>,
+        /// Location of the extension
+        extension: DiagnosticLocation,
+    },
     #[error("{name} directive definition cannot reference itself")]
     RecursiveDefinition { name: String },
     #[error("interface {name} cannot implement itself")]
diff --git a/crates/apollo-compiler/src/validation/extension.rs b/crates/apollo-compiler/src/validation/extension.rs
new file mode 100644
index 0000000..a56efd7
--- /dev/null
+++ b/crates/apollo-compiler/src/validation/extension.rs
@@ -0,0 +1,75 @@
+use crate::{
+    diagnostics::{ApolloDiagnostic, DiagnosticData, Label},
+    hir::{TypeDefinition, TypeExtension},
+    validation::ValidationDatabase,
+};
+
+/// Choose "a" or "an" depending on the first letter of `ty`. Only supports capital letters.
+fn particle(ty: &str) -> &str {
+    match ty.chars().next() {
+        Some('A' | 'E' | 'I' | 'O' | 'U') => "an",
+        _ => "a",
+    }
+}
+
+pub fn validate_extensions(db: &dyn ValidationDatabase) -> Vec<ApolloDiagnostic> {
+    let mut diagnostics = Vec::new();
+
+    for extension in db.extensions().iter() {
+        let Some(definition) = db.find_type_definition_by_name(extension.name().into()) else {
+            diagnostics.push(
+                ApolloDiagnostic::new(db, extension.loc().into(), DiagnosticData::UndefinedDefinition {
+                    name: extension.name().into(),
+                })
+                .label(Label::new(extension.name_src().loc.unwrap(), format!("`{}` type definition does not exist", extension.name()))),
+            );
+
+            continue;
+        };
+
+        match (definition, extension) {
+            (TypeDefinition::ScalarTypeDefinition(_), TypeExtension::ScalarTypeExtension(_))
+            | (TypeDefinition::ObjectTypeDefinition(_), TypeExtension::ObjectTypeExtension(_))
+            | (
+                TypeDefinition::InterfaceTypeDefinition(_),
+                TypeExtension::InterfaceTypeExtension(_),
+            )
+            | (TypeDefinition::UnionTypeDefinition(_), TypeExtension::UnionTypeExtension(_))
+            | (TypeDefinition::EnumTypeDefinition(_), TypeExtension::EnumTypeExtension(_))
+            | (
+                TypeDefinition::InputObjectTypeDefinition(_),
+                TypeExtension::InputObjectTypeExtension(_),
+            ) => {
+                // Definition/extension kinds are the same
+            }
+            (definition, extension) => {
+                let mut diagnostic = ApolloDiagnostic::new(
+                    db,
+                    extension.loc().into(),
+                    DiagnosticData::WrongTypeExtension {
+                        name: extension.name().into(),
+                        definition: definition.loc().map(|loc| loc.into()),
+                        extension: extension.loc().into(),
+                    },
+                )
+                .label(Label::new(
+                    extension.loc(),
+                    format!(
+                        "adding {} {}, but `{}` is {} {}",
+                        particle(extension.kind()),
+                        extension.kind(),
+                        extension.name(),
+                        particle(definition.kind()),
+                        definition.kind()
+                    ),
+                ));
+                if let Some(def_loc) = definition.loc() {
+                    diagnostic = diagnostic.label(Label::new(def_loc, "original type defined here"))
+                }
+                diagnostics.push(diagnostic);
+            }
+        }
+    }
+
+    diagnostics
+}
diff --git a/crates/apollo-compiler/src/validation/mod.rs b/crates/apollo-compiler/src/validation/mod.rs
index 31e2be3..c7fabe5 100644
--- a/crates/apollo-compiler/src/validation/mod.rs
+++ b/crates/apollo-compiler/src/validation/mod.rs
@@ -3,6 +3,7 @@ mod validation_db;
 mod argument;
 mod directive;
 mod enum_;
+mod extension;
 mod field;
 mod fragment;
 mod input_object;
diff --git a/crates/apollo-compiler/src/validation/validation_db.rs b/crates/apollo-compiler/src/validation/validation_db.rs
index 29c8d3e..84af812 100644
--- a/crates/apollo-compiler/src/validation/validation_db.rs
+++ b/crates/apollo-compiler/src/validation/validation_db.rs
@@ -6,8 +6,8 @@ use crate::{
     diagnostics::{ApolloDiagnostic, DiagnosticData, Label},
     hir::*,
     validation::{
-        argument, directive, enum_, fragment, input_object, interface, object, operation, scalar,
-        schema, selection, union_, variable,
+        argument, directive, enum_, extension, fragment, input_object, interface, object,
+        operation, scalar, schema, selection, union_, variable,
     },
     AstDatabase, FileId, HirDatabase, InputDatabase,
 };
@@ -109,6 +109,9 @@ pub trait ValidationDatabase:
     #[salsa::invoke(object::validate_object_type_definitions)]
     fn validate_object_type_definitions(&self) -> Vec<ApolloDiagnostic>;
 
+    #[salsa::invoke(extension::validate_extensions)]
+    fn validate_extensions(&self) -> Vec<ApolloDiagnostic>;
+
     #[salsa::invoke(operation::validate_subscription_operations)]
     fn validate_subscription_operations(
         &self,
@@ -295,6 +298,8 @@ pub fn validate_type_system(db: &dyn ValidationDatabase) -> Vec<ApolloDiagnostic
     diagnostics.extend(db.validate_input_object_definitions());
     diagnostics.extend(db.validate_object_type_definitions());
 
+    diagnostics.extend(db.validate_extensions());
+
     diagnostics
 }
 


Test patch:
diff --git a/crates/apollo-compiler/test_data/diagnostics/0063_extension_orphan.graphql b/crates/apollo-compiler/test_data/diagnostics/0063_extension_orphan.graphql
new file mode 100644
index 0000000..d6e2663
--- /dev/null
+++ b/crates/apollo-compiler/test_data/diagnostics/0063_extension_orphan.graphql
@@ -0,0 +1,19 @@
+type Query {
+  x: Int
+}
+
+extend scalar A @deprecated(reason: "Use B instead")
+extend type B { field: Int! }
+extend schema {
+  query: Query
+}
+extend union C = String
+extend interface D {
+  field: Int
+}
+extend input E {
+  field: Int
+}
+extend enum F {
+  MEMBER
+}
diff --git a/crates/apollo-compiler/test_data/diagnostics/0063_extension_orphan.txt b/crates/apollo-compiler/test_data/diagnostics/0063_extension_orphan.txt
new file mode 100644
index 0000000..a7bf950
--- /dev/null
+++ b/crates/apollo-compiler/test_data/diagnostics/0063_extension_orphan.txt
@@ -0,0 +1,170 @@
+[
+    ApolloDiagnostic {
+        cache: {
+            63: "0063_extension_orphan.graphql",
+        },
+        location: DiagnosticLocation {
+            file_id: FileId {
+                id: 63,
+            },
+            offset: 25,
+            length: 52,
+        },
+        labels: [
+            Label {
+                location: DiagnosticLocation {
+                    file_id: FileId {
+                        id: 63,
+                    },
+                    offset: 39,
+                    length: 1,
+                },
+                text: "`A` type definition does not exist",
+            },
+        ],
+        help: None,
+        data: UndefinedDefinition {
+            name: "A",
+        },
+    },
+    ApolloDiagnostic {
+        cache: {
+            63: "0063_extension_orphan.graphql",
+        },
+        location: DiagnosticLocation {
+            file_id: FileId {
+                id: 63,
+            },
+            offset: 78,
+            length: 29,
+        },
+        labels: [
+            Label {
+                location: DiagnosticLocation {
+                    file_id: FileId {
+                        id: 63,
+                    },
+                    offset: 90,
+                    length: 1,
+                },
+                text: "`B` type definition does not exist",
+            },
+        ],
+        help: None,
+        data: UndefinedDefinition {
+            name: "B",
+        },
+    },
+    ApolloDiagnostic {
+        cache: {
+            63: "0063_extension_orphan.graphql",
+        },
+        location: DiagnosticLocation {
+            file_id: FileId {
+                id: 63,
+            },
+            offset: 141,
+            length: 23,
+        },
+        labels: [
+            Label {
+                location: DiagnosticLocation {
+                    file_id: FileId {
+                        id: 63,
+                    },
+                    offset: 154,
+                    length: 1,
+                },
+                text: "`C` type definition does not exist",
+            },
+        ],
+        help: None,
+        data: UndefinedDefinition {
+            name: "C",
+        },
+    },
+    ApolloDiagnostic {
+        cache: {
+            63: "0063_extension_orphan.graphql",
+        },
+        location: DiagnosticLocation {
+            file_id: FileId {
+                id: 63,
+            },
+            offset: 165,
+            length: 35,
+        },
+        labels: [
+            Label {
+                location: DiagnosticLocation {
+                    file_id: FileId {
+                        id: 63,
+                    },
+                    offset: 182,
+                    length: 1,
+                },
+                text: "`D` type definition does not exist",
+            },
+        ],
+        help: None,
+        data: UndefinedDefinition {
+            name: "D",
+        },
+    },
+    ApolloDiagnostic {
+        cache: {
+            63: "0063_extension_orphan.graphql",
+        },
+        location: DiagnosticLocation {
+            file_id: FileId {
+                id: 63,
+            },
+            offset: 201,
+            length: 31,
+        },
+        labels: [
+            Label {
+                location: DiagnosticLocation {
+                    file_id: FileId {
+                        id: 63,
+                    },
+                    offset: 214,
+                    length: 1,
+                },
+                text: "`E` type definition does not exist",
+            },
+        ],
+        help: None,
+        data: UndefinedDefinition {
+            name: "E",
+        },
+    },
+    ApolloDiagnostic {
+        cache: {
+            63: "0063_extension_orphan.graphql",
+        },
+        location: DiagnosticLocation {
+            file_id: FileId {
+                id: 63,
+            },
+            offset: 233,
+            length: 26,
+        },
+        labels: [
+            Label {
+                location: DiagnosticLocation {
+                    file_id: FileId {
+                        id: 63,
+                    },
+                    offset: 245,
+                    length: 1,
+                },
+                text: "`F` type definition does not exist",
+            },
+        ],
+        help: None,
+        data: UndefinedDefinition {
+            name: "F",
+        },
+    },
+]
diff --git a/crates/apollo-compiler/test_data/diagnostics/0064_extension_wrong_type.graphql b/crates/apollo-compiler/test_data/diagnostics/0064_extension_wrong_type.graphql
new file mode 100644
index 0000000..aa11adf
--- /dev/null
+++ b/crates/apollo-compiler/test_data/diagnostics/0064_extension_wrong_type.graphql
@@ -0,0 +1,28 @@
+scalar Scalar @specifiedBy(url: "https://apollographql.com")
+interface Intf {
+  field: Int,
+}
+type Object {
+  field: Int,
+}
+union Union = Scalar | Object
+input Input {
+  field: Int,
+}
+enum Enum {
+  MEMBER
+}
+
+extend union Scalar = String
+extend interface Object {
+  field2: Scalar,
+}
+extend enum Intf {
+  MEMBER_2
+}
+extend type Input {
+  field2: Scalar,
+}
+extend input Enum {
+  field2: Scalar,
+}
diff --git a/crates/apollo-compiler/test_data/diagnostics/0064_extension_wrong_type.txt b/crates/apollo-compiler/test_data/diagnostics/0064_extension_wrong_type.txt
new file mode 100644
index 0000000..4fbf90d
--- /dev/null
+++ b/crates/apollo-compiler/test_data/diagnostics/0064_extension_wrong_type.txt
@@ -0,0 +1,303 @@
+[
+    ApolloDiagnostic {
+        cache: {
+            64: "0064_extension_wrong_type.graphql",
+        },
+        location: DiagnosticLocation {
+            file_id: FileId {
+                id: 64,
+            },
+            offset: 138,
+            length: 6,
+        },
+        labels: [
+            Label {
+                location: DiagnosticLocation {
+                    file_id: FileId {
+                        id: 64,
+                    },
+                    offset: 138,
+                    length: 6,
+                },
+                text: "This is of `ScalarTypeDefinition` type",
+            },
+        ],
+        help: Some(
+            "Union members must be of base Object Type.",
+        ),
+        data: ObjectType {
+            name: "Scalar",
+            ty: "ScalarTypeDefinition",
+        },
+    },
+    ApolloDiagnostic {
+        cache: {
+            64: "0064_extension_wrong_type.graphql",
+        },
+        location: DiagnosticLocation {
+            file_id: FileId {
+                id: 64,
+            },
+            offset: 208,
+            length: 28,
+        },
+        labels: [
+            Label {
+                location: DiagnosticLocation {
+                    file_id: FileId {
+                        id: 64,
+                    },
+                    offset: 208,
+                    length: 28,
+                },
+                text: "adding an UnionTypeExtension, but `Scalar` is a ScalarTypeDefinition",
+            },
+            Label {
+                location: DiagnosticLocation {
+                    file_id: FileId {
+                        id: 64,
+                    },
+                    offset: 0,
+                    length: 60,
+                },
+                text: "original type defined here",
+            },
+        ],
+        help: None,
+        data: WrongTypeExtension {
+            name: "Scalar",
+            definition: Some(
+                DiagnosticLocation {
+                    file_id: FileId {
+                        id: 64,
+                    },
+                    offset: 0,
+                    length: 60,
+                },
+            ),
+            extension: DiagnosticLocation {
+                file_id: FileId {
+                    id: 64,
+                },
+                offset: 208,
+                length: 28,
+            },
+        },
+    },
+    ApolloDiagnostic {
+        cache: {
+            64: "0064_extension_wrong_type.graphql",
+        },
+        location: DiagnosticLocation {
+            file_id: FileId {
+                id: 64,
+            },
+            offset: 237,
+            length: 45,
+        },
+        labels: [
+            Label {
+                location: DiagnosticLocation {
+                    file_id: FileId {
+                        id: 64,
+                    },
+                    offset: 237,
+                    length: 45,
+                },
+                text: "adding an InterfaceTypeExtension, but `Object` is an ObjectTypeDefinition",
+            },
+            Label {
+                location: DiagnosticLocation {
+                    file_id: FileId {
+                        id: 64,
+                    },
+                    offset: 94,
+                    length: 29,
+                },
+                text: "original type defined here",
+            },
+        ],
+        help: None,
+        data: WrongTypeExtension {
+            name: "Object",
+            definition: Some(
+                DiagnosticLocation {
+                    file_id: FileId {
+                        id: 64,
+                    },
+                    offset: 94,
+                    length: 29,
+                },
+            ),
+            extension: DiagnosticLocation {
+                file_id: FileId {
+                    id: 64,
+                },
+                offset: 237,
+                length: 45,
+            },
+        },
+    },
+    ApolloDiagnostic {
+        cache: {
+            64: "0064_extension_wrong_type.graphql",
+        },
+        location: DiagnosticLocation {
+            file_id: FileId {
+                id: 64,
+            },
+            offset: 283,
+            length: 31,
+        },
+        labels: [
+            Label {
+                location: DiagnosticLocation {
+                    file_id: FileId {
+                        id: 64,
+                    },
+                    offset: 283,
+                    length: 31,
+                },
+                text: "adding an EnumTypeExtension, but `Intf` is an InterfaceTypeDefinition",
+            },
+            Label {
+                location: DiagnosticLocation {
+                    file_id: FileId {
+                        id: 64,
+                    },
+                    offset: 61,
+                    length: 32,
+                },
+                text: "original type defined here",
+            },
+        ],
+        help: None,
+        data: WrongTypeExtension {
+            name: "Intf",
+            definition: Some(
+                DiagnosticLocation {
+                    file_id: FileId {
+                        id: 64,
+                    },
+                    offset: 61,
+                    length: 32,
+                },
+            ),
+            extension: DiagnosticLocation {
+                file_id: FileId {
+                    id: 64,
+                },
+                offset: 283,
+                length: 31,
+            },
+        },
+    },
+    ApolloDiagnostic {
+        cache: {
+            64: "0064_extension_wrong_type.graphql",
+        },
+        location: DiagnosticLocation {
+            file_id: FileId {
+                id: 64,
+            },
+            offset: 315,
+            length: 39,
+        },
+        labels: [
+            Label {
+                location: DiagnosticLocation {
+                    file_id: FileId {
+                        id: 64,
+                    },
+                    offset: 315,
+                    length: 39,
+                },
+                text: "adding an ObjectTypeExtension, but `Input` is an InputObjectTypeDefinition",
+            },
+            Label {
+                location: DiagnosticLocation {
+                    file_id: FileId {
+                        id: 64,
+                    },
+                    offset: 154,
+                    length: 29,
+                },
+                text: "original type defined here",
+            },
+        ],
+        help: None,
+        data: WrongTypeExtension {
+            name: "Input",
+            definition: Some(
+                DiagnosticLocation {
+                    file_id: FileId {
+                        id: 64,
+                    },
+                    offset: 154,
+                    length: 29,
+                },
+            ),
+            extension: DiagnosticLocation {
+                file_id: FileId {
+                    id: 64,
+                },
+                offset: 315,
+                length: 39,
+            },
+        },
+    },
+    ApolloDiagnostic {
+        cache: {
+            64: "0064_extension_wrong_type.graphql",
+        },
+        location: DiagnosticLocation {
+            file_id: FileId {
+                id: 64,
+            },
+            offset: 355,
+            length: 39,
+        },
+        labels: [
+            Label {
+                location: DiagnosticLocation {
+                    file_id: FileId {
+                        id: 64,
+                    },
+                    offset: 355,
+                    length: 39,
+                },
+                text: "adding an InputObjectTypeExtension, but `Enum` is an EnumTypeDefinition",
+            },
+            Label {
+                location: DiagnosticLocation {
+                    file_id: FileId {
+                        id: 64,
+                    },
+                    offset: 184,
+                    length: 22,
+                },
+                text: "original type defined here",
+            },
+        ],
+        help: None,
+        data: WrongTypeExtension {
+            name: "Enum",
+            definition: Some(
+                DiagnosticLocation {
+                    file_id: FileId {
+                        id: 64,
+                    },
+                    offset: 184,
+                    length: 22,
+                },
+            ),
+            extension: DiagnosticLocation {
+                file_id: FileId {
+                    id: 64,
+                },
+                offset: 355,
+                length: 39,
+            },
+        },
+    },
+]
diff --git a/crates/apollo-compiler/test_data/ok/0019_extensions.graphql b/crates/apollo-compiler/test_data/ok/0019_extensions.graphql
new file mode 100644
index 0000000..35fa538
--- /dev/null
+++ b/crates/apollo-compiler/test_data/ok/0019_extensions.graphql
@@ -0,0 +1,34 @@
+scalar Scalar @specifiedBy(url: "https://apollographql.com")
+
+type Object implements Intf {
+  field: Int,
+}
+
+interface Intf {
+  field: Int,
+}
+
+input Input {
+  field: Enum,
+}
+enum Enum {
+  MEMBER,
+}
+
+extend scalar Scalar @deprecated(reason: "do something else")
+extend interface Intf {
+  field2: Scalar,
+}
+extend type Object {
+  field2: Scalar,
+}
+extend enum Enum {
+  MEMBER_2,
+}
+extend input Input {
+  field2: Int,
+}
+
+type Query {
+  object: Object,
+}
diff --git a/crates/apollo-compiler/test_data/ok/0019_extensions.txt b/crates/apollo-compiler/test_data/ok/0019_extensions.txt
new file mode 100644
index 0000000..5425304
--- /dev/null
+++ b/crates/apollo-compiler/test_data/ok/0019_extensions.txt
@@ -0,0 +1,244 @@
+- DOCUMENT@0..451
+    - SCALAR_TYPE_DEFINITION@0..60
+        - scalar_KW@0..6 "scalar"
+        - WHITESPACE@6..7 " "
+        - NAME@7..13
+            - IDENT@7..13 "Scalar"
+        - WHITESPACE@13..14 " "
+        - DIRECTIVES@14..60
+            - DIRECTIVE@14..60
+                - AT@14..15 "@"
+                - NAME@15..26
+                    - IDENT@15..26 "specifiedBy"
+                - ARGUMENTS@26..60
+                    - L_PAREN@26..27 "("
+                    - ARGUMENT@27..59
+                        - NAME@27..30
+                            - IDENT@27..30 "url"
+                        - COLON@30..31 ":"
+                        - WHITESPACE@31..32 " "
+                        - STRING_VALUE@32..59
+                            - STRING@32..59 "\"https://apollographql.com\""
+                    - R_PAREN@59..60 ")"
+    - WHITESPACE@60..62 "\n\n"
+    - OBJECT_TYPE_DEFINITION@62..107
+        - type_KW@62..66 "type"
+        - WHITESPACE@66..67 " "
+        - NAME@67..73
+            - IDENT@67..73 "Object"
+        - WHITESPACE@73..74 " "
+        - IMPLEMENTS_INTERFACES@74..89
+            - implements_KW@74..84 "implements"
+            - WHITESPACE@84..85 " "
+            - NAMED_TYPE@85..89
+                - NAME@85..89
+                    - IDENT@85..89 "Intf"
+        - WHITESPACE@89..90 " "
+        - FIELDS_DEFINITION@90..107
+            - L_CURLY@90..91 "{"
+            - WHITESPACE@91..94 "\n  "
+            - FIELD_DEFINITION@94..104
+                - NAME@94..99
+                    - IDENT@94..99 "field"
+                - COLON@99..100 ":"
+                - WHITESPACE@100..101 " "
+                - NAMED_TYPE@101..104
+                    - NAME@101..104
+                        - IDENT@101..104 "Int"
+            - COMMA@104..105 ","
+            - WHITESPACE@105..106 "\n"
+            - R_CURLY@106..107 "}"
+    - WHITESPACE@107..109 "\n\n"
+    - INTERFACE_TYPE_DEFINITION@109..141
+        - interface_KW@109..118 "interface"
+        - WHITESPACE@118..119 " "
+        - NAME@119..123
+            - IDENT@119..123 "Intf"
+        - WHITESPACE@123..124 " "
+        - FIELDS_DEFINITION@124..141
+            - L_CURLY@124..125 "{"
+            - WHITESPACE@125..128 "\n  "
+            - FIELD_DEFINITION@128..138
+                - NAME@128..133
+                    - IDENT@128..133 "field"
+                - COLON@133..134 ":"
+                - WHITESPACE@134..135 " "
+                - NAMED_TYPE@135..138
+                    - NAME@135..138
+                        - IDENT@135..138 "Int"
+            - COMMA@138..139 ","
+            - WHITESPACE@139..140 "\n"
+            - R_CURLY@140..141 "}"
+    - WHITESPACE@141..143 "\n\n"
+    - INPUT_OBJECT_TYPE_DEFINITION@143..173
+        - input_KW@143..148 "input"
+        - WHITESPACE@148..149 " "
+        - NAME@149..154
+            - IDENT@149..154 "Input"
+        - WHITESPACE@154..155 " "
+        - INPUT_FIELDS_DEFINITION@155..173
+            - L_CURLY@155..156 "{"
+            - WHITESPACE@156..159 "\n  "
+            - INPUT_VALUE_DEFINITION@159..170
+                - NAME@159..164
+                    - IDENT@159..164 "field"
+                - COLON@164..165 ":"
+                - WHITESPACE@165..166 " "
+                - NAMED_TYPE@166..170
+                    - NAME@166..170
+                        - IDENT@166..170 "Enum"
+            - COMMA@170..171 ","
+            - WHITESPACE@171..172 "\n"
+            - R_CURLY@172..173 "}"
+    - WHITESPACE@173..174 "\n"
+    - ENUM_TYPE_DEFINITION@174..197
+        - enum_KW@174..178 "enum"
+        - WHITESPACE@178..179 " "
+        - NAME@179..183
+            - IDENT@179..183 "Enum"
+        - WHITESPACE@183..184 " "
+        - ENUM_VALUES_DEFINITION@184..197
+            - L_CURLY@184..185 "{"
+            - WHITESPACE@185..188 "\n  "
+            - ENUM_VALUE_DEFINITION@188..194
+                - ENUM_VALUE@188..194
+                    - NAME@188..194
+                        - IDENT@188..194 "MEMBER"
+            - COMMA@194..195 ","
+            - WHITESPACE@195..196 "\n"
+            - R_CURLY@196..197 "}"
+    - WHITESPACE@197..199 "\n\n"
+    - SCALAR_TYPE_EXTENSION@199..260
+        - extend_KW@199..205 "extend"
+        - WHITESPACE@205..206 " "
+        - scalar_KW@206..212 "scalar"
+        - WHITESPACE@212..213 " "
+        - NAME@213..219
+            - IDENT@213..219 "Scalar"
+        - WHITESPACE@219..220 " "
+        - DIRECTIVES@220..260
+            - DIRECTIVE@220..260
+                - AT@220..221 "@"
+                - NAME@221..231
+                    - IDENT@221..231 "deprecated"
+                - ARGUMENTS@231..260
+                    - L_PAREN@231..232 "("
+                    - ARGUMENT@232..259
+                        - NAME@232..238
+                            - IDENT@232..238 "reason"
+                        - COLON@238..239 ":"
+                        - WHITESPACE@239..240 " "
+                        - STRING_VALUE@240..259
+                            - STRING@240..259 "\"do something else\""
+                    - R_PAREN@259..260 ")"
+    - WHITESPACE@260..261 "\n"
+    - INTERFACE_TYPE_EXTENSION@261..304
+        - extend_KW@261..267 "extend"
+        - WHITESPACE@267..268 " "
+        - interface_KW@268..277 "interface"
+        - WHITESPACE@277..278 " "
+        - NAME@278..282
+            - IDENT@278..282 "Intf"
+        - WHITESPACE@282..283 " "
+        - FIELDS_DEFINITION@283..304
+            - L_CURLY@283..284 "{"
+            - WHITESPACE@284..287 "\n  "
+            - FIELD_DEFINITION@287..301
+                - NAME@287..293
+                    - IDENT@287..293 "field2"
+                - COLON@293..294 ":"
+                - WHITESPACE@294..295 " "
+                - NAMED_TYPE@295..301
+                    - NAME@295..301
+                        - IDENT@295..301 "Scalar"
+            - COMMA@301..302 ","
+            - WHITESPACE@302..303 "\n"
+            - R_CURLY@303..304 "}"
+    - WHITESPACE@304..305 "\n"
+    - OBJECT_TYPE_EXTENSION@305..345
+        - extend_KW@305..311 "extend"
+        - WHITESPACE@311..312 " "
+        - type_KW@312..316 "type"
+        - WHITESPACE@316..317 " "
+        - NAME@317..323
+            - IDENT@317..323 "Object"
+        - WHITESPACE@323..324 " "
+        - FIELDS_DEFINITION@324..345
+            - L_CURLY@324..325 "{"
+            - WHITESPACE@325..328 "\n  "
+            - FIELD_DEFINITION@328..342
+                - NAME@328..334
+                    - IDENT@328..334 "field2"
+                - COLON@334..335 ":"
+                - WHITESPACE@335..336 " "
+                - NAMED_TYPE@336..342
+                    - NAME@336..342
+                        - IDENT@336..342 "Scalar"
+            - COMMA@342..343 ","
+            - WHITESPACE@343..344 "\n"
+            - R_CURLY@344..345 "}"
+    - WHITESPACE@345..346 "\n"
+    - ENUM_TYPE_EXTENSION@346..378
+        - extend_KW@346..352 "extend"
+        - WHITESPACE@352..353 " "
+        - enum_KW@353..357 "enum"
+        - WHITESPACE@357..358 " "
+        - NAME@358..362
+            - IDENT@358..362 "Enum"
+        - WHITESPACE@362..363 " "
+        - ENUM_VALUES_DEFINITION@363..378
+            - L_CURLY@363..364 "{"
+            - WHITESPACE@364..367 "\n  "
+            - ENUM_VALUE_DEFINITION@367..375
+                - ENUM_VALUE@367..375
+                    - NAME@367..375
+                        - IDENT@367..375 "MEMBER_2"
+            - COMMA@375..376 ","
+            - WHITESPACE@376..377 "\n"
+            - R_CURLY@377..378 "}"
+    - WHITESPACE@378..379 "\n"
+    - INPUT_OBJECT_TYPE_EXTENSION@379..416
+        - extend_KW@379..385 "extend"
+        - WHITESPACE@385..386 " "
+        - input_KW@386..391 "input"
+        - WHITESPACE@391..392 " "
+        - NAME@392..397
+            - IDENT@392..397 "Input"
+        - WHITESPACE@397..398 " "
+        - INPUT_FIELDS_DEFINITION@398..416
+            - L_CURLY@398..399 "{"
+            - WHITESPACE@399..402 "\n  "
+            - INPUT_VALUE_DEFINITION@402..413
+                - NAME@402..408
+                    - IDENT@402..408 "field2"
+                - COLON@408..409 ":"
+                - WHITESPACE@409..410 " "
+                - NAMED_TYPE@410..413
+                    - NAME@410..413
+                        - IDENT@410..413 "Int"
+            - COMMA@413..414 ","
+            - WHITESPACE@414..415 "\n"
+            - R_CURLY@415..416 "}"
+    - WHITESPACE@416..418 "\n\n"
+    - OBJECT_TYPE_DEFINITION@418..450
+        - type_KW@418..422 "type"
+        - WHITESPACE@422..423 " "
+        - NAME@423..428
+            - IDENT@423..428 "Query"
+        - WHITESPACE@428..429 " "
+        - FIELDS_DEFINITION@429..450
+            - L_CURLY@429..430 "{"
+            - WHITESPACE@430..433 "\n  "
+            - FIELD_DEFINITION@433..447
+                - NAME@433..439
+                    - IDENT@433..439 "object"
+                - COLON@439..440 ":"
+                - WHITESPACE@440..441 " "
+                - NAMED_TYPE@441..447
+                    - NAME@441..447
+                        - IDENT@441..447 "Object"
+            - COMMA@447..448 ","
+            - WHITESPACE@448..449 "\n"
+            - R_CURLY@449..450 "}"
+    - WHITESPACE@450..451 "\n"
+recursion limit: 4096, high: 0