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