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<
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 && find_attr!(self.cx.tcx, def_id, MacroExport { .. })
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.exact_paths = self.exact_paths;
182 top_level_module
183 }
184
185 fn visit_mod_contents(&mut self, def_id: LocalDefId, m: &'tcx hir::Mod<'tcx>) {
189 debug!("Going through module {m:?}");
190 let orig_inside_public_path = self.inside_public_path;
192 self.inside_public_path &= self.cx.tcx.local_visibility(def_id).is_public();
193
194 for &i in m.item_ids {
197 let item = self.cx.tcx.hir_item(i);
198 if !matches!(item.kind, hir::ItemKind::Use(_, hir::UseKind::Glob)) {
199 self.visit_item(item);
200 }
201 }
202 for &i in m.item_ids {
203 let item = self.cx.tcx.hir_item(i);
204 if matches!(item.kind, hir::ItemKind::Use(_, hir::UseKind::Glob)) {
208 self.visit_item(item);
209 }
210 }
211 self.inside_public_path = orig_inside_public_path;
212 debug!("Leaving module {m:?}");
213 }
214
215 fn maybe_inline_local(
225 &mut self,
226 def_id: LocalDefId,
227 res: Res,
228 renamed: Option<Symbol>,
229 please_inline: bool,
230 ) -> bool {
231 debug!("maybe_inline_local (renamed: {renamed:?}) res: {res:?}");
232
233 if renamed == Some(kw::Underscore) {
234 return false;
236 }
237
238 if self.cx.is_json_output() {
239 return false;
240 }
241
242 let tcx = self.cx.tcx;
243 let Some(ori_res_did) = res.opt_def_id() else {
244 return false;
245 };
246
247 let document_hidden = self.cx.document_hidden();
248 let use_attrs = tcx.hir_attrs(tcx.local_def_id_to_hir_id(def_id));
249 let is_no_inline = find_attr!(
251 use_attrs,
252 Doc(d)
253 if d.inline.first().is_some_and(|(inline, _)| *inline == DocInline::NoInline)
254 ) || (document_hidden
255 && use_attrs.iter().any(|attr| attr.is_doc_hidden()));
256
257 if is_no_inline {
258 return false;
259 }
260
261 let is_glob = renamed.is_none();
262 let is_hidden = !document_hidden && tcx.is_doc_hidden(ori_res_did);
263 let Some(res_did) = ori_res_did.as_local() else {
264 crate::visit_lib::lib_embargo_visit_item(self.cx, ori_res_did);
269 if is_hidden || is_glob {
270 return false;
271 }
272 self.modules
278 .last_mut()
279 .unwrap()
280 .inlined_foreigns
281 .insert((ori_res_did, renamed), (res, def_id));
282 return true;
283 };
284
285 let is_private = !self.cx.cache.effective_visibilities.is_directly_public(tcx, ori_res_did);
286 let item = tcx.hir_node_by_def_id(res_did);
287
288 if !please_inline {
289 let inherits_hidden = !document_hidden && inherits_doc_hidden(tcx, res_did, None);
290 if (!is_private && !inherits_hidden) || (
292 is_hidden &&
293 !matches!(item, Node::Item(&hir::Item { kind: hir::ItemKind::Mod(..), .. }))
296 ) ||
297 self.reexport_public_and_not_hidden(def_id, res_did)
299 {
300 return false;
301 }
302 }
303
304 let is_bang_macro = matches!(
305 item,
306 Node::Item(&hir::Item { kind: hir::ItemKind::Macro(_, _, kinds), .. }) if kinds.contains(MacroKinds::BANG)
307 );
308
309 if !self.view_item_stack.insert(res_did) && !is_bang_macro {
310 return false;
311 }
312
313 let inlined = match item {
314 Node::Item(_) if is_bang_macro && !please_inline && !is_glob && is_hidden => {
318 return false;
319 }
320 Node::Item(&hir::Item { kind: hir::ItemKind::Mod(_, m), .. }) if is_glob => {
321 let prev = mem::replace(&mut self.inlining, true);
322 for &i in m.item_ids {
323 let i = tcx.hir_item(i);
324 self.visit_item_inner(i, None, Some(def_id));
325 }
326 self.inlining = prev;
327 true
328 }
329 Node::Item(it) if !is_glob => {
330 let prev = mem::replace(&mut self.inlining, true);
331 self.visit_item_inner(it, renamed, Some(def_id));
332 self.inlining = prev;
333 true
334 }
335 Node::ForeignItem(it) if !is_glob => {
336 let prev = mem::replace(&mut self.inlining, true);
337 self.visit_foreign_item_inner(it, renamed, Some(def_id));
338 self.inlining = prev;
339 true
340 }
341 _ => false,
342 };
343 self.view_item_stack.remove(&res_did);
344 if inlined {
345 self.cx.cache.inlined_items.insert(ori_res_did);
346 }
347 inlined
348 }
349
350 fn reexport_public_and_not_hidden(
355 &self,
356 import_def_id: LocalDefId,
357 target_def_id: LocalDefId,
358 ) -> bool {
359 if self.cx.document_hidden() {
360 return true;
361 }
362 let tcx = self.cx.tcx;
363 let item_def_id = reexport_chain(tcx, import_def_id, target_def_id.to_def_id())
364 .iter()
365 .flat_map(|reexport| reexport.id())
366 .map(|id| id.expect_local())
367 .nth(1)
368 .unwrap_or(target_def_id);
369 item_def_id != import_def_id
370 && self.cx.cache.effective_visibilities.is_directly_public(tcx, item_def_id.to_def_id())
371 && !tcx.is_doc_hidden(item_def_id)
372 && !inherits_doc_hidden(tcx, item_def_id, None)
373 }
374
375 #[inline]
376 fn add_impl_to_current_mod(&mut self, item: &'tcx hir::Item<'_>, impl_: hir::Impl<'_>) {
377 self.add_to_current_mod(
378 item,
379 if impl_.of_trait.is_none() { None } else { Some(rustc_span::symbol::kw::Impl) },
384 None,
385 );
386 }
387
388 #[inline]
389 fn add_to_current_mod(
390 &mut self,
391 item: &'tcx hir::Item<'_>,
392 mut renamed: Option<Symbol>,
393 import_id: Option<LocalDefId>,
394 ) {
395 if self.is_importable_from_parent
396 || match item.kind {
399 hir::ItemKind::Impl(..) => true,
400 hir::ItemKind::Macro(_, _, _) => {
401 find_attr!(self.cx.tcx, item.owner_id.def_id, MacroExport{..})
402 }
403 _ => false,
404 }
405 {
406 if renamed == item.kind.ident().map(|ident| ident.name) {
407 renamed = None;
408 }
409 let key = (item.owner_id.def_id, renamed);
410 if let Some(import_id) = import_id {
411 self.modules
412 .last_mut()
413 .unwrap()
414 .items
415 .entry(key)
416 .and_modify(|v| v.2.push(import_id))
417 .or_insert_with(|| (item, renamed, vec![import_id]));
418 } else {
419 self.modules.last_mut().unwrap().items.insert(key, (item, renamed, Vec::new()));
420 }
421 }
422 }
423
424 fn visit_item_inner(
425 &mut self,
426 item: &'tcx hir::Item<'_>,
427 renamed: Option<Symbol>,
428 import_id: Option<LocalDefId>,
429 ) {
430 debug!("visiting item {item:?}");
431 if self.inside_body {
432 if let hir::ItemKind::Impl(impl_) = item.kind {
443 self.add_impl_to_current_mod(item, impl_);
444 }
445 return;
446 }
447 let get_name = || renamed.unwrap_or(item.kind.ident().unwrap().name);
448 let tcx = self.cx.tcx;
449
450 let def_id = item.owner_id.to_def_id();
451 let is_pub = tcx.visibility(def_id).is_public();
452
453 if is_pub {
454 self.store_path(item.owner_id.to_def_id());
455 }
456
457 match item.kind {
458 hir::ItemKind::ForeignMod { items, .. } => {
459 for &item in items {
460 let item = tcx.hir_foreign_item(item);
461 self.visit_foreign_item_inner(item, None, None);
462 }
463 }
464 _ if self.inlining && !is_pub => {}
466 hir::ItemKind::GlobalAsm { .. } => {}
467 hir::ItemKind::Use(_, hir::UseKind::ListStem) => {}
468 hir::ItemKind::Use(path, kind) => {
469 for res in path.res.present_items() {
470 if should_ignore_res(res) {
473 continue;
474 }
475
476 let attrs = tcx.hir_attrs(tcx.local_def_id_to_hir_id(item.owner_id.def_id));
477
478 if is_pub && self.inside_public_path {
481 let please_inline = find_attr!(
482 attrs,
483 Doc(d)
484 if d.inline.first().is_some_and(|(inline, _)| *inline == DocInline::Inline)
485 );
486 let ident = match kind {
487 hir::UseKind::Single(ident) => Some(ident.name),
488 hir::UseKind::Glob => None,
489 hir::UseKind::ListStem => unreachable!(),
490 };
491 if self.maybe_inline_local(item.owner_id.def_id, res, ident, please_inline)
492 {
493 debug!("Inlining {:?}", item.owner_id.def_id);
494 continue;
495 }
496 }
497 self.add_to_current_mod(item, renamed, import_id);
498 }
499 }
500 hir::ItemKind::Macro(_, macro_def, _) => {
501 let def_id = item.owner_id.to_def_id();
513 let is_macro_2_0 = !macro_def.macro_rules;
514 let nonexported = !find_attr!(tcx, def_id, MacroExport { .. });
515
516 if is_macro_2_0 || nonexported || self.inlining {
517 self.add_to_current_mod(item, renamed, import_id);
518 }
519 }
520 hir::ItemKind::Mod(_, m) => {
521 self.enter_mod(item.owner_id.def_id, m, get_name(), renamed, import_id);
522 }
523 hir::ItemKind::Fn { .. }
524 | hir::ItemKind::ExternCrate(..)
525 | hir::ItemKind::Enum(..)
526 | hir::ItemKind::Struct(..)
527 | hir::ItemKind::Union(..)
528 | hir::ItemKind::TyAlias(..)
529 | hir::ItemKind::Static(..)
530 | hir::ItemKind::Trait(..)
531 | hir::ItemKind::TraitAlias(..) => {
532 self.add_to_current_mod(item, renamed, import_id);
533 }
534 hir::ItemKind::Const(..) => {
535 if get_name() != kw::Underscore {
538 self.add_to_current_mod(item, renamed, import_id);
539 }
540 }
541 hir::ItemKind::Impl(impl_) => {
542 if !self.inlining {
545 self.add_impl_to_current_mod(item, impl_);
546 }
547 }
548 }
549 }
550
551 fn visit_foreign_item_inner(
552 &mut self,
553 item: &'tcx hir::ForeignItem<'_>,
554 renamed: Option<Symbol>,
555 import_id: Option<LocalDefId>,
556 ) {
557 if !self.inlining || self.cx.tcx.visibility(item.owner_id).is_public() {
559 self.modules.last_mut().unwrap().foreigns.push((item, renamed, import_id));
560 }
561 }
562
563 fn enter_mod(
567 &mut self,
568 id: LocalDefId,
569 m: &'tcx hir::Mod<'tcx>,
570 name: Symbol,
571 renamed: Option<Symbol>,
572 import_id: Option<LocalDefId>,
573 ) {
574 self.modules.push(Module::new(name, id, m.spans.inner_span, renamed, import_id));
575
576 self.visit_mod_contents(id, m);
577
578 let last = self.modules.pop().unwrap();
579 self.modules.last_mut().unwrap().mods.push(last);
580 }
581}
582
583impl<'tcx> Visitor<'tcx> for RustdocVisitor<'_, 'tcx> {
586 type NestedFilter = nested_filter::All;
587
588 fn maybe_tcx(&mut self) -> Self::MaybeTyCtxt {
589 self.cx.tcx
590 }
591
592 fn visit_item(&mut self, i: &'tcx hir::Item<'tcx>) {
593 self.visit_item_inner(i, None, None);
594 let new_value = self.is_importable_from_parent
595 && matches!(
596 i.kind,
597 hir::ItemKind::Mod(..)
598 | hir::ItemKind::ForeignMod { .. }
599 | hir::ItemKind::Impl(..)
600 | hir::ItemKind::Trait(..)
601 );
602 let prev = mem::replace(&mut self.is_importable_from_parent, new_value);
603 walk_item(self, i);
604 self.is_importable_from_parent = prev;
605 }
606
607 fn visit_mod(&mut self, _: &hir::Mod<'tcx>, _: Span, _: hir::HirId) {
608 }
610
611 fn visit_use(&mut self, _: &hir::UsePath<'tcx>, _: hir::HirId) {
612 }
614
615 fn visit_path(&mut self, _: &hir::Path<'tcx>, _: hir::HirId) {
616 }
618
619 fn visit_label(&mut self, _: &rustc_ast::Label) {
620 }
622
623 fn visit_infer(
624 &mut self,
625 _inf_id: hir::HirId,
626 _inf_span: Span,
627 _kind: hir::intravisit::InferKind<'tcx>,
628 ) -> Self::Result {
629 }
631
632 fn visit_lifetime(&mut self, _: &hir::Lifetime) {
633 }
635
636 fn visit_body(&mut self, b: &hir::Body<'tcx>) {
637 let prev = mem::replace(&mut self.inside_body, true);
638 walk_body(self, b);
639 self.inside_body = prev;
640 }
641}