use std::collections::HashMap;
use std::collections::HashSet;
use std::sync::Arc;
use apollo_parser::ast::AstChildren;
use apollo_parser::ast::AstNode;
use apollo_parser::ast::{self};
use apollo_parser::SyntaxNode;
use indexmap::IndexMap;
use crate::database::document;
use crate::database::FileId;
use crate::hir::*;
use crate::AstDatabase;
use crate::InputDatabase;
const INTROSPECTION_OBJECT_TYS: [&str; 6] = [
"__Schema",
"__Type",
"__Field",
"__InputValue",
"__EnumValue",
"__Directive",
];
const INTROSPECTION_ENUM_TYS: [&str; 2] = ["__TypeKind", "__DirectiveLocation"];
#[salsa::query_group(HirStorage)]
pub trait HirDatabase: InputDatabase + AstDatabase {
#[salsa::invoke(type_system_definitions)]
fn type_system_definitions(&self) -> Arc<TypeSystemDefinitions>;
#[salsa::invoke(type_system)]
fn type_system(&self) -> Arc<TypeSystem>;
#[salsa::invoke(extensions)]
fn extensions(&self) -> Arc<Vec<TypeExtension>>;
#[salsa::invoke(operations)]
fn operations(&self, file_id: FileId) -> Arc<Vec<Arc<OperationDefinition>>>;
#[salsa::invoke(fragments)]
fn fragments(&self, file_id: FileId) -> ByName<FragmentDefinition>;
#[salsa::invoke(all_operations)]
fn all_operations(&self) -> Arc<Vec<Arc<OperationDefinition>>>;
#[salsa::invoke(all_fragments)]
fn all_fragments(&self) -> ByName<FragmentDefinition>;
#[salsa::invoke(schema)]
fn schema(&self) -> Arc<SchemaDefinition>;
#[salsa::invoke(object_types)]
fn object_types(&self) -> ByName<ObjectTypeDefinition>;
fn object_types_with_built_ins(&self) -> ByName<ObjectTypeDefinition>;
#[salsa::invoke(scalars)]
fn scalars(&self) -> ByName<ScalarTypeDefinition>;
#[salsa::invoke(enums)]
fn enums(&self) -> ByName<EnumTypeDefinition>;
fn enums_with_built_ins(&self) -> ByName<EnumTypeDefinition>;
#[salsa::invoke(unions)]
fn unions(&self) -> ByName<UnionTypeDefinition>;
#[salsa::invoke(interfaces)]
fn interfaces(&self) -> ByName<InterfaceTypeDefinition>;
#[salsa::invoke(directive_definitions)]
fn directive_definitions(&self) -> ByName<DirectiveDefinition>;
#[salsa::invoke(input_objects)]
fn input_objects(&self) -> ByName<InputObjectTypeDefinition>;
#[salsa::invoke(document::find_operation)]
fn find_operation(
&self,
file_id: FileId,
name: Option<String>,
) -> Option<Arc<OperationDefinition>>;
#[salsa::transparent]
#[salsa::invoke(document::find_fragment_by_name)]
fn find_fragment_by_name(
&self,
file_id: FileId,
name: String,
) -> Option<Arc<FragmentDefinition>>;
#[salsa::transparent]
#[salsa::invoke(document::find_object_type_by_name)]
fn find_object_type_by_name(&self, name: String) -> Option<Arc<ObjectTypeDefinition>>;
#[salsa::transparent]
#[salsa::invoke(document::find_union_by_name)]
fn find_union_by_name(&self, name: String) -> Option<Arc<UnionTypeDefinition>>;
#[salsa::transparent]
#[salsa::invoke(document::find_enum_by_name)]
fn find_enum_by_name(&self, name: String) -> Option<Arc<EnumTypeDefinition>>;
#[salsa::transparent]
#[salsa::invoke(document::find_scalar_by_name)]
fn find_scalar_by_name(&self, name: String) -> Option<Arc<ScalarTypeDefinition>>;
#[salsa::transparent]
#[salsa::invoke(document::find_interface_by_name)]
fn find_interface_by_name(&self, name: String) -> Option<Arc<InterfaceTypeDefinition>>;
#[salsa::transparent]
#[salsa::invoke(document::find_directive_definition_by_name)]
fn find_directive_definition_by_name(&self, name: String) -> Option<Arc<DirectiveDefinition>>;
#[salsa::invoke(document::find_types_with_directive)]
fn find_types_with_directive(&self, directive: String) -> Arc<Vec<TypeDefinition>>;
#[salsa::transparent]
#[salsa::invoke(document::find_input_object_by_name)]
fn find_input_object_by_name(&self, name: String) -> Option<Arc<InputObjectTypeDefinition>>;
#[salsa::invoke(document::types_definitions_by_name)]
fn types_definitions_by_name(&self) -> Arc<IndexMap<String, TypeDefinition>>;
#[salsa::transparent]
#[salsa::invoke(document::find_type_definition_by_name)]
fn find_type_definition_by_name(&self, name: String) -> Option<TypeDefinition>;
#[salsa::invoke(document::query_operations)]
fn query_operations(&self, file_id: FileId) -> Arc<Vec<Arc<OperationDefinition>>>;
#[salsa::invoke(document::mutation_operations)]
fn mutation_operations(&self, file_id: FileId) -> Arc<Vec<Arc<OperationDefinition>>>;
#[salsa::invoke(document::subscription_operations)]
fn subscription_operations(&self, file_id: FileId) -> Arc<Vec<Arc<OperationDefinition>>>;
#[salsa::invoke(document::operation_fields)]
fn operation_fields(&self, selection_set: SelectionSet) -> Arc<Vec<Field>>;
#[salsa::invoke(document::operation_inline_fragment_fields)]
fn operation_inline_fragment_fields(&self, selection_set: SelectionSet) -> Arc<Vec<Field>>;
#[salsa::invoke(document::operation_fragment_spread_fields)]
fn operation_fragment_spread_fields(&self, selection_set: SelectionSet) -> Arc<Vec<Field>>;
#[salsa::invoke(document::operation_fragment_references)]
fn operation_fragment_references(
&self,
selection_set: SelectionSet,
) -> Arc<Vec<Arc<FragmentDefinition>>>;
#[salsa::invoke(document::flattened_operation_fields)]
fn flattened_operation_fields(&self, selection_set: SelectionSet) -> Vec<Arc<Field>>;
#[salsa::invoke(document::selection_variables)]
fn selection_variables(&self, selection_set: SelectionSet) -> Arc<HashSet<Variable>>;
#[salsa::invoke(document::operation_definition_variables)]
fn operation_definition_variables(
&self,
variables: Arc<Vec<VariableDefinition>>,
) -> Arc<HashSet<Variable>>;
#[salsa::invoke(document::subtype_map)]
fn subtype_map(&self) -> Arc<HashMap<String, HashSet<String>>>;
#[salsa::transparent]
#[salsa::invoke(document::is_subtype)]
fn is_subtype(&self, abstract_type: String, maybe_subtype: String) -> bool;
}
fn type_system_definitions(db: &dyn HirDatabase) -> Arc<TypeSystemDefinitions> {
Arc::new(TypeSystemDefinitions {
schema: db.schema(),
scalars: db.scalars(),
objects: db.object_types_with_built_ins(),
interfaces: db.interfaces(),
unions: db.unions(),
enums: db.enums_with_built_ins(),
input_objects: db.input_objects(),
directives: db.directive_definitions(),
})
}
fn type_system(db: &dyn HirDatabase) -> Arc<TypeSystem> {
if let Some(precomputed_input) = db.type_system_hir_input() {
return precomputed_input;
}
Arc::new(TypeSystem {
definitions: db.type_system_definitions(),
type_definitions_by_name: db.types_definitions_by_name(),
subtype_map: db.subtype_map(),
inputs: db
.type_definition_files()
.into_iter()
.map(|file_id| (file_id, db.input(file_id)))
.collect(),
})
}
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)
.document()
.syntax()
.children()
.filter_map(ast::OperationDefinition::cast)
.filter_map(|def| operation_definition(db, def, file_id))
.map(Arc::new)
.collect(),
)
}
fn fragments(db: &dyn HirDatabase, file_id: FileId) -> ByName<FragmentDefinition> {
let mut map = IndexMap::new();
for def in db
.ast(file_id)
.document()
.syntax()
.children()
.filter_map(ast::FragmentDefinition::cast)
.filter_map(|def| fragment_definition(db, def, file_id))
{
let name = def.name().to_owned();
map.entry(name).or_insert_with(|| Arc::new(def));
}
Arc::new(map)
}
fn all_operations(db: &dyn HirDatabase) -> Arc<Vec<Arc<OperationDefinition>>> {
let mut operations = Vec::new();
for file_id in db.executable_definition_files() {
operations.extend(db.operations(file_id).iter().cloned())
}
Arc::new(operations)
}
fn all_fragments(db: &dyn HirDatabase) -> ByName<FragmentDefinition> {
let mut fragments = IndexMap::new();
for file_id in db.executable_definition_files() {
for (name, def) in db.fragments(file_id).iter() {
fragments.entry(name.clone()).or_insert_with(|| def.clone());
}
}
Arc::new(fragments)
}
fn type_definitions<'db, AstType, TryConvert, HirType>(
db: &'db dyn HirDatabase,
try_convert: TryConvert,
) -> impl Iterator<Item = HirType> + 'db
where
AstType: 'db + ast::AstNode,
TryConvert: 'db + Copy + Fn(&dyn HirDatabase, AstType, FileId) -> Option<HirType>,
{
db.type_definition_files()
.into_iter()
.flat_map(move |file_id| {
db.ast(file_id)
.document()
.syntax()
.children()
.filter_map(AstNode::cast)
.filter_map(move |def| try_convert(db, def, file_id))
})
}
fn schema(db: &dyn HirDatabase) -> Arc<SchemaDefinition> {
if let Some(precomputed) = db.type_system_hir_input() {
return precomputed.definitions.schema.clone();
}
Arc::new(
type_definitions(db, schema_definition)
.next()
.unwrap_or_else(|| implicit_schema_definition(db)),
)
}
macro_rules! by_name {
($db: ident, $convert: expr) => {{
let mut map = IndexMap::new();
for def in type_definitions($db, $convert) {
let name = def.name().to_owned();
map.entry(name).or_insert_with(|| Arc::new(def));
}
map
}};
}
macro_rules! by_name_extensible {
($db: ident, $convert: expr, $extension_type: ident) => {{
let mut map = by_name!($db, $convert);
for ext in $db.extensions().iter() {
if let TypeExtension::$extension_type(ext) = ext {
if let Some(def) = map.get_mut(ext.name()) {
Arc::get_mut(def).unwrap().push_extension(Arc::clone(ext))
}
}
}
map
}};
}
fn object_types_with_built_ins(db: &dyn HirDatabase) -> ByName<ObjectTypeDefinition> {
if let Some(precomputed) = db.type_system_hir_input() {
return precomputed.definitions.objects.clone();
}
Arc::new(by_name_extensible!(
db,
object_type_definition,
ObjectTypeExtension
))
}
fn object_types(db: &dyn HirDatabase) -> ByName<ObjectTypeDefinition> {
let mut objs = db.object_types_with_built_ins().as_ref().clone();
objs.retain(|_k, v| !v.is_introspection());
Arc::new(objs)
}
fn scalars(db: &dyn HirDatabase) -> ByName<ScalarTypeDefinition> {
if let Some(precomputed) = db.type_system_hir_input() {
return precomputed.definitions.scalars.clone();
}
Arc::new(by_name_extensible!(
db,
scalar_definition,
ScalarTypeExtension
))
}
fn enums_with_built_ins(db: &dyn HirDatabase) -> ByName<EnumTypeDefinition> {
if let Some(precomputed) = db.type_system_hir_input() {
return precomputed.definitions.enums.clone();
}
Arc::new(by_name_extensible!(db, enum_definition, EnumTypeExtension))
}
fn enums(db: &dyn HirDatabase) -> ByName<EnumTypeDefinition> {
let mut enums = db.enums_with_built_ins().as_ref().clone();
enums.retain(|_k, v| !v.is_introspection());
Arc::new(enums)
}
fn unions(db: &dyn HirDatabase) -> ByName<UnionTypeDefinition> {
if let Some(precomputed) = db.type_system_hir_input() {
return precomputed.definitions.unions.clone();
}
Arc::new(by_name_extensible!(
db,
union_definition,
UnionTypeExtension
))
}
fn interfaces(db: &dyn HirDatabase) -> ByName<InterfaceTypeDefinition> {
if let Some(precomputed) = db.type_system_hir_input() {
return precomputed.definitions.interfaces.clone();
}
Arc::new(by_name_extensible!(
db,
interface_definition,
InterfaceTypeExtension
))
}
fn input_objects(db: &dyn HirDatabase) -> ByName<InputObjectTypeDefinition> {
if let Some(precomputed) = db.type_system_hir_input() {
return precomputed.definitions.input_objects.clone();
}
Arc::new(by_name_extensible!(
db,
input_object_definition,
InputObjectTypeExtension
))
}
fn directive_definitions(db: &dyn HirDatabase) -> ByName<DirectiveDefinition> {
if let Some(precomputed) = db.type_system_hir_input() {
return precomputed.definitions.directives.clone();
}
Arc::new(by_name!(db, directive_definition))
}
fn operation_definition(
db: &dyn HirDatabase,
op_def: ast::OperationDefinition,
file_id: FileId,
) -> Option<OperationDefinition> {
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().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());
Some(OperationDefinition {
operation_ty: ty,
name,
variables,
selection_set,
directives,
loc,
})
}
fn fragment_definition(
db: &dyn HirDatabase,
fragment_def: ast::FragmentDefinition,
file_id: FileId,
) -> Option<FragmentDefinition> {
let name = name(fragment_def.fragment_name()?.name(), file_id)?;
let type_condition = fragment_def
.type_condition()?
.named_type()?
.name()?
.text()
.to_string();
let selection_set = selection_set(
db,
fragment_def.selection_set(),
Some(type_condition.clone()),
file_id,
);
let directives = directives(fragment_def.directives(), file_id);
let loc = location(file_id, fragment_def.syntax());
Some(FragmentDefinition {
name,
type_condition,
selection_set,
directives,
loc,
})
}
fn schema_definition(
db: &dyn HirDatabase,
schema_def: ast::SchemaDefinition,
file_id: FileId,
) -> Option<SchemaDefinition> {
let description = description(schema_def.description());
let directives = directives(schema_def.directives(), file_id);
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: 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
}
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_with_built_ins().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(
_db: &dyn HirDatabase,
obj_def: ast::ObjectTypeDefinition,
file_id: FileId,
) -> Option<ObjectTypeDefinition> {
let description = description(obj_def.description());
let name = name(obj_def.name(), file_id)?;
let implements_interfaces = implements_interfaces(obj_def.implements_interfaces(), file_id);
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);
let is_introspection = INTROSPECTION_OBJECT_TYS.contains(&obj_def.name()?.text().as_str());
let implicit_fields = Arc::new(vec![type_field(), typename_field(), schema_field()]);
Some(ObjectTypeDefinition {
description,
name,
implements_interfaces,
directives,
fields_definition,
loc,
extensions: Vec::new(),
fields_by_name,
implements_interfaces_by_name,
is_introspection,
implicit_fields,
})
}
fn object_type_extension(
_db: &dyn HirDatabase,
def: ast::ObjectTypeExtension,
file_id: FileId,
) -> 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(
db: &dyn HirDatabase,
scalar_def: ast::ScalarTypeDefinition,
file_id: FileId,
) -> Option<ScalarTypeDefinition> {
let description = description(scalar_def.description());
let name = name(scalar_def.name(), file_id)?;
let directives = directives(scalar_def.directives(), file_id);
let loc = location(file_id, scalar_def.syntax());
let built_in = db.input(file_id).source_type().is_built_in();
Some(ScalarTypeDefinition {
description,
name,
directives,
loc,
built_in,
extensions: Vec::new(),
})
}
fn scalar_extension(
_db: &dyn HirDatabase,
def: ast::ScalarTypeExtension,
file_id: FileId,
) -> 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(
_db: &dyn HirDatabase,
enum_def: ast::EnumTypeDefinition,
file_id: FileId,
) -> Option<EnumTypeDefinition> {
let description = description(enum_def.description());
let name = name(enum_def.name(), file_id)?;
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);
let is_introspection = INTROSPECTION_ENUM_TYS.contains(&enum_def.name()?.text().as_str());
Some(EnumTypeDefinition {
description,
name,
directives,
enum_values_definition,
loc,
extensions: Vec::new(),
values_by_name,
is_introspection,
})
}
fn enum_extension(
_db: &dyn HirDatabase,
def: ast::EnumTypeExtension,
file_id: FileId,
) -> 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(
enum_values_def: Option<ast::EnumValuesDefinition>,
file_id: FileId,
) -> Arc<Vec<EnumValueDefinition>> {
match enum_values_def {
Some(enum_values) => {
let enum_values = enum_values
.enum_value_definitions()
.filter_map(|e| enum_value_definition(e, file_id))
.collect();
Arc::new(enum_values)
}
None => Arc::new(Vec::new()),
}
}
fn enum_value_definition(
enum_value_def: ast::EnumValueDefinition,
file_id: FileId,
) -> Option<EnumValueDefinition> {
let description = description(enum_value_def.description());
let enum_value = enum_value(enum_value_def.enum_value(), file_id)?;
let directives = directives(enum_value_def.directives(), file_id);
let loc = location(file_id, enum_value_def.syntax());
Some(EnumValueDefinition {
description,
enum_value,
directives,
loc,
})
}
fn union_definition(
_db: &dyn HirDatabase,
union_def: ast::UnionTypeDefinition,
file_id: FileId,
) -> Option<UnionTypeDefinition> {
let description = description(union_def.description());
let name = name(union_def.name(), file_id)?;
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);
let implicit_fields = Arc::new(vec![typename_field()]);
Some(UnionTypeDefinition {
description,
name,
directives,
union_members,
loc,
extensions: Vec::new(),
members_by_name,
implicit_fields,
})
}
fn union_extension(
_db: &dyn HirDatabase,
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,
name,
union_members,
loc,
members_by_name,
}))
}
fn union_members(
union_members: Option<ast::UnionMemberTypes>,
file_id: FileId,
) -> Arc<Vec<UnionMember>> {
match union_members {
Some(members) => {
let mems = members
.named_types()
.filter_map(|u| union_member(u, file_id))
.collect();
Arc::new(mems)
}
None => Arc::new(Vec::new()),
}
}
fn union_member(member: ast::NamedType, file_id: FileId) -> Option<UnionMember> {
let name = name(member.name(), file_id)?;
let loc = location(file_id, member.syntax());
Some(UnionMember { name, loc })
}
fn interface_definition(
_db: &dyn HirDatabase,
interface_def: ast::InterfaceTypeDefinition,
file_id: FileId,
) -> Option<InterfaceTypeDefinition> {
let description = description(interface_def.description());
let name = name(interface_def.name(), file_id)?;
let implements_interfaces =
implements_interfaces(interface_def.implements_interfaces(), file_id);
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);
let implicit_fields = Arc::new(vec![typename_field()]);
Some(InterfaceTypeDefinition {
description,
name,
implements_interfaces,
directives,
fields_definition,
loc,
extensions: Vec::new(),
fields_by_name,
implements_interfaces_by_name,
implicit_fields,
})
}
fn interface_extension(
_db: &dyn HirDatabase,
def: ast::InterfaceTypeExtension,
file_id: FileId,
) -> 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(
_db: &dyn HirDatabase,
directive_def: ast::DirectiveDefinition,
file_id: FileId,
) -> Option<DirectiveDefinition> {
let name = name(directive_def.name(), file_id)?;
let description = description(directive_def.description());
let arguments = arguments_definition(directive_def.arguments_definition(), file_id);
let repeatable = directive_def.repeatable_token().is_some();
let directive_locations = directive_locations(directive_def.directive_locations());
let loc = location(file_id, directive_def.syntax());
Some(DirectiveDefinition {
description,
name,
arguments,
repeatable,
directive_locations,
loc,
})
}
fn input_object_definition(
_db: &dyn HirDatabase,
input_obj: ast::InputObjectTypeDefinition,
file_id: FileId,
) -> Option<InputObjectTypeDefinition> {
let description = description(input_obj.description());
let name = name(input_obj.name(), file_id)?;
let directives = directives(input_obj.directives(), file_id);
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);
Some(InputObjectTypeDefinition {
description,
name,
directives,
input_fields_definition,
loc,
extensions: Vec::new(),
input_fields_by_name,
})
}
fn input_object_extension(
_db: &dyn HirDatabase,
def: ast::InputObjectTypeExtension,
file_id: FileId,
) -> 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 type_field() -> FieldDefinition {
FieldDefinition {
description: None,
name: Name {
src: "__type".into(),
loc: None,
},
arguments: ArgumentsDefinition {
input_values: Arc::new(vec![InputValueDefinition {
description: None,
name: Name {
src: "name".into(),
loc: None,
},
ty: Type::NonNull {
ty: Box::new(Type::Named {
name: "String".into(),
loc: None,
}),
loc: None,
},
default_value: None,
directives: Arc::new(Vec::new()),
loc: None,
}]),
loc: None,
},
ty: Type::Named {
name: "__Type".into(),
loc: None,
},
directives: Arc::new(Vec::new()),
loc: None,
}
}
fn schema_field() -> FieldDefinition {
FieldDefinition {
description: None,
name: Name {
src: "__schema".into(),
loc: None,
},
arguments: ArgumentsDefinition {
input_values: Arc::new(Vec::new()),
loc: None,
},
ty: Type::NonNull {
ty: Box::new(Type::Named {
name: "__Schema".into(),
loc: None,
}),
loc: None,
},
directives: Arc::new(Vec::new()),
loc: None,
}
}
fn typename_field() -> FieldDefinition {
FieldDefinition {
description: None,
name: Name {
src: "__typename".into(),
loc: None,
},
arguments: ArgumentsDefinition {
input_values: Arc::new(Vec::new()),
loc: None,
},
ty: Type::NonNull {
ty: Box::new(Type::Named {
name: "String".into(),
loc: None,
}),
loc: None,
},
directives: Arc::new(Vec::new()),
loc: None,
}
}
fn implements_interfaces(
implements_interfaces: Option<ast::ImplementsInterfaces>,
file_id: FileId,
) -> Arc<Vec<ImplementsInterface>> {
let interfaces: Vec<ImplementsInterface> = implements_interfaces
.iter()
.flat_map(|interfaces| {
let types: Vec<ImplementsInterface> = interfaces
.named_types()
.filter_map(|n| {
let name = n.name()?;
Some(ImplementsInterface {
interface: name_hir_node(name, file_id),
loc: location(file_id, n.syntax()),
})
})
.collect();
types
})
.collect();
Arc::new(interfaces)
}
fn fields_definition(
fields_definition: Option<ast::FieldsDefinition>,
file_id: FileId,
) -> Arc<Vec<FieldDefinition>> {
match fields_definition {
Some(fields_def) => {
let fields: Vec<FieldDefinition> = fields_def
.field_definitions()
.filter_map(|f| field_definition(f, file_id))
.collect();
Arc::new(fields)
}
None => Arc::new(Vec::new()),
}
}
fn field_definition(field: ast::FieldDefinition, file_id: FileId) -> Option<FieldDefinition> {
let description = description(field.description());
let name = name(field.name(), file_id)?;
let arguments = arguments_definition(field.arguments_definition(), file_id);
let ty = ty(field.ty()?, file_id)?;
let directives = directives(field.directives(), file_id);
let loc = location(file_id, field.syntax());
Some(FieldDefinition {
description,
name,
arguments,
ty,
directives,
loc: Some(loc),
})
}
fn arguments_definition(
arguments_definition: Option<ast::ArgumentsDefinition>,
file_id: FileId,
) -> ArgumentsDefinition {
match arguments_definition {
Some(arguments) => {
let input_values =
input_value_definitions(arguments.input_value_definitions(), file_id);
let loc = location(file_id, arguments.syntax());
ArgumentsDefinition {
input_values,
loc: Some(loc),
}
}
None => ArgumentsDefinition {
input_values: Arc::new(Vec::new()),
loc: None,
},
}
}
fn input_fields_definition(
input_fields: Option<ast::InputFieldsDefinition>,
file_id: FileId,
) -> Arc<Vec<InputValueDefinition>> {
match input_fields {
Some(fields) => input_value_definitions(fields.input_value_definitions(), file_id),
None => Arc::new(Vec::new()),
}
}
fn input_value_definitions(
input_values: AstChildren<ast::InputValueDefinition>,
file_id: FileId,
) -> Arc<Vec<InputValueDefinition>> {
let input_values: Vec<InputValueDefinition> = input_values
.filter_map(|input| {
let description = description(input.description());
let name = name(input.name(), file_id)?;
let ty = ty(input.ty()?, file_id)?;
let default_value = default_value(input.default_value(), file_id);
let directives = directives(input.directives(), file_id);
let loc = location(file_id, input.syntax());
Some(InputValueDefinition {
description,
name,
ty,
default_value,
directives,
loc: Some(loc),
})
})
.collect();
Arc::new(input_values)
}
fn default_value(
default_value: Option<ast::DefaultValue>,
file_id: FileId,
) -> Option<DefaultValue> {
default_value
.and_then(|val| val.value())
.and_then(|val| value(val, file_id))
}
fn root_operation_type_definition(
root_type_def: AstChildren<ast::RootOperationTypeDefinition>,
file_id: FileId,
) -> Vec<RootOperationTypeDefinition> {
root_type_def
.into_iter()
.filter_map(|ty| {
if let Some(named_ty) = ty.named_type() {
let operation_type = operation_type(ty.operation_type());
let named_type = named_type(named_ty.name()?, file_id);
let loc = location(file_id, ty.syntax());
Some(RootOperationTypeDefinition {
operation_ty: operation_type,
named_type,
loc: Some(loc),
})
} else {
None
}
})
.collect()
}
fn operation_type(op_type: Option<ast::OperationType>) -> OperationType {
match op_type {
Some(ty) => {
if ty.query_token().is_some() {
OperationType::Query
} else if ty.mutation_token().is_some() {
OperationType::Mutation
} else if ty.subscription_token().is_some() {
OperationType::Subscription
} else {
OperationType::Query
}
}
None => OperationType::Query,
}
}
fn variable_definitions(
variable_definitions: Option<ast::VariableDefinitions>,
file_id: FileId,
) -> Arc<Vec<VariableDefinition>> {
match variable_definitions {
Some(vars) => {
let variable_definitions = vars
.variable_definitions()
.filter_map(|v| variable_definition(v, file_id))
.collect();
Arc::new(variable_definitions)
}
None => Arc::new(Vec::new()),
}
}
fn variable_definition(
var: ast::VariableDefinition,
file_id: FileId,
) -> Option<VariableDefinition> {
let name = name(var.variable()?.name(), file_id)?;
let directives = directives(var.directives(), file_id);
let default_value = default_value(var.default_value(), file_id);
let ty = ty(var.ty()?, file_id)?;
let loc = location(file_id, var.syntax());
Some(VariableDefinition {
name,
directives,
ty,
default_value,
loc,
})
}
fn ty(ty_: ast::Type, file_id: FileId) -> Option<Type> {
match ty_ {
ast::Type::NamedType(name) => name.name().map(|name| named_type(name, file_id)),
ast::Type::ListType(list) => Some(Type::List {
ty: Box::new(ty(list.ty()?, file_id)?),
loc: Some(location(file_id, list.syntax())),
}),
ast::Type::NonNullType(non_null) => {
if let Some(n) = non_null.named_type() {
let named_type = n.name().map(|name| named_type(name, file_id))?;
Some(Type::NonNull {
ty: Box::new(named_type),
loc: Some(location(file_id, n.syntax())),
})
} else if let Some(list) = non_null.list_type() {
let list_type = Type::List {
ty: Box::new(ty(list.ty()?, file_id)?),
loc: Some(location(file_id, list.syntax())),
};
Some(Type::NonNull {
ty: Box::new(list_type),
loc: Some(location(file_id, list.syntax())),
})
} else {
panic!("Parser should have caught this error");
}
}
}
}
fn named_type(name: ast::Name, file_id: FileId) -> Type {
Type::Named {
name: name.text().to_string(),
loc: Some(location(file_id, name.syntax())),
}
}
fn directive_locations(
directive_locations: Option<ast::DirectiveLocations>,
) -> Arc<Vec<DirectiveLocation>> {
match directive_locations {
Some(directive_loc) => {
let locations: Vec<DirectiveLocation> = directive_loc
.directive_locations()
.map(|loc| loc.into())
.collect();
Arc::new(locations)
}
None => Arc::new(Vec::new()),
}
}
fn directives(directives: Option<ast::Directives>, file_id: FileId) -> Arc<Vec<Directive>> {
match directives {
Some(directives) => {
let directives = directives
.directives()
.filter_map(|d| directive(d, file_id))
.collect();
Arc::new(directives)
}
None => Arc::new(Vec::new()),
}
}
fn directive(directive: ast::Directive, file_id: FileId) -> Option<Directive> {
let name = name(directive.name(), file_id)?;
let arguments = arguments(directive.arguments(), file_id);
let loc = location(file_id, directive.syntax());
Some(Directive {
name,
arguments,
loc,
})
}
fn arguments(arguments: Option<ast::Arguments>, file_id: FileId) -> Arc<Vec<Argument>> {
match arguments {
Some(arguments) => {
let arguments = arguments
.arguments()
.filter_map(|a| argument(a, file_id))
.collect();
Arc::new(arguments)
}
None => Arc::new(Vec::new()),
}
}
fn argument(argument: ast::Argument, file_id: FileId) -> Option<Argument> {
let name = name(argument.name(), file_id)?;
let value = value(argument.value()?, file_id)?;
let loc = location(file_id, argument.syntax());
Some(Argument { name, value, loc })
}
fn value(val: ast::Value, file_id: FileId) -> Option<Value> {
let hir_val = match val {
ast::Value::Variable(var) => Value::Variable(Variable {
name: var.name()?.text().to_string(),
loc: location(file_id, var.syntax()),
}),
ast::Value::StringValue(string_val) => Value::String {
loc: location(file_id, string_val.syntax()),
value: string_val.into(),
},
ast::Value::FloatValue(float) => Value::Float {
loc: location(file_id, float.syntax()),
value: Float::new(float.try_into().unwrap()),
},
ast::Value::IntValue(int) => Value::Int {
loc: location(file_id, int.syntax()),
value: Float::new(f64::try_from(int).unwrap()),
},
ast::Value::BooleanValue(bool) => Value::Boolean {
loc: location(file_id, bool.syntax()),
value: bool.try_into().unwrap(),
},
ast::Value::NullValue(null) => Value::Null {
loc: location(file_id, null.syntax()),
},
ast::Value::EnumValue(enum_) => Value::Enum {
loc: location(file_id, enum_.syntax()),
value: name(enum_.name(), file_id)?,
},
ast::Value::ListValue(list) => {
let li: Vec<Value> = list.values().filter_map(|v| value(v, file_id)).collect();
Value::List {
loc: location(file_id, list.syntax()),
value: li,
}
}
ast::Value::ObjectValue(object) => {
let object_values: Vec<(Name, Value)> = object
.object_fields()
.filter_map(|o| {
let name = name(o.name(), file_id)?;
let value = value(o.value()?, file_id)?;
Some((name, value))
})
.collect();
Value::Object {
loc: location(file_id, object.syntax()),
value: object_values,
}
}
};
Some(hir_val)
}
fn selection_set(
db: &dyn HirDatabase,
selections: Option<ast::SelectionSet>,
parent_obj_ty: Option<String>,
file_id: FileId,
) -> SelectionSet {
let selection_set = match selections {
Some(sel) => sel
.selections()
.filter_map(|sel| selection(db, sel, parent_obj_ty.as_ref().cloned(), file_id))
.collect(),
None => Vec::new(),
};
SelectionSet {
selection: Arc::new(selection_set),
}
}
fn selection(
db: &dyn HirDatabase,
selection: ast::Selection,
parent_obj_ty: Option<String>,
file_id: FileId,
) -> Option<Selection> {
match selection {
ast::Selection::Field(sel_field) => {
field(db, sel_field, parent_obj_ty, file_id).map(Selection::Field)
}
ast::Selection::FragmentSpread(fragment) => {
fragment_spread(db, fragment, parent_obj_ty, file_id).map(Selection::FragmentSpread)
}
ast::Selection::InlineFragment(fragment) => Some(Selection::InlineFragment(
inline_fragment(db, fragment, parent_obj_ty, file_id),
)),
}
}
fn inline_fragment(
db: &dyn HirDatabase,
fragment: ast::InlineFragment,
parent_obj: Option<String>,
file_id: FileId,
) -> Arc<InlineFragment> {
let type_condition = fragment.type_condition().and_then(|tc| {
let tc = tc.named_type()?.name()?;
Some(name_hir_node(tc, file_id))
});
let directives = directives(fragment.directives(), file_id);
let new_parent_obj = type_condition
.clone()
.map_or_else(|| parent_obj.clone(), |tc| Some(tc.src().to_string()));
let selection_set: SelectionSet =
selection_set(db, fragment.selection_set(), new_parent_obj, file_id);
let loc = location(file_id, fragment.syntax());
let fragment_data = InlineFragment {
type_condition,
directives,
selection_set,
parent_obj,
loc,
};
Arc::new(fragment_data)
}
fn fragment_spread(
_db: &dyn HirDatabase,
fragment: ast::FragmentSpread,
parent_obj: Option<String>,
file_id: FileId,
) -> Option<Arc<FragmentSpread>> {
let name = name(fragment.fragment_name()?.name(), file_id)?;
let directives = directives(fragment.directives(), file_id);
let loc = location(file_id, fragment.syntax());
let fragment_data = FragmentSpread {
name,
directives,
parent_obj,
loc,
};
Some(Arc::new(fragment_data))
}
fn field(
db: &dyn HirDatabase,
field: ast::Field,
parent_obj: Option<String>,
file_id: FileId,
) -> Option<Arc<Field>> {
let name = name(field.name(), file_id)?;
let alias = alias(field.alias());
let new_parent_obj = parent_ty(db, name.src(), parent_obj.clone());
let selection_set = selection_set(db, field.selection_set(), new_parent_obj, file_id);
let directives = directives(field.directives(), file_id);
let arguments = arguments(field.arguments(), file_id);
let loc = location(file_id, field.syntax());
let field_data = Field {
name,
alias,
selection_set,
parent_obj,
directives,
arguments,
loc,
};
Some(Arc::new(field_data))
}
fn parent_ty(db: &dyn HirDatabase, field_name: &str, parent_obj: Option<String>) -> Option<String> {
Some(
db.find_type_definition_by_name(parent_obj?)?
.field(db, field_name)?
.ty()
.name(),
)
}
fn name(name: Option<ast::Name>, file_id: FileId) -> Option<Name> {
name.map(|name| name_hir_node(name, file_id))
}
fn name_hir_node(name: ast::Name, file_id: FileId) -> Name {
Name {
src: name.text().to_string(),
loc: Some(location(file_id, name.syntax())),
}
}
fn enum_value(enum_value: Option<ast::EnumValue>, file_id: FileId) -> Option<Name> {
let name = enum_value?.name()?;
Some(name_hir_node(name, file_id))
}
fn description(description: Option<ast::Description>) -> Option<String> {
description.and_then(|desc| Some(desc.string_value()?.into()))
}
fn alias(alias: Option<ast::Alias>) -> Option<Arc<Alias>> {
alias.and_then(|alias| {
let name = alias.name()?.text().to_string();
let alias_data = Alias(name);
Some(Arc::new(alias_data))
})
}
fn location(file_id: FileId, syntax_node: &SyntaxNode) -> HirNodeLocation {
HirNodeLocation::new(file_id, syntax_node)
}