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