use rustc_data_structures::stable_set::FxHashSet;
use rustc_errors::{Applicability, DiagnosticBuilder};
use rustc_expand::base::SyntaxExtensionKind;
-use rustc_feature::UnstableFeatures;
use rustc_hir as hir;
use rustc_hir::def::{
DefKind,
use rustc_hir::def_id::DefId;
use rustc_middle::ty;
use rustc_resolve::ParentScope;
-use rustc_session::lint;
+use rustc_session::lint::{
+ builtin::{BROKEN_INTRA_DOC_LINKS, PRIVATE_INTRA_DOC_LINKS},
+ Lint,
+};
use rustc_span::hygiene::MacroKind;
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;
};
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);
+ let mut coll = LinkCollector::new(cx);
+ coll.fold_crate(krate)
+}
- coll.fold_crate(krate)
+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)
}
}
-enum ErrorKind {
- ResolutionFailure,
- 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),
+ /// The link failed to resolve. `resolution_failure` should look to see if there's
+ /// a more helpful error that can be given.
+ NotResolved { module_id: DefId, partial_res: Option<Res>, unresolved: Cow<'a, str> },
+ /// should not ever happen
+ NoParentItem,
+ /// used to communicate that this should be ignored, but shouldn't be reported to the user
+ Dummy,
+}
+
+impl ResolutionFailure<'a> {
+ // 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,
- Variant,
- AssocConstant,
- AssocType,
- Field,
- Method,
+ RustdocAnchorConflict(Res),
}
struct LinkCollector<'a, 'tcx> {
/// 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;
+ let no_res = || ResolutionFailure::NotResolved {
+ module_id,
+ partial_res: None,
+ unresolved: path_str.into(),
+ };
+ 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_name =
- split.next().map(|f| Symbol::intern(f)).ok_or(ErrorKind::ResolutionFailure)?;
+ let (variant_field_str, variant_field_name) = split
+ .next()
+ .map(|f| (f, Symbol::intern(f)))
+ .expect("fold_item should ensure link is non-empty");
+ let (variant_str, variant_name) =
+ // we're not sure this is a variant at all, so use the full string
+ // If there's no second component, the link looks like `[path]`.
+ // So there's no partial res and we should say the whole link failed to resolve.
+ split.next().map(|f| (f, Symbol::intern(f))).ok_or_else(no_res)?;
let path = split
.next()
.map(|f| {
}
f.to_owned()
})
- .ok_or(ErrorKind::ResolutionFailure)?;
- let (_, ty_res) = cx
+ // If there's no third component, we saw `[a::b]` before and it failed to resolve.
+ // So there's no partial res.
+ .ok_or_else(no_res)?;
+ let ty_res = cx
.enter_resolver(|resolver| {
resolver.resolve_str_path_error(DUMMY_SP, &path, TypeNS, module_id)
})
- .map_err(|_| ErrorKind::ResolutionFailure)?;
+ .map(|(_, res)| res)
+ .unwrap_or(Res::Err);
if let Res::Err = ty_res {
- return Err(ErrorKind::ResolutionFailure);
+ return Err(no_res().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(ResolutionFailure::Dummy.into());
}
- 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((
ty_res,
Some(format!(
"variant.{}.field.{}",
- variant_name, variant_field_name
+ variant_str, variant_field_name
)),
))
} else {
- Err(ErrorKind::ResolutionFailure)
+ Err(ResolutionFailure::NotResolved {
+ module_id,
+ partial_res: Some(Res::Def(DefKind::Enum, def.did)),
+ unresolved: variant_field_str.into(),
+ }
+ .into())
}
}
- _ => Err(ErrorKind::ResolutionFailure),
+ _ => unreachable!(),
}
}
- _ => Err(ErrorKind::ResolutionFailure),
+ _ => Err(ResolutionFailure::NotResolved {
+ module_id,
+ partial_res: Some(ty_res),
+ unresolved: variant_str.into(),
+ }
+ .into()),
}
}
/// 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,
+ module_id: 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 {
- 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);
- }
+ 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 Ok(res);
}
- } else {
- debug!("attempting to resolve item without parent module: {}", path_str);
}
- None
+ Err(ResolutionFailure::NotResolved {
+ module_id,
+ partial_res: None,
+ unresolved: 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>,
+ module_id: 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.
- if let Some(module_id) = parent_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),
- };
+ 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(()),
+ x => x,
+ };
- 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(AnchorFailure::Primitive));
- }
- return Ok((res, Some(path_str.to_owned())));
- }
- Res::Def(DefKind::Mod, _) => {
- return Ok((res, extra_fragment.clone()));
- }
- _ => {
- return Ok((res, extra_fragment.clone()));
+ 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(ty) => {
+ if extra_fragment.is_some() {
+ return Err(ErrorKind::AnchorFailure(
+ AnchorFailure::RustdocAnchorConflict(res),
+ ));
}
- };
-
- if value != (ns == ValueNS) {
- return Err(ErrorKind::ResolutionFailure);
+ return Ok((res, Some(ty.name_str().to_owned())));
+ }
+ Res::Def(DefKind::Mod, _) => {
+ return Ok((res, extra_fragment.clone()));
}
- } else if let Some((path, prim)) = is_primitive(path_str, ns) {
- if extra_fragment.is_some() {
- return Err(ErrorKind::AnchorFailure(AnchorFailure::Primitive));
+ _ => {
+ return Ok((res, extra_fragment.clone()));
}
- return Ok((prim, Some(path.to_owned())));
+ };
+
+ if value != (ns == ValueNS) {
+ return Err(ResolutionFailure::WrongNamespace(res, ns).into());
+ }
+ // FIXME: why is this necessary?
+ } else if let Some((path, prim)) = is_primitive(path_str, ns) {
+ if extra_fragment.is_some() {
+ return Err(ErrorKind::AnchorFailure(AnchorFailure::RustdocAnchorConflict(prim)));
}
+ return Ok((prim, Some(path.to_owned())));
+ }
- // 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((path, prim)) = is_primitive(&path, TypeNS) {
- for &impl_ in primitive_impl(cx, &path).ok_or(ErrorKind::ResolutionFailure)? {
- let link = cx
- .tcx
- .associated_items(impl_)
- .find_by_name_and_namespace(
- cx.tcx,
- Ident::with_dummy_span(item_name),
- ns,
- impl_,
- )
- .and_then(|item| match item.kind {
- ty::AssocKind::Fn => Some("method"),
- _ => None,
- })
- .map(|out| (prim, Some(format!("{}#{}.{}", path, out, item_name))));
- if let Some(link) = link {
- return Ok(link);
+ // Try looking for methods and associated items.
+ let mut split = path_str.rsplitn(2, "::");
+ // this can be an `unwrap()` because we ensure the link is never empty
+ let (item_str, item_name) = split.next().map(|i| (i, Symbol::intern(i))).unwrap();
+ let path_root = split
+ .next()
+ .map(|f| {
+ if f == "self" || f == "Self" {
+ if let Some(name) = current_item.as_ref() {
+ return name.clone();
}
}
- return Err(ErrorKind::ResolutionFailure);
+ f.to_owned()
+ })
+ // 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);
+ ResolutionFailure::NotResolved {
+ module_id,
+ partial_res: None,
+ unresolved: item_str.into(),
+ }
+ })?;
+
+ if let Some((path, prim)) = is_primitive(&path_root, TypeNS) {
+ let impls =
+ primitive_impl(cx, &path).ok_or_else(|| ResolutionFailure::NotResolved {
+ module_id,
+ partial_res: Some(prim),
+ unresolved: item_str.into(),
+ })?;
+ for &impl_ in impls {
+ let link = cx
+ .tcx
+ .associated_items(impl_)
+ .find_by_name_and_namespace(
+ cx.tcx,
+ Ident::with_dummy_span(item_name),
+ ns,
+ impl_,
+ )
+ .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_str))));
+ if let Some(link) = link {
+ return Ok(link);
+ }
}
+ debug!(
+ "returning primitive error for {}::{} in {} namespace",
+ path,
+ item_name,
+ ns.descr()
+ );
+ return Err(ResolutionFailure::NotResolved {
+ module_id,
+ partial_res: Some(prim),
+ unresolved: item_str.into(),
+ }
+ .into());
+ }
- 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 {
+ let ty_res = cx
+ .enter_resolver(|resolver| {
+ // only types can have associated items
+ resolver.resolve_str_path_error(DUMMY_SP, &path_root, TypeNS, module_id)
+ })
+ .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 {
- Err(ErrorKind::ResolutionFailure)
+ Err(ResolutionFailure::NotResolved {
+ module_id,
+ partial_res: None,
+ unresolved: path_root.into(),
+ }
+ .into())
};
}
- let ty_res = ty_res.map_id(|_| panic!("unexpected node_id"));
- let res = match ty_res {
- Res::Def(
- DefKind::Struct | DefKind::Union | DefKind::Enum | DefKind::TyAlias,
- did,
- ) => {
- debug!("looking for associated item named {} for item {:?}", item_name, did);
- // Checks if item_name belongs to `impl SomeItem`
- let kind = cx
- .tcx
- .inherent_impls(did)
- .iter()
- .flat_map(|&imp| {
- cx.tcx.associated_items(imp).find_by_name_and_namespace(
- cx.tcx,
- Ident::with_dummy_span(item_name),
- ns,
- imp,
- )
- })
- .map(|item| item.kind)
- // 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`
- // This gives precedence to `impl SomeItem`:
- // Although having both would be ambiguous, use impl version for compat. sake.
- // To handle that properly resolve() would have to support
- // something like [`ambi_fn`](<SomeStruct as SomeTrait>::ambi_fn)
- .or_else(|| {
- let kind = resolve_associated_trait_item(
- did, module_id, item_name, ns, &self.cx,
- );
- debug!("got associated item kind {:?}", kind);
- kind
- });
-
- if let Some(kind) = kind {
- let out = match kind {
- ty::AssocKind::Fn => "method",
- ty::AssocKind::Const => "associatedconstant",
- ty::AssocKind::Type => "associatedtype",
- };
- Some(if extra_fragment.is_some() {
- Err(ErrorKind::AnchorFailure(if kind == ty::AssocKind::Fn {
- AnchorFailure::Method
+ Ok(res) => res,
+ };
+ let ty_res = ty_res.map_id(|_| panic!("unexpected node_id"));
+ let res = match ty_res {
+ Res::Def(DefKind::Struct | DefKind::Union | DefKind::Enum | DefKind::TyAlias, did) => {
+ debug!("looking for associated item named {} for item {:?}", item_name, did);
+ // Checks if item_name belongs to `impl SomeItem`
+ let assoc_item = cx
+ .tcx
+ .inherent_impls(did)
+ .iter()
+ .flat_map(|&imp| {
+ cx.tcx.associated_items(imp).find_by_name_and_namespace(
+ cx.tcx,
+ Ident::with_dummy_span(item_name),
+ ns,
+ imp,
+ )
+ })
+ .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`
+ // This gives precedence to `impl SomeItem`:
+ // Although having both would be ambiguous, use impl version for compat. sake.
+ // To handle that properly resolve() would have to support
+ // something like [`ambi_fn`](<SomeStruct as SomeTrait>::ambi_fn)
+ .or_else(|| {
+ let kind =
+ resolve_associated_trait_item(did, module_id, item_name, ns, &self.cx);
+ debug!("got associated item kind {:?}", kind);
+ kind
+ });
+
+ if let Some((kind, id)) = assoc_item {
+ let out = match kind {
+ ty::AssocKind::Fn => "method",
+ ty::AssocKind::Const => "associatedconstant",
+ ty::AssocKind::Type => "associatedtype",
+ };
+ Some(if extra_fragment.is_some() {
+ Err(ErrorKind::AnchorFailure(AnchorFailure::RustdocAnchorConflict(ty_res)))
+ } else {
+ // 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(), id)));
+ Ok((ty_res, Some(format!("{}.{}", out, item_str))))
+ })
+ } else if ns == Namespace::ValueNS {
+ 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 {
- AnchorFailure::AssocConstant
- }))
- } else {
- // 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()));
- Ok((ty_res, Some(format!("{}.{}", out, item_name))))
- })
- } else if ns == Namespace::ValueNS {
- 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 {
- def.non_enum_variant()
- .fields
- .iter()
- .find(|item| item.ident.name == item_name)
- };
- field.map(|item| {
- if extra_fragment.is_some() {
- Err(ErrorKind::AnchorFailure(if def.is_enum() {
- AnchorFailure::Variant
+ def.non_enum_variant()
+ .fields
+ .iter()
+ .find(|item| item.ident.name == item_name)
+ };
+ field.map(|item| {
+ if extra_fragment.is_some() {
+ let res = Res::Def(
+ if def.is_enum() {
+ DefKind::Variant
} else {
- AnchorFailure::Field
- }))
- } else {
- Ok((
- ty_res,
- Some(format!(
- "{}.{}",
- if def.is_enum() {
- "variant"
- } else {
- "structfield"
- },
- item.ident
- )),
- ))
- }
- })
+ DefKind::Field
+ },
+ item.did,
+ );
+ Err(ErrorKind::AnchorFailure(
+ AnchorFailure::RustdocAnchorConflict(res),
+ ))
+ } else {
+ Ok((
+ ty_res,
+ Some(format!(
+ "{}.{}",
+ if def.is_enum() { "variant" } else { "structfield" },
+ item.ident
+ )),
+ ))
+ }
+ })
+ }
+ _ => None,
+ }
+ } else {
+ // We already know this isn't in ValueNS, so no need to check variant_field
+ return Err(ResolutionFailure::NotResolved {
+ module_id,
+ partial_res: Some(ty_res),
+ unresolved: item_str.into(),
+ }
+ .into());
+ }
+ }
+ Res::Def(DefKind::Trait, did) => cx
+ .tcx
+ .associated_items(did)
+ .find_by_name_and_namespace(cx.tcx, Ident::with_dummy_span(item_name), ns, did)
+ .map(|item| {
+ let kind = match item.kind {
+ ty::AssocKind::Const => "associatedconstant",
+ ty::AssocKind::Type => "associatedtype",
+ ty::AssocKind::Fn => {
+ if item.defaultness.has_value() {
+ "method"
+ } else {
+ "tymethod"
}
- _ => None,
}
+ };
+
+ if extra_fragment.is_some() {
+ Err(ErrorKind::AnchorFailure(AnchorFailure::RustdocAnchorConflict(ty_res)))
} else {
- // We already know this isn't in ValueNS, so no need to check variant_field
- return Err(ErrorKind::ResolutionFailure);
+ let res = Res::Def(item.kind.as_def_kind(), item.def_id);
+ Ok((res, Some(format!("{}.{}", kind, item_str))))
}
+ }),
+ _ => None,
+ };
+ res.unwrap_or_else(|| {
+ if ns == Namespace::ValueNS {
+ self.variant_field(path_str, current_item, module_id)
+ } else {
+ Err(ResolutionFailure::NotResolved {
+ module_id,
+ partial_res: Some(ty_res),
+ unresolved: item_str.into(),
}
- Res::Def(DefKind::Trait, did) => cx
- .tcx
- .associated_items(did)
- .find_by_name_and_namespace(cx.tcx, Ident::with_dummy_span(item_name), ns, did)
- .map(|item| {
- let kind = match item.kind {
- ty::AssocKind::Const => "associatedconstant",
- ty::AssocKind::Type => "associatedtype",
- ty::AssocKind::Fn => {
- if item.defaultness.has_value() {
- "method"
- } else {
- "tymethod"
- }
- }
- };
+ .into())
+ }
+ })
+ }
- if extra_fragment.is_some() {
- Err(ErrorKind::AnchorFailure(if item.kind == ty::AssocKind::Const {
- AnchorFailure::AssocConstant
- } else if item.kind == ty::AssocKind::Type {
- AnchorFailure::AssocType
- } else {
- AnchorFailure::Method
- }))
- } else {
- let res = Res::Def(item.kind.as_def_kind(), item.def_id);
- Ok((res, Some(format!("{}.{}", kind, item_name))))
- }
- }),
- _ => None,
- };
- res.unwrap_or_else(|| {
- if ns == Namespace::ValueNS {
- self.variant_field(path_str, current_item, module_id)
- } else {
- Err(ErrorKind::ResolutionFailure)
+ /// 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(
+ &self,
+ ns: Namespace,
+ path_str: &str,
+ module_id: DefId,
+ current_item: &Option<String>,
+ extra_fragment: &Option<String>,
+ ) -> Option<Res> {
+ let check_full_res_inner = |this: &Self, result: Result<Res, ErrorKind<'_>>| {
+ let res = match result {
+ Ok(res) => Some(res),
+ Err(ErrorKind::Resolve(box kind)) => kind.full_res(),
+ Err(ErrorKind::AnchorFailure(AnchorFailure::RustdocAnchorConflict(res))) => {
+ Some(res)
}
- })
- } else {
- debug!("attempting to resolve item without parent module: {}", path_str);
- Err(ErrorKind::ResolutionFailure)
+ Err(ErrorKind::AnchorFailure(AnchorFailure::MultipleAnchors)) => None,
+ };
+ this.kind_side_channel.take().map(|(kind, id)| Res::Def(kind, id)).or(res)
+ };
+ // cannot be used for macro namespace
+ let check_full_res = |this: &Self, ns| {
+ let result = this.resolve(path_str, ns, current_item, module_id, extra_fragment);
+ check_full_res_inner(this, result.map(|(res, _)| res))
+ };
+ let check_full_res_macro = |this: &Self| {
+ let result = this.macro_resolve(path_str, module_id);
+ check_full_res_inner(this, result.map_err(ErrorKind::from))
+ };
+ match ns {
+ Namespace::MacroNS => check_full_res_macro(self),
+ Namespace::TypeNS | Namespace::ValueNS => check_full_res(self, ns),
}
}
}
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 trait_ref = cx.tcx.impl_trait_ref(impl_).expect("this is not an inherent impl");
// Check if these are the same type.
let impl_type = trait_ref.self_ty();
- debug!(
+ trace!(
"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;
}
}
self.mod_ids.push(item.def_id);
}
- let cx = self.cx;
let dox = item.attrs.collapsed_doc_value().unwrap_or_else(String::new);
trace!("got documentation '{}'", dox);
});
for (ori_link, link_range) in markdown_links(&dox) {
- trace!("considering link '{}'", ori_link);
+ self.resolve_link(
+ &mut item,
+ &dox,
+ ¤t_item,
+ parent_node,
+ &parent_name,
+ ori_link,
+ link_range,
+ );
+ }
- // Bail early for real links.
- if ori_link.contains('/') {
- continue;
+ if item.is_mod() && !item.attrs.inner_docs {
+ self.mod_ids.push(item.def_id);
+ }
+
+ if item.is_mod() {
+ let ret = self.fold_item_recur(item);
+
+ self.mod_ids.pop();
+
+ ret
+ } else {
+ self.fold_item_recur(item)
+ }
+ }
+}
+
+impl LinkCollector<'_, '_> {
+ fn resolve_link(
+ &self,
+ item: &mut Item,
+ dox: &str,
+ current_item: &Option<String>,
+ parent_node: Option<DefId>,
+ parent_name: &Option<String>,
+ ori_link: String,
+ link_range: Option<Range<usize>>,
+ ) {
+ trace!("considering link '{}'", ori_link);
+
+ // Bail early for real links.
+ if ori_link.contains('/') {
+ return;
+ }
+
+ // [] is mostly likely not supposed to be a link
+ if ori_link.is_empty() {
+ return;
+ }
+
+ let cx = self.cx;
+ let link = ori_link.replace("`", "");
+ let parts = link.split('#').collect::<Vec<_>>();
+ let (link, extra_fragment) = if parts.len() > 2 {
+ anchor_failure(cx, &item, &link, dox, link_range, AnchorFailure::MultipleAnchors);
+ return;
+ } 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!
+ return;
}
+ (parts[0], Some(parts[1].to_owned()))
+ } else {
+ (parts[0], None)
+ };
+ let resolved_self;
+ let link_text;
+ let mut path_str;
+ let disambiguator;
+ let (mut res, mut fragment) = {
+ path_str = if let Ok((d, path)) = Disambiguator::from_str(&link) {
+ disambiguator = Some(d);
+ path
+ } else {
+ disambiguator = None;
+ &link
+ }
+ .trim();
- // [] is mostly likely not supposed to be a link
- if ori_link.is_empty() {
- continue;
+ if path_str.contains(|ch: char| !(ch.is_alphanumeric() || ch == ':' || ch == '_')) {
+ return;
}
- let link = ori_link.replace("`", "");
- let parts = link.split('#').collect::<Vec<_>>();
- let (link, extra_fragment) = if parts.len() > 2 {
- anchor_failure(cx, &item, &link, &dox, link_range, AnchorFailure::MultipleAnchors);
- 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()))
+ // 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
+ // 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 {
+ self.mod_ids.last().copied()
} else {
- (parts[0].to_owned(), None)
+ parent_node
};
- let resolved_self;
- let mut path_str;
- let disambiguator;
- let (mut res, mut fragment) = {
- path_str = if let Ok((d, path)) = Disambiguator::from_str(&link) {
- disambiguator = Some(d);
- path
- } else {
- disambiguator = None;
- &link
- }
- .trim();
- if path_str.contains(|ch: char| !(ch.is_alphanumeric() || ch == ':' || ch == '_')) {
- continue;
+ let mut module_id = if let Some(id) = base_node {
+ id
+ } else {
+ debug!("attempting to resolve item without parent module: {}", path_str);
+ let err_kind = ResolutionFailure::NoParentItem.into();
+ resolution_failure(
+ self,
+ &item,
+ path_str,
+ disambiguator,
+ dox,
+ link_range,
+ smallvec![err_kind],
+ );
+ return;
+ };
+
+ // 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;
}
+ } else if path_str.starts_with("crate::") {
+ use rustc_span::def_id::CRATE_DEF_INDEX;
+
+ // HACK(jynelson): rustc_resolve thinks that `crate` is the crate currently being documented.
+ // But rustdoc wants it to mean the crate this item was originally present in.
+ // To work around this, remove it and resolve relative to the crate root instead.
+ // HACK(jynelson)(2): If we just strip `crate::` then suddenly primitives become ambiguous
+ // (consider `crate::char`). Instead, change it to `self::`. This works because 'self' is now the crate root.
+ resolved_self = format!("self::{}", &path_str["crate::".len()..]);
+ path_str = &resolved_self;
+ module_id = DefId { krate: item.def_id.krate, index: CRATE_DEF_INDEX };
+ }
- // 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 {
- self.mod_ids.last().copied()
- } else {
- parent_node
- };
+ match self.resolve_with_disambiguator(
+ disambiguator,
+ item,
+ dox,
+ path_str,
+ current_item,
+ module_id,
+ extra_fragment,
+ &ori_link,
+ link_range.clone(),
+ ) {
+ Some(x) => x,
+ None => return,
+ }
+ };
- // 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;
+ // Check for a primitive which might conflict with a module
+ // Report the ambiguity and require that the user specify which one they meant.
+ // FIXME: could there ever be a primitive not in the type namespace?
+ if matches!(
+ disambiguator,
+ None | Some(Disambiguator::Namespace(Namespace::TypeNS) | Disambiguator::Primitive)
+ ) && !matches!(res, Res::PrimTy(_))
+ {
+ if let Some((path, prim)) = is_primitive(path_str, TypeNS) {
+ // `prim@char`
+ if matches!(disambiguator, Some(Disambiguator::Primitive)) {
+ if fragment.is_some() {
+ anchor_failure(
+ cx,
+ &item,
+ path_str,
+ dox,
+ link_range,
+ AnchorFailure::RustdocAnchorConflict(prim),
+ );
+ return;
}
+ res = prim;
+ fragment = Some(path.to_owned());
+ } else {
+ // `[char]` when a `char` module is in scope
+ let candidates = vec![res, prim];
+ ambiguity_error(cx, &item, path_str, dox, link_range, candidates);
+ return;
}
+ }
+ }
- 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);
- // 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;
- }
- }
+ 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);
+ let callback = |diag: &mut DiagnosticBuilder<'_>, sp| {
+ let note = format!(
+ "this link resolved to {} {}, which is not {} {}",
+ resolved.article(),
+ resolved.descr(),
+ specified.article(),
+ specified.descr()
+ );
+ diag.note(¬e);
+ suggest_disambiguator(resolved, diag, path_str, dox, sp, &link_range);
+ };
+ report_diagnostic(cx, BROKEN_INTRA_DOC_LINKS, &msg, &item, dox, &link_range, callback);
+ };
+ if let Res::PrimTy(..) = res {
+ match disambiguator {
+ Some(Disambiguator::Primitive | Disambiguator::Namespace(_)) | None => {
+ item.attrs.links.push(ItemLink {
+ link: ori_link,
+ link_text,
+ did: None,
+ fragment,
+ });
+ }
+ Some(other) => {
+ report_mismatch(other, Disambiguator::Primitive);
+ return;
+ }
+ }
+ } else {
+ debug!("intra-doc link to {} resolved to {:?}", path_str, res);
+
+ // 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().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.
+ | (DefKind::Fn | DefKind::AssocFn, Some(Disambiguator::Kind(DefKind::Fn)))
+ // These are namespaces; allow anything in the namespace to match
+ | (_, Some(Disambiguator::Namespace(_)))
+ // If no disambiguator given, allow anything
+ | (_, None)
+ // All of these are valid, so do nothing
+ => {}
+ (actual, Some(Disambiguator::Kind(expected))) if actual == expected => {}
+ (_, Some(specified @ Disambiguator::Kind(_) | specified @ Disambiguator::Primitive)) => {
+ report_mismatch(specified, Disambiguator::Kind(kind));
+ return;
}
- 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,
- TypeNS,
- ¤t_item,
- base_node,
- &extra_fragment,
- ) {
- Ok(res) => {
- debug!("got res in TypeNS: {:?}", res);
- Some(res)
- }
- Err(ErrorKind::AnchorFailure(msg)) => {
- anchor_failure(cx, &item, &ori_link, &dox, link_range, msg);
- continue;
- }
- Err(ErrorKind::ResolutionFailure) => None,
- },
- value_ns: match self.resolve(
- path_str,
- ValueNS,
- ¤t_item,
- base_node,
- &extra_fragment,
- ) {
- Ok(res) => Some(res),
- Err(ErrorKind::AnchorFailure(msg)) => {
- anchor_failure(cx, &item, &ori_link, &dox, link_range, msg);
- continue;
- }
- Err(ErrorKind::ResolutionFailure) => None,
- }
- .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;
- }
+ // item can be non-local e.g. when using #[doc(primitive = "pointer")]
+ if let Some((src_id, dst_id)) = res
+ .opt_def_id()
+ .and_then(|def_id| def_id.as_local())
+ .and_then(|dst_id| item.def_id.as_local().map(|src_id| (src_id, dst_id)))
+ {
+ use rustc_hir::def_id::LOCAL_CRATE;
- let len = candidates.clone().present_items().count();
+ let hir_src = self.cx.tcx.hir().local_def_id_to_hir_id(src_id);
+ let hir_dst = self.cx.tcx.hir().local_def_id_to_hir_id(dst_id);
- 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;
+ if self.cx.tcx.privacy_access_levels(LOCAL_CRATE).is_exported(hir_src)
+ && !self.cx.tcx.privacy_access_levels(LOCAL_CRATE).is_exported(hir_dst)
+ {
+ privacy_error(cx, &item, &path_str, dox, link_range);
+ }
+ }
+ let id = register_res(cx, res);
+ item.attrs.links.push(ItemLink { link: ori_link, link_text, did: Some(id), fragment });
+ }
+ }
+
+ fn resolve_with_disambiguator(
+ &self,
+ disambiguator: Option<Disambiguator>,
+ item: &mut Item,
+ dox: &str,
+ path_str: &str,
+ current_item: &Option<String>,
+ base_node: DefId,
+ extra_fragment: Option<String>,
+ ori_link: &str,
+ link_range: Option<Range<usize>>,
+ ) -> Option<(Res, Option<String>)> {
+ match disambiguator.map(Disambiguator::ns) {
+ Some(ns @ (ValueNS | TypeNS)) => {
+ match self.resolve(path_str, ns, ¤t_item, base_node, &extra_fragment) {
+ Ok(res) => Some(res),
+ Err(ErrorKind::Resolve(box mut kind)) => {
+ // We only looked in one namespace. Try to give a better error if possible.
+ if kind.full_res().is_none() {
+ let other_ns = if 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,
+ base_node,
+ ¤t_item,
+ &extra_fragment,
+ ) {
+ kind = ResolutionFailure::WrongNamespace(res, ns);
+ break;
+ }
}
- let candidates =
- candidates.map(|candidate| candidate.map(|(res, _)| res));
- let candidates = [TypeNS, ValueNS, MacroNS]
- .iter()
- .filter_map(|&ns| candidates[ns].map(|res| (res, ns)));
- ambiguity_error(
- cx,
- &item,
- path_str,
- &dox,
- link_range,
- candidates.collect(),
- );
- continue;
}
+ resolution_failure(
+ self,
+ &item,
+ path_str,
+ disambiguator,
+ 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.
+ return None;
}
- 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;
- }
+ Err(ErrorKind::AnchorFailure(msg)) => {
+ anchor_failure(self.cx, &item, &ori_link, dox, link_range, msg);
+ return None;
}
}
- };
-
- // Check for a primitive which might conflict with a module
- // Report the ambiguity and require that the user specify which one they meant.
- // FIXME: could there ever be a primitive not in the type namespace?
- if matches!(
- disambiguator,
- None | Some(Disambiguator::Namespace(Namespace::TypeNS) | Disambiguator::Primitive)
- ) && !matches!(res, Res::PrimTy(_))
- {
- if let Some((path, prim)) = is_primitive(path_str, TypeNS) {
- // `prim@char`
- if matches!(disambiguator, Some(Disambiguator::Primitive)) {
- if fragment.is_some() {
- anchor_failure(
- cx,
- &item,
- path_str,
- &dox,
- link_range,
- AnchorFailure::Primitive,
- );
- 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,
+ TypeNS,
+ ¤t_item,
+ base_node,
+ &extra_fragment,
+ ) {
+ Ok(res) => {
+ debug!("got res in TypeNS: {:?}", res);
+ Ok(res)
}
- res = prim;
- fragment = Some(path.to_owned());
- } else {
- // `[char]` when a `char` module is in scope
- let candidates = vec![(res, TypeNS), (prim, TypeNS)];
- ambiguity_error(cx, &item, path_str, &dox, link_range, candidates);
- continue;
+ Err(ErrorKind::AnchorFailure(msg)) => {
+ anchor_failure(self.cx, &item, ori_link, dox, link_range, msg);
+ return None;
+ }
+ Err(ErrorKind::Resolve(box kind)) => Err(kind),
+ },
+ value_ns: match self.resolve(
+ path_str,
+ ValueNS,
+ ¤t_item,
+ base_node,
+ &extra_fragment,
+ ) {
+ Ok(res) => Ok(res),
+ Err(ErrorKind::AnchorFailure(msg)) => {
+ anchor_failure(self.cx, &item, ori_link, dox, link_range, msg);
+ return None;
+ }
+ Err(ErrorKind::Resolve(box kind)) => Err(kind),
}
- }
- }
+ .and_then(|(res, fragment)| {
+ // Constructors are picked up in the type namespace.
+ match res {
+ Res::Def(DefKind::Ctor(..), _) | Res::SelfCtor(..) => {
+ Err(ResolutionFailure::WrongNamespace(res, TypeNS))
+ }
+ _ => match (fragment, extra_fragment) {
+ (Some(fragment), Some(_)) => {
+ // Shouldn't happen but who knows?
+ Ok((res, Some(fragment)))
+ }
+ (fragment, None) | (None, fragment) => Ok((res, fragment)),
+ },
+ }
+ }),
+ };
- 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| {
- let note = format!(
- "this link resolved to {} {}, which is not {} {}",
- resolved.article(),
- resolved.descr(),
- specified.article(),
- specified.descr()
+ let len = candidates.iter().filter(|res| res.is_ok()).count();
+
+ if len == 0 {
+ resolution_failure(
+ self,
+ &item,
+ path_str,
+ disambiguator,
+ dox,
+ link_range,
+ candidates.into_iter().filter_map(|res| res.err()).collect(),
);
- let suggestion = resolved.display_for(path_str);
- let help_msg =
- format!("to link to the {}, use its disambiguator", resolved.descr());
- diag.note(¬e);
- if let Some(sp) = sp {
- diag.span_suggestion(
- sp,
- &help_msg,
- suggestion,
- Applicability::MaybeIncorrect,
- );
- } else {
- diag.help(&format!("{}: {}", help_msg, suggestion));
- }
- });
- };
- if let Res::PrimTy(_) = res {
- match disambiguator {
- Some(Disambiguator::Primitive | Disambiguator::Namespace(_)) | None => {
- item.attrs.links.push((ori_link, None, fragment))
- }
- Some(other) => {
- report_mismatch(other, Disambiguator::Primitive);
- continue;
- }
+ // this could just be a normal link
+ return None;
}
- } else {
- debug!("intra-doc link to {} resolved to {:?}", path_str, res);
-
- // 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) {
- | (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.
- | (DefKind::Fn | DefKind::AssocFn, Some(Disambiguator::Kind(DefKind::Fn)))
- // These are namespaces; allow anything in the namespace to match
- | (_, Some(Disambiguator::Namespace(_)))
- // If no disambiguator given, allow anything
- | (_, None)
- // All of these are valid, so do nothing
- => {}
- (actual, Some(Disambiguator::Kind(expected))) if actual == expected => {}
- (_, Some(specified @ Disambiguator::Kind(_) | specified @ Disambiguator::Primitive)) => {
- report_mismatch(specified, Disambiguator::Kind(kind));
- continue;
- }
+
+ if len == 1 {
+ Some(candidates.into_iter().filter_map(|res| res.ok()).next().unwrap())
+ } 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);
}
+ // If we're reporting an ambiguity, don't mention the namespaces that failed
+ let candidates = candidates.map(|candidate| candidate.ok().map(|(res, _)| res));
+ ambiguity_error(
+ self.cx,
+ &item,
+ path_str,
+ dox,
+ link_range,
+ candidates.present_items().collect(),
+ );
+ return None;
}
-
- // item can be non-local e.g. when using #[doc(primitive = "pointer")]
- if let Some((src_id, dst_id)) = res
- .opt_def_id()
- .and_then(|def_id| def_id.as_local())
- .and_then(|dst_id| item.def_id.as_local().map(|src_id| (src_id, dst_id)))
- {
- use rustc_hir::def_id::LOCAL_CRATE;
-
- let hir_src = self.cx.tcx.hir().local_def_id_to_hir_id(src_id);
- let hir_dst = self.cx.tcx.hir().local_def_id_to_hir_id(dst_id);
-
- if self.cx.tcx.privacy_access_levels(LOCAL_CRATE).is_exported(hir_src)
- && !self.cx.tcx.privacy_access_levels(LOCAL_CRATE).is_exported(hir_dst)
- {
- privacy_error(cx, &item, &path_str, &dox, link_range);
- continue;
+ }
+ Some(MacroNS) => {
+ match self.macro_resolve(path_str, base_node) {
+ Ok(res) => Some((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) = self.check_full_res(
+ ns,
+ path_str,
+ base_node,
+ ¤t_item,
+ &extra_fragment,
+ ) {
+ kind = ResolutionFailure::WrongNamespace(res, MacroNS);
+ break;
+ }
+ }
+ resolution_failure(
+ self,
+ &item,
+ path_str,
+ disambiguator,
+ dox,
+ link_range,
+ smallvec![kind],
+ );
+ return None;
}
}
- 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.def_id);
- }
-
- 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
}
}
}
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};
}
}
- fn display_for(self, path_str: &str) -> String {
+ /// WARNING: panics on `Res::Err`
+ fn from_res(res: Res) -> Self {
+ match res {
+ Res::Def(kind, _) => Disambiguator::Kind(kind),
+ Res::PrimTy(_) => Disambiguator::Primitive,
+ _ => Disambiguator::Namespace(res.ns().expect("can't call `from_res` on Res::err")),
+ }
+ }
+
+ fn suggestion(self) -> Suggestion {
let kind = match self {
- Disambiguator::Primitive => return format!("prim@{}", path_str),
+ Disambiguator::Primitive => return Suggestion::Prefix("prim"),
Disambiguator::Kind(kind) => kind,
Disambiguator::Namespace(_) => panic!("display_for cannot be used on namespaces"),
};
if kind == DefKind::Macro(MacroKind::Bang) {
- return format!("{}!", path_str);
+ return Suggestion::Macro;
} else if kind == DefKind::Fn || kind == DefKind::AssocFn {
- return format!("{}()", path_str);
+ return Suggestion::Function;
}
+
let prefix = match kind {
DefKind::Struct => "struct",
DefKind::Enum => "enum",
Namespace::MacroNS => "macro",
},
};
- format!("{}@{}", prefix, path_str)
+
+ Suggestion::Prefix(prefix)
}
fn ns(self) -> Namespace {
}
}
+enum Suggestion {
+ Prefix(&'static str),
+ Function,
+ Macro,
+}
+
+impl Suggestion {
+ fn descr(&self) -> Cow<'static, str> {
+ match self {
+ Self::Prefix(x) => format!("prefix with `{}@`", x).into(),
+ Self::Function => "add parentheses".into(),
+ Self::Macro => "add an exclamation mark".into(),
+ }
+ }
+
+ fn as_help(&self, path_str: &str) -> String {
+ // FIXME: if this is an implied shortcut link, it's bad style to suggest `@`
+ match self {
+ Self::Prefix(prefix) => format!("{}@{}", prefix, path_str),
+ Self::Function => format!("{}()", path_str),
+ Self::Macro => format!("{}!", path_str),
+ }
+ }
+}
+
/// Reports a diagnostic for an intra-doc link.
///
/// If no link range is provided, or the source span of the link cannot be determined, the span of
/// to it.
fn report_diagnostic(
cx: &DocContext<'_>,
+ lint: &'static Lint,
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) {
let attrs = &item.attrs;
let sp = span_of_attrs(attrs).unwrap_or(item.source.span());
- cx.tcx.struct_span_lint_hir(lint::builtin::BROKEN_INTRA_DOC_LINKS, hir_id, sp, |lint| {
+ cx.tcx.struct_span_lint_hir(lint, hir_id, sp, |lint| {
let mut diag = lint.build(msg);
let span = link_range
// 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,
}
fn resolution_failure(
- cx: &DocContext<'_>,
+ collector: &LinkCollector<'_, '_>,
item: &Item,
path_str: &str,
+ disambiguator: Option<Disambiguator>,
dox: &str,
link_range: Option<Range<usize>>,
+ kinds: SmallVec<[ResolutionFailure<'_>; 3]>,
) {
report_diagnostic(
- cx,
+ collector.cx,
+ BROKEN_INTRA_DOC_LINKS,
&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 item = |res: Res| {
+ format!(
+ "the {} `{}`",
+ res.descr(),
+ collector.cx.tcx.item_name(res.def_id()).to_string()
+ )
+ };
+ let assoc_item_not_allowed = |res: Res| {
+ let def_id = res.def_id();
+ let name = collector.cx.tcx.item_name(def_id);
+ format!(
+ "`{}` is {} {}, not a module or type, and cannot have associated items",
+ name,
+ res.article(),
+ res.descr()
+ )
+ };
+ // ignore duplicates
+ let mut variants_seen = SmallVec::<[_; 3]>::new();
+ for mut failure in kinds {
+ let variant = std::mem::discriminant(&failure);
+ if variants_seen.contains(&variant) {
+ continue;
+ }
+ variants_seen.push(variant);
+
+ if let ResolutionFailure::NotResolved { module_id, partial_res, unresolved } =
+ &mut failure
+ {
+ use DefKind::*;
+
+ let module_id = *module_id;
+ // FIXME(jynelson): this might conflict with my `Self` fix in #76467
+ // FIXME: maybe use itertools `collect_tuple` instead?
+ fn split(path: &str) -> Option<(&str, &str)> {
+ let mut splitter = path.rsplitn(2, "::");
+ splitter.next().and_then(|right| splitter.next().map(|left| (left, right)))
+ }
- diag.help(r#"to escape `[` and `]` characters, add '\' before them like `\[` or `\]`"#);
+ // Check if _any_ parent of the path gets resolved.
+ // If so, report it and say the first which failed; if not, say the first path segment didn't resolve.
+ let mut name = path_str;
+ 'outer: loop {
+ let (start, end) = if let Some(x) = split(name) {
+ x
+ } else {
+ // avoid bug that marked [Quux::Z] as missing Z, not Quux
+ if partial_res.is_none() {
+ *unresolved = name.into();
+ }
+ break;
+ };
+ name = start;
+ for &ns in &[TypeNS, ValueNS, MacroNS] {
+ if let Some(res) =
+ collector.check_full_res(ns, &start, module_id, &None, &None)
+ {
+ debug!("found partial_res={:?}", res);
+ *partial_res = Some(res);
+ *unresolved = end.into();
+ break 'outer;
+ }
+ }
+ *unresolved = end.into();
+ }
+
+ let last_found_module = match *partial_res {
+ Some(Res::Def(DefKind::Mod, id)) => Some(id),
+ None => Some(module_id),
+ _ => None,
+ };
+ // See if this was a module: `[path]` or `[std::io::nope]`
+ if let Some(module) = last_found_module {
+ let module_name = collector.cx.tcx.item_name(module);
+ let note = format!(
+ "the module `{}` contains no item named `{}`",
+ module_name, unresolved
+ );
+ if let Some(span) = sp {
+ diag.span_label(span, ¬e);
+ } else {
+ diag.note(¬e);
+ }
+ // If the link has `::` in it, assume it was meant to be an intra-doc link.
+ // Otherwise, the `[]` might be unrelated.
+ // FIXME: don't show this for autolinks (`<>`), `()` style links, or reference links
+ if !path_str.contains("::") {
+ diag.help(r#"to escape `[` and `]` characters, add '\' before them like `\[` or `\]`"#);
+ }
+ continue;
+ }
+
+ // Otherwise, it must be an associated item or variant
+ let res = partial_res.expect("None case was handled by `last_found_module`");
+ let diagnostic_name;
+ let (kind, name) = match res {
+ Res::Def(kind, def_id) => {
+ diagnostic_name = collector.cx.tcx.item_name(def_id).as_str();
+ (Some(kind), &*diagnostic_name)
+ }
+ Res::PrimTy(ty) => (None, ty.name_str()),
+ _ => unreachable!("only ADTs and primitives are in scope at module level"),
+ };
+ let path_description = if let Some(kind) = kind {
+ match kind {
+ Mod | ForeignMod => "inner item",
+ Struct => "field or associated item",
+ Enum | Union => "variant or associated item",
+ Variant
+ | Field
+ | Closure
+ | Generator
+ | AssocTy
+ | AssocConst
+ | AssocFn
+ | Fn
+ | Macro(_)
+ | Const
+ | ConstParam
+ | ExternCrate
+ | Use
+ | LifetimeParam
+ | Ctor(_, _)
+ | AnonConst => {
+ let note = assoc_item_not_allowed(res);
+ if let Some(span) = sp {
+ diag.span_label(span, ¬e);
+ } else {
+ diag.note(¬e);
+ }
+ return;
+ }
+ Trait | TyAlias | ForeignTy | OpaqueTy | TraitAlias | TyParam
+ | Static => "associated item",
+ Impl | GlobalAsm => unreachable!("not a path"),
+ }
+ } else {
+ "associated item"
+ };
+ let note = format!(
+ "the {} `{}` has no {} named `{}`",
+ res.descr(),
+ name,
+ disambiguator.map_or(path_description, |d| d.descr()),
+ unresolved,
+ );
+ if let Some(span) = sp {
+ diag.span_label(span, ¬e);
+ } else {
+ diag.note(¬e);
+ }
+
+ continue;
+ }
+ let note = match failure {
+ ResolutionFailure::NotResolved { .. } => unreachable!("handled above"),
+ ResolutionFailure::Dummy => continue,
+ ResolutionFailure::WrongNamespace(res, expected_ns) => {
+ if let Res::Def(kind, _) = res {
+ let disambiguator = Disambiguator::Kind(kind);
+ suggest_disambiguator(
+ disambiguator,
+ diag,
+ path_str,
+ dox,
+ sp,
+ &link_range,
+ )
+ }
+
+ format!(
+ "this link resolves to {}, which is not in the {} namespace",
+ item(res),
+ expected_ns.descr()
+ )
+ }
+ ResolutionFailure::NoParentItem => {
+ diag.level = rustc_errors::Level::Bug;
+ "all intra doc links should have a parent item".to_owned()
+ }
+ };
+ if let Some(span) = sp {
+ diag.span_label(span, ¬e);
+ } else {
+ diag.note(¬e);
+ }
+ }
},
);
}
) {
let msg = match failure {
AnchorFailure::MultipleAnchors => format!("`{}` contains multiple anchors", path_str),
- AnchorFailure::Primitive
- | AnchorFailure::Variant
- | AnchorFailure::AssocConstant
- | AnchorFailure::AssocType
- | AnchorFailure::Field
- | AnchorFailure::Method => {
- let kind = match failure {
- AnchorFailure::Primitive => "primitive type",
- AnchorFailure::Variant => "enum variant",
- AnchorFailure::AssocConstant => "associated constant",
- AnchorFailure::AssocType => "associated type",
- AnchorFailure::Field => "struct field",
- AnchorFailure::Method => "method",
- AnchorFailure::MultipleAnchors => unreachable!("should be handled already"),
- };
-
- format!(
- "`{}` contains an anchor, but links to {kind}s are already anchored",
- path_str,
- kind = kind
- )
- }
+ AnchorFailure::RustdocAnchorConflict(res) => format!(
+ "`{}` contains an anchor, but links to {kind}s are already anchored",
+ path_str,
+ kind = res.descr(),
+ ),
};
- report_diagnostic(cx, &msg, item, dox, link_range, |diag, sp| {
+ report_diagnostic(cx, BROKEN_INTRA_DOC_LINKS, &msg, item, dox, &link_range, |diag, sp| {
if let Some(sp) = sp {
diag.span_label(sp, "contains invalid anchor");
}
path_str: &str,
dox: &str,
link_range: Option<Range<usize>>,
- candidates: Vec<(Res, Namespace)>,
+ candidates: Vec<Res>,
) {
let mut msg = format!("`{}` is ", path_str);
match candidates.as_slice() {
- [(first_def, _), (second_def, _)] => {
+ [first_def, second_def] => {
msg += &format!(
"both {} {} and {} {}",
first_def.article(),
}
_ => {
let mut candidates = candidates.iter().peekable();
- while let Some((res, _)) = candidates.next() {
+ while let Some(res) = candidates.next() {
if candidates.peek().is_some() {
msg += &format!("{} {}, ", res.article(), res.descr());
} else {
}
}
- report_diagnostic(cx, &msg, item, dox, link_range.clone(), |diag, sp| {
+ report_diagnostic(cx, BROKEN_INTRA_DOC_LINKS, &msg, item, dox, &link_range, |diag, sp| {
if let Some(sp) = sp {
diag.span_label(sp, "ambiguous link");
+ } else {
+ diag.note("ambiguous link");
+ }
- let link_range = link_range.expect("must have a link range if we have a span");
-
- 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::PrimTy(_), _) => "prim",
- (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",
- };
+ for res in candidates {
+ let disambiguator = Disambiguator::from_res(res);
+ suggest_disambiguator(disambiguator, diag, path_str, dox, sp, &link_range);
+ }
+ });
+}
- // FIXME: if this is an implied shortcut link, it's bad style to suggest `@`
- ("prefix with the item type", format!("{}@{}", type_, path_str))
- }
- };
+fn suggest_disambiguator(
+ disambiguator: Disambiguator,
+ diag: &mut DiagnosticBuilder<'_>,
+ path_str: &str,
+ dox: &str,
+ sp: Option<rustc_span::Span>,
+ link_range: &Option<Range<usize>>,
+) {
+ let suggestion = disambiguator.suggestion();
+ let help = format!("to link to the {}, {}", disambiguator.descr(), suggestion.descr());
- if dox.bytes().nth(link_range.start) == Some(b'`') {
- suggestion = format!("`{}`", suggestion);
- }
+ if let Some(sp) = sp {
+ let link_range = link_range.as_ref().expect("must have a link range if we have a span");
+ let msg = if dox.bytes().nth(link_range.start) == Some(b'`') {
+ format!("`{}`", suggestion.as_help(path_str))
+ } else {
+ suggestion.as_help(path_str)
+ };
- // FIXME: Create a version of this suggestion for when we don't have the span.
- diag.span_suggestion(
- sp,
- &format!("to link to the {}, {}", res.descr(), action),
- suggestion,
- Applicability::MaybeIncorrect,
- );
- }
- }
- });
+ diag.span_suggestion(sp, &help, msg, Applicability::MaybeIncorrect);
+ } else {
+ diag.help(&format!("{}: {}", help, suggestion.as_help(path_str)));
+ }
}
fn privacy_error(
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, PRIVATE_INTRA_DOC_LINKS, &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() {
- return Err(ErrorKind::AnchorFailure(AnchorFailure::Variant));
+ return Err(ErrorKind::AnchorFailure(AnchorFailure::RustdocAnchorConflict(res)));
}
let parent = if let Some(parent) = cx.tcx.parent(res.def_id()) {
parent
} else {
- return Err(ErrorKind::ResolutionFailure);
+ return Err(ResolutionFailure::NoParentItem.into());
};
let parent_def = Res::Def(DefKind::Enum, parent);
let variant = cx.tcx.expect_variant_res(res);