use rustc_span::symbol::Ident;
use rustc_span::symbol::Symbol;
use rustc_span::DUMMY_SP;
-use smallvec::SmallVec;
+use smallvec::{smallvec, SmallVec};
+use std::borrow::Cow;
use std::cell::Cell;
use std::ops::Range;
}
}
-enum ErrorKind {
- ResolutionFailure,
+enum ErrorKind<'a> {
+ Resolve(ResolutionFailure<'a>),
AnchorFailure(AnchorFailure),
}
+#[derive(Debug)]
+enum ResolutionFailure<'a> {
+ /// This resolved, but with the wrong namespace.
+ /// `Namespace` is the expected namespace (as opposed to the actual).
+ WrongNamespace(Res, Namespace),
+ /// This has a partial resolution, but is not in the TypeNS and so cannot
+ /// have associated items or fields.
+ CannotHaveAssociatedItems(Res, Namespace),
+ /// `String` is the base name of the path (not necessarily the whole link)
+ NotInScope(Cow<'a, str>),
+ /// this is a primitive type without an impls (no associated methods)
+ /// when will this actually happen?
+ /// the `Res` is the primitive it resolved to
+ NoPrimitiveImpl(Res, String),
+ /// `[u8::not_found]`
+ /// the `Res` is the primitive it resolved to
+ NoPrimitiveAssocItem { res: Res, prim_name: &'a str, assoc_item: Symbol },
+ /// `[S::not_found]`
+ /// the `String` is the associated item that wasn't found
+ NoAssocItem(Res, Symbol),
+ /// should not ever happen
+ NoParentItem,
+ /// the root of this path resolved, but it was not an enum.
+ NotAnEnum(Res),
+ /// this could be an enum variant, but the last path fragment wasn't resolved.
+ /// the `String` is the variant that didn't exist
+ NotAVariant(Res, Symbol),
+ /// used to communicate that this should be ignored, but shouldn't be reported to the user
+ Dummy,
+}
+
+impl ResolutionFailure<'a> {
+ // A partial or full resolution
+ fn res(&self) -> Option<Res> {
+ use ResolutionFailure::*;
+ match self {
+ NoPrimitiveAssocItem { res, .. }
+ | NoAssocItem(res, _)
+ | NoPrimitiveImpl(res, _)
+ | NotAnEnum(res)
+ | NotAVariant(res, _)
+ | WrongNamespace(res, _)
+ | CannotHaveAssociatedItems(res, _) => Some(*res),
+ NotInScope(_) | NoParentItem | Dummy => None,
+ }
+ }
+
+ // This resolved fully (not just partially) but is erroneous for some other reason
+ fn full_res(&self) -> Option<Res> {
+ match self {
+ Self::WrongNamespace(res, _) => Some(*res),
+ _ => None,
+ }
+ }
+}
+
enum AnchorFailure {
MultipleAnchors,
Primitive,
/// This is used to store the kind of associated items,
/// because `clean` and the disambiguator code expect them to be different.
/// See the code for associated items on inherent impls for details.
- kind_side_channel: Cell<Option<DefKind>>,
+ kind_side_channel: Cell<Option<(DefKind, DefId)>>,
}
impl<'a, 'tcx> LinkCollector<'a, 'tcx> {
fn variant_field(
&self,
- path_str: &str,
+ path_str: &'path str,
current_item: &Option<String>,
module_id: DefId,
- ) -> Result<(Res, Option<String>), ErrorKind> {
+ ) -> Result<(Res, Option<String>), ErrorKind<'path>> {
let cx = self.cx;
+ debug!("looking for enum variant {}", path_str);
let mut split = path_str.rsplitn(3, "::");
- let variant_field_name =
- split.next().map(|f| Symbol::intern(f)).ok_or(ErrorKind::ResolutionFailure)?;
+ let variant_field_name = split
+ .next()
+ .map(|f| Symbol::intern(f))
+ .expect("fold_item should ensure link is non-empty");
let variant_name =
- split.next().map(|f| Symbol::intern(f)).ok_or(ErrorKind::ResolutionFailure)?;
+ // we're not sure this is a variant at all, so use the full string
+ split.next().map(|f| Symbol::intern(f)).ok_or(ErrorKind::Resolve(ResolutionFailure::NotInScope(path_str.into())))?;
let path = split
.next()
.map(|f| {
}
f.to_owned()
})
- .ok_or(ErrorKind::ResolutionFailure)?;
+ .ok_or(ErrorKind::Resolve(ResolutionFailure::NotInScope(
+ variant_name.to_string().into(),
+ )))?;
let (_, ty_res) = cx
.enter_resolver(|resolver| {
resolver.resolve_str_path_error(DUMMY_SP, &path, TypeNS, module_id)
})
- .map_err(|_| ErrorKind::ResolutionFailure)?;
+ .map_err(|_| {
+ ErrorKind::Resolve(ResolutionFailure::NotInScope(path.to_string().into()))
+ })?;
if let Res::Err = ty_res {
- return Err(ErrorKind::ResolutionFailure);
+ return Err(ErrorKind::Resolve(ResolutionFailure::NotInScope(path.to_string().into())));
}
let ty_res = ty_res.map_id(|_| panic!("unexpected node_id"));
match ty_res {
.flat_map(|imp| cx.tcx.associated_items(*imp).in_definition_order())
.any(|item| item.ident.name == variant_name)
{
- return Err(ErrorKind::ResolutionFailure);
+ // This is just to let `fold_item` know that this shouldn't be considered;
+ // it's a bug for the error to make it to the user
+ return Err(ErrorKind::Resolve(ResolutionFailure::Dummy));
}
- match cx.tcx.type_of(did).kind {
+ 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((
)),
))
} else {
- Err(ErrorKind::ResolutionFailure)
+ Err(ErrorKind::Resolve(ResolutionFailure::NotAVariant(
+ ty_res,
+ variant_field_name,
+ )))
}
}
- _ => Err(ErrorKind::ResolutionFailure),
+ _ => unreachable!(),
}
}
- _ => Err(ErrorKind::ResolutionFailure),
+ _ => Err(ErrorKind::Resolve(ResolutionFailure::NotAnEnum(ty_res))),
}
}
/// Resolves a string as a macro.
- fn macro_resolve(&self, path_str: &str, parent_id: Option<DefId>) -> Option<Res> {
+ fn macro_resolve(
+ &self,
+ path_str: &'a str,
+ parent_id: Option<DefId>,
+ ) -> Result<Res, ResolutionFailure<'a>> {
let cx = self.cx;
let path = ast::Path::from_ident(Ident::from_str(path_str));
cx.enter_resolver(|resolver| {
false,
) {
if let SyntaxExtensionKind::LegacyBang { .. } = ext.kind {
- return Some(res.map_id(|_| panic!("unexpected id")));
+ return Ok(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")));
+ return Ok(res.map_id(|_| panic!("unexpected id")));
}
if let Some(module_id) = parent_id {
+ debug!("resolving {} as a macro in the module {:?}", path_str, 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);
+ return Ok(res);
}
}
} else {
debug!("attempting to resolve item without parent module: {}", path_str);
+ return Err(ResolutionFailure::NoParentItem);
}
- None
+ return Err(ResolutionFailure::NotInScope(path_str.into()));
})
}
/// 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(
+ fn resolve<'path>(
&self,
- path_str: &str,
+ path_str: &'path str,
ns: Namespace,
current_item: &Option<String>,
parent_id: Option<DefId>,
extra_fragment: &Option<String>,
- ) -> Result<(Res, Option<String>), ErrorKind> {
+ ) -> Result<(Res, Option<String>), ErrorKind<'path>> {
let cx = self.cx;
// In case we're in a module, try to resolve the relative path.
});
debug!("{} resolved to {:?} in namespace {:?}", path_str, result, ns);
let result = match result {
- Ok((_, Res::Err)) => Err(ErrorKind::ResolutionFailure),
- _ => result.map_err(|_| ErrorKind::ResolutionFailure),
+ Ok((_, Res::Err)) => Err(()),
+ x => x,
};
if let Ok((_, res)) = result {
};
if value != (ns == ValueNS) {
- return Err(ErrorKind::ResolutionFailure);
+ return Err(ErrorKind::Resolve(ResolutionFailure::WrongNamespace(res, ns)));
}
} else if let Some((path, prim)) = is_primitive(path_str, ns) {
if extra_fragment.is_some() {
// 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
+ // this can be an `unwrap()` because we ensure the link is never empty
+ let item_name = Symbol::intern(split.next().unwrap());
+ let path_root = split
.next()
.map(|f| {
if f == "self" || f == "Self" {
}
f.to_owned()
})
- .ok_or(ErrorKind::ResolutionFailure)?;
-
- if let Some((path, prim)) = is_primitive(&path, TypeNS) {
- for &impl_ in primitive_impl(cx, &path).ok_or(ErrorKind::ResolutionFailure)? {
+ // If there's no `::`, it's not an associated item.
+ // So we can be sure that `rustc_resolve` was accurate when it said it wasn't resolved.
+ .ok_or_else(|| {
+ debug!("found no `::`, assumming {} was correctly not in scope", item_name);
+ ErrorKind::Resolve(ResolutionFailure::NotInScope(item_name.to_string().into()))
+ })?;
+
+ if let Some((path, prim)) = is_primitive(&path_root, ns) {
+ let impls = primitive_impl(cx, &path).ok_or_else(|| {
+ ErrorKind::Resolve(ResolutionFailure::NoPrimitiveImpl(prim, path_root.into()))
+ })?;
+ for &impl_ in impls {
let link = cx
.tcx
.associated_items(impl_)
ns,
impl_,
)
- .and_then(|item| match item.kind {
- ty::AssocKind::Fn => Some("method"),
- _ => None,
+ .map(|item| match item.kind {
+ ty::AssocKind::Fn => "method",
+ ty::AssocKind::Const => "associatedconstant",
+ ty::AssocKind::Type => "associatedtype",
})
.map(|out| (prim, Some(format!("{}#{}.{}", path, out, item_name))));
if let Some(link) = link {
return Ok(link);
}
}
- return Err(ErrorKind::ResolutionFailure);
+ return Err(ErrorKind::Resolve(ResolutionFailure::NoPrimitiveAssocItem {
+ res: prim,
+ prim_name: path,
+ assoc_item: item_name,
+ }));
}
- let (_, ty_res) = cx
+ let ty_res = cx
.enter_resolver(|resolver| {
- resolver.resolve_str_path_error(DUMMY_SP, &path, TypeNS, module_id)
+ // only types can have associated items
+ resolver.resolve_str_path_error(DUMMY_SP, &path_root, TypeNS, module_id)
})
- .map_err(|_| ErrorKind::ResolutionFailure)?;
- if let Res::Err = ty_res {
- return if ns == Namespace::ValueNS {
- self.variant_field(path_str, current_item, module_id)
- } else {
- Err(ErrorKind::ResolutionFailure)
- };
- }
+ .map(|(_, res)| res);
+ let ty_res = match ty_res {
+ Err(()) | Ok(Res::Err) => {
+ return if ns == Namespace::ValueNS {
+ self.variant_field(path_str, current_item, module_id)
+ } else {
+ // See if it only broke because of the namespace.
+ let kind = cx.enter_resolver(|resolver| {
+ for &ns in &[MacroNS, ValueNS] {
+ match resolver
+ .resolve_str_path_error(DUMMY_SP, &path_root, ns, module_id)
+ {
+ Ok((_, Res::Err)) | Err(()) => {}
+ Ok((_, res)) => {
+ let res = res.map_id(|_| panic!("unexpected node_id"));
+ return ResolutionFailure::CannotHaveAssociatedItems(
+ res, ns,
+ );
+ }
+ }
+ }
+ ResolutionFailure::NotInScope(path_root.into())
+ });
+ Err(ErrorKind::Resolve(kind))
+ };
+ }
+ Ok(res) => res,
+ };
let ty_res = ty_res.map_id(|_| panic!("unexpected node_id"));
let res = match ty_res {
Res::Def(
) => {
debug!("looking for associated item named {} for item {:?}", item_name, did);
// Checks if item_name belongs to `impl SomeItem`
- let kind = cx
+ let assoc_item = cx
.tcx
.inherent_impls(did)
.iter()
imp,
)
})
- .map(|item| item.kind)
+ .map(|item| (item.kind, item.def_id))
// There should only ever be one associated item that matches from any inherent impl
.next()
// Check if item_name belongs to `impl SomeTrait for SomeItem`
kind
});
- if let Some(kind) = kind {
+ if let Some((kind, id)) = assoc_item {
let out = match kind {
ty::AssocKind::Fn => "method",
ty::AssocKind::Const => "associatedconstant",
// HACK(jynelson): `clean` expects the type, not the associated item.
// but the disambiguator logic expects the associated item.
// Store the kind in a side channel so that only the disambiguator logic looks at it.
- self.kind_side_channel.set(Some(kind.as_def_kind()));
+ self.kind_side_channel.set(Some((kind.as_def_kind(), id)));
Ok((ty_res, Some(format!("{}.{}", out, item_name))))
})
} else if ns == Namespace::ValueNS {
- match cx.tcx.type_of(did).kind {
+ debug!("looking for variants or fields named {} for {:?}", item_name, did);
+ match cx.tcx.type_of(did).kind() {
ty::Adt(def, _) => {
let field = if def.is_enum() {
def.all_fields().find(|item| item.ident.name == item_name)
}
} else {
// We already know this isn't in ValueNS, so no need to check variant_field
- return Err(ErrorKind::ResolutionFailure);
+ return Err(ErrorKind::Resolve(ResolutionFailure::NoAssocItem(
+ ty_res, item_name,
+ )));
}
}
Res::Def(DefKind::Trait, did) => cx
if ns == Namespace::ValueNS {
self.variant_field(path_str, current_item, module_id)
} else {
- Err(ErrorKind::ResolutionFailure)
+ Err(ErrorKind::Resolve(ResolutionFailure::NoAssocItem(ty_res, item_name)))
}
})
} else {
debug!("attempting to resolve item without parent module: {}", path_str);
- Err(ErrorKind::ResolutionFailure)
+ // TODO: maybe this should just be an ICE?
+ Err(ErrorKind::Resolve(ResolutionFailure::NoParentItem))
}
}
}
item_name: Symbol,
ns: Namespace,
cx: &DocContext<'_>,
-) -> Option<ty::AssocKind> {
+) -> Option<(ty::AssocKind, DefId)> {
let ty = cx.tcx.type_of(did);
// First consider automatic impls: `impl From<T> for T`
let implicit_impls = crate::clean::get_auto_trait_and_blanket_impls(cx, ty, did);
// but provided methods come directly from `tcx`.
// Fortunately, we don't need the whole method, we just need to know
// what kind of associated item it is.
- Some((assoc.def_id, kind))
+ Some((kind, assoc.def_id))
});
let assoc = items.next();
debug_assert_eq!(items.count(), 0);
ns,
trait_,
)
- .map(|assoc| (assoc.def_id, assoc.kind))
+ .map(|assoc| (assoc.kind, assoc.def_id))
}
}
_ => panic!("get_impls returned something that wasn't an impl"),
cx.tcx
.associated_items(trait_)
.find_by_name_and_namespace(cx.tcx, Ident::with_dummy_span(item_name), ns, trait_)
- .map(|assoc| (assoc.def_id, assoc.kind))
+ .map(|assoc| (assoc.kind, assoc.def_id))
}));
}
// FIXME: warn about ambiguity
debug!("the candidates were {:?}", candidates);
- candidates.pop().map(|(_, kind)| kind)
+ candidates.pop()
}
/// Given a type, return all traits in scope in `module` implemented by that type.
let impl_type = trait_ref.self_ty();
debug!(
"comparing type {} with kind {:?} against type {:?}",
- impl_type, impl_type.kind, type_
+ impl_type,
+ impl_type.kind(),
+ type_
);
// Fast path: if this is a primitive simple `==` will work
saw_impl = impl_type == ty
- || match impl_type.kind {
+ || match impl_type.kind() {
// Check if these are the same def_id
ty::Adt(def, _) => {
debug!("adt def_id: {:?}", def.did);
def.did == type_
}
- ty::Foreign(def_id) => def_id == type_,
+ ty::Foreign(def_id) => *def_id == type_,
_ => false,
};
});
/// 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 {
+fn is_derive_trait_collision<T>(ns: &PerNS<Result<(Res, T), ResolutionFailure<'_>>>) -> bool {
if let PerNS {
- type_ns: Some((Res::Def(DefKind::Trait, _), _)),
- macro_ns: Some((Res::Def(DefKind::Macro(MacroKind::Derive), _), _)),
+ type_ns: Ok((Res::Def(DefKind::Trait, _), _)),
+ macro_ns: Ok((Res::Def(DefKind::Macro(MacroKind::Derive), _), _)),
..
} = *ns
{
let parent_node = if item.is_fake() {
// FIXME: is this correct?
None
+ // If we're documenting the crate root itself, it has no parent. Use the root instead.
+ } else if item.def_id.is_top_level_module() {
+ Some(item.def_id)
} else {
let mut current = item.def_id;
// The immediate parent might not always be a module.
}
current = parent;
} else {
+ debug!(
+ "{:?} has no parent (kind={:?}, original was {:?})",
+ current,
+ self.cx.tcx.def_kind(current),
+ item.def_id
+ );
break None;
}
}
// 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()))
+ (parts[0], Some(parts[1].to_owned()))
} else {
- (parts[0].to_owned(), None)
+ (parts[0], None)
};
let resolved_self;
+ let link_text;
let mut path_str;
let disambiguator;
let (mut res, mut fragment) = {
continue;
}
+ // We stripped `()` and `!` when parsing the disambiguator.
+ // Add them back to be displayed, but not prefix disambiguators.
+ link_text = disambiguator
+ .map(|d| d.display_for(path_str))
+ .unwrap_or_else(|| path_str.to_owned());
+
// 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
}
}
+ // used for reporting better errors
+ let check_full_res = |this: &mut Self, ns| {
+ let res =
+ match this.resolve(path_str, ns, ¤t_item, base_node, &extra_fragment)
+ {
+ Ok(res) => {
+ debug!(
+ "check_full_res: saw res for {} in {:?} ns: {:?}",
+ path_str, ns, res.0
+ );
+ Some(res.0)
+ }
+ Err(ErrorKind::Resolve(kind)) => kind.full_res(),
+ // TODO: add `Res` to AnchorFailure
+ Err(ErrorKind::AnchorFailure(_)) => None,
+ };
+ this.kind_side_channel.take().map(|(kind, id)| Res::Def(kind, id)).or(res)
+ };
+
match disambiguator.map(Disambiguator::ns) {
Some(ns @ (ValueNS | TypeNS)) => {
match self.resolve(path_str, ns, ¤t_item, base_node, &extra_fragment)
{
Ok(res) => res,
- Err(ErrorKind::ResolutionFailure) => {
- resolution_failure(cx, &item, path_str, &dox, link_range);
+ Err(ErrorKind::Resolve(mut kind)) => {
+ // We only looked in one namespace. Try to give a better error if possible.
+ // TODO: handle MacroNS too
+ if kind.full_res().is_none() {
+ let other_ns = if ns == ValueNS { TypeNS } else { ValueNS };
+ if let Some(res) = check_full_res(self, other_ns) {
+ // recall that this stores the _expected_ namespace
+ kind = ResolutionFailure::WrongNamespace(res, ns);
+ }
+ }
+ resolution_failure(
+ cx,
+ &item,
+ path_str,
+ &dox,
+ link_range,
+ smallvec![kind],
+ );
// 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.
) {
Ok(res) => {
debug!("got res in TypeNS: {:?}", res);
- Some(res)
+ Ok(res)
}
Err(ErrorKind::AnchorFailure(msg)) => {
anchor_failure(cx, &item, &ori_link, &dox, link_range, msg);
continue;
}
- Err(ErrorKind::ResolutionFailure) => None,
+ Err(ErrorKind::Resolve(kind)) => Err(kind),
},
value_ns: match self.resolve(
path_str,
base_node,
&extra_fragment,
) {
- Ok(res) => Some(res),
+ Ok(res) => Ok(res),
Err(ErrorKind::AnchorFailure(msg)) => {
anchor_failure(cx, &item, &ori_link, &dox, link_range, msg);
continue;
}
- Err(ErrorKind::ResolutionFailure) => None,
+ Err(ErrorKind::Resolve(kind)) => Err(kind),
}
.and_then(|(res, fragment)| {
// Constructors are picked up in the type namespace.
match res {
- Res::Def(DefKind::Ctor(..), _) | Res::SelfCtor(..) => None,
+ Res::Def(DefKind::Ctor(..), _) | Res::SelfCtor(..) => {
+ Err(ResolutionFailure::WrongNamespace(res, TypeNS))
+ }
_ => match (fragment, extra_fragment) {
(Some(fragment), Some(_)) => {
// Shouldn't happen but who knows?
- Some((res, Some(fragment)))
- }
- (fragment, None) | (None, fragment) => {
- Some((res, fragment))
+ Ok((res, Some(fragment)))
}
+ (fragment, None) | (None, fragment) => Ok((res, fragment)),
},
}
}),
};
- if candidates.is_empty() {
- resolution_failure(cx, &item, path_str, &dox, link_range);
+ let mut candidates_iter =
+ candidates.iter().filter_map(|res| res.as_ref().ok());
+ let len = candidates_iter.clone().count();
+
+ if len == 0 {
+ drop(candidates_iter);
+ resolution_failure(
+ cx,
+ &item,
+ path_str,
+ &dox,
+ link_range,
+ candidates.into_iter().filter_map(|res| res.err()).collect(),
+ );
// this could just be a normal link
continue;
}
- let len = candidates.clone().present_items().count();
-
if len == 1 {
- candidates.present_items().next().unwrap()
+ candidates_iter.next().unwrap().clone()
} else if len == 2 && is_derive_trait_collision(&candidates) {
+ drop(candidates_iter);
candidates.type_ns.unwrap()
} else {
+ drop(candidates_iter);
if is_derive_trait_collision(&candidates) {
- candidates.macro_ns = None;
+ candidates.macro_ns =
+ Err(ResolutionFailure::NotInScope(path_str.into()));
}
+ // If we're reporting an ambiguity, don't mention the namespaces that failed
let candidates =
- candidates.map(|candidate| candidate.map(|(res, _)| res));
+ candidates.map(|candidate| candidate.ok().map(|(res, _)| res));
ambiguity_error(
cx,
&item,
}
}
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;
+ match self.macro_resolve(path_str, base_node) {
+ Ok(res) => (res, extra_fragment),
+ Err(mut kind) => {
+ // `macro_resolve` only looks in the macro namespace. Try to give a better error if possible.
+ for &ns in &[TypeNS, ValueNS] {
+ if let Some(res) = check_full_res(self, ns) {
+ kind = ResolutionFailure::WrongNamespace(res, MacroNS);
+ break;
+ }
+ }
+ resolution_failure(
+ cx,
+ &item,
+ path_str,
+ &dox,
+ link_range,
+ smallvec![kind],
+ );
+ continue;
+ }
}
}
}
let report_mismatch = |specified: Disambiguator, resolved: Disambiguator| {
// The resolved item did not match the disambiguator; give a better error than 'not found'
let msg = format!("incompatible link kind for `{}`", path_str);
- report_diagnostic(cx, &msg, &item, &dox, link_range.clone(), |diag, sp| {
+ report_diagnostic(cx, &msg, &item, &dox, &link_range, |diag, sp| {
let note = format!(
"this link resolved to {} {}, which is not {} {}",
resolved.article(),
if let Res::PrimTy(_) = res {
match disambiguator {
Some(Disambiguator::Primitive | Disambiguator::Namespace(_)) | None => {
- item.attrs.links.push((ori_link, None, fragment))
+ item.attrs.links.push(ItemLink {
+ link: ori_link,
+ link_text: path_str.to_owned(),
+ did: None,
+ fragment,
+ });
}
Some(other) => {
report_mismatch(other, Disambiguator::Primitive);
// Disallow e.g. linking to enums with `struct@`
if let Res::Def(kind, _) = res {
debug!("saw kind {:?} with disambiguator {:?}", kind, disambiguator);
- match (self.kind_side_channel.take().unwrap_or(kind), disambiguator) {
+ match (self.kind_side_channel.take().map(|(kind, _)| kind).unwrap_or(kind), disambiguator) {
| (DefKind::Const | DefKind::ConstParam | DefKind::AssocConst | DefKind::AnonConst, Some(Disambiguator::Kind(DefKind::Const)))
// NOTE: this allows 'method' to mean both normal functions and associated functions
// This can't cause ambiguity because both are in the same namespace.
}
}
let id = register_res(cx, res);
- item.attrs.links.push((ori_link, Some(id), fragment));
+ item.attrs.links.push(ItemLink {
+ link: ori_link,
+ link_text,
+ did: Some(id),
+ fragment,
+ });
}
}
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
- }
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
}
impl Disambiguator {
+ /// The text that should be displayed when the path is rendered as HTML.
+ ///
+ /// NOTE: `path` is not the original link given by the user, but a name suitable for passing to `resolve`.
+ fn display_for(&self, path: &str) -> String {
+ match self {
+ // FIXME: this will have different output if the user had `m!()` originally.
+ Self::Kind(DefKind::Macro(MacroKind::Bang)) => format!("{}!", path),
+ Self::Kind(DefKind::Fn) => format!("{}()", path),
+ _ => path.to_owned(),
+ }
+ }
+
/// (disambiguator, path_str)
fn from_str(link: &str) -> Result<(Self, &str), ()> {
use Disambiguator::{Kind, Namespace as NS, Primitive};
}
/// Return (description of the change, suggestion)
- fn display_for(self, path_str: &str) -> (&'static str, String) {
+ fn suggestion_for(self, path_str: &str) -> (&'static str, String) {
const PREFIX: &str = "prefix with the item kind";
const FUNCTION: &str = "add parentheses";
const MACRO: &str = "add an exclamation mark";
msg: &str,
item: &Item,
dox: &str,
- link_range: Option<Range<usize>>,
+ link_range: &Option<Range<usize>>,
decorate: impl FnOnce(&mut DiagnosticBuilder<'_>, Option<rustc_span::Span>),
) {
let hir_id = match cx.as_local_hir_id(item.def_id) {
// 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$}",
+ {indicator: <before$}{indicator:^<found$}",
line = line,
indicator = "",
before = link_range.start - last_new_line_offset,
path_str: &str,
dox: &str,
link_range: Option<Range<usize>>,
+ kinds: SmallVec<[ResolutionFailure<'_>; 3]>,
) {
report_diagnostic(
cx,
&format!("unresolved link to `{}`", path_str),
item,
dox,
- link_range,
+ &link_range,
|diag, sp| {
- if let Some(sp) = sp {
- diag.span_label(sp, "unresolved link");
+ let in_scope = kinds.iter().any(|kind| kind.res().is_some());
+ let mut reported_not_in_scope = false;
+ let item = |res: Res| {
+ if let Some(id) = res.opt_def_id() {
+ (format!("the {} `{}`", res.descr(), cx.tcx.item_name(id).to_string()), ",")
+ } else {
+ (format!("{} {}", res.article(), res.descr()), "")
+ }
+ };
+ for failure in kinds {
+ match failure {
+ // already handled above
+ ResolutionFailure::NotInScope(base) => {
+ if in_scope || reported_not_in_scope {
+ continue;
+ }
+ reported_not_in_scope = true;
+ diag.note(&format!("no item named `{}` is in scope", base));
+ diag.help(r#"to escape `[` and `]` characters, add '\' before them like `\[` or `\]`"#);
+ }
+ ResolutionFailure::Dummy => continue,
+ ResolutionFailure::WrongNamespace(res, expected_ns) => {
+ let (item, comma) = item(res);
+ let note = format!(
+ "this link resolves to {}{} which is not in the {} namespace",
+ item,
+ comma,
+ expected_ns.descr()
+ );
+ diag.note(¬e);
+
+ if let Res::Def(kind, _) = res {
+ let disambiguator = Disambiguator::Kind(kind);
+ suggest_disambiguator(
+ disambiguator,
+ diag,
+ path_str,
+ dox,
+ sp,
+ &link_range,
+ )
+ }
+ }
+ ResolutionFailure::NoParentItem => {
+ panic!("all intra doc links should have a parent item")
+ }
+ ResolutionFailure::NoPrimitiveImpl(res, _) => {
+ let (item, comma) = item(res);
+ let note = format!(
+ "this link partially resolves to {}{} which does not have any associated items",
+ item, comma,
+ );
+ diag.note(¬e);
+ }
+ ResolutionFailure::NoPrimitiveAssocItem { prim_name, assoc_item, .. } => {
+ let note = format!(
+ "the builtin type `{}` does not have an associated item named `{}`",
+ prim_name, assoc_item
+ );
+ diag.note(¬e);
+ }
+ ResolutionFailure::NoAssocItem(res, assoc_item) => {
+ let (item, _) = item(res);
+ diag.note(&format!("this link partially resolves to {}", item));
+ // FIXME: when are items neither a primitive nor a Def?
+ if let Res::Def(_, def_id) = res {
+ let name = cx.tcx.item_name(def_id);
+ let note = format!("no `{}` in `{}`", assoc_item, name,);
+ diag.note(¬e);
+ }
+ }
+ ResolutionFailure::CannotHaveAssociatedItems(res, _) => {
+ let (item, _) = item(res);
+ diag.note(&format!("this link partially resolves to {}", item));
+ if let Res::Def(kind, def_id) = res {
+ let name = cx.tcx.item_name(def_id);
+ let note = format!(
+ "`{}` is {} {}, not a module or type, and cannot have associated items",
+ name,
+ kind.article(),
+ kind.descr(def_id)
+ );
+ diag.note(¬e);
+ }
+ }
+ // TODO: is there ever a case where this happens?
+ ResolutionFailure::NotAnEnum(res) => {
+ let (item, comma) = item(res);
+ let note =
+ format!("this link resolves to {}{} which is not an enum", item, comma);
+ diag.note(¬e);
+ diag.note("if this were an enum, it might have a variant which resolved");
+ }
+ ResolutionFailure::NotAVariant(res, variant) => {
+ let note = format!(
+ "this link partially resolves to {}, but there is no variant named {}",
+ item(res).0,
+ variant
+ );
+ diag.note(¬e);
+ }
+ }
}
-
- diag.help(r#"to escape `[` and `]` characters, add '\' before them like `\[` or `\]`"#);
},
);
}
}
};
- report_diagnostic(cx, &msg, item, dox, link_range, |diag, sp| {
+ report_diagnostic(cx, &msg, item, dox, &link_range, |diag, sp| {
if let Some(sp) = sp {
diag.span_label(sp, "contains invalid anchor");
}
});
}
-fn ambiguity_error(
+fn ambiguity_error<'a>(
cx: &DocContext<'_>,
item: &Item,
- path_str: &str,
+ path_str: &'a str,
dox: &str,
link_range: Option<Range<usize>>,
candidates: Vec<Res>,
}
}
- report_diagnostic(cx, &msg, item, dox, link_range.clone(), |diag, sp| {
+ report_diagnostic(cx, &msg, item, dox, &link_range, |diag, sp| {
if let Some(sp) = sp {
diag.span_label(sp, "ambiguous link");
} else {
sp: Option<rustc_span::Span>,
link_range: &Option<Range<usize>>,
) {
- let (action, mut suggestion) = disambiguator.display_for(path_str);
+ let (action, mut suggestion) = disambiguator.suggestion_for(path_str);
let help = format!("to link to the {}, {}", disambiguator.descr(), action);
if let Some(sp) = sp {
let msg =
format!("public documentation for `{}` links to private item `{}`", item_name, path_str);
- report_diagnostic(cx, &msg, item, dox, link_range, |diag, sp| {
+ report_diagnostic(cx, &msg, item, dox, &link_range, |diag, sp| {
if let Some(sp) = sp {
diag.span_label(sp, "this item is private");
}
cx: &DocContext<'_>,
res: Res,
extra_fragment: &Option<String>,
-) -> Result<(Res, Option<String>), ErrorKind> {
+) -> Result<(Res, Option<String>), ErrorKind<'static>> {
use rustc_middle::ty::DefIdTree;
if extra_fragment.is_some() {
let parent = if let Some(parent) = cx.tcx.parent(res.def_id()) {
parent
} else {
- return Err(ErrorKind::ResolutionFailure);
+ // TODO: this should just be an unwrap, there should never be `Variant`s without a parent
+ return Err(ErrorKind::Resolve(ResolutionFailure::NoParentItem));
};
let parent_def = Res::Def(DefKind::Enum, parent);
let variant = cx.tcx.expect_variant_res(res);