]>
Commit | Line | Data |
---|---|---|
532ac7d7 | 1 | use errors::Applicability; |
48663c56 | 2 | use rustc::hir::def::{Res, DefKind, Namespace::{self, *}, PerNS}; |
9fa01778 | 3 | use rustc::hir::def_id::DefId; |
532ac7d7 XL |
4 | use rustc::hir; |
5 | use rustc::lint as lint; | |
b7449926 | 6 | use rustc::ty; |
e1599b0c | 7 | use rustc_resolve::ParentScope; |
b7449926 | 8 | use syntax; |
532ac7d7 | 9 | use syntax::ast::{self, Ident}; |
416331ca | 10 | use syntax::ext::base::SyntaxExtensionKind; |
b7449926 XL |
11 | use syntax::feature_gate::UnstableFeatures; |
12 | use syntax::symbol::Symbol; | |
9fa01778 | 13 | use syntax_pos::DUMMY_SP; |
b7449926 XL |
14 | |
15 | use std::ops::Range; | |
16 | ||
9fa01778 XL |
17 | use crate::core::DocContext; |
18 | use crate::fold::DocFolder; | |
19 | use crate::html::markdown::markdown_links; | |
20 | use crate::clean::*; | |
21 | use crate::passes::{look_for_tests, Pass}; | |
0bf4aa26 | 22 | |
9fa01778 | 23 | use super::span_of_attrs; |
b7449926 | 24 | |
532ac7d7 XL |
25 | pub const COLLECT_INTRA_DOC_LINKS: Pass = Pass { |
26 | name: "collect-intra-doc-links", | |
27 | pass: collect_intra_doc_links, | |
28 | description: "reads a crate's documentation to resolve intra-doc-links", | |
29 | }; | |
b7449926 | 30 | |
532ac7d7 | 31 | pub fn collect_intra_doc_links(krate: Crate, cx: &DocContext<'_>) -> Crate { |
b7449926 XL |
32 | if !UnstableFeatures::from_environment().is_nightly_build() { |
33 | krate | |
34 | } else { | |
35 | let mut coll = LinkCollector::new(cx); | |
36 | ||
37 | coll.fold_crate(krate) | |
38 | } | |
39 | } | |
40 | ||
532ac7d7 XL |
41 | struct LinkCollector<'a, 'tcx> { |
42 | cx: &'a DocContext<'tcx>, | |
48663c56 | 43 | mod_ids: Vec<hir::HirId>, |
b7449926 XL |
44 | } |
45 | ||
532ac7d7 XL |
46 | impl<'a, 'tcx> LinkCollector<'a, 'tcx> { |
47 | fn new(cx: &'a DocContext<'tcx>) -> Self { | |
b7449926 XL |
48 | LinkCollector { |
49 | cx, | |
50 | mod_ids: Vec::new(), | |
51 | } | |
52 | } | |
53 | ||
532ac7d7 XL |
54 | /// Resolves a string as a path within a particular namespace. Also returns an optional |
55 | /// URL fragment in the case of variants and methods. | |
0bf4aa26 XL |
56 | fn resolve(&self, |
57 | path_str: &str, | |
532ac7d7 | 58 | ns: Namespace, |
0bf4aa26 | 59 | current_item: &Option<String>, |
48663c56 XL |
60 | parent_id: Option<hir::HirId>) |
61 | -> Result<(Res, Option<String>), ()> | |
b7449926 XL |
62 | { |
63 | let cx = self.cx; | |
64 | ||
416331ca XL |
65 | // In case we're in a module, try to resolve the relative path. |
66 | if let Some(module_id) = parent_id.or(self.mod_ids.last().cloned()) { | |
67 | let module_id = cx.tcx.hir().hir_to_node_id(module_id); | |
532ac7d7 | 68 | let result = cx.enter_resolver(|resolver| { |
416331ca | 69 | resolver.resolve_str_path_error(DUMMY_SP, &path_str, ns, module_id) |
b7449926 | 70 | }); |
416331ca XL |
71 | let result = match result { |
72 | Ok((_, Res::Err)) => Err(()), | |
73 | _ => result, | |
74 | }; | |
b7449926 | 75 | |
dc9dc135 XL |
76 | if let Ok((_, res)) = result { |
77 | let res = res.map_id(|_| panic!("unexpected node_id")); | |
b7449926 | 78 | // In case this is a trait item, skip the |
0731742a | 79 | // early return and try looking for the trait. |
dc9dc135 XL |
80 | let value = match res { |
81 | Res::Def(DefKind::Method, _) | Res::Def(DefKind::AssocConst, _) => true, | |
82 | Res::Def(DefKind::AssocTy, _) => false, | |
83 | Res::Def(DefKind::Variant, _) => return handle_variant(cx, res), | |
0731742a | 84 | // Not a trait item; just return what we found. |
416331ca | 85 | Res::PrimTy(..) => return Ok((res, Some(path_str.to_owned()))), |
dc9dc135 | 86 | _ => return Ok((res, None)) |
b7449926 XL |
87 | }; |
88 | ||
532ac7d7 | 89 | if value != (ns == ValueNS) { |
b7449926 XL |
90 | return Err(()) |
91 | } | |
532ac7d7 | 92 | } else if let Some(prim) = is_primitive(path_str, ns) { |
b7449926 XL |
93 | return Ok((prim, Some(path_str.to_owned()))) |
94 | } else { | |
95 | // If resolution failed, it may still be a method | |
96 | // because methods are not handled by the resolver | |
0731742a | 97 | // If so, bail when we're not looking for a value. |
532ac7d7 | 98 | if ns != ValueNS { |
b7449926 XL |
99 | return Err(()) |
100 | } | |
101 | } | |
102 | ||
0731742a | 103 | // Try looking for methods and associated items. |
b7449926 XL |
104 | let mut split = path_str.rsplitn(2, "::"); |
105 | let item_name = if let Some(first) = split.next() { | |
48663c56 | 106 | Symbol::intern(first) |
b7449926 XL |
107 | } else { |
108 | return Err(()) | |
109 | }; | |
110 | ||
111 | let mut path = if let Some(second) = split.next() { | |
112 | second.to_owned() | |
113 | } else { | |
114 | return Err(()) | |
115 | }; | |
116 | ||
117 | if path == "self" || path == "Self" { | |
118 | if let Some(name) = current_item.as_ref() { | |
119 | path = name.clone(); | |
120 | } | |
121 | } | |
532ac7d7 | 122 | if let Some(prim) = is_primitive(&path, TypeNS) { |
9fa01778 XL |
123 | let did = primitive_impl(cx, &path).ok_or(())?; |
124 | return cx.tcx.associated_items(did) | |
125 | .find(|item| item.ident.name == item_name) | |
126 | .and_then(|item| match item.kind { | |
dc9dc135 | 127 | ty::AssocKind::Method => Some("method"), |
9fa01778 XL |
128 | _ => None, |
129 | }) | |
130 | .map(|out| (prim, Some(format!("{}#{}.{}", path, out, item_name)))) | |
131 | .ok_or(()); | |
132 | } | |
b7449926 | 133 | |
416331ca XL |
134 | let (_, ty_res) = cx.enter_resolver(|resolver| { |
135 | resolver.resolve_str_path_error(DUMMY_SP, &path, TypeNS, module_id) | |
136 | })?; | |
137 | if let Res::Err = ty_res { | |
138 | return Err(()); | |
139 | } | |
dc9dc135 XL |
140 | let ty_res = ty_res.map_id(|_| panic!("unexpected node_id")); |
141 | match ty_res { | |
48663c56 XL |
142 | Res::Def(DefKind::Struct, did) |
143 | | Res::Def(DefKind::Union, did) | |
144 | | Res::Def(DefKind::Enum, did) | |
145 | | Res::Def(DefKind::TyAlias, did) => { | |
b7449926 XL |
146 | let item = cx.tcx.inherent_impls(did) |
147 | .iter() | |
148 | .flat_map(|imp| cx.tcx.associated_items(*imp)) | |
149 | .find(|item| item.ident.name == item_name); | |
150 | if let Some(item) = item { | |
151 | let out = match item.kind { | |
dc9dc135 XL |
152 | ty::AssocKind::Method if ns == ValueNS => "method", |
153 | ty::AssocKind::Const if ns == ValueNS => "associatedconstant", | |
b7449926 XL |
154 | _ => return Err(()) |
155 | }; | |
dc9dc135 | 156 | Ok((ty_res, Some(format!("{}.{}", out, item_name)))) |
b7449926 XL |
157 | } else { |
158 | match cx.tcx.type_of(did).sty { | |
159 | ty::Adt(def, _) => { | |
160 | if let Some(item) = if def.is_enum() { | |
161 | def.all_fields().find(|item| item.ident.name == item_name) | |
162 | } else { | |
163 | def.non_enum_variant() | |
164 | .fields | |
165 | .iter() | |
166 | .find(|item| item.ident.name == item_name) | |
167 | } { | |
dc9dc135 | 168 | Ok((ty_res, |
b7449926 XL |
169 | Some(format!("{}.{}", |
170 | if def.is_enum() { | |
171 | "variant" | |
172 | } else { | |
173 | "structfield" | |
174 | }, | |
175 | item.ident)))) | |
176 | } else { | |
177 | Err(()) | |
178 | } | |
179 | } | |
180 | _ => Err(()), | |
181 | } | |
182 | } | |
183 | } | |
48663c56 | 184 | Res::Def(DefKind::Trait, did) => { |
b7449926 XL |
185 | let item = cx.tcx.associated_item_def_ids(did).iter() |
186 | .map(|item| cx.tcx.associated_item(*item)) | |
187 | .find(|item| item.ident.name == item_name); | |
188 | if let Some(item) = item { | |
189 | let kind = match item.kind { | |
dc9dc135 XL |
190 | ty::AssocKind::Const if ns == ValueNS => "associatedconstant", |
191 | ty::AssocKind::Type if ns == TypeNS => "associatedtype", | |
192 | ty::AssocKind::Method if ns == ValueNS => { | |
b7449926 XL |
193 | if item.defaultness.has_value() { |
194 | "method" | |
195 | } else { | |
196 | "tymethod" | |
197 | } | |
198 | } | |
199 | _ => return Err(()) | |
200 | }; | |
201 | ||
dc9dc135 | 202 | Ok((ty_res, Some(format!("{}.{}", kind, item_name)))) |
b7449926 XL |
203 | } else { |
204 | Err(()) | |
205 | } | |
206 | } | |
207 | _ => Err(()) | |
208 | } | |
209 | } else { | |
48663c56 | 210 | debug!("attempting to resolve item without parent module: {}", path_str); |
b7449926 XL |
211 | Err(()) |
212 | } | |
213 | } | |
214 | } | |
215 | ||
532ac7d7 | 216 | impl<'a, 'tcx> DocFolder for LinkCollector<'a, 'tcx> { |
b7449926 | 217 | fn fold_item(&mut self, mut item: Item) -> Option<Item> { |
532ac7d7 XL |
218 | let item_hir_id = if item.is_mod() { |
219 | if let Some(id) = self.cx.tcx.hir().as_local_hir_id(item.def_id) { | |
b7449926 XL |
220 | Some(id) |
221 | } else { | |
222 | debug!("attempting to fold on a non-local item: {:?}", item); | |
223 | return self.fold_item_recur(item); | |
224 | } | |
225 | } else { | |
226 | None | |
227 | }; | |
228 | ||
0731742a | 229 | // FIXME: get the resolver to work with non-local resolve scopes. |
48663c56 | 230 | let parent_node = self.cx.as_local_hir_id(item.def_id).and_then(|hir_id| { |
0bf4aa26 | 231 | // FIXME: this fails hard for impls in non-module scope, but is necessary for the |
0731742a | 232 | // current `resolve()` implementation. |
48663c56 XL |
233 | match self.cx.tcx.hir().get_module_parent_node(hir_id) { |
234 | id if id != hir_id => Some(id), | |
0bf4aa26 XL |
235 | _ => None, |
236 | } | |
237 | }); | |
238 | ||
239 | if parent_node.is_some() { | |
240 | debug!("got parent node for {} {:?}, id {:?}", item.type_(), item.name, item.def_id); | |
241 | } | |
242 | ||
b7449926 XL |
243 | let current_item = match item.inner { |
244 | ModuleItem(..) => { | |
245 | if item.attrs.inner_docs { | |
532ac7d7 | 246 | if item_hir_id.unwrap() != hir::CRATE_HIR_ID { |
b7449926 XL |
247 | item.name.clone() |
248 | } else { | |
249 | None | |
250 | } | |
251 | } else { | |
0bf4aa26 | 252 | match parent_node.or(self.mod_ids.last().cloned()) { |
48663c56 | 253 | Some(parent) if parent != hir::CRATE_HIR_ID => { |
0731742a | 254 | // FIXME: can we pull the parent module's name from elsewhere? |
dc9dc135 | 255 | Some(self.cx.tcx.hir().name(parent).to_string()) |
b7449926 XL |
256 | } |
257 | _ => None, | |
258 | } | |
259 | } | |
260 | } | |
261 | ImplItem(Impl { ref for_, .. }) => { | |
262 | for_.def_id().map(|did| self.cx.tcx.item_name(did).to_string()) | |
263 | } | |
0731742a | 264 | // we don't display docs on `extern crate` items anyway, so don't process them. |
b7449926 XL |
265 | ExternCrateItem(..) => return self.fold_item_recur(item), |
266 | ImportItem(Import::Simple(ref name, ..)) => Some(name.clone()), | |
267 | MacroItem(..) => None, | |
268 | _ => item.name.clone(), | |
269 | }; | |
270 | ||
271 | if item.is_mod() && item.attrs.inner_docs { | |
48663c56 | 272 | self.mod_ids.push(item_hir_id.unwrap()); |
b7449926 XL |
273 | } |
274 | ||
275 | let cx = self.cx; | |
276 | let dox = item.attrs.collapsed_doc_value().unwrap_or_else(String::new); | |
277 | ||
a1dfa0c6 | 278 | look_for_tests(&cx, &dox, &item, true); |
0bf4aa26 | 279 | |
b7449926 | 280 | for (ori_link, link_range) in markdown_links(&dox) { |
0731742a | 281 | // Bail early for real links. |
b7449926 XL |
282 | if ori_link.contains('/') { |
283 | continue; | |
284 | } | |
532ac7d7 XL |
285 | |
286 | // [] is mostly likely not supposed to be a link | |
287 | if ori_link.is_empty() { | |
288 | continue; | |
289 | } | |
290 | ||
b7449926 | 291 | let link = ori_link.replace("`", ""); |
48663c56 | 292 | let (res, fragment) = { |
532ac7d7 | 293 | let mut kind = None; |
b7449926 XL |
294 | let path_str = if let Some(prefix) = |
295 | ["struct@", "enum@", "type@", | |
296 | "trait@", "union@"].iter() | |
297 | .find(|p| link.starts_with(**p)) { | |
532ac7d7 | 298 | kind = Some(TypeNS); |
0731742a | 299 | link.trim_start_matches(prefix) |
b7449926 XL |
300 | } else if let Some(prefix) = |
301 | ["const@", "static@", | |
302 | "value@", "function@", "mod@", | |
303 | "fn@", "module@", "method@"] | |
304 | .iter().find(|p| link.starts_with(**p)) { | |
532ac7d7 | 305 | kind = Some(ValueNS); |
0731742a | 306 | link.trim_start_matches(prefix) |
b7449926 | 307 | } else if link.ends_with("()") { |
532ac7d7 | 308 | kind = Some(ValueNS); |
0731742a | 309 | link.trim_end_matches("()") |
b7449926 | 310 | } else if link.starts_with("macro@") { |
532ac7d7 | 311 | kind = Some(MacroNS); |
0731742a | 312 | link.trim_start_matches("macro@") |
b7449926 | 313 | } else if link.ends_with('!') { |
532ac7d7 | 314 | kind = Some(MacroNS); |
0731742a | 315 | link.trim_end_matches('!') |
b7449926 XL |
316 | } else { |
317 | &link[..] | |
318 | }.trim(); | |
319 | ||
320 | if path_str.contains(|ch: char| !(ch.is_alphanumeric() || | |
321 | ch == ':' || ch == '_')) { | |
322 | continue; | |
323 | } | |
324 | ||
325 | match kind { | |
532ac7d7 | 326 | Some(ns @ ValueNS) => { |
48663c56 XL |
327 | if let Ok(res) = self.resolve(path_str, ns, ¤t_item, parent_node) { |
328 | res | |
b7449926 | 329 | } else { |
48663c56 | 330 | resolution_failure(cx, &item, path_str, &dox, link_range); |
0731742a | 331 | // This could just be a normal link or a broken link |
b7449926 | 332 | // we could potentially check if something is |
0731742a | 333 | // "intra-doc-link-like" and warn in that case. |
b7449926 XL |
334 | continue; |
335 | } | |
336 | } | |
532ac7d7 | 337 | Some(ns @ TypeNS) => { |
48663c56 XL |
338 | if let Ok(res) = self.resolve(path_str, ns, ¤t_item, parent_node) { |
339 | res | |
b7449926 | 340 | } else { |
48663c56 | 341 | resolution_failure(cx, &item, path_str, &dox, link_range); |
0731742a | 342 | // This could just be a normal link. |
b7449926 XL |
343 | continue; |
344 | } | |
345 | } | |
532ac7d7 | 346 | None => { |
0731742a | 347 | // Try everything! |
532ac7d7 | 348 | let candidates = PerNS { |
48663c56 | 349 | macro_ns: macro_resolve(cx, path_str).map(|res| (res, None)), |
532ac7d7 XL |
350 | type_ns: self |
351 | .resolve(path_str, TypeNS, ¤t_item, parent_node) | |
352 | .ok(), | |
353 | value_ns: self | |
354 | .resolve(path_str, ValueNS, ¤t_item, parent_node) | |
355 | .ok() | |
48663c56 | 356 | .and_then(|(res, fragment)| { |
532ac7d7 | 357 | // Constructors are picked up in the type namespace. |
48663c56 XL |
358 | match res { |
359 | Res::Def(DefKind::Ctor(..), _) | Res::SelfCtor(..) => None, | |
360 | _ => Some((res, fragment)) | |
532ac7d7 XL |
361 | } |
362 | }), | |
363 | }; | |
364 | ||
365 | if candidates.is_empty() { | |
48663c56 | 366 | resolution_failure(cx, &item, path_str, &dox, link_range); |
b7449926 XL |
367 | // this could just be a normal link |
368 | continue; | |
369 | } | |
532ac7d7 XL |
370 | |
371 | let is_unambiguous = candidates.clone().present_items().count() == 1; | |
372 | if is_unambiguous { | |
373 | candidates.present_items().next().unwrap() | |
374 | } else { | |
375 | ambiguity_error( | |
376 | cx, | |
48663c56 | 377 | &item, |
532ac7d7 XL |
378 | path_str, |
379 | &dox, | |
380 | link_range, | |
48663c56 | 381 | candidates.map(|candidate| candidate.map(|(res, _)| res)), |
532ac7d7 XL |
382 | ); |
383 | continue; | |
384 | } | |
b7449926 | 385 | } |
532ac7d7 | 386 | Some(MacroNS) => { |
48663c56 XL |
387 | if let Some(res) = macro_resolve(cx, path_str) { |
388 | (res, None) | |
b7449926 | 389 | } else { |
48663c56 | 390 | resolution_failure(cx, &item, path_str, &dox, link_range); |
b7449926 XL |
391 | continue |
392 | } | |
393 | } | |
394 | } | |
395 | }; | |
396 | ||
48663c56 | 397 | if let Res::PrimTy(_) = res { |
b7449926 XL |
398 | item.attrs.links.push((ori_link, None, fragment)); |
399 | } else { | |
48663c56 | 400 | let id = register_res(cx, res); |
b7449926 XL |
401 | item.attrs.links.push((ori_link, Some(id), fragment)); |
402 | } | |
403 | } | |
404 | ||
405 | if item.is_mod() && !item.attrs.inner_docs { | |
48663c56 | 406 | self.mod_ids.push(item_hir_id.unwrap()); |
b7449926 XL |
407 | } |
408 | ||
409 | if item.is_mod() { | |
410 | let ret = self.fold_item_recur(item); | |
411 | ||
412 | self.mod_ids.pop(); | |
413 | ||
414 | ret | |
415 | } else { | |
416 | self.fold_item_recur(item) | |
417 | } | |
418 | } | |
48663c56 XL |
419 | |
420 | // FIXME: if we can resolve intra-doc links from other crates, we can use the stock | |
421 | // `fold_crate`, but until then we should avoid scanning `krate.external_traits` since those | |
422 | // will never resolve properly | |
423 | fn fold_crate(&mut self, mut c: Crate) -> Crate { | |
424 | c.module = c.module.take().and_then(|module| self.fold_item(module)); | |
425 | ||
426 | c | |
427 | } | |
b7449926 XL |
428 | } |
429 | ||
9fa01778 | 430 | /// Resolves a string as a macro. |
48663c56 | 431 | fn macro_resolve(cx: &DocContext<'_>, path_str: &str) -> Option<Res> { |
416331ca | 432 | let path = ast::Path::from_ident(Ident::from_str(path_str)); |
532ac7d7 | 433 | cx.enter_resolver(|resolver| { |
416331ca | 434 | if let Ok((Some(ext), res)) = resolver.resolve_macro_path( |
e1599b0c | 435 | &path, None, &ParentScope::module(resolver.graph_root), false, false |
416331ca XL |
436 | ) { |
437 | if let SyntaxExtensionKind::LegacyBang { .. } = ext.kind { | |
438 | return Some(res.map_id(|_| panic!("unexpected id"))); | |
9fa01778 | 439 | } |
b7449926 | 440 | } |
48663c56 XL |
441 | if let Some(res) = resolver.all_macros.get(&Symbol::intern(path_str)) { |
442 | return Some(res.map_id(|_| panic!("unexpected id"))); | |
532ac7d7 XL |
443 | } |
444 | None | |
445 | }) | |
b7449926 XL |
446 | } |
447 | ||
0731742a XL |
448 | /// Reports a resolution failure diagnostic. |
449 | /// | |
9fa01778 XL |
450 | /// If we cannot find the exact source span of the resolution failure, we use the span of the |
451 | /// documentation attributes themselves. This is a little heavy-handed, so we display the markdown | |
452 | /// line containing the failure as a note as well. | |
b7449926 | 453 | fn resolution_failure( |
532ac7d7 | 454 | cx: &DocContext<'_>, |
48663c56 | 455 | item: &Item, |
b7449926 XL |
456 | path_str: &str, |
457 | dox: &str, | |
458 | link_range: Option<Range<usize>>, | |
459 | ) { | |
48663c56 XL |
460 | let hir_id = match cx.as_local_hir_id(item.def_id) { |
461 | Some(hir_id) => hir_id, | |
462 | None => { | |
463 | // If non-local, no need to check anything. | |
464 | return; | |
465 | } | |
466 | }; | |
467 | let attrs = &item.attrs; | |
416331ca | 468 | let sp = span_of_attrs(attrs).unwrap_or(item.source.span()); |
b7449926 | 469 | |
532ac7d7 | 470 | let mut diag = cx.tcx.struct_span_lint_hir( |
9fa01778 | 471 | lint::builtin::INTRA_DOC_LINK_RESOLUTION_FAILURE, |
48663c56 | 472 | hir_id, |
9fa01778 XL |
473 | sp, |
474 | &format!("`[{}]` cannot be resolved, ignoring it...", path_str), | |
475 | ); | |
476 | if let Some(link_range) = link_range { | |
477 | if let Some(sp) = super::source_span_for_markdown_range(cx, dox, &link_range, attrs) { | |
478 | diag.set_span(sp); | |
b7449926 XL |
479 | diag.span_label(sp, "cannot be resolved, ignoring"); |
480 | } else { | |
0731742a XL |
481 | // blah blah blah\nblah\nblah [blah] blah blah\nblah blah |
482 | // ^ ~~~~ | |
483 | // | link_range | |
484 | // last_new_line_offset | |
b7449926 XL |
485 | let last_new_line_offset = dox[..link_range.start].rfind('\n').map_or(0, |n| n + 1); |
486 | let line = dox[last_new_line_offset..].lines().next().unwrap_or(""); | |
487 | ||
0731742a | 488 | // Print the line containing the `link_range` and manually mark it with '^'s. |
b7449926 XL |
489 | diag.note(&format!( |
490 | "the link appears in this line:\n\n{line}\n\ | |
491 | {indicator: <before$}{indicator:^<found$}", | |
492 | line=line, | |
493 | indicator="", | |
494 | before=link_range.start - last_new_line_offset, | |
495 | found=link_range.len(), | |
496 | )); | |
497 | } | |
b7449926 XL |
498 | }; |
499 | diag.help("to escape `[` and `]` characters, just add '\\' before them like \ | |
500 | `\\[` or `\\]`"); | |
501 | diag.emit(); | |
502 | } | |
503 | ||
532ac7d7 XL |
504 | fn ambiguity_error( |
505 | cx: &DocContext<'_>, | |
48663c56 | 506 | item: &Item, |
532ac7d7 XL |
507 | path_str: &str, |
508 | dox: &str, | |
509 | link_range: Option<Range<usize>>, | |
48663c56 | 510 | candidates: PerNS<Option<Res>>, |
532ac7d7 | 511 | ) { |
48663c56 XL |
512 | let hir_id = match cx.as_local_hir_id(item.def_id) { |
513 | Some(hir_id) => hir_id, | |
514 | None => { | |
515 | // If non-local, no need to check anything. | |
516 | return; | |
517 | } | |
518 | }; | |
519 | let attrs = &item.attrs; | |
416331ca | 520 | let sp = span_of_attrs(attrs).unwrap_or(item.source.span()); |
b7449926 | 521 | |
532ac7d7 XL |
522 | let mut msg = format!("`{}` is ", path_str); |
523 | ||
524 | let candidates = [TypeNS, ValueNS, MacroNS].iter().filter_map(|&ns| { | |
48663c56 | 525 | candidates[ns].map(|res| (res, ns)) |
532ac7d7 XL |
526 | }).collect::<Vec<_>>(); |
527 | match candidates.as_slice() { | |
528 | [(first_def, _), (second_def, _)] => { | |
529 | msg += &format!( | |
530 | "both {} {} and {} {}", | |
531 | first_def.article(), | |
48663c56 | 532 | first_def.descr(), |
532ac7d7 | 533 | second_def.article(), |
48663c56 | 534 | second_def.descr(), |
532ac7d7 XL |
535 | ); |
536 | } | |
537 | _ => { | |
538 | let mut candidates = candidates.iter().peekable(); | |
48663c56 | 539 | while let Some((res, _)) = candidates.next() { |
532ac7d7 | 540 | if candidates.peek().is_some() { |
48663c56 | 541 | msg += &format!("{} {}, ", res.article(), res.descr()); |
532ac7d7 | 542 | } else { |
48663c56 | 543 | msg += &format!("and {} {}", res.article(), res.descr()); |
532ac7d7 XL |
544 | } |
545 | } | |
546 | } | |
b7449926 | 547 | } |
b7449926 | 548 | |
532ac7d7 XL |
549 | let mut diag = cx.tcx.struct_span_lint_hir( |
550 | lint::builtin::INTRA_DOC_LINK_RESOLUTION_FAILURE, | |
48663c56 | 551 | hir_id, |
532ac7d7 XL |
552 | sp, |
553 | &msg, | |
554 | ); | |
555 | ||
556 | if let Some(link_range) = link_range { | |
557 | if let Some(sp) = super::source_span_for_markdown_range(cx, dox, &link_range, attrs) { | |
558 | diag.set_span(sp); | |
559 | diag.span_label(sp, "ambiguous link"); | |
560 | ||
48663c56 XL |
561 | for (res, ns) in candidates { |
562 | let (action, mut suggestion) = match res { | |
563 | Res::Def(DefKind::Method, _) | Res::Def(DefKind::Fn, _) => { | |
532ac7d7 XL |
564 | ("add parentheses", format!("{}()", path_str)) |
565 | } | |
48663c56 | 566 | Res::Def(DefKind::Macro(..), _) => { |
532ac7d7 XL |
567 | ("add an exclamation mark", format!("{}!", path_str)) |
568 | } | |
569 | _ => { | |
48663c56 XL |
570 | let type_ = match (res, ns) { |
571 | (Res::Def(DefKind::Const, _), _) => "const", | |
572 | (Res::Def(DefKind::Static, _), _) => "static", | |
573 | (Res::Def(DefKind::Struct, _), _) => "struct", | |
574 | (Res::Def(DefKind::Enum, _), _) => "enum", | |
575 | (Res::Def(DefKind::Union, _), _) => "union", | |
576 | (Res::Def(DefKind::Trait, _), _) => "trait", | |
577 | (Res::Def(DefKind::Mod, _), _) => "module", | |
532ac7d7 XL |
578 | (_, TypeNS) => "type", |
579 | (_, ValueNS) => "value", | |
580 | (_, MacroNS) => "macro", | |
581 | }; | |
582 | ||
583 | // FIXME: if this is an implied shortcut link, it's bad style to suggest `@` | |
584 | ("prefix with the item type", format!("{}@{}", type_, path_str)) | |
585 | } | |
586 | }; | |
587 | ||
588 | if dox.bytes().nth(link_range.start) == Some(b'`') { | |
589 | suggestion = format!("`{}`", suggestion); | |
590 | } | |
591 | ||
592 | diag.span_suggestion( | |
593 | sp, | |
48663c56 | 594 | &format!("to link to the {}, {}", res.descr(), action), |
532ac7d7 XL |
595 | suggestion, |
596 | Applicability::MaybeIncorrect, | |
597 | ); | |
598 | } | |
599 | } else { | |
600 | // blah blah blah\nblah\nblah [blah] blah blah\nblah blah | |
601 | // ^ ~~~~ | |
602 | // | link_range | |
603 | // last_new_line_offset | |
604 | let last_new_line_offset = dox[..link_range.start].rfind('\n').map_or(0, |n| n + 1); | |
605 | let line = dox[last_new_line_offset..].lines().next().unwrap_or(""); | |
606 | ||
607 | // Print the line containing the `link_range` and manually mark it with '^'s. | |
608 | diag.note(&format!( | |
609 | "the link appears in this line:\n\n{line}\n\ | |
610 | {indicator: <before$}{indicator:^<found$}", | |
611 | line=line, | |
612 | indicator="", | |
613 | before=link_range.start - last_new_line_offset, | |
614 | found=link_range.len(), | |
615 | )); | |
616 | } | |
617 | } | |
618 | ||
619 | diag.emit(); | |
b7449926 XL |
620 | } |
621 | ||
48663c56 XL |
622 | /// Given an enum variant's res, return the res of its enum and the associated fragment. |
623 | fn handle_variant(cx: &DocContext<'_>, res: Res) -> Result<(Res, Option<String>), ()> { | |
b7449926 XL |
624 | use rustc::ty::DefIdTree; |
625 | ||
48663c56 | 626 | let parent = if let Some(parent) = cx.tcx.parent(res.def_id()) { |
b7449926 XL |
627 | parent |
628 | } else { | |
629 | return Err(()) | |
630 | }; | |
48663c56 XL |
631 | let parent_def = Res::Def(DefKind::Enum, parent); |
632 | let variant = cx.tcx.expect_variant_res(res); | |
0731742a | 633 | Ok((parent_def, Some(format!("{}.v", variant.ident.name)))) |
b7449926 XL |
634 | } |
635 | ||
48663c56 XL |
636 | const PRIMITIVES: &[(&str, Res)] = &[ |
637 | ("u8", Res::PrimTy(hir::PrimTy::Uint(syntax::ast::UintTy::U8))), | |
638 | ("u16", Res::PrimTy(hir::PrimTy::Uint(syntax::ast::UintTy::U16))), | |
639 | ("u32", Res::PrimTy(hir::PrimTy::Uint(syntax::ast::UintTy::U32))), | |
640 | ("u64", Res::PrimTy(hir::PrimTy::Uint(syntax::ast::UintTy::U64))), | |
641 | ("u128", Res::PrimTy(hir::PrimTy::Uint(syntax::ast::UintTy::U128))), | |
642 | ("usize", Res::PrimTy(hir::PrimTy::Uint(syntax::ast::UintTy::Usize))), | |
643 | ("i8", Res::PrimTy(hir::PrimTy::Int(syntax::ast::IntTy::I8))), | |
644 | ("i16", Res::PrimTy(hir::PrimTy::Int(syntax::ast::IntTy::I16))), | |
645 | ("i32", Res::PrimTy(hir::PrimTy::Int(syntax::ast::IntTy::I32))), | |
646 | ("i64", Res::PrimTy(hir::PrimTy::Int(syntax::ast::IntTy::I64))), | |
647 | ("i128", Res::PrimTy(hir::PrimTy::Int(syntax::ast::IntTy::I128))), | |
648 | ("isize", Res::PrimTy(hir::PrimTy::Int(syntax::ast::IntTy::Isize))), | |
649 | ("f32", Res::PrimTy(hir::PrimTy::Float(syntax::ast::FloatTy::F32))), | |
650 | ("f64", Res::PrimTy(hir::PrimTy::Float(syntax::ast::FloatTy::F64))), | |
651 | ("str", Res::PrimTy(hir::PrimTy::Str)), | |
652 | ("bool", Res::PrimTy(hir::PrimTy::Bool)), | |
653 | ("char", Res::PrimTy(hir::PrimTy::Char)), | |
b7449926 XL |
654 | ]; |
655 | ||
48663c56 | 656 | fn is_primitive(path_str: &str, ns: Namespace) -> Option<Res> { |
532ac7d7 | 657 | if ns == TypeNS { |
b7449926 | 658 | PRIMITIVES.iter().find(|x| x.0 == path_str).map(|x| x.1) |
532ac7d7 XL |
659 | } else { |
660 | None | |
b7449926 XL |
661 | } |
662 | } | |
9fa01778 | 663 | |
532ac7d7 | 664 | fn primitive_impl(cx: &DocContext<'_>, path_str: &str) -> Option<DefId> { |
9fa01778 XL |
665 | let tcx = cx.tcx; |
666 | match path_str { | |
667 | "u8" => tcx.lang_items().u8_impl(), | |
668 | "u16" => tcx.lang_items().u16_impl(), | |
669 | "u32" => tcx.lang_items().u32_impl(), | |
670 | "u64" => tcx.lang_items().u64_impl(), | |
671 | "u128" => tcx.lang_items().u128_impl(), | |
672 | "usize" => tcx.lang_items().usize_impl(), | |
673 | "i8" => tcx.lang_items().i8_impl(), | |
674 | "i16" => tcx.lang_items().i16_impl(), | |
675 | "i32" => tcx.lang_items().i32_impl(), | |
676 | "i64" => tcx.lang_items().i64_impl(), | |
677 | "i128" => tcx.lang_items().i128_impl(), | |
678 | "isize" => tcx.lang_items().isize_impl(), | |
679 | "f32" => tcx.lang_items().f32_impl(), | |
680 | "f64" => tcx.lang_items().f64_impl(), | |
681 | "str" => tcx.lang_items().str_impl(), | |
e1599b0c | 682 | "bool" => tcx.lang_items().bool_impl(), |
9fa01778 XL |
683 | "char" => tcx.lang_items().char_impl(), |
684 | _ => None, | |
685 | } | |
686 | } |