]> git.proxmox.com Git - rustc.git/blame - src/librustdoc/html/render/span_map.rs
New upstream version 1.76.0+dfsg1
[rustc.git] / src / librustdoc / html / render / span_map.rs
CommitLineData
add651ee 1use crate::clean::{self, rustc_span, PrimitiveType};
94222f64
XL
2use crate::html::sources;
3
4use rustc_data_structures::fx::FxHashMap;
5use rustc_hir::def::{DefKind, Res};
add651ee 6use rustc_hir::def_id::{DefId, LOCAL_CRATE};
5099ac24 7use rustc_hir::intravisit::{self, Visitor};
add651ee 8use rustc_hir::{ExprKind, HirId, Item, ItemKind, Mod, Node};
5099ac24 9use rustc_middle::hir::nested_filter;
94222f64 10use rustc_middle::ty::TyCtxt;
923072b8
FG
11use rustc_span::hygiene::MacroKind;
12use rustc_span::{BytePos, ExpnKind, Span};
94222f64
XL
13
14use 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)]
923072b8 24pub(crate) enum LinkFromSrc {
94222f64
XL
25 Local(clean::Span),
26 External(DefId),
c295e0f8 27 Primitive(PrimitiveType),
add651ee 28 Doc(DefId),
94222f64
XL
29}
30
31/// This function will do at most two things:
32///
353b0b11 33/// 1. Generate a `span` correspondence map which links an item `span` to its definition `span`.
94222f64
XL
34/// 2. Collect the source code files.
35///
353b0b11 36/// It returns the `krate`, the source code files and the `span` correspondence map.
94222f64 37///
353b0b11 38/// Note about the `span` correspondence map: the keys are actually `(lo, hi)` of `span`s. We don't
94222f64
XL
39/// need the `span` context later on, only their position, so instead of keep a whole `Span`, we
40/// only keep the `lo` and `hi`.
923072b8 41pub(crate) fn collect_spans_and_sources(
94222f64 42 tcx: TyCtxt<'_>,
3c0e092e 43 krate: &clean::Crate,
94222f64
XL
44 src_root: &Path,
45 include_sources: bool,
46 generate_link_to_definition: bool,
3c0e092e 47) -> (FxHashMap<PathBuf, String>, FxHashMap<Span, LinkFromSrc>) {
94222f64
XL
48 let mut visitor = SpanMapVisitor { tcx, matches: FxHashMap::default() };
49
50 if include_sources {
51 if generate_link_to_definition {
c295e0f8 52 tcx.hir().walk_toplevel_module(&mut visitor);
94222f64 53 }
923072b8 54 let sources = sources::collect_local_sources(tcx, src_root, krate);
3c0e092e 55 (sources, visitor.matches)
94222f64 56 } else {
3c0e092e 57 (Default::default(), Default::default())
94222f64
XL
58 }
59}
60
61struct SpanMapVisitor<'tcx> {
923072b8
FG
62 pub(crate) tcx: TyCtxt<'tcx>,
63 pub(crate) matches: FxHashMap<Span, LinkFromSrc>,
94222f64
XL
64}
65
66impl<'tcx> SpanMapVisitor<'tcx> {
67 /// This function is where we handle `hir::Path` elements and add them into the "span map".
923072b8 68 fn handle_path(&mut self, path: &rustc_hir::Path<'_>) {
add651ee 69 match path.res {
923072b8
FG
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`
94222f64 72 // (such as primitive types!).
add651ee
FG
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 self.matches.insert(path.span, link);
80 }
81 Res::Local(_) => {
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 }
85 }
c295e0f8
XL
86 Res::PrimTy(p) => {
87 // FIXME: Doesn't handle "path-like" primitives like arrays or tuples.
923072b8 88 self.matches.insert(path.span, LinkFromSrc::Primitive(PrimitiveType::from(p)));
c295e0f8 89 }
add651ee
FG
90 Res::Err => {}
91 _ => {}
92 }
93 }
94
95 /// Used to generate links on items' definition to go to their documentation page.
96 pub(crate) fn extract_info_from_hir_id(&mut self, hir_id: HirId) {
4b012472 97 if let Some(Node::Item(item)) = self.tcx.opt_hir_node(hir_id) {
add651ee
FG
98 if let Some(span) = self.tcx.def_ident_span(item.owner_id) {
99 let cspan = clean::Span::new(span);
100 // If the span isn't from the current crate, we ignore it.
101 if cspan.inner().is_dummy() || cspan.cnum(self.tcx.sess) != LOCAL_CRATE {
102 return;
103 }
104 self.matches.insert(span, LinkFromSrc::Doc(item.owner_id.to_def_id()));
105 }
923072b8
FG
106 }
107 }
108
109 /// Adds the macro call into the span map. Returns `true` if the `span` was inside a macro
110 /// expansion, whether or not it was added to the span map.
111 ///
112 /// The idea for the macro support is to check if the current `Span` comes from expansion. If
113 /// so, we loop until we find the macro definition by using `outer_expn_data` in a loop.
114 /// Finally, we get the information about the macro itself (`span` if "local", `DefId`
115 /// otherwise) and store it inside the span map.
116 fn handle_macro(&mut self, span: Span) -> bool {
117 if !span.from_expansion() {
118 return false;
119 }
120 // So if the `span` comes from a macro expansion, we need to get the original
121 // macro's `DefId`.
122 let mut data = span.ctxt().outer_expn_data();
123 let mut call_site = data.call_site;
124 // Macros can expand to code containing macros, which will in turn be expanded, etc.
125 // So the idea here is to "go up" until we're back to code that was generated from
126 // macro expansion so that we can get the `DefId` of the original macro that was at the
127 // origin of this expansion.
128 while call_site.from_expansion() {
129 data = call_site.ctxt().outer_expn_data();
130 call_site = data.call_site;
94222f64 131 }
923072b8
FG
132
133 let macro_name = match data.kind {
134 ExpnKind::Macro(MacroKind::Bang, macro_name) => macro_name,
135 // Even though we don't handle this kind of macro, this `data` still comes from
136 // expansion so we return `true` so we don't go any deeper in this code.
137 _ => return true,
138 };
139 let link_from_src = match data.macro_def_id {
add651ee
FG
140 Some(macro_def_id) => {
141 if macro_def_id.is_local() {
142 LinkFromSrc::Local(clean::Span::new(data.def_site))
143 } else {
144 LinkFromSrc::External(macro_def_id)
145 }
923072b8 146 }
923072b8
FG
147 None => return true,
148 };
149 let new_span = data.call_site;
150 let macro_name = macro_name.as_str();
151 // The "call_site" includes the whole macro with its "arguments". We only want
152 // the macro name.
153 let new_span = new_span.with_hi(new_span.lo() + BytePos(macro_name.len() as u32));
154 self.matches.insert(new_span, link_from_src);
155 true
94222f64
XL
156 }
157}
158
a2a8927a 159impl<'tcx> Visitor<'tcx> for SpanMapVisitor<'tcx> {
5099ac24 160 type NestedFilter = nested_filter::All;
94222f64 161
5099ac24
FG
162 fn nested_visit_map(&mut self) -> Self::Map {
163 self.tcx.hir()
94222f64
XL
164 }
165
487cf647 166 fn visit_path(&mut self, path: &rustc_hir::Path<'tcx>, _id: HirId) {
923072b8
FG
167 if self.handle_macro(path.span) {
168 return;
169 }
170 self.handle_path(path);
94222f64
XL
171 intravisit::walk_path(self, path);
172 }
173
174 fn visit_mod(&mut self, m: &'tcx Mod<'tcx>, span: Span, id: HirId) {
175 // To make the difference between "mod foo {}" and "mod foo;". In case we "import" another
176 // file, we want to link to it. Otherwise no need to create a link.
04454e1e 177 if !span.overlaps(m.spans.inner_span) {
94222f64
XL
178 // Now that we confirmed it's a file import, we want to get the span for the module
179 // name only and not all the "mod foo;".
4b012472 180 if let Some(Node::Item(item)) = self.tcx.opt_hir_node(id) {
04454e1e
FG
181 self.matches.insert(
182 item.ident.span,
183 LinkFromSrc::Local(clean::Span::new(m.spans.inner_span)),
184 );
94222f64 185 }
add651ee
FG
186 } else {
187 // If it's a "mod foo {}", we want to look to its documentation page.
188 self.extract_info_from_hir_id(id);
94222f64
XL
189 }
190 intravisit::walk_mod(self, m, id);
191 }
192
193 fn visit_expr(&mut self, expr: &'tcx rustc_hir::Expr<'tcx>) {
5099ac24 194 if let ExprKind::MethodCall(segment, ..) = expr.kind {
f2b60f7d
FG
195 let hir = self.tcx.hir();
196 let body_id = hir.enclosing_body_owner(segment.hir_id);
197 // FIXME: this is showing error messages for parts of the code that are not
198 // compiled (because of cfg)!
199 //
200 // See discussion in https://github.com/rust-lang/rust/issues/69426#issuecomment-1019412352
201 let typeck_results = self
202 .tcx
203 .typeck_body(hir.maybe_body_owned_by(body_id).expect("a body which isn't a body"));
204 if let Some(def_id) = typeck_results.type_dependent_def_id(expr.hir_id) {
add651ee
FG
205 let link = if def_id.as_local().is_some() {
206 LinkFromSrc::Local(rustc_span(def_id, self.tcx))
207 } else {
208 LinkFromSrc::External(def_id)
209 };
210 self.matches.insert(segment.ident.span, link);
94222f64 211 }
923072b8
FG
212 } else if self.handle_macro(expr.span) {
213 // We don't want to go deeper into the macro.
214 return;
94222f64
XL
215 }
216 intravisit::walk_expr(self, expr);
217 }
add651ee
FG
218
219 fn visit_item(&mut self, item: &'tcx Item<'tcx>) {
220 match item.kind {
221 ItemKind::Static(_, _, _)
222 | ItemKind::Const(_, _, _)
223 | ItemKind::Fn(_, _, _)
224 | ItemKind::Macro(_, _)
225 | ItemKind::TyAlias(_, _)
226 | ItemKind::Enum(_, _)
227 | ItemKind::Struct(_, _)
228 | ItemKind::Union(_, _)
229 | ItemKind::Trait(_, _, _, _, _)
230 | ItemKind::TraitAlias(_, _) => self.extract_info_from_hir_id(item.hir_id()),
231 ItemKind::Impl(_)
232 | ItemKind::Use(_, _)
233 | ItemKind::ExternCrate(_)
234 | ItemKind::ForeignMod { .. }
235 | ItemKind::GlobalAsm(_)
236 | ItemKind::OpaqueTy(_)
237 // We already have "visit_mod" above so no need to check it here.
238 | ItemKind::Mod(_) => {}
239 }
240 intravisit::walk_item(self, item);
241 }
94222f64 242}