diff options
Diffstat (limited to '')
-rw-r--r-- | src/librustdoc/passes/bare_urls.rs | 6 | ||||
-rw-r--r-- | src/librustdoc/passes/check_code_block_syntax.rs | 95 | ||||
-rw-r--r-- | src/librustdoc/passes/check_doc_test_visibility.rs | 14 | ||||
-rw-r--r-- | src/librustdoc/passes/collect_intra_doc_links.rs | 28 | ||||
-rw-r--r-- | src/librustdoc/passes/collect_intra_doc_links/early.rs | 44 | ||||
-rw-r--r-- | src/librustdoc/passes/html_tags.rs | 72 | ||||
-rw-r--r-- | src/librustdoc/passes/propagate_doc_cfg.rs | 2 | ||||
-rw-r--r-- | src/librustdoc/passes/strip_private.rs | 2 | ||||
-rw-r--r-- | src/librustdoc/passes/stripper.rs | 44 |
9 files changed, 187 insertions, 120 deletions
diff --git a/src/librustdoc/passes/bare_urls.rs b/src/librustdoc/passes/bare_urls.rs index 392e26ea6..7ff3ccef9 100644 --- a/src/librustdoc/passes/bare_urls.rs +++ b/src/librustdoc/passes/bare_urls.rs @@ -71,16 +71,14 @@ impl<'a, 'tcx> DocVisitor for BareUrlsLinter<'a, 'tcx> { let report_diag = |cx: &DocContext<'_>, msg: &str, url: &str, range: Range<usize>| { let sp = super::source_span_for_markdown_range(cx.tcx, &dox, &range, &item.attrs) .unwrap_or_else(|| item.attr_span(cx.tcx)); - cx.tcx.struct_span_lint_hir(crate::lint::BARE_URLS, hir_id, sp, |lint| { - lint.build(msg) - .note("bare URLs are not automatically turned into clickable links") + cx.tcx.struct_span_lint_hir(crate::lint::BARE_URLS, hir_id, sp, msg, |lint| { + lint.note("bare URLs are not automatically turned into clickable links") .span_suggestion( sp, "use an automatic link instead", format!("<{}>", url), Applicability::MachineApplicable, ) - .emit(); }); }; diff --git a/src/librustdoc/passes/check_code_block_syntax.rs b/src/librustdoc/passes/check_code_block_syntax.rs index 381ac7a5d..2e651b538 100644 --- a/src/librustdoc/passes/check_code_block_syntax.rs +++ b/src/librustdoc/passes/check_code_block_syntax.rs @@ -1,8 +1,9 @@ //! Validates syntax inside Rust code blocks (\`\`\`rust). use rustc_data_structures::sync::{Lock, Lrc}; use rustc_errors::{ - emitter::Emitter, translation::Translate, Applicability, Diagnostic, Handler, - LazyFallbackBundle, LintDiagnosticBuilder, + emitter::Emitter, + translation::{to_fluent_args, Translate}, + Applicability, Diagnostic, Handler, LazyFallbackBundle, }; use rustc_parse::parse_stream_from_source_str; use rustc_session::parse::ParseSess; @@ -97,48 +98,10 @@ impl<'a, 'tcx> SyntaxChecker<'a, 'tcx> { None => (item.attr_span(self.cx.tcx), false), }; - // lambda that will use the lint to start a new diagnostic and add - // a suggestion to it when needed. - let diag_builder = |lint: LintDiagnosticBuilder<'_, ()>| { - let explanation = if is_ignore { - "`ignore` code blocks require valid Rust code for syntax highlighting; \ - mark blocks that do not contain Rust code as text" - } else { - "mark blocks that do not contain Rust code as text" - }; - let msg = if buffer.has_errors { - "could not parse code block as Rust code" - } else { - "Rust code block is empty" - }; - let mut diag = lint.build(msg); - - if precise_span { - if is_ignore { - // giving an accurate suggestion is hard because `ignore` might not have come first in the list. - // just give a `help` instead. - diag.span_help( - sp.from_inner(InnerSpan::new(0, 3)), - &format!("{}: ```text", explanation), - ); - } else if empty_block { - diag.span_suggestion( - sp.from_inner(InnerSpan::new(0, 3)).shrink_to_hi(), - explanation, - "text", - Applicability::MachineApplicable, - ); - } - } else if empty_block || is_ignore { - diag.help(&format!("{}: ```text", explanation)); - } - - // FIXME(#67563): Provide more context for these errors by displaying the spans inline. - for message in buffer.messages.iter() { - diag.note(message); - } - - diag.emit(); + let msg = if buffer.has_errors { + "could not parse code block as Rust code" + } else { + "Rust code block is empty" }; // Finally build and emit the completed diagnostic. @@ -148,7 +111,42 @@ impl<'a, 'tcx> SyntaxChecker<'a, 'tcx> { crate::lint::INVALID_RUST_CODEBLOCKS, hir_id, sp, - diag_builder, + msg, + |lint| { + let explanation = if is_ignore { + "`ignore` code blocks require valid Rust code for syntax highlighting; \ + mark blocks that do not contain Rust code as text" + } else { + "mark blocks that do not contain Rust code as text" + }; + + if precise_span { + if is_ignore { + // giving an accurate suggestion is hard because `ignore` might not have come first in the list. + // just give a `help` instead. + lint.span_help( + sp.from_inner(InnerSpan::new(0, 3)), + &format!("{}: ```text", explanation), + ); + } else if empty_block { + lint.span_suggestion( + sp.from_inner(InnerSpan::new(0, 3)).shrink_to_hi(), + explanation, + "text", + Applicability::MachineApplicable, + ); + } + } else if empty_block || is_ignore { + lint.help(&format!("{}: ```text", explanation)); + } + + // FIXME(#67563): Provide more context for these errors by displaying the spans inline. + for message in buffer.messages.iter() { + lint.note(message); + } + + lint + }, ); } } @@ -195,8 +193,11 @@ impl Translate for BufferEmitter { impl Emitter for BufferEmitter { fn emit_diagnostic(&mut self, diag: &Diagnostic) { let mut buffer = self.buffer.borrow_mut(); - // FIXME(davidtwco): need to support translation here eventually - buffer.messages.push(format!("error from rustc: {}", diag.message[0].0.expect_str())); + + let fluent_args = to_fluent_args(diag.args()); + let translated_main_message = self.translate_message(&diag.message[0].0, &fluent_args); + + buffer.messages.push(format!("error from rustc: {}", translated_main_message)); if diag.is_error() { buffer.has_errors = true; } diff --git a/src/librustdoc/passes/check_doc_test_visibility.rs b/src/librustdoc/passes/check_doc_test_visibility.rs index 55d5f303d..7740c6d5b 100644 --- a/src/librustdoc/passes/check_doc_test_visibility.rs +++ b/src/librustdoc/passes/check_doc_test_visibility.rs @@ -56,7 +56,7 @@ impl crate::doctest::Tester for Tests { } pub(crate) fn should_have_doc_example(cx: &DocContext<'_>, item: &clean::Item) -> bool { - if !cx.cache.access_levels.is_public(item.item_id.expect_def_id()) + if !cx.cache.effective_visibilities.is_directly_public(item.item_id.expect_def_id()) || matches!( *item.kind, clean::StructFieldItem(_) @@ -125,21 +125,19 @@ pub(crate) fn look_for_tests<'tcx>(cx: &DocContext<'tcx>, dox: &str, item: &Item crate::lint::MISSING_DOC_CODE_EXAMPLES, hir_id, sp, - |lint| { - lint.build("missing code example in this documentation").emit(); - }, + "missing code example in this documentation", + |lint| lint, ); } } else if tests.found_tests > 0 - && !cx.cache.access_levels.is_exported(item.item_id.expect_def_id()) + && !cx.cache.effective_visibilities.is_exported(item.item_id.expect_def_id()) { cx.tcx.struct_span_lint_hir( crate::lint::PRIVATE_DOC_TESTS, hir_id, item.attr_span(cx.tcx), - |lint| { - lint.build("documentation test in private item").emit(); - }, + "documentation test in private item", + |lint| lint, ); } } diff --git a/src/librustdoc/passes/collect_intra_doc_links.rs b/src/librustdoc/passes/collect_intra_doc_links.rs index b2a41bfa4..8aa0abd36 100644 --- a/src/librustdoc/passes/collect_intra_doc_links.rs +++ b/src/librustdoc/passes/collect_intra_doc_links.rs @@ -80,10 +80,10 @@ impl Res { } } - fn def_id(self, tcx: TyCtxt<'_>) -> DefId { + fn def_id(self, tcx: TyCtxt<'_>) -> Option<DefId> { match self { - Res::Def(_, id) => id, - Res::Primitive(prim) => *PrimitiveType::primitive_locations(tcx).get(&prim).unwrap(), + Res::Def(_, id) => Some(id), + Res::Primitive(prim) => PrimitiveType::primitive_locations(tcx).get(&prim).copied(), } } @@ -1127,10 +1127,10 @@ impl LinkCollector<'_, '_> { } } - Some(ItemLink { + res.def_id(self.cx.tcx).map(|page_id| ItemLink { link: ori_link.link.clone(), link_text: link_text.clone(), - page_id: res.def_id(self.cx.tcx), + page_id, fragment, }) } @@ -1202,8 +1202,8 @@ impl LinkCollector<'_, '_> { item.item_id.expect_def_id().as_local().map(|src_id| (src_id, dst_id)) }) { - if self.cx.tcx.privacy_access_levels(()).is_exported(src_id) - && !self.cx.tcx.privacy_access_levels(()).is_exported(dst_id) + if self.cx.tcx.effective_visibilities(()).is_exported(src_id) + && !self.cx.tcx.effective_visibilities(()).is_exported(dst_id) { privacy_error(self.cx, diag_info, path_str); } @@ -1609,9 +1609,7 @@ fn report_diagnostic( let sp = item.attr_span(tcx); - tcx.struct_span_lint_hir(lint, hir_id, sp, |lint| { - let mut diag = lint.build(msg); - + tcx.struct_span_lint_hir(lint, hir_id, sp, msg, |lint| { let span = super::source_span_for_markdown_range(tcx, dox, link_range, &item.attrs).map(|sp| { if dox.as_bytes().get(link_range.start) == Some(&b'`') @@ -1624,7 +1622,7 @@ fn report_diagnostic( }); if let Some(sp) = span { - diag.set_span(sp); + lint.set_span(sp); } else { // blah blah blah\nblah\nblah [blah] blah blah\nblah blah // ^ ~~~~ @@ -1634,7 +1632,7 @@ fn report_diagnostic( let line = dox[last_new_line_offset..].lines().next().unwrap_or(""); // Print the line containing the `link_range` and manually mark it with '^'s. - diag.note(&format!( + lint.note(&format!( "the link appears in this line:\n\n{line}\n\ {indicator: <before$}{indicator:^<found$}", line = line, @@ -1644,9 +1642,9 @@ fn report_diagnostic( )); } - decorate(&mut diag, span); + decorate(lint, span); - diag.emit(); + lint }); } @@ -1895,7 +1893,7 @@ fn disambiguator_error( diag_info.link_range = disambiguator_range; report_diagnostic(cx.tcx, BROKEN_INTRA_DOC_LINKS, msg, &diag_info, |diag, _sp| { let msg = format!( - "see {}/rustdoc/write-documentation/linking-to-items-by-name.html#namespaces-and-disambiguators for more info about disambiguators", + "see {}/rustdoc/linking-to-items-by-name.html#namespaces-and-disambiguators for more info about disambiguators", crate::DOC_RUST_LANG_ORG_CHANNEL ); diag.note(&msg); diff --git a/src/librustdoc/passes/collect_intra_doc_links/early.rs b/src/librustdoc/passes/collect_intra_doc_links/early.rs index 38cfd7a27..1b373cfe5 100644 --- a/src/librustdoc/passes/collect_intra_doc_links/early.rs +++ b/src/librustdoc/passes/collect_intra_doc_links/early.rs @@ -37,7 +37,6 @@ pub(crate) fn early_resolve_intra_doc_links( markdown_links: Default::default(), doc_link_resolutions: Default::default(), traits_in_scope: Default::default(), - all_traits: Default::default(), all_trait_impls: Default::default(), all_macro_rules: Default::default(), document_private_items, @@ -48,7 +47,6 @@ pub(crate) fn early_resolve_intra_doc_links( 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); - link_resolver.process_extern_impls(); // 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. @@ -58,11 +56,12 @@ pub(crate) fn early_resolve_intra_doc_links( 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_traits: Some(link_resolver.all_traits), all_trait_impls: Some(link_resolver.all_trait_impls), all_macro_rules: link_resolver.all_macro_rules, } @@ -80,7 +79,6 @@ struct EarlyDocLinkResolver<'r, 'ra> { markdown_links: FxHashMap<String, Vec<PreprocessedMarkdownLink>>, doc_link_resolutions: FxHashMap<(Symbol, Namespace, DefId), Option<Res<ast::NodeId>>>, traits_in_scope: DefIdMap<Vec<TraitCandidate>>, - all_traits: Vec<DefId>, all_trait_impls: Vec<DefId>, all_macro_rules: FxHashMap<Symbol, Res<ast::NodeId>>, document_private_items: bool, @@ -121,8 +119,6 @@ impl<'ra> EarlyDocLinkResolver<'_, 'ra> { loop { let crates = Vec::from_iter(self.resolver.cstore().crates_untracked()); for &cnum in &crates[start_cnum..] { - let all_traits = - Vec::from_iter(self.resolver.cstore().traits_in_crate_untracked(cnum)); let all_trait_impls = Vec::from_iter(self.resolver.cstore().trait_impls_in_crate_untracked(cnum)); let all_inherent_impls = @@ -131,20 +127,18 @@ impl<'ra> EarlyDocLinkResolver<'_, 'ra> { self.resolver.cstore().incoherent_impls_in_crate_untracked(cnum), ); - // Querying traits in scope is expensive so we try to prune the impl and traits lists - // using privacy, private traits and impls from other crates are never documented in + // 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 &def_id in &all_traits { - if self.resolver.cstore().visibility_untracked(def_id).is_public() { - self.resolve_doc_links_extern_impl(def_id, false); - } - } 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); } } @@ -157,7 +151,6 @@ impl<'ra> EarlyDocLinkResolver<'_, 'ra> { self.resolve_doc_links_extern_impl(impl_def_id, true); } - self.all_traits.extend(all_traits); self.all_trait_impls .extend(all_trait_impls.into_iter().map(|(_, def_id, _)| def_id)); } @@ -306,15 +299,20 @@ impl<'ra> EarlyDocLinkResolver<'_, 'ra> { { if let Some(def_id) = child.res.opt_def_id() && !def_id.is_local() { let scope_id = match child.res { - Res::Def(DefKind::Variant, ..) => self.resolver.parent(def_id), + 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 } - // `DefKind::Trait`s are processed in `process_extern_impls`. - if let Res::Def(DefKind::Mod | DefKind::Enum, ..) = child.res { + 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, _) = @@ -356,10 +354,14 @@ impl Visitor<'_> for EarlyDocLinkResolver<'_, '_> { self.parent_scope.module = old_module; } else { match &item.kind { - ItemKind::Trait(..) => { - self.all_traits.push(self.resolver.local_def_id(item.id).to_def_id()); - } - ItemKind::Impl(box ast::Impl { of_trait: Some(..), .. }) => { + 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 => { diff --git a/src/librustdoc/passes/html_tags.rs b/src/librustdoc/passes/html_tags.rs index 885dadb32..a89ed7c7e 100644 --- a/src/librustdoc/passes/html_tags.rs +++ b/src/librustdoc/passes/html_tags.rs @@ -22,10 +22,8 @@ struct InvalidHtmlTagsLinter<'a, 'tcx> { } pub(crate) fn check_invalid_html_tags(krate: Crate, cx: &mut DocContext<'_>) -> Crate { - if cx.tcx.sess.is_nightly_build() { - let mut coll = InvalidHtmlTagsLinter { cx }; - coll.visit_crate(&krate); - } + let mut coll = InvalidHtmlTagsLinter { cx }; + coll.visit_crate(&krate); krate } @@ -186,7 +184,60 @@ fn extract_html_tag( } drop_tag(tags, tag_name, r, f); } else { - tags.push((tag_name, r)); + let mut is_self_closing = false; + let mut quote_pos = None; + if c != '>' { + let mut quote = None; + let mut after_eq = false; + for (i, c) in text[pos..].char_indices() { + if !c.is_whitespace() { + if let Some(q) = quote { + if c == q { + quote = None; + quote_pos = None; + after_eq = false; + } + } else if c == '>' { + break; + } else if c == '/' && !after_eq { + is_self_closing = true; + } else { + if is_self_closing { + is_self_closing = false; + } + if (c == '"' || c == '\'') && after_eq { + quote = Some(c); + quote_pos = Some(pos + i); + } else if c == '=' { + after_eq = true; + } + } + } else if quote.is_none() { + after_eq = false; + } + } + } + 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), + &qr, + false, + ); + } + if is_self_closing { + // https://html.spec.whatwg.org/#parse-error-non-void-html-element-start-tag-with-trailing-solidus + let valid = ALLOWED_UNCLOSED.contains(&&tag_name[..]) + || tags.iter().take(pos + 1).any(|(at, _)| { + let at = at.to_lowercase(); + at == "svg" || at == "math" + }); + if !valid { + f(&format!("invalid self-closing HTML tag `{}`", tag_name), &r, false); + } + } else { + tags.push((tag_name, r)); + } } } break; @@ -240,9 +291,8 @@ impl<'a, 'tcx> DocVisitor for InvalidHtmlTagsLinter<'a, 'tcx> { Some(sp) => sp, None => item.attr_span(tcx), }; - tcx.struct_span_lint_hir(crate::lint::INVALID_HTML_TAGS, hir_id, sp, |lint| { + tcx.struct_span_lint_hir(crate::lint::INVALID_HTML_TAGS, hir_id, sp, msg, |lint| { use rustc_lint_defs::Applicability; - let mut diag = lint.build(msg); // If a tag looks like `<this>`, it might actually be a generic. // We don't try to detect stuff `<like, this>` because that's not valid HTML, // and we don't try to detect stuff `<like this>` because that's not valid Rust. @@ -305,11 +355,10 @@ impl<'a, 'tcx> DocVisitor for InvalidHtmlTagsLinter<'a, 'tcx> { if (generics_start > 0 && dox.as_bytes()[generics_start - 1] == b'<') || (generics_end < dox.len() && dox.as_bytes()[generics_end] == b'>') { - diag.emit(); - return; + return lint; } // multipart form is chosen here because ``Vec<i32>`` would be confusing. - diag.multipart_suggestion( + lint.multipart_suggestion( "try marking as source code", vec![ (generics_sp.shrink_to_lo(), String::from("`")), @@ -318,7 +367,8 @@ impl<'a, 'tcx> DocVisitor for InvalidHtmlTagsLinter<'a, 'tcx> { Applicability::MaybeIncorrect, ); } - diag.emit() + + lint }); }; diff --git a/src/librustdoc/passes/propagate_doc_cfg.rs b/src/librustdoc/passes/propagate_doc_cfg.rs index 765f7c61b..de3a4b339 100644 --- a/src/librustdoc/passes/propagate_doc_cfg.rs +++ b/src/librustdoc/passes/propagate_doc_cfg.rs @@ -48,7 +48,7 @@ impl<'a, 'tcx> CfgPropagator<'a, 'tcx> { let expected_parent = hir.get_parent_item(hir_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) { + if self.parent == Some(expected_parent.def_id) { return; } } diff --git a/src/librustdoc/passes/strip_private.rs b/src/librustdoc/passes/strip_private.rs index f3aa3c7ce..450f69e15 100644 --- a/src/librustdoc/passes/strip_private.rs +++ b/src/librustdoc/passes/strip_private.rs @@ -23,7 +23,7 @@ pub(crate) fn strip_private(mut krate: clean::Crate, cx: &mut DocContext<'_>) -> { let mut stripper = Stripper { retained: &mut retained, - access_levels: &cx.cache.access_levels, + effective_visibilities: &cx.cache.effective_visibilities, update_retained: true, is_json_output, }; diff --git a/src/librustdoc/passes/stripper.rs b/src/librustdoc/passes/stripper.rs index a9d768f01..0089ce63d 100644 --- a/src/librustdoc/passes/stripper.rs +++ b/src/librustdoc/passes/stripper.rs @@ -1,15 +1,17 @@ //! A collection of utility functions for the `strip_*` passes. use rustc_hir::def_id::DefId; -use rustc_middle::middle::privacy::AccessLevels; +use rustc_middle::middle::privacy::EffectiveVisibilities; +use rustc_span::symbol::sym; + use std::mem; -use crate::clean::{self, Item, ItemId, ItemIdSet}; +use crate::clean::{self, Item, ItemId, ItemIdSet, NestedAttributesExt}; use crate::fold::{strip_item, DocFolder}; use crate::formats::cache::Cache; pub(crate) struct Stripper<'a> { pub(crate) retained: &'a mut ItemIdSet, - pub(crate) access_levels: &'a AccessLevels<DefId>, + pub(crate) effective_visibilities: &'a EffectiveVisibilities<DefId>, pub(crate) update_retained: bool, pub(crate) is_json_output: bool, } @@ -20,13 +22,13 @@ pub(crate) struct Stripper<'a> { #[inline] fn is_item_reachable( is_json_output: bool, - access_levels: &AccessLevels<DefId>, + effective_visibilities: &EffectiveVisibilities<DefId>, item_id: ItemId, ) -> bool { if is_json_output { - access_levels.is_reachable(item_id.expect_def_id()) + effective_visibilities.is_reachable(item_id.expect_def_id()) } else { - access_levels.is_exported(item_id.expect_def_id()) + effective_visibilities.is_exported(item_id.expect_def_id()) } } @@ -64,7 +66,7 @@ impl<'a> DocFolder for Stripper<'a> { | clean::ForeignTypeItem => { let item_id = i.item_id; if item_id.is_local() - && !is_item_reachable(self.is_json_output, self.access_levels, item_id) + && !is_item_reachable(self.is_json_output, self.effective_visibilities, item_id) { debug!("Stripper: stripping {:?} {:?}", i.type_(), i.name); return None; @@ -151,6 +153,22 @@ pub(crate) struct ImplStripper<'a> { pub(crate) document_private: bool, } +impl<'a> ImplStripper<'a> { + #[inline] + fn should_keep_impl(&self, item: &Item, for_def_id: DefId) -> bool { + if !for_def_id.is_local() || self.retained.contains(&for_def_id.into()) { + true + } else if self.is_json_output { + // 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(for_def_id) + && !item.attrs.lists(sym::doc).has_word(sym::hidden) + } else { + false + } + } +} + impl<'a> DocFolder for ImplStripper<'a> { fn fold_item(&mut self, i: Item) -> Option<Item> { if let clean::ImplItem(ref imp) = *i.kind { @@ -168,7 +186,7 @@ impl<'a> DocFolder for ImplStripper<'a> { item_id.is_local() && !is_item_reachable( self.is_json_output, - &self.cache.access_levels, + &self.cache.effective_visibilities, item_id, ) }) @@ -178,15 +196,17 @@ impl<'a> DocFolder for ImplStripper<'a> { return None; } } + // 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 did.is_local() && !imp.for_.is_assoc_ty() && !self.retained.contains(&did.into()) - { + 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.trait_.as_ref().map(|t| t.def_id()) { - if did.is_local() && !self.retained.contains(&did.into()) { + if !self.should_keep_impl(&i, did) { debug!("ImplStripper: impl item for stripped trait; removing"); return None; } @@ -194,7 +214,7 @@ impl<'a> DocFolder for ImplStripper<'a> { 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 did.is_local() && !self.retained.contains(&did.into()) { + if !self.should_keep_impl(&i, did) { debug!( "ImplStripper: stripped item in trait's generics; removing impl" ); |