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