Skip to content

Commit d117b41

Browse files
authored
Rollup merge of #111095 - GuillaumeGomez:fix-assoc-item-trait-inside-hidden, r=notriddle
Correctly handle associated items of a trait inside a `#[doc(hidden)]` item Fixes #111064. cc `@compiler-errors` r? `@notriddle`
2 parents f192227 + 8de4308 commit d117b41

7 files changed

+162
-46
lines changed

src/librustdoc/clean/mod.rs

+41-24
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,39 @@ pub(crate) fn clean_doc_module<'tcx>(doc: &DocModule<'tcx>, cx: &mut DocContext<
119119
});
120120

121121
let kind = ModuleItem(Module { items, span });
122-
Item::from_def_id_and_parts(doc.def_id.to_def_id(), Some(doc.name), kind, cx)
122+
generate_item_with_correct_attrs(cx, kind, doc.def_id, doc.name, doc.import_id, doc.renamed)
123+
}
124+
125+
fn generate_item_with_correct_attrs(
126+
cx: &mut DocContext<'_>,
127+
kind: ItemKind,
128+
local_def_id: LocalDefId,
129+
name: Symbol,
130+
import_id: Option<LocalDefId>,
131+
renamed: Option<Symbol>,
132+
) -> Item {
133+
let def_id = local_def_id.to_def_id();
134+
let target_attrs = inline::load_attrs(cx, def_id);
135+
let attrs = if let Some(import_id) = import_id {
136+
let is_inline = inline::load_attrs(cx, import_id.to_def_id())
137+
.lists(sym::doc)
138+
.get_word_attr(sym::inline)
139+
.is_some();
140+
let mut attrs = get_all_import_attributes(cx, import_id, local_def_id, is_inline);
141+
add_without_unwanted_attributes(&mut attrs, target_attrs, is_inline, None);
142+
attrs
143+
} else {
144+
// We only keep the item's attributes.
145+
target_attrs.iter().map(|attr| (Cow::Borrowed(attr), None)).collect()
146+
};
147+
148+
let cfg = attrs.cfg(cx.tcx, &cx.cache.hidden_cfg);
149+
let attrs = Attributes::from_ast_iter(attrs.iter().map(|(attr, did)| (&**attr, *did)), false);
150+
151+
let name = renamed.or(Some(name));
152+
let mut item = Item::from_def_id_and_attrs_and_parts(def_id, name, kind, Box::new(attrs), cfg);
153+
item.inline_stmt_id = import_id.map(|local| local.to_def_id());
154+
item
123155
}
124156

125157
fn clean_generic_bound<'tcx>(
@@ -2345,29 +2377,14 @@ fn clean_maybe_renamed_item<'tcx>(
23452377
_ => unreachable!("not yet converted"),
23462378
};
23472379

2348-
let target_attrs = inline::load_attrs(cx, def_id);
2349-
let attrs = if let Some(import_id) = import_id {
2350-
let is_inline = inline::load_attrs(cx, import_id.to_def_id())
2351-
.lists(sym::doc)
2352-
.get_word_attr(sym::inline)
2353-
.is_some();
2354-
let mut attrs =
2355-
get_all_import_attributes(cx, import_id, item.owner_id.def_id, is_inline);
2356-
add_without_unwanted_attributes(&mut attrs, target_attrs, is_inline, None);
2357-
attrs
2358-
} else {
2359-
// We only keep the item's attributes.
2360-
target_attrs.iter().map(|attr| (Cow::Borrowed(attr), None)).collect()
2361-
};
2362-
2363-
let cfg = attrs.cfg(cx.tcx, &cx.cache.hidden_cfg);
2364-
let attrs =
2365-
Attributes::from_ast_iter(attrs.iter().map(|(attr, did)| (&**attr, *did)), false);
2366-
2367-
let mut item =
2368-
Item::from_def_id_and_attrs_and_parts(def_id, Some(name), kind, Box::new(attrs), cfg);
2369-
item.inline_stmt_id = import_id.map(|local| local.to_def_id());
2370-
vec![item]
2380+
vec![generate_item_with_correct_attrs(
2381+
cx,
2382+
kind,
2383+
item.owner_id.def_id,
2384+
name,
2385+
import_id,
2386+
renamed,
2387+
)]
23712388
})
23722389
}
23732390

src/librustdoc/formats/cache.rs

+7-1
Original file line numberDiff line numberDiff line change
@@ -195,7 +195,13 @@ impl Cache {
195195
impl<'a, 'tcx> DocFolder for CacheBuilder<'a, 'tcx> {
196196
fn fold_item(&mut self, item: clean::Item) -> Option<clean::Item> {
197197
if item.item_id.is_local() {
198-
debug!("folding {} \"{:?}\", id {:?}", item.type_(), item.name, item.item_id);
198+
let is_stripped = matches!(*item.kind, clean::ItemKind::StrippedItem(..));
199+
debug!(
200+
"folding {} (stripped: {is_stripped:?}) \"{:?}\", id {:?}",
201+
item.type_(),
202+
item.name,
203+
item.item_id
204+
);
199205
}
200206

201207
// If this is a stripped module,

src/librustdoc/passes/check_doc_test_visibility.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ pub(crate) fn should_have_doc_example(cx: &DocContext<'_>, item: &clean::Item) -
9595
}
9696

9797
if cx.tcx.is_doc_hidden(def_id.to_def_id())
98-
|| inherits_doc_hidden(cx.tcx, def_id)
98+
|| inherits_doc_hidden(cx.tcx, def_id, None)
9999
|| cx.tcx.def_span(def_id.to_def_id()).in_derive_expansion()
100100
{
101101
return false;

src/librustdoc/passes/strip_hidden.rs

+25-5
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
//! Strip all doc(hidden) items from the output.
22
3+
use rustc_hir::def_id::LocalDefId;
34
use rustc_middle::ty::TyCtxt;
45
use rustc_span::symbol::sym;
56
use std::mem;
@@ -29,6 +30,7 @@ pub(crate) fn strip_hidden(krate: clean::Crate, cx: &mut DocContext<'_>) -> clea
2930
update_retained: true,
3031
tcx: cx.tcx,
3132
is_in_hidden_item: false,
33+
last_reexport: None,
3234
};
3335
stripper.fold_crate(krate)
3436
};
@@ -49,13 +51,24 @@ struct Stripper<'a, 'tcx> {
4951
update_retained: bool,
5052
tcx: TyCtxt<'tcx>,
5153
is_in_hidden_item: bool,
54+
last_reexport: Option<LocalDefId>,
5255
}
5356

5457
impl<'a, 'tcx> Stripper<'a, 'tcx> {
58+
fn set_last_reexport_then_fold_item(&mut self, i: Item) -> Item {
59+
let prev_from_reexport = self.last_reexport;
60+
if i.inline_stmt_id.is_some() {
61+
self.last_reexport = i.item_id.as_def_id().and_then(|def_id| def_id.as_local());
62+
}
63+
let ret = self.fold_item_recur(i);
64+
self.last_reexport = prev_from_reexport;
65+
ret
66+
}
67+
5568
fn set_is_in_hidden_item_and_fold(&mut self, is_in_hidden_item: bool, i: Item) -> Item {
5669
let prev = self.is_in_hidden_item;
5770
self.is_in_hidden_item |= is_in_hidden_item;
58-
let ret = self.fold_item_recur(i);
71+
let ret = self.set_last_reexport_then_fold_item(i);
5972
self.is_in_hidden_item = prev;
6073
ret
6174
}
@@ -64,7 +77,7 @@ impl<'a, 'tcx> Stripper<'a, 'tcx> {
6477
/// of `is_in_hidden_item` to `true` because the impl children inherit its visibility.
6578
fn recurse_in_impl_or_exported_macro(&mut self, i: Item) -> Item {
6679
let prev = mem::replace(&mut self.is_in_hidden_item, false);
67-
let ret = self.fold_item_recur(i);
80+
let ret = self.set_last_reexport_then_fold_item(i);
6881
self.is_in_hidden_item = prev;
6982
ret
7083
}
@@ -86,13 +99,20 @@ impl<'a, 'tcx> DocFolder for Stripper<'a, 'tcx> {
8699
if !is_impl_or_exported_macro {
87100
is_hidden = self.is_in_hidden_item || has_doc_hidden;
88101
if !is_hidden && i.inline_stmt_id.is_none() {
89-
// We don't need to check if it's coming from a reexport since the reexport itself was
90-
// already checked.
102+
// `i.inline_stmt_id` is `Some` if the item is directly reexported. If it is, we
103+
// don't need to check it, because the reexport itself was already checked.
104+
//
105+
// If this item is the child of a reexported module, `self.last_reexport` will be
106+
// `Some` even though `i.inline_stmt_id` is `None`. Hiddenness inheritance needs to
107+
// account for the possibility that an item's true parent module is hidden, but it's
108+
// inlined into a visible module true. This code shouldn't be reachable if the
109+
// module's reexport is itself hidden, for the same reason it doesn't need to be
110+
// checked if `i.inline_stmt_id` is Some: hidden reexports are never inlined.
91111
is_hidden = i
92112
.item_id
93113
.as_def_id()
94114
.and_then(|def_id| def_id.as_local())
95-
.map(|def_id| inherits_doc_hidden(self.tcx, def_id))
115+
.map(|def_id| inherits_doc_hidden(self.tcx, def_id, self.last_reexport))
96116
.unwrap_or(false);
97117
}
98118
}

src/librustdoc/visit_ast.rs

+36-15
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ pub(crate) struct Module<'hir> {
2727
pub(crate) where_inner: Span,
2828
pub(crate) mods: Vec<Module<'hir>>,
2929
pub(crate) def_id: LocalDefId,
30+
pub(crate) renamed: Option<Symbol>,
31+
pub(crate) import_id: Option<LocalDefId>,
3032
/// The key is the item `ItemId` and the value is: (item, renamed, import_id).
3133
/// We use `FxIndexMap` to keep the insert order.
3234
pub(crate) items: FxIndexMap<
@@ -37,11 +39,19 @@ pub(crate) struct Module<'hir> {
3739
}
3840

3941
impl Module<'_> {
40-
pub(crate) fn new(name: Symbol, def_id: LocalDefId, where_inner: Span) -> Self {
42+
pub(crate) fn new(
43+
name: Symbol,
44+
def_id: LocalDefId,
45+
where_inner: Span,
46+
renamed: Option<Symbol>,
47+
import_id: Option<LocalDefId>,
48+
) -> Self {
4149
Module {
4250
name,
4351
def_id,
4452
where_inner,
53+
renamed,
54+
import_id,
4555
mods: Vec::new(),
4656
items: FxIndexMap::default(),
4757
foreigns: Vec::new(),
@@ -60,9 +70,16 @@ fn def_id_to_path(tcx: TyCtxt<'_>, did: DefId) -> Vec<Symbol> {
6070
std::iter::once(crate_name).chain(relative).collect()
6171
}
6272

63-
pub(crate) fn inherits_doc_hidden(tcx: TyCtxt<'_>, mut def_id: LocalDefId) -> bool {
73+
pub(crate) fn inherits_doc_hidden(
74+
tcx: TyCtxt<'_>,
75+
mut def_id: LocalDefId,
76+
stop_at: Option<LocalDefId>,
77+
) -> bool {
6478
let hir = tcx.hir();
6579
while let Some(id) = tcx.opt_local_parent(def_id) {
80+
if let Some(stop_at) = stop_at && id == stop_at {
81+
return false;
82+
}
6683
def_id = id;
6784
if tcx.is_doc_hidden(def_id.to_def_id()) {
6885
return true;
@@ -100,6 +117,8 @@ impl<'a, 'tcx> RustdocVisitor<'a, 'tcx> {
100117
cx.tcx.crate_name(LOCAL_CRATE),
101118
CRATE_DEF_ID,
102119
cx.tcx.hir().root_module().spans.inner_span,
120+
None,
121+
None,
103122
);
104123

105124
RustdocVisitor {
@@ -261,7 +280,7 @@ impl<'a, 'tcx> RustdocVisitor<'a, 'tcx> {
261280

262281
let is_private =
263282
!self.cx.cache.effective_visibilities.is_directly_public(self.cx.tcx, ori_res_did);
264-
let is_hidden = inherits_doc_hidden(self.cx.tcx, res_did);
283+
let is_hidden = inherits_doc_hidden(self.cx.tcx, res_did, None);
265284

266285
// Only inline if requested or if the item would otherwise be stripped.
267286
if (!please_inline && !is_private && !is_hidden) || is_no_inline {
@@ -278,7 +297,7 @@ impl<'a, 'tcx> RustdocVisitor<'a, 'tcx> {
278297
.cache
279298
.effective_visibilities
280299
.is_directly_public(self.cx.tcx, item_def_id.to_def_id()) &&
281-
!inherits_doc_hidden(self.cx.tcx, item_def_id)
300+
!inherits_doc_hidden(self.cx.tcx, item_def_id, None)
282301
{
283302
// The imported item is public and not `doc(hidden)` so no need to inline it.
284303
return false;
@@ -427,7 +446,7 @@ impl<'a, 'tcx> RustdocVisitor<'a, 'tcx> {
427446
}
428447
}
429448
hir::ItemKind::Mod(ref m) => {
430-
self.enter_mod(item.owner_id.def_id, m, name);
449+
self.enter_mod(item.owner_id.def_id, m, name, renamed, import_id);
431450
}
432451
hir::ItemKind::Fn(..)
433452
| hir::ItemKind::ExternCrate(..)
@@ -480,8 +499,15 @@ impl<'a, 'tcx> RustdocVisitor<'a, 'tcx> {
480499
/// This method will create a new module and push it onto the "modules stack" then call
481500
/// `visit_mod_contents`. Once done, it'll remove it from the "modules stack" and instead
482501
/// add into the list of modules of the current module.
483-
fn enter_mod(&mut self, id: LocalDefId, m: &'tcx hir::Mod<'tcx>, name: Symbol) {
484-
self.modules.push(Module::new(name, id, m.spans.inner_span));
502+
fn enter_mod(
503+
&mut self,
504+
id: LocalDefId,
505+
m: &'tcx hir::Mod<'tcx>,
506+
name: Symbol,
507+
renamed: Option<Symbol>,
508+
import_id: Option<LocalDefId>,
509+
) {
510+
self.modules.push(Module::new(name, id, m.spans.inner_span, renamed, import_id));
485511

486512
self.visit_mod_contents(id, m);
487513

@@ -501,19 +527,14 @@ impl<'a, 'tcx> Visitor<'tcx> for RustdocVisitor<'a, 'tcx> {
501527

502528
fn visit_item(&mut self, i: &'tcx hir::Item<'tcx>) {
503529
self.visit_item_inner(i, None, None);
504-
let new_value = if self.is_importable_from_parent {
505-
matches!(
530+
let new_value = self.is_importable_from_parent
531+
&& matches!(
506532
i.kind,
507533
hir::ItemKind::Mod(..)
508534
| hir::ItemKind::ForeignMod { .. }
509535
| hir::ItemKind::Impl(..)
510536
| hir::ItemKind::Trait(..)
511-
)
512-
} else {
513-
// Whatever the context, if it's an impl block, the items inside it can be used so they
514-
// should be visible.
515-
matches!(i.kind, hir::ItemKind::Impl(..))
516-
};
537+
);
517538
let prev = mem::replace(&mut self.is_importable_from_parent, new_value);
518539
walk_item(self, i);
519540
self.is_importable_from_parent = prev;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
#![feature(no_core)]
2+
#![no_core]
3+
#![crate_name = "foo"]
4+
5+
// @!has 'foo/hidden/index.html'
6+
// FIXME: add missing `@` for the two next tests once issue is fixed!
7+
// To be done in <https://github.com/rust-lang/rust/issues/111249>.
8+
// !has 'foo/hidden/inner/index.html'
9+
// !has 'foo/hidden/inner/trait.Foo.html'
10+
#[doc(hidden)]
11+
pub mod hidden {
12+
pub mod inner {
13+
pub trait Foo {
14+
/// Hello, world!
15+
fn test();
16+
}
17+
}
18+
}
19+
20+
// @has 'foo/visible/index.html'
21+
// @has 'foo/visible/trait.Foo.html'
22+
#[doc(inline)]
23+
pub use hidden::inner as visible;
24+
25+
// @has 'foo/struct.Bar.html'
26+
// @count - '//*[@id="impl-Foo-for-Bar"]' 1
27+
pub struct Bar;
28+
29+
impl visible::Foo for Bar {
30+
fn test() {}
31+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
// Regression test for <https://github.com/rust-lang/rust/issues/111064>.
2+
// Methods from a re-exported trait inside a `#[doc(hidden)]` item should
3+
// be visible.
4+
5+
#![crate_name = "foo"]
6+
7+
// @has 'foo/index.html'
8+
// @has - '//*[@id="main-content"]//*[@class="item-name"]/a[@href="trait.Foo.html"]' 'Foo'
9+
10+
// @has 'foo/trait.Foo.html'
11+
// @has - '//*[@id="main-content"]//*[@class="code-header"]' 'fn test()'
12+
13+
#[doc(hidden)]
14+
mod hidden {
15+
pub trait Foo {
16+
/// Hello, world!
17+
fn test();
18+
}
19+
}
20+
21+
pub use hidden::Foo;

0 commit comments

Comments
 (0)