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};
6use rustc_hir::intravisit::{self, Visitor};
7use rustc_hir::{
8 ExprKind, HirId, Item, ItemKind, Mod, Node, Pat, PatExpr, PatExprKind, PatKind, QPath,
9};
10use rustc_middle::hir::nested_filter;
11use rustc_middle::ty::TyCtxt;
12use rustc_span::hygiene::MacroKind;
13use rustc_span::{BytePos, ExpnKind, Span};
14
15use crate::clean::{self, PrimitiveType, rustc_span};
16use crate::html::sources;
17
18#[derive(Debug)]
26pub(crate) enum LinkFromSrc {
27 Local(clean::Span),
28 External(DefId),
29 Primitive(PrimitiveType),
30 Doc(DefId),
31}
32
33pub(crate) fn collect_spans_and_sources(
44 tcx: TyCtxt<'_>,
45 krate: &clean::Crate,
46 src_root: &Path,
47 include_sources: bool,
48 generate_link_to_definition: bool,
49) -> (FxIndexMap<PathBuf, String>, FxHashMap<Span, LinkFromSrc>) {
50 if include_sources {
51 let mut visitor = SpanMapVisitor { tcx, matches: FxHashMap::default() };
52
53 if generate_link_to_definition {
54 tcx.hir().walk_toplevel_module(&mut visitor);
55 }
56 let sources = sources::collect_local_sources(tcx, src_root, krate);
57 (sources, visitor.matches)
58 } else {
59 (Default::default(), Default::default())
60 }
61}
62
63struct SpanMapVisitor<'tcx> {
64 pub(crate) tcx: TyCtxt<'tcx>,
65 pub(crate) matches: FxHashMap<Span, LinkFromSrc>,
66}
67
68impl SpanMapVisitor<'_> {
69 fn handle_path(&mut self, path: &rustc_hir::Path<'_>) {
71 match path.res {
72 Res::Def(kind, def_id) if kind != DefKind::TyParam => {
76 let link = if def_id.as_local().is_some() {
77 LinkFromSrc::Local(rustc_span(def_id, self.tcx))
78 } else {
79 LinkFromSrc::External(def_id)
80 };
81 let span = path
83 .segments
84 .last()
85 .map(|last| {
86 if path.span.contains(last.ident.span) {
90 path.span.with_hi(last.ident.span.hi())
91 } else {
92 path.span
93 }
94 })
95 .unwrap_or(path.span);
96 self.matches.insert(span, link);
97 }
98 Res::Local(_) => {
99 if let Some(span) = self.tcx.hir().res_span(path.res) {
100 self.matches.insert(path.span, LinkFromSrc::Local(clean::Span::new(span)));
101 }
102 }
103 Res::PrimTy(p) => {
104 self.matches.insert(path.span, LinkFromSrc::Primitive(PrimitiveType::from(p)));
106 }
107 Res::Err => {}
108 _ => {}
109 }
110 }
111
112 pub(crate) fn extract_info_from_hir_id(&mut self, hir_id: HirId) {
114 if let Node::Item(item) = self.tcx.hir_node(hir_id) {
115 if let Some(span) = self.tcx.def_ident_span(item.owner_id) {
116 let cspan = clean::Span::new(span);
117 if cspan.inner().is_dummy() || cspan.cnum(self.tcx.sess) != LOCAL_CRATE {
119 return;
120 }
121 self.matches.insert(span, LinkFromSrc::Doc(item.owner_id.to_def_id()));
122 }
123 }
124 }
125
126 fn handle_macro(&mut self, span: Span) -> bool {
134 if !span.from_expansion() {
135 return false;
136 }
137 let mut data = span.ctxt().outer_expn_data();
140 let mut call_site = data.call_site;
141 while call_site.from_expansion() {
146 data = call_site.ctxt().outer_expn_data();
147 call_site = data.call_site;
148 }
149
150 let macro_name = match data.kind {
151 ExpnKind::Macro(MacroKind::Bang, macro_name) => macro_name,
152 _ => return true,
155 };
156 let link_from_src = match data.macro_def_id {
157 Some(macro_def_id) => {
158 if macro_def_id.is_local() {
159 LinkFromSrc::Local(clean::Span::new(data.def_site))
160 } else {
161 LinkFromSrc::External(macro_def_id)
162 }
163 }
164 None => return true,
165 };
166 let new_span = data.call_site;
167 let macro_name = macro_name.as_str();
168 let new_span = new_span.with_hi(new_span.lo() + BytePos(macro_name.len() as u32));
171 self.matches.insert(new_span, link_from_src);
172 true
173 }
174
175 fn infer_id(&mut self, hir_id: HirId, expr_hir_id: Option<HirId>, span: Span) {
176 let hir = self.tcx.hir();
177 let body_id = hir.enclosing_body_owner(hir_id);
178 let typeck_results = self.tcx.typeck_body(hir.body_owned_by(body_id).id());
183 if let Some(def_id) = typeck_results.type_dependent_def_id(expr_hir_id.unwrap_or(hir_id)) {
186 let link = if def_id.as_local().is_some() {
187 LinkFromSrc::Local(rustc_span(def_id, self.tcx))
188 } else {
189 LinkFromSrc::External(def_id)
190 };
191 self.matches.insert(span, link);
192 }
193 }
194
195 fn handle_pat(&mut self, p: &Pat<'_>) {
196 let mut check_qpath = |qpath, hir_id| match qpath {
197 QPath::TypeRelative(_, path) if matches!(path.res, Res::Err) => {
198 self.infer_id(path.hir_id, Some(hir_id), qpath.span());
199 }
200 QPath::Resolved(_, path) => self.handle_path(path),
201 _ => {}
202 };
203 match p.kind {
204 PatKind::Binding(_, _, _, Some(p)) => self.handle_pat(p),
205 PatKind::Struct(qpath, _, _) | PatKind::TupleStruct(qpath, _, _) => {
206 check_qpath(qpath, p.hir_id)
207 }
208 PatKind::Expr(PatExpr { kind: PatExprKind::Path(qpath), hir_id, .. }) => {
209 check_qpath(*qpath, *hir_id)
210 }
211 PatKind::Or(pats) => {
212 for pat in pats {
213 self.handle_pat(pat);
214 }
215 }
216 _ => {}
217 }
218 }
219}
220
221impl<'tcx> Visitor<'tcx> for SpanMapVisitor<'tcx> {
222 type NestedFilter = nested_filter::All;
223
224 fn nested_visit_map(&mut self) -> Self::Map {
225 self.tcx.hir()
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);
233 intravisit::walk_path(self, path);
234 }
235
236 fn visit_pat(&mut self, p: &Pat<'tcx>) {
237 self.handle_pat(p);
238 }
239
240 fn visit_mod(&mut self, m: &'tcx Mod<'tcx>, span: Span, id: HirId) {
241 if !span.overlaps(m.spans.inner_span) {
244 if let Node::Item(item) = self.tcx.hir_node(id) {
247 self.matches.insert(
248 item.ident.span,
249 LinkFromSrc::Local(clean::Span::new(m.spans.inner_span)),
250 );
251 }
252 } else {
253 self.extract_info_from_hir_id(id);
255 }
256 intravisit::walk_mod(self, m, id);
257 }
258
259 fn visit_expr(&mut self, expr: &'tcx rustc_hir::Expr<'tcx>) {
260 match expr.kind {
261 ExprKind::MethodCall(segment, ..) => {
262 self.infer_id(segment.hir_id, Some(expr.hir_id), segment.ident.span)
263 }
264 ExprKind::Call(call, ..) => self.infer_id(call.hir_id, None, call.span),
265 _ => {
266 if self.handle_macro(expr.span) {
267 return;
269 }
270 }
271 }
272 intravisit::walk_expr(self, expr);
273 }
274
275 fn visit_item(&mut self, item: &'tcx Item<'tcx>) {
276 match item.kind {
277 ItemKind::Static(_, _, _)
278 | ItemKind::Const(_, _, _)
279 | ItemKind::Fn { .. }
280 | ItemKind::Macro(_, _)
281 | ItemKind::TyAlias(_, _)
282 | ItemKind::Enum(_, _)
283 | ItemKind::Struct(_, _)
284 | ItemKind::Union(_, _)
285 | ItemKind::Trait(_, _, _, _, _)
286 | ItemKind::TraitAlias(_, _) => self.extract_info_from_hir_id(item.hir_id()),
287 ItemKind::Impl(_)
288 | ItemKind::Use(_, _)
289 | ItemKind::ExternCrate(_)
290 | ItemKind::ForeignMod { .. }
291 | ItemKind::GlobalAsm(_)
292 | ItemKind::Mod(_) => {}
294 }
295 intravisit::walk_item(self, item);
296 }
297}