1use std::mem;
5
6use rustc_ast::attr::AttributeExt;
7use rustc_attr_ir::{DocInline, find_attr};
8use rustc_data_structures::fx::{FxHashSet, FxIndexMap};
9use rustc_hir::def::{DefKind, MacroKinds, Res};
10use rustc_hir::def_id::{DefId, DefIdMap, LocalDefId, LocalDefIdSet};
11use rustc_hir::intravisit::{Visitor, walk_body, walk_item};
12use rustc_hir::{self as hir, HirId, Node};
13use rustc_middle::hir::nested_filter;
14use rustc_middle::ty::TyCtxt;
15use rustc_span::Span;
16use rustc_span::def_id::{CRATE_DEF_ID, LOCAL_CRATE, LocalModId};
17use rustc_span::symbol::{Symbol, kw};
18use tracing::{debug, instrument, trace};
19
20use crate::clean::reexport_chain;
21use crate::clean::utils::{inherits_doc_hidden, should_ignore_res};
22use crate::core;
23
24#[derive(Debug)]
27pub(crate) struct Module<'hir> {
28 pub(crate) name: Symbol,
29 pub(crate) where_inner: Span,
30 pub(crate) mods: Vec<Module<'hir>>,
31 pub(crate) def_id: LocalDefId,
32 pub(crate) renamed: Option<Symbol>,
33 pub(crate) import_id: Option<LocalDefId>,
34 pub(crate) items: FxIndexMap<(LocalDefId, Option<Symbol>), ItemEntry<'hir>>,
54
55 pub(crate) inlined_foreigns: FxIndexMap<(DefId, Option<Symbol>), InlinedForeign>,
64 pub(crate) foreigns: Vec<Foreign<'hir>>,
66}
67
68#[derive(Debug)]
69pub(crate) struct ItemEntry<'hir> {
70 pub(crate) item: &'hir hir::Item<'hir>,
71 pub(crate) renamed: Option<Symbol>,
72 pub(crate) import_ids: Vec<LocalDefId>,
73}
74
75#[derive(Debug)]
76pub(crate) struct InlinedForeign {
77 pub(crate) res: Res,
78 pub(crate) import_id: LocalDefId,
79}
80
81#[derive(Debug)]
82pub(crate) struct Foreign<'hir> {
83 pub(crate) item: &'hir hir::ForeignItem<'hir>,
84 pub(crate) renamed: Option<Symbol>,
85 pub(crate) import_id: Option<LocalDefId>,
86}
87
88impl Module<'_> {
89 pub(crate) fn new(
90 name: Symbol,
91 def_id: LocalDefId,
92 where_inner: Span,
93 renamed: Option<Symbol>,
94 import_id: Option<LocalDefId>,
95 ) -> Self {
96 Module {
97 name,
98 def_id,
99 where_inner,
100 renamed,
101 import_id,
102 mods: Vec::new(),
103 items: FxIndexMap::default(),
104 inlined_foreigns: FxIndexMap::default(),
105 foreigns: Vec::new(),
106 }
107 }
108
109 pub(crate) fn where_outer(&self, tcx: TyCtxt<'_>) -> Span {
110 tcx.def_span(self.def_id)
111 }
112}
113
114fn def_id_to_path(tcx: TyCtxt<'_>, did: DefId) -> Vec<Symbol> {
116 let crate_name = tcx.crate_name(did.krate);
117 let relative = tcx.def_path(did).data.into_iter().filter_map(|elem| elem.data.get_opt_name());
118 std::iter::once(crate_name).chain(relative).collect()
119}
120
121#[derive(Copy, Clone)]
122enum GlobMode {
123 Everything,
125 NoGlob,
127 Only,
129}
130
131pub(crate) struct RustdocVisitor<'a, 'tcx> {
132 cx: &'a mut core::DocContext<'tcx>,
133 view_item_stack: LocalDefIdSet,
134 inlining: bool,
135 inside_public_path: bool,
137 exact_paths: DefIdMap<Vec<Symbol>>,
138 modules: Vec<Module<'tcx>>,
139 is_importable_from_parent: bool,
140 inside_body: bool,
141 glob_mode: GlobMode,
142}
143
144impl<'a, 'tcx> RustdocVisitor<'a, 'tcx> {
145 pub(crate) fn new(cx: &'a mut core::DocContext<'tcx>) -> RustdocVisitor<'a, 'tcx> {
146 let mut stack = LocalDefIdSet::default();
148 stack.insert(CRATE_DEF_ID);
149 let om = Module::new(
150 cx.tcx.crate_name(LOCAL_CRATE),
151 CRATE_DEF_ID,
152 cx.tcx.hir_root_module().spans.inner_span,
153 None,
154 None,
155 );
156
157 RustdocVisitor {
158 cx,
159 view_item_stack: stack,
160 inlining: false,
161 inside_public_path: true,
162 exact_paths: Default::default(),
163 modules: vec![om],
164 is_importable_from_parent: true,
165 inside_body: false,
166 glob_mode: GlobMode::Everything,
167 }
168 }
169
170 fn store_path(&mut self, did: DefId) {
171 let tcx = self.cx.tcx;
172 self.exact_paths.entry(did).or_insert_with(|| def_id_to_path(tcx, did));
173 }
174
175 pub(crate) fn visit(mut self) -> Module<'tcx> {
176 let root_module = self.cx.tcx.hir_root_module();
177 self.visit_mod_contents(CRATE_DEF_ID, root_module);
178
179 let mut top_level_module = self.modules.pop().unwrap();
180
181 let mut inserted = FxHashSet::default();
193 for child in self.cx.tcx.module_children_local(CRATE_DEF_ID) {
194 if !child.reexport_chain.is_empty()
195 && let Res::Def(DefKind::Macro(_), def_id) = child.res
196 && let Some(local_def_id) = def_id.as_local()
197 && find_attr!(self.cx.tcx, def_id, MacroExport { .. })
198 && inserted.insert(def_id)
199 {
200 let item = self.cx.tcx.hir_expect_item(local_def_id);
201 let (ident, _, _) = item.expect_macro();
202 top_level_module.items.insert(
203 (local_def_id, Some(ident.name)),
204 ItemEntry { item, renamed: None, import_ids: Vec::new() },
205 );
206 }
207 }
208
209 self.cx.cache.exact_paths = self.exact_paths;
210 top_level_module
211 }
212
213 #[instrument(level = "debug", skip(self))]
217 fn visit_mod_contents(&mut self, def_id: LocalDefId, m: &'tcx hir::Mod<'tcx>) {
218 let orig_inside_public_path = self.inside_public_path;
220 self.inside_public_path &= self.cx.tcx.local_visibility(def_id).is_public();
221
222 let old_glob_mode = mem::replace(&mut self.glob_mode, GlobMode::NoGlob);
225 for &i in m.item_ids {
226 let item = self.cx.tcx.hir_item(i);
227 self.visit_item(item);
228 }
229 self.glob_mode = GlobMode::Only;
230 for &i in m.item_ids {
231 let item = self.cx.tcx.hir_item(i);
232 if matches!(item.kind, hir::ItemKind::Use(..)) {
236 self.visit_item_inner(item, None, None);
237 }
238 }
239 self.glob_mode = old_glob_mode;
240 self.inside_public_path = orig_inside_public_path;
241 }
242
243 #[instrument(level = "debug", skip(self), ret)]
253 fn maybe_inline_local(
254 &mut self,
255 def_id: LocalDefId,
256 res: Res,
257 renamed: Option<Symbol>,
258 please_inline: bool,
259 import_id: Option<LocalDefId>,
260 ) -> bool {
261 if renamed == Some(kw::Underscore) {
262 debug!("not inlining `_` reexports");
263 return false;
265 }
266
267 if self.cx.is_json_output() {
268 return false;
269 }
270
271 let tcx = self.cx.tcx;
272 let Some(ori_res_did) = res.opt_def_id() else {
273 debug!("no resolution");
274 return false;
275 };
276
277 let document_hidden = self.cx.document_hidden();
278 let use_attrs = tcx.hir_attrs(tcx.local_def_id_to_hir_id(def_id));
279 let is_no_inline = find_attr!(
281 use_attrs,
282 Doc(d)
283 if d.inline.first().is_some_and(|(inline, _)| *inline == DocInline::NoInline)
284 ) || (document_hidden
285 && use_attrs.iter().any(|attr| attr.is_doc_hidden()));
286
287 if is_no_inline {
288 debug!("doc::no_inline or doc::hidden");
289 return false;
290 }
291
292 let is_glob = renamed.is_none();
293 let is_hidden = !document_hidden && tcx.is_doc_hidden(ori_res_did);
294 debug!(?is_hidden, ?is_glob);
295 let Some(res_did) = ori_res_did.as_local() else {
296 crate::visit_lib::lib_embargo_visit_item(self.cx, ori_res_did);
301 if is_hidden || is_glob {
302 return false;
303 }
304 self.modules
310 .last_mut()
311 .unwrap()
312 .inlined_foreigns
313 .insert((ori_res_did, renamed), InlinedForeign { res, import_id: def_id });
314 return true;
315 };
316
317 let is_private = !self.cx.cache.effective_visibilities.is_directly_public(tcx, ori_res_did);
318 debug!(?is_private);
319 let item = tcx.hir_node_by_def_id(res_did);
320
321 if !please_inline {
322 let inherits_hidden = !document_hidden && inherits_doc_hidden(tcx, res_did, None);
323 debug!(?inherits_hidden);
324 if (!is_private && !inherits_hidden) || (
326 is_hidden &&
327 !matches!(item, Node::Item(&hir::Item { kind: hir::ItemKind::Mod(..), .. }))
330 ) ||
331 self.reexport_public_and_not_hidden(def_id, res_did)
333 {
334 return false;
335 }
336 }
337
338 let is_bang_macro = matches!(
339 item,
340 Node::Item(&hir::Item { kind: hir::ItemKind::Macro(_, _, kinds), .. }) if kinds.contains(MacroKinds::BANG)
341 );
342
343 if !self.view_item_stack.insert(res_did) && !is_bang_macro {
344 return false;
345 }
346
347 trace!(?item);
348
349 let inlined = match item {
350 Node::NestedUseTree(..) | Node::Item(_)
354 if is_bang_macro && !please_inline && !is_glob && is_hidden =>
355 {
356 return false;
357 }
358 Node::Item(&hir::Item { kind: hir::ItemKind::Mod(_, m), .. }) if is_glob => {
359 let prev = mem::replace(&mut self.inlining, true);
360 let prev_glob = mem::replace(&mut self.glob_mode, GlobMode::Everything);
361 for &i in m.item_ids {
362 let i = tcx.hir_item(i);
363 self.visit_item_inner(i, None, Some(import_id.unwrap_or(def_id)));
364 }
365 self.glob_mode = prev_glob;
366 self.inlining = prev;
367 true
368 }
369 Node::Item(it) if !is_glob => {
370 debug!("inlining item");
371 let prev = mem::replace(&mut self.inlining, true);
372 self.visit_item_inner(it, renamed, Some(import_id.unwrap_or(def_id)));
373 self.inlining = prev;
374 true
375 }
376 Node::ForeignItem(it) if !is_glob => {
377 let prev = mem::replace(&mut self.inlining, true);
378 self.visit_foreign_item_inner(it, renamed, Some(import_id.unwrap_or(def_id)));
379 self.inlining = prev;
380 true
381 }
382 _ => false,
383 };
384 self.view_item_stack.remove(&res_did);
385 if inlined {
386 self.cx.cache.inlined_items.insert(ori_res_did);
387 }
388 inlined
389 }
390
391 #[instrument(level = "debug", skip(self), ret)]
396 fn reexport_public_and_not_hidden(
397 &self,
398 import_def_id: LocalDefId,
399 target_def_id: LocalDefId,
400 ) -> bool {
401 if self.cx.document_hidden() {
402 return true;
403 }
404 let tcx = self.cx.tcx;
405 let item_def_id = reexport_chain(tcx, import_def_id, target_def_id.to_def_id())
406 .iter()
407 .flat_map(|reexport| reexport.id())
408 .map(|id| id.expect_local())
409 .nth(1)
410 .unwrap_or(target_def_id);
411 debug!(?item_def_id);
412
413 item_def_id != import_def_id
414 && self.cx.cache.effective_visibilities.is_directly_public(tcx, item_def_id.to_def_id())
415 && !tcx.is_doc_hidden(item_def_id)
416 && !inherits_doc_hidden(tcx, item_def_id, None)
417 }
418
419 #[inline]
420 fn add_impl_to_current_mod(&mut self, item: &'tcx hir::Item<'_>, impl_: hir::Impl<'_>) {
421 self.add_to_current_mod(
422 item,
423 if impl_.of_trait.is_none() { None } else { Some(rustc_span::symbol::kw::Impl) },
428 None,
429 );
430 }
431
432 #[inline]
433 #[instrument(level = "debug", skip(self))]
434 fn add_to_current_mod(
435 &mut self,
436 item: &'tcx hir::Item<'_>,
437 mut renamed: Option<Symbol>,
438 import_id: Option<LocalDefId>,
439 ) {
440 if self.is_importable_from_parent
441 || match item.kind {
444 hir::ItemKind::Impl(..) => true,
445 hir::ItemKind::Macro(_, _, _) => {
446 find_attr!(self.cx.tcx, item.owner_id.def_id, MacroExport{..})
447 }
448 _ => false,
449 }
450 {
451 if renamed == item.kind.ident().map(|ident| ident.name) {
452 renamed = None;
453 }
454 let key = (item.owner_id.def_id, renamed);
455 self.modules
456 .last_mut()
457 .unwrap()
458 .items
459 .entry(key)
460 .or_insert_with(|| ItemEntry { item, renamed, import_ids: vec![] })
461 .import_ids
462 .extend(import_id);
463 }
464 }
465
466 #[instrument(level = "debug", skip(self))]
467 fn visit_item_inner(
468 &mut self,
469 item: &'tcx hir::Item<'_>,
470 renamed: Option<Symbol>,
471 import_id: Option<LocalDefId>,
472 ) {
473 if self.inside_body {
474 if let hir::ItemKind::Impl(impl_) = item.kind {
485 self.add_impl_to_current_mod(item, impl_);
486 }
487 return;
488 }
489 let get_name = || renamed.unwrap_or(item.kind.ident().unwrap().name);
490 let tcx = self.cx.tcx;
491
492 let def_id = item.owner_id.to_def_id();
493 let is_pub = tcx.visibility(def_id).is_public();
494
495 if is_pub {
496 self.store_path(item.owner_id.to_def_id());
497 }
498
499 match item.kind {
500 hir::ItemKind::ForeignMod { items, .. } => {
501 for &item in items {
502 let item = tcx.hir_foreign_item(item);
503 self.visit_foreign_item_inner(item, None, None);
504 }
505 }
506 _ if self.inlining && !is_pub => {}
508 hir::ItemKind::GlobalAsm { .. } => {}
509 hir::ItemKind::Use(ref tree) => {
510 self.visit_use_inner(
511 item,
512 renamed,
513 import_id,
514 is_pub,
515 item.owner_id.def_id,
516 item.hir_id(),
517 tree,
518 );
519 }
520 hir::ItemKind::Macro(_, macro_def, _) => {
521 let def_id = item.owner_id.to_def_id();
533 let is_macro_2_0 = !macro_def.macro_rules;
534 let nonexported = !find_attr!(tcx, def_id, MacroExport { .. });
535
536 if is_macro_2_0 || nonexported || self.inlining {
537 self.add_to_current_mod(item, renamed, import_id);
538 }
539 }
540 hir::ItemKind::Mod(_, m) => {
541 self.enter_mod(item.owner_id.def_id, m, get_name(), renamed, import_id);
542 }
543 hir::ItemKind::Fn { .. }
544 | hir::ItemKind::ExternCrate(..)
545 | hir::ItemKind::Enum(..)
546 | hir::ItemKind::Struct(..)
547 | hir::ItemKind::Union(..)
548 | hir::ItemKind::TyAlias(..)
549 | hir::ItemKind::Static(..)
550 | hir::ItemKind::Trait { .. }
551 | hir::ItemKind::TraitAlias(..) => {
552 self.add_to_current_mod(item, renamed, import_id);
553 }
554 hir::ItemKind::Const(..) => {
555 if get_name() != kw::Underscore {
558 self.add_to_current_mod(item, renamed, import_id);
559 }
560 }
561 hir::ItemKind::Impl(impl_) => {
562 if !self.inlining {
565 self.add_impl_to_current_mod(item, impl_);
566 }
567 }
568 hir::ItemKind::TestBinderConstraints { .. } => {}
569 }
570 }
571
572 #[instrument(level = "debug", skip(self))]
573 fn visit_use_inner(
574 &mut self,
575 item: &'tcx hir::Item<'tcx>,
576 renamed: Option<Symbol>,
577 import_id: Option<LocalDefId>,
578 is_pub: bool,
579 def_id: LocalDefId,
580 hir_id: HirId,
581 tree: &hir::UseTree<'tcx>,
582 ) {
583 let tcx = self.cx.tcx;
584 for res in tree.prefix.res.present_items() {
585 if should_ignore_res(res) {
588 continue;
589 }
590
591 let attrs = tcx.hir_attrs(hir_id);
592
593 if is_pub && self.inside_public_path {
596 let please_inline = if let Some(res_did) = res.opt_def_id()
597 && matches!(tcx.def_kind(res_did), DefKind::Macro(MacroKinds::BANG))
598 {
599 crate::clean::macro_reexport_is_inline(tcx, def_id, res_did)
600 } else {
601 find_attr!(
602 attrs,
603 Doc(d)
604 if d.inline.first().is_some_and(|(inline, _)| *inline == DocInline::Inline)
605 )
606 };
607 let ident = match (tree.kind, self.glob_mode) {
608 (hir::UseKind::Single(ident), GlobMode::NoGlob | GlobMode::Everything) => {
609 Some(ident.name)
610 }
611 (hir::UseKind::Glob, GlobMode::Only | GlobMode::Everything) => None,
612 (hir::UseKind::Single(_), GlobMode::Only)
613 | (hir::UseKind::Glob, GlobMode::NoGlob) => continue,
614 (hir::UseKind::Nested { items }, _) => {
615 for (tree, hir_id, def_id) in items {
616 self.visit_use_inner(
617 item, renamed, import_id, is_pub, *def_id, *hir_id, tree,
618 );
619 }
620 continue;
621 }
622 };
623 if self.maybe_inline_local(def_id, res, ident, please_inline, import_id) {
624 debug!("Inlining {:?}", def_id);
625 continue;
626 }
627 }
628 self.add_to_current_mod(item, renamed, import_id);
629 }
630 }
631
632 fn visit_foreign_item_inner(
633 &mut self,
634 item: &'tcx hir::ForeignItem<'_>,
635 renamed: Option<Symbol>,
636 import_id: Option<LocalDefId>,
637 ) {
638 if !self.inlining || self.cx.tcx.visibility(item.owner_id).is_public() {
640 self.modules.last_mut().unwrap().foreigns.push(Foreign { item, renamed, import_id });
641 }
642 }
643
644 fn enter_mod(
648 &mut self,
649 id: LocalDefId,
650 m: &'tcx hir::Mod<'tcx>,
651 name: Symbol,
652 renamed: Option<Symbol>,
653 import_id: Option<LocalDefId>,
654 ) {
655 self.modules.push(Module::new(name, id, m.spans.inner_span, renamed, import_id));
656
657 self.visit_mod_contents(id, m);
658
659 let last = self.modules.pop().unwrap();
660 self.modules.last_mut().unwrap().mods.push(last);
661 }
662}
663
664impl<'tcx> Visitor<'tcx> for RustdocVisitor<'_, 'tcx> {
667 type NestedFilter = nested_filter::All;
668
669 fn maybe_tcx(&mut self) -> Self::MaybeTyCtxt {
670 self.cx.tcx
671 }
672
673 #[instrument(level = "debug", skip(self))]
674 fn visit_item(&mut self, i: &'tcx hir::Item<'tcx>) {
675 self.visit_item_inner(i, None, None);
676 let new_value = self.is_importable_from_parent
677 && matches!(
678 i.kind,
679 hir::ItemKind::Mod(..)
680 | hir::ItemKind::ForeignMod { .. }
681 | hir::ItemKind::Impl(..)
682 | hir::ItemKind::Trait { .. }
683 );
684 let prev = mem::replace(&mut self.is_importable_from_parent, new_value);
685 walk_item(self, i);
686 self.is_importable_from_parent = prev;
687 }
688
689 fn visit_mod(&mut self, _: &hir::Mod<'tcx>, _: Span, _: LocalModId) {
690 }
692
693 fn visit_use(
694 &mut self,
695 _: &hir::UseTree<'tcx>,
696 _: hir::HirId,
697 _: rustc_span::def_id::LocalDefId,
698 ) {
699 }
701
702 fn visit_path(&mut self, _: &hir::Path<'tcx>, _: hir::HirId) {
703 }
705
706 fn visit_label(&mut self, _: &rustc_ast::Label) {
707 }
709
710 fn visit_infer(
711 &mut self,
712 _inf_id: hir::HirId,
713 _inf_span: Span,
714 _kind: hir::intravisit::InferKind<'tcx>,
715 ) -> Self::Result {
716 }
718
719 fn visit_lifetime(&mut self, _: &hir::Lifetime) {
720 }
722
723 fn visit_body(&mut self, b: &hir::Body<'tcx>) {
724 let prev = mem::replace(&mut self.inside_body, true);
725 walk_body(self, b);
726 self.inside_body = prev;
727 }
728}