]> git.lizzy.rs Git - rust.git/blob - src/librustdoc/html/render/span_map.rs
Rollup merge of #89876 - AlexApps99:const_ops, r=oli-obk
[rust.git] / src / librustdoc / html / render / span_map.rs
1 use crate::clean::{self, PrimitiveType};
2 use crate::html::sources;
3
4 use rustc_data_structures::fx::FxHashMap;
5 use rustc_hir::def::{DefKind, Res};
6 use rustc_hir::def_id::DefId;
7 use rustc_hir::intravisit::{self, NestedVisitorMap, Visitor};
8 use rustc_hir::{ExprKind, GenericParam, GenericParamKind, HirId, Mod, Node};
9 use rustc_middle::ty::TyCtxt;
10 use rustc_span::Span;
11
12 use std::path::{Path, PathBuf};
13
14 /// This enum allows us to store two different kinds of information:
15 ///
16 /// In case the `span` definition comes from the same crate, we can simply get the `span` and use
17 /// it as is.
18 ///
19 /// Otherwise, we store the definition `DefId` and will generate a link to the documentation page
20 /// instead of the source code directly.
21 #[derive(Debug)]
22 crate enum LinkFromSrc {
23     Local(clean::Span),
24     External(DefId),
25     Primitive(PrimitiveType),
26 }
27
28 /// This function will do at most two things:
29 ///
30 /// 1. Generate a `span` correspondance map which links an item `span` to its definition `span`.
31 /// 2. Collect the source code files.
32 ///
33 /// It returns the `krate`, the source code files and the `span` correspondance map.
34 ///
35 /// Note about the `span` correspondance map: the keys are actually `(lo, hi)` of `span`s. We don't
36 /// need the `span` context later on, only their position, so instead of keep a whole `Span`, we
37 /// only keep the `lo` and `hi`.
38 crate fn collect_spans_and_sources(
39     tcx: TyCtxt<'_>,
40     krate: clean::Crate,
41     src_root: &Path,
42     include_sources: bool,
43     generate_link_to_definition: bool,
44 ) -> (clean::Crate, FxHashMap<PathBuf, String>, FxHashMap<Span, LinkFromSrc>) {
45     let mut visitor = SpanMapVisitor { tcx, matches: FxHashMap::default() };
46
47     if include_sources {
48         if generate_link_to_definition {
49             tcx.hir().walk_toplevel_module(&mut visitor);
50         }
51         let (krate, sources) = sources::collect_local_sources(tcx, src_root, krate);
52         (krate, sources, visitor.matches)
53     } else {
54         (krate, Default::default(), Default::default())
55     }
56 }
57
58 struct SpanMapVisitor<'tcx> {
59     crate tcx: TyCtxt<'tcx>,
60     crate matches: FxHashMap<Span, LinkFromSrc>,
61 }
62
63 impl<'tcx> SpanMapVisitor<'tcx> {
64     /// This function is where we handle `hir::Path` elements and add them into the "span map".
65     fn handle_path(&mut self, path: &rustc_hir::Path<'_>, path_span: Option<Span>) {
66         let info = match path.res {
67             // FIXME: For now, we only handle `DefKind` if it's not `DefKind::TyParam` or
68             // `DefKind::Macro`. Would be nice to support them too alongside the other `DefKind`
69             // (such as primitive types!).
70             Res::Def(kind, def_id) if kind != DefKind::TyParam => {
71                 if matches!(kind, DefKind::Macro(_)) {
72                     return;
73                 }
74                 Some(def_id)
75             }
76             Res::Local(_) => None,
77             Res::PrimTy(p) => {
78                 // FIXME: Doesn't handle "path-like" primitives like arrays or tuples.
79                 let span = path_span.unwrap_or(path.span);
80                 self.matches.insert(span, LinkFromSrc::Primitive(PrimitiveType::from(p)));
81                 return;
82             }
83             Res::Err => return,
84             _ => return,
85         };
86         if let Some(span) = self.tcx.hir().res_span(path.res) {
87             self.matches
88                 .insert(path_span.unwrap_or(path.span), LinkFromSrc::Local(clean::Span::new(span)));
89         } else if let Some(def_id) = info {
90             self.matches.insert(path_span.unwrap_or(path.span), LinkFromSrc::External(def_id));
91         }
92     }
93 }
94
95 impl Visitor<'tcx> for SpanMapVisitor<'tcx> {
96     type Map = rustc_middle::hir::map::Map<'tcx>;
97
98     fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
99         NestedVisitorMap::All(self.tcx.hir())
100     }
101
102     fn visit_generic_param(&mut self, p: &'tcx GenericParam<'tcx>) {
103         if !matches!(p.kind, GenericParamKind::Type { .. }) {
104             return;
105         }
106         for bound in p.bounds {
107             if let Some(trait_ref) = bound.trait_ref() {
108                 self.handle_path(trait_ref.path, None);
109             }
110         }
111     }
112
113     fn visit_path(&mut self, path: &'tcx rustc_hir::Path<'tcx>, _id: HirId) {
114         self.handle_path(path, None);
115         intravisit::walk_path(self, path);
116     }
117
118     fn visit_mod(&mut self, m: &'tcx Mod<'tcx>, span: Span, id: HirId) {
119         // To make the difference between "mod foo {}" and "mod foo;". In case we "import" another
120         // file, we want to link to it. Otherwise no need to create a link.
121         if !span.overlaps(m.inner) {
122             // Now that we confirmed it's a file import, we want to get the span for the module
123             // name only and not all the "mod foo;".
124             if let Some(Node::Item(item)) = self.tcx.hir().find(id) {
125                 self.matches.insert(item.ident.span, LinkFromSrc::Local(clean::Span::new(m.inner)));
126             }
127         }
128         intravisit::walk_mod(self, m, id);
129     }
130
131     fn visit_expr(&mut self, expr: &'tcx rustc_hir::Expr<'tcx>) {
132         if let ExprKind::MethodCall(segment, method_span, _, _) = expr.kind {
133             if let Some(hir_id) = segment.hir_id {
134                 let hir = self.tcx.hir();
135                 let body_id = hir.enclosing_body_owner(hir_id);
136                 let typeck_results = self.tcx.sess.with_disabled_diagnostic(|| {
137                     self.tcx.typeck_body(
138                         hir.maybe_body_owned_by(body_id).expect("a body which isn't a body"),
139                     )
140                 });
141                 if let Some(def_id) = typeck_results.type_dependent_def_id(expr.hir_id) {
142                     self.matches.insert(
143                         method_span,
144                         match hir.span_if_local(def_id) {
145                             Some(span) => LinkFromSrc::Local(clean::Span::new(span)),
146                             None => LinkFromSrc::External(def_id),
147                         },
148                     );
149                 }
150             }
151         }
152         intravisit::walk_expr(self, expr);
153     }
154
155     fn visit_use(&mut self, path: &'tcx rustc_hir::Path<'tcx>, id: HirId) {
156         self.handle_path(path, None);
157         intravisit::walk_use(self, path, id);
158     }
159 }