1use std::mem;
5
6use rustc_data_structures::fx::{FxHashSet, FxIndexMap};
7use rustc_hir as hir;
8use rustc_hir::def::{DefKind, Res};
9use rustc_hir::def_id::{DefId, DefIdMap, LocalDefId, LocalDefIdSet};
10use rustc_hir::intravisit::{Visitor, walk_body, walk_item};
11use rustc_hir::{CRATE_HIR_ID, Node};
12use rustc_middle::hir::nested_filter;
13use rustc_middle::ty::TyCtxt;
14use rustc_span::Span;
15use rustc_span::def_id::{CRATE_DEF_ID, LOCAL_CRATE};
16use rustc_span::hygiene::MacroKind;
17use rustc_span::symbol::{Symbol, kw, sym};
18use tracing::debug;
19
20use crate::clean::cfg::Cfg;
21use crate::clean::utils::{inherits_doc_hidden, should_ignore_res};
22use crate::clean::{NestedAttributesExt, hir_attr_lists, reexport_chain};
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<
38 (LocalDefId, Option<Symbol>),
39 (&'hir hir::Item<'hir>, Option<Symbol>, Option<LocalDefId>),
40 >,
41
42 pub(crate) inlined_foreigns: FxIndexMap<(DefId, Option<Symbol>), (Res, LocalDefId)>,
51 pub(crate) foreigns: Vec<(&'hir hir::ForeignItem<'hir>, Option<Symbol>, Option<LocalDefId>)>,
53}
54
55impl Module<'_> {
56 pub(crate) fn new(
57 name: Symbol,
58 def_id: LocalDefId,
59 where_inner: Span,
60 renamed: Option<Symbol>,
61 import_id: Option<LocalDefId>,
62 ) -> Self {
63 Module {
64 name,
65 def_id,
66 where_inner,
67 renamed,
68 import_id,
69 mods: Vec::new(),
70 items: FxIndexMap::default(),
71 inlined_foreigns: FxIndexMap::default(),
72 foreigns: Vec::new(),
73 }
74 }
75
76 pub(crate) fn where_outer(&self, tcx: TyCtxt<'_>) -> Span {
77 tcx.def_span(self.def_id)
78 }
79}
80
81fn def_id_to_path(tcx: TyCtxt<'_>, did: DefId) -> Vec<Symbol> {
83 let crate_name = tcx.crate_name(did.krate);
84 let relative = tcx.def_path(did).data.into_iter().filter_map(|elem| elem.data.get_opt_name());
85 std::iter::once(crate_name).chain(relative).collect()
86}
87
88pub(crate) struct RustdocVisitor<'a, 'tcx> {
89 cx: &'a mut core::DocContext<'tcx>,
90 view_item_stack: LocalDefIdSet,
91 inlining: bool,
92 inside_public_path: bool,
94 exact_paths: DefIdMap<Vec<Symbol>>,
95 modules: Vec<Module<'tcx>>,
96 is_importable_from_parent: bool,
97 inside_body: bool,
98}
99
100impl<'a, 'tcx> RustdocVisitor<'a, 'tcx> {
101 pub(crate) fn new(cx: &'a mut core::DocContext<'tcx>) -> RustdocVisitor<'a, 'tcx> {
102 let mut stack = LocalDefIdSet::default();
104 stack.insert(CRATE_DEF_ID);
105 let om = Module::new(
106 cx.tcx.crate_name(LOCAL_CRATE),
107 CRATE_DEF_ID,
108 cx.tcx.hir_root_module().spans.inner_span,
109 None,
110 None,
111 );
112
113 RustdocVisitor {
114 cx,
115 view_item_stack: stack,
116 inlining: false,
117 inside_public_path: true,
118 exact_paths: Default::default(),
119 modules: vec![om],
120 is_importable_from_parent: true,
121 inside_body: false,
122 }
123 }
124
125 fn store_path(&mut self, did: DefId) {
126 let tcx = self.cx.tcx;
127 self.exact_paths.entry(did).or_insert_with(|| def_id_to_path(tcx, did));
128 }
129
130 pub(crate) fn visit(mut self) -> Module<'tcx> {
131 let root_module = self.cx.tcx.hir_root_module();
132 self.visit_mod_contents(CRATE_DEF_ID, root_module);
133
134 let mut top_level_module = self.modules.pop().unwrap();
135
136 let mut inserted = FxHashSet::default();
148 for child in self.cx.tcx.module_children_local(CRATE_DEF_ID) {
149 if !child.reexport_chain.is_empty()
150 && let Res::Def(DefKind::Macro(_), def_id) = child.res
151 && let Some(local_def_id) = def_id.as_local()
152 && self.cx.tcx.has_attr(def_id, sym::macro_export)
153 && inserted.insert(def_id)
154 {
155 let item = self.cx.tcx.hir_expect_item(local_def_id);
156 let (ident, _, _) = item.expect_macro();
157 top_level_module.items.insert((local_def_id, Some(ident.name)), (item, None, None));
158 }
159 }
160
161 self.cx.cache.hidden_cfg = self
162 .cx
163 .tcx
164 .hir_attrs(CRATE_HIR_ID)
165 .iter()
166 .filter(|attr| attr.has_name(sym::doc))
167 .flat_map(|attr| attr.meta_item_list().into_iter().flatten())
168 .filter(|attr| attr.has_name(sym::cfg_hide))
169 .flat_map(|attr| {
170 attr.meta_item_list()
171 .unwrap_or(&[])
172 .iter()
173 .filter_map(|attr| {
174 Cfg::parse(attr)
175 .map_err(|e| self.cx.sess().dcx().span_err(e.span, e.msg))
176 .ok()
177 })
178 .collect::<Vec<_>>()
179 })
180 .chain([
181 Cfg::Cfg(sym::test, None),
182 Cfg::Cfg(sym::doc, None),
183 Cfg::Cfg(sym::doctest, None),
184 ])
185 .collect();
186
187 self.cx.cache.exact_paths = self.exact_paths;
188 top_level_module
189 }
190
191 fn visit_mod_contents(&mut self, def_id: LocalDefId, m: &'tcx hir::Mod<'tcx>) {
195 debug!("Going through module {m:?}");
196 let orig_inside_public_path = self.inside_public_path;
198 self.inside_public_path &= self.cx.tcx.local_visibility(def_id).is_public();
199
200 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)) {
205 self.visit_item(item);
206 }
207 }
208 for &i in m.item_ids {
209 let item = self.cx.tcx.hir_item(i);
210 if matches!(item.kind, hir::ItemKind::Use(_, hir::UseKind::Glob)) {
214 self.visit_item(item);
215 }
216 }
217 self.inside_public_path = orig_inside_public_path;
218 debug!("Leaving module {m:?}");
219 }
220
221 fn maybe_inline_local(
231 &mut self,
232 def_id: LocalDefId,
233 res: Res,
234 renamed: Option<Symbol>,
235 please_inline: bool,
236 ) -> bool {
237 debug!("maybe_inline_local (renamed: {renamed:?}) res: {res:?}");
238
239 let glob = renamed.is_none();
240 if renamed == Some(kw::Underscore) {
241 return false;
243 }
244
245 if self.cx.is_json_output() {
246 return false;
247 }
248
249 let tcx = self.cx.tcx;
250 let Some(ori_res_did) = res.opt_def_id() else {
251 return false;
252 };
253
254 let document_hidden = self.cx.render_options.document_hidden;
255 let use_attrs = tcx.hir_attrs(tcx.local_def_id_to_hir_id(def_id));
256 let is_no_inline = hir_attr_lists(use_attrs, sym::doc).has_word(sym::no_inline)
258 || (document_hidden && hir_attr_lists(use_attrs, sym::doc).has_word(sym::hidden));
259
260 if is_no_inline {
261 return false;
262 }
263
264 let is_hidden = !document_hidden && tcx.is_doc_hidden(ori_res_did);
265 let Some(res_did) = ori_res_did.as_local() else {
266 crate::visit_lib::lib_embargo_visit_item(self.cx, ori_res_did);
271 if is_hidden || glob {
272 return false;
273 }
274 self.modules
280 .last_mut()
281 .unwrap()
282 .inlined_foreigns
283 .insert((ori_res_did, renamed), (res, def_id));
284 return true;
285 };
286
287 let is_private = !self.cx.cache.effective_visibilities.is_directly_public(tcx, ori_res_did);
288 let item = tcx.hir_node_by_def_id(res_did);
289
290 if !please_inline {
291 let inherits_hidden = !document_hidden && inherits_doc_hidden(tcx, res_did, None);
292 if (!is_private && !inherits_hidden) || (
294 is_hidden &&
295 !matches!(item, Node::Item(&hir::Item { kind: hir::ItemKind::Mod(..), .. }))
298 ) ||
299 self.reexport_public_and_not_hidden(def_id, res_did)
301 {
302 return false;
303 }
304 }
305
306 let is_bang_macro = matches!(
307 item,
308 Node::Item(&hir::Item { kind: hir::ItemKind::Macro(_, _, MacroKind::Bang), .. })
309 );
310
311 if !self.view_item_stack.insert(res_did) && !is_bang_macro {
312 return false;
313 }
314
315 let inlined = match item {
316 Node::Item(_) if is_bang_macro && !please_inline && renamed.is_some() && is_hidden => {
320 return false;
321 }
322 Node::Item(&hir::Item { kind: hir::ItemKind::Mod(_, m), .. }) if glob => {
323 let prev = mem::replace(&mut self.inlining, true);
324 for &i in m.item_ids {
325 let i = tcx.hir_item(i);
326 self.visit_item_inner(i, None, Some(def_id));
327 }
328 self.inlining = prev;
329 true
330 }
331 Node::Item(it) if !glob => {
332 let prev = mem::replace(&mut self.inlining, true);
333 self.visit_item_inner(it, renamed, Some(def_id));
334 self.inlining = prev;
335 true
336 }
337 Node::ForeignItem(it) if !glob => {
338 let prev = mem::replace(&mut self.inlining, true);
339 self.visit_foreign_item_inner(it, renamed, Some(def_id));
340 self.inlining = prev;
341 true
342 }
343 _ => false,
344 };
345 self.view_item_stack.remove(&res_did);
346 if inlined {
347 self.cx.cache.inlined_items.insert(ori_res_did);
348 }
349 inlined
350 }
351
352 fn reexport_public_and_not_hidden(
357 &self,
358 import_def_id: LocalDefId,
359 target_def_id: LocalDefId,
360 ) -> bool {
361 if self.cx.render_options.document_hidden {
362 return true;
363 }
364 let tcx = self.cx.tcx;
365 let item_def_id = reexport_chain(tcx, import_def_id, target_def_id.to_def_id())
366 .iter()
367 .flat_map(|reexport| reexport.id())
368 .map(|id| id.expect_local())
369 .nth(1)
370 .unwrap_or(target_def_id);
371 item_def_id != import_def_id
372 && self.cx.cache.effective_visibilities.is_directly_public(tcx, item_def_id.to_def_id())
373 && !tcx.is_doc_hidden(item_def_id)
374 && !inherits_doc_hidden(tcx, item_def_id, None)
375 }
376
377 #[inline]
378 fn add_to_current_mod(
379 &mut self,
380 item: &'tcx hir::Item<'_>,
381 renamed: Option<Symbol>,
382 parent_id: Option<LocalDefId>,
383 ) {
384 if self.is_importable_from_parent
385 || match item.kind {
388 hir::ItemKind::Impl(..) => true,
389 hir::ItemKind::Macro(_, _, MacroKind::Bang) => {
390 self.cx.tcx.has_attr(item.owner_id.def_id, sym::macro_export)
391 }
392 _ => false,
393 }
394 {
395 self.modules
396 .last_mut()
397 .unwrap()
398 .items
399 .insert((item.owner_id.def_id, renamed), (item, renamed, parent_id));
400 }
401 }
402
403 fn visit_item_inner(
404 &mut self,
405 item: &'tcx hir::Item<'_>,
406 renamed: Option<Symbol>,
407 import_id: Option<LocalDefId>,
408 ) {
409 debug!("visiting item {item:?}");
410 if self.inside_body {
411 if let hir::ItemKind::Impl(impl_) = item.kind &&
422 impl_.of_trait.is_none()
425 {
426 self.add_to_current_mod(item, None, None);
427 }
428 return;
429 }
430 let get_name = || renamed.unwrap_or(item.kind.ident().unwrap().name);
431 let tcx = self.cx.tcx;
432
433 let def_id = item.owner_id.to_def_id();
434 let is_pub = tcx.visibility(def_id).is_public();
435
436 if is_pub {
437 self.store_path(item.owner_id.to_def_id());
438 }
439
440 match item.kind {
441 hir::ItemKind::ForeignMod { items, .. } => {
442 for item in items {
443 let item = tcx.hir_foreign_item(item.id);
444 self.visit_foreign_item_inner(item, None, None);
445 }
446 }
447 _ if self.inlining && !is_pub => {}
449 hir::ItemKind::GlobalAsm { .. } => {}
450 hir::ItemKind::Use(_, hir::UseKind::ListStem) => {}
451 hir::ItemKind::Use(path, kind) => {
452 for res in path.res.present_items() {
453 if should_ignore_res(res) {
456 continue;
457 }
458
459 let attrs = tcx.hir_attrs(tcx.local_def_id_to_hir_id(item.owner_id.def_id));
460
461 if is_pub && self.inside_public_path {
464 let please_inline = attrs.iter().any(|item| match item.meta_item_list() {
465 Some(ref list) if item.has_name(sym::doc) => {
466 list.iter().any(|i| i.has_name(sym::inline))
467 }
468 _ => false,
469 });
470 let ident = match kind {
471 hir::UseKind::Single(ident) => Some(renamed.unwrap_or(ident.name)),
472 hir::UseKind::Glob => None,
473 hir::UseKind::ListStem => unreachable!(),
474 };
475 if self.maybe_inline_local(item.owner_id.def_id, res, ident, please_inline)
476 {
477 debug!("Inlining {:?}", item.owner_id.def_id);
478 continue;
479 }
480 }
481 self.add_to_current_mod(item, renamed, import_id);
482 }
483 }
484 hir::ItemKind::Macro(_, macro_def, _) => {
485 let def_id = item.owner_id.to_def_id();
497 let is_macro_2_0 = !macro_def.macro_rules;
498 let nonexported = !tcx.has_attr(def_id, sym::macro_export);
499
500 if is_macro_2_0 || nonexported || self.inlining {
501 self.add_to_current_mod(item, renamed, import_id);
502 }
503 }
504 hir::ItemKind::Mod(_, m) => {
505 self.enter_mod(item.owner_id.def_id, m, get_name(), renamed, import_id);
506 }
507 hir::ItemKind::Fn { .. }
508 | hir::ItemKind::ExternCrate(..)
509 | hir::ItemKind::Enum(..)
510 | hir::ItemKind::Struct(..)
511 | hir::ItemKind::Union(..)
512 | hir::ItemKind::TyAlias(..)
513 | hir::ItemKind::Static(..)
514 | hir::ItemKind::Trait(..)
515 | hir::ItemKind::TraitAlias(..) => {
516 self.add_to_current_mod(item, renamed, import_id);
517 }
518 hir::ItemKind::Const(..) => {
519 if get_name() != kw::Underscore {
522 self.add_to_current_mod(item, renamed, import_id);
523 }
524 }
525 hir::ItemKind::Impl(impl_) => {
526 if !self.inlining && impl_.of_trait.is_none() {
529 self.add_to_current_mod(item, None, None);
530 }
531 }
532 }
533 }
534
535 fn visit_foreign_item_inner(
536 &mut self,
537 item: &'tcx hir::ForeignItem<'_>,
538 renamed: Option<Symbol>,
539 import_id: Option<LocalDefId>,
540 ) {
541 if !self.inlining || self.cx.tcx.visibility(item.owner_id).is_public() {
543 self.modules.last_mut().unwrap().foreigns.push((item, renamed, import_id));
544 }
545 }
546
547 fn enter_mod(
551 &mut self,
552 id: LocalDefId,
553 m: &'tcx hir::Mod<'tcx>,
554 name: Symbol,
555 renamed: Option<Symbol>,
556 import_id: Option<LocalDefId>,
557 ) {
558 self.modules.push(Module::new(name, id, m.spans.inner_span, renamed, import_id));
559
560 self.visit_mod_contents(id, m);
561
562 let last = self.modules.pop().unwrap();
563 self.modules.last_mut().unwrap().mods.push(last);
564 }
565}
566
567impl<'tcx> Visitor<'tcx> for RustdocVisitor<'_, 'tcx> {
570 type NestedFilter = nested_filter::All;
571
572 fn maybe_tcx(&mut self) -> Self::MaybeTyCtxt {
573 self.cx.tcx
574 }
575
576 fn visit_item(&mut self, i: &'tcx hir::Item<'tcx>) {
577 self.visit_item_inner(i, None, None);
578 let new_value = self.is_importable_from_parent
579 && matches!(
580 i.kind,
581 hir::ItemKind::Mod(..)
582 | hir::ItemKind::ForeignMod { .. }
583 | hir::ItemKind::Impl(..)
584 | hir::ItemKind::Trait(..)
585 );
586 let prev = mem::replace(&mut self.is_importable_from_parent, new_value);
587 walk_item(self, i);
588 self.is_importable_from_parent = prev;
589 }
590
591 fn visit_mod(&mut self, _: &hir::Mod<'tcx>, _: Span, _: hir::HirId) {
592 }
594
595 fn visit_use(&mut self, _: &hir::UsePath<'tcx>, _: hir::HirId) {
596 }
598
599 fn visit_path(&mut self, _: &hir::Path<'tcx>, _: hir::HirId) {
600 }
602
603 fn visit_label(&mut self, _: &rustc_ast::Label) {
604 }
606
607 fn visit_infer(
608 &mut self,
609 _inf_id: hir::HirId,
610 _inf_span: Span,
611 _kind: hir::intravisit::InferKind<'tcx>,
612 ) -> Self::Result {
613 }
615
616 fn visit_lifetime(&mut self, _: &hir::Lifetime) {
617 }
619
620 fn visit_body(&mut self, b: &hir::Body<'tcx>) {
621 let prev = mem::replace(&mut self.inside_body, true);
622 walk_body(self, b);
623 self.inside_body = prev;
624 }
625}