use rustc_hir::def_id::{DefId, CRATE_DEF_ID};
use rustc_hir::Mutability;
use rustc_middle::ty::{DefIdTree, Ty, TyCtxt};
-use rustc_middle::{bug, span_bug, ty};
+use rustc_middle::{bug, ty};
+use rustc_resolve::ParentScope;
use rustc_session::lint::Lint;
use rustc_span::hygiene::MacroKind;
use rustc_span::symbol::{sym, Ident, Symbol};
-use rustc_span::{BytePos, DUMMY_SP};
+use rustc_span::BytePos;
use smallvec::{smallvec, SmallVec};
use std::borrow::Cow;
krate
}
-/// Top-level errors emitted by this pass.
-enum ErrorKind<'a> {
- Resolve(Box<ResolutionFailure<'a>>),
- AnchorFailure(AnchorFailure),
-}
-
-impl<'a> From<ResolutionFailure<'a>> for ErrorKind<'a> {
- fn from(err: ResolutionFailure<'a>) -> Self {
- ErrorKind::Resolve(box err)
- }
-}
-
#[derive(Copy, Clone, Debug, Hash)]
enum Res {
Def(DefKind, DefId),
}
}
- fn as_hir_res(self) -> Option<rustc_hir::def::Res> {
- match self {
- Res::Def(kind, id) => Some(rustc_hir::def::Res::Def(kind, id)),
- // FIXME: maybe this should handle the subset of PrimitiveType that fits into hir::PrimTy?
- Res::Primitive(_) => None,
- }
+ fn from_def_id(tcx: TyCtxt<'_>, def_id: DefId) -> Res {
+ Res::Def(tcx.def_kind(def_id), def_id)
}
/// Used for error reporting.
}
}
-/// A link failed to resolve.
-#[derive(Clone, Debug)]
+/// The link failed to resolve. [`resolution_failure`] should look to see if there's
+/// a more helpful error that can be given.
+#[derive(Debug)]
+struct UnresolvedPath<'a> {
+ /// Item on which the link is resolved, used for resolving `Self`.
+ item_id: ItemId,
+ /// The scope the link was resolved in.
+ module_id: DefId,
+ /// If part of the link resolved, this has the `Res`.
+ ///
+ /// In `[std::io::Error::x]`, `std::io::Error` would be a partial resolution.
+ partial_res: Option<Res>,
+ /// The remaining unresolved path segments.
+ ///
+ /// In `[std::io::Error::x]`, `x` would be unresolved.
+ unresolved: Cow<'a, str>,
+}
+
+#[derive(Debug)]
enum ResolutionFailure<'a> {
/// This resolved, but with the wrong namespace.
WrongNamespace {
/// even though `Result`'s actual namespace is [`Namespace::TypeNS`].
expected_ns: Namespace,
},
- /// The link failed to resolve. [`resolution_failure`] should look to see if there's
- /// a more helpful error that can be given.
- NotResolved {
- /// Item on which the link is resolved, used for resolving `Self`.
- item_id: ItemId,
- /// The scope the link was resolved in.
- module_id: DefId,
- /// If part of the link resolved, this has the `Res`.
- ///
- /// In `[std::io::Error::x]`, `std::io::Error` would be a partial resolution.
- partial_res: Option<Res>,
- /// The remaining unresolved path segments.
- ///
- /// In `[std::io::Error::x]`, `x` would be unresolved.
- unresolved: Cow<'a, str>,
- },
- /// This happens when rustdoc can't determine the parent scope for an item.
- /// It is always a bug in rustdoc.
- NoParentItem,
- /// This link has malformed generic parameters; e.g., the angle brackets are unbalanced.
- MalformedGenerics(MalformedGenerics),
- /// Used to communicate that this should be ignored, but shouldn't be reported to the user.
- ///
- /// This happens when there is no disambiguator and one of the namespaces
- /// failed to resolve.
- Dummy,
+ NotResolved(UnresolvedPath<'a>),
}
-#[derive(Clone, Debug)]
+#[derive(Clone, Copy, Debug)]
enum MalformedGenerics {
/// This link has unbalanced angle brackets.
///
EmptyAngleBrackets,
}
-impl ResolutionFailure<'_> {
- /// This resolved fully (not just partially) but is erroneous for some other reason
- ///
- /// Returns the full resolution of the link, if present.
- fn full_res(&self) -> Option<Res> {
- match self {
- Self::WrongNamespace { res, expected_ns: _ } => Some(*res),
- _ => None,
- }
- }
-}
-
-#[derive(Clone, Copy)]
-enum AnchorFailure {
- /// User error: `[std#x#y]` is not valid
- MultipleAnchors,
- /// The anchor provided by the user conflicts with Rustdoc's generated anchor.
- ///
- /// This is an unfortunate state of affairs. Not every item that can be
- /// linked to has its own page; sometimes it is a subheading within a page,
- /// like for associated items. In those cases, rustdoc uses an anchor to
- /// link to the subheading. Since you can't have two anchors for the same
- /// link, Rustdoc disallows having a user-specified anchor.
- ///
- /// Most of the time this is fine, because you can just link to the page of
- /// the item if you want to provide your own anchor.
- RustdocAnchorConflict(Res),
-}
-
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
crate enum UrlFragment {
Item(ItemFragment),
VariantField,
}
-impl ItemFragment {
- /// Create a fragment for an associated item.
- #[instrument(level = "debug")]
- fn from_assoc_item(item: &ty::AssocItem) -> Self {
- let def_id = item.def_id;
- match item.kind {
- ty::AssocKind::Fn => {
- if item.defaultness.has_value() {
- ItemFragment(FragmentKind::Method, def_id)
+impl FragmentKind {
+ fn from_def_id(tcx: TyCtxt<'_>, def_id: DefId) -> FragmentKind {
+ match tcx.def_kind(def_id) {
+ DefKind::AssocFn => {
+ if tcx.associated_item(def_id).defaultness.has_value() {
+ FragmentKind::Method
} else {
- ItemFragment(FragmentKind::TyMethod, def_id)
+ FragmentKind::TyMethod
}
}
- ty::AssocKind::Const => ItemFragment(FragmentKind::AssociatedConstant, def_id),
- ty::AssocKind::Type => ItemFragment(FragmentKind::AssociatedType, def_id),
+ DefKind::AssocConst => FragmentKind::AssociatedConstant,
+ DefKind::AssocTy => FragmentKind::AssociatedType,
+ DefKind::Variant => FragmentKind::Variant,
+ DefKind::Field => {
+ if tcx.def_kind(tcx.parent(def_id).unwrap()) == DefKind::Variant {
+ FragmentKind::VariantField
+ } else {
+ FragmentKind::StructField
+ }
+ }
+ kind => bug!("unexpected associated item kind: {:?}", kind),
}
}
+}
+impl ItemFragment {
/// Render the fragment, including the leading `#`.
crate fn render(&self, s: &mut String, tcx: TyCtxt<'_>) -> std::fmt::Result {
write!(s, "#")?;
FragmentKind::StructField => write!(s, "structfield.{}", name),
FragmentKind::Variant => write!(s, "variant.{}", name),
FragmentKind::VariantField => {
- let variant = tcx.item_name(tcx.parent(def_id).unwrap());
+ let variant = tcx.item_name(tcx.parent(def_id));
write!(s, "variant.{}.field.{}", variant, name)
}
}
path_str: &'path str,
item_id: ItemId,
module_id: DefId,
- ) -> Result<(Res, Option<ItemFragment>), ErrorKind<'path>> {
+ ) -> Result<(Res, DefId), UnresolvedPath<'path>> {
let tcx = self.cx.tcx;
- let no_res = || ResolutionFailure::NotResolved {
+ let no_res = || UnresolvedPath {
item_id,
module_id,
partial_res: None,
let ty_res = self.resolve_path(&path, TypeNS, item_id, module_id).ok_or_else(no_res)?;
match ty_res {
- Res::Def(DefKind::Enum, did) => {
- if tcx
- .inherent_impls(did)
- .iter()
- .flat_map(|imp| tcx.associated_items(*imp).in_definition_order())
- .any(|item| item.name == variant_name)
- {
- // 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(ResolutionFailure::Dummy.into());
- }
- match tcx.type_of(did).kind() {
- ty::Adt(def, _) if def.is_enum() => {
- if let Some(field) = def.all_fields().find(|f| f.name == variant_field_name)
- {
- Ok((ty_res, Some(ItemFragment(FragmentKind::VariantField, field.did))))
- } else {
- Err(ResolutionFailure::NotResolved {
- item_id,
- module_id,
- partial_res: Some(Res::Def(DefKind::Enum, def.did())),
- unresolved: variant_field_name.to_string().into(),
- }
- .into())
- }
+ Res::Def(DefKind::Enum, did) => match tcx.type_of(did).kind() {
+ ty::Adt(def, _) if def.is_enum() => {
+ if let Some(field) = def.all_fields().find(|f| f.name == variant_field_name) {
+ Ok((ty_res, field.did))
+ } else {
+ Err(UnresolvedPath {
+ item_id,
+ module_id,
+ partial_res: Some(Res::Def(DefKind::Enum, def.did())),
+ unresolved: variant_field_name.to_string().into(),
+ })
}
- _ => unreachable!(),
}
- }
- _ => Err(ResolutionFailure::NotResolved {
+ _ => unreachable!(),
+ },
+ _ => Err(UnresolvedPath {
item_id,
module_id,
partial_res: Some(ty_res),
unresolved: variant_name.to_string().into(),
- }
- .into()),
+ }),
}
}
prim_ty: PrimitiveType,
ns: Namespace,
item_name: Symbol,
- ) -> Option<(Res, ItemFragment)> {
+ ) -> Option<(Res, DefId)> {
let tcx = self.cx.tcx;
prim_ty.impls(tcx).find_map(|impl_| {
tcx.associated_items(impl_)
.find_by_name_and_namespace(tcx, Ident::with_dummy_span(item_name), ns, impl_)
- .map(|item| {
- let fragment = ItemFragment::from_assoc_item(item);
- (Res::Primitive(prim_ty), fragment)
- })
- })
- }
-
- /// Resolves a string as a macro.
- ///
- /// FIXME(jynelson): Can this be unified with `resolve()`?
- fn resolve_macro(
- &self,
- path_str: &'a str,
- item_id: ItemId,
- module_id: DefId,
- ) -> Result<Res, ResolutionFailure<'a>> {
- self.resolve_path(path_str, MacroNS, item_id, module_id).ok_or_else(|| {
- ResolutionFailure::NotResolved {
- item_id,
- module_id,
- partial_res: None,
- unresolved: path_str.into(),
- }
+ .map(|item| (Res::Primitive(prim_ty), item.def_id))
})
}
| DefKind::AssocTy
| DefKind::Variant
| DefKind::Field) => {
- let parent_def_id = tcx.parent(def_id).expect("nested item has no parent");
+ let parent_def_id = tcx.parent(def_id);
if def_kind == DefKind::Field && tcx.def_kind(parent_def_id) == DefKind::Variant
{
- tcx.parent(parent_def_id).expect("variant has no parent")
+ tcx.parent(parent_def_id)
} else {
parent_def_id
}
.copied()
.unwrap_or_else(|| {
self.cx.enter_resolver(|resolver| {
- resolver.resolve_rustdoc_path(path_str, ns, module_id)
+ let parent_scope =
+ ParentScope::module(resolver.expect_module(module_id), resolver);
+ resolver.resolve_rustdoc_path(path_str, ns, parent_scope)
})
})
.and_then(|res| res.try_into().ok())
ns: Namespace,
item_id: ItemId,
module_id: DefId,
- user_fragment: &Option<String>,
- ) -> Result<(Res, Option<UrlFragment>), ErrorKind<'path>> {
- let (res, rustdoc_fragment) = self.resolve_inner(path_str, ns, item_id, module_id)?;
- let chosen_fragment = match (user_fragment, rustdoc_fragment) {
- (Some(_), Some(r_frag)) => {
- let diag_res = match r_frag {
- ItemFragment(_, did) => Res::Def(self.cx.tcx.def_kind(did), did),
- };
- let failure = AnchorFailure::RustdocAnchorConflict(diag_res);
- return Err(ErrorKind::AnchorFailure(failure));
- }
- (Some(u_frag), None) => Some(UrlFragment::UserWritten(u_frag.clone())),
- (None, Some(r_frag)) => Some(UrlFragment::Item(r_frag)),
- (None, None) => None,
- };
- Ok((res, chosen_fragment))
- }
-
- fn resolve_inner<'path>(
- &mut self,
- path_str: &'path str,
- ns: Namespace,
- item_id: ItemId,
- module_id: DefId,
- ) -> Result<(Res, Option<ItemFragment>), ErrorKind<'path>> {
+ ) -> Result<(Res, Option<DefId>), UnresolvedPath<'path>> {
if let Some(res) = self.resolve_path(path_str, ns, item_id, module_id) {
- match res {
- // FIXME(#76467): make this fallthrough to lookup the associated
- // item a separate function.
- Res::Def(DefKind::AssocFn | DefKind::AssocConst, _) => assert_eq!(ns, ValueNS),
- Res::Def(DefKind::AssocTy, _) => assert_eq!(ns, TypeNS),
- Res::Def(DefKind::Variant, _) => {
- return handle_variant(self.cx, res);
+ return Ok(match res {
+ Res::Def(
+ DefKind::AssocFn | DefKind::AssocConst | DefKind::AssocTy | DefKind::Variant,
+ def_id,
+ ) => {
+ let parent_def_id = self.cx.tcx.parent(def_id).unwrap();
+ (Res::from_def_id(self.cx.tcx, parent_def_id), Some(def_id))
}
- // Not a trait item; just return what we found.
- _ => return Ok((res, None)),
- }
+ _ => ((res, None)),
+ });
+ } else if ns == MacroNS {
+ return Err(UnresolvedPath {
+ item_id,
+ module_id,
+ partial_res: None,
+ unresolved: path_str.into(),
+ });
}
// Try looking for methods and associated items.
// 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);
- ResolutionFailure::NotResolved {
+ UnresolvedPath {
item_id,
module_id,
partial_res: None,
resolve_primitive(&path_root, TypeNS)
.or_else(|| self.resolve_path(&path_root, TypeNS, item_id, module_id))
.and_then(|ty_res| {
- let (res, fragment) =
- self.resolve_associated_item(ty_res, item_name, ns, module_id)?;
-
- Some(Ok((res, Some(fragment))))
+ self.resolve_associated_item(ty_res, item_name, ns, module_id).map(Ok)
})
.unwrap_or_else(|| {
if ns == Namespace::ValueNS {
self.variant_field(path_str, item_id, module_id)
} else {
- Err(ResolutionFailure::NotResolved {
+ Err(UnresolvedPath {
item_id,
module_id,
partial_res: None,
unresolved: path_root.into(),
- }
- .into())
+ })
}
})
+ .map(|(res, def_id)| (res, Some(def_id)))
}
/// Convert a DefId to a Res, where possible.
ty::FnPtr(_) => Res::Primitive(Fn),
ty::Never => Res::Primitive(Never),
ty::Adt(ty::AdtDef(Interned(&ty::AdtDefData { did, .. }, _)), _) | ty::Foreign(did) => {
- Res::Def(self.cx.tcx.def_kind(did), did)
+ Res::from_def_id(self.cx.tcx, did)
}
ty::Projection(_)
| ty::Closure(..)
item_name: Symbol,
ns: Namespace,
module_id: DefId,
- ) -> Option<(Res, ItemFragment)> {
+ ) -> Option<(Res, DefId)> {
let tcx = self.cx.tcx;
match root_res {
Res::Primitive(prim) => {
self.resolve_primitive_associated_item(prim, ns, item_name).or_else(|| {
- let assoc_item = self
- .primitive_type_to_ty(prim)
+ self.primitive_type_to_ty(prim)
.map(|ty| {
resolve_associated_trait_item(ty, module_id, item_name, ns, self.cx)
})
- .flatten();
-
- assoc_item.map(|item| {
- let fragment = ItemFragment::from_assoc_item(&item);
- (root_res, fragment)
- })
+ .flatten()
+ .map(|item| (root_res, item.def_id))
})
}
Res::Def(DefKind::TyAlias, did) => {
ty::Adt(adt_def, _) => {
for variant in adt_def.variants() {
if variant.name == item_name {
- return Some((
- root_res,
- ItemFragment(FragmentKind::Variant, variant.def_id),
- ));
+ return Some((root_res, variant.def_id));
}
}
}
debug!("got associated item {:?}", assoc_item);
if let Some(item) = assoc_item {
- let fragment = ItemFragment::from_assoc_item(&item);
- return Some((root_res, fragment));
+ return Some((root_res, item.def_id));
}
if ns != Namespace::ValueNS {
};
let field =
def.non_enum_variant().fields.iter().find(|item| item.name == item_name)?;
- Some((root_res, ItemFragment(FragmentKind::StructField, field.did)))
+ Some((root_res, field.did))
}
Res::Def(DefKind::Trait, did) => tcx
.associated_items(did)
.find_by_name_and_namespace(tcx, Ident::with_dummy_span(item_name), ns, did)
.map(|item| {
- let fragment = ItemFragment::from_assoc_item(item);
let res = Res::Def(item.kind.as_def_kind(), item.def_id);
- (res, fragment)
+ (res, item.def_id)
}),
_ => None,
}
}
+}
- /// Used for reporting better errors.
- ///
- /// Returns whether the link resolved 'fully' in another namespace.
- /// 'fully' here means that all parts of the link resolved, not just some path segments.
- /// This returns the `Res` even if it was erroneous for some reason
- /// (such as having invalid URL fragments or being in the wrong namespace).
- fn check_full_res(
- &mut self,
- ns: Namespace,
- path_str: &str,
- item_id: ItemId,
- module_id: DefId,
- extra_fragment: &Option<String>,
- ) -> Option<Res> {
- // resolve() can't be used for macro namespace
- let result = match ns {
- Namespace::MacroNS => self
- .resolve_macro(path_str, item_id, module_id)
- .map(|res| (res, None))
- .map_err(ErrorKind::from),
- Namespace::TypeNS | Namespace::ValueNS => {
- self.resolve(path_str, ns, item_id, module_id, extra_fragment)
- }
- };
-
- let res = match result {
- Ok((res, frag)) => {
- if let Some(UrlFragment::Item(ItemFragment(_, id))) = frag {
- Some(Res::Def(self.cx.tcx.def_kind(id), id))
- } else {
- Some(res)
- }
- }
- Err(ErrorKind::Resolve(box kind)) => kind.full_res(),
- Err(ErrorKind::AnchorFailure(AnchorFailure::RustdocAnchorConflict(res))) => Some(res),
- Err(ErrorKind::AnchorFailure(AnchorFailure::MultipleAnchors)) => None,
- };
- res
- }
+fn full_res(tcx: TyCtxt<'_>, (base, assoc_item): (Res, Option<DefId>)) -> Res {
+ assoc_item.map_or(base, |def_id| Res::from_def_id(tcx, def_id))
}
/// Look to see if a resolved item has an associated item named `item_name`.
}
enum PreprocessingError {
- Anchor(AnchorFailure),
+ /// User error: `[std#x#y]` is not valid
+ MultipleAnchors,
Disambiguator(Range<usize>, String),
- Resolution(ResolutionFailure<'static>, String, Option<Disambiguator>),
+ MalformedGenerics(MalformedGenerics, String),
}
-impl From<AnchorFailure> for PreprocessingError {
- fn from(err: AnchorFailure) -> Self {
- Self::Anchor(err)
+impl PreprocessingError {
+ fn report(&self, cx: &DocContext<'_>, diag_info: DiagnosticInfo<'_>) {
+ match self {
+ PreprocessingError::MultipleAnchors => report_multiple_anchors(cx, diag_info),
+ PreprocessingError::Disambiguator(range, msg) => {
+ disambiguator_error(cx, diag_info, range.clone(), msg)
+ }
+ PreprocessingError::MalformedGenerics(err, path_str) => {
+ report_malformed_generics(cx, diag_info, *err, path_str)
+ }
+ }
}
}
let extra_fragment = parts.next();
if parts.next().is_some() {
// A valid link can't have multiple #'s
- return Some(Err(AnchorFailure::MultipleAnchors.into()));
+ return Some(Err(PreprocessingError::MultipleAnchors));
}
// Parse and strip the disambiguator from the link, if present.
let path_str = if path_str.contains(['<', '>'].as_slice()) {
match strip_generics_from_path(path_str) {
Ok(path) => path,
- Err(err_kind) => {
+ Err(err) => {
debug!("link has malformed generics: {}", path_str);
- return Some(Err(PreprocessingError::Resolution(
- err_kind,
- path_str.to_owned(),
- disambiguator,
- )));
+ return Some(Err(PreprocessingError::MalformedGenerics(err, path_str.to_owned())));
}
}
} else {
link_range: ori_link.range.clone(),
};
- let PreprocessingInfo { path_str, disambiguator, extra_fragment, link_text } = match pp_link
- {
- Ok(x) => x,
- Err(err) => {
- match err {
- PreprocessingError::Anchor(err) => anchor_failure(self.cx, diag_info, *err),
- PreprocessingError::Disambiguator(range, msg) => {
- disambiguator_error(self.cx, diag_info, range.clone(), msg)
- }
- PreprocessingError::Resolution(err, path_str, disambiguator) => {
- resolution_failure(
- self,
- diag_info,
- path_str,
- *disambiguator,
- smallvec![err.clone()],
- );
- }
- }
- return None;
- }
- };
+ let PreprocessingInfo { path_str, disambiguator, extra_fragment, link_text } =
+ pp_link.as_ref().map_err(|err| err.report(self.cx, diag_info.clone())).ok()?;
let disambiguator = *disambiguator;
- let inner_docs = item.inner_docs(self.cx.tcx);
-
// 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
// 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 inner_docs = item.inner_docs(self.cx.tcx);
let base_node =
if item.is_mod() && inner_docs { self.mod_ids.last().copied() } else { parent_node };
-
- let Some(module_id) = base_node else {
- // This is a bug.
- debug!("attempting to resolve item without parent module: {}", path_str);
- resolution_failure(
- self,
- diag_info,
- path_str,
- disambiguator,
- smallvec![ResolutionFailure::NoParentItem],
- );
- return None;
- };
+ let module_id = base_node.expect("doc link without parent module");
let (mut res, fragment) = self.resolve_with_disambiguator_cached(
ResolutionInfo {
}
}
- let res = self.resolve_with_disambiguator(&key, diag);
+ let res = self.resolve_with_disambiguator(&key, diag.clone()).and_then(|(res, def_id)| {
+ let fragment = match (&key.extra_fragment, def_id) {
+ (Some(_), Some(def_id)) => {
+ report_anchor_conflict(self.cx, diag, Res::from_def_id(self.cx.tcx, def_id));
+ return None;
+ }
+ (Some(u_frag), None) => Some(UrlFragment::UserWritten(u_frag.clone())),
+ (None, Some(def_id)) => Some(UrlFragment::Item(ItemFragment(
+ FragmentKind::from_def_id(self.cx.tcx, def_id),
+ def_id,
+ ))),
+ (None, None) => None,
+ };
+ Some((res, fragment))
+ });
// Cache only if resolved successfully - don't silence duplicate errors
if let Some(res) = res {
&mut self,
key: &ResolutionInfo,
diag: DiagnosticInfo<'_>,
- ) -> Option<(Res, Option<UrlFragment>)> {
+ ) -> Option<(Res, Option<DefId>)> {
let disambiguator = key.dis;
let path_str = &key.path_str;
let item_id = key.item_id;
let base_node = key.module_id;
- let extra_fragment = &key.extra_fragment;
match disambiguator.map(Disambiguator::ns) {
- Some(expected_ns @ (ValueNS | TypeNS)) => {
- match self.resolve(path_str, expected_ns, item_id, base_node, extra_fragment) {
+ Some(expected_ns) => {
+ match self.resolve(path_str, expected_ns, item_id, base_node) {
Ok(res) => Some(res),
- Err(ErrorKind::Resolve(box mut kind)) => {
+ Err(err) => {
// We only looked in one namespace. Try to give a better error if possible.
- if kind.full_res().is_none() {
- let other_ns = if expected_ns == ValueNS { TypeNS } else { ValueNS };
- // FIXME: really it should be `resolution_failure` that does this, not `resolve_with_disambiguator`
- // See https://github.com/rust-lang/rust/pull/76955#discussion_r493953382 for a good approach
- for new_ns in [other_ns, MacroNS] {
- if let Some(res) = self.check_full_res(
- new_ns,
- path_str,
- item_id,
- base_node,
- extra_fragment,
- ) {
- kind = ResolutionFailure::WrongNamespace { res, expected_ns };
+ // FIXME: really it should be `resolution_failure` that does this, not `resolve_with_disambiguator`.
+ // See https://github.com/rust-lang/rust/pull/76955#discussion_r493953382 for a good approach.
+ let mut err = ResolutionFailure::NotResolved(err);
+ for other_ns in [TypeNS, ValueNS, MacroNS] {
+ if other_ns != expected_ns {
+ if let Ok(res) =
+ self.resolve(path_str, other_ns, item_id, base_node)
+ {
+ err = ResolutionFailure::WrongNamespace {
+ res: full_res(self.cx.tcx, res),
+ expected_ns,
+ };
break;
}
}
}
- resolution_failure(self, diag, path_str, disambiguator, smallvec![kind]);
+ resolution_failure(self, diag, path_str, disambiguator, smallvec![err]);
// 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.
None
}
- Err(ErrorKind::AnchorFailure(msg)) => {
- anchor_failure(self.cx, diag, msg);
- None
- }
}
}
None => {
// Try everything!
- let mut candidates = PerNS {
- macro_ns: self
- .resolve_macro(path_str, item_id, base_node)
- .map(|res| (res, extra_fragment.clone().map(UrlFragment::UserWritten))),
- type_ns: match self.resolve(
- path_str,
- TypeNS,
- item_id,
- base_node,
- extra_fragment,
- ) {
- Ok(res) => {
- debug!("got res in TypeNS: {:?}", res);
- Ok(res)
- }
- Err(ErrorKind::AnchorFailure(msg)) => {
- anchor_failure(self.cx, diag, msg);
- return None;
- }
- Err(ErrorKind::Resolve(box kind)) => Err(kind),
- },
- value_ns: match self.resolve(
- path_str,
- ValueNS,
- item_id,
- base_node,
- extra_fragment,
- ) {
- Ok(res) => Ok(res),
- Err(ErrorKind::AnchorFailure(msg)) => {
- anchor_failure(self.cx, diag, msg);
- return None;
- }
- Err(ErrorKind::Resolve(box kind)) => Err(kind),
- }
- .and_then(|(res, fragment)| {
- // Constructors are picked up in the type namespace.
+ let mut candidate = |ns| {
+ self.resolve(path_str, ns, item_id, base_node)
+ .map_err(ResolutionFailure::NotResolved)
+ };
+
+ let candidates = PerNS {
+ macro_ns: candidate(MacroNS),
+ type_ns: candidate(TypeNS),
+ value_ns: candidate(ValueNS).and_then(|(res, def_id)| {
match res {
+ // Constructors are picked up in the type namespace.
Res::Def(DefKind::Ctor(..), _) => {
Err(ResolutionFailure::WrongNamespace { res, expected_ns: TypeNS })
}
- _ => {
- match (fragment, extra_fragment.clone()) {
- (Some(fragment), Some(_)) => {
- // Shouldn't happen but who knows?
- Ok((res, Some(fragment)))
- }
- (fragment, None) => Ok((res, fragment)),
- (None, fragment) => {
- Ok((res, fragment.map(UrlFragment::UserWritten)))
- }
- }
- }
+ _ => Ok((res, def_id)),
}
}),
};
} else if len == 2 && is_derive_trait_collision(&candidates) {
Some(candidates.type_ns.unwrap())
} else {
- if is_derive_trait_collision(&candidates) {
- candidates.macro_ns = Err(ResolutionFailure::Dummy);
- }
+ let ignore_macro = is_derive_trait_collision(&candidates);
// If we're reporting an ambiguity, don't mention the namespaces that failed
- let candidates = candidates.map(|candidate| candidate.ok().map(|(res, _)| res));
+ let mut candidates =
+ candidates.map(|candidate| candidate.ok().map(|(res, _)| res));
+ if ignore_macro {
+ candidates.macro_ns = None;
+ }
ambiguity_error(self.cx, diag, path_str, candidates.present_items().collect());
None
}
}
- Some(MacroNS) => {
- match self.resolve_macro(path_str, item_id, base_node) {
- Ok(res) => Some((res, extra_fragment.clone().map(UrlFragment::UserWritten))),
- Err(mut kind) => {
- // `resolve_macro` only looks in the macro namespace. Try to give a better error if possible.
- for ns in [TypeNS, ValueNS] {
- if let Some(res) = self.check_full_res(
- ns,
- path_str,
- item_id,
- base_node,
- extra_fragment,
- ) {
- kind =
- ResolutionFailure::WrongNamespace { res, expected_ns: MacroNS };
- break;
- }
- }
- resolution_failure(self, diag, path_str, disambiguator, smallvec![kind]);
- None
- }
- }
- }
}
}
}
}
variants_seen.push(variant);
- if let ResolutionFailure::NotResolved {
+ if let ResolutionFailure::NotResolved(UnresolvedPath {
item_id,
module_id,
partial_res,
unresolved,
- } = &mut failure
+ }) = &mut failure
{
use DefKind::*;
};
name = start;
for ns in [TypeNS, ValueNS, MacroNS] {
- if let Some(res) =
- collector.check_full_res(ns, start, item_id, module_id, &None)
- {
+ if let Ok(res) = collector.resolve(start, ns, item_id, module_id) {
debug!("found partial_res={:?}", res);
- *partial_res = Some(res);
+ *partial_res = Some(full_res(collector.cx.tcx, res));
*unresolved = end.into();
break 'outer;
}
}
let note = match failure {
ResolutionFailure::NotResolved { .. } => unreachable!("handled above"),
- ResolutionFailure::Dummy => continue,
ResolutionFailure::WrongNamespace { res, expected_ns } => {
suggest_disambiguator(res, diag, path_str, diag_info.ori_link, sp);
expected_ns.descr()
)
}
- ResolutionFailure::NoParentItem => {
- // FIXME(eddyb) this doesn't belong here, whatever made
- // the `ResolutionFailure::NoParentItem` should emit an
- // immediate or delayed `span_bug` about the issue.
- tcx.sess.delay_span_bug(
- sp.unwrap_or(DUMMY_SP),
- "intra-doc link missing parent item",
- );
-
- "BUG: all intra-doc links should have a parent item".to_owned()
- }
- ResolutionFailure::MalformedGenerics(variant) => match variant {
- MalformedGenerics::UnbalancedAngleBrackets => {
- String::from("unbalanced angle brackets")
- }
- MalformedGenerics::MissingType => {
- String::from("missing type for generic parameters")
- }
- MalformedGenerics::HasFullyQualifiedSyntax => {
- diag.note("see https://github.com/rust-lang/rust/issues/74563 for more information");
- String::from("fully-qualified syntax is unsupported")
- }
- MalformedGenerics::InvalidPathSeparator => {
- String::from("has invalid path separator")
- }
- MalformedGenerics::TooManyAngleBrackets => {
- String::from("too many angle brackets")
- }
- MalformedGenerics::EmptyAngleBrackets => {
- String::from("empty angle brackets")
- }
- },
};
if let Some(span) = sp {
diag.span_label(span, ¬e);
);
}
-/// Report an anchor failure.
-fn anchor_failure(cx: &DocContext<'_>, diag_info: DiagnosticInfo<'_>, failure: AnchorFailure) {
- let (msg, anchor_idx) = match failure {
- AnchorFailure::MultipleAnchors => {
- (format!("`{}` contains multiple anchors", diag_info.ori_link), 1)
- }
- AnchorFailure::RustdocAnchorConflict(res) => (
- format!(
- "`{}` contains an anchor, but links to {kind}s are already anchored",
- diag_info.ori_link,
- kind = res.descr(),
- ),
- 0,
- ),
- };
+fn report_multiple_anchors(cx: &DocContext<'_>, diag_info: DiagnosticInfo<'_>) {
+ let msg = format!("`{}` contains multiple anchors", diag_info.ori_link);
+ anchor_failure(cx, diag_info, &msg, 1)
+}
+
+fn report_anchor_conflict(cx: &DocContext<'_>, diag_info: DiagnosticInfo<'_>, res: Res) {
+ let (link, kind) = (diag_info.ori_link, res.descr());
+ let msg = format!("`{link}` contains an anchor, but links to {kind}s are already anchored");
+ anchor_failure(cx, diag_info, &msg, 0)
+}
+/// Report an anchor failure.
+fn anchor_failure(
+ cx: &DocContext<'_>,
+ diag_info: DiagnosticInfo<'_>,
+ msg: &str,
+ anchor_idx: usize,
+) {
report_diagnostic(cx.tcx, BROKEN_INTRA_DOC_LINKS, &msg, &diag_info, |diag, sp| {
if let Some(mut sp) = sp {
if let Some((fragment_offset, _)) =
}
diag.span_label(sp, "invalid anchor");
}
- if let AnchorFailure::RustdocAnchorConflict(Res::Primitive(_)) = failure {
- if let Some(sp) = sp {
- span_bug!(sp, "anchors should be allowed now");
- } else {
- bug!("anchors should be allowed now");
- }
- }
});
}
});
}
+fn report_malformed_generics(
+ cx: &DocContext<'_>,
+ diag_info: DiagnosticInfo<'_>,
+ err: MalformedGenerics,
+ path_str: &str,
+) {
+ report_diagnostic(
+ cx.tcx,
+ BROKEN_INTRA_DOC_LINKS,
+ &format!("unresolved link to `{}`", path_str),
+ &diag_info,
+ |diag, sp| {
+ let note = match err {
+ MalformedGenerics::UnbalancedAngleBrackets => "unbalanced angle brackets",
+ MalformedGenerics::MissingType => "missing type for generic parameters",
+ MalformedGenerics::HasFullyQualifiedSyntax => {
+ diag.note(
+ "see https://github.com/rust-lang/rust/issues/74563 for more information",
+ );
+ "fully-qualified syntax is unsupported"
+ }
+ MalformedGenerics::InvalidPathSeparator => "has invalid path separator",
+ MalformedGenerics::TooManyAngleBrackets => "too many angle brackets",
+ MalformedGenerics::EmptyAngleBrackets => "empty angle brackets",
+ };
+ if let Some(span) = sp {
+ diag.span_label(span, note);
+ } else {
+ diag.note(note);
+ }
+ },
+ );
+}
+
/// Report an ambiguity error, where there were multiple possible resolutions.
fn ambiguity_error(
cx: &DocContext<'_>,
});
}
-/// Given an enum variant's res, return the res of its enum and the associated fragment.
-fn handle_variant(
- cx: &DocContext<'_>,
- res: Res,
-) -> Result<(Res, Option<ItemFragment>), ErrorKind<'static>> {
- cx.tcx
- .parent(res.def_id(cx.tcx))
- .map(|parent| {
- let parent_def = Res::Def(DefKind::Enum, parent);
- let variant = cx.tcx.expect_variant_res(res.as_hir_res().unwrap());
- (parent_def, Some(ItemFragment(FragmentKind::Variant, variant.def_id)))
- })
- .ok_or_else(|| ResolutionFailure::NoParentItem.into())
-}
-
/// Resolve a primitive type or value.
fn resolve_primitive(path_str: &str, ns: Namespace) -> Option<Res> {
if ns != TypeNS {
Some(Res::Primitive(prim))
}
-fn strip_generics_from_path(path_str: &str) -> Result<String, ResolutionFailure<'static>> {
+fn strip_generics_from_path(path_str: &str) -> Result<String, MalformedGenerics> {
let mut stripped_segments = vec![];
let mut path = path_str.chars().peekable();
let mut segment = Vec::new();
stripped_segments.push(stripped_segment);
}
} else {
- return Err(ResolutionFailure::MalformedGenerics(
- MalformedGenerics::InvalidPathSeparator,
- ));
+ return Err(MalformedGenerics::InvalidPathSeparator);
}
}
'<' => {
match path.next() {
Some('<') => {
- return Err(ResolutionFailure::MalformedGenerics(
- MalformedGenerics::TooManyAngleBrackets,
- ));
+ return Err(MalformedGenerics::TooManyAngleBrackets);
}
Some('>') => {
- return Err(ResolutionFailure::MalformedGenerics(
- MalformedGenerics::EmptyAngleBrackets,
- ));
+ return Err(MalformedGenerics::EmptyAngleBrackets);
}
Some(chr) => {
segment.push(chr);
let stripped_path = stripped_segments.join("::");
- if !stripped_path.is_empty() {
- Ok(stripped_path)
- } else {
- Err(ResolutionFailure::MalformedGenerics(MalformedGenerics::MissingType))
- }
+ if !stripped_path.is_empty() { Ok(stripped_path) } else { Err(MalformedGenerics::MissingType) }
}
-fn strip_generics_from_path_segment(
- segment: Vec<char>,
-) -> Result<String, ResolutionFailure<'static>> {
+fn strip_generics_from_path_segment(segment: Vec<char>) -> Result<String, MalformedGenerics> {
let mut stripped_segment = String::new();
let mut param_depth = 0;
if latest_generics_chunk.contains(" as ") {
// The segment tries to use fully-qualified syntax, which is currently unsupported.
// Give a helpful error message instead of completely ignoring the angle brackets.
- return Err(ResolutionFailure::MalformedGenerics(
- MalformedGenerics::HasFullyQualifiedSyntax,
- ));
+ return Err(MalformedGenerics::HasFullyQualifiedSyntax);
}
} else {
if param_depth == 0 {
Ok(stripped_segment)
} else {
// The segment has unbalanced angle brackets, e.g. `Vec<T` or `Vec<T>>`
- Err(ResolutionFailure::MalformedGenerics(MalformedGenerics::UnbalancedAngleBrackets))
+ Err(MalformedGenerics::UnbalancedAngleBrackets)
}
}