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