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 ) -> bool {
248 debug!("maybe_inline_local (renamed: {renamed:?}) res: {res:?}");
249
250 if renamed == Some(kw::Underscore) {
251 return false;
253 }
254
255 if self.cx.is_json_output() {
256 return false;
257 }
258
259 let tcx = self.cx.tcx;
260 let Some(ori_res_did) = res.opt_def_id() else {
261 return false;
262 };
263
264 let document_hidden = self.cx.document_hidden();
265 let use_attrs = tcx.hir_attrs(tcx.local_def_id_to_hir_id(def_id));
266 let is_no_inline = find_attr!(
268 use_attrs,
269 Doc(d)
270 if d.inline.first().is_some_and(|(inline, _)| *inline == DocInline::NoInline)
271 ) || (document_hidden
272 && use_attrs.iter().any(|attr| attr.is_doc_hidden()));
273
274 if is_no_inline {
275 return false;
276 }
277
278 let is_glob = renamed.is_none();
279 let is_hidden = !document_hidden && tcx.is_doc_hidden(ori_res_did);
280 let Some(res_did) = ori_res_did.as_local() else {
281 crate::visit_lib::lib_embargo_visit_item(self.cx, ori_res_did);
286 if is_hidden || is_glob {
287 return false;
288 }
289 self.modules
295 .last_mut()
296 .unwrap()
297 .inlined_foreigns
298 .insert((ori_res_did, renamed), InlinedForeign { res, import_id: def_id });
299 return true;
300 };
301
302 let is_private = !self.cx.cache.effective_visibilities.is_directly_public(tcx, ori_res_did);
303 let item = tcx.hir_node_by_def_id(res_did);
304
305 if !please_inline {
306 let inherits_hidden = !document_hidden && inherits_doc_hidden(tcx, res_did, None);
307 if (!is_private && !inherits_hidden) || (
309 is_hidden &&
310 !matches!(item, Node::Item(&hir::Item { kind: hir::ItemKind::Mod(..), .. }))
313 ) ||
314 self.reexport_public_and_not_hidden(def_id, res_did)
316 {
317 return false;
318 }
319 }
320
321 let is_bang_macro = matches!(
322 item,
323 Node::Item(&hir::Item { kind: hir::ItemKind::Macro(_, _, kinds), .. }) if kinds.contains(MacroKinds::BANG)
324 );
325
326 if !self.view_item_stack.insert(res_did) && !is_bang_macro {
327 return false;
328 }
329
330 let inlined = match item {
331 Node::Item(_) if is_bang_macro && !please_inline && !is_glob && is_hidden => {
335 return false;
336 }
337 Node::Item(&hir::Item { kind: hir::ItemKind::Mod(_, m), .. }) if is_glob => {
338 let prev = mem::replace(&mut self.inlining, true);
339 for &i in m.item_ids {
340 let i = tcx.hir_item(i);
341 self.visit_item_inner(i, None, Some(def_id));
342 }
343 self.inlining = prev;
344 true
345 }
346 Node::Item(it) if !is_glob => {
347 let prev = mem::replace(&mut self.inlining, true);
348 self.visit_item_inner(it, renamed, Some(def_id));
349 self.inlining = prev;
350 true
351 }
352 Node::ForeignItem(it) if !is_glob => {
353 let prev = mem::replace(&mut self.inlining, true);
354 self.visit_foreign_item_inner(it, renamed, Some(def_id));
355 self.inlining = prev;
356 true
357 }
358 _ => false,
359 };
360 self.view_item_stack.remove(&res_did);
361 if inlined {
362 self.cx.cache.inlined_items.insert(ori_res_did);
363 }
364 inlined
365 }
366
367 fn reexport_public_and_not_hidden(
372 &self,
373 import_def_id: LocalDefId,
374 target_def_id: LocalDefId,
375 ) -> bool {
376 if self.cx.document_hidden() {
377 return true;
378 }
379 let tcx = self.cx.tcx;
380 let item_def_id = reexport_chain(tcx, import_def_id, target_def_id.to_def_id())
381 .iter()
382 .flat_map(|reexport| reexport.id())
383 .map(|id| id.expect_local())
384 .nth(1)
385 .unwrap_or(target_def_id);
386 item_def_id != import_def_id
387 && self.cx.cache.effective_visibilities.is_directly_public(tcx, item_def_id.to_def_id())
388 && !tcx.is_doc_hidden(item_def_id)
389 && !inherits_doc_hidden(tcx, item_def_id, None)
390 }
391
392 #[inline]
393 fn add_impl_to_current_mod(&mut self, item: &'tcx hir::Item<'_>, impl_: hir::Impl<'_>) {
394 self.add_to_current_mod(
395 item,
396 if impl_.of_trait.is_none() { None } else { Some(rustc_span::symbol::kw::Impl) },
401 None,
402 );
403 }
404
405 #[inline]
406 fn add_to_current_mod(
407 &mut self,
408 item: &'tcx hir::Item<'_>,
409 mut renamed: Option<Symbol>,
410 import_id: Option<LocalDefId>,
411 ) {
412 if self.is_importable_from_parent
413 || match item.kind {
416 hir::ItemKind::Impl(..) => true,
417 hir::ItemKind::Macro(_, _, _) => {
418 find_attr!(self.cx.tcx, item.owner_id.def_id, MacroExport{..})
419 }
420 _ => false,
421 }
422 {
423 if renamed == item.kind.ident().map(|ident| ident.name) {
424 renamed = None;
425 }
426 let key = (item.owner_id.def_id, renamed);
427 if let Some(import_id) = import_id {
428 self.modules
429 .last_mut()
430 .unwrap()
431 .items
432 .entry(key)
433 .and_modify(|v| v.import_ids.push(import_id))
434 .or_insert_with(|| ItemEntry { item, renamed, import_ids: vec![import_id] });
435 } else {
436 self.modules
437 .last_mut()
438 .unwrap()
439 .items
440 .insert(key, ItemEntry { item, renamed, import_ids: Vec::new() });
441 }
442 }
443 }
444
445 fn visit_item_inner(
446 &mut self,
447 item: &'tcx hir::Item<'_>,
448 renamed: Option<Symbol>,
449 import_id: Option<LocalDefId>,
450 ) {
451 debug!("visiting item {item:?}");
452 if self.inside_body {
453 if let hir::ItemKind::Impl(impl_) = item.kind {
464 self.add_impl_to_current_mod(item, impl_);
465 }
466 return;
467 }
468 let get_name = || renamed.unwrap_or(item.kind.ident().unwrap().name);
469 let tcx = self.cx.tcx;
470
471 let def_id = item.owner_id.to_def_id();
472 let is_pub = tcx.visibility(def_id).is_public();
473
474 if is_pub {
475 self.store_path(item.owner_id.to_def_id());
476 }
477
478 match item.kind {
479 hir::ItemKind::ForeignMod { items, .. } => {
480 for &item in items {
481 let item = tcx.hir_foreign_item(item);
482 self.visit_foreign_item_inner(item, None, None);
483 }
484 }
485 _ if self.inlining && !is_pub => {}
487 hir::ItemKind::GlobalAsm { .. } => {}
488 hir::ItemKind::Use(_, hir::UseKind::ListStem) => {}
489 hir::ItemKind::Use(path, kind) => {
490 for res in path.res.present_items() {
491 if should_ignore_res(res) {
494 continue;
495 }
496
497 let attrs = tcx.hir_attrs(tcx.local_def_id_to_hir_id(item.owner_id.def_id));
498
499 if is_pub && self.inside_public_path {
502 let please_inline = if let Some(res_did) = res.opt_def_id()
503 && matches!(tcx.def_kind(res_did), DefKind::Macro(MacroKinds::BANG))
504 {
505 crate::clean::macro_reexport_is_inline(
506 tcx,
507 item.owner_id.def_id,
508 res_did,
509 )
510 } else {
511 find_attr!(
512 attrs,
513 Doc(d)
514 if d.inline.first().is_some_and(|(inline, _)| *inline == DocInline::Inline)
515 )
516 };
517 let ident = match kind {
518 hir::UseKind::Single(ident) => Some(ident.name),
519 hir::UseKind::Glob => None,
520 hir::UseKind::ListStem => unreachable!(),
521 };
522 if self.maybe_inline_local(item.owner_id.def_id, res, ident, please_inline)
523 {
524 debug!("Inlining {:?}", item.owner_id.def_id);
525 continue;
526 }
527 }
528 self.add_to_current_mod(item, renamed, import_id);
529 }
530 }
531 hir::ItemKind::Macro(_, macro_def, _) => {
532 let def_id = item.owner_id.to_def_id();
544 let is_macro_2_0 = !macro_def.macro_rules;
545 let nonexported = !find_attr!(tcx, def_id, MacroExport { .. });
546
547 if is_macro_2_0 || nonexported || self.inlining {
548 self.add_to_current_mod(item, renamed, import_id);
549 }
550 }
551 hir::ItemKind::Mod(_, m) => {
552 self.enter_mod(item.owner_id.def_id, m, get_name(), renamed, import_id);
553 }
554 hir::ItemKind::Fn { .. }
555 | hir::ItemKind::ExternCrate(..)
556 | hir::ItemKind::Enum(..)
557 | hir::ItemKind::Struct(..)
558 | hir::ItemKind::Union(..)
559 | hir::ItemKind::TyAlias(..)
560 | hir::ItemKind::Static(..)
561 | hir::ItemKind::Trait { .. }
562 | hir::ItemKind::TraitAlias(..) => {
563 self.add_to_current_mod(item, renamed, import_id);
564 }
565 hir::ItemKind::Const(..) => {
566 if get_name() != kw::Underscore {
569 self.add_to_current_mod(item, renamed, import_id);
570 }
571 }
572 hir::ItemKind::Impl(impl_) => {
573 if !self.inlining {
576 self.add_impl_to_current_mod(item, impl_);
577 }
578 }
579 }
580 }
581
582 fn visit_foreign_item_inner(
583 &mut self,
584 item: &'tcx hir::ForeignItem<'_>,
585 renamed: Option<Symbol>,
586 import_id: Option<LocalDefId>,
587 ) {
588 if !self.inlining || self.cx.tcx.visibility(item.owner_id).is_public() {
590 self.modules.last_mut().unwrap().foreigns.push(Foreign { item, renamed, import_id });
591 }
592 }
593
594 fn enter_mod(
598 &mut self,
599 id: LocalDefId,
600 m: &'tcx hir::Mod<'tcx>,
601 name: Symbol,
602 renamed: Option<Symbol>,
603 import_id: Option<LocalDefId>,
604 ) {
605 self.modules.push(Module::new(name, id, m.spans.inner_span, renamed, import_id));
606
607 self.visit_mod_contents(id, m);
608
609 let last = self.modules.pop().unwrap();
610 self.modules.last_mut().unwrap().mods.push(last);
611 }
612}
613
614impl<'tcx> Visitor<'tcx> for RustdocVisitor<'_, 'tcx> {
617 type NestedFilter = nested_filter::All;
618
619 fn maybe_tcx(&mut self) -> Self::MaybeTyCtxt {
620 self.cx.tcx
621 }
622
623 fn visit_item(&mut self, i: &'tcx hir::Item<'tcx>) {
624 self.visit_item_inner(i, None, None);
625 let new_value = self.is_importable_from_parent
626 && matches!(
627 i.kind,
628 hir::ItemKind::Mod(..)
629 | hir::ItemKind::ForeignMod { .. }
630 | hir::ItemKind::Impl(..)
631 | hir::ItemKind::Trait { .. }
632 );
633 let prev = mem::replace(&mut self.is_importable_from_parent, new_value);
634 walk_item(self, i);
635 self.is_importable_from_parent = prev;
636 }
637
638 fn visit_mod(&mut self, _: &hir::Mod<'tcx>, _: Span, _: hir::HirId) {
639 }
641
642 fn visit_use(&mut self, _: &hir::UsePath<'tcx>, _: hir::HirId) {
643 }
645
646 fn visit_path(&mut self, _: &hir::Path<'tcx>, _: hir::HirId) {
647 }
649
650 fn visit_label(&mut self, _: &rustc_ast::Label) {
651 }
653
654 fn visit_infer(
655 &mut self,
656 _inf_id: hir::HirId,
657 _inf_span: Span,
658 _kind: hir::intravisit::InferKind<'tcx>,
659 ) -> Self::Result {
660 }
662
663 fn visit_lifetime(&mut self, _: &hir::Lifetime) {
664 }
666
667 fn visit_body(&mut self, b: &hir::Body<'tcx>) {
668 let prev = mem::replace(&mut self.inside_body, true);
669 walk_body(self, b);
670 self.inside_body = prev;
671 }
672}