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