rustdoc/html/render/
span_map.rs

1use std::path::{Path, PathBuf};
2
3use rustc_data_structures::fx::{FxHashMap, FxIndexMap};
4use rustc_hir::def::{DefKind, Res};
5use rustc_hir::def_id::{DefId, LOCAL_CRATE, LocalDefId};
6use rustc_hir::intravisit::{self, Visitor, VisitorExt};
7use rustc_hir::{ExprKind, HirId, Item, ItemKind, Mod, Node, QPath};
8use rustc_middle::hir::nested_filter;
9use rustc_middle::ty::TyCtxt;
10use rustc_span::hygiene::MacroKind;
11use rustc_span::{BytePos, ExpnKind, Span};
12
13use crate::clean::{self, PrimitiveType, rustc_span};
14use crate::html::sources;
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)]
24pub(crate) enum LinkFromSrc {
25    Local(clean::Span),
26    External(DefId),
27    Primitive(PrimitiveType),
28    Doc(DefId),
29}
30
31/// This function will do at most two things:
32///
33/// 1. Generate a `span` correspondence map which links an item `span` to its definition `span`.
34/// 2. Collect the source code files.
35///
36/// It returns the source code files and the `span` correspondence map.
37///
38/// Note about the `span` correspondence map: the keys are actually `(lo, hi)` of `span`s. We don't
39/// need the `span` context later on, only their position, so instead of keeping a whole `Span`, we
40/// only keep the `lo` and `hi`.
41pub(crate) fn collect_spans_and_sources(
42    tcx: TyCtxt<'_>,
43    krate: &clean::Crate,
44    src_root: &Path,
45    include_sources: bool,
46    generate_link_to_definition: bool,
47) -> (FxIndexMap<PathBuf, String>, FxHashMap<Span, LinkFromSrc>) {
48    if include_sources {
49        let mut visitor = SpanMapVisitor { tcx, matches: FxHashMap::default() };
50
51        if generate_link_to_definition {
52            tcx.hir_walk_toplevel_module(&mut visitor);
53        }
54        let sources = sources::collect_local_sources(tcx, src_root, krate);
55        (sources, visitor.matches)
56    } else {
57        (Default::default(), Default::default())
58    }
59}
60
61struct SpanMapVisitor<'tcx> {
62    pub(crate) tcx: TyCtxt<'tcx>,
63    pub(crate) matches: FxHashMap<Span, LinkFromSrc>,
64}
65
66impl SpanMapVisitor<'_> {
67    /// This function is where we handle `hir::Path` elements and add them into the "span map".
68    fn handle_path(&mut self, path: &rustc_hir::Path<'_>, only_use_last_segment: bool) {
69        match path.res {
70            // FIXME: For now, we handle `DefKind` if it's not a `DefKind::TyParam`.
71            // Would be nice to support them too alongside the other `DefKind`
72            // (such as primitive types!).
73            Res::Def(kind, def_id) if kind != DefKind::TyParam => {
74                let link = if def_id.as_local().is_some() {
75                    LinkFromSrc::Local(rustc_span(def_id, self.tcx))
76                } else {
77                    LinkFromSrc::External(def_id)
78                };
79                // In case the path ends with generics, we remove them from the span.
80                let span = if only_use_last_segment
81                    && let Some(path_span) = path.segments.last().map(|segment| segment.ident.span)
82                {
83                    path_span
84                } else {
85                    path.segments
86                        .last()
87                        .map(|last| {
88                            // In `use` statements, the included item is not in the path segments.
89                            // However, it doesn't matter because you can't have generics on `use`
90                            // statements.
91                            if path.span.contains(last.ident.span) {
92                                path.span.with_hi(last.ident.span.hi())
93                            } else {
94                                path.span
95                            }
96                        })
97                        .unwrap_or(path.span)
98                };
99                self.matches.insert(span, link);
100            }
101            Res::Local(_) if let Some(span) = self.tcx.hir_res_span(path.res) => {
102                let path_span = if only_use_last_segment
103                    && let Some(path_span) = path.segments.last().map(|segment| segment.ident.span)
104                {
105                    path_span
106                } else {
107                    path.span
108                };
109                self.matches.insert(path_span, LinkFromSrc::Local(clean::Span::new(span)));
110            }
111            Res::PrimTy(p) => {
112                // FIXME: Doesn't handle "path-like" primitives like arrays or tuples.
113                self.matches.insert(path.span, LinkFromSrc::Primitive(PrimitiveType::from(p)));
114            }
115            Res::Err => {}
116            _ => {}
117        }
118    }
119
120    /// Used to generate links on items' definition to go to their documentation page.
121    pub(crate) fn extract_info_from_hir_id(&mut self, hir_id: HirId) {
122        if let Node::Item(item) = self.tcx.hir_node(hir_id)
123            && let Some(span) = self.tcx.def_ident_span(item.owner_id)
124        {
125            let cspan = clean::Span::new(span);
126            // If the span isn't from the current crate, we ignore it.
127            if cspan.inner().is_dummy() || cspan.cnum(self.tcx.sess) != LOCAL_CRATE {
128                return;
129            }
130            self.matches.insert(span, LinkFromSrc::Doc(item.owner_id.to_def_id()));
131        }
132    }
133
134    /// Adds the macro call into the span map. Returns `true` if the `span` was inside a macro
135    /// expansion, whether or not it was added to the span map.
136    ///
137    /// The idea for the macro support is to check if the current `Span` comes from expansion. If
138    /// so, we loop until we find the macro definition by using `outer_expn_data` in a loop.
139    /// Finally, we get the information about the macro itself (`span` if "local", `DefId`
140    /// otherwise) and store it inside the span map.
141    fn handle_macro(&mut self, span: Span) -> bool {
142        if !span.from_expansion() {
143            return false;
144        }
145        // So if the `span` comes from a macro expansion, we need to get the original
146        // macro's `DefId`.
147        let mut data = span.ctxt().outer_expn_data();
148        let mut call_site = data.call_site;
149        // Macros can expand to code containing macros, which will in turn be expanded, etc.
150        // So the idea here is to "go up" until we're back to code that was generated from
151        // macro expansion so that we can get the `DefId` of the original macro that was at the
152        // origin of this expansion.
153        while call_site.from_expansion() {
154            data = call_site.ctxt().outer_expn_data();
155            call_site = data.call_site;
156        }
157
158        let macro_name = match data.kind {
159            ExpnKind::Macro(MacroKind::Bang, macro_name) => macro_name,
160            // Even though we don't handle this kind of macro, this `data` still comes from
161            // expansion so we return `true` so we don't go any deeper in this code.
162            _ => return true,
163        };
164        let link_from_src = match data.macro_def_id {
165            Some(macro_def_id) => {
166                if macro_def_id.is_local() {
167                    LinkFromSrc::Local(clean::Span::new(data.def_site))
168                } else {
169                    LinkFromSrc::External(macro_def_id)
170                }
171            }
172            None => return true,
173        };
174        let new_span = data.call_site;
175        let macro_name = macro_name.as_str();
176        // The "call_site" includes the whole macro with its "arguments". We only want
177        // the macro name.
178        let new_span = new_span.with_hi(new_span.lo() + BytePos(macro_name.len() as u32));
179        self.matches.insert(new_span, link_from_src);
180        true
181    }
182
183    fn infer_id(&mut self, hir_id: HirId, expr_hir_id: Option<HirId>, span: Span) {
184        let tcx = self.tcx;
185        let body_id = tcx.hir_enclosing_body_owner(hir_id);
186        // FIXME: this is showing error messages for parts of the code that are not
187        // compiled (because of cfg)!
188        //
189        // See discussion in https://github.com/rust-lang/rust/issues/69426#issuecomment-1019412352
190        let typeck_results = tcx.typeck_body(tcx.hir_body_owned_by(body_id).id());
191        // Interestingly enough, for method calls, we need the whole expression whereas for static
192        // method/function calls, we need the call expression specifically.
193        if let Some(def_id) = typeck_results.type_dependent_def_id(expr_hir_id.unwrap_or(hir_id)) {
194            let link = if def_id.as_local().is_some() {
195                LinkFromSrc::Local(rustc_span(def_id, tcx))
196            } else {
197                LinkFromSrc::External(def_id)
198            };
199            self.matches.insert(span, link);
200        }
201    }
202}
203
204// This is a reimplementation of `hir_enclosing_body_owner` which allows to fail without
205// panicking.
206fn hir_enclosing_body_owner(tcx: TyCtxt<'_>, hir_id: HirId) -> Option<LocalDefId> {
207    for (_, node) in tcx.hir_parent_iter(hir_id) {
208        // FIXME: associated type impl items don't have an associated body, so we don't handle
209        // them currently.
210        if let Node::ImplItem(impl_item) = node
211            && matches!(impl_item.kind, rustc_hir::ImplItemKind::Type(_))
212        {
213            return None;
214        } else if let Some((def_id, _)) = node.associated_body() {
215            return Some(def_id);
216        }
217    }
218    None
219}
220
221impl<'tcx> Visitor<'tcx> for SpanMapVisitor<'tcx> {
222    type NestedFilter = nested_filter::All;
223
224    fn maybe_tcx(&mut self) -> Self::MaybeTyCtxt {
225        self.tcx
226    }
227
228    fn visit_path(&mut self, path: &rustc_hir::Path<'tcx>, _id: HirId) {
229        if self.handle_macro(path.span) {
230            return;
231        }
232        self.handle_path(path, false);
233        intravisit::walk_path(self, path);
234    }
235
236    fn visit_qpath(&mut self, qpath: &QPath<'tcx>, id: HirId, _span: Span) {
237        match *qpath {
238            QPath::TypeRelative(qself, path) => {
239                if matches!(path.res, Res::Err) {
240                    let tcx = self.tcx;
241                    if let Some(body_id) = hir_enclosing_body_owner(tcx, id) {
242                        let typeck_results = tcx.typeck_body(tcx.hir_body_owned_by(body_id).id());
243                        let path = rustc_hir::Path {
244                            // We change the span to not include parens.
245                            span: path.ident.span,
246                            res: typeck_results.qpath_res(qpath, id),
247                            segments: &[],
248                        };
249                        self.handle_path(&path, false);
250                    }
251                } else {
252                    self.infer_id(path.hir_id, Some(id), path.ident.span);
253                }
254
255                rustc_ast::visit::try_visit!(self.visit_ty_unambig(qself));
256                self.visit_path_segment(path);
257            }
258            QPath::Resolved(maybe_qself, path) => {
259                self.handle_path(path, true);
260
261                rustc_ast::visit::visit_opt!(self, visit_ty_unambig, maybe_qself);
262                if !self.handle_macro(path.span) {
263                    intravisit::walk_path(self, path);
264                }
265            }
266            _ => {}
267        }
268    }
269
270    fn visit_mod(&mut self, m: &'tcx Mod<'tcx>, span: Span, id: HirId) {
271        // To make the difference between "mod foo {}" and "mod foo;". In case we "import" another
272        // file, we want to link to it. Otherwise no need to create a link.
273        if !span.overlaps(m.spans.inner_span) {
274            // Now that we confirmed it's a file import, we want to get the span for the module
275            // name only and not all the "mod foo;".
276            if let Node::Item(item) = self.tcx.hir_node(id) {
277                let (ident, _) = item.expect_mod();
278                self.matches
279                    .insert(ident.span, LinkFromSrc::Local(clean::Span::new(m.spans.inner_span)));
280            }
281        } else {
282            // If it's a "mod foo {}", we want to look to its documentation page.
283            self.extract_info_from_hir_id(id);
284        }
285        intravisit::walk_mod(self, m);
286    }
287
288    fn visit_expr(&mut self, expr: &'tcx rustc_hir::Expr<'tcx>) {
289        match expr.kind {
290            ExprKind::MethodCall(segment, ..) => {
291                self.infer_id(segment.hir_id, Some(expr.hir_id), segment.ident.span)
292            }
293            ExprKind::Call(call, ..) => self.infer_id(call.hir_id, None, call.span),
294            _ => {
295                if self.handle_macro(expr.span) {
296                    // We don't want to go deeper into the macro.
297                    return;
298                }
299            }
300        }
301        intravisit::walk_expr(self, expr);
302    }
303
304    fn visit_item(&mut self, item: &'tcx Item<'tcx>) {
305        match item.kind {
306            ItemKind::Static(..)
307            | ItemKind::Const(..)
308            | ItemKind::Fn { .. }
309            | ItemKind::Macro(..)
310            | ItemKind::TyAlias(..)
311            | ItemKind::Enum(..)
312            | ItemKind::Struct(..)
313            | ItemKind::Union(..)
314            | ItemKind::Trait(..)
315            | ItemKind::TraitAlias(..) => self.extract_info_from_hir_id(item.hir_id()),
316            ItemKind::Impl(_)
317            | ItemKind::Use(..)
318            | ItemKind::ExternCrate(..)
319            | ItemKind::ForeignMod { .. }
320            | ItemKind::GlobalAsm { .. }
321            // We already have "visit_mod" above so no need to check it here.
322            | ItemKind::Mod(..) => {}
323        }
324        intravisit::walk_item(self, item);
325    }
326}