diff options
| -rw-r--r-- | src/librustdoc/visit_ast.rs | 273 |
1 files changed, 115 insertions, 158 deletions
diff --git a/src/librustdoc/visit_ast.rs b/src/librustdoc/visit_ast.rs index a8fc85a3ee1..22068ebe041 100644 --- a/src/librustdoc/visit_ast.rs +++ b/src/librustdoc/visit_ast.rs @@ -5,11 +5,8 @@ use rustc_data_structures::fx::{FxHashMap, FxHashSet}; use rustc_hir as hir; use rustc_hir::def::{DefKind, Res}; use rustc_hir::def_id::DefId; -use rustc_hir::intravisit::{walk_item, Visitor}; use rustc_hir::Node; use rustc_hir::CRATE_HIR_ID; -use rustc_middle::hir::map::Map; -use rustc_middle::hir::nested_filter; use rustc_middle::ty::TyCtxt; use rustc_span::def_id::{CRATE_DEF_ID, LOCAL_CRATE}; use rustc_span::symbol::{kw, sym, Symbol}; @@ -60,6 +57,9 @@ pub(crate) fn inherits_doc_hidden(tcx: TyCtxt<'_>, mut node: hir::HirId) -> bool false } +// Also, is there some reason that this doesn't use the 'visit' +// framework from syntax?. + pub(crate) struct RustdocVisitor<'a, 'tcx> { cx: &'a mut core::DocContext<'tcx>, view_item_stack: FxHashSet<hir::HirId>, @@ -67,8 +67,6 @@ pub(crate) struct RustdocVisitor<'a, 'tcx> { /// Are the current module and all of its parents public? inside_public_path: bool, exact_paths: FxHashMap<DefId, Vec<Symbol>>, - modules: Vec<Module<'tcx>>, - map: Map<'tcx>, } impl<'a, 'tcx> RustdocVisitor<'a, 'tcx> { @@ -76,21 +74,12 @@ impl<'a, 'tcx> RustdocVisitor<'a, 'tcx> { // If the root is re-exported, terminate all recursion. let mut stack = FxHashSet::default(); stack.insert(hir::CRATE_HIR_ID); - let om = Module::new( - cx.tcx.crate_name(LOCAL_CRATE), - hir::CRATE_HIR_ID, - cx.tcx.hir().root_module().spans.inner_span, - ); - let map = cx.tcx.hir(); - RustdocVisitor { cx, view_item_stack: stack, inlining: false, inside_public_path: true, exact_paths: FxHashMap::default(), - modules: vec![om], - map, } } @@ -99,6 +88,101 @@ impl<'a, 'tcx> RustdocVisitor<'a, 'tcx> { self.exact_paths.entry(did).or_insert_with(|| def_id_to_path(tcx, did)); } + pub(crate) fn visit(mut self) -> Module<'tcx> { + let mut top_level_module = self.visit_mod_contents( + hir::CRATE_HIR_ID, + self.cx.tcx.hir().root_module(), + self.cx.tcx.crate_name(LOCAL_CRATE), + None, + ); + + // `#[macro_export] macro_rules!` items are reexported at the top level of the + // crate, regardless of where they're defined. We want to document the + // top level rexport of the macro, not its original definition, since + // the rexport defines the path that a user will actually see. Accordingly, + // we add the rexport as an item here, and then skip over the original + // definition in `visit_item()` below. + // + // We also skip `#[macro_export] macro_rules!` that have already been inserted, + // it can happen if within the same module a `#[macro_export] macro_rules!` + // is declared but also a reexport of itself producing two exports of the same + // macro in the same module. + let mut inserted = FxHashSet::default(); + for export in self.cx.tcx.module_reexports(CRATE_DEF_ID).unwrap_or(&[]) { + if let Res::Def(DefKind::Macro(_), def_id) = export.res { + if let Some(local_def_id) = def_id.as_local() { + if self.cx.tcx.has_attr(def_id, sym::macro_export) { + if inserted.insert(def_id) { + let item = self.cx.tcx.hir().expect_item(local_def_id); + top_level_module.items.push((item, None, None)); + } + } + } + } + } + + self.cx.cache.hidden_cfg = self + .cx + .tcx + .hir() + .attrs(CRATE_HIR_ID) + .iter() + .filter(|attr| attr.has_name(sym::doc)) + .flat_map(|attr| attr.meta_item_list().into_iter().flatten()) + .filter(|attr| attr.has_name(sym::cfg_hide)) + .flat_map(|attr| { + attr.meta_item_list() + .unwrap_or(&[]) + .iter() + .filter_map(|attr| { + Cfg::parse(attr.meta_item()?) + .map_err(|e| self.cx.sess().diagnostic().span_err(e.span, e.msg)) + .ok() + }) + .collect::<Vec<_>>() + }) + .chain( + [Cfg::Cfg(sym::test, None), Cfg::Cfg(sym::doc, None), Cfg::Cfg(sym::doctest, None)] + .into_iter(), + ) + .collect(); + + self.cx.cache.exact_paths = self.exact_paths; + top_level_module + } + + fn visit_mod_contents( + &mut self, + id: hir::HirId, + m: &'tcx hir::Mod<'tcx>, + name: Symbol, + parent_id: Option<hir::HirId>, + ) -> Module<'tcx> { + let mut om = Module::new(name, id, m.spans.inner_span); + let def_id = self.cx.tcx.hir().local_def_id(id).to_def_id(); + // Keep track of if there were any private modules in the path. + let orig_inside_public_path = self.inside_public_path; + self.inside_public_path &= self.cx.tcx.visibility(def_id).is_public(); + for &i in m.item_ids { + let item = self.cx.tcx.hir().item(i); + if matches!(item.kind, hir::ItemKind::Use(_, hir::UseKind::Glob)) { + continue; + } + self.visit_item(item, None, &mut om, parent_id); + } + for &i in m.item_ids { + let item = self.cx.tcx.hir().item(i); + // To match the way import precedence works, visit glob imports last. + // Later passes in rustdoc will de-duplicate by name and kind, so if glob- + // imported items appear last, then they'll be the ones that get discarded. + if matches!(item.kind, hir::ItemKind::Use(_, hir::UseKind::Glob)) { + self.visit_item(item, None, &mut om, parent_id); + } + } + self.inside_public_path = orig_inside_public_path; + om + } + /// Tries to resolve the target of a `pub use` statement and inlines the /// target if it is defined locally and would not be documented otherwise, /// or when it is specifically requested with `please_inline`. @@ -114,6 +198,7 @@ impl<'a, 'tcx> RustdocVisitor<'a, 'tcx> { res: Res, renamed: Option<Symbol>, glob: bool, + om: &mut Module<'tcx>, please_inline: bool, ) -> bool { debug!("maybe_inline_local res: {:?}", res); @@ -164,20 +249,20 @@ impl<'a, 'tcx> RustdocVisitor<'a, 'tcx> { let prev = mem::replace(&mut self.inlining, true); for &i in m.item_ids { let i = self.cx.tcx.hir().item(i); - self.visit_item_inner(i, None, Some(id)); + self.visit_item(i, None, om, Some(id)); } self.inlining = prev; true } Node::Item(it) if !glob => { let prev = mem::replace(&mut self.inlining, true); - self.visit_item_inner(it, renamed, Some(id)); + self.visit_item(it, renamed, om, Some(id)); self.inlining = prev; true } Node::ForeignItem(it) if !glob => { let prev = mem::replace(&mut self.inlining, true); - self.visit_foreign_item_inner(it, renamed); + self.visit_foreign_item(it, renamed, om); self.inlining = prev; true } @@ -187,12 +272,13 @@ impl<'a, 'tcx> RustdocVisitor<'a, 'tcx> { ret } - fn visit_item_inner( + fn visit_item( &mut self, item: &'tcx hir::Item<'_>, renamed: Option<Symbol>, + om: &mut Module<'tcx>, parent_id: Option<hir::HirId>, - ) -> bool { + ) { debug!("visiting item {:?}", item); let name = renamed.unwrap_or(item.ident.name); @@ -207,7 +293,7 @@ impl<'a, 'tcx> RustdocVisitor<'a, 'tcx> { hir::ItemKind::ForeignMod { items, .. } => { for item in items { let item = self.cx.tcx.hir().foreign_item(item.id); - self.visit_foreign_item_inner(item, None); + self.visit_foreign_item(item, None, om); } } // If we're inlining, skip private items or item reexported as "_". @@ -247,7 +333,7 @@ impl<'a, 'tcx> RustdocVisitor<'a, 'tcx> { } } - self.modules.last_mut().unwrap().items.push((item, renamed, parent_id)); + om.items.push((item, renamed, parent_id)) } } hir::ItemKind::Macro(ref macro_def, _) => { @@ -267,11 +353,11 @@ impl<'a, 'tcx> RustdocVisitor<'a, 'tcx> { let nonexported = !self.cx.tcx.has_attr(def_id, sym::macro_export); if is_macro_2_0 || nonexported || self.inlining { - self.modules.last_mut().unwrap().items.push((item, renamed, None)); + om.items.push((item, renamed, None)); } } hir::ItemKind::Mod(ref m) => { - self.enter_mod(item.hir_id(), m, name); + om.mods.push(self.visit_mod_contents(item.hir_id(), m, name, parent_id)); } hir::ItemKind::Fn(..) | hir::ItemKind::ExternCrate(..) @@ -282,162 +368,33 @@ impl<'a, 'tcx> RustdocVisitor<'a, 'tcx> { | hir::ItemKind::OpaqueTy(..) | hir::ItemKind::Static(..) | hir::ItemKind::Trait(..) - | hir::ItemKind::TraitAlias(..) => { - self.modules.last_mut().unwrap().items.push((item, renamed, parent_id)) - } + | hir::ItemKind::TraitAlias(..) => om.items.push((item, renamed, parent_id)), hir::ItemKind::Const(..) => { // Underscore constants do not correspond to a nameable item and // so are never useful in documentation. if name != kw::Underscore { - self.modules.last_mut().unwrap().items.push((item, renamed, parent_id)); + om.items.push((item, renamed, parent_id)); } } hir::ItemKind::Impl(impl_) => { // Don't duplicate impls when inlining or if it's implementing a trait, we'll pick // them up regardless of where they're located. if !self.inlining && impl_.of_trait.is_none() { - self.modules.last_mut().unwrap().items.push((item, None, None)); + om.items.push((item, None, None)); } } } - true } - fn visit_foreign_item_inner( + fn visit_foreign_item( &mut self, item: &'tcx hir::ForeignItem<'_>, renamed: Option<Symbol>, + om: &mut Module<'tcx>, ) { // If inlining we only want to include public functions. if !self.inlining || self.cx.tcx.visibility(item.owner_id).is_public() { - self.modules.last_mut().unwrap().foreigns.push((item, renamed)); + om.foreigns.push((item, renamed)); } } - - pub(crate) fn visit(mut self) -> Module<'tcx> { - let root_module = self.cx.tcx.hir().root_module(); - self.visit_mod_contents(CRATE_HIR_ID, root_module); - - let mut top_level_module = self.modules.pop().unwrap(); - - // `#[macro_export] macro_rules!` items are reexported at the top level of the - // crate, regardless of where they're defined. We want to document the - // top level rexport of the macro, not its original definition, since - // the rexport defines the path that a user will actually see. Accordingly, - // we add the rexport as an item here, and then skip over the original - // definition in `visit_item()` below. - // - // We also skip `#[macro_export] macro_rules!` that have already been inserted, - // it can happen if within the same module a `#[macro_export] macro_rules!` - // is declared but also a reexport of itself producing two exports of the same - // macro in the same module. - let mut inserted = FxHashSet::default(); - for export in self.cx.tcx.module_reexports(CRATE_DEF_ID).unwrap_or(&[]) { - if let Res::Def(DefKind::Macro(_), def_id) = export.res { - if let Some(local_def_id) = def_id.as_local() { - if self.cx.tcx.has_attr(def_id, sym::macro_export) { - if inserted.insert(def_id) { - let item = self.cx.tcx.hir().expect_item(local_def_id); - top_level_module.items.push((item, None, None)); - } - } - } - } - } - - self.cx.cache.hidden_cfg = self - .cx - .tcx - .hir() - .attrs(CRATE_HIR_ID) - .iter() - .filter(|attr| attr.has_name(sym::doc)) - .flat_map(|attr| attr.meta_item_list().into_iter().flatten()) - .filter(|attr| attr.has_name(sym::cfg_hide)) - .flat_map(|attr| { - attr.meta_item_list() - .unwrap_or(&[]) - .iter() - .filter_map(|attr| { - Cfg::parse(attr.meta_item()?) - .map_err(|e| self.cx.sess().diagnostic().span_err(e.span, e.msg)) - .ok() - }) - .collect::<Vec<_>>() - }) - .chain( - [Cfg::Cfg(sym::test, None), Cfg::Cfg(sym::doc, None), Cfg::Cfg(sym::doctest, None)] - .into_iter(), - ) - .collect(); - - self.cx.cache.exact_paths = self.exact_paths; - top_level_module - } - - /// This method will create a new module and push it onto the "modules stack" then call - /// `visit_mod_contents`. Once done, it'll remove it from the "modules stack" and instead - /// add into the list of modules of the current module. - fn enter_mod(&mut self, id: hir::HirId, m: &'tcx hir::Mod<'tcx>, name: Symbol) { - self.modules.push(Module::new(name, id, m.spans.inner_span)); - - self.visit_mod_contents(id, m); - - let last = self.modules.pop().unwrap(); - self.modules.last_mut().unwrap().mods.push(last); - } - - /// This method will go through the given module items in two passes: - /// 1. The items which are not glob imports/reexports. - /// 2. The glob imports/reexports. - fn visit_mod_contents(&mut self, id: hir::HirId, m: &'tcx hir::Mod<'tcx>) { - debug!("Going through module {:?}", m); - let def_id = self.cx.tcx.hir().local_def_id(id).to_def_id(); - // Keep track of if there were any private modules in the path. - let orig_inside_public_path = self.inside_public_path; - self.inside_public_path &= self.cx.tcx.visibility(def_id).is_public(); - - // Reimplementation of `walk_mod`: - for &i in m.item_ids { - let item = self.cx.tcx.hir().item(i); - if !matches!(item.kind, hir::ItemKind::Use(_, hir::UseKind::Glob)) { - self.visit_item(item); - } - } - for &i in m.item_ids { - let item = self.cx.tcx.hir().item(i); - // To match the way import precedence works, visit glob imports last. - // Later passes in rustdoc will de-duplicate by name and kind, so if glob- - // imported items appear last, then they'll be the ones that get discarded. - if matches!(item.kind, hir::ItemKind::Use(_, hir::UseKind::Glob)) { - self.visit_item(item); - } - } - self.inside_public_path = orig_inside_public_path; - } -} - -// We need to implement this visitor so it'll go everywhere and retrieve items we're interested in -// such as impl blocks in const blocks. -impl<'a, 'tcx> Visitor<'tcx> for RustdocVisitor<'a, 'tcx> { - type NestedFilter = nested_filter::All; - - fn nested_visit_map(&mut self) -> Self::Map { - self.map - } - - fn visit_item(&mut self, i: &'tcx hir::Item<'tcx>) { - let parent_id = if self.modules.len() > 1 { - Some(self.modules[self.modules.len() - 2].id) - } else { - None - }; - if self.visit_item_inner(i, None, parent_id) { - walk_item(self, i); - } - } - - fn visit_mod(&mut self, _: &hir::Mod<'tcx>, _: Span, _: hir::HirId) { - // handled in `visit_item_inner` - } } |
