//! Detecting diagnostic items. //! //! Diagnostic items are items that are not language-inherent, but can reasonably be expected to //! exist for diagnostic purposes. This allows diagnostic authors to refer to specific items //! directly, without having to guess module paths and crates. //! Examples are: //! //! * Traits like `Debug`, that have no bearing on language semantics //! //! * Compiler internal types like `Ty` and `TyCtxt` use rustc_ast::ast; use rustc_data_structures::fx::FxHashMap; use rustc_hir as hir; use rustc_hir::def_id::{DefId, LOCAL_CRATE}; use rustc_hir::itemlikevisit::ItemLikeVisitor; use rustc_middle::ty::query::Providers; use rustc_middle::ty::TyCtxt; use rustc_span::symbol::{sym, Symbol}; struct DiagnosticItemCollector<'tcx> { // items from this crate items: FxHashMap, tcx: TyCtxt<'tcx>, } impl<'v, 'tcx> ItemLikeVisitor<'v> for DiagnosticItemCollector<'tcx> { fn visit_item(&mut self, item: &hir::Item<'_>) { self.observe_item(&item.attrs, item.hir_id); } fn visit_trait_item(&mut self, trait_item: &hir::TraitItem<'_>) { self.observe_item(&trait_item.attrs, trait_item.hir_id); } fn visit_impl_item(&mut self, impl_item: &hir::ImplItem<'_>) { self.observe_item(&impl_item.attrs, impl_item.hir_id); } } impl<'tcx> DiagnosticItemCollector<'tcx> { fn new(tcx: TyCtxt<'tcx>) -> DiagnosticItemCollector<'tcx> { DiagnosticItemCollector { tcx, items: Default::default() } } fn observe_item(&mut self, attrs: &[ast::Attribute], hir_id: hir::HirId) { if let Some(name) = extract(attrs) { let def_id = self.tcx.hir().local_def_id(hir_id); // insert into our table collect_item(self.tcx, &mut self.items, name, def_id); } } } fn collect_item( tcx: TyCtxt<'_>, items: &mut FxHashMap, name: Symbol, item_def_id: DefId, ) { // Check for duplicates. if let Some(original_def_id) = items.insert(name, item_def_id) { if original_def_id != item_def_id { let mut err = match tcx.hir().span_if_local(item_def_id) { Some(span) => tcx.sess.struct_span_err( span, &format!("duplicate diagnostic item found: `{}`.", name), ), None => tcx.sess.struct_err(&format!( "duplicate diagnostic item in crate `{}`: `{}`.", tcx.crate_name(item_def_id.krate), name )), }; if let Some(span) = tcx.hir().span_if_local(original_def_id) { err.span_note(span, "the diagnostic item is first defined here"); } else { err.note(&format!( "the diagnostic item is first defined in crate `{}`.", tcx.crate_name(original_def_id.krate) )); } err.emit(); } } } /// Extract the first `rustc_diagnostic_item = "$name"` out of a list of attributes. fn extract(attrs: &[ast::Attribute]) -> Option { attrs.iter().find_map(|attr| { if attr.check_name(sym::rustc_diagnostic_item) { attr.value_str() } else { None } }) } /// Traverse and collect the diagnostic items in the current fn collect<'tcx>(tcx: TyCtxt<'tcx>) -> &'tcx FxHashMap { // Initialize the collector. let mut collector = DiagnosticItemCollector::new(tcx); // Collect diagnostic items in this crate. tcx.hir().krate().visit_all_item_likes(&mut collector); tcx.arena.alloc(collector.items) } /// Traverse and collect all the diagnostic items in all crates. fn collect_all<'tcx>(tcx: TyCtxt<'tcx>) -> &'tcx FxHashMap { // Initialize the collector. let mut collector = FxHashMap::default(); // Collect diagnostic items in other crates. for &cnum in tcx.crates().iter().chain(std::iter::once(&LOCAL_CRATE)) { for (&name, &def_id) in tcx.diagnostic_items(cnum).iter() { collect_item(tcx, &mut collector, name, def_id); } } tcx.arena.alloc(collector) } pub fn provide(providers: &mut Providers<'_>) { providers.diagnostic_items = |tcx, id| { assert_eq!(id, LOCAL_CRATE); collect(tcx) }; providers.all_diagnostic_items = |tcx, id| { assert_eq!(id, LOCAL_CRATE); collect_all(tcx) }; }