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