diff options
Diffstat (limited to 'src/librustdoc/passes')
-rw-r--r-- | src/librustdoc/passes/calculate_doc_coverage.rs | 8 | ||||
-rw-r--r-- | src/librustdoc/passes/check_doc_test_visibility.rs | 38 | ||||
-rw-r--r-- | src/librustdoc/passes/collect_intra_doc_links.rs | 313 | ||||
-rw-r--r-- | src/librustdoc/passes/collect_intra_doc_links/early.rs | 407 | ||||
-rw-r--r-- | src/librustdoc/passes/collect_trait_impls.rs | 91 | ||||
-rw-r--r-- | src/librustdoc/passes/lint/check_code_block_syntax.rs | 11 | ||||
-rw-r--r-- | src/librustdoc/passes/lint/html_tags.rs | 10 | ||||
-rw-r--r-- | src/librustdoc/passes/mod.rs | 3 | ||||
-rw-r--r-- | src/librustdoc/passes/propagate_doc_cfg.rs | 17 | ||||
-rw-r--r-- | src/librustdoc/passes/strip_hidden.rs | 117 | ||||
-rw-r--r-- | src/librustdoc/passes/stripper.rs | 32 |
11 files changed, 281 insertions, 766 deletions
diff --git a/src/librustdoc/passes/calculate_doc_coverage.rs b/src/librustdoc/passes/calculate_doc_coverage.rs index 02b227896..0b22f943d 100644 --- a/src/librustdoc/passes/calculate_doc_coverage.rs +++ b/src/librustdoc/passes/calculate_doc_coverage.rs @@ -216,13 +216,7 @@ impl<'a, 'b> DocVisitor for CoverageCalculator<'a, 'b> { ); let has_doc_example = tests.found_tests != 0; - // The `expect_def_id()` should be okay because `local_def_id_to_hir_id` - // would presumably panic if a fake `DefIndex` were passed. - let hir_id = self - .ctx - .tcx - .hir() - .local_def_id_to_hir_id(i.item_id.expect_def_id().expect_local()); + let hir_id = DocContext::as_local_hir_id(self.ctx.tcx, i.item_id).unwrap(); let (level, source) = self.ctx.tcx.lint_level_at_node(MISSING_DOCS, hir_id); // In case we have: diff --git a/src/librustdoc/passes/check_doc_test_visibility.rs b/src/librustdoc/passes/check_doc_test_visibility.rs index 6aa2dda98..a39d57d42 100644 --- a/src/librustdoc/passes/check_doc_test_visibility.rs +++ b/src/librustdoc/passes/check_doc_test_visibility.rs @@ -14,8 +14,8 @@ use crate::visit::DocVisitor; use crate::visit_ast::inherits_doc_hidden; use rustc_hir as hir; use rustc_middle::lint::LintLevelSource; +use rustc_middle::ty::DefIdTree; use rustc_session::lint; -use rustc_span::symbol::sym; pub(crate) const CHECK_DOC_TEST_VISIBILITY: Pass = Pass { name: "check_doc_test_visibility", @@ -79,30 +79,32 @@ pub(crate) fn should_have_doc_example(cx: &DocContext<'_>, item: &clean::Item) - // The `expect_def_id()` should be okay because `local_def_id_to_hir_id` // would presumably panic if a fake `DefIndex` were passed. - let hir_id = cx.tcx.hir().local_def_id_to_hir_id(item.item_id.expect_def_id().expect_local()); + let def_id = item.item_id.expect_def_id().expect_local(); // check if parent is trait impl - if let Some(parent_hir_id) = cx.tcx.hir().opt_parent_id(hir_id) { - if let Some(parent_node) = cx.tcx.hir().find(parent_hir_id) { - if matches!( - parent_node, - hir::Node::Item(hir::Item { - kind: hir::ItemKind::Impl(hir::Impl { of_trait: Some(_), .. }), - .. - }) - ) { - return false; - } - } + if let Some(parent_def_id) = cx.tcx.opt_local_parent(def_id) && + let Some(parent_node) = cx.tcx.hir().find_by_def_id(parent_def_id) && + matches!( + parent_node, + hir::Node::Item(hir::Item { + kind: hir::ItemKind::Impl(hir::Impl { of_trait: Some(_), .. }), + .. + }) + ) + { + return false; } - if cx.tcx.hir().attrs(hir_id).lists(sym::doc).has_word(sym::hidden) - || inherits_doc_hidden(cx.tcx, hir_id) - || cx.tcx.hir().span(hir_id).in_derive_expansion() + if cx.tcx.is_doc_hidden(def_id.to_def_id()) + || inherits_doc_hidden(cx.tcx, def_id) + || cx.tcx.def_span(def_id.to_def_id()).in_derive_expansion() { return false; } - let (level, source) = cx.tcx.lint_level_at_node(crate::lint::MISSING_DOC_CODE_EXAMPLES, hir_id); + let (level, source) = cx.tcx.lint_level_at_node( + crate::lint::MISSING_DOC_CODE_EXAMPLES, + cx.tcx.hir().local_def_id_to_hir_id(def_id), + ); level != lint::Level::Allow || matches!(source, LintLevelSource::Default) } 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, ¬e); + } else { + diag.note(¬e); + } + 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) - } -} diff --git a/src/librustdoc/passes/collect_intra_doc_links/early.rs b/src/librustdoc/passes/collect_intra_doc_links/early.rs deleted file mode 100644 index 42677bd84..000000000 --- a/src/librustdoc/passes/collect_intra_doc_links/early.rs +++ /dev/null @@ -1,407 +0,0 @@ -use crate::clean::Attributes; -use crate::core::ResolverCaches; -use crate::passes::collect_intra_doc_links::preprocessed_markdown_links; -use crate::passes::collect_intra_doc_links::{Disambiguator, PreprocessedMarkdownLink}; - -use rustc_ast::visit::{self, AssocCtxt, Visitor}; -use rustc_ast::{self as ast, ItemKind}; -use rustc_data_structures::fx::FxHashMap; -use rustc_hir::def::Namespace::*; -use rustc_hir::def::{DefKind, Namespace, Res}; -use rustc_hir::def_id::{DefId, DefIdMap, DefIdSet, CRATE_DEF_ID}; -use rustc_hir::TraitCandidate; -use rustc_middle::ty::{DefIdTree, Visibility}; -use rustc_resolve::{ParentScope, Resolver}; -use rustc_span::symbol::sym; -use rustc_span::{Symbol, SyntaxContext}; - -use std::collections::hash_map::Entry; -use std::mem; - -pub(crate) fn early_resolve_intra_doc_links( - resolver: &mut Resolver<'_>, - krate: &ast::Crate, - document_private_items: bool, -) -> ResolverCaches { - let parent_scope = - ParentScope::module(resolver.expect_module(CRATE_DEF_ID.to_def_id()), resolver); - let mut link_resolver = EarlyDocLinkResolver { - resolver, - parent_scope, - visited_mods: Default::default(), - markdown_links: Default::default(), - doc_link_resolutions: Default::default(), - traits_in_scope: Default::default(), - all_trait_impls: Default::default(), - all_macro_rules: Default::default(), - document_private_items, - }; - - // Overridden `visit_item` below doesn't apply to the crate root, - // so we have to visit its attributes and reexports separately. - link_resolver.resolve_doc_links_local(&krate.attrs); - link_resolver.process_module_children_or_reexports(CRATE_DEF_ID.to_def_id()); - visit::walk_crate(&mut link_resolver, krate); - - // FIXME: somehow rustdoc is still missing crates even though we loaded all - // the known necessary crates. Load them all unconditionally until we find a way to fix this. - // DO NOT REMOVE THIS without first testing on the reproducer in - // https://github.com/jyn514/objr/commit/edcee7b8124abf0e4c63873e8422ff81beb11ebb - for (extern_name, _) in - link_resolver.resolver.sess().opts.externs.iter().filter(|(_, entry)| entry.add_prelude) - { - link_resolver.resolver.resolve_rustdoc_path(extern_name, TypeNS, parent_scope); - } - - link_resolver.process_extern_impls(); - - ResolverCaches { - markdown_links: Some(link_resolver.markdown_links), - doc_link_resolutions: link_resolver.doc_link_resolutions, - traits_in_scope: link_resolver.traits_in_scope, - all_trait_impls: Some(link_resolver.all_trait_impls), - all_macro_rules: link_resolver.all_macro_rules, - } -} - -fn doc_attrs<'a>(attrs: impl Iterator<Item = &'a ast::Attribute>) -> Attributes { - Attributes::from_ast_iter(attrs.map(|attr| (attr, None)), true) -} - -struct EarlyDocLinkResolver<'r, 'ra> { - resolver: &'r mut Resolver<'ra>, - parent_scope: ParentScope<'ra>, - visited_mods: DefIdSet, - markdown_links: FxHashMap<String, Vec<PreprocessedMarkdownLink>>, - doc_link_resolutions: FxHashMap<(Symbol, Namespace, DefId), Option<Res<ast::NodeId>>>, - traits_in_scope: DefIdMap<Vec<TraitCandidate>>, - all_trait_impls: Vec<DefId>, - all_macro_rules: FxHashMap<Symbol, Res<ast::NodeId>>, - document_private_items: bool, -} - -impl<'ra> EarlyDocLinkResolver<'_, 'ra> { - fn add_traits_in_scope(&mut self, def_id: DefId) { - // Calls to `traits_in_scope` are expensive, so try to avoid them if only possible. - // Keys in the `traits_in_scope` cache are always module IDs. - if let Entry::Vacant(entry) = self.traits_in_scope.entry(def_id) { - let module = self.resolver.get_nearest_non_block_module(def_id); - let module_id = module.def_id(); - let entry = if module_id == def_id { - Some(entry) - } else if let Entry::Vacant(entry) = self.traits_in_scope.entry(module_id) { - Some(entry) - } else { - None - }; - if let Some(entry) = entry { - entry.insert(self.resolver.traits_in_scope( - None, - &ParentScope::module(module, self.resolver), - SyntaxContext::root(), - None, - )); - } - } - } - - /// Add traits in scope for links in impls collected by the `collect-intra-doc-links` pass. - /// That pass filters impls using type-based information, but we don't yet have such - /// information here, so we just conservatively calculate traits in scope for *all* modules - /// having impls in them. - fn process_extern_impls(&mut self) { - // Resolving links in already existing crates may trigger loading of new crates. - let mut start_cnum = 0; - loop { - let crates = Vec::from_iter(self.resolver.cstore().crates_untracked()); - for &cnum in &crates[start_cnum..] { - let all_trait_impls = - Vec::from_iter(self.resolver.cstore().trait_impls_in_crate_untracked(cnum)); - let all_inherent_impls = - Vec::from_iter(self.resolver.cstore().inherent_impls_in_crate_untracked(cnum)); - let all_incoherent_impls = Vec::from_iter( - self.resolver.cstore().incoherent_impls_in_crate_untracked(cnum), - ); - - // Querying traits in scope is expensive so we try to prune the impl lists using - // privacy, private traits and impls from other crates are never documented in - // the current crate, and links in their doc comments are not resolved. - for &(trait_def_id, impl_def_id, simplified_self_ty) in &all_trait_impls { - if self.resolver.cstore().visibility_untracked(trait_def_id).is_public() - && simplified_self_ty.and_then(|ty| ty.def()).map_or(true, |ty_def_id| { - self.resolver.cstore().visibility_untracked(ty_def_id).is_public() - }) - { - if self.visited_mods.insert(trait_def_id) { - self.resolve_doc_links_extern_impl(trait_def_id, false); - } - self.resolve_doc_links_extern_impl(impl_def_id, false); - } - } - for (ty_def_id, impl_def_id) in all_inherent_impls { - if self.resolver.cstore().visibility_untracked(ty_def_id).is_public() { - self.resolve_doc_links_extern_impl(impl_def_id, true); - } - } - for impl_def_id in all_incoherent_impls { - self.resolve_doc_links_extern_impl(impl_def_id, true); - } - - self.all_trait_impls - .extend(all_trait_impls.into_iter().map(|(_, def_id, _)| def_id)); - } - - if crates.len() > start_cnum { - start_cnum = crates.len(); - } else { - break; - } - } - } - - fn resolve_doc_links_extern_impl(&mut self, def_id: DefId, is_inherent: bool) { - self.resolve_doc_links_extern_outer_fixme(def_id, def_id); - let assoc_item_def_ids = Vec::from_iter( - self.resolver.cstore().associated_item_def_ids_untracked(def_id, self.resolver.sess()), - ); - for assoc_def_id in assoc_item_def_ids { - if !is_inherent || self.resolver.cstore().visibility_untracked(assoc_def_id).is_public() - { - self.resolve_doc_links_extern_outer_fixme(assoc_def_id, def_id); - } - } - } - - // FIXME: replace all uses with `resolve_doc_links_extern_outer` to actually resolve links, not - // just add traits in scope. This may be expensive and require benchmarking and optimization. - fn resolve_doc_links_extern_outer_fixme(&mut self, def_id: DefId, scope_id: DefId) { - if !self.resolver.cstore().may_have_doc_links_untracked(def_id) { - return; - } - if let Some(parent_id) = self.resolver.opt_parent(scope_id) { - self.add_traits_in_scope(parent_id); - } - } - - fn resolve_doc_links_extern_outer(&mut self, def_id: DefId, scope_id: DefId) { - if !self.resolver.cstore().may_have_doc_links_untracked(def_id) { - return; - } - let attrs = Vec::from_iter( - self.resolver.cstore().item_attrs_untracked(def_id, self.resolver.sess()), - ); - let parent_scope = ParentScope::module( - self.resolver.get_nearest_non_block_module( - self.resolver.opt_parent(scope_id).unwrap_or(scope_id), - ), - self.resolver, - ); - self.resolve_doc_links(doc_attrs(attrs.iter()), parent_scope); - } - - fn resolve_doc_links_extern_inner(&mut self, def_id: DefId) { - if !self.resolver.cstore().may_have_doc_links_untracked(def_id) { - return; - } - let attrs = Vec::from_iter( - self.resolver.cstore().item_attrs_untracked(def_id, self.resolver.sess()), - ); - let parent_scope = ParentScope::module(self.resolver.expect_module(def_id), self.resolver); - self.resolve_doc_links(doc_attrs(attrs.iter()), parent_scope); - } - - fn resolve_doc_links_local(&mut self, attrs: &[ast::Attribute]) { - if !attrs.iter().any(|attr| attr.may_have_doc_links()) { - return; - } - self.resolve_doc_links(doc_attrs(attrs.iter()), self.parent_scope); - } - - fn resolve_and_cache( - &mut self, - path_str: &str, - ns: Namespace, - parent_scope: &ParentScope<'ra>, - ) -> bool { - // FIXME: This caching may be incorrect in case of multiple `macro_rules` - // items with the same name in the same module. - self.doc_link_resolutions - .entry((Symbol::intern(path_str), ns, parent_scope.module.def_id())) - .or_insert_with_key(|(path, ns, _)| { - self.resolver.resolve_rustdoc_path(path.as_str(), *ns, *parent_scope) - }) - .is_some() - } - - fn resolve_doc_links(&mut self, attrs: Attributes, parent_scope: ParentScope<'ra>) { - let mut need_traits_in_scope = false; - for (doc_module, doc) in attrs.prepare_to_doc_link_resolution() { - assert_eq!(doc_module, None); - let mut tmp_links = mem::take(&mut self.markdown_links); - let links = - tmp_links.entry(doc).or_insert_with_key(|doc| preprocessed_markdown_links(doc)); - for PreprocessedMarkdownLink(pp_link, _) in links { - if let Ok(pinfo) = pp_link { - // The logic here is a conservative approximation for path resolution in - // `resolve_with_disambiguator`. - if let Some(ns) = pinfo.disambiguator.map(Disambiguator::ns) { - if self.resolve_and_cache(&pinfo.path_str, ns, &parent_scope) { - continue; - } - } - - // Resolve all namespaces due to no disambiguator or for diagnostics. - let mut any_resolved = false; - let mut need_assoc = false; - for ns in [TypeNS, ValueNS, MacroNS] { - if self.resolve_and_cache(&pinfo.path_str, ns, &parent_scope) { - any_resolved = true; - } else if ns != MacroNS { - need_assoc = true; - } - } - - // Resolve all prefixes for type-relative resolution or for diagnostics. - if need_assoc || !any_resolved { - let mut path = &pinfo.path_str[..]; - while let Some(idx) = path.rfind("::") { - path = &path[..idx]; - need_traits_in_scope = true; - for ns in [TypeNS, ValueNS, MacroNS] { - self.resolve_and_cache(path, ns, &parent_scope); - } - } - } - } - } - self.markdown_links = tmp_links; - } - - if need_traits_in_scope { - self.add_traits_in_scope(parent_scope.module.def_id()); - } - } - - /// When reexports are inlined, they are replaced with item which they refer to, those items - /// may have links in their doc comments, those links are resolved at the item definition site, - /// so we need to know traits in scope at that definition site. - fn process_module_children_or_reexports(&mut self, module_id: DefId) { - if !self.visited_mods.insert(module_id) { - return; // avoid infinite recursion - } - - for child in self.resolver.module_children_or_reexports(module_id) { - // This condition should give a superset of `denied` from `fn clean_use_statement`. - if child.vis.is_public() - || self.document_private_items - && child.vis != Visibility::Restricted(module_id) - && module_id.is_local() - { - if let Some(def_id) = child.res.opt_def_id() && !def_id.is_local() { - let scope_id = match child.res { - Res::Def( - DefKind::Variant - | DefKind::AssocTy - | DefKind::AssocFn - | DefKind::AssocConst, - .., - ) => self.resolver.parent(def_id), - _ => def_id, - }; - self.resolve_doc_links_extern_outer(def_id, scope_id); // Outer attribute scope - if let Res::Def(DefKind::Mod, ..) = child.res { - self.resolve_doc_links_extern_inner(def_id); // Inner attribute scope - } - if let Res::Def(DefKind::Mod | DefKind::Enum | DefKind::Trait, ..) = child.res { - self.process_module_children_or_reexports(def_id); - } - if let Res::Def(DefKind::Struct | DefKind::Union | DefKind::Variant, _) = - child.res - { - let field_def_ids = Vec::from_iter( - self.resolver - .cstore() - .associated_item_def_ids_untracked(def_id, self.resolver.sess()), - ); - for field_def_id in field_def_ids { - self.resolve_doc_links_extern_outer(field_def_id, scope_id); - } - } - } - } - } - } -} - -impl Visitor<'_> for EarlyDocLinkResolver<'_, '_> { - fn visit_item(&mut self, item: &ast::Item) { - self.resolve_doc_links_local(&item.attrs); // Outer attribute scope - if let ItemKind::Mod(..) = item.kind { - let module_def_id = self.resolver.local_def_id(item.id).to_def_id(); - let module = self.resolver.expect_module(module_def_id); - let old_module = mem::replace(&mut self.parent_scope.module, module); - let old_macro_rules = self.parent_scope.macro_rules; - self.resolve_doc_links_local(&item.attrs); // Inner attribute scope - self.process_module_children_or_reexports(module_def_id); - visit::walk_item(self, item); - if item - .attrs - .iter() - .all(|attr| !attr.has_name(sym::macro_use) && !attr.has_name(sym::macro_escape)) - { - self.parent_scope.macro_rules = old_macro_rules; - } - self.parent_scope.module = old_module; - } else { - match &item.kind { - ItemKind::Impl(box ast::Impl { of_trait: Some(trait_ref), .. }) => { - if let Some(partial_res) = self.resolver.get_partial_res(trait_ref.ref_id) - && let Some(res) = partial_res.full_res() - && let Some(trait_def_id) = res.opt_def_id() - && !trait_def_id.is_local() - && self.visited_mods.insert(trait_def_id) { - self.resolve_doc_links_extern_impl(trait_def_id, false); - } - self.all_trait_impls.push(self.resolver.local_def_id(item.id).to_def_id()); - } - ItemKind::MacroDef(macro_def) if macro_def.macro_rules => { - let (macro_rules_scope, res) = - self.resolver.macro_rules_scope(self.resolver.local_def_id(item.id)); - self.parent_scope.macro_rules = macro_rules_scope; - self.all_macro_rules.insert(item.ident.name, res); - } - _ => {} - } - visit::walk_item(self, item); - } - } - - fn visit_assoc_item(&mut self, item: &ast::AssocItem, ctxt: AssocCtxt) { - self.resolve_doc_links_local(&item.attrs); - visit::walk_assoc_item(self, item, ctxt) - } - - fn visit_foreign_item(&mut self, item: &ast::ForeignItem) { - self.resolve_doc_links_local(&item.attrs); - visit::walk_foreign_item(self, item) - } - - fn visit_variant(&mut self, v: &ast::Variant) { - self.resolve_doc_links_local(&v.attrs); - visit::walk_variant(self, v) - } - - fn visit_field_def(&mut self, field: &ast::FieldDef) { - self.resolve_doc_links_local(&field.attrs); - visit::walk_field_def(self, field) - } - - fn visit_block(&mut self, block: &ast::Block) { - let old_macro_rules = self.parent_scope.macro_rules; - visit::walk_block(self, block); - self.parent_scope.macro_rules = old_macro_rules; - } - - // NOTE: if doc-comments are ever allowed on other nodes (e.g. function parameters), - // then this will have to implement other visitor methods too. -} diff --git a/src/librustdoc/passes/collect_trait_impls.rs b/src/librustdoc/passes/collect_trait_impls.rs index 79db3c6c3..01ed4a60b 100644 --- a/src/librustdoc/passes/collect_trait_impls.rs +++ b/src/librustdoc/passes/collect_trait_impls.rs @@ -7,8 +7,8 @@ use crate::core::DocContext; use crate::formats::cache::Cache; use crate::visit::DocVisitor; -use rustc_data_structures::fx::{FxHashMap, FxHashSet}; -use rustc_hir::def_id::{DefId, LOCAL_CRATE}; +use rustc_data_structures::fx::FxHashSet; +use rustc_hir::def_id::{DefId, DefIdMap, DefIdSet, LOCAL_CRATE}; use rustc_middle::ty::{self, DefIdTree}; use rustc_span::symbol::sym; @@ -45,18 +45,20 @@ pub(crate) fn collect_trait_impls(mut krate: Crate, cx: &mut DocContext<'_>) -> let mut new_items_local = Vec::new(); // External trait impls. - cx.with_all_trait_impls(|cx, all_trait_impls| { + { let _prof_timer = cx.tcx.sess.prof.generic_activity("build_extern_trait_impls"); - for &impl_def_id in all_trait_impls.iter().skip_while(|def_id| def_id.is_local()) { - inline::build_impl(cx, None, impl_def_id, None, &mut new_items_external); + for &cnum in cx.tcx.crates(()) { + for &impl_def_id in cx.tcx.trait_impls_in_crate(cnum) { + inline::build_impl(cx, None, impl_def_id, None, &mut new_items_external); + } } - }); + } // Local trait impls. - cx.with_all_trait_impls(|cx, all_trait_impls| { + { let _prof_timer = cx.tcx.sess.prof.generic_activity("build_local_trait_impls"); let mut attr_buf = Vec::new(); - for &impl_def_id in all_trait_impls.iter().take_while(|def_id| def_id.is_local()) { + for &impl_def_id in cx.tcx.trait_impls_in_crate(LOCAL_CRATE) { let mut parent = Some(cx.tcx.parent(impl_def_id)); while let Some(did) = parent { attr_buf.extend( @@ -76,7 +78,7 @@ pub(crate) fn collect_trait_impls(mut krate: Crate, cx: &mut DocContext<'_>) -> inline::build_impl(cx, None, impl_def_id, Some(&attr_buf), &mut new_items_local); attr_buf.clear(); } - }); + } cx.tcx.sess.prof.generic_activity("build_primitive_trait_impls").run(|| { for def_id in PrimitiveType::all_impls(cx.tcx) { @@ -107,7 +109,7 @@ pub(crate) fn collect_trait_impls(mut krate: Crate, cx: &mut DocContext<'_>) -> // `Generics`. To avoid relying on the `impl` block, these // things would need to be created from wholecloth, in a // form that is valid for use in type inference. - let ty = tcx.type_of(def_id); + let ty = tcx.type_of(def_id).subst_identity(); match ty.kind() { ty::Slice(ty) | ty::Ref(_, ty, _) @@ -126,14 +128,14 @@ pub(crate) fn collect_trait_impls(mut krate: Crate, cx: &mut DocContext<'_>) -> }); let mut cleaner = BadImplStripper { prims, items: crate_items, cache: &cx.cache }; - let mut type_did_to_deref_target: FxHashMap<DefId, &Type> = FxHashMap::default(); + let mut type_did_to_deref_target: DefIdMap<&Type> = DefIdMap::default(); // Follow all `Deref` targets of included items and recursively add them as valid fn add_deref_target( cx: &DocContext<'_>, - map: &FxHashMap<DefId, &Type>, + map: &DefIdMap<&Type>, cleaner: &mut BadImplStripper<'_>, - targets: &mut FxHashSet<DefId>, + targets: &mut DefIdSet, type_did: DefId, ) { if let Some(target) = map.get(&type_did) { @@ -154,39 +156,38 @@ pub(crate) fn collect_trait_impls(mut krate: Crate, cx: &mut DocContext<'_>) -> // scan through included items ahead of time to splice in Deref targets to the "valid" sets for it in new_items_external.iter().chain(new_items_local.iter()) { - if let ImplItem(box Impl { ref for_, ref trait_, ref items, .. }) = *it.kind { - if trait_.as_ref().map(|t| t.def_id()) == cx.tcx.lang_items().deref_trait() - && cleaner.keep_impl(for_, true) - { - let target = items - .iter() - .find_map(|item| match *item.kind { - AssocTypeItem(ref t, _) => Some(&t.type_), - _ => None, - }) - .expect("Deref impl without Target type"); + if let ImplItem(box Impl { ref for_, ref trait_, ref items, .. }) = *it.kind && + trait_.as_ref().map(|t| t.def_id()) == cx.tcx.lang_items().deref_trait() && + cleaner.keep_impl(for_, true) + { + let target = items + .iter() + .find_map(|item| match *item.kind { + AssocTypeItem(ref t, _) => Some(&t.type_), + _ => None, + }) + .expect("Deref impl without Target type"); - if let Some(prim) = target.primitive_type() { - cleaner.prims.insert(prim); - } else if let Some(did) = target.def_id(&cx.cache) { - cleaner.items.insert(did.into()); - } - if let Some(for_did) = for_.def_id(&cx.cache) { - if type_did_to_deref_target.insert(for_did, target).is_none() { - // Since only the `DefId` portion of the `Type` instances is known to be same for both the - // `Deref` target type and the impl for type positions, this map of types is keyed by - // `DefId` and for convenience uses a special cleaner that accepts `DefId`s directly. - if cleaner.keep_impl_with_def_id(for_did.into()) { - let mut targets = FxHashSet::default(); - targets.insert(for_did); - add_deref_target( - cx, - &type_did_to_deref_target, - &mut cleaner, - &mut targets, - for_did, - ); - } + if let Some(prim) = target.primitive_type() { + cleaner.prims.insert(prim); + } else if let Some(did) = target.def_id(&cx.cache) { + cleaner.items.insert(did.into()); + } + if let Some(for_did) = for_.def_id(&cx.cache) { + if type_did_to_deref_target.insert(for_did, target).is_none() { + // Since only the `DefId` portion of the `Type` instances is known to be same for both the + // `Deref` target type and the impl for type positions, this map of types is keyed by + // `DefId` and for convenience uses a special cleaner that accepts `DefId`s directly. + if cleaner.keep_impl_with_def_id(for_did.into()) { + let mut targets = DefIdSet::default(); + targets.insert(for_did); + add_deref_target( + cx, + &type_did_to_deref_target, + &mut cleaner, + &mut targets, + for_did, + ); } } } diff --git a/src/librustdoc/passes/lint/check_code_block_syntax.rs b/src/librustdoc/passes/lint/check_code_block_syntax.rs index 7158355ff..26fbb03a4 100644 --- a/src/librustdoc/passes/lint/check_code_block_syntax.rs +++ b/src/librustdoc/passes/lint/check_code_block_syntax.rs @@ -19,8 +19,7 @@ use crate::passes::source_span_for_markdown_range; pub(crate) fn visit_item(cx: &DocContext<'_>, item: &clean::Item) { if let Some(dox) = &item.attrs.collapsed_doc_value() { let sp = item.attr_span(cx.tcx); - let extra = - crate::html::markdown::ExtraInfo::new_did(cx.tcx, item.item_id.expect_def_id(), sp); + let extra = crate::html::markdown::ExtraInfo::new(cx.tcx, item.item_id.expect_def_id(), sp); for code_block in markdown::rust_code_blocks(dox, &extra) { check_rust_syntax(cx, item, dox, code_block); } @@ -34,8 +33,10 @@ fn check_rust_syntax( code_block: RustCodeBlock, ) { let buffer = Lrc::new(Lock::new(Buffer::default())); - let fallback_bundle = - rustc_errors::fallback_fluent_bundle(rustc_errors::DEFAULT_LOCALE_RESOURCES, false); + let fallback_bundle = rustc_errors::fallback_fluent_bundle( + rustc_driver::DEFAULT_LOCALE_RESOURCES.to_vec(), + false, + ); let emitter = BufferEmitter { buffer: Lrc::clone(&buffer), fallback_bundle }; let sm = Lrc::new(SourceMap::new(FilePathMapping::empty())); @@ -73,7 +74,6 @@ fn check_rust_syntax( return; }; - let hir_id = cx.tcx.hir().local_def_id_to_hir_id(local_id); let empty_block = code_block.lang_string == Default::default() && code_block.is_fenced; let is_ignore = code_block.lang_string.ignore != markdown::Ignore::None; @@ -93,6 +93,7 @@ fn check_rust_syntax( // Finally build and emit the completed diagnostic. // All points of divergence have been handled earlier so this can be // done the same way whether the span is precise or not. + let hir_id = cx.tcx.hir().local_def_id_to_hir_id(local_id); cx.tcx.struct_span_lint_hir(crate::lint::INVALID_RUST_CODEBLOCKS, hir_id, sp, msg, |lint| { let explanation = if is_ignore { "`ignore` code blocks require valid Rust code for syntax highlighting; \ diff --git a/src/librustdoc/passes/lint/html_tags.rs b/src/librustdoc/passes/lint/html_tags.rs index 070c0aab5..4f72df5a5 100644 --- a/src/librustdoc/passes/lint/html_tags.rs +++ b/src/librustdoc/passes/lint/html_tags.rs @@ -113,7 +113,7 @@ pub(crate) fn visit_item(cx: &DocContext<'_>, item: &Item) { if let Some(link) = link_names.iter().find(|link| *link.original_text == *broken_link.reference) { - Some((link.href.as_str().into(), link.new_text.as_str().into())) + Some((link.href.as_str().into(), link.new_text.to_string().into())) } else if matches!( &broken_link.link_type, LinkType::Reference | LinkType::ReferenceUnknown @@ -210,11 +210,9 @@ fn extract_path_backwards(text: &str, end_pos: usize) -> Option<usize> { .take_while(|(_, c)| is_id_start(*c) || is_id_continue(*c)) .reduce(|_accum, item| item) .and_then(|(new_pos, c)| is_id_start(c).then_some(new_pos)); - if let Some(new_pos) = new_pos { - if current_pos != new_pos { - current_pos = new_pos; - continue; - } + if let Some(new_pos) = new_pos && current_pos != new_pos { + current_pos = new_pos; + continue; } break; } diff --git a/src/librustdoc/passes/mod.rs b/src/librustdoc/passes/mod.rs index 634e70ec9..4b1ff68df 100644 --- a/src/librustdoc/passes/mod.rs +++ b/src/librustdoc/passes/mod.rs @@ -2,11 +2,12 @@ //! process. use rustc_middle::ty::TyCtxt; +use rustc_resolve::rustdoc::DocFragmentKind; use rustc_span::{InnerSpan, Span, DUMMY_SP}; use std::ops::Range; use self::Condition::*; -use crate::clean::{self, DocFragmentKind}; +use crate::clean; use crate::core::DocContext; mod stripper; diff --git a/src/librustdoc/passes/propagate_doc_cfg.rs b/src/librustdoc/passes/propagate_doc_cfg.rs index de3a4b339..a4bc48690 100644 --- a/src/librustdoc/passes/propagate_doc_cfg.rs +++ b/src/librustdoc/passes/propagate_doc_cfg.rs @@ -9,6 +9,7 @@ use crate::fold::DocFolder; use crate::passes::Pass; use rustc_hir::def_id::LocalDefId; +use rustc_middle::ty::DefIdTree; pub(crate) const PROPAGATE_DOC_CFG: Pass = Pass { name: "propagate-doc-cfg", @@ -41,24 +42,22 @@ impl<'a, 'tcx> CfgPropagator<'a, 'tcx> { let Some(def_id) = item.item_id.as_def_id().and_then(|def_id| def_id.as_local()) else { return }; - let hir = self.cx.tcx.hir(); - let hir_id = hir.local_def_id_to_hir_id(def_id); - if check_parent { - let expected_parent = hir.get_parent_item(hir_id); + let expected_parent = self.cx.tcx.opt_local_parent(def_id); // If parents are different, it means that `item` is a reexport and we need // to compute the actual `cfg` by iterating through its "real" parents. - if self.parent == Some(expected_parent.def_id) { + if self.parent.is_some() && self.parent == expected_parent { return; } } let mut attrs = Vec::new(); - for (parent_hir_id, _) in hir.parent_iter(hir_id) { - if let Some(def_id) = hir.opt_local_def_id(parent_hir_id) { - attrs.extend_from_slice(load_attrs(self.cx, def_id.to_def_id())); - } + let mut next_def_id = def_id; + while let Some(parent_def_id) = self.cx.tcx.opt_local_parent(next_def_id) { + attrs.extend_from_slice(load_attrs(self.cx, parent_def_id.to_def_id())); + next_def_id = parent_def_id; } + let (_, cfg) = merge_attrs(self.cx, None, item.attrs.other_attrs.as_slice(), Some(&attrs)); item.cfg = cfg; } diff --git a/src/librustdoc/passes/strip_hidden.rs b/src/librustdoc/passes/strip_hidden.rs index e07a788a7..890b3e8d6 100644 --- a/src/librustdoc/passes/strip_hidden.rs +++ b/src/librustdoc/passes/strip_hidden.rs @@ -1,4 +1,6 @@ //! Strip all doc(hidden) items from the output. + +use rustc_middle::ty::TyCtxt; use rustc_span::symbol::sym; use std::mem; @@ -7,6 +9,7 @@ use crate::clean::{Item, ItemIdSet, NestedAttributesExt}; use crate::core::DocContext; use crate::fold::{strip_item, DocFolder}; use crate::passes::{ImplStripper, Pass}; +use crate::visit_ast::inherits_doc_hidden; pub(crate) const STRIP_HIDDEN: Pass = Pass { name: "strip-hidden", @@ -21,7 +24,12 @@ pub(crate) fn strip_hidden(krate: clean::Crate, cx: &mut DocContext<'_>) -> clea // strip all #[doc(hidden)] items let krate = { - let mut stripper = Stripper { retained: &mut retained, update_retained: true }; + let mut stripper = Stripper { + retained: &mut retained, + update_retained: true, + tcx: cx.tcx, + is_in_hidden_item: false, + }; stripper.fold_crate(krate) }; @@ -36,40 +44,97 @@ pub(crate) fn strip_hidden(krate: clean::Crate, cx: &mut DocContext<'_>) -> clea stripper.fold_crate(krate) } -struct Stripper<'a> { +struct Stripper<'a, 'tcx> { retained: &'a mut ItemIdSet, update_retained: bool, + tcx: TyCtxt<'tcx>, + is_in_hidden_item: bool, } -impl<'a> DocFolder for Stripper<'a> { +impl<'a, 'tcx> Stripper<'a, 'tcx> { + fn set_is_in_hidden_item_and_fold(&mut self, is_in_hidden_item: bool, i: Item) -> Item { + let prev = self.is_in_hidden_item; + self.is_in_hidden_item |= is_in_hidden_item; + let ret = self.fold_item_recur(i); + self.is_in_hidden_item = prev; + ret + } + + /// In case `i` is a non-hidden impl block, then we special-case it by changing the value + /// of `is_in_hidden_item` to `true` because the impl children inherit its visibility. + fn recurse_in_impl_or_exported_macro(&mut self, i: Item) -> Item { + let prev = mem::replace(&mut self.is_in_hidden_item, false); + let ret = self.fold_item_recur(i); + self.is_in_hidden_item = prev; + ret + } +} + +impl<'a, 'tcx> DocFolder for Stripper<'a, 'tcx> { fn fold_item(&mut self, i: Item) -> Option<Item> { - if i.attrs.lists(sym::doc).has_word(sym::hidden) { - debug!("strip_hidden: stripping {:?} {:?}", i.type_(), i.name); - // Use a dedicated hidden item for fields, variants, and modules. - // We need to keep private fields and variants, so that the docs - // can show a placeholder "// some variants omitted". We need to keep - // private modules, because they can contain impl blocks, and impl - // block privacy is inherited from the type and trait, not from the - // module it's defined in. Both of these are marked "stripped," and - // not included in the final docs, but since they still have an effect - // on the final doc, cannot be completely removed from the Clean IR. - match *i.kind { - clean::StructFieldItem(..) | clean::ModuleItem(..) | clean::VariantItem(..) => { - // We need to recurse into stripped modules to - // strip things like impl methods but when doing so - // we must not add any items to the `retained` set. - let old = mem::replace(&mut self.update_retained, false); - let ret = strip_item(self.fold_item_recur(i)); - self.update_retained = old; - return Some(ret); - } - _ => return None, + let has_doc_hidden = i.attrs.lists(sym::doc).has_word(sym::hidden); + let is_impl_or_exported_macro = match *i.kind { + clean::ImplItem(..) => true, + // If the macro has the `#[macro_export]` attribute, it means it's accessible at the + // crate level so it should be handled differently. + clean::MacroItem(..) => { + i.attrs.other_attrs.iter().any(|attr| attr.has_name(sym::macro_export)) } - } else { + _ => false, + }; + let mut is_hidden = has_doc_hidden; + if !is_impl_or_exported_macro { + is_hidden = self.is_in_hidden_item || has_doc_hidden; + if !is_hidden && i.inline_stmt_id.is_none() { + // We don't need to check if it's coming from a reexport since the reexport itself was + // already checked. + is_hidden = i + .item_id + .as_def_id() + .and_then(|def_id| def_id.as_local()) + .map(|def_id| inherits_doc_hidden(self.tcx, def_id)) + .unwrap_or(false); + } + } + if !is_hidden { if self.update_retained { self.retained.insert(i.item_id); } + return Some(if is_impl_or_exported_macro { + self.recurse_in_impl_or_exported_macro(i) + } else { + self.set_is_in_hidden_item_and_fold(false, i) + }); + } + debug!("strip_hidden: stripping {:?} {:?}", i.type_(), i.name); + // Use a dedicated hidden item for fields, variants, and modules. + // We need to keep private fields and variants, so that the docs + // can show a placeholder "// some variants omitted". We need to keep + // private modules, because they can contain impl blocks, and impl + // block privacy is inherited from the type and trait, not from the + // module it's defined in. Both of these are marked "stripped," and + // not included in the final docs, but since they still have an effect + // on the final doc, cannot be completely removed from the Clean IR. + match *i.kind { + clean::StructFieldItem(..) | clean::ModuleItem(..) | clean::VariantItem(..) => { + // We need to recurse into stripped modules to + // strip things like impl methods but when doing so + // we must not add any items to the `retained` set. + let old = mem::replace(&mut self.update_retained, false); + let ret = strip_item(self.set_is_in_hidden_item_and_fold(true, i)); + self.update_retained = old; + Some(ret) + } + _ => { + let ret = self.set_is_in_hidden_item_and_fold(true, i); + if has_doc_hidden { + // If the item itself has `#[doc(hidden)]`, then we simply remove it. + None + } else { + // However if it's a "descendant" of a `#[doc(hidden)]` item, then we strip it. + Some(strip_item(ret)) + } + } } - Some(self.fold_item_recur(i)) } } diff --git a/src/librustdoc/passes/stripper.rs b/src/librustdoc/passes/stripper.rs index 048ed2646..cba55e5fe 100644 --- a/src/librustdoc/passes/stripper.rs +++ b/src/librustdoc/passes/stripper.rs @@ -201,27 +201,25 @@ impl<'a> DocFolder for ImplStripper<'a, '_> { // Because we don't inline in `maybe_inline_local` if the output format is JSON, // we need to make a special check for JSON output: we want to keep it unless it has // a `#[doc(hidden)]` attribute if the `for_` type is exported. - if let Some(did) = imp.for_.def_id(self.cache) { - if !imp.for_.is_assoc_ty() && !self.should_keep_impl(&i, did) { - debug!("ImplStripper: impl item for stripped type; removing"); - return None; - } + if let Some(did) = imp.for_.def_id(self.cache) && + !imp.for_.is_assoc_ty() && !self.should_keep_impl(&i, did) + { + debug!("ImplStripper: impl item for stripped type; removing"); + return None; } - if let Some(did) = imp.trait_.as_ref().map(|t| t.def_id()) { - if !self.should_keep_impl(&i, did) { - debug!("ImplStripper: impl item for stripped trait; removing"); - return None; - } + if let Some(did) = imp.trait_.as_ref().map(|t| t.def_id()) && + !self.should_keep_impl(&i, did) { + debug!("ImplStripper: impl item for stripped trait; removing"); + return None; } if let Some(generics) = imp.trait_.as_ref().and_then(|t| t.generics()) { for typaram in generics { - if let Some(did) = typaram.def_id(self.cache) { - if !self.should_keep_impl(&i, did) { - debug!( - "ImplStripper: stripped item in trait's generics; removing impl" - ); - return None; - } + if let Some(did) = typaram.def_id(self.cache) && !self.should_keep_impl(&i, did) + { + debug!( + "ImplStripper: stripped item in trait's generics; removing impl" + ); + return None; } } } |