diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-30 03:59:35 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-30 03:59:35 +0000 |
commit | d1b2d29528b7794b41e66fc2136e395a02f8529b (patch) | |
tree | a4a17504b260206dec3cf55b2dca82929a348ac2 /src/librustdoc/passes | |
parent | Releasing progress-linux version 1.72.1+dfsg1-1~progress7.99u1. (diff) | |
download | rustc-d1b2d29528b7794b41e66fc2136e395a02f8529b.tar.xz rustc-d1b2d29528b7794b41e66fc2136e395a02f8529b.zip |
Merging upstream version 1.73.0+dfsg1.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
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 | 11 | ||||
-rw-r--r-- | src/librustdoc/passes/collect_intra_doc_links.rs | 222 | ||||
-rw-r--r-- | src/librustdoc/passes/collect_trait_impls.rs | 4 | ||||
-rw-r--r-- | src/librustdoc/passes/lint.rs | 2 | ||||
-rw-r--r-- | src/librustdoc/passes/lint/bare_urls.rs | 13 | ||||
-rw-r--r-- | src/librustdoc/passes/lint/check_code_block_syntax.rs | 19 | ||||
-rw-r--r-- | src/librustdoc/passes/lint/html_tags.rs | 14 | ||||
-rw-r--r-- | src/librustdoc/passes/lint/redundant_explicit_links.rs | 347 | ||||
-rw-r--r-- | src/librustdoc/passes/propagate_doc_cfg.rs | 5 | ||||
-rw-r--r-- | src/librustdoc/passes/strip_hidden.rs | 3 | ||||
-rw-r--r-- | src/librustdoc/passes/strip_priv_imports.rs | 7 | ||||
-rw-r--r-- | src/librustdoc/passes/strip_private.rs | 9 | ||||
-rw-r--r-- | src/librustdoc/passes/stripper.rs | 19 |
14 files changed, 569 insertions, 114 deletions
diff --git a/src/librustdoc/passes/calculate_doc_coverage.rs b/src/librustdoc/passes/calculate_doc_coverage.rs index 6ead0cd96..592dd0a14 100644 --- a/src/librustdoc/passes/calculate_doc_coverage.rs +++ b/src/librustdoc/passes/calculate_doc_coverage.rs @@ -146,8 +146,10 @@ impl<'a, 'b> CoverageCalculator<'a, 'b> { examples_percentage: f64, ) { println!( - "| {:<35} | {:>10} | {:>9.1}% | {:>10} | {:>9.1}% |", - name, count.with_docs, percentage, count.with_examples, examples_percentage, + "| {name:<35} | {with_docs:>10} | {percentage:>9.1}% | {with_examples:>10} | \ + {examples_percentage:>9.1}% |", + with_docs = count.with_docs, + with_examples = count.with_examples, ); } @@ -249,7 +251,7 @@ impl<'a, 'b> DocVisitor for CoverageCalculator<'a, 'b> { if let Some(span) = i.span(self.ctx.tcx) { let filename = span.filename(self.ctx.sess()); - debug!("counting {:?} {:?} in {:?}", i.type_(), i.name, filename); + debug!("counting {:?} {:?} in {filename:?}", i.type_(), i.name); self.items.entry(filename).or_default().count_item( has_docs, has_doc_example, diff --git a/src/librustdoc/passes/check_doc_test_visibility.rs b/src/librustdoc/passes/check_doc_test_visibility.rs index b6cd897d3..e732a4057 100644 --- a/src/librustdoc/passes/check_doc_test_visibility.rs +++ b/src/librustdoc/passes/check_doc_test_visibility.rs @@ -7,11 +7,11 @@ use super::Pass; use crate::clean; +use crate::clean::utils::inherits_doc_hidden; use crate::clean::*; use crate::core::DocContext; use crate::html::markdown::{find_testable_code, ErrorCodes, Ignore, LangString}; use crate::visit::DocVisitor; -use crate::visit_ast::inherits_doc_hidden; use rustc_hir as hir; use rustc_middle::lint::LintLevelSource; use rustc_session::lint; @@ -92,8 +92,8 @@ pub(crate) fn should_have_doc_example(cx: &DocContext<'_>, item: &clean::Item) - return false; } - if cx.tcx.is_doc_hidden(def_id.to_def_id()) - || inherits_doc_hidden(cx.tcx, def_id, None) + if (!cx.render_options.document_hidden + && (cx.tcx.is_doc_hidden(def_id.to_def_id()) || inherits_doc_hidden(cx.tcx, def_id, None))) || cx.tcx.def_span(def_id.to_def_id()).in_derive_expansion() { return false; @@ -106,8 +106,7 @@ pub(crate) fn should_have_doc_example(cx: &DocContext<'_>, item: &clean::Item) - } pub(crate) fn look_for_tests<'tcx>(cx: &DocContext<'tcx>, dox: &str, item: &Item) { - let Some(hir_id) = DocContext::as_local_hir_id(cx.tcx, item.item_id) - else { + let Some(hir_id) = DocContext::as_local_hir_id(cx.tcx, item.item_id) else { // If non-local, no need to check anything. return; }; @@ -118,7 +117,7 @@ pub(crate) fn look_for_tests<'tcx>(cx: &DocContext<'tcx>, dox: &str, item: &Item if tests.found_tests == 0 && cx.tcx.features().rustdoc_missing_doc_code_examples { if should_have_doc_example(cx, item) { - debug!("reporting error for {:?} (hir_id={:?})", item, hir_id); + debug!("reporting error for {item:?} (hir_id={hir_id:?})"); let sp = item.attr_span(cx.tcx); cx.tcx.struct_span_lint_hir( crate::lint::MISSING_DOC_CODE_EXAMPLES, diff --git a/src/librustdoc/passes/collect_intra_doc_links.rs b/src/librustdoc/passes/collect_intra_doc_links.rs index 0dd9e590b..7b0a7a90d 100644 --- a/src/librustdoc/passes/collect_intra_doc_links.rs +++ b/src/librustdoc/passes/collect_intra_doc_links.rs @@ -14,7 +14,7 @@ use rustc_hir::def::{DefKind, Namespace, PerNS}; use rustc_hir::def_id::{DefId, CRATE_DEF_ID}; use rustc_hir::Mutability; use rustc_middle::ty::{Ty, TyCtxt}; -use rustc_middle::{bug, ty}; +use rustc_middle::{bug, span_bug, ty}; use rustc_resolve::rustdoc::{has_primitive_or_keyword_docs, prepare_to_doc_link_resolution}; use rustc_resolve::rustdoc::{strip_generics_from_path, MalformedGenerics}; use rustc_session::lint::Lint; @@ -150,7 +150,7 @@ impl TryFrom<ResolveRes> for Res { PrimTy(prim) => Ok(Res::Primitive(PrimitiveType::from_hir(prim))), // e.g. `#[derive]` ToolMod | NonMacroAttr(..) | Err => Result::Err(()), - other => bug!("unrecognized res {:?}", other), + other => bug!("unrecognized res {other:?}"), } } } @@ -224,7 +224,7 @@ impl UrlFragment { "structfield." } } - kind => bug!("unexpected associated item kind: {:?}", kind), + kind => bug!("unexpected associated item kind: {kind:?}"), }; s.push_str(kind); s.push_str(tcx.item_name(def_id).as_str()); @@ -279,7 +279,7 @@ impl<'a, 'tcx> LinkCollector<'a, 'tcx> { unresolved: path_str.into(), }; - debug!("looking for enum variant {}", path_str); + debug!("looking for enum variant {path_str}"); let mut split = path_str.rsplitn(3, "::"); let variant_field_name = split .next() @@ -298,7 +298,7 @@ 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).subst_identity().kind() { + Res::Def(DefKind::Enum, did) => match tcx.type_of(did).instantiate_identity().kind() { ty::Adt(def, _) if def.is_enum() => { 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) { @@ -402,10 +402,15 @@ impl<'a, 'tcx> LinkCollector<'a, 'tcx> { // `doc_link_resolutions` is missing a `path_str`, that means that there are valid links // that are being missed. To fix the ICE, change // `rustc_resolve::rustdoc::attrs_to_preprocessed_links` to cache the link. - .unwrap_or_else(|| panic!("no resolution for {:?} {:?} {:?}", path_str, ns, module_id)) + .unwrap_or_else(|| { + span_bug!( + self.cx.tcx.def_span(item_id), + "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); + debug!("{path_str} resolved to {result:?} in namespace {ns:?}"); result } @@ -448,7 +453,7 @@ impl<'a, 'tcx> LinkCollector<'a, 'tcx> { // 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 `::`, assuming {} was correctly not in scope", item_name); + debug!("found no `::`, assuming {item_name} was correctly not in scope"); UnresolvedPath { item_id, module_id, @@ -493,7 +498,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).subst_identity().kind() { + Some(match *self.cx.tcx.type_of(ty_id).instantiate_identity().kind() { ty::Bool => Res::Primitive(Bool), ty::Char => Res::Primitive(Char), ty::Int(ity) => Res::Primitive(ity.into()), @@ -587,7 +592,7 @@ impl<'a, 'tcx> LinkCollector<'a, 'tcx> { .unwrap_or(Vec::new()) } } - Res::Def(DefKind::TyAlias, did) => { + Res::Def(DefKind::TyAlias { .. }, did) => { // Resolve the link on the type the alias points to. // FIXME: if the associated item is defined directly on the type alias, // it will show up on its documentation page, we should link there instead. @@ -598,10 +603,10 @@ impl<'a, 'tcx> LinkCollector<'a, 'tcx> { def_kind @ (DefKind::Struct | DefKind::Union | DefKind::Enum | DefKind::ForeignTy), did, ) => { - debug!("looking for associated item named {} for item {:?}", item_name, did); + debug!("looking for associated item named {item_name} for item {did:?}"); // Checks if item_name is a variant of the `SomeItem` enum if ns == TypeNS && def_kind == DefKind::Enum { - match tcx.type_of(did).subst_identity().kind() { + match tcx.type_of(did).instantiate_identity().kind() { ty::Adt(adt_def, _) => { for variant in adt_def.variants() { if variant.name == item_name { @@ -635,7 +640,7 @@ impl<'a, 'tcx> LinkCollector<'a, 'tcx> { // To handle that properly resolve() would have to support // something like [`ambi_fn`](<SomeStruct as SomeTrait>::ambi_fn) assoc_items = resolve_associated_trait_item( - tcx.type_of(did).subst_identity(), + tcx.type_of(did).instantiate_identity(), module_id, item_name, ns, @@ -646,7 +651,7 @@ impl<'a, 'tcx> LinkCollector<'a, 'tcx> { .collect::<Vec<_>>(); } - debug!("got associated item {:?}", assoc_items); + debug!("got associated item {assoc_items:?}"); if !assoc_items.is_empty() { return assoc_items; @@ -655,7 +660,7 @@ impl<'a, 'tcx> LinkCollector<'a, 'tcx> { if ns != Namespace::ValueNS { return Vec::new(); } - debug!("looking for fields named {} for {:?}", item_name, did); + debug!("looking for fields named {item_name} for {did:?}"); // FIXME: this doesn't really belong in `associated_item` (maybe `variant_field` is better?) // NOTE: it's different from variant_field because it only resolves struct fields, // not variant fields (2 path segments, not 3). @@ -671,7 +676,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).subst_identity().kind() { + let def = match tcx.type_of(did).instantiate_identity().kind() { ty::Adt(def, _) if !def.is_enum() => def, _ => return Vec::new(), }; @@ -722,7 +727,7 @@ fn resolve_associated_trait_item<'a>( // Give precedence to inherent impls. let traits = trait_impls_for(cx, ty, module); let tcx = cx.tcx; - debug!("considering traits {:?}", traits); + debug!("considering traits {traits:?}"); let candidates = traits .iter() .flat_map(|&(impl_, trait_)| { @@ -739,7 +744,7 @@ fn resolve_associated_trait_item<'a>( }) .collect::<Vec<_>>(); // FIXME(#74563): warn about ambiguity - debug!("the candidates were {:?}", candidates); + debug!("the candidates were {candidates:?}"); candidates } @@ -785,10 +790,8 @@ fn trait_impls_for<'a>( // Check if these are the same type. let impl_type = trait_ref.skip_binder().self_ty(); trace!( - "comparing type {} with kind {:?} against type {:?}", - impl_type, - impl_type.kind(), - ty + "comparing type {impl_type} with kind {kind:?} against type {ty:?}", + kind = impl_type.kind(), ); // Fast path: if this is a primitive simple `==` will work // NOTE: the `match` is necessary; see #92662. @@ -935,7 +938,7 @@ fn preprocess_link( let path_str = match strip_generics_from_path(path_str) { Ok(path) => path, Err(err) => { - debug!("link has malformed generics: {}", path_str); + debug!("link has malformed generics: {path_str}"); return Some(Err(PreprocessingError::MalformedGenerics(err, path_str.to_owned()))); } }; @@ -963,6 +966,7 @@ fn preprocessed_markdown_links(s: &str) -> Vec<PreprocessedMarkdownLink> { } impl LinkCollector<'_, '_> { + #[instrument(level = "debug", skip_all)] fn resolve_links(&mut self, item: &Item) { if !self.cx.render_options.document_private && let Some(def_id) = item.item_id.as_def_id() @@ -981,7 +985,7 @@ impl LinkCollector<'_, '_> { if !may_have_doc_links(&doc) { continue; } - debug!("combined_docs={}", doc); + debug!("combined_docs={doc}"); // 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 item_id = item_id.unwrap_or_else(|| item.item_id.expect_def_id()); @@ -990,7 +994,7 @@ impl LinkCollector<'_, '_> { _ => find_nearest_parent_module(self.cx.tcx, item_id).unwrap(), }; for md_link in preprocessed_markdown_links(&doc) { - let link = self.resolve_link(item, item_id, module_id, &doc, &md_link); + let link = self.resolve_link(&doc, item, item_id, module_id, &md_link); if let Some(link) = link { self.cx.cache.intra_doc_links.entry(item.item_id).or_default().insert(link); } @@ -1003,13 +1007,12 @@ impl LinkCollector<'_, '_> { /// FIXME(jynelson): this is way too many arguments fn resolve_link( &mut self, + dox: &String, item: &Item, item_id: DefId, module_id: DefId, - dox: &str, - link: &PreprocessedMarkdownLink, + PreprocessedMarkdownLink(pp_link, ori_link): &PreprocessedMarkdownLink, ) -> Option<ItemLink> { - let PreprocessedMarkdownLink(pp_link, ori_link) = link; trace!("considering link '{}'", ori_link.link); let diag_info = DiagnosticInfo { @@ -1018,7 +1021,6 @@ impl LinkCollector<'_, '_> { ori_link: &ori_link.link, link_range: ori_link.range.clone(), }; - let PreprocessingInfo { path_str, disambiguator, extra_fragment, link_text } = pp_link.as_ref().map_err(|err| err.report(self.cx, diag_info.clone())).ok()?; let disambiguator = *disambiguator; @@ -1036,8 +1038,24 @@ impl LinkCollector<'_, '_> { // resolutions are cached, for other links we want to report an error every // time so they are not cached. matches!(ori_link.kind, LinkType::Reference | LinkType::Shortcut), + false, )?; + if ori_link.display_text.is_some() { + self.resolve_display_text( + path_str, + ResolutionInfo { + item_id, + module_id, + dis: disambiguator, + path_str: ori_link.display_text.clone()?.into_boxed_str(), + extra_fragment: extra_fragment.clone(), + }, + &ori_link, + &diag_info, + ); + } + // 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? @@ -1124,10 +1142,10 @@ impl LinkCollector<'_, '_> { item: &Item, diag_info: &DiagnosticInfo<'_>, ) -> Option<()> { - debug!("intra-doc link to {} resolved to {:?}", path_str, (kind, id)); + debug!("intra-doc link to {path_str} resolved to {:?}", (kind, id)); // Disallow e.g. linking to enums with `struct@` - debug!("saw kind {:?} with disambiguator {:?}", kind, disambiguator); + debug!("saw kind {kind:?} with disambiguator {disambiguator:?}"); match (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 @@ -1168,7 +1186,7 @@ impl LinkCollector<'_, '_> { diag_info: &DiagnosticInfo<'_>, ) { // 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 msg = format!("incompatible link kind for `{path_str}`"); let callback = |diag: &mut Diagnostic, sp: Option<rustc_span::Span>, link_range| { let note = format!( "this link resolved to {} {}, which is not {} {}", @@ -1217,6 +1235,9 @@ impl LinkCollector<'_, '_> { // If errors are cached then they are only reported on first occurrence // which we want in some cases but not in others. cache_errors: bool, + // If this call is intended to be recoverable, then pass true to silence. + // This is only recoverable when path is failed to resolved. + recoverable: bool, ) -> Option<(Res, Option<UrlFragment>)> { if let Some(res) = self.visited_links.get(&key) { if res.is_some() || cache_errors { @@ -1224,7 +1245,7 @@ impl LinkCollector<'_, '_> { } } - let mut candidates = self.resolve_with_disambiguator(&key, diag.clone()); + let mut candidates = self.resolve_with_disambiguator(&key, diag.clone(), recoverable); // FIXME: it would be nice to check that the feature gate was enabled in the original crate, not just ignore it altogether. // However I'm not sure how to check that across crates. @@ -1275,6 +1296,9 @@ impl LinkCollector<'_, '_> { &mut self, key: &ResolutionInfo, diag: DiagnosticInfo<'_>, + // If this call is intended to be recoverable, then pass true to silence. + // This is only recoverable when path is failed to resolved. + recoverable: bool, ) -> Vec<(Res, Option<DefId>)> { let disambiguator = key.dis; let path_str = &key.path_str; @@ -1304,7 +1328,9 @@ impl LinkCollector<'_, '_> { } } } - resolution_failure(self, diag, path_str, disambiguator, smallvec![err]); + if !recoverable { + resolution_failure(self, diag, path_str, disambiguator, smallvec![err]); + } return vec![]; } } @@ -1341,13 +1367,15 @@ impl LinkCollector<'_, '_> { .fold(0, |acc, res| if let Ok(res) = res { acc + res.len() } else { acc }); if len == 0 { - resolution_failure( - self, - diag, - path_str, - disambiguator, - candidates.into_iter().filter_map(|res| res.err()).collect(), - ); + if !recoverable { + resolution_failure( + self, + diag, + path_str, + disambiguator, + candidates.into_iter().filter_map(|res| res.err()).collect(), + ); + } return vec![]; } else if len == 1 { candidates.into_iter().filter_map(|res| res.ok()).flatten().collect::<Vec<_>>() @@ -1368,6 +1396,58 @@ impl LinkCollector<'_, '_> { } } } + + /// Resolve display text if the provided link has separated parts of links. + /// + /// For example: + /// Inline link `[display_text](dest_link)` and reference link `[display_text][reference_link]` has + /// separated parts of links. + fn resolve_display_text( + &mut self, + explicit_link: &Box<str>, + display_res_info: ResolutionInfo, + ori_link: &MarkdownLink, + diag_info: &DiagnosticInfo<'_>, + ) { + // Check if explicit resolution's path is same as resolution of original link's display text path, see + // tests/rustdoc-ui/lint/redundant_explicit_links.rs for more cases. + // + // To avoid disambiguator from panicking, we check if display text path is possible to be disambiguated + // into explicit path. + if !matches!( + ori_link.kind, + LinkType::Inline | LinkType::Reference | LinkType::ReferenceUnknown + ) { + return; + } + + // Algorithm to check if display text could possibly be the explicit link: + // + // Consider 2 links which are display text and explicit link, pick the shorter + // one as symbol and longer one as full qualified path, and tries to match symbol + // to the full qualified path's last symbol. + // + // Otherwise, check if 2 links are same, if so, skip the resolve process. + // + // Notice that this algorithm is passive, might possibly miss actual redudant cases. + let explicit_link = explicit_link.to_string(); + let display_text = ori_link.display_text.as_ref().unwrap(); + + if display_text.len() == explicit_link.len() { + // Whether they are same or not, skip the resolve process. + return; + } + + if explicit_link.ends_with(&display_text[..]) || display_text.ends_with(&explicit_link[..]) + { + self.resolve_with_disambiguator_cached( + display_res_info, + diag_info.clone(), // this struct should really be Copy, but Range is not :( + false, + true, + ); + } + } } /// Get the section of a link between the backticks, @@ -1453,7 +1533,7 @@ impl Disambiguator { "value" => NS(Namespace::ValueNS), "macro" => NS(Namespace::MacroNS), "prim" | "primitive" => Primitive, - _ => return Err((format!("unknown disambiguator `{}`", prefix), 0..idx)), + _ => return Err((format!("unknown disambiguator `{prefix}`"), 0..idx)), }; Ok(Some((d, &rest[1..], &rest[1..]))) } else { @@ -1521,7 +1601,7 @@ enum Suggestion { impl Suggestion { fn descr(&self) -> Cow<'static, str> { match self { - Self::Prefix(x) => format!("prefix with `{}@`", x).into(), + Self::Prefix(x) => format!("prefix with `{x}@`").into(), Self::Function => "add parentheses".into(), Self::Macro => "add an exclamation mark".into(), Self::RemoveDisambiguator => "remove the disambiguator".into(), @@ -1531,9 +1611,9 @@ impl Suggestion { 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), + Self::Prefix(prefix) => format!("{prefix}@{path_str}"), + Self::Function => format!("{path_str}()"), + Self::Macro => format!("{path_str}!"), Self::RemoveDisambiguator => path_str.into(), } } @@ -1568,7 +1648,7 @@ impl Suggestion { match self { Self::Prefix(prefix) => { // FIXME: if this is an implied shortcut link, it's bad style to suggest `@` - let mut sugg = vec![(sp.with_hi(inner_sp.lo()), format!("{}@", prefix))]; + let mut sugg = vec![(sp.with_hi(inner_sp.lo()), format!("{prefix}@"))]; if sp.hi() != inner_sp.hi() { sugg.push((inner_sp.shrink_to_hi().with_hi(sp.hi()), String::new())); } @@ -1610,10 +1690,9 @@ fn report_diagnostic( DiagnosticInfo { item, ori_link: _, dox, link_range }: &DiagnosticInfo<'_>, decorate: impl FnOnce(&mut Diagnostic, Option<rustc_span::Span>, MarkdownLinkRange), ) { - let Some(hir_id) = DocContext::as_local_hir_id(tcx, item.item_id) - else { + let Some(hir_id) = DocContext::as_local_hir_id(tcx, item.item_id) else { // If non-local, no need to check anything. - info!("ignoring warning from parent crate: {}", msg); + info!("ignoring warning from parent crate: {msg}"); return; }; @@ -1691,15 +1770,14 @@ fn resolution_failure( report_diagnostic( tcx, BROKEN_INTRA_DOC_LINKS, - format!("unresolved link to `{}`", path_str), + format!("unresolved link to `{path_str}`"), &diag_info, |diag, sp, link_range| { - let item = |res: Res| format!("the {} `{}`", res.descr(), res.name(tcx),); + let item = |res: Res| format!("the {} `{}`", res.descr(), res.name(tcx)); let assoc_item_not_allowed = |res: Res| { let name = res.name(tcx); format!( - "`{}` is {} {}, not a module or type, and cannot have associated items", - name, + "`{name}` is {} {}, not a module or type, and cannot have associated items", res.article(), res.descr() ) @@ -1745,7 +1823,7 @@ fn resolution_failure( name = start; for ns in [TypeNS, ValueNS, MacroNS] { if let Ok(v_res) = collector.resolve(start, ns, item_id, module_id) { - debug!("found partial_res={:?}", v_res); + debug!("found partial_res={v_res:?}"); if !v_res.is_empty() { *partial_res = Some(full_res(tcx, v_res[0])); *unresolved = end.into(); @@ -1766,10 +1844,10 @@ fn resolution_failure( let note = if partial_res.is_some() { // Part of the link resolved; e.g. `std::io::nonexistent` let module_name = tcx.item_name(module); - format!("no item named `{}` in module `{}`", unresolved, module_name) + format!("no item named `{unresolved}` in module `{module_name}`") } else { // None of the link resolved; e.g. `Notimported` - format!("no item named `{}` in scope", unresolved) + format!("no item named `{unresolved}` in scope") }; if let Some(span) = sp { diag.span_label(span, note); @@ -1811,7 +1889,7 @@ fn resolution_failure( Res::Primitive(_) => None, }; let is_struct_variant = |did| { - if let ty::Adt(def, _) = tcx.type_of(did).subst_identity().kind() + if let ty::Adt(def, _) = tcx.type_of(did).instantiate_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 @@ -1860,8 +1938,13 @@ fn resolution_failure( } return; } - Trait | TyAlias | ForeignTy | OpaqueTy | ImplTraitPlaceholder - | TraitAlias | TyParam | Static(_) => "associated item", + Trait + | TyAlias { .. } + | ForeignTy + | OpaqueTy + | TraitAlias + | TyParam + | Static(_) => "associated item", Impl { .. } | GlobalAsm => unreachable!("not a path"), } } else { @@ -1869,11 +1952,9 @@ fn resolution_failure( }; let name = res.name(tcx); let note = format!( - "the {} `{}` has no {} named `{}`", - res.descr(), - name, - disambiguator.map_or(path_description, |d| d.descr()), - unresolved, + "the {res} `{name}` has no {disamb_res} named `{unresolved}`", + res = res.descr(), + disamb_res = disambiguator.map_or(path_description, |d| d.descr()), ); if let Some(span) = sp { diag.span_label(span, note); @@ -1968,7 +2049,7 @@ fn report_malformed_generics( report_diagnostic( cx.tcx, BROKEN_INTRA_DOC_LINKS, - format!("unresolved link to `{}`", path_str), + format!("unresolved link to `{path_str}`"), &diag_info, |diag, sp, _link_range| { let note = match err { @@ -2020,7 +2101,7 @@ fn ambiguity_error( return false; } - let mut msg = format!("`{}` is ", path_str); + let mut msg = format!("`{path_str}` is "); match kinds.as_slice() { [res1, res2] => { msg += &format!( @@ -2084,7 +2165,7 @@ fn suggest_disambiguator( diag.span_suggestion_verbose(sp, help, suggestion_text, Applicability::MaybeIncorrect); } } else { - diag.help(format!("{}: {}", help, suggestion.as_help(path_str))); + diag.help(format!("{help}: {}", suggestion.as_help(path_str))); } } @@ -2098,8 +2179,7 @@ fn privacy_error(cx: &DocContext<'_>, diag_info: &DiagnosticInfo<'_>, path_str: } None => "<unknown>", }; - let msg = - format!("public documentation for `{}` links to private item `{}`", item_name, path_str); + let msg = format!("public documentation for `{item_name}` links to private item `{path_str}`"); report_diagnostic(cx.tcx, PRIVATE_INTRA_DOC_LINKS, msg, diag_info, |diag, sp, _link_range| { if let Some(sp) = sp { @@ -2150,6 +2230,6 @@ fn resolve_primitive(path_str: &str, ns: Namespace) -> Option<Res> { "never" | "!" => Never, _ => return None, }; - debug!("resolved primitives {:?}", prim); + debug!("resolved primitives {prim:?}"); Some(Res::Primitive(prim)) } diff --git a/src/librustdoc/passes/collect_trait_impls.rs b/src/librustdoc/passes/collect_trait_impls.rs index fbf827cce..ff89d4e08 100644 --- a/src/librustdoc/passes/collect_trait_impls.rs +++ b/src/librustdoc/passes/collect_trait_impls.rs @@ -103,11 +103,11 @@ pub(crate) fn collect_trait_impls(mut krate: Crate, cx: &mut DocContext<'_>) -> // nothing to do with the inherent impl. // // Rustdoc currently uses these `impl` block as a source of - // the `Ty`, as well as the `ParamEnv`, `SubstsRef`, and + // the `Ty`, as well as the `ParamEnv`, `GenericArgsRef`, and // `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).subst_identity(); + let ty = tcx.type_of(def_id).instantiate_identity(); match ty.kind() { ty::Slice(ty) | ty::Ref(_, ty, _) diff --git a/src/librustdoc/passes/lint.rs b/src/librustdoc/passes/lint.rs index e653207b9..c6d5b7bd3 100644 --- a/src/librustdoc/passes/lint.rs +++ b/src/librustdoc/passes/lint.rs @@ -4,6 +4,7 @@ mod bare_urls; mod check_code_block_syntax; mod html_tags; +mod redundant_explicit_links; mod unescaped_backticks; use super::Pass; @@ -29,6 +30,7 @@ impl<'a, 'tcx> DocVisitor for Linter<'a, 'tcx> { check_code_block_syntax::visit_item(self.cx, item); html_tags::visit_item(self.cx, item); unescaped_backticks::visit_item(self.cx, item); + redundant_explicit_links::visit_item(self.cx, item); self.visit_item_recur(item) } diff --git a/src/librustdoc/passes/lint/bare_urls.rs b/src/librustdoc/passes/lint/bare_urls.rs index e9cee92d2..97078a5cb 100644 --- a/src/librustdoc/passes/lint/bare_urls.rs +++ b/src/librustdoc/passes/lint/bare_urls.rs @@ -13,11 +13,10 @@ use std::mem; use std::sync::LazyLock; pub(super) fn visit_item(cx: &DocContext<'_>, item: &Item) { - let Some(hir_id) = DocContext::as_local_hir_id(cx.tcx, item.item_id) - else { - // If non-local, no need to check anything. - return; - }; + let Some(hir_id) = DocContext::as_local_hir_id(cx.tcx, item.item_id) else { + // If non-local, no need to check anything. + return; + }; let dox = item.doc_value(); if !dox.is_empty() { let report_diag = @@ -29,7 +28,7 @@ pub(super) fn visit_item(cx: &DocContext<'_>, item: &Item) { .span_suggestion( sp, "use an automatic link instead", - format!("<{}>", url), + format!("<{url}>"), Applicability::MachineApplicable, ) }); @@ -75,7 +74,7 @@ fn find_raw_urls( range: Range<usize>, f: &impl Fn(&DocContext<'_>, &'static str, &str, Range<usize>), ) { - trace!("looking for raw urls in {}", text); + trace!("looking for raw urls in {text}"); // For now, we only check "full" URLs (meaning, starting with "http://" or "https://"). for match_ in URL_REGEX.find_iter(text) { let url = match_.as_str(); diff --git a/src/librustdoc/passes/lint/check_code_block_syntax.rs b/src/librustdoc/passes/lint/check_code_block_syntax.rs index f489f5081..37e28e1fb 100644 --- a/src/librustdoc/passes/lint/check_code_block_syntax.rs +++ b/src/librustdoc/passes/lint/check_code_block_syntax.rs @@ -40,7 +40,7 @@ fn check_rust_syntax( let emitter = BufferEmitter { buffer: Lrc::clone(&buffer), fallback_bundle }; let sm = Lrc::new(SourceMap::new(FilePathMapping::empty())); - let handler = Handler::with_emitter(false, None, Box::new(emitter)); + let handler = Handler::with_emitter(Box::new(emitter)).disable_warnings(); let source = dox[code_block.code].to_owned(); let sess = ParseSess::with_span_handler(handler, sm); @@ -67,12 +67,11 @@ fn check_rust_syntax( return; } - let Some(local_id) = item.item_id.as_def_id().and_then(|x| x.as_local()) - else { - // We don't need to check the syntax for other crates so returning - // without doing anything should not be a problem. - return; - }; + let Some(local_id) = item.item_id.as_def_id().and_then(|x| x.as_local()) else { + // We don't need to check the syntax for other crates so returning + // without doing anything should not be a problem. + return; + }; let empty_block = code_block.lang_string == Default::default() && code_block.is_fenced; let is_ignore = code_block.lang_string.ignore != markdown::Ignore::None; @@ -108,7 +107,7 @@ fn check_rust_syntax( // just give a `help` instead. lint.span_help( sp.from_inner(InnerSpan::new(0, 3)), - format!("{}: ```text", explanation), + format!("{explanation}: ```text"), ); } else if empty_block { lint.span_suggestion( @@ -119,7 +118,7 @@ fn check_rust_syntax( ); } } else if empty_block || is_ignore { - lint.help(format!("{}: ```text", explanation)); + lint.help(format!("{explanation}: ```text")); } // FIXME(#67563): Provide more context for these errors by displaying the spans inline. @@ -161,7 +160,7 @@ impl Emitter for BufferEmitter { .translate_message(&diag.message[0].0, &fluent_args) .unwrap_or_else(|e| panic!("{e}")); - buffer.messages.push(format!("error from rustc: {}", translated_main_message)); + buffer.messages.push(format!("error from rustc: {translated_main_message}")); if diag.is_error() { buffer.has_errors = true; } diff --git a/src/librustdoc/passes/lint/html_tags.rs b/src/librustdoc/passes/lint/html_tags.rs index 5273f52bc..c135c584c 100644 --- a/src/librustdoc/passes/lint/html_tags.rs +++ b/src/librustdoc/passes/lint/html_tags.rs @@ -14,7 +14,9 @@ pub(crate) fn visit_item(cx: &DocContext<'_>, item: &Item) { let tcx = cx.tcx; let Some(hir_id) = DocContext::as_local_hir_id(tcx, item.item_id) // If non-local, no need to check anything. - else { return }; + else { + return; + }; let dox = item.doc_value(); if !dox.is_empty() { let report_diag = |msg: String, range: &Range<usize>, is_open_tag: bool| { @@ -153,7 +155,7 @@ pub(crate) fn visit_item(cx: &DocContext<'_>, item: &Item) { let t = t.to_lowercase(); !ALLOWED_UNCLOSED.contains(&t.as_str()) }) { - report_diag(format!("unclosed HTML tag `{}`", tag), range, true); + report_diag(format!("unclosed HTML tag `{tag}`"), range, true); } if let Some(range) = is_in_comment { @@ -192,14 +194,14 @@ fn drop_tag( // `tags` is used as a queue, meaning that everything after `pos` is included inside it. // So `<h2><h3></h2>` will look like `["h2", "h3"]`. So when closing `h2`, we will still // have `h3`, meaning the tag wasn't closed as it should have. - f(format!("unclosed HTML tag `{}`", last_tag_name), &last_tag_span, true); + f(format!("unclosed HTML tag `{last_tag_name}`"), &last_tag_span, true); } // Remove the `tag_name` that was originally closed tags.pop(); } else { // It can happen for example in this case: `<h2></script></h2>` (the `h2` tag isn't required // but it helps for the visualization). - f(format!("unopened HTML tag `{}`", tag_name), &range, false); + f(format!("unopened HTML tag `{tag_name}`"), &range, false); } } @@ -353,7 +355,7 @@ fn extract_html_tag( if let Some(quote_pos) = quote_pos { let qr = Range { start: quote_pos, end: quote_pos }; f( - format!("unclosed quoted HTML attribute on tag `{}`", tag_name), + format!("unclosed quoted HTML attribute on tag `{tag_name}`"), &qr, false, ); @@ -366,7 +368,7 @@ fn extract_html_tag( at == "svg" || at == "math" }); if !valid { - f(format!("invalid self-closing HTML tag `{}`", tag_name), &r, false); + f(format!("invalid self-closing HTML tag `{tag_name}`"), &r, false); } } else { tags.push((tag_name, r)); diff --git a/src/librustdoc/passes/lint/redundant_explicit_links.rs b/src/librustdoc/passes/lint/redundant_explicit_links.rs new file mode 100644 index 000000000..67cd2cc97 --- /dev/null +++ b/src/librustdoc/passes/lint/redundant_explicit_links.rs @@ -0,0 +1,347 @@ +use std::ops::Range; + +use pulldown_cmark::{BrokenLink, CowStr, Event, LinkType, OffsetIter, Parser, Tag}; +use rustc_ast::NodeId; +use rustc_errors::SuggestionStyle; +use rustc_hir::def::{DefKind, DocLinkResMap, Namespace, Res}; +use rustc_hir::HirId; +use rustc_lint_defs::Applicability; +use rustc_span::Symbol; + +use crate::clean::utils::find_nearest_parent_module; +use crate::clean::utils::inherits_doc_hidden; +use crate::clean::Item; +use crate::core::DocContext; +use crate::html::markdown::main_body_opts; +use crate::passes::source_span_for_markdown_range; + +#[derive(Debug)] +struct LinkData { + resolvable_link: Option<String>, + resolvable_link_range: Option<Range<usize>>, + display_link: String, +} + +pub(crate) fn visit_item(cx: &DocContext<'_>, item: &Item) { + let Some(hir_id) = DocContext::as_local_hir_id(cx.tcx, item.item_id) else { + // If non-local, no need to check anything. + return; + }; + + let doc = item.doc_value(); + if doc.is_empty() { + return; + } + + if item.link_names(&cx.cache).is_empty() { + // If there's no link names in this item, + // then we skip resolution querying to + // avoid from panicking. + return; + } + + let Some(item_id) = item.def_id() else { + return; + }; + let Some(local_item_id) = item_id.as_local() else { + return; + }; + + let is_hidden = !cx.render_options.document_hidden + && (item.is_doc_hidden() || inherits_doc_hidden(cx.tcx, local_item_id, None)); + if is_hidden { + return; + } + let is_private = !cx.render_options.document_private + && !cx.cache.effective_visibilities.is_directly_public(cx.tcx, item_id); + if is_private { + return; + } + + check_redundant_explicit_link(cx, item, hir_id, &doc); +} + +fn check_redundant_explicit_link<'md>( + cx: &DocContext<'_>, + item: &Item, + hir_id: HirId, + doc: &'md str, +) -> Option<()> { + let mut broken_line_callback = |link: BrokenLink<'md>| Some((link.reference, "".into())); + let mut offset_iter = Parser::new_with_broken_link_callback( + &doc, + main_body_opts(), + Some(&mut broken_line_callback), + ) + .into_offset_iter(); + let item_id = item.def_id()?; + let module_id = match cx.tcx.def_kind(item_id) { + DefKind::Mod if item.inner_docs(cx.tcx) => item_id, + _ => find_nearest_parent_module(cx.tcx, item_id).unwrap(), + }; + let resolutions = cx.tcx.doc_link_resolutions(module_id); + + while let Some((event, link_range)) = offset_iter.next() { + match event { + Event::Start(Tag::Link(link_type, dest, _)) => { + let link_data = collect_link_data(&mut offset_iter); + + if let Some(resolvable_link) = link_data.resolvable_link.as_ref() { + if &link_data.display_link.replace("`", "") != resolvable_link { + // Skips if display link does not match to actual + // resolvable link, usually happens if display link + // has several segments, e.g. + // [this is just an `Option`](Option) + continue; + } + } + + let explicit_link = dest.to_string(); + let display_link = link_data.resolvable_link.clone()?; + + if explicit_link.ends_with(&display_link) || display_link.ends_with(&explicit_link) + { + match link_type { + LinkType::Inline | LinkType::ReferenceUnknown => { + check_inline_or_reference_unknown_redundancy( + cx, + item, + hir_id, + doc, + resolutions, + link_range, + dest.to_string(), + link_data, + if link_type == LinkType::Inline { + (b'(', b')') + } else { + (b'[', b']') + }, + ); + } + LinkType::Reference => { + check_reference_redundancy( + cx, + item, + hir_id, + doc, + resolutions, + link_range, + &dest, + link_data, + ); + } + _ => {} + } + } + } + _ => {} + } + } + + None +} + +/// FIXME(ChAoSUnItY): Too many arguments. +fn check_inline_or_reference_unknown_redundancy( + cx: &DocContext<'_>, + item: &Item, + hir_id: HirId, + doc: &str, + resolutions: &DocLinkResMap, + link_range: Range<usize>, + dest: String, + link_data: LinkData, + (open, close): (u8, u8), +) -> Option<()> { + let (resolvable_link, resolvable_link_range) = + (&link_data.resolvable_link?, &link_data.resolvable_link_range?); + let (dest_res, display_res) = + (find_resolution(resolutions, &dest)?, find_resolution(resolutions, resolvable_link)?); + + if dest_res == display_res { + let link_span = source_span_for_markdown_range(cx.tcx, &doc, &link_range, &item.attrs) + .unwrap_or(item.attr_span(cx.tcx)); + let explicit_span = source_span_for_markdown_range( + cx.tcx, + &doc, + &offset_explicit_range(doc, link_range, open, close), + &item.attrs, + )?; + let display_span = + source_span_for_markdown_range(cx.tcx, &doc, &resolvable_link_range, &item.attrs)?; + + cx.tcx.struct_span_lint_hir(crate::lint::REDUNDANT_EXPLICIT_LINKS, hir_id, explicit_span, "redundant explicit link target", |lint| { + lint.span_label(explicit_span, "explicit target is redundant") + .span_label(display_span, "because label contains path that resolves to same destination") + .note("when a link's destination is not specified,\nthe label is used to resolve intra-doc links") + .span_suggestion_with_style(link_span, "remove explicit link target", format!("[{}]", link_data.display_link), Applicability::MaybeIncorrect, SuggestionStyle::ShowAlways); + + lint + }); + } + + None +} + +/// FIXME(ChAoSUnItY): Too many arguments. +fn check_reference_redundancy( + cx: &DocContext<'_>, + item: &Item, + hir_id: HirId, + doc: &str, + resolutions: &DocLinkResMap, + link_range: Range<usize>, + dest: &CowStr<'_>, + link_data: LinkData, +) -> Option<()> { + let (resolvable_link, resolvable_link_range) = + (&link_data.resolvable_link?, &link_data.resolvable_link_range?); + let (dest_res, display_res) = + (find_resolution(resolutions, &dest)?, find_resolution(resolutions, resolvable_link)?); + + if dest_res == display_res { + let link_span = source_span_for_markdown_range(cx.tcx, &doc, &link_range, &item.attrs) + .unwrap_or(item.attr_span(cx.tcx)); + let explicit_span = source_span_for_markdown_range( + cx.tcx, + &doc, + &offset_explicit_range(doc, link_range.clone(), b'[', b']'), + &item.attrs, + )?; + let display_span = + source_span_for_markdown_range(cx.tcx, &doc, &resolvable_link_range, &item.attrs)?; + let def_span = source_span_for_markdown_range( + cx.tcx, + &doc, + &offset_reference_def_range(doc, dest, link_range), + &item.attrs, + )?; + + cx.tcx.struct_span_lint_hir(crate::lint::REDUNDANT_EXPLICIT_LINKS, hir_id, explicit_span, "redundant explicit link target", |lint| { + lint.span_label(explicit_span, "explicit target is redundant") + .span_label(display_span, "because label contains path that resolves to same destination") + .span_note(def_span, "referenced explicit link target defined here") + .note("when a link's destination is not specified,\nthe label is used to resolve intra-doc links") + .span_suggestion_with_style(link_span, "remove explicit link target", format!("[{}]", link_data.display_link), Applicability::MaybeIncorrect, SuggestionStyle::ShowAlways); + + lint + }); + } + + None +} + +fn find_resolution(resolutions: &DocLinkResMap, path: &str) -> Option<Res<NodeId>> { + [Namespace::TypeNS, Namespace::ValueNS, Namespace::MacroNS] + .into_iter() + .find_map(|ns| resolutions.get(&(Symbol::intern(path), ns)).copied().flatten()) +} + +/// Collects all neccessary data of link. +fn collect_link_data(offset_iter: &mut OffsetIter<'_, '_>) -> LinkData { + let mut resolvable_link = None; + let mut resolvable_link_range = None; + let mut display_link = String::new(); + + while let Some((event, range)) = offset_iter.next() { + match event { + Event::Text(code) => { + let code = code.to_string(); + display_link.push_str(&code); + resolvable_link = Some(code); + resolvable_link_range = Some(range); + } + Event::Code(code) => { + let code = code.to_string(); + display_link.push('`'); + display_link.push_str(&code); + display_link.push('`'); + resolvable_link = Some(code); + resolvable_link_range = Some(range); + } + Event::End(_) => { + break; + } + _ => {} + } + } + + LinkData { resolvable_link, resolvable_link_range, display_link } +} + +fn offset_explicit_range(md: &str, link_range: Range<usize>, open: u8, close: u8) -> Range<usize> { + let mut open_brace = !0; + let mut close_brace = !0; + for (i, b) in md.as_bytes()[link_range.clone()].iter().copied().enumerate().rev() { + let i = i + link_range.start; + if b == close { + close_brace = i; + break; + } + } + + if close_brace < link_range.start || close_brace >= link_range.end { + return link_range; + } + + let mut nesting = 1; + + for (i, b) in md.as_bytes()[link_range.start..close_brace].iter().copied().enumerate().rev() { + let i = i + link_range.start; + if b == close { + nesting += 1; + } + if b == open { + nesting -= 1; + } + if nesting == 0 { + open_brace = i; + break; + } + } + + assert!(open_brace != close_brace); + + if open_brace < link_range.start || open_brace >= link_range.end { + return link_range; + } + // do not actually include braces in the span + (open_brace + 1)..close_brace +} + +fn offset_reference_def_range( + md: &str, + dest: &CowStr<'_>, + link_range: Range<usize>, +) -> Range<usize> { + // For diagnostics, we want to underline the link's definition but `span` will point at + // where the link is used. This is a problem for reference-style links, where the definition + // is separate from the usage. + + match dest { + // `Borrowed` variant means the string (the link's destination) may come directly from + // the markdown text and we can locate the original link destination. + // NOTE: LinkReplacer also provides `Borrowed` but possibly from other sources, + // so `locate()` can fall back to use `span`. + CowStr::Borrowed(s) => { + // FIXME: remove this function once pulldown_cmark can provide spans for link definitions. + unsafe { + let s_start = dest.as_ptr(); + let s_end = s_start.add(s.len()); + let md_start = md.as_ptr(); + let md_end = md_start.add(md.len()); + if md_start <= s_start && s_end <= md_end { + let start = s_start.offset_from(md_start) as usize; + let end = s_end.offset_from(md_start) as usize; + start..end + } else { + link_range + } + } + } + + // For anything else, we can only use the provided range. + CowStr::Boxed(_) | CowStr::Inlined(_) => link_range, + } +} diff --git a/src/librustdoc/passes/propagate_doc_cfg.rs b/src/librustdoc/passes/propagate_doc_cfg.rs index 8a33e51b3..95273a225 100644 --- a/src/librustdoc/passes/propagate_doc_cfg.rs +++ b/src/librustdoc/passes/propagate_doc_cfg.rs @@ -38,8 +38,9 @@ impl<'a, 'tcx> CfgPropagator<'a, 'tcx> { _ => return, }; - let Some(def_id) = item.item_id.as_def_id().and_then(|def_id| def_id.as_local()) - else { return }; + let Some(def_id) = item.item_id.as_def_id().and_then(|def_id| def_id.as_local()) else { + return; + }; if check_parent { let expected_parent = self.cx.tcx.opt_local_parent(def_id); diff --git a/src/librustdoc/passes/strip_hidden.rs b/src/librustdoc/passes/strip_hidden.rs index e2e38d3e7..81a90ed49 100644 --- a/src/librustdoc/passes/strip_hidden.rs +++ b/src/librustdoc/passes/strip_hidden.rs @@ -6,11 +6,11 @@ use rustc_span::symbol::sym; use std::mem; use crate::clean; +use crate::clean::utils::inherits_doc_hidden; use crate::clean::{Item, ItemIdSet}; 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", @@ -42,6 +42,7 @@ pub(crate) fn strip_hidden(krate: clean::Crate, cx: &mut DocContext<'_>) -> clea cache: &cx.cache, is_json_output, document_private: cx.render_options.document_private, + document_hidden: cx.render_options.document_hidden, }; stripper.fold_crate(krate) } diff --git a/src/librustdoc/passes/strip_priv_imports.rs b/src/librustdoc/passes/strip_priv_imports.rs index 4c992e948..468712ba3 100644 --- a/src/librustdoc/passes/strip_priv_imports.rs +++ b/src/librustdoc/passes/strip_priv_imports.rs @@ -13,5 +13,10 @@ pub(crate) const STRIP_PRIV_IMPORTS: Pass = Pass { pub(crate) fn strip_priv_imports(krate: clean::Crate, cx: &mut DocContext<'_>) -> clean::Crate { let is_json_output = cx.output_format.is_json() && !cx.show_coverage; - ImportStripper { tcx: cx.tcx, is_json_output }.fold_crate(krate) + ImportStripper { + tcx: cx.tcx, + is_json_output, + document_hidden: cx.render_options.document_hidden, + } + .fold_crate(krate) } diff --git a/src/librustdoc/passes/strip_private.rs b/src/librustdoc/passes/strip_private.rs index bb6dccb7c..3b6f484fd 100644 --- a/src/librustdoc/passes/strip_private.rs +++ b/src/librustdoc/passes/strip_private.rs @@ -28,8 +28,12 @@ pub(crate) fn strip_private(mut krate: clean::Crate, cx: &mut DocContext<'_>) -> is_json_output, tcx: cx.tcx, }; - krate = - ImportStripper { tcx: cx.tcx, is_json_output }.fold_crate(stripper.fold_crate(krate)); + krate = ImportStripper { + tcx: cx.tcx, + is_json_output, + document_hidden: cx.render_options.document_hidden, + } + .fold_crate(stripper.fold_crate(krate)); } // strip all impls referencing private items @@ -39,6 +43,7 @@ pub(crate) fn strip_private(mut krate: clean::Crate, cx: &mut DocContext<'_>) -> cache: &cx.cache, is_json_output, document_private: cx.render_options.document_private, + document_hidden: cx.render_options.document_hidden, }; stripper.fold_crate(krate) } diff --git a/src/librustdoc/passes/stripper.rs b/src/librustdoc/passes/stripper.rs index 90c361d9d..64a4ace5b 100644 --- a/src/librustdoc/passes/stripper.rs +++ b/src/librustdoc/passes/stripper.rs @@ -3,6 +3,7 @@ use rustc_hir::def_id::DefId; use rustc_middle::ty::{TyCtxt, Visibility}; use std::mem; +use crate::clean::utils::inherits_doc_hidden; use crate::clean::{self, Item, ItemId, ItemIdSet}; use crate::fold::{strip_item, DocFolder}; use crate::formats::cache::Cache; @@ -151,6 +152,7 @@ pub(crate) struct ImplStripper<'a, 'tcx> { pub(crate) cache: &'a Cache, pub(crate) is_json_output: bool, pub(crate) document_private: bool, + pub(crate) document_hidden: bool, } impl<'a> ImplStripper<'a, '_> { @@ -162,7 +164,13 @@ impl<'a> ImplStripper<'a, '_> { // If the "for" item is exported and the impl block isn't `#[doc(hidden)]`, then we // need to keep it. self.cache.effective_visibilities.is_exported(self.tcx, for_def_id) - && !item.is_doc_hidden() + && (self.document_hidden + || ((!item.is_doc_hidden() + && for_def_id + .as_local() + .map(|def_id| !inherits_doc_hidden(self.tcx, def_id, None)) + .unwrap_or(true)) + || self.cache.inlined_items.contains(&for_def_id))) } else { false } @@ -231,6 +239,7 @@ impl<'a> DocFolder for ImplStripper<'a, '_> { pub(crate) struct ImportStripper<'tcx> { pub(crate) tcx: TyCtxt<'tcx>, pub(crate) is_json_output: bool, + pub(crate) document_hidden: bool, } impl<'tcx> ImportStripper<'tcx> { @@ -247,8 +256,12 @@ impl<'tcx> ImportStripper<'tcx> { impl<'tcx> DocFolder for ImportStripper<'tcx> { fn fold_item(&mut self, i: Item) -> Option<Item> { match *i.kind { - clean::ImportItem(imp) if self.import_should_be_hidden(&i, &imp) => None, - clean::ImportItem(_) if i.is_doc_hidden() => None, + clean::ImportItem(imp) + if !self.document_hidden && self.import_should_be_hidden(&i, &imp) => + { + None + } + // clean::ImportItem(_) if !self.document_hidden && i.is_doc_hidden() => None, clean::ExternCrateItem { .. } | clean::ImportItem(..) if i.visibility(self.tcx) != Some(Visibility::Public) => { |