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