]>
Commit | Line | Data |
---|---|---|
add651ee | 1 | use crate::clean::{self, rustc_span, PrimitiveType}; |
94222f64 XL |
2 | use crate::html::sources; |
3 | ||
4 | use rustc_data_structures::fx::FxHashMap; | |
5 | use rustc_hir::def::{DefKind, Res}; | |
add651ee | 6 | use rustc_hir::def_id::{DefId, LOCAL_CRATE}; |
5099ac24 | 7 | use rustc_hir::intravisit::{self, Visitor}; |
add651ee | 8 | use rustc_hir::{ExprKind, HirId, Item, ItemKind, Mod, Node}; |
5099ac24 | 9 | use rustc_middle::hir::nested_filter; |
94222f64 | 10 | use rustc_middle::ty::TyCtxt; |
923072b8 FG |
11 | use rustc_span::hygiene::MacroKind; |
12 | use rustc_span::{BytePos, ExpnKind, Span}; | |
94222f64 XL |
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)] | |
923072b8 | 24 | pub(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 | 41 | pub(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 | ||
61 | struct SpanMapVisitor<'tcx> { | |
923072b8 FG |
62 | pub(crate) tcx: TyCtxt<'tcx>, |
63 | pub(crate) matches: FxHashMap<Span, LinkFromSrc>, | |
94222f64 XL |
64 | } |
65 | ||
66 | impl<'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 | 159 | impl<'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 | } |