1use std::mem;
5
6use rustc_data_structures::fx::{FxHashSet, FxIndexMap};
7use rustc_hir as hir;
8use rustc_hir::def::{DefKind, Res};
9use rustc_hir::def_id::{DefId, DefIdMap, LocalDefId, LocalDefIdSet};
10use rustc_hir::intravisit::{Visitor, walk_body, walk_item};
11use rustc_hir::{CRATE_HIR_ID, Node};
12use rustc_middle::hir::nested_filter;
13use rustc_middle::ty::TyCtxt;
14use rustc_span::Span;
15use rustc_span::def_id::{CRATE_DEF_ID, LOCAL_CRATE};
16use rustc_span::hygiene::MacroKind;
17use rustc_span::symbol::{Symbol, kw, sym};
18use tracing::debug;
19
20use crate::clean::cfg::Cfg;
21use crate::clean::utils::{inherits_doc_hidden, should_ignore_res};
22use crate::clean::{NestedAttributesExt, hir_attr_lists, reexport_chain};
23use crate::core;
24
25#[derive(Debug)]
28pub(crate) struct Module<'hir> {
29 pub(crate) name: Symbol,
30 pub(crate) where_inner: Span,
31 pub(crate) mods: Vec<Module<'hir>>,
32 pub(crate) def_id: LocalDefId,
33 pub(crate) renamed: Option<Symbol>,
34 pub(crate) import_id: Option<LocalDefId>,
35 pub(crate) items: FxIndexMap<
56 (LocalDefId, Option<Symbol>),
57 (&'hir hir::Item<'hir>, Option<Symbol>, Vec<LocalDefId>),
58 >,
59
60 pub(crate) inlined_foreigns: FxIndexMap<(DefId, Option<Symbol>), (Res, LocalDefId)>,
69 pub(crate) foreigns: Vec<(&'hir hir::ForeignItem<'hir>, Option<Symbol>, Option<LocalDefId>)>,
71}
72
73impl Module<'_> {
74 pub(crate) fn new(
75 name: Symbol,
76 def_id: LocalDefId,
77 where_inner: Span,
78 renamed: Option<Symbol>,
79 import_id: Option<LocalDefId>,
80 ) -> Self {
81 Module {
82 name,
83 def_id,
84 where_inner,
85 renamed,
86 import_id,
87 mods: Vec::new(),
88 items: FxIndexMap::default(),
89 inlined_foreigns: FxIndexMap::default(),
90 foreigns: Vec::new(),
91 }
92 }
93
94 pub(crate) fn where_outer(&self, tcx: TyCtxt<'_>) -> Span {
95 tcx.def_span(self.def_id)
96 }
97}
98
99fn def_id_to_path(tcx: TyCtxt<'_>, did: DefId) -> Vec<Symbol> {
101 let crate_name = tcx.crate_name(did.krate);
102 let relative = tcx.def_path(did).data.into_iter().filter_map(|elem| elem.data.get_opt_name());
103 std::iter::once(crate_name).chain(relative).collect()
104}
105
106pub(crate) struct RustdocVisitor<'a, 'tcx> {
107 cx: &'a mut core::DocContext<'tcx>,
108 view_item_stack: LocalDefIdSet,
109 inlining: bool,
110 inside_public_path: bool,
112 exact_paths: DefIdMap<Vec<Symbol>>,
113 modules: Vec<Module<'tcx>>,
114 is_importable_from_parent: bool,
115 inside_body: bool,
116}
117
118impl<'a, 'tcx> RustdocVisitor<'a, 'tcx> {
119 pub(crate) fn new(cx: &'a mut core::DocContext<'tcx>) -> RustdocVisitor<'a, 'tcx> {
120 let mut stack = LocalDefIdSet::default();
122 stack.insert(CRATE_DEF_ID);
123 let om = Module::new(
124 cx.tcx.crate_name(LOCAL_CRATE),
125 CRATE_DEF_ID,
126 cx.tcx.hir_root_module().spans.inner_span,
127 None,
128 None,
129 );
130
131 RustdocVisitor {
132 cx,
133 view_item_stack: stack,
134 inlining: false,
135 inside_public_path: true,
136 exact_paths: Default::default(),
137 modules: vec![om],
138 is_importable_from_parent: true,
139 inside_body: false,
140 }
141 }
142
143 fn store_path(&mut self, did: DefId) {
144 let tcx = self.cx.tcx;
145 self.exact_paths.entry(did).or_insert_with(|| def_id_to_path(tcx, did));
146 }
147
148 pub(crate) fn visit(mut self) -> Module<'tcx> {
149 let root_module = self.cx.tcx.hir_root_module();
150 self.visit_mod_contents(CRATE_DEF_ID, root_module);
151
152 let mut top_level_module = self.modules.pop().unwrap();
153
154 let mut inserted = FxHashSet::default();
166 for child in self.cx.tcx.module_children_local(CRATE_DEF_ID) {
167 if !child.reexport_chain.is_empty()
168 && let Res::Def(DefKind::Macro(_), def_id) = child.res
169 && let Some(local_def_id) = def_id.as_local()
170 && self.cx.tcx.has_attr(def_id, sym::macro_export)
171 && inserted.insert(def_id)
172 {
173 let item = self.cx.tcx.hir_expect_item(local_def_id);
174 let (ident, _, _) = item.expect_macro();
175 top_level_module
176 .items
177 .insert((local_def_id, Some(ident.name)), (item, None, Vec::new()));
178 }
179 }
180
181 self.cx.cache.hidden_cfg = self
182 .cx
183 .tcx
184 .hir_attrs(CRATE_HIR_ID)
185 .iter()
186 .filter(|attr| attr.has_name(sym::doc))
187 .flat_map(|attr| attr.meta_item_list().into_iter().flatten())
188 .filter(|attr| attr.has_name(sym::cfg_hide))
189 .flat_map(|attr| {
190 attr.meta_item_list()
191 .unwrap_or(&[])
192 .iter()
193 .filter_map(|attr| {
194 Cfg::parse(attr)
195 .map_err(|e| self.cx.sess().dcx().span_err(e.span, e.msg))
196 .ok()
197 })
198 .collect::<Vec<_>>()
199 })
200 .chain([
201 Cfg::Cfg(sym::test, None),
202 Cfg::Cfg(sym::doc, None),
203 Cfg::Cfg(sym::doctest, None),
204 ])
205 .collect();
206
207 self.cx.cache.exact_paths = self.exact_paths;
208 top_level_module
209 }
210
211 fn visit_mod_contents(&mut self, def_id: LocalDefId, m: &'tcx hir::Mod<'tcx>) {
215 debug!("Going through module {m:?}");
216 let orig_inside_public_path = self.inside_public_path;
218 self.inside_public_path &= self.cx.tcx.local_visibility(def_id).is_public();
219
220 for &i in m.item_ids {
223 let item = self.cx.tcx.hir_item(i);
224 if !matches!(item.kind, hir::ItemKind::Use(_, hir::UseKind::Glob)) {
225 self.visit_item(item);
226 }
227 }
228 for &i in m.item_ids {
229 let item = self.cx.tcx.hir_item(i);
230 if matches!(item.kind, hir::ItemKind::Use(_, hir::UseKind::Glob)) {
234 self.visit_item(item);
235 }
236 }
237 self.inside_public_path = orig_inside_public_path;
238 debug!("Leaving module {m:?}");
239 }
240
241 fn maybe_inline_local(
251 &mut self,
252 def_id: LocalDefId,
253 res: Res,
254 renamed: Option<Symbol>,
255 please_inline: bool,
256 ) -> bool {
257 debug!("maybe_inline_local (renamed: {renamed:?}) res: {res:?}");
258
259 if renamed == Some(kw::Underscore) {
260 return false;
262 }
263
264 if self.cx.is_json_output() {
265 return false;
266 }
267
268 let tcx = self.cx.tcx;
269 let Some(ori_res_did) = res.opt_def_id() else {
270 return false;
271 };
272
273 let document_hidden = self.cx.render_options.document_hidden;
274 let use_attrs = tcx.hir_attrs(tcx.local_def_id_to_hir_id(def_id));
275 let is_no_inline = hir_attr_lists(use_attrs, sym::doc).has_word(sym::no_inline)
277 || (document_hidden && hir_attr_lists(use_attrs, sym::doc).has_word(sym::hidden));
278
279 if is_no_inline {
280 return false;
281 }
282
283 let is_glob = renamed.is_none();
284 let is_hidden = !document_hidden && tcx.is_doc_hidden(ori_res_did);
285 let Some(res_did) = ori_res_did.as_local() else {
286 crate::visit_lib::lib_embargo_visit_item(self.cx, ori_res_did);
291 if is_hidden || is_glob {
292 return false;
293 }
294 self.modules
300 .last_mut()
301 .unwrap()
302 .inlined_foreigns
303 .insert((ori_res_did, renamed), (res, def_id));
304 return true;
305 };
306
307 let is_private = !self.cx.cache.effective_visibilities.is_directly_public(tcx, ori_res_did);
308 let item = tcx.hir_node_by_def_id(res_did);
309
310 if !please_inline {
311 let inherits_hidden = !document_hidden && inherits_doc_hidden(tcx, res_did, None);
312 if (!is_private && !inherits_hidden) || (
314 is_hidden &&
315 !matches!(item, Node::Item(&hir::Item { kind: hir::ItemKind::Mod(..), .. }))
318 ) ||
319 self.reexport_public_and_not_hidden(def_id, res_did)
321 {
322 return false;
323 }
324 }
325
326 let is_bang_macro = matches!(
327 item,
328 Node::Item(&hir::Item { kind: hir::ItemKind::Macro(_, _, MacroKind::Bang), .. })
329 );
330
331 if !self.view_item_stack.insert(res_did) && !is_bang_macro {
332 return false;
333 }
334
335 let inlined = match item {
336 Node::Item(_) if is_bang_macro && !please_inline && !is_glob && is_hidden => {
340 return false;
341 }
342 Node::Item(&hir::Item { kind: hir::ItemKind::Mod(_, m), .. }) if is_glob => {
343 let prev = mem::replace(&mut self.inlining, true);
344 for &i in m.item_ids {
345 let i = tcx.hir_item(i);
346 self.visit_item_inner(i, None, Some(def_id));
347 }
348 self.inlining = prev;
349 true
350 }
351 Node::Item(it) if !is_glob => {
352 let prev = mem::replace(&mut self.inlining, true);
353 self.visit_item_inner(it, renamed, Some(def_id));
354 self.inlining = prev;
355 true
356 }
357 Node::ForeignItem(it) if !is_glob => {
358 let prev = mem::replace(&mut self.inlining, true);
359 self.visit_foreign_item_inner(it, renamed, Some(def_id));
360 self.inlining = prev;
361 true
362 }
363 _ => false,
364 };
365 self.view_item_stack.remove(&res_did);
366 if inlined {
367 self.cx.cache.inlined_items.insert(ori_res_did);
368 }
369 inlined
370 }
371
372 fn reexport_public_and_not_hidden(
377 &self,
378 import_def_id: LocalDefId,
379 target_def_id: LocalDefId,
380 ) -> bool {
381 if self.cx.render_options.document_hidden {
382 return true;
383 }
384 let tcx = self.cx.tcx;
385 let item_def_id = reexport_chain(tcx, import_def_id, target_def_id.to_def_id())
386 .iter()
387 .flat_map(|reexport| reexport.id())
388 .map(|id| id.expect_local())
389 .nth(1)
390 .unwrap_or(target_def_id);
391 item_def_id != import_def_id
392 && self.cx.cache.effective_visibilities.is_directly_public(tcx, item_def_id.to_def_id())
393 && !tcx.is_doc_hidden(item_def_id)
394 && !inherits_doc_hidden(tcx, item_def_id, None)
395 }
396
397 #[inline]
398 fn add_to_current_mod(
399 &mut self,
400 item: &'tcx hir::Item<'_>,
401 mut renamed: Option<Symbol>,
402 import_id: Option<LocalDefId>,
403 ) {
404 if self.is_importable_from_parent
405 || match item.kind {
408 hir::ItemKind::Impl(..) => true,
409 hir::ItemKind::Macro(_, _, MacroKind::Bang) => {
410 self.cx.tcx.has_attr(item.owner_id.def_id, sym::macro_export)
411 }
412 _ => false,
413 }
414 {
415 if renamed == item.kind.ident().map(|ident| ident.name) {
416 renamed = None;
417 }
418 let key = (item.owner_id.def_id, renamed);
419 if let Some(import_id) = import_id {
420 self.modules
421 .last_mut()
422 .unwrap()
423 .items
424 .entry(key)
425 .and_modify(|v| v.2.push(import_id))
426 .or_insert_with(|| (item, renamed, vec![import_id]));
427 } else {
428 self.modules.last_mut().unwrap().items.insert(key, (item, renamed, Vec::new()));
429 }
430 }
431 }
432
433 fn visit_item_inner(
434 &mut self,
435 item: &'tcx hir::Item<'_>,
436 renamed: Option<Symbol>,
437 import_id: Option<LocalDefId>,
438 ) {
439 debug!("visiting item {item:?}");
440 if self.inside_body {
441 if let hir::ItemKind::Impl(impl_) = item.kind &&
452 impl_.of_trait.is_none()
455 {
456 self.add_to_current_mod(item, None, None);
457 }
458 return;
459 }
460 let get_name = || renamed.unwrap_or(item.kind.ident().unwrap().name);
461 let tcx = self.cx.tcx;
462
463 let def_id = item.owner_id.to_def_id();
464 let is_pub = tcx.visibility(def_id).is_public();
465
466 if is_pub {
467 self.store_path(item.owner_id.to_def_id());
468 }
469
470 match item.kind {
471 hir::ItemKind::ForeignMod { items, .. } => {
472 for &item in items {
473 let item = tcx.hir_foreign_item(item);
474 self.visit_foreign_item_inner(item, None, None);
475 }
476 }
477 _ if self.inlining && !is_pub => {}
479 hir::ItemKind::GlobalAsm { .. } => {}
480 hir::ItemKind::Use(_, hir::UseKind::ListStem) => {}
481 hir::ItemKind::Use(path, kind) => {
482 for res in path.res.present_items() {
483 if should_ignore_res(res) {
486 continue;
487 }
488
489 let attrs = tcx.hir_attrs(tcx.local_def_id_to_hir_id(item.owner_id.def_id));
490
491 if is_pub && self.inside_public_path {
494 let please_inline = attrs.iter().any(|item| match item.meta_item_list() {
495 Some(ref list) if item.has_name(sym::doc) => {
496 list.iter().any(|i| i.has_name(sym::inline))
497 }
498 _ => false,
499 });
500 let ident = match kind {
501 hir::UseKind::Single(ident) => Some(ident.name),
502 hir::UseKind::Glob => None,
503 hir::UseKind::ListStem => unreachable!(),
504 };
505 if self.maybe_inline_local(item.owner_id.def_id, res, ident, please_inline)
506 {
507 debug!("Inlining {:?}", item.owner_id.def_id);
508 continue;
509 }
510 }
511 self.add_to_current_mod(item, renamed, import_id);
512 }
513 }
514 hir::ItemKind::Macro(_, macro_def, _) => {
515 let def_id = item.owner_id.to_def_id();
527 let is_macro_2_0 = !macro_def.macro_rules;
528 let nonexported = !tcx.has_attr(def_id, sym::macro_export);
529
530 if is_macro_2_0 || nonexported || self.inlining {
531 self.add_to_current_mod(item, renamed, import_id);
532 }
533 }
534 hir::ItemKind::Mod(_, m) => {
535 self.enter_mod(item.owner_id.def_id, m, get_name(), renamed, import_id);
536 }
537 hir::ItemKind::Fn { .. }
538 | hir::ItemKind::ExternCrate(..)
539 | hir::ItemKind::Enum(..)
540 | hir::ItemKind::Struct(..)
541 | hir::ItemKind::Union(..)
542 | hir::ItemKind::TyAlias(..)
543 | hir::ItemKind::Static(..)
544 | hir::ItemKind::Trait(..)
545 | hir::ItemKind::TraitAlias(..) => {
546 self.add_to_current_mod(item, renamed, import_id);
547 }
548 hir::ItemKind::Const(..) => {
549 if get_name() != kw::Underscore {
552 self.add_to_current_mod(item, renamed, import_id);
553 }
554 }
555 hir::ItemKind::Impl(impl_) => {
556 if !self.inlining && impl_.of_trait.is_none() {
559 self.add_to_current_mod(item, None, None);
560 }
561 }
562 }
563 }
564
565 fn visit_foreign_item_inner(
566 &mut self,
567 item: &'tcx hir::ForeignItem<'_>,
568 renamed: Option<Symbol>,
569 import_id: Option<LocalDefId>,
570 ) {
571 if !self.inlining || self.cx.tcx.visibility(item.owner_id).is_public() {
573 self.modules.last_mut().unwrap().foreigns.push((item, renamed, import_id));
574 }
575 }
576
577 fn enter_mod(
581 &mut self,
582 id: LocalDefId,
583 m: &'tcx hir::Mod<'tcx>,
584 name: Symbol,
585 renamed: Option<Symbol>,
586 import_id: Option<LocalDefId>,
587 ) {
588 self.modules.push(Module::new(name, id, m.spans.inner_span, renamed, import_id));
589
590 self.visit_mod_contents(id, m);
591
592 let last = self.modules.pop().unwrap();
593 self.modules.last_mut().unwrap().mods.push(last);
594 }
595}
596
597impl<'tcx> Visitor<'tcx> for RustdocVisitor<'_, 'tcx> {
600 type NestedFilter = nested_filter::All;
601
602 fn maybe_tcx(&mut self) -> Self::MaybeTyCtxt {
603 self.cx.tcx
604 }
605
606 fn visit_item(&mut self, i: &'tcx hir::Item<'tcx>) {
607 self.visit_item_inner(i, None, None);
608 let new_value = self.is_importable_from_parent
609 && matches!(
610 i.kind,
611 hir::ItemKind::Mod(..)
612 | hir::ItemKind::ForeignMod { .. }
613 | hir::ItemKind::Impl(..)
614 | hir::ItemKind::Trait(..)
615 );
616 let prev = mem::replace(&mut self.is_importable_from_parent, new_value);
617 walk_item(self, i);
618 self.is_importable_from_parent = prev;
619 }
620
621 fn visit_mod(&mut self, _: &hir::Mod<'tcx>, _: Span, _: hir::HirId) {
622 }
624
625 fn visit_use(&mut self, _: &hir::UsePath<'tcx>, _: hir::HirId) {
626 }
628
629 fn visit_path(&mut self, _: &hir::Path<'tcx>, _: hir::HirId) {
630 }
632
633 fn visit_label(&mut self, _: &rustc_ast::Label) {
634 }
636
637 fn visit_infer(
638 &mut self,
639 _inf_id: hir::HirId,
640 _inf_span: Span,
641 _kind: hir::intravisit::InferKind<'tcx>,
642 ) -> Self::Result {
643 }
645
646 fn visit_lifetime(&mut self, _: &hir::Lifetime) {
647 }
649
650 fn visit_body(&mut self, b: &hir::Body<'tcx>) {
651 let prev = mem::replace(&mut self.inside_body, true);
652 walk_body(self, b);
653 self.inside_body = prev;
654 }
655}