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