1use std::mem;
5
6use rustc_ast::attr::AttributeExt;
7use rustc_data_structures::fx::{FxHashSet, FxIndexMap};
8use rustc_hir as hir;
9use rustc_hir::attrs::DocInline;
10use rustc_hir::def::{DefKind, MacroKinds, Res};
11use rustc_hir::def_id::{DefId, DefIdMap, LocalDefId, LocalDefIdSet};
12use rustc_hir::intravisit::{Visitor, walk_body, walk_item};
13use rustc_hir::{Node, find_attr};
14use rustc_middle::hir::nested_filter;
15use rustc_middle::ty::TyCtxt;
16use rustc_span::Span;
17use rustc_span::def_id::{CRATE_DEF_ID, LOCAL_CRATE};
18use rustc_span::symbol::{Symbol, kw};
19use tracing::debug;
20
21use crate::clean::reexport_chain;
22use crate::clean::utils::{inherits_doc_hidden, should_ignore_res};
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<(LocalDefId, Option<Symbol>), ItemEntry<'hir>>,
55
56 pub(crate) inlined_foreigns: FxIndexMap<(DefId, Option<Symbol>), InlinedForeign>,
65 pub(crate) foreigns: Vec<Foreign<'hir>>,
67}
68
69#[derive(Debug)]
70pub(crate) struct ItemEntry<'hir> {
71 pub(crate) item: &'hir hir::Item<'hir>,
72 pub(crate) renamed: Option<Symbol>,
73 pub(crate) import_ids: Vec<LocalDefId>,
74}
75
76#[derive(Debug)]
77pub(crate) struct InlinedForeign {
78 pub(crate) res: Res,
79 pub(crate) import_id: LocalDefId,
80}
81
82#[derive(Debug)]
83pub(crate) struct Foreign<'hir> {
84 pub(crate) item: &'hir hir::ForeignItem<'hir>,
85 pub(crate) renamed: Option<Symbol>,
86 pub(crate) import_id: Option<LocalDefId>,
87}
88
89impl Module<'_> {
90 pub(crate) fn new(
91 name: Symbol,
92 def_id: LocalDefId,
93 where_inner: Span,
94 renamed: Option<Symbol>,
95 import_id: Option<LocalDefId>,
96 ) -> Self {
97 Module {
98 name,
99 def_id,
100 where_inner,
101 renamed,
102 import_id,
103 mods: Vec::new(),
104 items: FxIndexMap::default(),
105 inlined_foreigns: FxIndexMap::default(),
106 foreigns: Vec::new(),
107 }
108 }
109
110 pub(crate) fn where_outer(&self, tcx: TyCtxt<'_>) -> Span {
111 tcx.def_span(self.def_id)
112 }
113}
114
115fn def_id_to_path(tcx: TyCtxt<'_>, did: DefId) -> Vec<Symbol> {
117 let crate_name = tcx.crate_name(did.krate);
118 let relative = tcx.def_path(did).data.into_iter().filter_map(|elem| elem.data.get_opt_name());
119 std::iter::once(crate_name).chain(relative).collect()
120}
121
122pub(crate) struct RustdocVisitor<'a, 'tcx> {
123 cx: &'a mut core::DocContext<'tcx>,
124 view_item_stack: LocalDefIdSet,
125 inlining: bool,
126 inside_public_path: bool,
128 exact_paths: DefIdMap<Vec<Symbol>>,
129 modules: Vec<Module<'tcx>>,
130 is_importable_from_parent: bool,
131 inside_body: bool,
132}
133
134impl<'a, 'tcx> RustdocVisitor<'a, 'tcx> {
135 pub(crate) fn new(cx: &'a mut core::DocContext<'tcx>) -> RustdocVisitor<'a, 'tcx> {
136 let mut stack = LocalDefIdSet::default();
138 stack.insert(CRATE_DEF_ID);
139 let om = Module::new(
140 cx.tcx.crate_name(LOCAL_CRATE),
141 CRATE_DEF_ID,
142 cx.tcx.hir_root_module().spans.inner_span,
143 None,
144 None,
145 );
146
147 RustdocVisitor {
148 cx,
149 view_item_stack: stack,
150 inlining: false,
151 inside_public_path: true,
152 exact_paths: Default::default(),
153 modules: vec![om],
154 is_importable_from_parent: true,
155 inside_body: false,
156 }
157 }
158
159 fn store_path(&mut self, did: DefId) {
160 let tcx = self.cx.tcx;
161 self.exact_paths.entry(did).or_insert_with(|| def_id_to_path(tcx, did));
162 }
163
164 pub(crate) fn visit(mut self) -> Module<'tcx> {
165 let root_module = self.cx.tcx.hir_root_module();
166 self.visit_mod_contents(CRATE_DEF_ID, root_module);
167
168 let mut top_level_module = self.modules.pop().unwrap();
169
170 let mut inserted = FxHashSet::default();
182 for child in self.cx.tcx.module_children_local(CRATE_DEF_ID) {
183 if !child.reexport_chain.is_empty()
184 && let Res::Def(DefKind::Macro(_), def_id) = child.res
185 && let Some(local_def_id) = def_id.as_local()
186 && find_attr!(self.cx.tcx, def_id, MacroExport { .. })
187 && inserted.insert(def_id)
188 {
189 let item = self.cx.tcx.hir_expect_item(local_def_id);
190 let (ident, _, _) = item.expect_macro();
191 top_level_module.items.insert(
192 (local_def_id, Some(ident.name)),
193 ItemEntry { item, renamed: None, import_ids: Vec::new() },
194 );
195 }
196 }
197
198 self.cx.cache.exact_paths = self.exact_paths;
199 top_level_module
200 }
201
202 fn visit_mod_contents(&mut self, def_id: LocalDefId, m: &'tcx hir::Mod<'tcx>) {
206 debug!("Going through module {m:?}");
207 let orig_inside_public_path = self.inside_public_path;
209 self.inside_public_path &= self.cx.tcx.local_visibility(def_id).is_public();
210
211 for &i in m.item_ids {
214 let item = self.cx.tcx.hir_item(i);
215 if !matches!(item.kind, hir::ItemKind::Use(_, hir::UseKind::Glob)) {
216 self.visit_item(item);
217 }
218 }
219 for &i in m.item_ids {
220 let item = self.cx.tcx.hir_item(i);
221 if matches!(item.kind, hir::ItemKind::Use(_, hir::UseKind::Glob)) {
225 self.visit_item(item);
226 }
227 }
228 self.inside_public_path = orig_inside_public_path;
229 debug!("Leaving module {m:?}");
230 }
231
232 fn maybe_inline_local(
242 &mut self,
243 def_id: LocalDefId,
244 res: Res,
245 renamed: Option<Symbol>,
246 please_inline: bool,
247 import_id: Option<LocalDefId>,
248 ) -> bool {
249 debug!("maybe_inline_local (renamed: {renamed:?}) res: {res:?}");
250
251 if renamed == Some(kw::Underscore) {
252 return false;
254 }
255
256 if self.cx.is_json_output() {
257 return false;
258 }
259
260 let tcx = self.cx.tcx;
261 let Some(ori_res_did) = res.opt_def_id() else {
262 return false;
263 };
264
265 let document_hidden = self.cx.document_hidden();
266 let use_attrs = tcx.hir_attrs(tcx.local_def_id_to_hir_id(def_id));
267 let is_no_inline = find_attr!(
269 use_attrs,
270 Doc(d)
271 if d.inline.first().is_some_and(|(inline, _)| *inline == DocInline::NoInline)
272 ) || (document_hidden
273 && use_attrs.iter().any(|attr| attr.is_doc_hidden()));
274
275 if is_no_inline {
276 return false;
277 }
278
279 let is_glob = renamed.is_none();
280 let is_hidden = !document_hidden && tcx.is_doc_hidden(ori_res_did);
281 let Some(res_did) = ori_res_did.as_local() else {
282 crate::visit_lib::lib_embargo_visit_item(self.cx, ori_res_did);
287 if is_hidden || is_glob {
288 return false;
289 }
290 self.modules
296 .last_mut()
297 .unwrap()
298 .inlined_foreigns
299 .insert((ori_res_did, renamed), InlinedForeign { res, import_id: def_id });
300 return true;
301 };
302
303 let is_private = !self.cx.cache.effective_visibilities.is_directly_public(tcx, ori_res_did);
304 let item = tcx.hir_node_by_def_id(res_did);
305
306 if !please_inline {
307 let inherits_hidden = !document_hidden && inherits_doc_hidden(tcx, res_did, None);
308 if (!is_private && !inherits_hidden) || (
310 is_hidden &&
311 !matches!(item, Node::Item(&hir::Item { kind: hir::ItemKind::Mod(..), .. }))
314 ) ||
315 self.reexport_public_and_not_hidden(def_id, res_did)
317 {
318 return false;
319 }
320 }
321
322 let is_bang_macro = matches!(
323 item,
324 Node::Item(&hir::Item { kind: hir::ItemKind::Macro(_, _, kinds), .. }) if kinds.contains(MacroKinds::BANG)
325 );
326
327 if !self.view_item_stack.insert(res_did) && !is_bang_macro {
328 return false;
329 }
330
331 let inlined = match item {
332 Node::Item(_) if is_bang_macro && !please_inline && !is_glob && is_hidden => {
336 return false;
337 }
338 Node::Item(&hir::Item { kind: hir::ItemKind::Mod(_, m), .. }) if is_glob => {
339 let prev = mem::replace(&mut self.inlining, true);
340 for &i in m.item_ids {
341 let i = tcx.hir_item(i);
342 self.visit_item_inner(i, None, Some(import_id.unwrap_or(def_id)));
343 }
344 self.inlining = prev;
345 true
346 }
347 Node::Item(it) if !is_glob => {
348 let prev = mem::replace(&mut self.inlining, true);
349 self.visit_item_inner(it, renamed, Some(import_id.unwrap_or(def_id)));
350 self.inlining = prev;
351 true
352 }
353 Node::ForeignItem(it) if !is_glob => {
354 let prev = mem::replace(&mut self.inlining, true);
355 self.visit_foreign_item_inner(it, renamed, Some(import_id.unwrap_or(def_id)));
356 self.inlining = prev;
357 true
358 }
359 _ => false,
360 };
361 self.view_item_stack.remove(&res_did);
362 if inlined {
363 self.cx.cache.inlined_items.insert(ori_res_did);
364 }
365 inlined
366 }
367
368 fn reexport_public_and_not_hidden(
373 &self,
374 import_def_id: LocalDefId,
375 target_def_id: LocalDefId,
376 ) -> bool {
377 if self.cx.document_hidden() {
378 return true;
379 }
380 let tcx = self.cx.tcx;
381 let item_def_id = reexport_chain(tcx, import_def_id, target_def_id.to_def_id())
382 .iter()
383 .flat_map(|reexport| reexport.id())
384 .map(|id| id.expect_local())
385 .nth(1)
386 .unwrap_or(target_def_id);
387 item_def_id != import_def_id
388 && self.cx.cache.effective_visibilities.is_directly_public(tcx, item_def_id.to_def_id())
389 && !tcx.is_doc_hidden(item_def_id)
390 && !inherits_doc_hidden(tcx, item_def_id, None)
391 }
392
393 #[inline]
394 fn add_impl_to_current_mod(&mut self, item: &'tcx hir::Item<'_>, impl_: hir::Impl<'_>) {
395 self.add_to_current_mod(
396 item,
397 if impl_.of_trait.is_none() { None } else { Some(rustc_span::symbol::kw::Impl) },
402 None,
403 );
404 }
405
406 #[inline]
407 fn add_to_current_mod(
408 &mut self,
409 item: &'tcx hir::Item<'_>,
410 mut renamed: Option<Symbol>,
411 import_id: Option<LocalDefId>,
412 ) {
413 if self.is_importable_from_parent
414 || match item.kind {
417 hir::ItemKind::Impl(..) => true,
418 hir::ItemKind::Macro(_, _, _) => {
419 find_attr!(self.cx.tcx, item.owner_id.def_id, MacroExport{..})
420 }
421 _ => false,
422 }
423 {
424 if renamed == item.kind.ident().map(|ident| ident.name) {
425 renamed = None;
426 }
427 let key = (item.owner_id.def_id, renamed);
428 if let Some(import_id) = import_id {
429 self.modules
430 .last_mut()
431 .unwrap()
432 .items
433 .entry(key)
434 .and_modify(|v| v.import_ids.push(import_id))
435 .or_insert_with(|| ItemEntry { item, renamed, import_ids: vec![import_id] });
436 } else {
437 self.modules
438 .last_mut()
439 .unwrap()
440 .items
441 .insert(key, ItemEntry { item, renamed, import_ids: Vec::new() });
442 }
443 }
444 }
445
446 fn visit_item_inner(
447 &mut self,
448 item: &'tcx hir::Item<'_>,
449 renamed: Option<Symbol>,
450 import_id: Option<LocalDefId>,
451 ) {
452 debug!("visiting item {item:?}");
453 if self.inside_body {
454 if let hir::ItemKind::Impl(impl_) = item.kind {
465 self.add_impl_to_current_mod(item, impl_);
466 }
467 return;
468 }
469 let get_name = || renamed.unwrap_or(item.kind.ident().unwrap().name);
470 let tcx = self.cx.tcx;
471
472 let def_id = item.owner_id.to_def_id();
473 let is_pub = tcx.visibility(def_id).is_public();
474
475 if is_pub {
476 self.store_path(item.owner_id.to_def_id());
477 }
478
479 match item.kind {
480 hir::ItemKind::ForeignMod { items, .. } => {
481 for &item in items {
482 let item = tcx.hir_foreign_item(item);
483 self.visit_foreign_item_inner(item, None, None);
484 }
485 }
486 _ if self.inlining && !is_pub => {}
488 hir::ItemKind::GlobalAsm { .. } => {}
489 hir::ItemKind::Use(_, hir::UseKind::ListStem) => {}
490 hir::ItemKind::Use(path, kind) => {
491 for res in path.res.present_items() {
492 if should_ignore_res(res) {
495 continue;
496 }
497
498 let attrs = tcx.hir_attrs(tcx.local_def_id_to_hir_id(item.owner_id.def_id));
499
500 if is_pub && self.inside_public_path {
503 let please_inline = if let Some(res_did) = res.opt_def_id()
504 && matches!(tcx.def_kind(res_did), DefKind::Macro(MacroKinds::BANG))
505 {
506 crate::clean::macro_reexport_is_inline(
507 tcx,
508 item.owner_id.def_id,
509 res_did,
510 )
511 } else {
512 find_attr!(
513 attrs,
514 Doc(d)
515 if d.inline.first().is_some_and(|(inline, _)| *inline == DocInline::Inline)
516 )
517 };
518 let ident = match kind {
519 hir::UseKind::Single(ident) => Some(ident.name),
520 hir::UseKind::Glob => None,
521 hir::UseKind::ListStem => unreachable!(),
522 };
523 if self.maybe_inline_local(
524 item.owner_id.def_id,
525 res,
526 ident,
527 please_inline,
528 import_id,
529 ) {
530 debug!("Inlining {:?}", item.owner_id.def_id);
531 continue;
532 }
533 }
534 self.add_to_current_mod(item, renamed, import_id);
535 }
536 }
537 hir::ItemKind::Macro(_, macro_def, _) => {
538 let def_id = item.owner_id.to_def_id();
550 let is_macro_2_0 = !macro_def.macro_rules;
551 let nonexported = !find_attr!(tcx, def_id, MacroExport { .. });
552
553 if is_macro_2_0 || nonexported || self.inlining {
554 self.add_to_current_mod(item, renamed, import_id);
555 }
556 }
557 hir::ItemKind::Mod(_, m) => {
558 self.enter_mod(item.owner_id.def_id, m, get_name(), renamed, import_id);
559 }
560 hir::ItemKind::Fn { .. }
561 | hir::ItemKind::ExternCrate(..)
562 | hir::ItemKind::Enum(..)
563 | hir::ItemKind::Struct(..)
564 | hir::ItemKind::Union(..)
565 | hir::ItemKind::TyAlias(..)
566 | hir::ItemKind::Static(..)
567 | hir::ItemKind::Trait { .. }
568 | hir::ItemKind::TraitAlias(..) => {
569 self.add_to_current_mod(item, renamed, import_id);
570 }
571 hir::ItemKind::Const(..) => {
572 if get_name() != kw::Underscore {
575 self.add_to_current_mod(item, renamed, import_id);
576 }
577 }
578 hir::ItemKind::Impl(impl_) => {
579 if !self.inlining {
582 self.add_impl_to_current_mod(item, impl_);
583 }
584 }
585 }
586 }
587
588 fn visit_foreign_item_inner(
589 &mut self,
590 item: &'tcx hir::ForeignItem<'_>,
591 renamed: Option<Symbol>,
592 import_id: Option<LocalDefId>,
593 ) {
594 if !self.inlining || self.cx.tcx.visibility(item.owner_id).is_public() {
596 self.modules.last_mut().unwrap().foreigns.push(Foreign { item, renamed, import_id });
597 }
598 }
599
600 fn enter_mod(
604 &mut self,
605 id: LocalDefId,
606 m: &'tcx hir::Mod<'tcx>,
607 name: Symbol,
608 renamed: Option<Symbol>,
609 import_id: Option<LocalDefId>,
610 ) {
611 self.modules.push(Module::new(name, id, m.spans.inner_span, renamed, import_id));
612
613 self.visit_mod_contents(id, m);
614
615 let last = self.modules.pop().unwrap();
616 self.modules.last_mut().unwrap().mods.push(last);
617 }
618}
619
620impl<'tcx> Visitor<'tcx> for RustdocVisitor<'_, 'tcx> {
623 type NestedFilter = nested_filter::All;
624
625 fn maybe_tcx(&mut self) -> Self::MaybeTyCtxt {
626 self.cx.tcx
627 }
628
629 fn visit_item(&mut self, i: &'tcx hir::Item<'tcx>) {
630 self.visit_item_inner(i, None, None);
631 let new_value = self.is_importable_from_parent
632 && matches!(
633 i.kind,
634 hir::ItemKind::Mod(..)
635 | hir::ItemKind::ForeignMod { .. }
636 | hir::ItemKind::Impl(..)
637 | hir::ItemKind::Trait { .. }
638 );
639 let prev = mem::replace(&mut self.is_importable_from_parent, new_value);
640 walk_item(self, i);
641 self.is_importable_from_parent = prev;
642 }
643
644 fn visit_mod(&mut self, _: &hir::Mod<'tcx>, _: Span, _: hir::HirId) {
645 }
647
648 fn visit_use(&mut self, _: &hir::UsePath<'tcx>, _: hir::HirId) {
649 }
651
652 fn visit_path(&mut self, _: &hir::Path<'tcx>, _: hir::HirId) {
653 }
655
656 fn visit_label(&mut self, _: &rustc_ast::Label) {
657 }
659
660 fn visit_infer(
661 &mut self,
662 _inf_id: hir::HirId,
663 _inf_span: Span,
664 _kind: hir::intravisit::InferKind<'tcx>,
665 ) -> Self::Result {
666 }
668
669 fn visit_lifetime(&mut self, _: &hir::Lifetime) {
670 }
672
673 fn visit_body(&mut self, b: &hir::Body<'tcx>) {
674 let prev = mem::replace(&mut self.inside_body, true);
675 walk_body(self, b);
676 self.inside_body = prev;
677 }
678}