]> git.lizzy.rs Git - rust.git/blob - src/librustdoc/passes/collect_intra_doc_links.rs
Auto merge of #55808 - estebank:type-arguments, r=petrochenkov
[rust.git] / src / librustdoc / passes / collect_intra_doc_links.rs
1 // Copyright 2018 The Rust Project Developers. See the COPYRIGHT
2 // file at the top-level directory of this distribution and at
3 // http://rust-lang.org/COPYRIGHT.
4 //
5 // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6 // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7 // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8 // option. This file may not be copied, modified, or distributed
9 // except according to those terms.
10
11 use clean::*;
12
13 use rustc::lint as lint;
14 use rustc::hir;
15 use rustc::hir::def::Def;
16 use rustc::ty;
17 use syntax;
18 use syntax::ast::{self, Ident, NodeId};
19 use syntax::feature_gate::UnstableFeatures;
20 use syntax::symbol::Symbol;
21 use syntax_pos::{self, DUMMY_SP};
22
23 use std::ops::Range;
24
25 use core::DocContext;
26 use fold::DocFolder;
27 use html::markdown::markdown_links;
28
29 use passes::{look_for_tests, Pass};
30
31 pub const COLLECT_INTRA_DOC_LINKS: Pass =
32     Pass::early("collect-intra-doc-links", collect_intra_doc_links,
33                 "reads a crate's documentation to resolve intra-doc-links");
34
35 pub fn collect_intra_doc_links(krate: Crate, cx: &DocContext) -> Crate {
36     if !UnstableFeatures::from_environment().is_nightly_build() {
37         krate
38     } else {
39         let mut coll = LinkCollector::new(cx);
40
41         coll.fold_crate(krate)
42     }
43 }
44
45 #[derive(Debug)]
46 enum PathKind {
47     /// can be either value or type, not a macro
48     Unknown,
49     /// macro
50     Macro,
51     /// values, functions, consts, statics, everything in the value namespace
52     Value,
53     /// types, traits, everything in the type namespace
54     Type,
55 }
56
57 struct LinkCollector<'a, 'tcx: 'a, 'rcx: 'a, 'cstore: 'rcx> {
58     cx: &'a DocContext<'a, 'tcx, 'rcx, 'cstore>,
59     mod_ids: Vec<NodeId>,
60     is_nightly_build: bool,
61 }
62
63 impl<'a, 'tcx, 'rcx, 'cstore> LinkCollector<'a, 'tcx, 'rcx, 'cstore> {
64     fn new(cx: &'a DocContext<'a, 'tcx, 'rcx, 'cstore>) -> Self {
65         LinkCollector {
66             cx,
67             mod_ids: Vec::new(),
68             is_nightly_build: UnstableFeatures::from_environment().is_nightly_build(),
69         }
70     }
71
72     /// Resolve a given string as a path, along with whether or not it is
73     /// in the value namespace. Also returns an optional URL fragment in the case
74     /// of variants and methods
75     fn resolve(&self,
76                path_str: &str,
77                is_val: bool,
78                current_item: &Option<String>,
79                parent_id: Option<NodeId>)
80         -> Result<(Def, Option<String>), ()>
81     {
82         let cx = self.cx;
83
84         // In case we're in a module, try to resolve the relative
85         // path
86         if let Some(id) = parent_id.or(self.mod_ids.last().cloned()) {
87             // FIXME: `with_scope` requires the NodeId of a module
88             let result = cx.resolver.borrow_mut()
89                                     .with_scope(id,
90                 |resolver| {
91                     resolver.resolve_str_path_error(DUMMY_SP,
92                                                     &path_str, is_val)
93             });
94
95             if let Ok(result) = result {
96                 // In case this is a trait item, skip the
97                 // early return and try looking for the trait
98                 let value = match result.def {
99                     Def::Method(_) | Def::AssociatedConst(_) => true,
100                     Def::AssociatedTy(_) => false,
101                     Def::Variant(_) => return handle_variant(cx, result.def),
102                     // not a trait item, just return what we found
103                     _ => return Ok((result.def, None))
104                 };
105
106                 if value != is_val {
107                     return Err(())
108                 }
109             } else if let Some(prim) = is_primitive(path_str, is_val) {
110                 return Ok((prim, Some(path_str.to_owned())))
111             } else {
112                 // If resolution failed, it may still be a method
113                 // because methods are not handled by the resolver
114                 // If so, bail when we're not looking for a value
115                 if !is_val {
116                     return Err(())
117                 }
118             }
119
120             // Try looking for methods and associated items
121             let mut split = path_str.rsplitn(2, "::");
122             let item_name = if let Some(first) = split.next() {
123                 first
124             } else {
125                 return Err(())
126             };
127
128             let mut path = if let Some(second) = split.next() {
129                 second.to_owned()
130             } else {
131                 return Err(())
132             };
133
134             if path == "self" || path == "Self" {
135                 if let Some(name) = current_item.as_ref() {
136                     path = name.clone();
137                 }
138             }
139
140             // FIXME: `with_scope` requires the NodeId of a module
141             let ty = cx.resolver.borrow_mut()
142                                 .with_scope(id,
143                 |resolver| {
144                     resolver.resolve_str_path_error(DUMMY_SP, &path, false)
145             })?;
146             match ty.def {
147                 Def::Struct(did) | Def::Union(did) | Def::Enum(did) | Def::TyAlias(did) => {
148                     let item = cx.tcx.inherent_impls(did)
149                                      .iter()
150                                      .flat_map(|imp| cx.tcx.associated_items(*imp))
151                                      .find(|item| item.ident.name == item_name);
152                     if let Some(item) = item {
153                         let out = match item.kind {
154                             ty::AssociatedKind::Method if is_val => "method",
155                             ty::AssociatedKind::Const if is_val => "associatedconstant",
156                             _ => return Err(())
157                         };
158                         Ok((ty.def, Some(format!("{}.{}", out, item_name))))
159                     } else {
160                         match cx.tcx.type_of(did).sty {
161                             ty::Adt(def, _) => {
162                                 if let Some(item) = if def.is_enum() {
163                                     def.all_fields().find(|item| item.ident.name == item_name)
164                                 } else {
165                                     def.non_enum_variant()
166                                        .fields
167                                        .iter()
168                                        .find(|item| item.ident.name == item_name)
169                                 } {
170                                     Ok((ty.def,
171                                         Some(format!("{}.{}",
172                                                      if def.is_enum() {
173                                                          "variant"
174                                                      } else {
175                                                          "structfield"
176                                                      },
177                                                      item.ident))))
178                                 } else {
179                                     Err(())
180                                 }
181                             }
182                             _ => Err(()),
183                         }
184                     }
185                 }
186                 Def::Trait(did) => {
187                     let item = cx.tcx.associated_item_def_ids(did).iter()
188                                  .map(|item| cx.tcx.associated_item(*item))
189                                  .find(|item| item.ident.name == item_name);
190                     if let Some(item) = item {
191                         let kind = match item.kind {
192                             ty::AssociatedKind::Const if is_val => "associatedconstant",
193                             ty::AssociatedKind::Type if !is_val => "associatedtype",
194                             ty::AssociatedKind::Method if is_val => {
195                                 if item.defaultness.has_value() {
196                                     "method"
197                                 } else {
198                                     "tymethod"
199                                 }
200                             }
201                             _ => return Err(())
202                         };
203
204                         Ok((ty.def, Some(format!("{}.{}", kind, item_name))))
205                     } else {
206                         Err(())
207                     }
208                 }
209                 _ => Err(())
210             }
211         } else {
212             Err(())
213         }
214     }
215 }
216
217 impl<'a, 'tcx, 'rcx, 'cstore> DocFolder for LinkCollector<'a, 'tcx, 'rcx, 'cstore> {
218     fn fold_item(&mut self, mut item: Item) -> Option<Item> {
219         let item_node_id = if item.is_mod() {
220             if let Some(id) = self.cx.tcx.hir.as_local_node_id(item.def_id) {
221                 Some(id)
222             } else {
223                 debug!("attempting to fold on a non-local item: {:?}", item);
224                 return self.fold_item_recur(item);
225             }
226         } else {
227             None
228         };
229
230         // FIXME: get the resolver to work with non-local resolve scopes
231         let parent_node = self.cx.as_local_node_id(item.def_id).and_then(|node_id| {
232             // FIXME: this fails hard for impls in non-module scope, but is necessary for the
233             // current resolve() implementation
234             match self.cx.tcx.hir.get_module_parent_node(node_id) {
235                 id if id != node_id => Some(id),
236                 _ => None,
237             }
238         });
239
240         if parent_node.is_some() {
241             debug!("got parent node for {} {:?}, id {:?}", item.type_(), item.name, item.def_id);
242         }
243
244         let current_item = match item.inner {
245             ModuleItem(..) => {
246                 if item.attrs.inner_docs {
247                     if item_node_id.unwrap() != NodeId::from_u32(0) {
248                         item.name.clone()
249                     } else {
250                         None
251                     }
252                 } else {
253                     match parent_node.or(self.mod_ids.last().cloned()) {
254                         Some(parent) if parent != NodeId::from_u32(0) => {
255                             //FIXME: can we pull the parent module's name from elsewhere?
256                             Some(self.cx.tcx.hir.name(parent).to_string())
257                         }
258                         _ => None,
259                     }
260                 }
261             }
262             ImplItem(Impl { ref for_, .. }) => {
263                 for_.def_id().map(|did| self.cx.tcx.item_name(did).to_string())
264             }
265             // we don't display docs on `extern crate` items anyway, so don't process them
266             ExternCrateItem(..) => return self.fold_item_recur(item),
267             ImportItem(Import::Simple(ref name, ..)) => Some(name.clone()),
268             MacroItem(..) => None,
269             _ => item.name.clone(),
270         };
271
272         if item.is_mod() && item.attrs.inner_docs {
273             self.mod_ids.push(item_node_id.unwrap());
274         }
275
276         let cx = self.cx;
277         let dox = item.attrs.collapsed_doc_value().unwrap_or_else(String::new);
278
279         look_for_tests(&cx, &dox, &item, true);
280
281         if !self.is_nightly_build {
282             return None;
283         }
284
285         for (ori_link, link_range) in markdown_links(&dox) {
286             // bail early for real links
287             if ori_link.contains('/') {
288                 continue;
289             }
290             let link = ori_link.replace("`", "");
291             let (def, fragment) = {
292                 let mut kind = PathKind::Unknown;
293                 let path_str = if let Some(prefix) =
294                     ["struct@", "enum@", "type@",
295                      "trait@", "union@"].iter()
296                                       .find(|p| link.starts_with(**p)) {
297                     kind = PathKind::Type;
298                     link.trim_left_matches(prefix)
299                 } else if let Some(prefix) =
300                     ["const@", "static@",
301                      "value@", "function@", "mod@",
302                      "fn@", "module@", "method@"]
303                         .iter().find(|p| link.starts_with(**p)) {
304                     kind = PathKind::Value;
305                     link.trim_left_matches(prefix)
306                 } else if link.ends_with("()") {
307                     kind = PathKind::Value;
308                     link.trim_right_matches("()")
309                 } else if link.starts_with("macro@") {
310                     kind = PathKind::Macro;
311                     link.trim_left_matches("macro@")
312                 } else if link.ends_with('!') {
313                     kind = PathKind::Macro;
314                     link.trim_right_matches('!')
315                 } else {
316                     &link[..]
317                 }.trim();
318
319                 if path_str.contains(|ch: char| !(ch.is_alphanumeric() ||
320                                                   ch == ':' || ch == '_')) {
321                     continue;
322                 }
323
324                 match kind {
325                     PathKind::Value => {
326                         if let Ok(def) = self.resolve(path_str, true, &current_item, parent_node) {
327                             def
328                         } else {
329                             resolution_failure(cx, &item.attrs, path_str, &dox, link_range);
330                             // this could just be a normal link or a broken link
331                             // we could potentially check if something is
332                             // "intra-doc-link-like" and warn in that case
333                             continue;
334                         }
335                     }
336                     PathKind::Type => {
337                         if let Ok(def) = self.resolve(path_str, false, &current_item, parent_node) {
338                             def
339                         } else {
340                             resolution_failure(cx, &item.attrs, path_str, &dox, link_range);
341                             // this could just be a normal link
342                             continue;
343                         }
344                     }
345                     PathKind::Unknown => {
346                         // try everything!
347                         if let Some(macro_def) = macro_resolve(cx, path_str) {
348                             if let Ok(type_def) =
349                                 self.resolve(path_str, false, &current_item, parent_node)
350                             {
351                                 let (type_kind, article, type_disambig)
352                                     = type_ns_kind(type_def.0, path_str);
353                                 ambiguity_error(cx, &item.attrs, path_str,
354                                                 article, type_kind, &type_disambig,
355                                                 "a", "macro", &format!("macro@{}", path_str));
356                                 continue;
357                             } else if let Ok(value_def) =
358                                 self.resolve(path_str, true, &current_item, parent_node)
359                             {
360                                 let (value_kind, value_disambig)
361                                     = value_ns_kind(value_def.0, path_str)
362                                         .expect("struct and mod cases should have been \
363                                                  caught in previous branch");
364                                 ambiguity_error(cx, &item.attrs, path_str,
365                                                 "a", value_kind, &value_disambig,
366                                                 "a", "macro", &format!("macro@{}", path_str));
367                             }
368                             (macro_def, None)
369                         } else if let Ok(type_def) =
370                             self.resolve(path_str, false, &current_item, parent_node)
371                         {
372                             // It is imperative we search for not-a-value first
373                             // Otherwise we will find struct ctors for when we are looking
374                             // for structs, and the link won't work.
375                             // if there is something in both namespaces
376                             if let Ok(value_def) =
377                                 self.resolve(path_str, true, &current_item, parent_node)
378                             {
379                                 let kind = value_ns_kind(value_def.0, path_str);
380                                 if let Some((value_kind, value_disambig)) = kind {
381                                     let (type_kind, article, type_disambig)
382                                         = type_ns_kind(type_def.0, path_str);
383                                     ambiguity_error(cx, &item.attrs, path_str,
384                                                     article, type_kind, &type_disambig,
385                                                     "a", value_kind, &value_disambig);
386                                     continue;
387                                 }
388                             }
389                             type_def
390                         } else if let Ok(value_def) =
391                             self.resolve(path_str, true, &current_item, parent_node)
392                         {
393                             value_def
394                         } else {
395                             resolution_failure(cx, &item.attrs, path_str, &dox, link_range);
396                             // this could just be a normal link
397                             continue;
398                         }
399                     }
400                     PathKind::Macro => {
401                         if let Some(def) = macro_resolve(cx, path_str) {
402                             (def, None)
403                         } else {
404                             resolution_failure(cx, &item.attrs, path_str, &dox, link_range);
405                             continue
406                         }
407                     }
408                 }
409             };
410
411             if let Def::PrimTy(_) = def {
412                 item.attrs.links.push((ori_link, None, fragment));
413             } else {
414                 let id = register_def(cx, def);
415                 item.attrs.links.push((ori_link, Some(id), fragment));
416             }
417         }
418
419         if item.is_mod() && !item.attrs.inner_docs {
420             self.mod_ids.push(item_node_id.unwrap());
421         }
422
423         if item.is_mod() {
424             let ret = self.fold_item_recur(item);
425
426             self.mod_ids.pop();
427
428             ret
429         } else {
430             self.fold_item_recur(item)
431         }
432     }
433 }
434
435 /// Resolve a string as a macro
436 fn macro_resolve(cx: &DocContext, path_str: &str) -> Option<Def> {
437     use syntax::ext::base::{MacroKind, SyntaxExtension};
438     let segment = ast::PathSegment::from_ident(Ident::from_str(path_str));
439     let path = ast::Path { segments: vec![segment], span: DUMMY_SP };
440     let mut resolver = cx.resolver.borrow_mut();
441     let parent_scope = resolver.dummy_parent_scope();
442     if let Ok(def) = resolver.resolve_macro_to_def_inner(&path, MacroKind::Bang,
443                                                          &parent_scope, false, false) {
444         if let SyntaxExtension::DeclMacro { .. } = *resolver.get_macro(def) {
445             return Some(def);
446         }
447     }
448     if let Some(def) = resolver.all_macros.get(&Symbol::intern(path_str)) {
449         return Some(*def);
450     }
451     None
452 }
453
454 pub fn span_of_attrs(attrs: &Attributes) -> syntax_pos::Span {
455     if attrs.doc_strings.is_empty() {
456         return DUMMY_SP;
457     }
458     let start = attrs.doc_strings[0].span();
459     let end = attrs.doc_strings.last().expect("No doc strings provided").span();
460     start.to(end)
461 }
462
463 fn resolution_failure(
464     cx: &DocContext,
465     attrs: &Attributes,
466     path_str: &str,
467     dox: &str,
468     link_range: Option<Range<usize>>,
469 ) {
470     let sp = span_of_attrs(attrs);
471     let msg = format!("`[{}]` cannot be resolved, ignoring it...", path_str);
472
473     let code_dox = sp.to_src(cx);
474
475     let doc_comment_padding = 3;
476     let mut diag = if let Some(link_range) = link_range {
477         // blah blah blah\nblah\nblah [blah] blah blah\nblah blah
478         //                       ^    ~~~~~~
479         //                       |    link_range
480         //                       last_new_line_offset
481
482         let mut diag;
483         if dox.lines().count() == code_dox.lines().count() {
484             let line_offset = dox[..link_range.start].lines().count();
485             // The span starts in the `///`, so we don't have to account for the leading whitespace
486             let code_dox_len = if line_offset <= 1 {
487                 doc_comment_padding
488             } else {
489                 // The first `///`
490                 doc_comment_padding +
491                     // Each subsequent leading whitespace and `///`
492                     code_dox.lines().skip(1).take(line_offset - 1).fold(0, |sum, line| {
493                         sum + doc_comment_padding + line.len() - line.trim_start().len()
494                     })
495             };
496
497             // Extract the specific span
498             let sp = sp.from_inner_byte_pos(
499                 link_range.start + code_dox_len,
500                 link_range.end + code_dox_len,
501             );
502
503             diag = cx.tcx.struct_span_lint_node(lint::builtin::INTRA_DOC_LINK_RESOLUTION_FAILURE,
504                                                 NodeId::from_u32(0),
505                                                 sp,
506                                                 &msg);
507             diag.span_label(sp, "cannot be resolved, ignoring");
508         } else {
509             diag = cx.tcx.struct_span_lint_node(lint::builtin::INTRA_DOC_LINK_RESOLUTION_FAILURE,
510                                                 NodeId::from_u32(0),
511                                                 sp,
512                                                 &msg);
513
514             let last_new_line_offset = dox[..link_range.start].rfind('\n').map_or(0, |n| n + 1);
515             let line = dox[last_new_line_offset..].lines().next().unwrap_or("");
516
517             // Print the line containing the `link_range` and manually mark it with '^'s
518             diag.note(&format!(
519                 "the link appears in this line:\n\n{line}\n\
520                  {indicator: <before$}{indicator:^<found$}",
521                 line=line,
522                 indicator="",
523                 before=link_range.start - last_new_line_offset,
524                 found=link_range.len(),
525             ));
526         }
527         diag
528     } else {
529         cx.tcx.struct_span_lint_node(lint::builtin::INTRA_DOC_LINK_RESOLUTION_FAILURE,
530                                      NodeId::from_u32(0),
531                                      sp,
532                                      &msg)
533     };
534     diag.help("to escape `[` and `]` characters, just add '\\' before them like \
535                `\\[` or `\\]`");
536     diag.emit();
537 }
538
539 fn ambiguity_error(cx: &DocContext, attrs: &Attributes,
540                    path_str: &str,
541                    article1: &str, kind1: &str, disambig1: &str,
542                    article2: &str, kind2: &str, disambig2: &str) {
543     let sp = span_of_attrs(attrs);
544     cx.sess()
545       .struct_span_warn(sp,
546                         &format!("`{}` is both {} {} and {} {}",
547                                  path_str, article1, kind1,
548                                  article2, kind2))
549       .help(&format!("try `{}` if you want to select the {}, \
550                       or `{}` if you want to \
551                       select the {}",
552                       disambig1, kind1, disambig2,
553                       kind2))
554       .emit();
555 }
556
557 /// Given a def, returns its name and disambiguator
558 /// for a value namespace
559 ///
560 /// Returns None for things which cannot be ambiguous since
561 /// they exist in both namespaces (structs and modules)
562 fn value_ns_kind(def: Def, path_str: &str) -> Option<(&'static str, String)> {
563     match def {
564         // structs, variants, and mods exist in both namespaces. skip them
565         Def::StructCtor(..) | Def::Mod(..) | Def::Variant(..) |
566         Def::VariantCtor(..) | Def::SelfCtor(..)
567             => None,
568         Def::Fn(..)
569             => Some(("function", format!("{}()", path_str))),
570         Def::Method(..)
571             => Some(("method", format!("{}()", path_str))),
572         Def::Const(..)
573             => Some(("const", format!("const@{}", path_str))),
574         Def::Static(..)
575             => Some(("static", format!("static@{}", path_str))),
576         _ => Some(("value", format!("value@{}", path_str))),
577     }
578 }
579
580 /// Given a def, returns its name, the article to be used, and a disambiguator
581 /// for the type namespace
582 fn type_ns_kind(def: Def, path_str: &str) -> (&'static str, &'static str, String) {
583     let (kind, article) = match def {
584         // we can still have non-tuple structs
585         Def::Struct(..) => ("struct", "a"),
586         Def::Enum(..) => ("enum", "an"),
587         Def::Trait(..) => ("trait", "a"),
588         Def::Union(..) => ("union", "a"),
589         _ => ("type", "a"),
590     };
591     (kind, article, format!("{}@{}", kind, path_str))
592 }
593
594 /// Given an enum variant's def, return the def of its enum and the associated fragment
595 fn handle_variant(cx: &DocContext, def: Def) -> Result<(Def, Option<String>), ()> {
596     use rustc::ty::DefIdTree;
597
598     let parent = if let Some(parent) = cx.tcx.parent(def.def_id()) {
599         parent
600     } else {
601         return Err(())
602     };
603     let parent_def = Def::Enum(parent);
604     let variant = cx.tcx.expect_variant_def(def);
605     Ok((parent_def, Some(format!("{}.v", variant.name))))
606 }
607
608 const PRIMITIVES: &[(&str, Def)] = &[
609     ("u8",    Def::PrimTy(hir::PrimTy::Uint(syntax::ast::UintTy::U8))),
610     ("u16",   Def::PrimTy(hir::PrimTy::Uint(syntax::ast::UintTy::U16))),
611     ("u32",   Def::PrimTy(hir::PrimTy::Uint(syntax::ast::UintTy::U32))),
612     ("u64",   Def::PrimTy(hir::PrimTy::Uint(syntax::ast::UintTy::U64))),
613     ("u128",  Def::PrimTy(hir::PrimTy::Uint(syntax::ast::UintTy::U128))),
614     ("usize", Def::PrimTy(hir::PrimTy::Uint(syntax::ast::UintTy::Usize))),
615     ("i8",    Def::PrimTy(hir::PrimTy::Int(syntax::ast::IntTy::I8))),
616     ("i16",   Def::PrimTy(hir::PrimTy::Int(syntax::ast::IntTy::I16))),
617     ("i32",   Def::PrimTy(hir::PrimTy::Int(syntax::ast::IntTy::I32))),
618     ("i64",   Def::PrimTy(hir::PrimTy::Int(syntax::ast::IntTy::I64))),
619     ("i128",  Def::PrimTy(hir::PrimTy::Int(syntax::ast::IntTy::I128))),
620     ("isize", Def::PrimTy(hir::PrimTy::Int(syntax::ast::IntTy::Isize))),
621     ("f32",   Def::PrimTy(hir::PrimTy::Float(syntax::ast::FloatTy::F32))),
622     ("f64",   Def::PrimTy(hir::PrimTy::Float(syntax::ast::FloatTy::F64))),
623     ("str",   Def::PrimTy(hir::PrimTy::Str)),
624     ("bool",  Def::PrimTy(hir::PrimTy::Bool)),
625     ("char",  Def::PrimTy(hir::PrimTy::Char)),
626 ];
627
628 fn is_primitive(path_str: &str, is_val: bool) -> Option<Def> {
629     if is_val {
630         None
631     } else {
632         PRIMITIVES.iter().find(|x| x.0 == path_str).map(|x| x.1)
633     }
634 }