[go: up one dir, main page]

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