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