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