}
/// Given a primitive type, try to resolve an associated item.
- ///
- /// HACK(jynelson): `item_str` is passed in instead of derived from `item_name` so the
- /// lifetimes on `&'path` will work.
fn resolve_primitive_associated_item(
&self,
prim_ty: PrimitiveType,
ns: Namespace,
- module_id: DefId,
item_name: Symbol,
- item_str: &'path str,
- ) -> Result<(Res, Option<String>), ErrorKind<'path>> {
+ ) -> Option<(Res, String, Option<(DefKind, DefId)>)> {
let tcx = self.cx.tcx;
- prim_ty
- .impls(tcx)
- .into_iter()
- .find_map(|&impl_| {
- tcx.associated_items(impl_)
- .find_by_name_and_namespace(tcx, Ident::with_dummy_span(item_name), ns, impl_)
- .map(|item| {
- let kind = item.kind;
- self.kind_side_channel.set(Some((kind.as_def_kind(), item.def_id)));
- match kind {
- ty::AssocKind::Fn => "method",
- ty::AssocKind::Const => "associatedconstant",
- ty::AssocKind::Type => "associatedtype",
- }
- })
- .map(|out| {
- (
- Res::Primitive(prim_ty),
- Some(format!("{}#{}.{}", prim_ty.as_str(), out, item_str)),
- )
- })
- })
- .ok_or_else(|| {
- debug!(
- "returning primitive error for {}::{} in {} namespace",
- prim_ty.as_str(),
- item_name,
- ns.descr()
- );
- ResolutionFailure::NotResolved {
- module_id,
- partial_res: Some(Res::Primitive(prim_ty)),
- unresolved: item_str.into(),
- }
- .into()
- })
+ prim_ty.impls(tcx).into_iter().find_map(|&impl_| {
+ tcx.associated_items(impl_)
+ .find_by_name_and_namespace(tcx, Ident::with_dummy_span(item_name), ns, impl_)
+ .map(|item| {
+ let kind = item.kind;
+ let out = match kind {
+ ty::AssocKind::Fn => "method",
+ ty::AssocKind::Const => "associatedconstant",
+ ty::AssocKind::Type => "associatedtype",
+ };
+ let fragment = format!("{}#{}.{}", prim_ty.as_str(), out, item_name);
+ (Res::Primitive(prim_ty), fragment, Some((kind.as_def_kind(), item.def_id)))
+ })
+ })
}
/// Resolves a string as a macro.
module_id: DefId,
extra_fragment: &Option<String>,
) -> Result<(Res, Option<String>), ErrorKind<'path>> {
- let tcx = self.cx.tcx;
-
if let Some(res) = self.resolve_path(path_str, ns, module_id) {
match res {
// FIXME(#76467): make this fallthrough to lookup the associated
}
})?;
- // FIXME: are these both necessary?
- let ty_res = if let Some(ty_res) = resolve_primitive(&path_root, TypeNS)
+ // FIXME(#83862): this arbitrarily gives precedence to primitives over modules to support
+ // links to primitives when `#[doc(primitive)]` is present. It should give an ambiguity
+ // error instead and special case *only* modules with `#[doc(primitive)]`, not all
+ // primitives.
+ resolve_primitive(&path_root, TypeNS)
.or_else(|| self.resolve_path(&path_root, TypeNS, module_id))
- {
- ty_res
- } else {
- // FIXME: this is duplicated on the end of this function.
- return if ns == Namespace::ValueNS {
- self.variant_field(path_str, module_id)
- } else {
- Err(ResolutionFailure::NotResolved {
- module_id,
- partial_res: None,
- unresolved: path_root.into(),
+ .and_then(|ty_res| {
+ let (res, fragment, side_channel) =
+ self.resolve_associated_item(ty_res, item_name, ns, module_id)?;
+ let result = if extra_fragment.is_some() {
+ let diag_res = side_channel.map_or(res, |(k, r)| Res::Def(k, r));
+ Err(ErrorKind::AnchorFailure(AnchorFailure::RustdocAnchorConflict(diag_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.
+ if let Some((kind, id)) = side_channel {
+ self.kind_side_channel.set(Some((kind, id)));
+ }
+ Ok((res, Some(fragment)))
+ };
+ Some(result)
+ })
+ .unwrap_or_else(|| {
+ if ns == Namespace::ValueNS {
+ self.variant_field(path_str, module_id)
+ } else {
+ Err(ResolutionFailure::NotResolved {
+ module_id,
+ partial_res: None,
+ unresolved: path_root.into(),
+ }
+ .into())
}
- .into())
- };
- };
+ })
+ }
- let res = match ty_res {
- Res::Primitive(prim) => Some(
- self.resolve_primitive_associated_item(prim, ns, module_id, item_name, item_str),
- ),
+ /// Returns:
+ /// - None if no associated item was found
+ /// - Some((_, _, Some(_))) if an item was found and should go through a side channel
+ /// - Some((_, _, None)) otherwise
+ fn resolve_associated_item(
+ &mut self,
+ root_res: Res,
+ item_name: Symbol,
+ ns: Namespace,
+ module_id: DefId,
+ ) -> Option<(Res, String, Option<(DefKind, DefId)>)> {
+ let tcx = self.cx.tcx;
+
+ match root_res {
+ Res::Primitive(prim) => self.resolve_primitive_associated_item(prim, ns, item_name),
Res::Def(
DefKind::Struct
| DefKind::Union
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);
- // FIXME(jynelson): why is this different from
- // `variant_field`?
- match 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() {
- let res = Res::Def(
- if def.is_enum() {
- DefKind::Variant
- } else {
- 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 {
- None
+ // 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.
+ return Some((
+ root_res,
+ format!("{}.{}", out, item_name),
+ Some((kind.as_def_kind(), id)),
+ ));
+ }
+
+ if ns != Namespace::ValueNS {
+ return None;
}
+ debug!("looking for variants or fields named {} for {:?}", item_name, did);
+ // FIXME: this doesn't really belong in `associated_item` (maybe `variant_field` is better?)
+ // NOTE: it's different from variant_field because it resolves fields and variants,
+ // not variant fields (2 path segments, not 3).
+ let def = match tcx.type_of(did).kind() {
+ ty::Adt(def, _) => def,
+ _ => return None,
+ };
+ 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)
+ }?;
+ let kind = if def.is_enum() { DefKind::Variant } else { DefKind::Field };
+ Some((
+ root_res,
+ format!(
+ "{}.{}",
+ if def.is_enum() { "variant" } else { "structfield" },
+ field.ident
+ ),
+ Some((kind, field.did)),
+ ))
}
Res::Def(DefKind::Trait, did) => tcx
.associated_items(did)
}
};
- if extra_fragment.is_some() {
- Err(ErrorKind::AnchorFailure(AnchorFailure::RustdocAnchorConflict(ty_res)))
- } else {
- let res = Res::Def(item.kind.as_def_kind(), item.def_id);
- Ok((res, Some(format!("{}.{}", kind, item_str))))
- }
+ let res = Res::Def(item.kind.as_def_kind(), item.def_id);
+ (res, format!("{}.{}", kind, item_name), None)
}),
_ => None,
- };
- res.unwrap_or_else(|| {
- if ns == Namespace::ValueNS {
- self.variant_field(path_str, module_id)
- } else {
- Err(ResolutionFailure::NotResolved {
- module_id,
- partial_res: Some(ty_res),
- unresolved: item_str.into(),
- }
- .into())
- }
- })
+ }
}
/// Used for reporting better errors.
}
impl<'a, 'tcx> DocFolder for LinkCollector<'a, 'tcx> {
- fn fold_item(&mut self, mut item: Item) -> Option<Item> {
+ fn fold_item(&mut self, item: Item) -> Option<Item> {
use rustc_middle::ty::DefIdTree;
let parent_node = if item.is_fake() {
for md_link in markdown_links(&doc) {
let link = self.resolve_link(&item, &doc, &self_name, parent_node, krate, md_link);
if let Some(link) = link {
- item.attrs.links.push(link);
+ self.cx.cache.intra_doc_links.entry(item.def_id).or_default().push(link);
}
}
}
// See issue #83859.
let disambiguator_range = (no_backticks_range.start + relative_range.start)
..(no_backticks_range.start + relative_range.end);
- disambiguator_error(self.cx, &item, dox, disambiguator_range, &err_msg);
+ disambiguator_error(self.cx, diag_info, disambiguator_range, &err_msg);
}
return None;
}
} else {
// `[char]` when a `char` module is in scope
let candidates = vec![res, prim];
- ambiguity_error(self.cx, &item, path_str, dox, ori_link.range, candidates);
+ ambiguity_error(self.cx, diag_info, path_str, candidates);
return None;
}
}
diag.note(¬e);
suggest_disambiguator(resolved, diag, path_str, dox, sp, &ori_link.range);
};
- report_diagnostic(
- self.cx.tcx,
- BROKEN_INTRA_DOC_LINKS,
- &msg,
- &item,
- dox,
- &ori_link.range,
- callback,
- );
+ report_diagnostic(self.cx.tcx, BROKEN_INTRA_DOC_LINKS, &msg, &diag_info, callback);
};
let verify = |kind: DefKind, id: DefId| {
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(self.cx, &item, &path_str, dox, &ori_link);
+ privacy_error(self.cx, &diag_info, &path_str);
}
}
}
// 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,
- diag.item,
- path_str,
- diag.dox,
- diag.link_range,
- candidates.present_items().collect(),
- );
+ ambiguity_error(self.cx, diag, path_str, candidates.present_items().collect());
None
}
}
tcx: TyCtxt<'_>,
lint: &'static Lint,
msg: &str,
- item: &Item,
- dox: &str,
- link_range: &Range<usize>,
+ DiagnosticInfo { item, ori_link: _, dox, link_range }: &DiagnosticInfo<'_>,
decorate: impl FnOnce(&mut DiagnosticBuilder<'_>, Option<rustc_span::Span>),
) {
let hir_id = match DocContext::as_local_hir_id(tcx, item.def_id) {
/// `std::io::Error::x`, this will resolve `std::io::Error`.
fn resolution_failure(
collector: &mut LinkCollector<'_, '_>,
- DiagnosticInfo { item, ori_link: _, dox, link_range }: DiagnosticInfo<'_>,
+ diag_info: DiagnosticInfo<'_>,
path_str: &str,
disambiguator: Option<Disambiguator>,
kinds: SmallVec<[ResolutionFailure<'_>; 3]>,
tcx,
BROKEN_INTRA_DOC_LINKS,
&format!("unresolved link to `{}`", path_str),
- item,
- dox,
- &link_range,
+ &diag_info,
|diag, sp| {
let item = |res: Res| format!("the {} `{}`", res.descr(), res.name(tcx),);
let assoc_item_not_allowed = |res: Res| {
disambiguator,
diag,
path_str,
- dox,
+ diag_info.dox,
sp,
- &link_range,
+ &diag_info.link_range,
)
}
}
/// Report an anchor failure.
-fn anchor_failure(
- cx: &DocContext<'_>,
- DiagnosticInfo { item, ori_link, dox, link_range }: DiagnosticInfo<'_>,
- failure: AnchorFailure,
-) {
+fn anchor_failure(cx: &DocContext<'_>, diag_info: DiagnosticInfo<'_>, failure: AnchorFailure) {
let msg = match failure {
- AnchorFailure::MultipleAnchors => format!("`{}` contains multiple anchors", ori_link),
+ AnchorFailure::MultipleAnchors => {
+ format!("`{}` contains multiple anchors", diag_info.ori_link)
+ }
AnchorFailure::RustdocAnchorConflict(res) => format!(
"`{}` contains an anchor, but links to {kind}s are already anchored",
- ori_link,
+ diag_info.ori_link,
kind = res.descr(),
),
};
- report_diagnostic(cx.tcx, BROKEN_INTRA_DOC_LINKS, &msg, item, dox, &link_range, |diag, sp| {
+ report_diagnostic(cx.tcx, BROKEN_INTRA_DOC_LINKS, &msg, &diag_info, |diag, sp| {
if let Some(sp) = sp {
diag.span_label(sp, "contains invalid anchor");
}
+ if let AnchorFailure::RustdocAnchorConflict(Res::Primitive(_)) = failure {
+ diag.note("this restriction may be lifted in a future release");
+ diag.note("see https://github.com/rust-lang/rust/issues/83083 for more information");
+ }
});
}
/// Report an error in the link disambiguator.
fn disambiguator_error(
cx: &DocContext<'_>,
- item: &Item,
- dox: &str,
+ mut diag_info: DiagnosticInfo<'_>,
disambiguator_range: Range<usize>,
msg: &str,
) {
- report_diagnostic(cx.tcx, BROKEN_INTRA_DOC_LINKS, msg, item, dox, &disambiguator_range, |_diag, _sp| {});
+ diag_info.link_range = disambiguator_range;
+ report_diagnostic(cx.tcx, BROKEN_INTRA_DOC_LINKS, msg, &diag_info, |diag, _sp| {
+ let msg = format!(
+ "see https://doc.rust-lang.org/{}/rustdoc/linking-to-items-by-name.html#namespaces-and-disambiguators \
+ for more info about disambiguators",
+ crate::doc_rust_lang_org_channel(),
+ );
+ diag.note(&msg);
+ });
}
/// Report an ambiguity error, where there were multiple possible resolutions.
fn ambiguity_error(
cx: &DocContext<'_>,
- item: &Item,
+ diag_info: DiagnosticInfo<'_>,
path_str: &str,
- dox: &str,
- link_range: Range<usize>,
candidates: Vec<Res>,
) {
let mut msg = format!("`{}` is ", path_str);
}
}
- report_diagnostic(cx.tcx, BROKEN_INTRA_DOC_LINKS, &msg, item, dox, &link_range, |diag, sp| {
+ report_diagnostic(cx.tcx, BROKEN_INTRA_DOC_LINKS, &msg, &diag_info, |diag, sp| {
if let Some(sp) = sp {
diag.span_label(sp, "ambiguous link");
} else {
for res in candidates {
let disambiguator = Disambiguator::from_res(res);
- suggest_disambiguator(disambiguator, diag, path_str, dox, sp, &link_range);
+ suggest_disambiguator(
+ disambiguator,
+ diag,
+ path_str,
+ diag_info.dox,
+ sp,
+ &diag_info.link_range,
+ );
}
});
}
}
/// Report a link from a public item to a private one.
-fn privacy_error(cx: &DocContext<'_>, item: &Item, path_str: &str, dox: &str, link: &MarkdownLink) {
+fn privacy_error(cx: &DocContext<'_>, diag_info: &DiagnosticInfo<'_>, path_str: &str) {
let sym;
- let item_name = match item.name {
+ let item_name = match diag_info.item.name {
Some(name) => {
sym = name.as_str();
&*sym
let msg =
format!("public documentation for `{}` links to private item `{}`", item_name, path_str);
- report_diagnostic(cx.tcx, PRIVATE_INTRA_DOC_LINKS, &msg, item, dox, &link.range, |diag, sp| {
+ report_diagnostic(cx.tcx, PRIVATE_INTRA_DOC_LINKS, &msg, diag_info, |diag, sp| {
if let Some(sp) = sp {
diag.span_label(sp, "this item is private");
}