]> git.lizzy.rs Git - rust.git/blob - src/librustdoc/html/render/span_map.rs
rustdoc: remove unnecessary wrapper div.item-decl from HTML
[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, Visitor};
8 use rustc_hir::{ExprKind, HirId, Mod, Node};
9 use rustc_middle::hir::nested_filter;
10 use rustc_middle::ty::TyCtxt;
11 use rustc_span::hygiene::MacroKind;
12 use rustc_span::{BytePos, ExpnKind, Span};
13
14 use std::path::{Path, PathBuf};
15
16 /// This enum allows us to store two different kinds of information:
17 ///
18 /// In case the `span` definition comes from the same crate, we can simply get the `span` and use
19 /// it as is.
20 ///
21 /// Otherwise, we store the definition `DefId` and will generate a link to the documentation page
22 /// instead of the source code directly.
23 #[derive(Debug)]
24 pub(crate) enum LinkFromSrc {
25     Local(clean::Span),
26     External(DefId),
27     Primitive(PrimitiveType),
28 }
29
30 /// This function will do at most two things:
31 ///
32 /// 1. Generate a `span` correspondance map which links an item `span` to its definition `span`.
33 /// 2. Collect the source code files.
34 ///
35 /// It returns the `krate`, the source code files and the `span` correspondance map.
36 ///
37 /// Note about the `span` correspondance map: the keys are actually `(lo, hi)` of `span`s. We don't
38 /// need the `span` context later on, only their position, so instead of keep a whole `Span`, we
39 /// only keep the `lo` and `hi`.
40 pub(crate) fn collect_spans_and_sources(
41     tcx: TyCtxt<'_>,
42     krate: &clean::Crate,
43     src_root: &Path,
44     include_sources: bool,
45     generate_link_to_definition: bool,
46 ) -> (FxHashMap<PathBuf, String>, FxHashMap<Span, LinkFromSrc>) {
47     let mut visitor = SpanMapVisitor { tcx, matches: FxHashMap::default() };
48
49     if include_sources {
50         if generate_link_to_definition {
51             tcx.hir().walk_toplevel_module(&mut visitor);
52         }
53         let sources = sources::collect_local_sources(tcx, src_root, krate);
54         (sources, visitor.matches)
55     } else {
56         (Default::default(), Default::default())
57     }
58 }
59
60 struct SpanMapVisitor<'tcx> {
61     pub(crate) tcx: TyCtxt<'tcx>,
62     pub(crate) matches: FxHashMap<Span, LinkFromSrc>,
63 }
64
65 impl<'tcx> SpanMapVisitor<'tcx> {
66     /// This function is where we handle `hir::Path` elements and add them into the "span map".
67     fn handle_path(&mut self, path: &rustc_hir::Path<'_>) {
68         let info = match path.res {
69             // FIXME: For now, we handle `DefKind` if it's not a `DefKind::TyParam`.
70             // Would be nice to support them too alongside the other `DefKind`
71             // (such as primitive types!).
72             Res::Def(kind, def_id) if kind != DefKind::TyParam => Some(def_id),
73             Res::Local(_) => None,
74             Res::PrimTy(p) => {
75                 // FIXME: Doesn't handle "path-like" primitives like arrays or tuples.
76                 self.matches.insert(path.span, LinkFromSrc::Primitive(PrimitiveType::from(p)));
77                 return;
78             }
79             Res::Err => return,
80             _ => return,
81         };
82         if let Some(span) = self.tcx.hir().res_span(path.res) {
83             self.matches.insert(path.span, LinkFromSrc::Local(clean::Span::new(span)));
84         } else if let Some(def_id) = info {
85             self.matches.insert(path.span, LinkFromSrc::External(def_id));
86         }
87     }
88
89     /// Adds the macro call into the span map. Returns `true` if the `span` was inside a macro
90     /// expansion, whether or not it was added to the span map.
91     ///
92     /// The idea for the macro support is to check if the current `Span` comes from expansion. If
93     /// so, we loop until we find the macro definition by using `outer_expn_data` in a loop.
94     /// Finally, we get the information about the macro itself (`span` if "local", `DefId`
95     /// otherwise) and store it inside the span map.
96     fn handle_macro(&mut self, span: Span) -> bool {
97         if !span.from_expansion() {
98             return false;
99         }
100         // So if the `span` comes from a macro expansion, we need to get the original
101         // macro's `DefId`.
102         let mut data = span.ctxt().outer_expn_data();
103         let mut call_site = data.call_site;
104         // Macros can expand to code containing macros, which will in turn be expanded, etc.
105         // So the idea here is to "go up" until we're back to code that was generated from
106         // macro expansion so that we can get the `DefId` of the original macro that was at the
107         // origin of this expansion.
108         while call_site.from_expansion() {
109             data = call_site.ctxt().outer_expn_data();
110             call_site = data.call_site;
111         }
112
113         let macro_name = match data.kind {
114             ExpnKind::Macro(MacroKind::Bang, macro_name) => macro_name,
115             // Even though we don't handle this kind of macro, this `data` still comes from
116             // expansion so we return `true` so we don't go any deeper in this code.
117             _ => return true,
118         };
119         let link_from_src = match data.macro_def_id {
120             Some(macro_def_id) if macro_def_id.is_local() => {
121                 LinkFromSrc::Local(clean::Span::new(data.def_site))
122             }
123             Some(macro_def_id) => LinkFromSrc::External(macro_def_id),
124             None => return true,
125         };
126         let new_span = data.call_site;
127         let macro_name = macro_name.as_str();
128         // The "call_site" includes the whole macro with its "arguments". We only want
129         // the macro name.
130         let new_span = new_span.with_hi(new_span.lo() + BytePos(macro_name.len() as u32));
131         self.matches.insert(new_span, link_from_src);
132         true
133     }
134 }
135
136 impl<'tcx> Visitor<'tcx> for SpanMapVisitor<'tcx> {
137     type NestedFilter = nested_filter::All;
138
139     fn nested_visit_map(&mut self) -> Self::Map {
140         self.tcx.hir()
141     }
142
143     fn visit_path(&mut self, path: &rustc_hir::Path<'tcx>, _id: HirId) {
144         if self.handle_macro(path.span) {
145             return;
146         }
147         self.handle_path(path);
148         intravisit::walk_path(self, path);
149     }
150
151     fn visit_mod(&mut self, m: &'tcx Mod<'tcx>, span: Span, id: HirId) {
152         // To make the difference between "mod foo {}" and "mod foo;". In case we "import" another
153         // file, we want to link to it. Otherwise no need to create a link.
154         if !span.overlaps(m.spans.inner_span) {
155             // Now that we confirmed it's a file import, we want to get the span for the module
156             // name only and not all the "mod foo;".
157             if let Some(Node::Item(item)) = self.tcx.hir().find(id) {
158                 self.matches.insert(
159                     item.ident.span,
160                     LinkFromSrc::Local(clean::Span::new(m.spans.inner_span)),
161                 );
162             }
163         }
164         intravisit::walk_mod(self, m, id);
165     }
166
167     fn visit_expr(&mut self, expr: &'tcx rustc_hir::Expr<'tcx>) {
168         if let ExprKind::MethodCall(segment, ..) = expr.kind {
169             let hir = self.tcx.hir();
170             let body_id = hir.enclosing_body_owner(segment.hir_id);
171             // FIXME: this is showing error messages for parts of the code that are not
172             // compiled (because of cfg)!
173             //
174             // See discussion in https://github.com/rust-lang/rust/issues/69426#issuecomment-1019412352
175             let typeck_results = self
176                 .tcx
177                 .typeck_body(hir.maybe_body_owned_by(body_id).expect("a body which isn't a body"));
178             if let Some(def_id) = typeck_results.type_dependent_def_id(expr.hir_id) {
179                 self.matches.insert(
180                     segment.ident.span,
181                     match hir.span_if_local(def_id) {
182                         Some(span) => LinkFromSrc::Local(clean::Span::new(span)),
183                         None => LinkFromSrc::External(def_id),
184                     },
185                 );
186             }
187         } else if self.handle_macro(expr.span) {
188             // We don't want to go deeper into the macro.
189             return;
190         }
191         intravisit::walk_expr(self, expr);
192     }
193 }