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::{BytePos, ExpnKind};
11
12use crate::clean::{self, PrimitiveType, rustc_span};
13use crate::html::sources;
14
15#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
21pub(crate) struct Span {
22 lo: BytePos,
23 hi: BytePos,
24}
25
26impl From<rustc_span::Span> for Span {
27 fn from(value: rustc_span::Span) -> Self {
28 Self { lo: value.lo(), hi: value.hi() }
29 }
30}
31
32impl Span {
33 pub(crate) fn lo(self) -> BytePos {
34 self.lo
35 }
36
37 pub(crate) fn hi(self) -> BytePos {
38 self.hi
39 }
40
41 pub(crate) fn with_lo(self, lo: BytePos) -> Self {
42 Self { lo, hi: self.hi() }
43 }
44
45 pub(crate) fn with_hi(self, hi: BytePos) -> Self {
46 Self { lo: self.lo(), hi }
47 }
48}
49
50pub(crate) const DUMMY_SP: Span = Span { lo: BytePos(0), hi: BytePos(0) };
51
52#[derive(Debug)]
60pub(crate) enum LinkFromSrc {
61 Local(clean::Span),
62 External(DefId),
63 Primitive(PrimitiveType),
64 Doc(DefId),
65}
66
67pub(crate) fn collect_spans_and_sources(
78 tcx: TyCtxt<'_>,
79 krate: &clean::Crate,
80 src_root: &Path,
81 include_sources: bool,
82 generate_link_to_definition: bool,
83) -> (FxIndexMap<PathBuf, String>, FxHashMap<Span, LinkFromSrc>) {
84 if include_sources {
85 let mut visitor = SpanMapVisitor { tcx, matches: FxHashMap::default() };
86
87 if generate_link_to_definition {
88 tcx.hir_walk_toplevel_module(&mut visitor);
89 }
90 let sources = sources::collect_local_sources(tcx, src_root, krate);
91 (sources, visitor.matches)
92 } else {
93 (Default::default(), Default::default())
94 }
95}
96
97struct SpanMapVisitor<'tcx> {
98 pub(crate) tcx: TyCtxt<'tcx>,
99 pub(crate) matches: FxHashMap<Span, LinkFromSrc>,
100}
101
102impl SpanMapVisitor<'_> {
103 fn handle_path(&mut self, path: &rustc_hir::Path<'_>, only_use_last_segment: bool) {
105 match path.res {
106 Res::Def(kind, def_id) if kind != DefKind::TyParam => {
110 let link = if def_id.as_local().is_some() {
111 LinkFromSrc::Local(rustc_span(def_id, self.tcx))
112 } else {
113 LinkFromSrc::External(def_id)
114 };
115 let span = if only_use_last_segment
117 && let Some(path_span) = path.segments.last().map(|segment| segment.ident.span)
118 {
119 path_span
120 } else {
121 path.segments
122 .last()
123 .map(|last| {
124 if path.span.contains(last.ident.span) {
128 path.span.with_hi(last.ident.span.hi())
129 } else {
130 path.span
131 }
132 })
133 .unwrap_or(path.span)
134 };
135 self.matches.insert(span.into(), link);
136 }
137 Res::Local(_) if let Some(span) = self.tcx.hir_res_span(path.res) => {
138 let path_span = if only_use_last_segment
139 && let Some(path_span) = path.segments.last().map(|segment| segment.ident.span)
140 {
141 path_span
142 } else {
143 path.span
144 };
145 self.matches.insert(path_span.into(), LinkFromSrc::Local(clean::Span::new(span)));
146 }
147 Res::PrimTy(p) => {
148 self.matches
150 .insert(path.span.into(), LinkFromSrc::Primitive(PrimitiveType::from(p)));
151 }
152 Res::Err => {}
153 _ => {}
154 }
155 }
156
157 pub(crate) fn extract_info_from_hir_id(&mut self, hir_id: HirId) {
159 if let Node::Item(item) = self.tcx.hir_node(hir_id)
160 && let Some(span) = self.tcx.def_ident_span(item.owner_id)
161 {
162 let cspan = clean::Span::new(span);
163 if cspan.inner().is_dummy() || cspan.cnum(self.tcx.sess) != LOCAL_CRATE {
165 return;
166 }
167 self.matches.insert(span.into(), LinkFromSrc::Doc(item.owner_id.to_def_id()));
168 }
169 }
170
171 fn handle_macro(&mut self, span: rustc_span::Span) -> bool {
179 if !span.from_expansion() {
180 return false;
181 }
182 let mut data = span.ctxt().outer_expn_data();
185 let mut call_site = data.call_site;
186 while call_site.from_expansion() {
191 data = call_site.ctxt().outer_expn_data();
192 call_site = data.call_site;
193 }
194
195 let macro_name = match data.kind {
196 ExpnKind::Macro(_, macro_name) => macro_name,
197 _ => return true,
200 };
201 let link_from_src = match data.macro_def_id {
202 Some(macro_def_id) => {
203 if macro_def_id.is_local() {
204 LinkFromSrc::Local(clean::Span::new(data.def_site))
205 } else {
206 LinkFromSrc::External(macro_def_id)
207 }
208 }
209 None => return true,
210 };
211 let new_span = data.call_site;
212 let macro_name = macro_name.as_str();
213 let new_span = new_span.with_hi(new_span.lo() + BytePos(macro_name.len() as u32));
216 self.matches.insert(new_span.into(), link_from_src);
217 true
218 }
219
220 fn infer_id(&mut self, hir_id: HirId, expr_hir_id: Option<HirId>, span: Span) {
221 let tcx = self.tcx;
222 let body_id = tcx.hir_enclosing_body_owner(hir_id);
223 let typeck_results = tcx.typeck_body(tcx.hir_body_owned_by(body_id).id());
228 if let Some(def_id) = typeck_results.type_dependent_def_id(expr_hir_id.unwrap_or(hir_id)) {
231 let link = if def_id.as_local().is_some() {
232 LinkFromSrc::Local(rustc_span(def_id, tcx))
233 } else {
234 LinkFromSrc::External(def_id)
235 };
236 self.matches.insert(span, link);
237 }
238 }
239}
240
241fn hir_enclosing_body_owner(tcx: TyCtxt<'_>, hir_id: HirId) -> Option<LocalDefId> {
244 for (_, node) in tcx.hir_parent_iter(hir_id) {
245 if let Node::ImplItem(impl_item) = node
248 && matches!(impl_item.kind, rustc_hir::ImplItemKind::Type(_))
249 {
250 return None;
251 } else if let Some((def_id, _)) = node.associated_body() {
252 return Some(def_id);
253 }
254 }
255 None
256}
257
258impl<'tcx> Visitor<'tcx> for SpanMapVisitor<'tcx> {
259 type NestedFilter = nested_filter::All;
260
261 fn maybe_tcx(&mut self) -> Self::MaybeTyCtxt {
262 self.tcx
263 }
264
265 fn visit_path(&mut self, path: &rustc_hir::Path<'tcx>, _id: HirId) {
266 if self.handle_macro(path.span) {
267 return;
268 }
269 self.handle_path(path, false);
270 intravisit::walk_path(self, path);
271 }
272
273 fn visit_qpath(&mut self, qpath: &QPath<'tcx>, id: HirId, _span: rustc_span::Span) {
274 match *qpath {
275 QPath::TypeRelative(qself, path) => {
276 if matches!(path.res, Res::Err) {
277 let tcx = self.tcx;
278 if let Some(body_id) = hir_enclosing_body_owner(tcx, id) {
279 let typeck_results = tcx.typeck_body(tcx.hir_body_owned_by(body_id).id());
280 let path = rustc_hir::Path {
281 span: path.ident.span,
283 res: typeck_results.qpath_res(qpath, id),
284 segments: &[],
285 };
286 self.handle_path(&path, false);
287 }
288 } else {
289 self.infer_id(path.hir_id, Some(id), path.ident.span.into());
290 }
291
292 rustc_ast::visit::try_visit!(self.visit_ty_unambig(qself));
293 self.visit_path_segment(path);
294 }
295 QPath::Resolved(maybe_qself, path) => {
296 self.handle_path(path, true);
297
298 rustc_ast::visit::visit_opt!(self, visit_ty_unambig, maybe_qself);
299 if !self.handle_macro(path.span) {
300 intravisit::walk_path(self, path);
301 }
302 }
303 }
304 }
305
306 fn visit_mod(&mut self, m: &'tcx Mod<'tcx>, span: rustc_span::Span, id: HirId) {
307 if !span.overlaps(m.spans.inner_span) {
310 if let Node::Item(item) = self.tcx.hir_node(id) {
313 let (ident, _) = item.expect_mod();
314 self.matches.insert(
315 ident.span.into(),
316 LinkFromSrc::Local(clean::Span::new(m.spans.inner_span)),
317 );
318 }
319 } else {
320 self.extract_info_from_hir_id(id);
322 }
323 intravisit::walk_mod(self, m);
324 }
325
326 fn visit_expr(&mut self, expr: &'tcx rustc_hir::Expr<'tcx>) {
327 match expr.kind {
328 ExprKind::MethodCall(segment, ..) => {
329 self.infer_id(segment.hir_id, Some(expr.hir_id), segment.ident.span.into())
330 }
331 ExprKind::Call(call, ..) => self.infer_id(call.hir_id, None, call.span.into()),
332 _ => {
333 if self.handle_macro(expr.span) {
334 return;
336 }
337 }
338 }
339 intravisit::walk_expr(self, expr);
340 }
341
342 fn visit_item(&mut self, item: &'tcx Item<'tcx>) {
343 match item.kind {
344 ItemKind::Static(..)
345 | ItemKind::Const(..)
346 | ItemKind::Fn { .. }
347 | ItemKind::Macro(..)
348 | ItemKind::TyAlias(..)
349 | ItemKind::Enum(..)
350 | ItemKind::Struct(..)
351 | ItemKind::Union(..)
352 | ItemKind::Trait(..)
353 | ItemKind::TraitAlias(..) => self.extract_info_from_hir_id(item.hir_id()),
354 ItemKind::Impl(_)
355 | ItemKind::Use(..)
356 | ItemKind::ExternCrate(..)
357 | ItemKind::ForeignMod { .. }
358 | ItemKind::GlobalAsm { .. }
359 | ItemKind::Mod(..) => {}
361 }
362 intravisit::walk_item(self, item);
363 }
364}