summaryrefslogtreecommitdiffstats
path: root/src/librustdoc/passes/collect_intra_doc_links.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/librustdoc/passes/collect_intra_doc_links.rs')
-rw-r--r--src/librustdoc/passes/collect_intra_doc_links.rs313
1 files changed, 88 insertions, 225 deletions
diff --git a/src/librustdoc/passes/collect_intra_doc_links.rs b/src/librustdoc/passes/collect_intra_doc_links.rs
index 075951312..cbfc58138 100644
--- a/src/librustdoc/passes/collect_intra_doc_links.rs
+++ b/src/librustdoc/passes/collect_intra_doc_links.rs
@@ -15,7 +15,8 @@ use rustc_hir::def_id::{DefId, CRATE_DEF_ID};
use rustc_hir::Mutability;
use rustc_middle::ty::{DefIdTree, Ty, TyCtxt};
use rustc_middle::{bug, ty};
-use rustc_resolve::ParentScope;
+use rustc_resolve::rustdoc::MalformedGenerics;
+use rustc_resolve::rustdoc::{prepare_to_doc_link_resolution, strip_generics_from_path};
use rustc_session::lint::Lint;
use rustc_span::hygiene::MacroKind;
use rustc_span::symbol::{sym, Ident, Symbol};
@@ -34,9 +35,6 @@ use crate::lint::{BROKEN_INTRA_DOC_LINKS, PRIVATE_INTRA_DOC_LINKS};
use crate::passes::Pass;
use crate::visit::DocVisitor;
-mod early;
-pub(crate) use early::early_resolve_intra_doc_links;
-
pub(crate) const COLLECT_INTRA_DOC_LINKS: Pass = Pass {
name: "collect-intra-doc-links",
run: collect_intra_doc_links,
@@ -179,47 +177,6 @@ enum ResolutionFailure<'a> {
NotResolved(UnresolvedPath<'a>),
}
-#[derive(Clone, Copy, Debug)]
-enum MalformedGenerics {
- /// This link has unbalanced angle brackets.
- ///
- /// For example, `Vec<T` should trigger this, as should `Vec<T>>`.
- UnbalancedAngleBrackets,
- /// The generics are not attached to a type.
- ///
- /// For example, `<T>` should trigger this.
- ///
- /// This is detected by checking if the path is empty after the generics are stripped.
- MissingType,
- /// The link uses fully-qualified syntax, which is currently unsupported.
- ///
- /// For example, `<Vec as IntoIterator>::into_iter` should trigger this.
- ///
- /// This is detected by checking if ` as ` (the keyword `as` with spaces around it) is inside
- /// angle brackets.
- HasFullyQualifiedSyntax,
- /// The link has an invalid path separator.
- ///
- /// For example, `Vec:<T>:new()` should trigger this. Note that `Vec:new()` will **not**
- /// trigger this because it has no generics and thus [`strip_generics_from_path`] will not be
- /// called.
- ///
- /// Note that this will also **not** be triggered if the invalid path separator is inside angle
- /// brackets because rustdoc mostly ignores what's inside angle brackets (except for
- /// [`HasFullyQualifiedSyntax`](MalformedGenerics::HasFullyQualifiedSyntax)).
- ///
- /// This is detected by checking if there is a colon followed by a non-colon in the link.
- InvalidPathSeparator,
- /// The link has too many angle brackets.
- ///
- /// For example, `Vec<<T>>` should trigger this.
- TooManyAngleBrackets,
- /// The link has empty angle brackets.
- ///
- /// For example, `Vec<>` should trigger this.
- EmptyAngleBrackets,
-}
-
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
pub(crate) enum UrlFragment {
Item(DefId),
@@ -271,7 +228,7 @@ struct ResolutionInfo {
item_id: ItemId,
module_id: DefId,
dis: Option<Disambiguator>,
- path_str: String,
+ path_str: Box<str>,
extra_fragment: Option<String>,
}
@@ -336,9 +293,10 @@ impl<'a, 'tcx> LinkCollector<'a, 'tcx> {
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) => match tcx.type_of(did).kind() {
+ Res::Def(DefKind::Enum, did) => match tcx.type_of(did).subst_identity().kind() {
ty::Adt(def, _) if def.is_enum() => {
- if let Some(field) = def.all_fields().find(|f| f.name == variant_field_name) {
+ if let Some(variant) = def.variants().iter().find(|v| v.name == variant_name)
+ && let Some(field) = variant.fields.iter().find(|f| f.name == variant_field_name) {
Ok((ty_res, field.did))
} else {
Err(UnresolvedPath {
@@ -401,16 +359,16 @@ impl<'a, 'tcx> LinkCollector<'a, 'tcx> {
_ => def_id,
})
.and_then(|self_id| match tcx.def_kind(self_id) {
- DefKind::Impl => self.def_id_to_res(self_id),
+ DefKind::Impl { .. } => self.def_id_to_res(self_id),
DefKind::Use => None,
def_kind => Some(Res::Def(def_kind, self_id)),
})
}
- /// Convenience wrapper around `resolve_rustdoc_path`.
+ /// Convenience wrapper around `doc_link_resolutions`.
///
/// This also handles resolving `true` and `false` as booleans.
- /// NOTE: `resolve_rustdoc_path` knows only about paths, not about types.
+ /// NOTE: `doc_link_resolutions` knows only about paths, not about types.
/// Associated items will never be resolved by this function.
fn resolve_path(
&self,
@@ -426,17 +384,11 @@ impl<'a, 'tcx> LinkCollector<'a, 'tcx> {
// Resolver doesn't know about true, false, and types that aren't paths (e.g. `()`).
let result = self
.cx
- .resolver_caches
- .doc_link_resolutions
- .get(&(Symbol::intern(path_str), ns, module_id))
+ .tcx
+ .doc_link_resolutions(module_id)
+ .get(&(Symbol::intern(path_str), ns))
.copied()
- .unwrap_or_else(|| {
- self.cx.enter_resolver(|resolver| {
- let parent_scope =
- ParentScope::module(resolver.expect_module(module_id), resolver);
- resolver.resolve_rustdoc_path(path_str, ns, parent_scope)
- })
- })
+ .unwrap_or_else(|| panic!("no resolution for {:?} {:?} {:?}", path_str, ns, module_id))
.and_then(|res| res.try_into().ok())
.or_else(|| resolve_primitive(path_str, ns));
debug!("{} resolved to {:?} in namespace {:?}", path_str, result, ns);
@@ -519,7 +471,7 @@ impl<'a, 'tcx> LinkCollector<'a, 'tcx> {
/// This is used for resolving type aliases.
fn def_id_to_res(&self, ty_id: DefId) -> Option<Res> {
use PrimitiveType::*;
- Some(match *self.cx.tcx.type_of(ty_id).kind() {
+ Some(match *self.cx.tcx.type_of(ty_id).subst_identity().kind() {
ty::Bool => Res::Primitive(Bool),
ty::Char => Res::Primitive(Char),
ty::Int(ity) => Res::Primitive(ity.into()),
@@ -542,6 +494,7 @@ impl<'a, 'tcx> LinkCollector<'a, 'tcx> {
| ty::Closure(..)
| ty::Generator(..)
| ty::GeneratorWitness(_)
+ | ty::GeneratorWitnessMIR(..)
| ty::Dynamic(..)
| ty::Param(_)
| ty::Bound(..)
@@ -561,27 +514,27 @@ impl<'a, 'tcx> LinkCollector<'a, 'tcx> {
// FIXME: Only simple types are supported here, see if we can support
// other types such as Tuple, Array, Slice, etc.
// See https://github.com/rust-lang/rust/issues/90703#issuecomment-1004263455
- Some(tcx.mk_ty(match prim {
- Bool => ty::Bool,
- Str => ty::Str,
- Char => ty::Char,
- Never => ty::Never,
- I8 => ty::Int(ty::IntTy::I8),
- I16 => ty::Int(ty::IntTy::I16),
- I32 => ty::Int(ty::IntTy::I32),
- I64 => ty::Int(ty::IntTy::I64),
- I128 => ty::Int(ty::IntTy::I128),
- Isize => ty::Int(ty::IntTy::Isize),
- F32 => ty::Float(ty::FloatTy::F32),
- F64 => ty::Float(ty::FloatTy::F64),
- U8 => ty::Uint(ty::UintTy::U8),
- U16 => ty::Uint(ty::UintTy::U16),
- U32 => ty::Uint(ty::UintTy::U32),
- U64 => ty::Uint(ty::UintTy::U64),
- U128 => ty::Uint(ty::UintTy::U128),
- Usize => ty::Uint(ty::UintTy::Usize),
+ Some(match prim {
+ Bool => tcx.types.bool,
+ Str => tcx.types.str_,
+ Char => tcx.types.char,
+ Never => tcx.types.never,
+ I8 => tcx.types.i8,
+ I16 => tcx.types.i16,
+ I32 => tcx.types.i32,
+ I64 => tcx.types.i64,
+ I128 => tcx.types.i128,
+ Isize => tcx.types.isize,
+ F32 => tcx.types.f32,
+ F64 => tcx.types.f64,
+ U8 => tcx.types.u8,
+ U16 => tcx.types.u16,
+ U32 => tcx.types.u32,
+ U64 => tcx.types.u64,
+ U128 => tcx.types.u128,
+ Usize => tcx.types.usize,
_ => return None,
- }))
+ })
}
/// Resolve an associated item, returning its containing page's `Res`
@@ -619,7 +572,7 @@ impl<'a, 'tcx> LinkCollector<'a, 'tcx> {
debug!("looking for associated item named {} for item {:?}", item_name, did);
// Checks if item_name is a variant of the `SomeItem` enum
if ns == TypeNS && def_kind == DefKind::Enum {
- match tcx.type_of(did).kind() {
+ match tcx.type_of(did).subst_identity().kind() {
ty::Adt(adt_def, _) => {
for variant in adt_def.variants() {
if variant.name == item_name {
@@ -653,7 +606,7 @@ impl<'a, 'tcx> LinkCollector<'a, 'tcx> {
// something like [`ambi_fn`](<SomeStruct as SomeTrait>::ambi_fn)
.or_else(|| {
resolve_associated_trait_item(
- tcx.type_of(did),
+ tcx.type_of(did).subst_identity(),
module_id,
item_name,
ns,
@@ -686,7 +639,7 @@ impl<'a, 'tcx> LinkCollector<'a, 'tcx> {
// they also look like associated items (`module::Type::Variant`),
// because they are real Rust syntax (unlike the intra-doc links
// field syntax) and are handled by the compiler's resolver.
- let def = match tcx.type_of(did).kind() {
+ let def = match tcx.type_of(did).subst_identity().kind() {
ty::Adt(def, _) if !def.is_enum() => def,
_ => return None,
};
@@ -736,12 +689,12 @@ fn resolve_associated_trait_item<'a>(
.find_by_name_and_namespace(cx.tcx, Ident::with_dummy_span(item_name), ns, trait_)
.map(|trait_assoc| {
trait_assoc_to_impl_assoc_item(cx.tcx, impl_, trait_assoc.def_id)
- .unwrap_or(trait_assoc)
+ .unwrap_or(*trait_assoc)
})
});
// FIXME(#74563): warn about ambiguity
debug!("the candidates were {:?}", candidates.clone().collect::<Vec<_>>());
- candidates.next().copied()
+ candidates.next()
}
/// Find the associated item in the impl `impl_id` that corresponds to the
@@ -758,7 +711,7 @@ fn trait_assoc_to_impl_assoc_item<'tcx>(
tcx: TyCtxt<'tcx>,
impl_id: DefId,
trait_assoc_id: DefId,
-) -> Option<&'tcx ty::AssocItem> {
+) -> Option<ty::AssocItem> {
let trait_to_impl_assoc_map = tcx.impl_item_implementor_ids(impl_id);
debug!(?trait_to_impl_assoc_map);
let impl_assoc_id = *trait_to_impl_assoc_map.get(&trait_assoc_id)?;
@@ -778,8 +731,7 @@ fn trait_impls_for<'a>(
module: DefId,
) -> FxHashSet<(DefId, DefId)> {
let tcx = cx.tcx;
- let iter = cx.resolver_caches.traits_in_scope[&module].iter().flat_map(|trait_candidate| {
- let trait_ = trait_candidate.def_id;
+ let iter = tcx.doc_link_traits_in_scope(module).iter().flat_map(|&trait_| {
trace!("considering explicit impl for trait {:?}", trait_);
// Look at each trait implementation to see if it's an impl for `did`
@@ -845,7 +797,7 @@ impl<'a, 'tcx> DocVisitor for LinkCollector<'a, 'tcx> {
// In the presence of re-exports, this is not the same as the module of the item.
// Rather than merging all documentation into one, resolve it one attribute at a time
// so we know which module it came from.
- for (parent_module, doc) in item.attrs.prepare_to_doc_link_resolution() {
+ for (parent_module, doc) in prepare_to_doc_link_resolution(&item.attrs.doc_strings) {
if !may_have_doc_links(&doc) {
continue;
}
@@ -853,22 +805,12 @@ impl<'a, 'tcx> DocVisitor for LinkCollector<'a, 'tcx> {
// NOTE: if there are links that start in one crate and end in another, this will not resolve them.
// This is a degenerate case and it's not supported by rustdoc.
let parent_node = parent_module.or(parent_node);
- let mut tmp_links = self
- .cx
- .resolver_caches
- .markdown_links
- .take()
- .expect("`markdown_links` are already borrowed");
- if !tmp_links.contains_key(&doc) {
- tmp_links.insert(doc.clone(), preprocessed_markdown_links(&doc));
- }
- for md_link in &tmp_links[&doc] {
- let link = self.resolve_link(item, &doc, parent_node, md_link);
+ for md_link in preprocessed_markdown_links(&doc) {
+ let link = self.resolve_link(item, &doc, parent_node, &md_link);
if let Some(link) = link {
self.cx.cache.intra_doc_links.entry(item.item_id).or_default().push(link);
}
}
- self.cx.resolver_caches.markdown_links = Some(tmp_links);
}
if item.is_mod() {
@@ -907,10 +849,10 @@ impl PreprocessingError {
#[derive(Clone)]
struct PreprocessingInfo {
- path_str: String,
+ path_str: Box<str>,
disambiguator: Option<Disambiguator>,
extra_fragment: Option<String>,
- link_text: String,
+ link_text: Box<str>,
}
// Not a typedef to avoid leaking several private structures from this module.
@@ -942,7 +884,8 @@ fn preprocess_link(
let mut parts = stripped.split('#');
let link = parts.next().unwrap();
- if link.trim().is_empty() {
+ let link = link.trim();
+ if link.is_empty() {
// This is an anchor to an element of the current page, nothing to do in here!
return None;
}
@@ -955,7 +898,7 @@ fn preprocess_link(
// Parse and strip the disambiguator from the link, if present.
let (disambiguator, path_str, link_text) = match Disambiguator::from_str(link) {
Ok(Some((d, path, link_text))) => (Some(d), path.trim(), link_text.trim()),
- Ok(None) => (None, link.trim(), link.trim()),
+ Ok(None) => (None, link, link),
Err((err_msg, relative_range)) => {
// Only report error if we would not have ignored this link. See issue #83859.
if !should_ignore_link_with_disambiguators(link) {
@@ -974,16 +917,12 @@ fn preprocess_link(
}
// Strip generics from the path.
- let path_str = if path_str.contains(['<', '>'].as_slice()) {
- match strip_generics_from_path(path_str) {
- Ok(path) => path,
- Err(err) => {
- debug!("link has malformed generics: {}", path_str);
- return Some(Err(PreprocessingError::MalformedGenerics(err, path_str.to_owned())));
- }
+ let path_str = match strip_generics_from_path(path_str) {
+ Ok(path) => path,
+ Err(err) => {
+ debug!("link has malformed generics: {}", path_str);
+ return Some(Err(PreprocessingError::MalformedGenerics(err, path_str.to_owned())));
}
- } else {
- path_str.to_owned()
};
// Sanity check to make sure we don't have any angle brackets after stripping generics.
@@ -998,7 +937,7 @@ fn preprocess_link(
path_str,
disambiguator,
extra_fragment: extra_fragment.map(|frag| frag.to_owned()),
- link_text: link_text.to_owned(),
+ link_text: Box::<str>::from(link_text),
}))
}
@@ -1054,7 +993,7 @@ impl LinkCollector<'_, '_> {
item_id: item.item_id,
module_id,
dis: disambiguator,
- path_str: path_str.to_owned(),
+ path_str: path_str.clone(),
extra_fragment: extra_fragment.clone(),
},
diag_info.clone(), // this struct should really be Copy, but Range is not :(
@@ -1128,7 +1067,7 @@ impl LinkCollector<'_, '_> {
}
res.def_id(self.cx.tcx).map(|page_id| ItemLink {
- link: ori_link.link.clone(),
+ link: Box::<str>::from(&*ori_link.link),
link_text: link_text.clone(),
page_id,
fragment,
@@ -1152,7 +1091,7 @@ impl LinkCollector<'_, '_> {
let page_id = clean::register_res(self.cx, rustc_hir::def::Res::Def(kind, id));
Some(ItemLink {
- link: ori_link.link.clone(),
+ link: Box::<str>::from(&*ori_link.link),
link_text: link_text.clone(),
page_id,
fragment,
@@ -1194,14 +1133,9 @@ impl LinkCollector<'_, '_> {
}
// item can be non-local e.g. when using #[doc(primitive = "pointer")]
- if let Some((src_id, dst_id)) = id
- .as_local()
- // The `expect_def_id()` should be okay because `local_def_id_to_hir_id`
- // would presumably panic if a fake `DefIndex` were passed.
- .and_then(|dst_id| {
- item.item_id.expect_def_id().as_local().map(|src_id| (src_id, dst_id))
- })
- {
+ if let Some((src_id, dst_id)) = id.as_local().and_then(|dst_id| {
+ item.item_id.expect_def_id().as_local().map(|src_id| (src_id, dst_id))
+ }) {
if self.cx.tcx.effective_visibilities(()).is_exported(src_id)
&& !self.cx.tcx.effective_visibilities(()).is_exported(dst_id)
{
@@ -1681,7 +1615,7 @@ fn resolution_failure(
// ignore duplicates
let mut variants_seen = SmallVec::<[_; 3]>::new();
for mut failure in kinds {
- let variant = std::mem::discriminant(&failure);
+ let variant = mem::discriminant(&failure);
if variants_seen.contains(&variant) {
continue;
}
@@ -1751,7 +1685,7 @@ fn resolution_failure(
if !path_str.contains("::") {
if disambiguator.map_or(true, |d| d.ns() == MacroNS)
- && let Some(&res) = collector.cx.resolver_caches.all_macro_rules
+ && let Some(&res) = collector.cx.tcx.resolutions(()).all_macro_rules
.get(&Symbol::intern(path_str))
{
diag.note(format!(
@@ -1772,15 +1706,35 @@ fn resolution_failure(
// Otherwise, it must be an associated item or variant
let res = partial_res.expect("None case was handled by `last_found_module`");
- let kind = match res {
- Res::Def(kind, _) => Some(kind),
+ let kind_did = match res {
+ Res::Def(kind, did) => Some((kind, did)),
Res::Primitive(_) => None,
};
- let path_description = if let Some(kind) = kind {
+ let is_struct_variant = |did| {
+ if let ty::Adt(def, _) = tcx.type_of(did).subst_identity().kind()
+ && def.is_enum()
+ && let Some(variant) = def.variants().iter().find(|v| v.name == res.name(tcx)) {
+ // ctor is `None` if variant is a struct
+ variant.ctor.is_none()
+ } else {
+ false
+ }
+ };
+ let path_description = if let Some((kind, did)) = kind_did {
match kind {
Mod | ForeignMod => "inner item",
Struct => "field or associated item",
Enum | Union => "variant or associated item",
+ Variant if is_struct_variant(did) => {
+ let variant = res.name(tcx);
+ let note = format!("variant `{variant}` has no such field");
+ if let Some(span) = sp {
+ diag.span_label(span, &note);
+ } else {
+ diag.note(&note);
+ }
+ return;
+ }
Variant
| Field
| Closure
@@ -1808,7 +1762,7 @@ fn resolution_failure(
}
Trait | TyAlias | ForeignTy | OpaqueTy | ImplTraitPlaceholder
| TraitAlias | TyParam | Static(_) => "associated item",
- Impl | GlobalAsm => unreachable!("not a path"),
+ Impl { .. } | GlobalAsm => unreachable!("not a path"),
}
} else {
"associated item"
@@ -2068,94 +2022,3 @@ fn resolve_primitive(path_str: &str, ns: Namespace) -> Option<Res> {
debug!("resolved primitives {:?}", prim);
Some(Res::Primitive(prim))
}
-
-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();
-
- while let Some(chr) = path.next() {
- match chr {
- ':' => {
- if path.next_if_eq(&':').is_some() {
- let stripped_segment =
- strip_generics_from_path_segment(mem::take(&mut segment))?;
- if !stripped_segment.is_empty() {
- stripped_segments.push(stripped_segment);
- }
- } else {
- return Err(MalformedGenerics::InvalidPathSeparator);
- }
- }
- '<' => {
- segment.push(chr);
-
- match path.next() {
- Some('<') => {
- return Err(MalformedGenerics::TooManyAngleBrackets);
- }
- Some('>') => {
- return Err(MalformedGenerics::EmptyAngleBrackets);
- }
- Some(chr) => {
- segment.push(chr);
-
- while let Some(chr) = path.next_if(|c| *c != '>') {
- segment.push(chr);
- }
- }
- None => break,
- }
- }
- _ => segment.push(chr),
- }
- trace!("raw segment: {:?}", segment);
- }
-
- if !segment.is_empty() {
- let stripped_segment = strip_generics_from_path_segment(segment)?;
- if !stripped_segment.is_empty() {
- stripped_segments.push(stripped_segment);
- }
- }
-
- debug!("path_str: {:?}\nstripped segments: {:?}", path_str, &stripped_segments);
-
- let stripped_path = stripped_segments.join("::");
-
- if !stripped_path.is_empty() { Ok(stripped_path) } else { Err(MalformedGenerics::MissingType) }
-}
-
-fn strip_generics_from_path_segment(segment: Vec<char>) -> Result<String, MalformedGenerics> {
- let mut stripped_segment = String::new();
- let mut param_depth = 0;
-
- let mut latest_generics_chunk = String::new();
-
- for c in segment {
- if c == '<' {
- param_depth += 1;
- latest_generics_chunk.clear();
- } else if c == '>' {
- param_depth -= 1;
- 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(MalformedGenerics::HasFullyQualifiedSyntax);
- }
- } else {
- if param_depth == 0 {
- stripped_segment.push(c);
- } else {
- latest_generics_chunk.push(c);
- }
- }
- }
-
- if param_depth == 0 {
- Ok(stripped_segment)
- } else {
- // The segment has unbalanced angle brackets, e.g. `Vec<T` or `Vec<T>>`
- Err(MalformedGenerics::UnbalancedAngleBrackets)
- }
-}