blob: 4fcf6ceb44d507635840971204bb60c1cfeb9c96 [file] [log] [blame]
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,
&current_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,
&current_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,
&current_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,
&current_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,
}
}