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#[derive(Debug)]
24pub(crate) enum LinkFromSrc {
25 Local(clean::Span),
26 External(DefId),
27 Primitive(PrimitiveType),
28 Doc(DefId),
29}
30
31pub(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 fn handle_path(&mut self, path: &rustc_hir::Path<'_>, only_use_last_segment: bool) {
69 match path.res {
70 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 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 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 self.matches.insert(path.span, LinkFromSrc::Primitive(PrimitiveType::from(p)));
114 }
115 Res::Err => {}
116 _ => {}
117 }
118 }
119
120 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 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 fn handle_macro(&mut self, span: Span) -> bool {
142 if !span.from_expansion() {
143 return false;
144 }
145 let mut data = span.ctxt().outer_expn_data();
148 let mut call_site = data.call_site;
149 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 _ => 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 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 let typeck_results = tcx.typeck_body(tcx.hir_body_owned_by(body_id).id());
191 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
204fn hir_enclosing_body_owner(tcx: TyCtxt<'_>, hir_id: HirId) -> Option<LocalDefId> {
207 for (_, node) in tcx.hir_parent_iter(hir_id) {
208 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 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 if !span.overlaps(m.spans.inner_span) {
274 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 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 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 | ItemKind::Mod(..) => {}
323 }
324 intravisit::walk_item(self, item);
325 }
326}