Unnamed repository; edit this file 'description' to name the repository.
Diffstat (limited to 'crates/hir-def/src/nameres/assoc.rs')
| -rw-r--r-- | crates/hir-def/src/nameres/assoc.rs | 325 |
1 files changed, 325 insertions, 0 deletions
diff --git a/crates/hir-def/src/nameres/assoc.rs b/crates/hir-def/src/nameres/assoc.rs new file mode 100644 index 0000000000..dea6d334f8 --- /dev/null +++ b/crates/hir-def/src/nameres/assoc.rs @@ -0,0 +1,325 @@ +//! Expansion of associated items + +use hir_expand::{ + name::Name, AstId, ExpandResult, InFile, Intern, Lookup, MacroCallKind, MacroDefKind, +}; +use smallvec::SmallVec; +use span::{HirFileId, MacroCallId}; +use syntax::{ast, Parse}; +use triomphe::Arc; + +use crate::{ + db::DefDatabase, + expander::{Expander, Mark}, + item_tree::{self, AssocItem, ItemTree, ItemTreeId, MacroCall, ModItem, TreeId}, + macro_call_as_call_id, + nameres::{ + attr_resolution::ResolvedAttr, + diagnostics::{DefDiagnostic, DefDiagnostics}, + DefMap, LocalDefMap, MacroSubNs, + }, + AssocItemId, AstIdWithPath, ConstLoc, FunctionId, FunctionLoc, ImplId, ItemContainerId, + ItemLoc, ModuleId, TraitId, TypeAliasId, TypeAliasLoc, +}; + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct TraitItems { + pub items: Box<[(Name, AssocItemId)]>, + // box it as the vec is usually empty anyways + pub macro_calls: Option<Box<Vec<(AstId<ast::Item>, MacroCallId)>>>, +} + +impl TraitItems { + #[inline] + pub(crate) fn trait_items_query(db: &dyn DefDatabase, tr: TraitId) -> Arc<TraitItems> { + db.trait_items_with_diagnostics(tr).0 + } + + pub(crate) fn trait_items_with_diagnostics_query( + db: &dyn DefDatabase, + tr: TraitId, + ) -> (Arc<TraitItems>, DefDiagnostics) { + let ItemLoc { container: module_id, id: tree_id } = tr.lookup(db); + let item_tree = tree_id.item_tree(db); + let tr_def = &item_tree[tree_id.value]; + + let mut collector = + AssocItemCollector::new(db, module_id, tree_id.file_id(), ItemContainerId::TraitId(tr)); + collector.collect(&item_tree, tree_id.tree_id(), &tr_def.items); + let (items, macro_calls, diagnostics) = collector.finish(); + + (Arc::new(TraitItems { macro_calls, items }), DefDiagnostics::new(diagnostics)) + } + + pub fn associated_types(&self) -> impl Iterator<Item = TypeAliasId> + '_ { + self.items.iter().filter_map(|(_name, item)| match item { + AssocItemId::TypeAliasId(t) => Some(*t), + _ => None, + }) + } + + pub fn associated_type_by_name(&self, name: &Name) -> Option<TypeAliasId> { + self.items.iter().find_map(|(item_name, item)| match item { + AssocItemId::TypeAliasId(t) if item_name == name => Some(*t), + _ => None, + }) + } + + pub fn method_by_name(&self, name: &Name) -> Option<FunctionId> { + self.items.iter().find_map(|(item_name, item)| match item { + AssocItemId::FunctionId(t) if item_name == name => Some(*t), + _ => None, + }) + } + + pub fn attribute_calls(&self) -> impl Iterator<Item = (AstId<ast::Item>, MacroCallId)> + '_ { + self.macro_calls.iter().flat_map(|it| it.iter()).copied() + } +} + +#[derive(Debug, PartialEq, Eq)] +pub struct ImplItems { + pub items: Box<[(Name, AssocItemId)]>, + // box it as the vec is usually empty anyways + pub macro_calls: Option<Box<Vec<(AstId<ast::Item>, MacroCallId)>>>, +} + +impl ImplItems { + #[inline] + pub(crate) fn impl_items_query(db: &dyn DefDatabase, id: ImplId) -> Arc<ImplItems> { + db.impl_items_with_diagnostics(id).0 + } + + pub(crate) fn impl_items_with_diagnostics_query( + db: &dyn DefDatabase, + id: ImplId, + ) -> (Arc<ImplItems>, DefDiagnostics) { + let _p = tracing::info_span!("impl_items_with_diagnostics_query").entered(); + let ItemLoc { container: module_id, id: tree_id } = id.lookup(db); + + let item_tree = tree_id.item_tree(db); + let impl_def = &item_tree[tree_id.value]; + let mut collector = + AssocItemCollector::new(db, module_id, tree_id.file_id(), ItemContainerId::ImplId(id)); + collector.collect(&item_tree, tree_id.tree_id(), &impl_def.items); + + let (items, macro_calls, diagnostics) = collector.finish(); + + (Arc::new(ImplItems { items, macro_calls }), DefDiagnostics::new(diagnostics)) + } + + pub fn attribute_calls(&self) -> impl Iterator<Item = (AstId<ast::Item>, MacroCallId)> + '_ { + self.macro_calls.iter().flat_map(|it| it.iter()).copied() + } +} + +struct AssocItemCollector<'a> { + db: &'a dyn DefDatabase, + module_id: ModuleId, + def_map: Arc<DefMap>, + local_def_map: Arc<LocalDefMap>, + diagnostics: Vec<DefDiagnostic>, + container: ItemContainerId, + expander: Expander, + + items: Vec<(Name, AssocItemId)>, + macro_calls: Vec<(AstId<ast::Item>, MacroCallId)>, +} + +impl<'a> AssocItemCollector<'a> { + fn new( + db: &'a dyn DefDatabase, + module_id: ModuleId, + file_id: HirFileId, + container: ItemContainerId, + ) -> Self { + let (def_map, local_def_map) = module_id.local_def_map(db); + Self { + db, + module_id, + def_map, + local_def_map, + container, + expander: Expander::new(db, file_id, module_id), + items: Vec::new(), + macro_calls: Vec::new(), + diagnostics: Vec::new(), + } + } + + fn finish( + self, + ) -> ( + Box<[(Name, AssocItemId)]>, + Option<Box<Vec<(AstId<ast::Item>, MacroCallId)>>>, + Vec<DefDiagnostic>, + ) { + ( + self.items.into_boxed_slice(), + if self.macro_calls.is_empty() { None } else { Some(Box::new(self.macro_calls)) }, + self.diagnostics, + ) + } + + fn collect(&mut self, item_tree: &ItemTree, tree_id: TreeId, assoc_items: &[AssocItem]) { + let container = self.container; + self.items.reserve(assoc_items.len()); + + 'items: for &item in assoc_items { + let attrs = item_tree.attrs(self.db, self.module_id.krate, ModItem::from(item).into()); + if !attrs.is_cfg_enabled(self.expander.cfg_options()) { + self.diagnostics.push(DefDiagnostic::unconfigured_code( + self.module_id.local_id, + tree_id, + ModItem::from(item).into(), + attrs.cfg().unwrap(), + self.expander.cfg_options().clone(), + )); + continue; + } + + 'attrs: for attr in &*attrs { + let ast_id = + AstId::new(self.expander.current_file_id(), item.ast_id(item_tree).upcast()); + let ast_id_with_path = AstIdWithPath { path: attr.path.clone(), ast_id }; + + match self.def_map.resolve_attr_macro( + &self.local_def_map, + self.db, + self.module_id.local_id, + ast_id_with_path, + attr, + ) { + Ok(ResolvedAttr::Macro(call_id)) => { + let loc = self.db.lookup_intern_macro_call(call_id); + if let MacroDefKind::ProcMacro(_, exp, _) = loc.def.kind { + // If there's no expander for the proc macro (e.g. the + // proc macro is ignored, or building the proc macro + // crate failed), skip expansion like we would if it was + // disabled. This is analogous to the handling in + // `DefCollector::collect_macros`. + if let Some(err) = exp.as_expand_error(self.module_id.krate) { + self.diagnostics.push(DefDiagnostic::macro_error( + self.module_id.local_id, + ast_id, + (*attr.path).clone(), + err, + )); + continue 'attrs; + } + } + + self.macro_calls.push((ast_id, call_id)); + let res = + self.expander.enter_expand_id::<ast::MacroItems>(self.db, call_id); + self.collect_macro_items(res); + continue 'items; + } + Ok(_) => (), + Err(_) => { + self.diagnostics.push(DefDiagnostic::unresolved_macro_call( + self.module_id.local_id, + MacroCallKind::Attr { + ast_id, + attr_args: None, + invoc_attr_index: attr.id, + }, + attr.path().clone(), + )); + } + } + } + + self.collect_item(item_tree, tree_id, container, item); + } + } + + fn collect_item( + &mut self, + item_tree: &ItemTree, + tree_id: TreeId, + container: ItemContainerId, + item: AssocItem, + ) { + match item { + AssocItem::Function(id) => { + let item = &item_tree[id]; + let def = + FunctionLoc { container, id: ItemTreeId::new(tree_id, id) }.intern(self.db); + self.items.push((item.name.clone(), def.into())); + } + AssocItem::TypeAlias(id) => { + let item = &item_tree[id]; + let def = + TypeAliasLoc { container, id: ItemTreeId::new(tree_id, id) }.intern(self.db); + self.items.push((item.name.clone(), def.into())); + } + AssocItem::Const(id) => { + let item = &item_tree[id]; + let Some(name) = item.name.clone() else { return }; + let def = ConstLoc { container, id: ItemTreeId::new(tree_id, id) }.intern(self.db); + self.items.push((name, def.into())); + } + AssocItem::MacroCall(call) => { + let file_id = self.expander.current_file_id(); + let MacroCall { ast_id, expand_to, ctxt, ref path } = item_tree[call]; + let module = self.expander.module.local_id; + + let resolver = |path: &_| { + self.def_map + .resolve_path( + &self.local_def_map, + self.db, + module, + path, + crate::item_scope::BuiltinShadowMode::Other, + Some(MacroSubNs::Bang), + ) + .0 + .take_macros() + .map(|it| self.db.macro_def(it)) + }; + match macro_call_as_call_id( + self.db.upcast(), + &AstIdWithPath::new(file_id, ast_id, Clone::clone(path)), + ctxt, + expand_to, + self.expander.krate(), + resolver, + ) { + Ok(Some(call_id)) => { + let res = + self.expander.enter_expand_id::<ast::MacroItems>(self.db, call_id); + self.macro_calls.push((InFile::new(file_id, ast_id.upcast()), call_id)); + self.collect_macro_items(res); + } + Ok(None) => (), + Err(_) => { + self.diagnostics.push(DefDiagnostic::unresolved_macro_call( + self.module_id.local_id, + MacroCallKind::FnLike { + ast_id: InFile::new(file_id, ast_id), + expand_to, + eager: None, + }, + Clone::clone(path), + )); + } + } + } + } + } + + fn collect_macro_items(&mut self, res: ExpandResult<Option<(Mark, Parse<ast::MacroItems>)>>) { + let Some((mark, _parse)) = res.value else { return }; + + let tree_id = item_tree::TreeId::new(self.expander.current_file_id(), None); + let item_tree = tree_id.item_tree(self.db); + let iter: SmallVec<[_; 2]> = + item_tree.top_level_items().iter().filter_map(ModItem::as_assoc_item).collect(); + + self.collect(&item_tree, tree_id, &iter); + + self.expander.exit(mark); + } +} |