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::{AttributeKind, 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.get_all_attrs(def_id), AttributeKind::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 AttributeKind::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_to_current_mod(
377 &mut self,
378 item: &'tcx hir::Item<'_>,
379 mut renamed: Option<Symbol>,
380 import_id: Option<LocalDefId>,
381 ) {
382 if self.is_importable_from_parent
383 || match item.kind {
386 hir::ItemKind::Impl(..) => true,
387 hir::ItemKind::Macro(_, _, _) => {
388 find_attr!(self.cx.tcx.get_all_attrs(item.owner_id.def_id), AttributeKind::MacroExport{..})
389 }
390 _ => false,
391 }
392 {
393 if renamed == item.kind.ident().map(|ident| ident.name) {
394 renamed = None;
395 }
396 let key = (item.owner_id.def_id, renamed);
397 if let Some(import_id) = import_id {
398 self.modules
399 .last_mut()
400 .unwrap()
401 .items
402 .entry(key)
403 .and_modify(|v| v.2.push(import_id))
404 .or_insert_with(|| (item, renamed, vec![import_id]));
405 } else {
406 self.modules.last_mut().unwrap().items.insert(key, (item, renamed, Vec::new()));
407 }
408 }
409 }
410
411 fn visit_item_inner(
412 &mut self,
413 item: &'tcx hir::Item<'_>,
414 renamed: Option<Symbol>,
415 import_id: Option<LocalDefId>,
416 ) {
417 debug!("visiting item {item:?}");
418 if self.inside_body {
419 if let hir::ItemKind::Impl(impl_) = item.kind &&
430 impl_.of_trait.is_none()
433 {
434 self.add_to_current_mod(item, None, None);
435 }
436 return;
437 }
438 let get_name = || renamed.unwrap_or(item.kind.ident().unwrap().name);
439 let tcx = self.cx.tcx;
440
441 let def_id = item.owner_id.to_def_id();
442 let is_pub = tcx.visibility(def_id).is_public();
443
444 if is_pub {
445 self.store_path(item.owner_id.to_def_id());
446 }
447
448 match item.kind {
449 hir::ItemKind::ForeignMod { items, .. } => {
450 for &item in items {
451 let item = tcx.hir_foreign_item(item);
452 self.visit_foreign_item_inner(item, None, None);
453 }
454 }
455 _ if self.inlining && !is_pub => {}
457 hir::ItemKind::GlobalAsm { .. } => {}
458 hir::ItemKind::Use(_, hir::UseKind::ListStem) => {}
459 hir::ItemKind::Use(path, kind) => {
460 for res in path.res.present_items() {
461 if should_ignore_res(res) {
464 continue;
465 }
466
467 let attrs = tcx.hir_attrs(tcx.local_def_id_to_hir_id(item.owner_id.def_id));
468
469 if is_pub && self.inside_public_path {
472 let please_inline = find_attr!(
473 attrs,
474 AttributeKind::Doc(d)
475 if d.inline.first().is_some_and(|(inline, _)| *inline == DocInline::Inline)
476 );
477 let ident = match kind {
478 hir::UseKind::Single(ident) => Some(ident.name),
479 hir::UseKind::Glob => None,
480 hir::UseKind::ListStem => unreachable!(),
481 };
482 if self.maybe_inline_local(item.owner_id.def_id, res, ident, please_inline)
483 {
484 debug!("Inlining {:?}", item.owner_id.def_id);
485 continue;
486 }
487 }
488 self.add_to_current_mod(item, renamed, import_id);
489 }
490 }
491 hir::ItemKind::Macro(_, macro_def, _) => {
492 let def_id = item.owner_id.to_def_id();
504 let is_macro_2_0 = !macro_def.macro_rules;
505 let nonexported =
506 !find_attr!(tcx.get_all_attrs(def_id), AttributeKind::MacroExport { .. });
507
508 if is_macro_2_0 || nonexported || self.inlining {
509 self.add_to_current_mod(item, renamed, import_id);
510 }
511 }
512 hir::ItemKind::Mod(_, m) => {
513 self.enter_mod(item.owner_id.def_id, m, get_name(), renamed, import_id);
514 }
515 hir::ItemKind::Fn { .. }
516 | hir::ItemKind::ExternCrate(..)
517 | hir::ItemKind::Enum(..)
518 | hir::ItemKind::Struct(..)
519 | hir::ItemKind::Union(..)
520 | hir::ItemKind::TyAlias(..)
521 | hir::ItemKind::Static(..)
522 | hir::ItemKind::Trait(..)
523 | hir::ItemKind::TraitAlias(..) => {
524 self.add_to_current_mod(item, renamed, import_id);
525 }
526 hir::ItemKind::Const(..) => {
527 if get_name() != kw::Underscore {
530 self.add_to_current_mod(item, renamed, import_id);
531 }
532 }
533 hir::ItemKind::Impl(impl_) => {
534 if !self.inlining && impl_.of_trait.is_none() {
537 self.add_to_current_mod(item, None, None);
538 }
539 }
540 }
541 }
542
543 fn visit_foreign_item_inner(
544 &mut self,
545 item: &'tcx hir::ForeignItem<'_>,
546 renamed: Option<Symbol>,
547 import_id: Option<LocalDefId>,
548 ) {
549 if !self.inlining || self.cx.tcx.visibility(item.owner_id).is_public() {
551 self.modules.last_mut().unwrap().foreigns.push((item, renamed, import_id));
552 }
553 }
554
555 fn enter_mod(
559 &mut self,
560 id: LocalDefId,
561 m: &'tcx hir::Mod<'tcx>,
562 name: Symbol,
563 renamed: Option<Symbol>,
564 import_id: Option<LocalDefId>,
565 ) {
566 self.modules.push(Module::new(name, id, m.spans.inner_span, renamed, import_id));
567
568 self.visit_mod_contents(id, m);
569
570 let last = self.modules.pop().unwrap();
571 self.modules.last_mut().unwrap().mods.push(last);
572 }
573}
574
575impl<'tcx> Visitor<'tcx> for RustdocVisitor<'_, 'tcx> {
578 type NestedFilter = nested_filter::All;
579
580 fn maybe_tcx(&mut self) -> Self::MaybeTyCtxt {
581 self.cx.tcx
582 }
583
584 fn visit_item(&mut self, i: &'tcx hir::Item<'tcx>) {
585 self.visit_item_inner(i, None, None);
586 let new_value = self.is_importable_from_parent
587 && matches!(
588 i.kind,
589 hir::ItemKind::Mod(..)
590 | hir::ItemKind::ForeignMod { .. }
591 | hir::ItemKind::Impl(..)
592 | hir::ItemKind::Trait(..)
593 );
594 let prev = mem::replace(&mut self.is_importable_from_parent, new_value);
595 walk_item(self, i);
596 self.is_importable_from_parent = prev;
597 }
598
599 fn visit_mod(&mut self, _: &hir::Mod<'tcx>, _: Span, _: hir::HirId) {
600 }
602
603 fn visit_use(&mut self, _: &hir::UsePath<'tcx>, _: hir::HirId) {
604 }
606
607 fn visit_path(&mut self, _: &hir::Path<'tcx>, _: hir::HirId) {
608 }
610
611 fn visit_label(&mut self, _: &rustc_ast::Label) {
612 }
614
615 fn visit_infer(
616 &mut self,
617 _inf_id: hir::HirId,
618 _inf_span: Span,
619 _kind: hir::intravisit::InferKind<'tcx>,
620 ) -> Self::Result {
621 }
623
624 fn visit_lifetime(&mut self, _: &hir::Lifetime) {
625 }
627
628 fn visit_body(&mut self, b: &hir::Body<'tcx>) {
629 let prev = mem::replace(&mut self.inside_body, true);
630 walk_body(self, b);
631 self.inside_body = prev;
632 }
633}