Repository: apollographql/apollo-rs
Pull request #486 (2023-03-17 15:02:40)

docs(compiler): changelog for implicit extensions in HIR
Fixes https://github.com/apollographql/apollo-rs/issues/468, together with https://github.com/apollographql/apollo-rs/pull/481, https://github.com/apollographql/apollo-rs/pull/482, and https://github.com/apollographql/apollo-rs/pull/484.


Patch:
diff --git a/crates/apollo-compiler/CHANGELOG.md b/crates/apollo-compiler/CHANGELOG.md
index 277f120..f24a2d0 100644
--- a/crates/apollo-compiler/CHANGELOG.md
+++ b/crates/apollo-compiler/CHANGELOG.md
@@ -17,6 +17,93 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
 ## Maintenance
 
 ## Documentation -->
+
+# [0.7.0] (unreleased) - 2023-mm-dd
+
+> Important: X breaking changes below, indicated by **BREAKING**
+
+## BREAKING
+
+### Extensions are applied implicitly in HIR, [SimonSapin] in [pull/481], [pull/482], and [pull/484]
+
+Adding a GraphQL type extension is similar to modifying a type.
+This release makes a number of breaking changes to API signatures and behavior
+so these modifications are be accounted for implicitly.
+For example, `interface.field(name)` may now return a field
+from an `extend interface` extension as well as from the original `interface` definition.
+We expect that most callers don’t need to tell the difference.
+For callers that do, methods with a `self_` prefix are added (or renamed)
+for accessing components of a definition itself as opposed to added by an extension.
+
+Renamed:
+
+* `SchemaDefinition::root_operation_type_definition` → `self_root_operations`
+* `ObjectTypeDefinition::fields_definition` → `self_fields`
+* `InterfaceTypeDefinition::fields_definition` → `self_fields`
+* `InputObjectTypeDefinition::input_fields_definition` → `self_fields`
+* `ObjectTypeDefinition::implements_interfaces` → `self_implements_interfaces`
+* `InterfaceTypeDefinition::implements_interfaces` → `self_implements_interfaces`
+* `UnionTypeDefinition::union_members` → `self_members`
+* `EnumTypeDefinition::enum_values_definition` → `self_values`
+* `TypeDefiniton::directives` → `self_directives`
+* `SchemaDefiniton::directives` → `self_directives`
+* `EnumTypeDefiniton::directives` → `self_directives`
+* `UnionTypeDefiniton::directives` → `self_directives`
+* `ObjectTypeDefiniton::directives` → `self_directives`
+* `ScalarTypeDefiniton::directives` → `self_directives`
+* `InterfaceTypeDefiniton::directives` → `self_directives`
+* `InputObjectTypeDefiniton::directives` → `self_directives`
+
+Names freed by the above, redefined with new behavior (to consider extensions) and signature:
+
+* `ObjectTypeDefinition::implements_interfaces() -> impl Iterator`
+* `InterfaceTypeDefinition::implements_interfaces() -> impl Iterator`
+* `TypeDefiniton::directives() -> impl Iterator`
+* `SchemaDefiniton::directives() -> impl Iterator`
+* `EnumTypeDefiniton::directives() -> impl Iterator`
+* `UnionTypeDefiniton::directives() -> impl Iterator`
+* `ObjectTypeDefiniton::directives() -> impl Iterator`
+* `ScalarTypeDefiniton::directives() -> impl Iterator`
+* `InterfaceTypeDefiniton::directives() -> impl Iterator`
+* `InputObjectTypeDefiniton::directives() -> impl Iterator`
+
+Behavior and signature change (return the name of an object type instead of its definition):
+
+* `SchemaDefinition::query() -> Option<&str>`
+* `SchemaDefinition::mutation() -> Option<&str>`
+* `SchemaDefinition::subscription() -> Option<&str>`
+
+Behavior changed to consider extensions (no signature change):
+
+* `TypeDefinition::field(name) -> Option`
+* `ObjectTypeDefinition::field(name) -> Option`
+* `InterfaceTypeDefinition::field(name) -> Option`
+
+New methods (that consider extensions):
+
+* `SchemaDefinition::root_operations() -> impl Iterator`
+* `ObjectTypeDefinition::fields() -> impl Iterator`
+* `ObjectTypeDefinition::implements_interface(name) -> bool`
+* `InterfaceTypeDefinition::fields() -> impl Iterator`
+* `InterfaceTypeDefinition::implements_interface(name) -> bool`
+* `InputObjectTypeDefinition::self_fields() -> &[_]`
+* `InputObjectTypeDefinition::fields() -> impl Iterator`
+* `InputObjectTypeDefinition::field(name) -> Option`
+* `UnionTypeDefinition::members() -> impl Iterator`
+* `UnionTypeDefinition::has_member(name) -> bool`
+* `EnumTypeDefinition::values() -> impl Iterator`
+* `EnumTypeDefinition::value(name) -> Option`
+
+New methods, for every type that has a `directives` method:
+
+* `directive_by_name(name) -> Option`
+* `directives_by_name(name) -> impl Iterator`
+
+[SimonSapin]: https://github.com/SimonSapin
+[pull/481]: https://github.com/apollographql/apollo-rs/pull/481
+[pull/482]: https://github.com/apollographql/apollo-rs/pull/482
+[pull/484]: https://github.com/apollographql/apollo-rs/pull/484
+
 # [0.6.0](https://crates.io/crates/apollo-compiler/0.6.0) - 2023-01-18
 
 This release has a few breaking changes as we try to standardise APIs across the
diff --git a/crates/apollo-compiler/src/database/document.rs b/crates/apollo-compiler/src/database/document.rs
index 15f6fff..953c9b5 100644
--- a/crates/apollo-compiler/src/database/document.rs
+++ b/crates/apollo-compiler/src/database/document.rs
@@ -310,7 +310,7 @@ pub(crate) fn subtype_map(db: &dyn HirDatabase) -> Arc<HashMap<String, HashSet<S
             .insert(value.to_owned())
     };
     for (name, definition) in &*db.object_types() {
-        for implements in definition.implements_interfaces() {
+        for implements in definition.self_implements_interfaces() {
             add(implements.interface(), name);
         }
         for extension in definition.extensions() {
@@ -320,7 +320,7 @@ pub(crate) fn subtype_map(db: &dyn HirDatabase) -> Arc<HashMap<String, HashSet<S
         }
     }
     for (name, definition) in &*db.interfaces() {
-        for implements in definition.implements_interfaces() {
+        for implements in definition.self_implements_interfaces() {
             add(implements.interface(), name);
         }
         for extension in definition.extensions() {
@@ -330,7 +330,7 @@ pub(crate) fn subtype_map(db: &dyn HirDatabase) -> Arc<HashMap<String, HashSet<S
         }
     }
     for (name, definition) in &*db.unions() {
-        for member in definition.union_members() {
+        for member in definition.self_members() {
             add(name, member.name());
         }
         for extension in definition.extensions() {
diff --git a/crates/apollo-compiler/src/database/hir.rs b/crates/apollo-compiler/src/database/hir.rs
index ff992e5..2c50607 100644
--- a/crates/apollo-compiler/src/database/hir.rs
+++ b/crates/apollo-compiler/src/database/hir.rs
@@ -1,6 +1,6 @@
 use std::{
     collections::{HashMap, HashSet},
-    fmt,
+    fmt, hash,
     sync::Arc,
 };
 
@@ -446,11 +446,13 @@ impl OperationDefinition {
 
     /// Get operation's definition object type.
     pub fn object_type(&self, db: &dyn HirDatabase) -> Option<Arc<ObjectTypeDefinition>> {
-        match self.operation_ty {
-            OperationType::Query => db.schema().query(db),
-            OperationType::Mutation => db.schema().mutation(db),
-            OperationType::Subscription => db.schema().subscription(db),
-        }
+        let schema = db.schema();
+        let name = match self.operation_ty {
+            OperationType::Query => schema.query()?,
+            OperationType::Mutation => schema.mutation()?,
+            OperationType::Subscription => schema.subscription()?,
+        };
+        db.object_types().get(name).cloned()
     }
 
     /// Get a reference to the operation definition's variables.
@@ -554,13 +556,19 @@ impl OperationType {
     }
 }
 
+impl From<OperationType> for &'static str {
+    fn from(ty: OperationType) -> &'static str {
+        match ty {
+            OperationType::Query => "Query",
+            OperationType::Mutation => "Mutation",
+            OperationType::Subscription => "Subscription",
+        }
+    }
+}
+
 impl fmt::Display for OperationType {
     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-        match self {
-            OperationType::Query => write!(f, "Query"),
-            OperationType::Mutation => write!(f, "Mutation"),
-            OperationType::Subscription => write!(f, "Subscription"),
-        }
+        f.write_str((*self).into())
     }
 }
 
@@ -1358,7 +1366,7 @@ impl Field {
     /// Get field's original field definition.
     pub fn field_definition(&self, db: &dyn HirDatabase) -> Option<FieldDefinition> {
         db.find_object_type_by_name(self.parent_obj.as_ref()?.to_string())?
-            .fields_definition()
+            .self_fields()
             .iter()
             .find(|field| field.name() == self.name())
             .cloned()
@@ -1596,6 +1604,92 @@ impl Float {
     }
 }
 
+/// When some item (for example fields of an object type) have unique names
+/// and may be found on type extensions, this pre-computes where to find
+/// each item from its name
+#[derive(Clone, Debug, Eq)]
+pub(crate) struct ByNameWithExtensions {
+    /// `(None, i)` designates `def.example[i]`.
+    /// `(Some(j), i)` designates `def.extensions[j].example[i]`.
+    indices: IndexMap<String, (Option<usize>, usize)>,
+}
+
+/// Equivalent to ignoring a `ByNameWithExtensions` field in `PartialEq` for its parent struct,
+/// since it is determined by other fields.
+impl PartialEq for ByNameWithExtensions {
+    fn eq(&self, _other: &Self) -> bool {
+        true
+    }
+}
+
+/// Equivalent to ignoring a `ByNameWithExtensions` field in `Hash` for its parent struct,
+/// since it is determined by other fields.
+impl hash::Hash for ByNameWithExtensions {
+    fn hash<H: hash::Hasher>(&self, _state: &mut H) {
+        // do nothing
+    }
+}
+
+impl ByNameWithExtensions {
+    pub(crate) fn new<Item>(self_items: &[Item], name: impl Fn(&Item) -> &str) -> Self {
+        let mut indices = IndexMap::new();
+        for (i, item) in self_items.iter().enumerate() {
+            indices.entry(name(item).to_owned()).or_insert((None, i));
+        }
+        ByNameWithExtensions { indices }
+    }
+
+    pub(crate) fn add_extension<Item>(
+        &mut self,
+        extension_index: usize,
+        extension_items: &[Item],
+        name: impl Fn(&Item) -> &str,
+    ) {
+        for (i, item) in extension_items.iter().enumerate() {
+            self.indices
+                .entry(name(item).to_owned())
+                .or_insert((Some(extension_index), i));
+        }
+    }
+
+    fn get_by_index<'a, Item, Ext>(
+        &self,
+        (ext, i): (Option<usize>, usize),
+        self_items: &'a [Item],
+        extensions: &'a [Arc<Ext>],
+        extension_items: impl Fn(&'a Ext) -> &'a [Item],
+    ) -> &'a Item {
+        let items = if let Some(j) = ext {
+            extension_items(&extensions[j])
+        } else {
+            self_items
+        };
+        &items[i]
+    }
+
+    pub(crate) fn get<'a, Item, Ext>(
+        &self,
+        name: &str,
+        self_items: &'a [Item],
+        extensions: &'a [Arc<Ext>],
+        extension_items: impl Fn(&'a Ext) -> &'a [Item],
+    ) -> Option<&'a Item> {
+        let index = *self.indices.get(name)?;
+        Some(self.get_by_index(index, self_items, extensions, extension_items))
+    }
+
+    pub(crate) fn iter<'a, Item, Ext>(
+        &'a self,
+        self_items: &'a [Item],
+        extensions: &'a [Arc<Ext>],
+        extension_items: impl Fn(&'a Ext) -> &'a [Item] + Copy + 'a,
+    ) -> impl Iterator<Item = &'a Item> + ExactSizeIterator + DoubleEndedIterator {
+        self.indices
+            .values()
+            .map(move |&index| self.get_by_index(index, self_items, extensions, extension_items))
+    }
+}
+
 #[derive(Clone, Debug, Hash, PartialEq, Default, Eq)]
 pub struct SchemaDefinition {
     pub(crate) description: Option<String>,
@@ -1603,9 +1697,21 @@ pub struct SchemaDefinition {
     pub(crate) root_operation_type_definition: Arc<Vec<RootOperationTypeDefinition>>,
     pub(crate) loc: Option<HirNodeLocation>,
     pub(crate) extensions: Vec<Arc<SchemaExtension>>,
+    pub(crate) root_operation_names: RootOperationNames,
+}
+
+#[derive(Default, Clone, Debug, Hash, PartialEq, Eq)]
+pub(crate) struct RootOperationNames {
+    pub(crate) query: Option<String>,
+    pub(crate) mutation: Option<String>,
+    pub(crate) subscription: Option<String>,
 }
 
 impl SchemaDefinition {
+    pub fn description(&self) -> Option<&str> {
+        self.description.as_deref()
+    }
+
     /// Get a reference to the schema definition's directives (excluding those on extensions).
     pub fn self_directives(&self) -> &[Directive] {
         self.directives.as_ref()
@@ -1642,16 +1748,19 @@ impl SchemaDefinition {
             .filter(move |directive| directive.name() == name)
     }
 
-    /// Get a reference to the schema definition's root operation type definition.
-    pub fn root_operation_type_definition(&self) -> &[RootOperationTypeDefinition] {
+    /// Returns the root operations from this schema definition,
+    /// excluding those from schema extensions.
+    pub fn self_root_operations(&self) -> &[RootOperationTypeDefinition] {
         self.root_operation_type_definition.as_ref()
     }
 
-    /// Set the schema definition's root operation type definition.
-    pub(crate) fn set_root_operation_type_definition(&mut self, op: RootOperationTypeDefinition) {
-        Arc::get_mut(&mut self.root_operation_type_definition)
-            .unwrap()
-            .push(op)
+    /// Returns an iterator of root operations, from either on this schema defintion or its extensions.
+    pub fn root_operations(&self) -> impl Iterator<Item = &RootOperationTypeDefinition> {
+        self.self_root_operations().iter().chain(
+            self.extensions()
+                .iter()
+                .flat_map(|ext| ext.root_operations()),
+        )
     }
 
     /// Get the AST location information for this HIR node.
@@ -1664,40 +1773,31 @@ impl SchemaDefinition {
         &self.extensions
     }
 
-    // NOTE(@lrlna): potentially have the following fns on the database itself
-    // so they are memoised as well
-
-    /// Get Schema's query object type definition.
-    pub fn query(&self, db: &dyn HirDatabase) -> Option<Arc<ObjectTypeDefinition>> {
-        self.root_operation_type_definition().iter().find_map(|op| {
-            if op.operation_ty.is_query() {
-                op.object_type(db)
-            } else {
-                None
-            }
-        })
+    /// Returns the name of the object type for the `query` root operation,
+    /// defined either on this schema defintion or its extensions.
+    ///
+    /// The corresponding object type definition can be found
+    /// at [`compiler.db.object_types().get(name)`][HirDatabase::object_types].
+    pub fn query(&self) -> Option<&str> {
+        self.root_operation_names.query.as_deref()
     }
 
-    /// Get Schema's mutation object type definition.
-    pub fn mutation(&self, db: &dyn HirDatabase) -> Option<Arc<ObjectTypeDefinition>> {
-        self.root_operation_type_definition().iter().find_map(|op| {
-            if op.operation_ty.is_mutation() {
-                op.object_type(db)
-            } else {
-                None
-            }
-        })
+    /// Returns the name of the object type for the `mutation` root operation,
+    /// defined either on this schema defintion or its extensions.
+    ///
+    /// The corresponding object type definition can be found
+    /// at [`compiler.db.object_types().get(name)`][HirDatabase::object_types].
+    pub fn mutation(&self) -> Option<&str> {
+        self.root_operation_names.mutation.as_deref()
     }
 
-    /// Get Schema's subscription object type definition.
-    pub fn subscription(&self, db: &dyn HirDatabase) -> Option<Arc<ObjectTypeDefinition>> {
-        self.root_operation_type_definition().iter().find_map(|op| {
-            if op.operation_ty.is_subscription() {
-                op.object_type(db)
-            } else {
-                None
-            }
-        })
+    /// Returns the name of the object type for the `subscription` root operation,
+    /// defined either on this schema defintion or its extensions.
+    ///
+    /// The corresponding object type definition can be found
+    /// at [`compiler.db.object_types().get(name)`][HirDatabase::object_types].
+    pub fn subscription(&self) -> Option<&str> {
+        self.root_operation_names.subscription.as_deref()
     }
 }
 
@@ -1752,6 +1852,8 @@ pub struct ObjectTypeDefinition {
     pub(crate) fields_definition: Arc<Vec<FieldDefinition>>,
     pub(crate) loc: HirNodeLocation,
     pub(crate) extensions: Vec<Arc<ObjectTypeExtension>>,
+    pub(crate) fields_by_name: ByNameWithExtensions,
+    pub(crate) implements_interfaces_by_name: ByNameWithExtensions,
 }
 
 impl ObjectTypeDefinition {
@@ -1806,21 +1908,65 @@ impl ObjectTypeDefinition {
             .filter(move |directive| directive.name() == name)
     }
 
-    /// Get a reference to the object type definition's field definitions.
-    pub fn fields_definition(&self) -> &[FieldDefinition] {
+    /// Get a reference to the object type definition's field definitions,
+    /// excluding fields from extensions.
+    pub fn self_fields(&self) -> &[FieldDefinition] {
         self.fields_definition.as_ref()
     }
 
-    /// Find a field in object type definition.
+    /// Returns an iterator of fields of this object type,
+    /// whether from its own definition or from extensions.
+    pub fn fields(
+        &self,
+    ) -> impl Iterator<Item = &FieldDefinition> + ExactSizeIterator + DoubleEndedIterator {
+        self.fields_by_name.iter(
+            self.self_fields(),
+            self.extensions(),
+            ObjectTypeExtension::fields_definition,
+        )
+    }
+
+    /// Find a field by its name, either in this object type definition or its extensions.
     pub fn field(&self, name: &str) -> Option<&FieldDefinition> {
-        self.fields_definition().iter().find(|f| f.name() == name)
+        self.fields_by_name.get(
+            name,
+            self.self_fields(),
+            self.extensions(),
+            ObjectTypeExtension::fields_definition,
+        )
     }
 
-    /// Get a reference to object type definition's implements interfaces vector.
-    pub fn implements_interfaces(&self) -> &[ImplementsInterface] {
+    /// Returns interfaces implemented by this object type definition,
+    /// excluding those from extensions.
+    pub fn self_implements_interfaces(&self) -> &[ImplementsInterface] {
         self.implements_interfaces.as_ref()
     }
 
+    /// Returns an iterator of interfaces implemented by this object type,
+    /// whether from its own definition or from extensions.
+    pub fn implements_interfaces(
+        &self,
+    ) -> impl Iterator<Item = &ImplementsInterface> + ExactSizeIterator + DoubleEndedIterator {
+        self.implements_interfaces_by_name.iter(
+            self.self_implements_interfaces(),
+            self.extensions(),
+            ObjectTypeExtension::implements_interfaces,
+        )
+    }
+
+    /// Returns whether this object type implements the interface of the given name,
+    /// either in its own definition or its extensions.
+    pub fn implements_interface(&self, name: &str) -> bool {
+        self.implements_interfaces_by_name
+            .get(
+                name,
+                self.self_implements_interfaces(),
+                self.extensions(),
+                ObjectTypeExtension::implements_interfaces,
+            )
+            .is_some()
+    }
+
     /// Get the AST location information for this HIR node.
     pub fn loc(&self) -> HirNodeLocation {
         self.loc
@@ -1830,6 +1976,21 @@ impl ObjectTypeDefinition {
     pub fn extensions(&self) -> &[Arc<ObjectTypeExtension>] {
         &self.extensions
     }
+
+    pub(crate) fn push_extension(&mut self, ext: Arc<ObjectTypeExtension>) {
+        let next_index = self.extensions.len();
+        self.fields_by_name.add_extension(
+            next_index,
+            ext.fields_definition(),
+            FieldDefinition::name,
+        );
+        self.implements_interfaces_by_name.add_extension(
+            next_index,
+            ext.implements_interfaces(),
+            ImplementsInterface::interface,
+        );
+        self.extensions.push(ext);
+    }
 }
 
 #[derive(Clone, Debug, Hash, PartialEq, Eq)]
@@ -2082,6 +2243,10 @@ impl ScalarTypeDefinition {
     pub fn extensions(&self) -> &[Arc<ScalarTypeExtension>] {
         &self.extensions
     }
+
+    pub(crate) fn push_extension(&mut self, ext: Arc<ScalarTypeExtension>) {
+        self.extensions.push(ext);
+    }
 }
 
 #[derive(Clone, Debug, Hash, PartialEq, Eq)]
@@ -2092,6 +2257,7 @@ pub struct EnumTypeDefinition {
     pub(crate) enum_values_definition: Arc<Vec<EnumValueDefinition>>,
     pub(crate) loc: HirNodeLocation,
     pub(crate) extensions: Vec<Arc<EnumTypeExtension>>,
+    pub(crate) values_by_name: ByNameWithExtensions,
 }
 
 impl EnumTypeDefinition {
@@ -2146,11 +2312,33 @@ impl EnumTypeDefinition {
             .filter(move |directive| directive.name() == name)
     }
 
-    /// Get a reference to enum definition's enum values definition vector.
-    pub fn enum_values_definition(&self) -> &[EnumValueDefinition] {
+    /// Returns the values of this enum definition, excluding those from extensions.
+    pub fn self_values(&self) -> &[EnumValueDefinition] {
         self.enum_values_definition.as_ref()
     }
 
+    /// Returns an iterator of values of this enum type,
+    /// whether from its own definition or from extensions.
+    pub fn values(
+        &self,
+    ) -> impl Iterator<Item = &EnumValueDefinition> + ExactSizeIterator + DoubleEndedIterator {
+        self.values_by_name.iter(
+            self.self_values(),
+            self.extensions(),
+            EnumTypeExtension::enum_values_definition,
+        )
+    }
+
+    /// Find an enum value by its name, either in this enum type definition or its extensions.
+    pub fn value(&self, name: &str) -> Option<&EnumValueDefinition> {
+        self.values_by_name.get(
+            name,
+            self.self_values(),
+            self.extensions(),
+            EnumTypeExtension::enum_values_definition,
+        )
+    }
+
     /// Get the AST location information for this HIR node.
     pub fn loc(&self) -> HirNodeLocation {
         self.loc
@@ -2160,6 +2348,16 @@ impl EnumTypeDefinition {
     pub fn extensions(&self) -> &[Arc<EnumTypeExtension>] {
         &self.extensions
     }
+
+    pub(crate) fn push_extension(&mut self, ext: Arc<EnumTypeExtension>) {
+        let next_index = self.extensions.len();
+        self.values_by_name.add_extension(
+            next_index,
+            ext.enum_values_definition(),
+            EnumValueDefinition::enum_value,
+        );
+        self.extensions.push(ext);
+    }
 }
 
 #[derive(Clone, Debug, Hash, PartialEq, Eq)]
@@ -2171,6 +2369,10 @@ pub struct EnumValueDefinition {
 }
 
 impl EnumValueDefinition {
+    pub fn description(&self) -> Option<&str> {
+        self.description.as_deref()
+    }
+
     /// Get a reference to enum value definition's enum value
     pub fn enum_value(&self) -> &str {
         self.enum_value.src()
@@ -2214,6 +2416,7 @@ pub struct UnionTypeDefinition {
     pub(crate) union_members: Arc<Vec<UnionMember>>,
     pub(crate) loc: HirNodeLocation,
     pub(crate) extensions: Vec<Arc<UnionTypeExtension>>,
+    pub(crate) members_by_name: ByNameWithExtensions,
 }
 
 impl UnionTypeDefinition {
@@ -2268,11 +2471,37 @@ impl UnionTypeDefinition {
             .filter(move |directive| directive.name() == name)
     }
 
-    /// Get a reference to union definition's union members.
-    pub fn union_members(&self) -> &[UnionMember] {
+    /// Get a reference to union definition's union members,
+    /// excluding those from extensions.
+    pub fn self_members(&self) -> &[UnionMember] {
         self.union_members.as_ref()
     }
 
+    /// Returns an iterator of members of this union type,
+    /// whether from its own definition or from extensions.
+    pub fn members(
+        &self,
+    ) -> impl Iterator<Item = &UnionMember> + ExactSizeIterator + DoubleEndedIterator {
+        self.members_by_name.iter(
+            self.self_members(),
+            self.extensions(),
+            UnionTypeExtension::union_members,
+        )
+    }
+
+    /// Returns whether the type of the given name is a member of this union type,
+    /// either from the union type definition or its extensions.
+    pub fn has_member(&self, name: &str) -> bool {
+        self.members_by_name
+            .get(
+                name,
+                self.self_members(),
+                self.extensions(),
+                UnionTypeExtension::union_members,
+            )
+            .is_some()
+    }
+
     /// Get the AST location information for this HIR node.
     pub fn loc(&self) -> HirNodeLocation {
         self.loc
@@ -2282,6 +2511,13 @@ impl UnionTypeDefinition {
     pub fn extensions(&self) -> &[Arc<UnionTypeExtension>] {
         &self.extensions
     }
+
+    pub(crate) fn push_extension(&mut self, ext: Arc<UnionTypeExtension>) {
+        let next_index = self.extensions.len();
+        self.members_by_name
+            .add_extension(next_index, ext.union_members(), UnionMember::name);
+        self.extensions.push(ext);
+    }
 }
 
 #[derive(Clone, Debug, Hash, PartialEq, Eq)]
@@ -2316,6 +2552,8 @@ pub struct InterfaceTypeDefinition {
     pub(crate) fields_definition: Arc<Vec<FieldDefinition>>,
     pub(crate) loc: HirNodeLocation,
     pub(crate) extensions: Vec<Arc<InterfaceTypeExtension>>,
+    pub(crate) fields_by_name: ByNameWithExtensions,
+    pub(crate) implements_interfaces_by_name: ByNameWithExtensions,
 }
 
 impl InterfaceTypeDefinition {
@@ -2334,11 +2572,37 @@ impl InterfaceTypeDefinition {
         self.description.as_deref()
     }
 
-    /// Get a reference to interface definition's implements interfaces vector.
-    pub fn implements_interfaces(&self) -> &[ImplementsInterface] {
+    /// Returns interfaces implemented by this interface type definition,
+    /// excluding those from extensions.
+    pub fn self_implements_interfaces(&self) -> &[ImplementsInterface] {
         self.implements_interfaces.as_ref()
     }
 
+    /// Returns an iterator of interfaces implemented by this interface type,
+    /// whether from its own definition or from extensions.
+    pub fn implements_interfaces(
+        &self,
+    ) -> impl Iterator<Item = &ImplementsInterface> + ExactSizeIterator + DoubleEndedIterator {
+        self.implements_interfaces_by_name.iter(
+            self.self_implements_interfaces(),
+            self.extensions(),
+            InterfaceTypeExtension::implements_interfaces,
+        )
+    }
+
+    /// Returns whether this interface type implements the interface of the given name,
+    /// either in its own definition or its extensions.
+    pub fn implements_interface(&self, name: &str) -> bool {
+        self.implements_interfaces_by_name
+            .get(
+                name,
+                self.self_implements_interfaces(),
+                self.extensions(),
+                InterfaceTypeExtension::implements_interfaces,
+            )
+            .is_some()
+    }
+
     /// Get a reference to the interface definition's directives (excluding those on extensions).
     pub fn self_directives(&self) -> &[Directive] {
         self.directives.as_ref()
@@ -2375,14 +2639,32 @@ impl InterfaceTypeDefinition {
             .filter(move |directive| directive.name() == name)
     }
 
-    /// Get a reference to interface definition's fields.
-    pub fn fields_definition(&self) -> &[FieldDefinition] {
+    /// Get a reference to interface definition's fields,
+    /// excluding those from extensions.
+    pub fn self_fields(&self) -> &[FieldDefinition] {
         self.fields_definition.as_ref()
     }
 
-    /// Find a field in interface face definition.
+    /// Returns an iterator of fields of this interface type,
+    /// whether from its own definition or from extensions.
+    pub fn fields(
+        &self,
+    ) -> impl Iterator<Item = &FieldDefinition> + ExactSizeIterator + DoubleEndedIterator {
+        self.fields_by_name.iter(
+            self.self_fields(),
+            self.extensions(),
+            InterfaceTypeExtension::fields_definition,
+        )
+    }
+
+    /// Find a field by its name, either in this interface type definition or its extensions.
     pub fn field(&self, name: &str) -> Option<&FieldDefinition> {
-        self.fields_definition().iter().find(|f| f.name() == name)
+        self.fields_by_name.get(
+            name,
+            self.self_fields(),
+            self.extensions(),
+            InterfaceTypeExtension::fields_definition,
+        )
     }
 
     /// Get the AST location information for this HIR node.
@@ -2394,6 +2676,21 @@ impl InterfaceTypeDefinition {
     pub fn extensions(&self) -> &[Arc<InterfaceTypeExtension>] {
         &self.extensions
     }
+
+    pub(crate) fn push_extension(&mut self, ext: Arc<InterfaceTypeExtension>) {
+        let next_index = self.extensions.len();
+        self.fields_by_name.add_extension(
+            next_index,
+            ext.fields_definition(),
+            FieldDefinition::name,
+        );
+        self.implements_interfaces_by_name.add_extension(
+            next_index,
+            ext.implements_interfaces(),
+            ImplementsInterface::interface,
+        );
+        self.extensions.push(ext);
+    }
 }
 
 #[derive(Clone, Debug, Hash, PartialEq, Eq)]
@@ -2404,6 +2701,7 @@ pub struct InputObjectTypeDefinition {
     pub(crate) input_fields_definition: Arc<Vec<InputValueDefinition>>,
     pub(crate) loc: HirNodeLocation,
     pub(crate) extensions: Vec<Arc<InputObjectTypeExtension>>,
+    pub(crate) input_fields_by_name: ByNameWithExtensions,
 }
 
 impl InputObjectTypeDefinition {
@@ -2458,11 +2756,34 @@ impl InputObjectTypeDefinition {
             .filter(move |directive| directive.name() == name)
     }
 
-    /// Get a reference to input fields definitions.
-    pub fn input_fields_definition(&self) -> &[InputValueDefinition] {
+    /// Get a reference to input fields definitions,
+    /// excluding those from extensions.
+    pub fn self_fields(&self) -> &[InputValueDefinition] {
         self.input_fields_definition.as_ref()
     }
 
+    /// Returns an iterator of fields of this input object type,
+    /// whether from its own definition or from extensions.
+    pub fn fields(
+        &self,
+    ) -> impl Iterator<Item = &InputValueDefinition> + ExactSizeIterator + DoubleEndedIterator {
+        self.input_fields_by_name.iter(
+            self.self_fields(),
+            self.extensions(),
+            InputObjectTypeExtension::input_fields_definition,
+        )
+    }
+
+    /// Find a field by its name, either in this input object type definition or its extensions.
+    pub fn field(&self, name: &str) -> Option<&InputValueDefinition> {
+        self.input_fields_by_name.get(
+            name,
+            self.self_fields(),
+            self.extensions(),
+            InputObjectTypeExtension::input_fields_definition,
+        )
+    }
+
     /// Get the AST location information for this HIR node.
     pub fn loc(&self) -> HirNodeLocation {
         self.loc
@@ -2472,6 +2793,16 @@ impl InputObjectTypeDefinition {
     pub fn extensions(&self) -> &[Arc<InputObjectTypeExtension>] {
         &self.extensions
     }
+
+    pub(crate) fn push_extension(&mut self, ext: Arc<InputObjectTypeExtension>) {
+        let next_index = self.extensions.len();
+        self.input_fields_by_name.add_extension(
+            next_index,
+            ext.input_fields_definition(),
+            InputValueDefinition::name,
+        );
+        self.extensions.push(ext);
+    }
 }
 
 #[derive(Clone, Debug, Hash, PartialEq, Eq)]
@@ -2540,7 +2871,7 @@ impl SchemaExtension {
     }
 
     /// Get a reference to the schema definition's root operation type definition.
-    pub fn root_operation_type_definition(&self) -> &[RootOperationTypeDefinition] {
+    pub fn root_operations(&self) -> &[RootOperationTypeDefinition] {
         self.root_operation_type_definition.as_ref()
     }
 
@@ -2733,6 +3064,7 @@ pub struct UnionTypeExtension {
     pub(crate) directives: Arc<Vec<Directive>>,
     pub(crate) union_members: Arc<Vec<UnionMember>>,
     pub(crate) loc: HirNodeLocation,
+    pub(crate) members_by_name: ByNameWithExtensions,
 }
 
 impl UnionTypeExtension {
@@ -2968,29 +3300,234 @@ mod tests {
         assert_eq!(default_values[3], "98765432109876540000");
     }
 
+    #[test]
+    fn root_operations() {
+        let mut compiler = ApolloCompiler::new();
+        let first = r#"
+            schema @core(feature: "https://specs.apollo.dev/core/v0.1")
+            type Query {
+                field: Int
+            }
+            type Subscription {
+                newsletter: [String]
+            }
+        "#;
+        let second = r#"
+            extend schema @core(feature: "https://specs.apollo.dev/join/v0.1") {
+                query: MyQuery
+            }
+            type MyQuery {
+                different_field: String
+            }
+        "#;
+        compiler.add_type_system(first, "first.graphql");
+        compiler.add_type_system(second, "second.graphql");
+
+        let schema = compiler.db.schema();
+        assert_eq!(
+            schema
+                .self_root_operations()
+                .iter()
+                .map(|op| op.named_type().name())
+                .collect::<Vec<_>>(),
+            ["Subscription"]
+        );
+        assert_eq!(
+            schema
+                .root_operations()
+                .map(|op| op.named_type().name())
+                .collect::<Vec<_>>(),
+            ["Subscription", "MyQuery"]
+        );
+        assert!(schema.mutation().is_none());
+        assert_eq!(schema.query().unwrap(), "MyQuery");
+        assert_eq!(schema.subscription().unwrap(), "Subscription");
+    }
+
     #[test]
     fn extensions() {
         let mut compiler = ApolloCompiler::new();
-        let first = "
-            scalar URL
-        ";
-        let second = "
-            extend scalar URL @example(arg: true)
-        ";
+        let first = r#"
+            scalar Scalar @specifiedBy(url: "https://apollographql.com")
+            type Object implements Intf {
+                field: Int,
+            }
+            type Object2 {
+                field: String,
+            }
+            interface Intf {
+                field: Int,
+            }
+            input Input {
+                field: Enum,
+            }
+            enum Enum {
+                VALUE,
+            }
+            union Union = Object | Object2;
+        "#;
+        let second = r#"
+            extend scalar Scalar @deprecated(reason: "do something else")
+            extend interface Intf implements Intf2 {
+                field2: Scalar,
+            }
+            interface Intf2 {
+                field3: String,
+            }
+            extend type Object implements Intf2 {
+                field2: Scalar,
+                field3: String,
+            }
+            extend enum Enum {
+                "like VALUE, but more"
+                VALUE_2,
+            }
+            extend input Input {
+                field2: Int,
+            }
+            extend union Union = Query;
+            type Query {
+                object: Object,
+            }
+        "#;
         compiler.add_type_system(first, "first.graphql");
         compiler.add_type_system(second, "second.graphql");
 
-        assert!(matches!(
-            compiler
-                .db
-                .find_type_definition_by_name("URL".into())
-                .unwrap()
-                .directive_by_name("example")
+        let scalar = &compiler.db.types_definitions_by_name()["Scalar"];
+        let object = &compiler.db.object_types()["Object"];
+        let interface = &compiler.db.interfaces()["Intf"];
+        let input = &compiler.db.input_objects()["Input"];
+        let enum_ = &compiler.db.enums()["Enum"];
+        let union_ = &compiler.db.unions()["Union"];
+
+        assert_eq!(
+            scalar
+                .self_directives()
+                .iter()
+                .map(|d| d.name())
+                .collect::<Vec<_>>(),
+            ["specifiedBy"]
+        );
+        assert_eq!(
+            scalar.directives().map(|d| d.name()).collect::<Vec<_>>(),
+            ["specifiedBy", "deprecated"]
+        );
+        assert_eq!(
+            *scalar
+                .directive_by_name("deprecated")
                 .unwrap()
-                .argument_by_name("arg")
+                .argument_by_name("reason")
                 .unwrap(),
-            super::Value::Boolean(true)
-        ));
+            super::Value::String("do something else".to_owned())
+        );
+        assert!(scalar.directive_by_name("haunted").is_none());
+
+        assert_eq!(
+            object
+                .self_fields()
+                .iter()
+                .map(|f| f.name())
+                .collect::<Vec<_>>(),
+            ["field"]
+        );
+        assert_eq!(
+            object.fields().map(|f| f.name()).collect::<Vec<_>>(),
+            ["field", "field2", "field3"]
+        );
+        assert_eq!(object.field("field").unwrap().ty().name(), "Int");
+        assert!(object.field("field4").is_none());
+
+        assert_eq!(
+            object
+                .self_implements_interfaces()
+                .iter()
+                .map(|i| i.interface())
+                .collect::<Vec<_>>(),
+            ["Intf"]
+        );
+        assert_eq!(
+            object
+                .implements_interfaces()
+                .map(|f| f.interface())
+                .collect::<Vec<_>>(),
+            ["Intf", "Intf2"]
+        );
+        assert!(object.implements_interface("Intf2"));
+        assert!(!object.implements_interface("Intf3"));
+
+        assert_eq!(
+            interface
+                .self_fields()
+                .iter()
+                .map(|f| f.name())
+                .collect::<Vec<_>>(),
+            ["field"]
+        );
+        assert_eq!(
+            interface.fields().map(|f| f.name()).collect::<Vec<_>>(),
+            ["field", "field2"]
+        );
+        assert_eq!(interface.field("field").unwrap().ty().name(), "Int");
+        assert!(interface.field("field4").is_none());
+
+        assert!(interface.self_implements_interfaces().is_empty());
+        assert_eq!(
+            interface
+                .implements_interfaces()
+                .map(|f| f.interface())
+                .collect::<Vec<_>>(),
+            ["Intf2"]
+        );
+        assert!(interface.implements_interface("Intf2"));
+        assert!(!interface.implements_interface("Intf3"));
+
+        assert_eq!(
+            input
+                .self_fields()
+                .iter()
+                .map(|f| f.name())
+                .collect::<Vec<_>>(),
+            ["field"]
+        );
+        assert_eq!(
+            input.fields().map(|f| f.name()).collect::<Vec<_>>(),
+            ["field", "field2"]
+        );
+        assert_eq!(input.field("field").unwrap().ty().name(), "Enum");
+        assert!(input.field("field3").is_none());
+
+        assert_eq!(
+            enum_
+                .self_values()
+                .iter()
+                .map(|v| v.enum_value())
+                .collect::<Vec<_>>(),
+            ["VALUE"]
+        );
+        assert_eq!(
+            enum_.values().map(|v| v.enum_value()).collect::<Vec<_>>(),
+            ["VALUE", "VALUE_2"]
+        );
+        assert_eq!(
+            enum_.value("VALUE_2").unwrap().description(),
+            Some("like VALUE, but more")
+        );
+        assert!(enum_.value("VALUE_3").is_none());
+
+        assert_eq!(
+            union_
+                .self_members()
+                .iter()
+                .map(|m| m.name())
+                .collect::<Vec<_>>(),
+            ["Object", "Object2"]
+        );
+        assert_eq!(
+            union_.members().map(|m| m.name()).collect::<Vec<_>>(),
+            ["Object", "Object2", "Query"]
+        );
+        assert!(union_.has_member("Object2"));
+        assert!(!union_.has_member("Enum"));
     }
 
     #[test]
diff --git a/crates/apollo-compiler/src/database/hir_db.rs b/crates/apollo-compiler/src/database/hir_db.rs
index 947a9b9..fec40b7 100644
--- a/crates/apollo-compiler/src/database/hir_db.rs
+++ b/crates/apollo-compiler/src/database/hir_db.rs
@@ -347,24 +347,11 @@ fn schema(db: &dyn HirDatabase) -> Arc<SchemaDefinition> {
         // Panics in `ApolloCompiler` methods ensure `type_definition_files().is_empty()`
         return precomputed.definitions.schema.clone();
     }
-    let mut schema_def = type_definitions(db, schema_definition)
-        .next()
-        .unwrap_or_default();
-
-    // NOTE(@lrlna):
-    //
-    // "Query", "Subscription", "Mutation" object type definitions do not need
-    // to be explicitly defined in a schema definition, but are implicitly
-    // added.
-    //
-    // There will be a time when we need to distinguish between implicit and
-    // explicit definitions for validation purposes.
-    let type_defs = add_object_type_id_to_schema(db);
-    type_defs
-        .iter()
-        .for_each(|type_def| schema_def.set_root_operation_type_definition(type_def.clone()));
-
-    Arc::new(schema_def)
+    Arc::new(
+        type_definitions(db, schema_definition)
+            .next()
+            .unwrap_or_else(|| implicit_schema_definition(db)),
+    )
 }
 
 macro_rules! by_name {
@@ -385,7 +372,7 @@ macro_rules! by_name_extensible {
             // 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))
+                    Arc::get_mut(def).unwrap().push_extension(Arc::clone(ext))
                 }
             }
         }
@@ -476,17 +463,13 @@ fn operation_definition(
     let name = op_def.name().map(|n| name_hir_node(n, file_id));
     let ty = operation_type(op_def.operation_type());
     let variables = variable_definitions(op_def.variable_definitions(), file_id);
-    let parent_object_ty = db
-        .schema()
-        .root_operation_type_definition()
-        .iter()
-        .find_map(|op| {
-            if op.operation_ty() == ty {
-                Some(op.named_type().name())
-            } else {
-                None
-            }
-        });
+    let parent_object_ty = db.schema().self_root_operations().iter().find_map(|op| {
+        if op.operation_ty() == ty {
+            Some(op.named_type().name())
+        } else {
+            None
+        }
+    });
     let selection_set = selection_set(db, op_def.selection_set(), parent_object_ty, file_id);
     let directives = directives(op_def.directives(), file_id);
     let loc = location(file_id, op_def.syntax());
@@ -536,31 +519,113 @@ fn schema_definition(
     schema_def: ast::SchemaDefinition,
     file_id: FileId,
 ) -> Option<SchemaDefinition> {
-    let extensions = type_definitions(db, |_db, def: ast::SchemaExtension, file_id| {
-        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();
-
     let description = description(schema_def.description());
     let directives = directives(schema_def.directives(), file_id);
-    let root_operation_type_definition =
+    let mut operations =
         root_operation_type_definition(schema_def.root_operation_type_definitions(), file_id);
     let loc = location(file_id, schema_def.syntax());
+    let extensions = schema_extensions(db);
+    let mut root_operation_names = root_operations_names(&operations, &extensions);
+    add_implicit_operations(db, &mut operations, &mut root_operation_names);
 
     Some(SchemaDefinition {
         description,
         directives,
-        root_operation_type_definition,
+        root_operation_type_definition: Arc::new(operations),
         loc: Some(loc),
         extensions,
+        root_operation_names,
+    })
+}
+
+fn implicit_schema_definition(db: &dyn HirDatabase) -> SchemaDefinition {
+    let extensions = schema_extensions(db);
+    let mut operations = Vec::new();
+    let mut root_operation_names = root_operations_names(&operations, &extensions);
+    add_implicit_operations(db, &mut operations, &mut root_operation_names);
+    SchemaDefinition {
+        description: None,
+        directives: Arc::new(Vec::new()),
+        root_operation_type_definition: Arc::new(operations),
+        loc: None,
+        extensions,
+        root_operation_names,
+    }
+}
+
+fn schema_extensions(db: &dyn HirDatabase) -> Vec<Arc<SchemaExtension>> {
+    type_definitions(db, |_db, def: ast::SchemaExtension, file_id| {
+        let directives = directives(def.directives(), file_id);
+        let root_operation_type_definition = Arc::new(root_operation_type_definition(
+            def.root_operation_type_definitions(),
+            file_id,
+        ));
+        let loc = location(file_id, def.syntax());
+        Some(Arc::new(SchemaExtension {
+            directives,
+            root_operation_type_definition,
+            loc,
+        }))
     })
+    .collect()
+}
+
+fn root_operations_names(
+    root_operation_type_definition: &[RootOperationTypeDefinition],
+    extensions: &[Arc<SchemaExtension>],
+) -> RootOperationNames {
+    let mut names = RootOperationNames::default();
+    let mut add_operations = |ops: &[RootOperationTypeDefinition]| {
+        for op in ops {
+            let name_field = match op.operation_ty() {
+                OperationType::Query => &mut names.query,
+                OperationType::Mutation => &mut names.mutation,
+                OperationType::Subscription => &mut names.subscription,
+            };
+            if name_field.is_none() {
+                *name_field = Some(op.named_type().name());
+            }
+        }
+    };
+    add_operations(root_operation_type_definition);
+    for extension in extensions {
+        add_operations(extension.root_operations());
+    }
+    names
+}
+
+/// https://spec.graphql.org/October2021/#sec-Root-Operation-Types.Default-Root-Operation-Type-Names
+///
+/// To distinguish between implicit and explicit definitions for validation purposes,
+/// check `operation.loc.is_none()`.
+///
+/// NOTE(@lrlna):
+/// "Query", "Subscription", "Mutation" object type definitions do not need
+/// to be explicitly defined in a schema definition, but are implicitly
+/// added.
+fn add_implicit_operations(
+    db: &dyn HirDatabase,
+    operations: &mut Vec<RootOperationTypeDefinition>,
+    names: &mut RootOperationNames,
+) {
+    for (name_field, operation_ty) in [
+        (&mut names.query, OperationType::Query),
+        (&mut names.mutation, OperationType::Mutation),
+        (&mut names.subscription, OperationType::Subscription),
+    ] {
+        let name = operation_ty.into();
+        if name_field.is_none() && db.object_types().contains_key(name) {
+            *name_field = Some(name.to_owned());
+            operations.push(RootOperationTypeDefinition {
+                operation_ty,
+                named_type: Type::Named {
+                    name: name.to_owned(),
+                    loc: None,
+                },
+                loc: None,
+            })
+        }
+    }
 }
 
 fn object_type_definition(
@@ -574,6 +639,9 @@ fn object_type_definition(
     let directives = directives(obj_def.directives(), file_id);
     let fields_definition = fields_definition(obj_def.fields_definition(), file_id);
     let loc = location(file_id, obj_def.syntax());
+    let fields_by_name = ByNameWithExtensions::new(&fields_definition, FieldDefinition::name);
+    let implements_interfaces_by_name =
+        ByNameWithExtensions::new(&implements_interfaces, ImplementsInterface::interface);
 
     // TODO(@goto-bus-stop) when a name is missing on this,
     // we might still want to produce a HIR node, so we can validate other parts of the definition
@@ -585,6 +653,8 @@ fn object_type_definition(
         fields_definition,
         loc,
         extensions: Vec::new(),
+        fields_by_name,
+        implements_interfaces_by_name,
     })
 }
 
@@ -646,6 +716,8 @@ fn enum_definition(
     let directives = directives(enum_def.directives(), file_id);
     let enum_values_definition = enum_values_definition(enum_def.enum_values_definition(), file_id);
     let loc = location(file_id, enum_def.syntax());
+    let values_by_name =
+        ByNameWithExtensions::new(&enum_values_definition, EnumValueDefinition::enum_value);
 
     // TODO(@goto-bus-stop) when a name is missing on this,
     // we might still want to produce a HIR node, so we can validate other parts of the definition
@@ -656,6 +728,7 @@ fn enum_definition(
         enum_values_definition,
         loc,
         extensions: Vec::new(),
+        values_by_name,
     })
 }
 
@@ -715,6 +788,7 @@ fn union_definition(
     let directives = directives(union_def.directives(), file_id);
     let union_members = union_members(union_def.union_member_types(), file_id);
     let loc = location(file_id, union_def.syntax());
+    let members_by_name = ByNameWithExtensions::new(&union_members, UnionMember::name);
 
     // TODO(@goto-bus-stop) when a name is missing on this,
     // we might still want to produce a HIR node, so we can validate other parts of the definition
@@ -725,6 +799,7 @@ fn union_definition(
         union_members,
         loc,
         extensions: Vec::new(),
+        members_by_name,
     })
 }
 
@@ -733,11 +808,17 @@ fn union_extension(
     def: ast::UnionTypeExtension,
     file_id: FileId,
 ) -> Option<Arc<UnionTypeExtension>> {
+    let directives = directives(def.directives(), file_id);
+    let name = name(def.name(), file_id)?;
+    let union_members = union_members(def.union_member_types(), file_id);
+    let loc = location(file_id, def.syntax());
+    let members_by_name = ByNameWithExtensions::new(&union_members, UnionMember::name);
     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()),
+        directives,
+        name,
+        union_members,
+        loc,
+        members_by_name,
     }))
 }
 
@@ -776,6 +857,9 @@ fn interface_definition(
     let directives = directives(interface_def.directives(), file_id);
     let fields_definition = fields_definition(interface_def.fields_definition(), file_id);
     let loc = location(file_id, interface_def.syntax());
+    let fields_by_name = ByNameWithExtensions::new(&fields_definition, FieldDefinition::name);
+    let implements_interfaces_by_name =
+        ByNameWithExtensions::new(&implements_interfaces, ImplementsInterface::interface);
 
     // TODO(@goto-bus-stop) when a name is missing on this,
     // we might still want to produce a HIR node, so we can validate other parts of the definition
@@ -787,6 +871,8 @@ fn interface_definition(
         fields_definition,
         loc,
         extensions: Vec::new(),
+        fields_by_name,
+        implements_interfaces_by_name,
     })
 }
 
@@ -839,6 +925,8 @@ fn input_object_definition(
     let input_fields_definition =
         input_fields_definition(input_obj.input_fields_definition(), file_id);
     let loc = location(file_id, input_obj.syntax());
+    let input_fields_by_name =
+        ByNameWithExtensions::new(&input_fields_definition, InputValueDefinition::name);
 
     // TODO(@goto-bus-stop) when a name is missing on this,
     // we might still want to produce a HIR node, so we can validate other parts of the definition
@@ -849,6 +937,7 @@ fn input_object_definition(
         input_fields_definition,
         loc,
         extensions: Vec::new(),
+        input_fields_by_name,
     })
 }
 
@@ -889,38 +978,6 @@ fn extension(db: &dyn HirDatabase, def: ast::Definition, file_id: FileId) -> Opt
     }
 }
 
-fn add_object_type_id_to_schema(db: &dyn HirDatabase) -> Arc<Vec<RootOperationTypeDefinition>> {
-    // Schema Definition does not have to be present in the SDL if ObjectType name is
-    // - Query
-    // - Subscription
-    // - Mutation
-    //
-    // Compiler's internal schema, however, should have a reference to these
-    // object types if they are present
-    let type_defs: Vec<RootOperationTypeDefinition> = db
-        .object_types()
-        .values()
-        .filter_map(|obj_type| {
-            let obj_name = obj_type.name();
-            if matches!(obj_name, "Query" | "Subscription" | "Mutation") {
-                let operation_type = obj_name.into();
-                Some(RootOperationTypeDefinition {
-                    operation_ty: operation_type,
-                    named_type: Type::Named {
-                        name: obj_name.to_string(),
-                        loc: None,
-                    },
-                    loc: None,
-                })
-            } else {
-                None
-            }
-        })
-        .collect();
-
-    Arc::new(type_defs)
-}
-
 fn implements_interfaces(
     implements_interfaces: Option<ast::ImplementsInterfaces>,
     file_id: FileId,
@@ -1049,8 +1106,8 @@ fn default_value(
 fn root_operation_type_definition(
     root_type_def: AstChildren<ast::RootOperationTypeDefinition>,
     file_id: FileId,
-) -> Arc<Vec<RootOperationTypeDefinition>> {
-    let type_defs: Vec<RootOperationTypeDefinition> = root_type_def
+) -> Vec<RootOperationTypeDefinition> {
+    root_type_def
         .into_iter()
         .filter_map(|ty| {
             if let Some(named_ty) = ty.named_type() {
@@ -1067,9 +1124,7 @@ fn root_operation_type_definition(
                 None
             }
         })
-        .collect();
-
-    Arc::new(type_defs)
+        .collect()
 }
 
 fn operation_type(op_type: Option<ast::OperationType>) -> OperationType {
diff --git a/crates/apollo-compiler/src/lib.rs b/crates/apollo-compiler/src/lib.rs
index e0b01b8..915dda0 100644
--- a/crates/apollo-compiler/src/lib.rs
+++ b/crates/apollo-compiler/src/lib.rs
@@ -748,7 +748,7 @@ enum Pet {
 
         let enums = compiler.db.enums();
         let enum_values: Vec<&str> = enums["Pet"]
-            .enum_values_definition()
+            .self_values()
             .iter()
             .map(|enum_val| enum_val.enum_value())
             .collect();
@@ -790,14 +790,14 @@ type SearchQuery {
 
         let unions = compiler.db.unions();
         let union_members: Vec<&str> = unions["SearchResult"]
-            .union_members()
+            .self_members()
             .iter()
             .map(|member| member.name())
             .collect();
         assert_eq!(union_members, ["Photo", "Person"]);
 
         let photo_object = unions["SearchResult"]
-            .union_members()
+            .self_members()
             .iter()
             .find(|mem| mem.name() == "Person")
             .unwrap()
@@ -805,7 +805,7 @@ type SearchQuery {
 
         if let Some(photo) = photo_object {
             let fields: Vec<&str> = photo
-                .fields_definition()
+                .self_fields()
                 .iter()
                 .map(|field| field.name())
                 .collect();
@@ -873,7 +873,7 @@ input Point2D {
 
         let input_objects = compiler.db.input_objects();
         let fields: Vec<&str> = input_objects["Point2D"]
-            .input_fields_definition()
+            .self_fields()
             .iter()
             .map(|val| val.name())
             .collect();
@@ -944,7 +944,7 @@ scalar Url @specifiedBy(url: "https://tools.ietf.org/html/rfc3986")
 
         if let Some(person) = person_obj {
             let field_ty_directive: Vec<String> = person
-                .fields_definition()
+                .self_fields()
                 .iter()
                 .filter_map(|f| {
                     // get access to the actual definition the field is using
@@ -969,7 +969,7 @@ scalar Url @specifiedBy(url: "https://tools.ietf.org/html/rfc3986")
             assert_eq!(field_ty_directive, ["specifiedBy"]);
 
             let field_arg_ty_vals: Vec<String> = person
-                .fields_definition()
+                .self_fields()
                 .iter()
                 .flat_map(|f| {
                     let enum_vals: Vec<String> = f
@@ -982,7 +982,7 @@ scalar Url @specifiedBy(url: "https://tools.ietf.org/html/rfc3986")
                                     // get that definition's directives, for example
                                     TypeDefinition::EnumTypeDefinition(enum_) => {
                                         let dir_names: Vec<String> = enum_
-                                            .enum_values_definition()
+                                            .self_values()
                                             .iter()
                                             .map(|enum_val| enum_val.enum_value().to_owned())
                                             .collect();
@@ -1026,7 +1026,7 @@ scalar Url @specifiedBy(url: "https://tools.ietf.org/html/rfc3986")
 
         if let Some(person) = person_obj {
             let field_ty_directive: Vec<String> = person
-                .input_fields_definition()
+                .self_fields()
                 .iter()
                 .filter_map(|f| {
                     if let Some(field_ty) = f.ty().type_def(&compiler.db) {
diff --git a/crates/apollo-compiler/src/validation/0062_root_operation_with_list_type.graphql b/crates/apollo-compiler/src/validation/0062_root_operation_with_list_type.graphql
deleted file mode 100644
index c610245..0000000
--- a/crates/apollo-compiler/src/validation/0062_root_operation_with_list_type.graphql
+++ /dev/null
@@ -1,6 +0,0 @@
-schema {
-  query: [Object]
-}
-type Object {
-  field: Int
-}
\ No newline at end of file
diff --git a/crates/apollo-compiler/src/validation/enum_.rs b/crates/apollo-compiler/src/validation/enum_.rs
index a5a2104..c62d6ae 100644
--- a/crates/apollo-compiler/src/validation/enum_.rs
+++ b/crates/apollo-compiler/src/validation/enum_.rs
@@ -27,7 +27,7 @@ pub fn validate_enum_definition(
     );
 
     let mut seen: HashMap<&str, &EnumValueDefinition> = HashMap::new();
-    for enum_val in enum_def.enum_values_definition() {
+    for enum_val in enum_def.self_values() {
         diagnostics.extend(db.validate_enum_value(enum_val.clone()));
 
         // An Enum type must define one or more unique enum values.
diff --git a/crates/apollo-compiler/src/validation/interface.rs b/crates/apollo-compiler/src/validation/interface.rs
index cd94f45..5318693 100644
--- a/crates/apollo-compiler/src/validation/interface.rs
+++ b/crates/apollo-compiler/src/validation/interface.rs
@@ -44,7 +44,7 @@ pub fn validate_interface_definition(
     //   name: String
     // }
     for (name, interface_def) in db.interfaces().iter() {
-        for implements_interface in interface_def.implements_interfaces() {
+        for implements_interface in interface_def.self_implements_interfaces() {
             if let Some(interface) = implements_interface.interface_definition(db.upcast()) {
                 let super_name = interface.name();
                 if name == super_name {
@@ -67,11 +67,12 @@ pub fn validate_interface_definition(
     }
 
     // Interface Type field validation.
-    diagnostics.extend(db.validate_field_definitions(interface_def.fields_definition().to_vec()));
+    diagnostics.extend(db.validate_field_definitions(interface_def.self_fields().to_vec()));
 
     // Implements Interfaceds validation.
-    diagnostics
-        .extend(db.validate_implements_interfaces(interface_def.implements_interfaces().to_vec()));
+    diagnostics.extend(
+        db.validate_implements_interfaces(interface_def.self_implements_interfaces().to_vec()),
+    );
 
     // When defining an interface that implements another interface, the
     // implementing interface must define each field that is specified by
@@ -79,17 +80,17 @@ pub fn validate_interface_definition(
     //
     // Returns a Missing Field error.
     let fields: HashSet<ValidationSet> = interface_def
-        .fields_definition()
+        .self_fields()
         .iter()
         .map(|field| ValidationSet {
             name: field.name().into(),
             loc: field.loc(),
         })
         .collect();
-    for implements_interface in interface_def.implements_interfaces().iter() {
+    for implements_interface in interface_def.self_implements_interfaces().iter() {
         if let Some(super_interface) = implements_interface.interface_definition(db.upcast()) {
             let implements_interface_fields: HashSet<ValidationSet> = super_interface
-                .fields_definition()
+                .self_fields()
                 .iter()
                 .map(|field| ValidationSet {
                     name: field.name().into(),
@@ -176,7 +177,7 @@ pub fn validate_implements_interfaces(
         .filter_map(|implements_interface| {
             if let Some(interface) = implements_interface.interface_definition(db.upcast()) {
                 let child_interfaces: HashSet<ValidationSet> = interface
-                    .implements_interfaces()
+                    .self_implements_interfaces()
                     .iter()
                     .map(|interface| ValidationSet {
                         name: interface.interface().to_owned(),
diff --git a/crates/apollo-compiler/src/validation/object.rs b/crates/apollo-compiler/src/validation/object.rs
index f1da414..685c470 100644
--- a/crates/apollo-compiler/src/validation/object.rs
+++ b/crates/apollo-compiler/src/validation/object.rs
@@ -30,10 +30,11 @@ pub fn validate_object_type_definition(
     ));
 
     // Object Type field validations.
-    diagnostics.extend(db.validate_field_definitions(object.fields_definition().to_vec()));
+    diagnostics.extend(db.validate_field_definitions(object.self_fields().to_vec()));
 
     // Implements Interfaces validation.
-    diagnostics.extend(db.validate_implements_interfaces(object.implements_interfaces().to_vec()));
+    diagnostics
+        .extend(db.validate_implements_interfaces(object.self_implements_interfaces().to_vec()));
 
     // When defining an interface that implements another interface, the
     // implementing interface must define each field that is specified by
@@ -41,17 +42,17 @@ pub fn validate_object_type_definition(
     //
     // Returns a Missing Field error.
     let fields: HashSet<ValidationSet> = object
-        .fields_definition()
+        .self_fields()
         .iter()
         .map(|field| ValidationSet {
             name: field.name().into(),
             loc: field.loc(),
         })
         .collect();
-    for implements_interface in object.implements_interfaces().iter() {
+    for implements_interface in object.self_implements_interfaces().iter() {
         if let Some(interface) = implements_interface.interface_definition(db.upcast()) {
             let implements_interface_fields: HashSet<ValidationSet> = interface
-                .fields_definition()
+                .self_fields()
                 .iter()
                 .map(|field| ValidationSet {
                     name: field.name().into(),
diff --git a/crates/apollo-compiler/src/validation/operation.rs b/crates/apollo-compiler/src/validation/operation.rs
index 840baaa..7630a1f 100644
--- a/crates/apollo-compiler/src/validation/operation.rs
+++ b/crates/apollo-compiler/src/validation/operation.rs
@@ -133,7 +133,7 @@ pub fn validate_subscription_operations(
     // defined schema root operation types.
     //
     //   * subscription operation - subscription root operation
-    if !subscriptions.is_empty() && db.schema().subscription(db.upcast()).is_none() {
+    if !subscriptions.is_empty() && db.schema().subscription().is_none() {
         let unsupported_ops: Vec<ApolloDiagnostic> = subscriptions
             .iter()
             .map(|op| {
@@ -162,7 +162,7 @@ pub fn validate_query_operations(
     // defined schema root operation types.
     //
     //   * query operation - query root operation
-    if !queries.is_empty() && db.schema().query(db.upcast()).is_none() {
+    if !queries.is_empty() && db.schema().query().is_none() {
         let unsupported_ops: Vec<ApolloDiagnostic> = queries
             .iter()
             .map(|op| {
@@ -202,7 +202,7 @@ pub fn validate_mutation_operations(
     // defined schema root operation types.
     //
     //   * mutation operation - mutation root operation
-    if !mutations.is_empty() && db.schema().mutation(db.upcast()).is_none() {
+    if !mutations.is_empty() && db.schema().mutation().is_none() {
         let unsupported_ops: Vec<ApolloDiagnostic> = mutations
             .iter()
             .map(|op| {
diff --git a/crates/apollo-compiler/src/validation/schema.rs b/crates/apollo-compiler/src/validation/schema.rs
index 86e0f38..4441485 100644
--- a/crates/apollo-compiler/src/validation/schema.rs
+++ b/crates/apollo-compiler/src/validation/schema.rs
@@ -12,7 +12,7 @@ pub fn validate_schema_definition(
     let mut diagnostics = Vec::new();
 
     // A GraphQL schema must have a Query root operation.
-    if schema_def.query(db.upcast()).is_none() {
+    if schema_def.query().is_none() {
         if let Some(loc) = db.schema().loc() {
             diagnostics.push(
                 ApolloDiagnostic::new(db, loc.into(), DiagnosticData::QueryRootOperationType)
@@ -23,11 +23,8 @@ pub fn validate_schema_definition(
             );
         }
     }
-    diagnostics.extend(
-        db.validate_root_operation_definitions(
-            schema_def.root_operation_type_definition().to_vec(),
-        ),
-    );
+    diagnostics
+        .extend(db.validate_root_operation_definitions(schema_def.self_root_operations().to_vec()));
 
     diagnostics.extend(db.validate_directives(
         schema_def.self_directives().to_vec(),
diff --git a/crates/apollo-compiler/src/validation/union_.rs b/crates/apollo-compiler/src/validation/union_.rs
index b5dfacc..40ebcbe 100644
--- a/crates/apollo-compiler/src/validation/union_.rs
+++ b/crates/apollo-compiler/src/validation/union_.rs
@@ -27,7 +27,7 @@ pub fn validate_union_definition(
     );
 
     let mut seen: HashMap<&str, &UnionMember> = HashMap::new();
-    for union_member in union_def.union_members().iter() {
+    for union_member in union_def.self_members().iter() {
         let name = union_member.name();
         let redefined_definition = union_member.loc();
         // A Union type must include one or more unique member types.


Test patch:
diff --git a/crates/apollo-compiler/test_data/diagnostics/0059_root_operation_object_type.txt b/crates/apollo-compiler/test_data/diagnostics/0059_root_operation_object_type.txt
index 1c15cce..1cc2507 100644
--- a/crates/apollo-compiler/test_data/diagnostics/0059_root_operation_object_type.txt
+++ b/crates/apollo-compiler/test_data/diagnostics/0059_root_operation_object_type.txt
@@ -1,30 +1,4 @@
 [
-    ApolloDiagnostic {
-        cache: {
-            58: "0059_root_operation_object_type.graphql",
-        },
-        location: DiagnosticLocation {
-            file_id: FileId {
-                id: 58,
-            },
-            offset: 0,
-            length: 36,
-        },
-        labels: [
-            Label {
-                location: DiagnosticLocation {
-                    file_id: FileId {
-                        id: 58,
-                    },
-                    offset: 0,
-                    length: 36,
-                },
-                text: "`query` root operation type must be defined here",
-            },
-        ],
-        help: None,
-        data: QueryRootOperationType,
-    },
     ApolloDiagnostic {
         cache: {
             58: "0059_root_operation_object_type.graphql",
diff --git a/crates/apollo-compiler/test_data/diagnostics/0060_root_operation_definition_undefined_operation_type.txt b/crates/apollo-compiler/test_data/diagnostics/0060_root_operation_definition_undefined_operation_type.txt
index c258582..fd936f5 100644
--- a/crates/apollo-compiler/test_data/diagnostics/0060_root_operation_definition_undefined_operation_type.txt
+++ b/crates/apollo-compiler/test_data/diagnostics/0060_root_operation_definition_undefined_operation_type.txt
@@ -1,30 +1,4 @@
 [
-    ApolloDiagnostic {
-        cache: {
-            59: "0060_root_operation_definition_undefined_operation_type.graphql",
-        },
-        location: DiagnosticLocation {
-            file_id: FileId {
-                id: 59,
-            },
-            offset: 0,
-            length: 31,
-        },
-        labels: [
-            Label {
-                location: DiagnosticLocation {
-                    file_id: FileId {
-                        id: 59,
-                    },
-                    offset: 0,
-                    length: 31,
-                },
-                text: "`query` root operation type must be defined here",
-            },
-        ],
-        help: None,
-        data: QueryRootOperationType,
-    },
     ApolloDiagnostic {
         cache: {
             59: "0060_root_operation_definition_undefined_operation_type.graphql",
diff --git a/crates/apollo-compiler/test_data/diagnostics/0061_root_operation_with_default_undefined_query.txt b/crates/apollo-compiler/test_data/diagnostics/0061_root_operation_with_default_undefined_query.txt
index 43ff2ab..e00cf83 100644
--- a/crates/apollo-compiler/test_data/diagnostics/0061_root_operation_with_default_undefined_query.txt
+++ b/crates/apollo-compiler/test_data/diagnostics/0061_root_operation_with_default_undefined_query.txt
@@ -1,30 +1,4 @@
 [
-    ApolloDiagnostic {
-        cache: {
-            60: "0061_root_operation_with_default_undefined_query.graphql",
-        },
-        location: DiagnosticLocation {
-            file_id: FileId {
-                id: 60,
-            },
-            offset: 0,
-            length: 27,
-        },
-        labels: [
-            Label {
-                location: DiagnosticLocation {
-                    file_id: FileId {
-                        id: 60,
-                    },
-                    offset: 0,
-                    length: 27,
-                },
-                text: "`query` root operation type must be defined here",
-            },
-        ],
-        help: None,
-        data: QueryRootOperationType,
-    },
     ApolloDiagnostic {
         cache: {
             60: "0061_root_operation_with_default_undefined_query.graphql",