2 use rustc_errors::Applicability;
3 use rustc_expand::base::SyntaxExtensionKind;
4 use rustc_feature::UnstableFeatures;
11 use rustc_hir::def_id::DefId;
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;
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};
28 use super::span_of_attrs;
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",
36 pub fn collect_intra_doc_links(krate: Crate, cx: &DocContext<'_>) -> Crate {
37 if !UnstableFeatures::from_environment().is_nightly_build() {
40 let mut coll = LinkCollector::new(cx);
42 coll.fold_crate(krate)
48 AnchorFailure(&'static str),
51 struct LinkCollector<'a, 'tcx> {
52 cx: &'a DocContext<'tcx>,
53 // NOTE: this may not necessarily be a module in the current crate
57 impl<'a, 'tcx> LinkCollector<'a, 'tcx> {
58 fn new(cx: &'a DocContext<'tcx>) -> Self {
59 LinkCollector { cx, mod_ids: Vec::new() }
65 current_item: &Option<String>,
67 ) -> Result<(Res, Option<String>), ErrorKind> {
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)?;
74 split.next().map(|f| Symbol::intern(f)).ok_or(ErrorKind::ResolutionFailure)?;
78 if f == "self" || f == "Self" {
79 if let Some(name) = current_item.as_ref() {
85 .ok_or(ErrorKind::ResolutionFailure)?;
87 .enter_resolver(|resolver| {
88 resolver.resolve_str_path_error(DUMMY_SP, &path, TypeNS, module_id)
90 .map_err(|_| ErrorKind::ResolutionFailure)?;
91 if let Res::Err = ty_res {
92 return Err(ErrorKind::ResolutionFailure);
94 let ty_res = ty_res.map_id(|_| panic!("unexpected node_id"));
96 Res::Def(DefKind::Enum, did) => {
101 .flat_map(|imp| cx.tcx.associated_items(*imp).in_definition_order())
102 .any(|item| item.ident.name == variant_name)
104 return Err(ErrorKind::ResolutionFailure);
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) {
112 "variant.{}.field.{}",
113 variant_name, variant_field_name
117 Err(ErrorKind::ResolutionFailure)
120 _ => Err(ErrorKind::ResolutionFailure),
123 _ => Err(ErrorKind::ResolutionFailure),
127 /// Resolves a string as a macro.
128 fn macro_resolve(&self, path_str: &str, parent_id: Option<DefId>) -> Option<Res> {
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(
135 &ParentScope::module(resolver.graph_root()),
139 if let SyntaxExtensionKind::LegacyBang { .. } = ext.kind {
140 return Some(res.map_id(|_| panic!("unexpected id")));
143 if let Some(res) = resolver.all_macros().get(&Symbol::intern(path_str)) {
144 return Some(res.map_id(|_| panic!("unexpected id")));
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)
150 // don't resolve builtins like `#[derive]`
151 if let Res::Def(..) = res {
152 let res = res.map_id(|_| panic!("unexpected node_id"));
157 debug!("attempting to resolve item without parent module: {}", path_str);
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.
167 disambiguator: Option<&str>,
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> {
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)
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),
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);
197 // Not a trait item; just return what we found.
199 if extra_fragment.is_some() {
200 return Err(ErrorKind::AnchorFailure(
201 "primitive types cannot be followed by anchors",
204 return Ok((res, Some(path_str.to_owned())));
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",
216 return Ok((prim, Some(path_str.to_owned())));
219 return Ok((res, extra_fragment.clone()));
222 return Ok((res, extra_fragment.clone()));
226 if value != (ns == ValueNS) {
227 return Err(ErrorKind::ResolutionFailure);
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",
235 return Ok((prim, Some(path_str.to_owned())));
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.
241 return Err(ErrorKind::ResolutionFailure);
245 // Try looking for methods and associated items.
246 let mut split = path_str.rsplitn(2, "::");
248 split.next().map(|f| Symbol::intern(f)).ok_or(ErrorKind::ResolutionFailure)?;
252 if f == "self" || f == "Self" {
253 if let Some(name) = current_item.as_ref() {
259 .ok_or(ErrorKind::ResolutionFailure)?;
261 if let Some(prim) = is_primitive(&path, TypeNS) {
262 let did = primitive_impl(cx, &path).ok_or(ErrorKind::ResolutionFailure)?;
265 .associated_items(did)
266 .filter_by_name_unhygienic(item_name)
268 .and_then(|item| match item.kind {
269 ty::AssocKind::Fn => Some("method"),
272 .map(|out| (prim, Some(format!("{}#{}.{}", path, out, item_name))))
273 .ok_or(ErrorKind::ResolutionFailure);
277 .enter_resolver(|resolver| {
278 resolver.resolve_str_path_error(DUMMY_SP, &path, TypeNS, module_id)
280 .map_err(|_| ErrorKind::ResolutionFailure)?;
281 if let Res::Err = ty_res {
282 return self.variant_field(path_str, current_item, module_id);
284 let ty_res = ty_res.map_id(|_| panic!("unexpected node_id"));
287 DefKind::Struct | DefKind::Union | DefKind::Enum | DefKind::TyAlias,
290 // Checks if item_name belongs to `impl SomeItem`
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);
304 Some(hir::Node::Item(hir::Item {
305 kind: hir::ItemKind::Impl { of_trait: Some(_), self_ty, .. },
309 .associated_item_def_ids(self_ty.hir_id.owner)
312 let associated_item = cx.tcx.associated_item(*child);
315 .find(|child| child.ident.name == item_name),
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
324 // [`ambi_fn`](<SomeStruct as SomeTrait>::ambi_fn)
327 (None, Some(from_trait)) => Some(from_trait),
328 (Some(from_impl), None) => Some(from_impl),
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),
339 if extra_fragment.is_some() {
340 Err(ErrorKind::AnchorFailure(if item.kind == ty::AssocKind::Fn {
341 "methods cannot be followed by anchors"
343 "associated constants cannot be followed by anchors"
346 Ok((ty_res, Some(format!("{}.{}", out, item_name))))
349 match cx.tcx.type_of(did).kind {
351 if let Some(item) = if def.is_enum() {
352 def.all_fields().find(|item| item.ident.name == item_name)
354 def.non_enum_variant()
357 .find(|item| item.ident.name == item_name)
359 if extra_fragment.is_some() {
360 Err(ErrorKind::AnchorFailure(if def.is_enum() {
361 "enum variants cannot be followed by anchors"
363 "struct fields cannot be followed by anchors"
380 self.variant_field(path_str, current_item, module_id)
383 _ => self.variant_field(path_str, current_item, module_id),
387 Res::Def(DefKind::Trait, did) => {
390 .associated_item_def_ids(did)
392 .map(|item| cx.tcx.associated_item(*item))
393 .find(|item| item.ident.name == item_name);
394 if let Some(item) = item {
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" }
402 _ => return self.variant_field(path_str, current_item, module_id),
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"
411 "methods cannot be followed by anchors"
414 Ok((ty_res, Some(format!("{}.{}", kind, item_name))))
417 self.variant_field(path_str, current_item, module_id)
420 _ => self.variant_field(path_str, current_item, module_id),
423 debug!("attempting to resolve item without parent module: {}", path_str);
424 Err(ErrorKind::ResolutionFailure)
429 /// Check for resolve collisions between a trait and its derive
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 {
434 type_ns: Some((Res::Def(DefKind::Trait, _), _)),
435 macro_ns: Some((Res::Def(DefKind::Macro(MacroKind::Derive), _), _)),
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;
449 let parent_node = if item.is_fake() {
450 // FIXME: is this correct?
453 let mut current = item.def_id;
454 // The immediate parent might not always be a module.
455 // Find the first parent which is.
457 if let Some(parent) = self.cx.tcx.parent(current) {
458 if self.cx.tcx.def_kind(parent) == DefKind::Mod {
468 if parent_node.is_some() {
469 trace!("got parent node for {:?} {:?}, id {:?}", item.type_(), item.name, item.def_id);
472 let current_item = match item.inner {
474 if item.attrs.inner_docs {
475 if item.def_id.is_top_level_module() { item.name.clone() } else { None }
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())
486 ImplItem(Impl { ref for_, .. }) => {
487 for_.def_id().map(|did| self.cx.tcx.item_name(did).to_string())
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);
494 ImportItem(Import::Simple(ref name, ..)) => Some(name.clone()),
495 MacroItem(..) => None,
496 _ => item.name.clone(),
499 if item.is_mod() && item.attrs.inner_docs {
500 self.mod_ids.push(item.def_id);
504 let dox = item.attrs.collapsed_doc_value().unwrap_or_else(String::new);
505 trace!("got documentation '{}'", dox);
507 look_for_tests(&cx, &dox, &item, true);
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);
514 Some(hir::Node::Item(hir::Item {
516 hir::ItemKind::Impl {
520 hir::TyKind::Path(hir::QPath::Resolved(
522 hir::Path { segments, .. },
529 })) => segments.first().map(|seg| seg.ident.to_string()),
530 Some(hir::Node::Item(hir::Item {
531 ident, kind: hir::ItemKind::Enum(..), ..
533 | Some(hir::Node::Item(hir::Item {
534 ident, kind: hir::ItemKind::Struct(..), ..
536 | Some(hir::Node::Item(hir::Item {
537 ident, kind: hir::ItemKind::Union(..), ..
539 | Some(hir::Node::Item(hir::Item {
540 ident, kind: hir::ItemKind::Trait(..), ..
541 })) => Some(ident.to_string()),
546 for (ori_link, link_range) in markdown_links(&dox) {
547 trace!("considering link '{}'", ori_link);
549 // Bail early for real links.
550 if ori_link.contains('/') {
554 // [] is mostly likely not supposed to be a link
555 if ori_link.is_empty() {
559 let link = ori_link.replace("`", "");
560 let parts = link.split('#').collect::<Vec<_>>();
561 let (link, extra_fragment) = if parts.len() > 2 {
568 "has an issue with the link anchor.",
569 "only one `#` is allowed in a link",
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!
578 (parts[0].to_owned(), Some(parts[1].to_owned()))
580 (parts[0].to_owned(), None)
584 let (res, fragment) = {
586 let mut disambiguator = None;
587 path_str = if let Some(prefix) = ["struct@", "enum@", "type@", "trait@", "union@"]
589 .find(|p| link.starts_with(**p))
592 disambiguator = Some(&prefix[..prefix.len() - 1]);
593 link.trim_start_matches(prefix)
594 } else if let Some(prefix) = [
605 .find(|p| link.starts_with(**p))
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('!')
634 if path_str.contains(|ch: char| !(ch.is_alphanumeric() || ch == ':' || ch == '_')) {
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()
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;
664 Some(ns @ ValueNS) => {
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.
682 Err(ErrorKind::AnchorFailure(msg)) => {
683 anchor_failure(cx, &item, &ori_link, &dox, link_range, msg);
688 Some(ns @ TypeNS) => {
699 Err(ErrorKind::ResolutionFailure) => {
700 resolution_failure(cx, &item, path_str, &dox, link_range);
701 // This could just be a normal link.
704 Err(ErrorKind::AnchorFailure(msg)) => {
705 anchor_failure(cx, &item, &ori_link, &dox, link_range, msg);
712 let mut candidates = PerNS {
714 .macro_resolve(path_str, base_node)
715 .map(|res| (res, extra_fragment.clone())),
716 type_ns: match self.resolve(
725 Err(ErrorKind::AnchorFailure(msg)) => {
726 anchor_failure(cx, &item, &ori_link, &dox, link_range, msg);
731 value_ns: match self.resolve(
740 Err(ErrorKind::AnchorFailure(msg)) => {
741 anchor_failure(cx, &item, &ori_link, &dox, link_range, msg);
746 .and_then(|(res, fragment)| {
747 // Constructors are picked up in the type namespace.
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)))
755 (fragment, None) | (None, fragment) => {
756 Some((res, fragment))
763 if candidates.is_empty() {
764 resolution_failure(cx, &item, path_str, &dox, link_range);
765 // this could just be a normal link
769 let len = candidates.clone().present_items().count();
772 candidates.present_items().next().unwrap()
773 } else if len == 2 && is_derive_trait_collision(&candidates) {
774 candidates.type_ns.unwrap()
776 if is_derive_trait_collision(&candidates) {
777 candidates.macro_ns = None;
785 candidates.map(|candidate| candidate.map(|(res, _)| res)),
791 if let Some(res) = self.macro_resolve(path_str, base_node) {
792 (res, extra_fragment)
794 resolution_failure(cx, &item, path_str, &dox, link_range);
801 if let Res::PrimTy(_) = res {
802 item.attrs.links.push((ori_link, None, fragment));
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;
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
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",
825 "this item is private",
831 let id = register_res(cx, res);
832 item.attrs.links.push((ori_link, Some(id), fragment));
836 if item.is_mod() && !item.attrs.inner_docs {
837 self.mod_ids.push(item.def_id);
841 let ret = self.fold_item_recur(item);
847 self.fold_item_recur(item)
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));
866 link_range: Option<Range<usize>>,
869 help_msg: Option<&str>,
871 let hir_id = match cx.as_local_hir_id(item.def_id) {
872 Some(hir_id) => hir_id,
874 // If non-local, no need to check anything.
875 info!("ignoring warning from parent crate: {}", err_msg);
879 let attrs = &item.attrs;
880 let sp = span_of_attrs(attrs).unwrap_or(item.source.span());
882 cx.tcx.struct_span_lint_hir(
883 lint::builtin::INTRA_DOC_LINK_RESOLUTION_FAILURE,
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)
892 diag.span_label(sp, short_err_msg);
894 // blah blah blah\nblah\nblah [blah] blah blah\nblah blah
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("");
902 // Print the line containing the `link_range` and manually mark it with '^'s.
904 "the link appears in this line:\n\n{line}\n\
905 {indicator: <before$}{indicator:^<found$}",
908 before = link_range.start - last_new_line_offset,
909 found = link_range.len(),
913 if let Some(help_msg) = help_msg {
921 /// Reports a resolution failure diagnostic.
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(
931 link_range: Option<Range<usize>>,
939 "cannot be resolved, ignoring it.",
940 "cannot be resolved, ignoring",
941 Some("to escape `[` and `]` characters, just add '\\' before them like `\\[` or `\\]`"),
950 link_range: Option<Range<usize>>,
959 "has an issue with the link anchor.",
970 link_range: Option<Range<usize>>,
971 candidates: PerNS<Option<Res>>,
973 let hir_id = match cx.as_local_hir_id(item.def_id) {
974 Some(hir_id) => hir_id,
976 // If non-local, no need to check anything.
980 let attrs = &item.attrs;
981 let sp = span_of_attrs(attrs).unwrap_or(item.source.span());
983 cx.tcx.struct_span_lint_hir(
984 lint::builtin::INTRA_DOC_LINK_RESOLUTION_FAILURE,
988 let mut msg = format!("`{}` is ", path_str);
990 let candidates = [TypeNS, ValueNS, MacroNS]
992 .filter_map(|&ns| candidates[ns].map(|res| (res, ns)))
993 .collect::<Vec<_>>();
994 match candidates.as_slice() {
995 [(first_def, _), (second_def, _)] => {
997 "both {} {} and {} {}",
1000 second_def.article(),
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());
1010 msg += &format!("and {} {}", res.article(), res.descr());
1016 let mut diag = lint.build(&msg);
1018 if let Some(link_range) = link_range {
1019 if let Some(sp) = super::source_span_for_markdown_range(cx, dox, &link_range, attrs)
1022 diag.span_label(sp, "ambiguous link");
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))
1029 Res::Def(DefKind::Macro(MacroKind::Bang), _) => {
1030 ("add an exclamation mark", format!("{}!", path_str))
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) => {
1046 (_, MacroNS) => "macro",
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))
1054 if dox.bytes().nth(link_range.start) == Some(b'`') {
1055 suggestion = format!("`{}`", suggestion);
1058 diag.span_suggestion(
1060 &format!("to link to the {}, {}", res.descr(), action),
1062 Applicability::MaybeIncorrect,
1066 // blah blah blah\nblah\nblah [blah] blah blah\nblah blah
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("");
1074 // Print the line containing the `link_range` and manually mark it with '^'s.
1076 "the link appears in this line:\n\n{line}\n\
1077 {indicator: <before$}{indicator:^<found$}",
1080 before = link_range.start - last_new_line_offset,
1081 found = link_range.len(),
1090 /// Given an enum variant's res, return the res of its enum and the associated fragment.
1092 cx: &DocContext<'_>,
1094 extra_fragment: &Option<String>,
1095 ) -> Result<(Res, Option<String>), ErrorKind> {
1096 use rustc_middle::ty::DefIdTree;
1098 if extra_fragment.is_some() {
1099 return Err(ErrorKind::AnchorFailure("variants cannot be followed by anchors"));
1101 let parent = if let Some(parent) = cx.tcx.parent(res.def_id()) {
1104 return Err(ErrorKind::ResolutionFailure);
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))))
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)),
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 }
1135 fn primitive_impl(cx: &DocContext<'_>, path_str: &str) -> Option<DefId> {
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(),