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) =
588 ["struct@", "enum@", "type@", "trait@", "union@", "module@", "mod@"]
590 .find(|p| link.starts_with(**p))
593 disambiguator = Some(&prefix[..prefix.len() - 1]);
594 link.trim_start_matches(prefix)
595 } else if let Some(prefix) =
596 ["const@", "static@", "value@", "function@", "fn@", "method@"]
598 .find(|p| link.starts_with(**p))
600 kind = Some(ValueNS);
601 disambiguator = Some(&prefix[..prefix.len() - 1]);
602 link.trim_start_matches(prefix)
603 } else if link.ends_with("!()") {
604 kind = Some(MacroNS);
605 link.trim_end_matches("!()")
606 } else if link.ends_with("()") {
607 kind = Some(ValueNS);
608 disambiguator = Some("fn");
609 link.trim_end_matches("()")
610 } else if link.starts_with("macro@") {
611 kind = Some(MacroNS);
612 disambiguator = Some("macro");
613 link.trim_start_matches("macro@")
614 } else if link.starts_with("derive@") {
615 kind = Some(MacroNS);
616 disambiguator = Some("derive");
617 link.trim_start_matches("derive@")
618 } else if link.ends_with('!') {
619 kind = Some(MacroNS);
620 disambiguator = Some("macro");
621 link.trim_end_matches('!')
627 if path_str.contains(|ch: char| !(ch.is_alphanumeric() || ch == ':' || ch == '_')) {
631 // In order to correctly resolve intra-doc-links we need to
632 // pick a base AST node to work from. If the documentation for
633 // this module came from an inner comment (//!) then we anchor
634 // our name resolution *inside* the module. If, on the other
635 // hand it was an outer comment (///) then we anchor the name
636 // resolution in the parent module on the basis that the names
637 // used are more likely to be intended to be parent names. For
638 // this, we set base_node to None for inner comments since
639 // we've already pushed this node onto the resolution stack but
640 // for outer comments we explicitly try and resolve against the
641 // parent_node first.
642 let base_node = if item.is_mod() && item.attrs.inner_docs {
643 self.mod_ids.last().copied()
648 // replace `Self` with suitable item's parent name
649 if path_str.starts_with("Self::") {
650 if let Some(ref name) = parent_name {
651 resolved_self = format!("{}::{}", name, &path_str[6..]);
652 path_str = &resolved_self;
657 Some(ns @ ValueNS) => {
668 Err(ErrorKind::ResolutionFailure) => {
669 resolution_failure(cx, &item, path_str, &dox, link_range);
670 // This could just be a normal link or a broken link
671 // we could potentially check if something is
672 // "intra-doc-link-like" and warn in that case.
675 Err(ErrorKind::AnchorFailure(msg)) => {
676 anchor_failure(cx, &item, &ori_link, &dox, link_range, msg);
681 Some(ns @ TypeNS) => {
692 Err(ErrorKind::ResolutionFailure) => {
693 resolution_failure(cx, &item, path_str, &dox, link_range);
694 // This could just be a normal link.
697 Err(ErrorKind::AnchorFailure(msg)) => {
698 anchor_failure(cx, &item, &ori_link, &dox, link_range, msg);
705 let mut candidates = PerNS {
707 .macro_resolve(path_str, base_node)
708 .map(|res| (res, extra_fragment.clone())),
709 type_ns: match self.resolve(
718 Err(ErrorKind::AnchorFailure(msg)) => {
719 anchor_failure(cx, &item, &ori_link, &dox, link_range, msg);
724 value_ns: match self.resolve(
733 Err(ErrorKind::AnchorFailure(msg)) => {
734 anchor_failure(cx, &item, &ori_link, &dox, link_range, msg);
739 .and_then(|(res, fragment)| {
740 // Constructors are picked up in the type namespace.
742 Res::Def(DefKind::Ctor(..), _) | Res::SelfCtor(..) => None,
743 _ => match (fragment, extra_fragment) {
744 (Some(fragment), Some(_)) => {
745 // Shouldn't happen but who knows?
746 Some((res, Some(fragment)))
748 (fragment, None) | (None, fragment) => {
749 Some((res, fragment))
756 if candidates.is_empty() {
757 resolution_failure(cx, &item, path_str, &dox, link_range);
758 // this could just be a normal link
762 let len = candidates.clone().present_items().count();
765 candidates.present_items().next().unwrap()
766 } else if len == 2 && is_derive_trait_collision(&candidates) {
767 candidates.type_ns.unwrap()
769 if is_derive_trait_collision(&candidates) {
770 candidates.macro_ns = None;
778 candidates.map(|candidate| candidate.map(|(res, _)| res)),
784 if let Some(res) = self.macro_resolve(path_str, base_node) {
785 (res, extra_fragment)
787 resolution_failure(cx, &item, path_str, &dox, link_range);
794 if let Res::PrimTy(_) = res {
795 item.attrs.links.push((ori_link, None, fragment));
797 debug!("intra-doc link to {} resolved to {:?}", path_str, res);
798 if let Some(local) = res.opt_def_id().and_then(|def_id| def_id.as_local()) {
799 use rustc_hir::def_id::LOCAL_CRATE;
801 let hir_id = self.cx.tcx.hir().as_local_hir_id(local);
802 if !self.cx.tcx.privacy_access_levels(LOCAL_CRATE).is_exported(hir_id)
803 && (item.visibility == Visibility::Public)
804 && !self.cx.render_options.document_private
806 let item_name = item.name.as_deref().unwrap_or("<unknown>");
807 let err_msg = format!(
808 "public documentation for `{}` links to a private item",
818 "this item is private",
824 let id = register_res(cx, res);
825 item.attrs.links.push((ori_link, Some(id), fragment));
829 if item.is_mod() && !item.attrs.inner_docs {
830 self.mod_ids.push(item.def_id);
834 let ret = self.fold_item_recur(item);
840 self.fold_item_recur(item)
844 // FIXME: if we can resolve intra-doc links from other crates, we can use the stock
845 // `fold_crate`, but until then we should avoid scanning `krate.external_traits` since those
846 // will never resolve properly
847 fn fold_crate(&mut self, mut c: Crate) -> Crate {
848 c.module = c.module.take().and_then(|module| self.fold_item(module));
859 link_range: Option<Range<usize>>,
862 help_msg: Option<&str>,
864 let hir_id = match cx.as_local_hir_id(item.def_id) {
865 Some(hir_id) => hir_id,
867 // If non-local, no need to check anything.
868 info!("ignoring warning from parent crate: {}", err_msg);
872 let attrs = &item.attrs;
873 let sp = span_of_attrs(attrs).unwrap_or(item.source.span());
875 cx.tcx.struct_span_lint_hir(
876 lint::builtin::INTRA_DOC_LINK_RESOLUTION_FAILURE,
880 let mut diag = lint.build(&format!("`[{}]` {}", path_str, err_msg));
881 if let Some(link_range) = link_range {
882 if let Some(sp) = super::source_span_for_markdown_range(cx, dox, &link_range, attrs)
885 diag.span_label(sp, short_err_msg);
887 // blah blah blah\nblah\nblah [blah] blah blah\nblah blah
890 // last_new_line_offset
891 let last_new_line_offset =
892 dox[..link_range.start].rfind('\n').map_or(0, |n| n + 1);
893 let line = dox[last_new_line_offset..].lines().next().unwrap_or("");
895 // Print the line containing the `link_range` and manually mark it with '^'s.
897 "the link appears in this line:\n\n{line}\n\
898 {indicator: <before$}{indicator:^<found$}",
901 before = link_range.start - last_new_line_offset,
902 found = link_range.len(),
906 if let Some(help_msg) = help_msg {
914 /// Reports a resolution failure diagnostic.
916 /// If we cannot find the exact source span of the resolution failure, we use the span of the
917 /// documentation attributes themselves. This is a little heavy-handed, so we display the markdown
918 /// line containing the failure as a note as well.
919 fn resolution_failure(
924 link_range: Option<Range<usize>>,
932 "cannot be resolved, ignoring it.",
933 "cannot be resolved, ignoring",
934 Some("to escape `[` and `]` characters, just add '\\' before them like `\\[` or `\\]`"),
943 link_range: Option<Range<usize>>,
952 "has an issue with the link anchor.",
963 link_range: Option<Range<usize>>,
964 candidates: PerNS<Option<Res>>,
966 let hir_id = match cx.as_local_hir_id(item.def_id) {
967 Some(hir_id) => hir_id,
969 // If non-local, no need to check anything.
973 let attrs = &item.attrs;
974 let sp = span_of_attrs(attrs).unwrap_or(item.source.span());
976 cx.tcx.struct_span_lint_hir(
977 lint::builtin::INTRA_DOC_LINK_RESOLUTION_FAILURE,
981 let mut msg = format!("`{}` is ", path_str);
983 let candidates = [TypeNS, ValueNS, MacroNS]
985 .filter_map(|&ns| candidates[ns].map(|res| (res, ns)))
986 .collect::<Vec<_>>();
987 match candidates.as_slice() {
988 [(first_def, _), (second_def, _)] => {
990 "both {} {} and {} {}",
993 second_def.article(),
998 let mut candidates = candidates.iter().peekable();
999 while let Some((res, _)) = candidates.next() {
1000 if candidates.peek().is_some() {
1001 msg += &format!("{} {}, ", res.article(), res.descr());
1003 msg += &format!("and {} {}", res.article(), res.descr());
1009 let mut diag = lint.build(&msg);
1011 if let Some(link_range) = link_range {
1012 if let Some(sp) = super::source_span_for_markdown_range(cx, dox, &link_range, attrs)
1015 diag.span_label(sp, "ambiguous link");
1017 for (res, ns) in candidates {
1018 let (action, mut suggestion) = match res {
1019 Res::Def(DefKind::AssocFn | DefKind::Fn, _) => {
1020 ("add parentheses", format!("{}()", path_str))
1022 Res::Def(DefKind::Macro(MacroKind::Bang), _) => {
1023 ("add an exclamation mark", format!("{}!", path_str))
1026 let type_ = match (res, ns) {
1027 (Res::Def(DefKind::Const, _), _) => "const",
1028 (Res::Def(DefKind::Static, _), _) => "static",
1029 (Res::Def(DefKind::Struct, _), _) => "struct",
1030 (Res::Def(DefKind::Enum, _), _) => "enum",
1031 (Res::Def(DefKind::Union, _), _) => "union",
1032 (Res::Def(DefKind::Trait, _), _) => "trait",
1033 (Res::Def(DefKind::Mod, _), _) => "module",
1034 (_, TypeNS) => "type",
1035 (_, ValueNS) => "value",
1036 (Res::Def(DefKind::Macro(MacroKind::Derive), _), MacroNS) => {
1039 (_, MacroNS) => "macro",
1042 // FIXME: if this is an implied shortcut link, it's bad style to suggest `@`
1043 ("prefix with the item type", format!("{}@{}", type_, path_str))
1047 if dox.bytes().nth(link_range.start) == Some(b'`') {
1048 suggestion = format!("`{}`", suggestion);
1051 diag.span_suggestion(
1053 &format!("to link to the {}, {}", res.descr(), action),
1055 Applicability::MaybeIncorrect,
1059 // blah blah blah\nblah\nblah [blah] blah blah\nblah blah
1062 // last_new_line_offset
1063 let last_new_line_offset =
1064 dox[..link_range.start].rfind('\n').map_or(0, |n| n + 1);
1065 let line = dox[last_new_line_offset..].lines().next().unwrap_or("");
1067 // Print the line containing the `link_range` and manually mark it with '^'s.
1069 "the link appears in this line:\n\n{line}\n\
1070 {indicator: <before$}{indicator:^<found$}",
1073 before = link_range.start - last_new_line_offset,
1074 found = link_range.len(),
1083 /// Given an enum variant's res, return the res of its enum and the associated fragment.
1085 cx: &DocContext<'_>,
1087 extra_fragment: &Option<String>,
1088 ) -> Result<(Res, Option<String>), ErrorKind> {
1089 use rustc_middle::ty::DefIdTree;
1091 if extra_fragment.is_some() {
1092 return Err(ErrorKind::AnchorFailure("variants cannot be followed by anchors"));
1094 let parent = if let Some(parent) = cx.tcx.parent(res.def_id()) {
1097 return Err(ErrorKind::ResolutionFailure);
1099 let parent_def = Res::Def(DefKind::Enum, parent);
1100 let variant = cx.tcx.expect_variant_res(res);
1101 Ok((parent_def, Some(format!("variant.{}", variant.ident.name))))
1104 const PRIMITIVES: &[(&str, Res)] = &[
1105 ("u8", Res::PrimTy(hir::PrimTy::Uint(rustc_ast::ast::UintTy::U8))),
1106 ("u16", Res::PrimTy(hir::PrimTy::Uint(rustc_ast::ast::UintTy::U16))),
1107 ("u32", Res::PrimTy(hir::PrimTy::Uint(rustc_ast::ast::UintTy::U32))),
1108 ("u64", Res::PrimTy(hir::PrimTy::Uint(rustc_ast::ast::UintTy::U64))),
1109 ("u128", Res::PrimTy(hir::PrimTy::Uint(rustc_ast::ast::UintTy::U128))),
1110 ("usize", Res::PrimTy(hir::PrimTy::Uint(rustc_ast::ast::UintTy::Usize))),
1111 ("i8", Res::PrimTy(hir::PrimTy::Int(rustc_ast::ast::IntTy::I8))),
1112 ("i16", Res::PrimTy(hir::PrimTy::Int(rustc_ast::ast::IntTy::I16))),
1113 ("i32", Res::PrimTy(hir::PrimTy::Int(rustc_ast::ast::IntTy::I32))),
1114 ("i64", Res::PrimTy(hir::PrimTy::Int(rustc_ast::ast::IntTy::I64))),
1115 ("i128", Res::PrimTy(hir::PrimTy::Int(rustc_ast::ast::IntTy::I128))),
1116 ("isize", Res::PrimTy(hir::PrimTy::Int(rustc_ast::ast::IntTy::Isize))),
1117 ("f32", Res::PrimTy(hir::PrimTy::Float(rustc_ast::ast::FloatTy::F32))),
1118 ("f64", Res::PrimTy(hir::PrimTy::Float(rustc_ast::ast::FloatTy::F64))),
1119 ("str", Res::PrimTy(hir::PrimTy::Str)),
1120 ("bool", Res::PrimTy(hir::PrimTy::Bool)),
1121 ("char", Res::PrimTy(hir::PrimTy::Char)),
1124 fn is_primitive(path_str: &str, ns: Namespace) -> Option<Res> {
1125 if ns == TypeNS { PRIMITIVES.iter().find(|x| x.0 == path_str).map(|x| x.1) } else { None }
1128 fn primitive_impl(cx: &DocContext<'_>, path_str: &str) -> Option<DefId> {
1131 "u8" => tcx.lang_items().u8_impl(),
1132 "u16" => tcx.lang_items().u16_impl(),
1133 "u32" => tcx.lang_items().u32_impl(),
1134 "u64" => tcx.lang_items().u64_impl(),
1135 "u128" => tcx.lang_items().u128_impl(),
1136 "usize" => tcx.lang_items().usize_impl(),
1137 "i8" => tcx.lang_items().i8_impl(),
1138 "i16" => tcx.lang_items().i16_impl(),
1139 "i32" => tcx.lang_items().i32_impl(),
1140 "i64" => tcx.lang_items().i64_impl(),
1141 "i128" => tcx.lang_items().i128_impl(),
1142 "isize" => tcx.lang_items().isize_impl(),
1143 "f32" => tcx.lang_items().f32_impl(),
1144 "f64" => tcx.lang_items().f64_impl(),
1145 "str" => tcx.lang_items().str_impl(),
1146 "bool" => tcx.lang_items().bool_impl(),
1147 "char" => tcx.lang_items().char_impl(),