| use rustc_ast::ast; |
| use rustc_errors::Applicability; |
| use rustc_expand::base::SyntaxExtensionKind; |
| use rustc_feature::UnstableFeatures; |
| use rustc_hir as hir; |
| use rustc_hir::def::{ |
| DefKind, |
| Namespace::{self, *}, |
| PerNS, Res, |
| }; |
| use rustc_hir::def_id::{DefId, LocalDefId}; |
| use rustc_middle::ty; |
| use rustc_resolve::ParentScope; |
| use rustc_session::lint; |
| use rustc_span::hygiene::MacroKind; |
| use rustc_span::symbol::Ident; |
| use rustc_span::symbol::Symbol; |
| use rustc_span::DUMMY_SP; |
| |
| use std::ops::Range; |
| |
| use crate::clean::*; |
| use crate::core::DocContext; |
| use crate::fold::DocFolder; |
| use crate::html::markdown::markdown_links; |
| use crate::passes::{look_for_tests, Pass}; |
| |
| use super::span_of_attrs; |
| |
| pub const COLLECT_INTRA_DOC_LINKS: Pass = Pass { |
| name: "collect-intra-doc-links", |
| run: collect_intra_doc_links, |
| description: "reads a crate's documentation to resolve intra-doc-links", |
| }; |
| |
| pub fn collect_intra_doc_links(krate: Crate, cx: &DocContext<'_>) -> Crate { |
| if !UnstableFeatures::from_environment().is_nightly_build() { |
| krate |
| } else { |
| let mut coll = LinkCollector::new(cx); |
| |
| coll.fold_crate(krate) |
| } |
| } |
| |
| enum ErrorKind { |
| ResolutionFailure, |
| AnchorFailure(&'static str), |
| } |
| |
| struct LinkCollector<'a, 'tcx> { |
| cx: &'a DocContext<'tcx>, |
| mod_ids: Vec<hir::HirId>, |
| } |
| |
| impl<'a, 'tcx> LinkCollector<'a, 'tcx> { |
| fn new(cx: &'a DocContext<'tcx>) -> Self { |
| LinkCollector { cx, mod_ids: Vec::new() } |
| } |
| |
| fn variant_field( |
| &self, |
| path_str: &str, |
| current_item: &Option<String>, |
| module_id: LocalDefId, |
| ) -> Result<(Res, Option<String>), ErrorKind> { |
| let cx = self.cx; |
| |
| let mut split = path_str.rsplitn(3, "::"); |
| let variant_field_name = |
| split.next().map(|f| Symbol::intern(f)).ok_or(ErrorKind::ResolutionFailure)?; |
| let variant_name = |
| split.next().map(|f| Symbol::intern(f)).ok_or(ErrorKind::ResolutionFailure)?; |
| let path = split |
| .next() |
| .map(|f| { |
| if f == "self" || f == "Self" { |
| if let Some(name) = current_item.as_ref() { |
| return name.clone(); |
| } |
| } |
| f.to_owned() |
| }) |
| .ok_or(ErrorKind::ResolutionFailure)?; |
| let (_, ty_res) = cx |
| .enter_resolver(|resolver| { |
| resolver.resolve_str_path_error(DUMMY_SP, &path, TypeNS, module_id) |
| }) |
| .map_err(|_| ErrorKind::ResolutionFailure)?; |
| if let Res::Err = ty_res { |
| return Err(ErrorKind::ResolutionFailure); |
| } |
| let ty_res = ty_res.map_id(|_| panic!("unexpected node_id")); |
| match ty_res { |
| Res::Def(DefKind::Enum, did) => { |
| if cx |
| .tcx |
| .inherent_impls(did) |
| .iter() |
| .flat_map(|imp| cx.tcx.associated_items(*imp).in_definition_order()) |
| .any(|item| item.ident.name == variant_name) |
| { |
| return Err(ErrorKind::ResolutionFailure); |
| } |
| match cx.tcx.type_of(did).kind { |
| ty::Adt(def, _) if def.is_enum() => { |
| if def.all_fields().any(|item| item.ident.name == variant_field_name) { |
| Ok(( |
| ty_res, |
| Some(format!( |
| "variant.{}.field.{}", |
| variant_name, variant_field_name |
| )), |
| )) |
| } else { |
| Err(ErrorKind::ResolutionFailure) |
| } |
| } |
| _ => Err(ErrorKind::ResolutionFailure), |
| } |
| } |
| _ => Err(ErrorKind::ResolutionFailure), |
| } |
| } |
| |
| /// Resolves a string as a macro. |
| fn macro_resolve(&self, path_str: &str, parent_id: Option<hir::HirId>) -> Option<Res> { |
| let cx = self.cx; |
| let path = ast::Path::from_ident(Ident::from_str(path_str)); |
| cx.enter_resolver(|resolver| { |
| if let Ok((Some(ext), res)) = resolver.resolve_macro_path( |
| &path, |
| None, |
| &ParentScope::module(resolver.graph_root()), |
| false, |
| false, |
| ) { |
| if let SyntaxExtensionKind::LegacyBang { .. } = ext.kind { |
| return Some(res.map_id(|_| panic!("unexpected id"))); |
| } |
| } |
| if let Some(res) = resolver.all_macros().get(&Symbol::intern(path_str)) { |
| return Some(res.map_id(|_| panic!("unexpected id"))); |
| } |
| if let Some(module_id) = parent_id.or(self.mod_ids.last().cloned()) { |
| let module_id = cx.tcx.hir().local_def_id(module_id); |
| if let Ok((_, res)) = |
| resolver.resolve_str_path_error(DUMMY_SP, path_str, MacroNS, module_id) |
| { |
| // don't resolve builtins like `#[derive]` |
| if let Res::Def(..) = res { |
| let res = res.map_id(|_| panic!("unexpected node_id")); |
| return Some(res); |
| } |
| } |
| } else { |
| debug!("attempting to resolve item without parent module: {}", path_str); |
| } |
| None |
| }) |
| } |
| /// Resolves a string as a path within a particular namespace. Also returns an optional |
| /// URL fragment in the case of variants and methods. |
| fn resolve( |
| &self, |
| path_str: &str, |
| disambiguator: Option<&str>, |
| ns: Namespace, |
| current_item: &Option<String>, |
| parent_id: Option<hir::HirId>, |
| extra_fragment: &Option<String>, |
| item_opt: Option<&Item>, |
| ) -> Result<(Res, Option<String>), ErrorKind> { |
| let cx = self.cx; |
| |
| // In case we're in a module, try to resolve the relative path. |
| if let Some(module_id) = parent_id.or(self.mod_ids.last().cloned()) { |
| let module_id = cx.tcx.hir().local_def_id(module_id); |
| let result = cx.enter_resolver(|resolver| { |
| resolver.resolve_str_path_error(DUMMY_SP, &path_str, ns, module_id) |
| }); |
| debug!("{} resolved to {:?} in namespace {:?}", path_str, result, ns); |
| let result = match result { |
| Ok((_, Res::Err)) => Err(ErrorKind::ResolutionFailure), |
| _ => result.map_err(|_| ErrorKind::ResolutionFailure), |
| }; |
| |
| if let Ok((_, res)) = result { |
| let res = res.map_id(|_| panic!("unexpected node_id")); |
| // In case this is a trait item, skip the |
| // early return and try looking for the trait. |
| let value = match res { |
| Res::Def(DefKind::AssocFn | DefKind::AssocConst, _) => true, |
| Res::Def(DefKind::AssocTy, _) => false, |
| Res::Def(DefKind::Variant, _) => { |
| return handle_variant(cx, res, extra_fragment); |
| } |
| // Not a trait item; just return what we found. |
| Res::PrimTy(..) => { |
| if extra_fragment.is_some() { |
| return Err(ErrorKind::AnchorFailure( |
| "primitive types cannot be followed by anchors", |
| )); |
| } |
| return Ok((res, Some(path_str.to_owned()))); |
| } |
| Res::Def(DefKind::Mod, _) => { |
| // This resolved to a module, but if we were passed `type@`, |
| // we want primitive types to take precedence instead. |
| if disambiguator == Some("type") { |
| if let Some(prim) = is_primitive(path_str, ns) { |
| if extra_fragment.is_some() { |
| return Err(ErrorKind::AnchorFailure( |
| "primitive types cannot be followed by anchors", |
| )); |
| } |
| return Ok((prim, Some(path_str.to_owned()))); |
| } |
| } |
| return Ok((res, extra_fragment.clone())); |
| } |
| _ => { |
| return Ok((res, extra_fragment.clone())); |
| } |
| }; |
| |
| if value != (ns == ValueNS) { |
| return Err(ErrorKind::ResolutionFailure); |
| } |
| } else if let Some(prim) = is_primitive(path_str, ns) { |
| if extra_fragment.is_some() { |
| return Err(ErrorKind::AnchorFailure( |
| "primitive types cannot be followed by anchors", |
| )); |
| } |
| return Ok((prim, Some(path_str.to_owned()))); |
| } else { |
| // If resolution failed, it may still be a method |
| // because methods are not handled by the resolver |
| // If so, bail when we're not looking for a value. |
| if ns != ValueNS { |
| return Err(ErrorKind::ResolutionFailure); |
| } |
| } |
| |
| // Try looking for methods and associated items. |
| let mut split = path_str.rsplitn(2, "::"); |
| let item_name = |
| split.next().map(|f| Symbol::intern(f)).ok_or(ErrorKind::ResolutionFailure)?; |
| let path = split |
| .next() |
| .map(|f| { |
| if f == "self" || f == "Self" { |
| if let Some(name) = current_item.as_ref() { |
| return name.clone(); |
| } |
| } |
| f.to_owned() |
| }) |
| .ok_or(ErrorKind::ResolutionFailure)?; |
| |
| if let Some(prim) = is_primitive(&path, TypeNS) { |
| let did = primitive_impl(cx, &path).ok_or(ErrorKind::ResolutionFailure)?; |
| return cx |
| .tcx |
| .associated_items(did) |
| .filter_by_name_unhygienic(item_name) |
| .next() |
| .and_then(|item| match item.kind { |
| ty::AssocKind::Fn => Some("method"), |
| _ => None, |
| }) |
| .map(|out| (prim, Some(format!("{}#{}.{}", path, out, item_name)))) |
| .ok_or(ErrorKind::ResolutionFailure); |
| } |
| |
| let (_, ty_res) = cx |
| .enter_resolver(|resolver| { |
| resolver.resolve_str_path_error(DUMMY_SP, &path, TypeNS, module_id) |
| }) |
| .map_err(|_| ErrorKind::ResolutionFailure)?; |
| if let Res::Err = ty_res { |
| return self.variant_field(path_str, current_item, module_id); |
| } |
| let ty_res = ty_res.map_id(|_| panic!("unexpected node_id")); |
| match ty_res { |
| Res::Def( |
| DefKind::Struct | DefKind::Union | DefKind::Enum | DefKind::TyAlias, |
| did, |
| ) => { |
| // Checks if item_name belongs to `impl SomeItem` |
| let impl_item = cx |
| .tcx |
| .inherent_impls(did) |
| .iter() |
| .flat_map(|imp| cx.tcx.associated_items(*imp).in_definition_order()) |
| .find(|item| item.ident.name == item_name); |
| let trait_item = item_opt |
| .and_then(|item| self.cx.as_local_hir_id(item.def_id)) |
| .and_then(|item_hir| { |
| // Checks if item_name belongs to `impl SomeTrait for SomeItem` |
| let parent_hir = self.cx.tcx.hir().get_parent_item(item_hir); |
| let item_parent = self.cx.tcx.hir().find(parent_hir); |
| match item_parent { |
| Some(hir::Node::Item(hir::Item { |
| kind: hir::ItemKind::Impl { of_trait: Some(_), self_ty, .. }, |
| .. |
| })) => cx |
| .tcx |
| .associated_item_def_ids(self_ty.hir_id.owner) |
| .iter() |
| .map(|child| { |
| let associated_item = cx.tcx.associated_item(*child); |
| associated_item |
| }) |
| .find(|child| child.ident.name == item_name), |
| _ => None, |
| } |
| }); |
| let item = match (impl_item, trait_item) { |
| (Some(from_impl), Some(_)) => { |
| // Although it's ambiguous, return impl version for compat. sake. |
| // To handle that properly resolve() would have to support |
| // something like |
| // [`ambi_fn`](<SomeStruct as SomeTrait>::ambi_fn) |
| Some(from_impl) |
| } |
| (None, Some(from_trait)) => Some(from_trait), |
| (Some(from_impl), None) => Some(from_impl), |
| _ => None, |
| }; |
| |
| if let Some(item) = item { |
| let out = match item.kind { |
| ty::AssocKind::Fn if ns == ValueNS => "method", |
| ty::AssocKind::Const if ns == ValueNS => "associatedconstant", |
| ty::AssocKind::Type if ns == ValueNS => "associatedtype", |
| _ => return self.variant_field(path_str, current_item, module_id), |
| }; |
| if extra_fragment.is_some() { |
| Err(ErrorKind::AnchorFailure(if item.kind == ty::AssocKind::Fn { |
| "methods cannot be followed by anchors" |
| } else { |
| "associated constants cannot be followed by anchors" |
| })) |
| } else { |
| Ok((ty_res, Some(format!("{}.{}", out, item_name)))) |
| } |
| } else { |
| match cx.tcx.type_of(did).kind { |
| ty::Adt(def, _) => { |
| if let Some(item) = if def.is_enum() { |
| def.all_fields().find(|item| item.ident.name == item_name) |
| } else { |
| def.non_enum_variant() |
| .fields |
| .iter() |
| .find(|item| item.ident.name == item_name) |
| } { |
| if extra_fragment.is_some() { |
| Err(ErrorKind::AnchorFailure(if def.is_enum() { |
| "enum variants cannot be followed by anchors" |
| } else { |
| "struct fields cannot be followed by anchors" |
| })) |
| } else { |
| Ok(( |
| ty_res, |
| Some(format!( |
| "{}.{}", |
| if def.is_enum() { |
| "variant" |
| } else { |
| "structfield" |
| }, |
| item.ident |
| )), |
| )) |
| } |
| } else { |
| self.variant_field(path_str, current_item, module_id) |
| } |
| } |
| _ => self.variant_field(path_str, current_item, module_id), |
| } |
| } |
| } |
| Res::Def(DefKind::Trait, did) => { |
| let item = cx |
| .tcx |
| .associated_item_def_ids(did) |
| .iter() |
| .map(|item| cx.tcx.associated_item(*item)) |
| .find(|item| item.ident.name == item_name); |
| if let Some(item) = item { |
| let kind = |
| match item.kind { |
| ty::AssocKind::Const if ns == ValueNS => "associatedconstant", |
| ty::AssocKind::Type if ns == TypeNS => "associatedtype", |
| ty::AssocKind::Fn if ns == ValueNS => { |
| if item.defaultness.has_value() { "method" } else { "tymethod" } |
| } |
| _ => return self.variant_field(path_str, current_item, module_id), |
| }; |
| |
| if extra_fragment.is_some() { |
| Err(ErrorKind::AnchorFailure(if item.kind == ty::AssocKind::Const { |
| "associated constants cannot be followed by anchors" |
| } else if item.kind == ty::AssocKind::Type { |
| "associated types cannot be followed by anchors" |
| } else { |
| "methods cannot be followed by anchors" |
| })) |
| } else { |
| Ok((ty_res, Some(format!("{}.{}", kind, item_name)))) |
| } |
| } else { |
| self.variant_field(path_str, current_item, module_id) |
| } |
| } |
| _ => self.variant_field(path_str, current_item, module_id), |
| } |
| } else { |
| debug!("attempting to resolve item without parent module: {}", path_str); |
| Err(ErrorKind::ResolutionFailure) |
| } |
| } |
| } |
| |
| /// Check for resolve collisions between a trait and its derive |
| /// |
| /// These are common and we should just resolve to the trait in that case |
| fn is_derive_trait_collision<T>(ns: &PerNS<Option<(Res, T)>>) -> bool { |
| if let PerNS { |
| type_ns: Some((Res::Def(DefKind::Trait, _), _)), |
| macro_ns: Some((Res::Def(DefKind::Macro(MacroKind::Derive), _), _)), |
| .. |
| } = *ns |
| { |
| true |
| } else { |
| false |
| } |
| } |
| |
| impl<'a, 'tcx> DocFolder for LinkCollector<'a, 'tcx> { |
| fn fold_item(&mut self, mut item: Item) -> Option<Item> { |
| let item_hir_id = if item.is_mod() { |
| if let Some(def_id) = item.def_id.as_local() { |
| Some(self.cx.tcx.hir().as_local_hir_id(def_id)) |
| } else { |
| debug!("attempting to fold on a non-local item: {:?}", item); |
| return self.fold_item_recur(item); |
| } |
| } else { |
| None |
| }; |
| |
| // FIXME: get the resolver to work with non-local resolve scopes. |
| let parent_node = self.cx.as_local_hir_id(item.def_id).and_then(|hir_id| { |
| // FIXME: this fails hard for impls in non-module scope, but is necessary for the |
| // current `resolve()` implementation. |
| match self.cx.as_local_hir_id(self.cx.tcx.parent_module(hir_id).to_def_id()).unwrap() { |
| id if id != hir_id => Some(id), |
| _ => None, |
| } |
| }); |
| |
| if parent_node.is_some() { |
| debug!("got parent node for {:?} {:?}, id {:?}", item.type_(), item.name, item.def_id); |
| } |
| |
| let current_item = match item.inner { |
| ModuleItem(..) => { |
| if item.attrs.inner_docs { |
| if item_hir_id.unwrap() != hir::CRATE_HIR_ID { item.name.clone() } else { None } |
| } else { |
| match parent_node.or(self.mod_ids.last().cloned()) { |
| Some(parent) if parent != hir::CRATE_HIR_ID => { |
| // FIXME: can we pull the parent module's name from elsewhere? |
| Some(self.cx.tcx.hir().name(parent).to_string()) |
| } |
| _ => None, |
| } |
| } |
| } |
| ImplItem(Impl { ref for_, .. }) => { |
| for_.def_id().map(|did| self.cx.tcx.item_name(did).to_string()) |
| } |
| // we don't display docs on `extern crate` items anyway, so don't process them. |
| ExternCrateItem(..) => return self.fold_item_recur(item), |
| ImportItem(Import::Simple(ref name, ..)) => Some(name.clone()), |
| MacroItem(..) => None, |
| _ => item.name.clone(), |
| }; |
| |
| if item.is_mod() && item.attrs.inner_docs { |
| self.mod_ids.push(item_hir_id.unwrap()); |
| } |
| |
| let cx = self.cx; |
| let dox = item.attrs.collapsed_doc_value().unwrap_or_else(String::new); |
| |
| look_for_tests(&cx, &dox, &item, true); |
| |
| // find item's parent to resolve `Self` in item's docs below |
| let parent_name = self.cx.as_local_hir_id(item.def_id).and_then(|item_hir| { |
| let parent_hir = self.cx.tcx.hir().get_parent_item(item_hir); |
| let item_parent = self.cx.tcx.hir().find(parent_hir); |
| match item_parent { |
| Some(hir::Node::Item(hir::Item { |
| kind: |
| hir::ItemKind::Impl { |
| self_ty: |
| hir::Ty { |
| kind: |
| hir::TyKind::Path(hir::QPath::Resolved( |
| _, |
| hir::Path { segments, .. }, |
| )), |
| .. |
| }, |
| .. |
| }, |
| .. |
| })) => segments.first().map(|seg| seg.ident.to_string()), |
| Some(hir::Node::Item(hir::Item { |
| ident, kind: hir::ItemKind::Enum(..), .. |
| })) |
| | Some(hir::Node::Item(hir::Item { |
| ident, kind: hir::ItemKind::Struct(..), .. |
| })) |
| | Some(hir::Node::Item(hir::Item { |
| ident, kind: hir::ItemKind::Union(..), .. |
| })) |
| | Some(hir::Node::Item(hir::Item { |
| ident, kind: hir::ItemKind::Trait(..), .. |
| })) => Some(ident.to_string()), |
| _ => None, |
| } |
| }); |
| |
| for (ori_link, link_range) in markdown_links(&dox) { |
| // Bail early for real links. |
| if ori_link.contains('/') { |
| continue; |
| } |
| |
| // [] is mostly likely not supposed to be a link |
| if ori_link.is_empty() { |
| continue; |
| } |
| |
| let link = ori_link.replace("`", ""); |
| let parts = link.split('#').collect::<Vec<_>>(); |
| let (link, extra_fragment) = if parts.len() > 2 { |
| build_diagnostic( |
| cx, |
| &item, |
| &link, |
| &dox, |
| link_range, |
| "has an issue with the link anchor.", |
| "only one `#` is allowed in a link", |
| None, |
| ); |
| continue; |
| } else if parts.len() == 2 { |
| if parts[0].trim().is_empty() { |
| // This is an anchor to an element of the current page, nothing to do in here! |
| continue; |
| } |
| (parts[0].to_owned(), Some(parts[1].to_owned())) |
| } else { |
| (parts[0].to_owned(), None) |
| }; |
| let resolved_self; |
| let mut path_str; |
| let (res, fragment) = { |
| let mut kind = None; |
| let mut disambiguator = None; |
| path_str = if let Some(prefix) = ["struct@", "enum@", "type@", "trait@", "union@"] |
| .iter() |
| .find(|p| link.starts_with(**p)) |
| { |
| kind = Some(TypeNS); |
| disambiguator = Some(&prefix[..prefix.len() - 1]); |
| link.trim_start_matches(prefix) |
| } else if let Some(prefix) = [ |
| "const@", |
| "static@", |
| "value@", |
| "function@", |
| "mod@", |
| "fn@", |
| "module@", |
| "method@", |
| ] |
| .iter() |
| .find(|p| link.starts_with(**p)) |
| { |
| kind = Some(ValueNS); |
| disambiguator = Some(&prefix[..prefix.len() - 1]); |
| link.trim_start_matches(prefix) |
| } else if link.ends_with("!()") { |
| kind = Some(MacroNS); |
| link.trim_end_matches("!()") |
| } else if link.ends_with("()") { |
| kind = Some(ValueNS); |
| disambiguator = Some("fn"); |
| link.trim_end_matches("()") |
| } else if link.starts_with("macro@") { |
| kind = Some(MacroNS); |
| disambiguator = Some("macro"); |
| link.trim_start_matches("macro@") |
| } else if link.starts_with("derive@") { |
| kind = Some(MacroNS); |
| disambiguator = Some("derive"); |
| link.trim_start_matches("derive@") |
| } else if link.ends_with('!') { |
| kind = Some(MacroNS); |
| disambiguator = Some("macro"); |
| link.trim_end_matches('!') |
| } else { |
| &link[..] |
| } |
| .trim(); |
| |
| if path_str.contains(|ch: char| !(ch.is_alphanumeric() || ch == ':' || ch == '_')) { |
| continue; |
| } |
| |
| // In order to correctly resolve intra-doc-links we need to |
| // pick a base AST node to work from. If the documentation for |
| // this module came from an inner comment (//!) then we anchor |
| // our name resolution *inside* the module. If, on the other |
| // hand it was an outer comment (///) then we anchor the name |
| // resolution in the parent module on the basis that the names |
| // used are more likely to be intended to be parent names. For |
| // this, we set base_node to None for inner comments since |
| // we've already pushed this node onto the resolution stack but |
| // for outer comments we explicitly try and resolve against the |
| // parent_node first. |
| let base_node = |
| if item.is_mod() && item.attrs.inner_docs { None } else { parent_node }; |
| |
| // replace `Self` with suitable item's parent name |
| if path_str.starts_with("Self::") { |
| if let Some(ref name) = parent_name { |
| resolved_self = format!("{}::{}", name, &path_str[6..]); |
| path_str = &resolved_self; |
| } |
| } |
| |
| match kind { |
| Some(ns @ ValueNS) => { |
| match self.resolve( |
| path_str, |
| disambiguator, |
| ns, |
| ¤t_item, |
| base_node, |
| &extra_fragment, |
| Some(&item), |
| ) { |
| Ok(res) => res, |
| Err(ErrorKind::ResolutionFailure) => { |
| resolution_failure(cx, &item, path_str, &dox, link_range); |
| // This could just be a normal link or a broken link |
| // we could potentially check if something is |
| // "intra-doc-link-like" and warn in that case. |
| continue; |
| } |
| Err(ErrorKind::AnchorFailure(msg)) => { |
| anchor_failure(cx, &item, &ori_link, &dox, link_range, msg); |
| continue; |
| } |
| } |
| } |
| Some(ns @ TypeNS) => { |
| match self.resolve( |
| path_str, |
| disambiguator, |
| ns, |
| ¤t_item, |
| base_node, |
| &extra_fragment, |
| Some(&item), |
| ) { |
| Ok(res) => res, |
| Err(ErrorKind::ResolutionFailure) => { |
| resolution_failure(cx, &item, path_str, &dox, link_range); |
| // This could just be a normal link. |
| continue; |
| } |
| Err(ErrorKind::AnchorFailure(msg)) => { |
| anchor_failure(cx, &item, &ori_link, &dox, link_range, msg); |
| continue; |
| } |
| } |
| } |
| None => { |
| // Try everything! |
| let mut candidates = PerNS { |
| macro_ns: self |
| .macro_resolve(path_str, base_node) |
| .map(|res| (res, extra_fragment.clone())), |
| type_ns: match self.resolve( |
| path_str, |
| disambiguator, |
| TypeNS, |
| ¤t_item, |
| base_node, |
| &extra_fragment, |
| Some(&item), |
| ) { |
| Err(ErrorKind::AnchorFailure(msg)) => { |
| anchor_failure(cx, &item, &ori_link, &dox, link_range, msg); |
| continue; |
| } |
| x => x.ok(), |
| }, |
| value_ns: match self.resolve( |
| path_str, |
| disambiguator, |
| ValueNS, |
| ¤t_item, |
| base_node, |
| &extra_fragment, |
| Some(&item), |
| ) { |
| Err(ErrorKind::AnchorFailure(msg)) => { |
| anchor_failure(cx, &item, &ori_link, &dox, link_range, msg); |
| continue; |
| } |
| x => x.ok(), |
| } |
| .and_then(|(res, fragment)| { |
| // Constructors are picked up in the type namespace. |
| match res { |
| Res::Def(DefKind::Ctor(..), _) | Res::SelfCtor(..) => None, |
| _ => match (fragment, extra_fragment) { |
| (Some(fragment), Some(_)) => { |
| // Shouldn't happen but who knows? |
| Some((res, Some(fragment))) |
| } |
| (fragment, None) | (None, fragment) => { |
| Some((res, fragment)) |
| } |
| }, |
| } |
| }), |
| }; |
| |
| if candidates.is_empty() { |
| resolution_failure(cx, &item, path_str, &dox, link_range); |
| // this could just be a normal link |
| continue; |
| } |
| |
| let len = candidates.clone().present_items().count(); |
| |
| if len == 1 { |
| candidates.present_items().next().unwrap() |
| } else if len == 2 && is_derive_trait_collision(&candidates) { |
| candidates.type_ns.unwrap() |
| } else { |
| if is_derive_trait_collision(&candidates) { |
| candidates.macro_ns = None; |
| } |
| ambiguity_error( |
| cx, |
| &item, |
| path_str, |
| &dox, |
| link_range, |
| candidates.map(|candidate| candidate.map(|(res, _)| res)), |
| ); |
| continue; |
| } |
| } |
| Some(MacroNS) => { |
| if let Some(res) = self.macro_resolve(path_str, base_node) { |
| (res, extra_fragment) |
| } else { |
| resolution_failure(cx, &item, path_str, &dox, link_range); |
| continue; |
| } |
| } |
| } |
| }; |
| |
| if let Res::PrimTy(_) = res { |
| item.attrs.links.push((ori_link, None, fragment)); |
| } else { |
| debug!("intra-doc link to {} resolved to {:?}", path_str, res); |
| if let Some(local) = res.opt_def_id().and_then(|def_id| def_id.as_local()) { |
| use rustc_hir::def_id::LOCAL_CRATE; |
| |
| let hir_id = self.cx.tcx.hir().as_local_hir_id(local); |
| if !self.cx.tcx.privacy_access_levels(LOCAL_CRATE).is_exported(hir_id) |
| && !self.cx.render_options.document_private |
| { |
| let item_name = item.name.as_deref().unwrap_or("<unknown>"); |
| let err_msg = format!( |
| "public documentation for `{}` links to a private item", |
| item_name |
| ); |
| build_diagnostic( |
| cx, |
| &item, |
| path_str, |
| &dox, |
| link_range, |
| &err_msg, |
| "this item is private", |
| None, |
| ); |
| continue; |
| } |
| } |
| let id = register_res(cx, res); |
| item.attrs.links.push((ori_link, Some(id), fragment)); |
| } |
| } |
| |
| if item.is_mod() && !item.attrs.inner_docs { |
| self.mod_ids.push(item_hir_id.unwrap()); |
| } |
| |
| if item.is_mod() { |
| let ret = self.fold_item_recur(item); |
| |
| self.mod_ids.pop(); |
| |
| ret |
| } else { |
| self.fold_item_recur(item) |
| } |
| } |
| |
| // FIXME: if we can resolve intra-doc links from other crates, we can use the stock |
| // `fold_crate`, but until then we should avoid scanning `krate.external_traits` since those |
| // will never resolve properly |
| fn fold_crate(&mut self, mut c: Crate) -> Crate { |
| c.module = c.module.take().and_then(|module| self.fold_item(module)); |
| |
| c |
| } |
| } |
| |
| fn build_diagnostic( |
| cx: &DocContext<'_>, |
| item: &Item, |
| path_str: &str, |
| dox: &str, |
| link_range: Option<Range<usize>>, |
| err_msg: &str, |
| short_err_msg: &str, |
| help_msg: Option<&str>, |
| ) { |
| let hir_id = match cx.as_local_hir_id(item.def_id) { |
| Some(hir_id) => hir_id, |
| None => { |
| // If non-local, no need to check anything. |
| return; |
| } |
| }; |
| let attrs = &item.attrs; |
| let sp = span_of_attrs(attrs).unwrap_or(item.source.span()); |
| |
| cx.tcx.struct_span_lint_hir( |
| lint::builtin::INTRA_DOC_LINK_RESOLUTION_FAILURE, |
| hir_id, |
| sp, |
| |lint| { |
| let mut diag = lint.build(&format!("`[{}]` {}", path_str, err_msg)); |
| if let Some(link_range) = link_range { |
| if let Some(sp) = super::source_span_for_markdown_range(cx, dox, &link_range, attrs) |
| { |
| diag.set_span(sp); |
| diag.span_label(sp, short_err_msg); |
| } else { |
| // blah blah blah\nblah\nblah [blah] blah blah\nblah blah |
| // ^ ~~~~ |
| // | link_range |
| // last_new_line_offset |
| let last_new_line_offset = |
| dox[..link_range.start].rfind('\n').map_or(0, |n| n + 1); |
| let line = dox[last_new_line_offset..].lines().next().unwrap_or(""); |
| |
| // Print the line containing the `link_range` and manually mark it with '^'s. |
| diag.note(&format!( |
| "the link appears in this line:\n\n{line}\n\ |
| {indicator: <before$}{indicator:^<found$}", |
| line = line, |
| indicator = "", |
| before = link_range.start - last_new_line_offset, |
| found = link_range.len(), |
| )); |
| } |
| }; |
| if let Some(help_msg) = help_msg { |
| diag.help(help_msg); |
| } |
| diag.emit(); |
| }, |
| ); |
| } |
| |
| /// Reports a resolution failure diagnostic. |
| /// |
| /// If we cannot find the exact source span of the resolution failure, we use the span of the |
| /// documentation attributes themselves. This is a little heavy-handed, so we display the markdown |
| /// line containing the failure as a note as well. |
| fn resolution_failure( |
| cx: &DocContext<'_>, |
| item: &Item, |
| path_str: &str, |
| dox: &str, |
| link_range: Option<Range<usize>>, |
| ) { |
| build_diagnostic( |
| cx, |
| item, |
| path_str, |
| dox, |
| link_range, |
| "cannot be resolved, ignoring it.", |
| "cannot be resolved, ignoring", |
| Some("to escape `[` and `]` characters, just add '\\' before them like `\\[` or `\\]`"), |
| ); |
| } |
| |
| fn anchor_failure( |
| cx: &DocContext<'_>, |
| item: &Item, |
| path_str: &str, |
| dox: &str, |
| link_range: Option<Range<usize>>, |
| msg: &str, |
| ) { |
| build_diagnostic( |
| cx, |
| item, |
| path_str, |
| dox, |
| link_range, |
| "has an issue with the link anchor.", |
| msg, |
| None, |
| ); |
| } |
| |
| fn ambiguity_error( |
| cx: &DocContext<'_>, |
| item: &Item, |
| path_str: &str, |
| dox: &str, |
| link_range: Option<Range<usize>>, |
| candidates: PerNS<Option<Res>>, |
| ) { |
| let hir_id = match cx.as_local_hir_id(item.def_id) { |
| Some(hir_id) => hir_id, |
| None => { |
| // If non-local, no need to check anything. |
| return; |
| } |
| }; |
| let attrs = &item.attrs; |
| let sp = span_of_attrs(attrs).unwrap_or(item.source.span()); |
| |
| cx.tcx.struct_span_lint_hir( |
| lint::builtin::INTRA_DOC_LINK_RESOLUTION_FAILURE, |
| hir_id, |
| sp, |
| |lint| { |
| let mut msg = format!("`{}` is ", path_str); |
| |
| let candidates = [TypeNS, ValueNS, MacroNS] |
| .iter() |
| .filter_map(|&ns| candidates[ns].map(|res| (res, ns))) |
| .collect::<Vec<_>>(); |
| match candidates.as_slice() { |
| [(first_def, _), (second_def, _)] => { |
| msg += &format!( |
| "both {} {} and {} {}", |
| first_def.article(), |
| first_def.descr(), |
| second_def.article(), |
| second_def.descr(), |
| ); |
| } |
| _ => { |
| let mut candidates = candidates.iter().peekable(); |
| while let Some((res, _)) = candidates.next() { |
| if candidates.peek().is_some() { |
| msg += &format!("{} {}, ", res.article(), res.descr()); |
| } else { |
| msg += &format!("and {} {}", res.article(), res.descr()); |
| } |
| } |
| } |
| } |
| |
| let mut diag = lint.build(&msg); |
| |
| if let Some(link_range) = link_range { |
| if let Some(sp) = super::source_span_for_markdown_range(cx, dox, &link_range, attrs) |
| { |
| diag.set_span(sp); |
| diag.span_label(sp, "ambiguous link"); |
| |
| for (res, ns) in candidates { |
| let (action, mut suggestion) = match res { |
| Res::Def(DefKind::AssocFn | DefKind::Fn, _) => { |
| ("add parentheses", format!("{}()", path_str)) |
| } |
| Res::Def(DefKind::Macro(MacroKind::Bang), _) => { |
| ("add an exclamation mark", format!("{}!", path_str)) |
| } |
| _ => { |
| let type_ = match (res, ns) { |
| (Res::Def(DefKind::Const, _), _) => "const", |
| (Res::Def(DefKind::Static, _), _) => "static", |
| (Res::Def(DefKind::Struct, _), _) => "struct", |
| (Res::Def(DefKind::Enum, _), _) => "enum", |
| (Res::Def(DefKind::Union, _), _) => "union", |
| (Res::Def(DefKind::Trait, _), _) => "trait", |
| (Res::Def(DefKind::Mod, _), _) => "module", |
| (_, TypeNS) => "type", |
| (_, ValueNS) => "value", |
| (Res::Def(DefKind::Macro(MacroKind::Derive), _), MacroNS) => { |
| "derive" |
| } |
| (_, MacroNS) => "macro", |
| }; |
| |
| // FIXME: if this is an implied shortcut link, it's bad style to suggest `@` |
| ("prefix with the item type", format!("{}@{}", type_, path_str)) |
| } |
| }; |
| |
| if dox.bytes().nth(link_range.start) == Some(b'`') { |
| suggestion = format!("`{}`", suggestion); |
| } |
| |
| diag.span_suggestion( |
| sp, |
| &format!("to link to the {}, {}", res.descr(), action), |
| suggestion, |
| Applicability::MaybeIncorrect, |
| ); |
| } |
| } else { |
| // blah blah blah\nblah\nblah [blah] blah blah\nblah blah |
| // ^ ~~~~ |
| // | link_range |
| // last_new_line_offset |
| let last_new_line_offset = |
| dox[..link_range.start].rfind('\n').map_or(0, |n| n + 1); |
| let line = dox[last_new_line_offset..].lines().next().unwrap_or(""); |
| |
| // Print the line containing the `link_range` and manually mark it with '^'s. |
| diag.note(&format!( |
| "the link appears in this line:\n\n{line}\n\ |
| {indicator: <before$}{indicator:^<found$}", |
| line = line, |
| indicator = "", |
| before = link_range.start - last_new_line_offset, |
| found = link_range.len(), |
| )); |
| } |
| } |
| diag.emit(); |
| }, |
| ); |
| } |
| |
| /// Given an enum variant's res, return the res of its enum and the associated fragment. |
| fn handle_variant( |
| cx: &DocContext<'_>, |
| res: Res, |
| extra_fragment: &Option<String>, |
| ) -> Result<(Res, Option<String>), ErrorKind> { |
| use rustc_middle::ty::DefIdTree; |
| |
| if extra_fragment.is_some() { |
| return Err(ErrorKind::AnchorFailure("variants cannot be followed by anchors")); |
| } |
| let parent = if let Some(parent) = cx.tcx.parent(res.def_id()) { |
| parent |
| } else { |
| return Err(ErrorKind::ResolutionFailure); |
| }; |
| let parent_def = Res::Def(DefKind::Enum, parent); |
| let variant = cx.tcx.expect_variant_res(res); |
| Ok((parent_def, Some(format!("variant.{}", variant.ident.name)))) |
| } |
| |
| const PRIMITIVES: &[(&str, Res)] = &[ |
| ("u8", Res::PrimTy(hir::PrimTy::Uint(rustc_ast::ast::UintTy::U8))), |
| ("u16", Res::PrimTy(hir::PrimTy::Uint(rustc_ast::ast::UintTy::U16))), |
| ("u32", Res::PrimTy(hir::PrimTy::Uint(rustc_ast::ast::UintTy::U32))), |
| ("u64", Res::PrimTy(hir::PrimTy::Uint(rustc_ast::ast::UintTy::U64))), |
| ("u128", Res::PrimTy(hir::PrimTy::Uint(rustc_ast::ast::UintTy::U128))), |
| ("usize", Res::PrimTy(hir::PrimTy::Uint(rustc_ast::ast::UintTy::Usize))), |
| ("i8", Res::PrimTy(hir::PrimTy::Int(rustc_ast::ast::IntTy::I8))), |
| ("i16", Res::PrimTy(hir::PrimTy::Int(rustc_ast::ast::IntTy::I16))), |
| ("i32", Res::PrimTy(hir::PrimTy::Int(rustc_ast::ast::IntTy::I32))), |
| ("i64", Res::PrimTy(hir::PrimTy::Int(rustc_ast::ast::IntTy::I64))), |
| ("i128", Res::PrimTy(hir::PrimTy::Int(rustc_ast::ast::IntTy::I128))), |
| ("isize", Res::PrimTy(hir::PrimTy::Int(rustc_ast::ast::IntTy::Isize))), |
| ("f32", Res::PrimTy(hir::PrimTy::Float(rustc_ast::ast::FloatTy::F32))), |
| ("f64", Res::PrimTy(hir::PrimTy::Float(rustc_ast::ast::FloatTy::F64))), |
| ("str", Res::PrimTy(hir::PrimTy::Str)), |
| ("bool", Res::PrimTy(hir::PrimTy::Bool)), |
| ("char", Res::PrimTy(hir::PrimTy::Char)), |
| ]; |
| |
| fn is_primitive(path_str: &str, ns: Namespace) -> Option<Res> { |
| if ns == TypeNS { PRIMITIVES.iter().find(|x| x.0 == path_str).map(|x| x.1) } else { None } |
| } |
| |
| fn primitive_impl(cx: &DocContext<'_>, path_str: &str) -> Option<DefId> { |
| let tcx = cx.tcx; |
| match path_str { |
| "u8" => tcx.lang_items().u8_impl(), |
| "u16" => tcx.lang_items().u16_impl(), |
| "u32" => tcx.lang_items().u32_impl(), |
| "u64" => tcx.lang_items().u64_impl(), |
| "u128" => tcx.lang_items().u128_impl(), |
| "usize" => tcx.lang_items().usize_impl(), |
| "i8" => tcx.lang_items().i8_impl(), |
| "i16" => tcx.lang_items().i16_impl(), |
| "i32" => tcx.lang_items().i32_impl(), |
| "i64" => tcx.lang_items().i64_impl(), |
| "i128" => tcx.lang_items().i128_impl(), |
| "isize" => tcx.lang_items().isize_impl(), |
| "f32" => tcx.lang_items().f32_impl(), |
| "f64" => tcx.lang_items().f64_impl(), |
| "str" => tcx.lang_items().str_impl(), |
| "bool" => tcx.lang_items().bool_impl(), |
| "char" => tcx.lang_items().char_impl(), |
| _ => None, |
| } |
| } |