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