Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
182 changes: 128 additions & 54 deletions src/librustdoc/clean/inline.rs
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ pub(crate) fn try_inline(
Res::Def(DefKind::Ctor(..), _) | Res::SelfCtor(..) => return Some(Vec::new()),
Res::Def(DefKind::Mod, did) => {
record_extern_fqn(cx, did, ItemType::Module);
clean::ModuleItem(build_module(cx, did, visited))
clean::ModuleItem(build_module(cx, did, name, visited))
}
Res::Def(DefKind::Static { .. }, did) => {
record_extern_fqn(cx, did, ItemType::Static);
Expand Down Expand Up @@ -207,6 +207,7 @@ pub(crate) fn try_inline_glob(
let mut items = build_module_items(
cx,
did,
cx.tcx.item_name(did),
visited,
inlined_names,
Some(&reexports),
Expand Down Expand Up @@ -665,16 +666,43 @@ pub(crate) fn build_impl(
));
}

fn build_module(cx: &mut DocContext<'_>, did: DefId, visited: &mut DefIdSet) -> clean::Module {
let items = build_module_items(cx, did, visited, &mut FxHashSet::default(), None, None);
fn build_module(
cx: &mut DocContext<'_>,
did: DefId,
name: Symbol,
visited: &mut DefIdSet,
) -> clean::Module {
let items = build_module_items(cx, did, name, visited, &mut FxHashSet::default(), None, None);

let span = clean::Span::new(cx.tcx.def_span(did));
clean::Module { items, span }
}

// We are only interested into `Res::Def`. And in there, we only want "items" which get their own
// rustdoc page. So not `DefKind::Ctor` for example (which is returned by `tcx.module_children()`).
fn should_ignore_res(res: Res) -> bool {
Comment thread
Urgau marked this conversation as resolved.
!matches!(
res,
Res::Def(DefKind::Trait, _)
| Res::Def(DefKind::TraitAlias, _)
| Res::Def(DefKind::Fn, _)
| Res::Def(DefKind::Struct, _)
| Res::Def(DefKind::Union, _)
| Res::Def(DefKind::TyAlias, _)
| Res::Def(DefKind::Enum, _)
| Res::Def(DefKind::ForeignTy, _)
| Res::Def(DefKind::Variant, _)
| Res::Def(DefKind::Mod, _)
| Res::Def(DefKind::Static { .. }, _)
| Res::Def(DefKind::Const { .. }, _)
| Res::Def(DefKind::Macro(_), _)
)
}

fn build_module_items(
cx: &mut DocContext<'_>,
did: DefId,
module_def_id: DefId,
module_name: Symbol,
visited: &mut DefIdSet,
inlined_names: &mut FxHashSet<(ItemType, Symbol)>,
allowed_def_ids: Option<&DefIdSet>,
Expand All @@ -685,61 +713,107 @@ fn build_module_items(
// If we're re-exporting a re-export it may actually re-export something in
// two namespaces, so the target may be listed twice. Make sure we only
// visit each node at most once.
for item in cx.tcx.module_children(did).iter() {
if item.vis.is_public() {
let res = item.res.expect_non_local();
if let Some(def_id) = res.opt_def_id()
&& let Some(allowed_def_ids) = allowed_def_ids
&& !allowed_def_ids.contains(&def_id)
for item in cx.tcx.module_children(module_def_id).iter() {
if !item.vis.is_public() {
continue;
}
let res = item.res.expect_non_local();
if let Some(def_id) = res.opt_def_id()
&& let Some(allowed_def_ids) = allowed_def_ids
&& !allowed_def_ids.contains(&def_id)
{
continue;
}
if let Some(def_id) = res.mod_def_id() {
// If we're inlining a glob import, it's possible to have
// two distinct modules with the same name. We don't want to
// inline it, or mark any of its contents as visited.
if module_def_id == def_id
|| inlined_names.contains(&(ItemType::Module, item.ident.name))
|| !visited.insert(def_id)
{
continue;
}
if let Some(def_id) = res.mod_def_id() {
// If we're inlining a glob import, it's possible to have
// two distinct modules with the same name. We don't want to
// inline it, or mark any of its contents as visited.
if did == def_id
|| inlined_names.contains(&(ItemType::Module, item.ident.name))
|| !visited.insert(def_id)
{
continue;
}
}
if let Res::PrimTy(p) = res {
// Primitive types can't be inlined so generate an import instead.
let prim_ty = clean::PrimitiveType::from(p);
items.push(clean::Item {
inner: Box::new(clean::ItemInner {
name: None,
// We can use the item's `DefId` directly since the only information ever
// used from it is `DefId.krate`.
item_id: ItemId::DefId(did),
attrs: Default::default(),
stability: None,
kind: clean::ImportItem(clean::Import::new_simple(
item.ident.name,
clean::ImportSource {
path: clean::Path {
res,
segments: thin_vec![clean::PathSegment {
name: prim_ty.as_sym(),
args: clean::GenericArgs::AngleBracketed {
args: Default::default(),
constraints: ThinVec::new(),
},
}],
},
did: None,
}
if let Res::PrimTy(p) = res {
// Primitive types can't be inlined so generate an import instead.
let prim_ty = clean::PrimitiveType::from(p);
items.push(clean::Item {
inner: Box::new(clean::ItemInner {
name: None,
// We can use the item's `DefId` directly since the only information ever
// used from it is `DefId.krate`.
item_id: ItemId::DefId(module_def_id),
attrs: Default::default(),
stability: None,
kind: clean::ImportItem(clean::Import::new_simple(
item.ident.name,
clean::ImportSource {
path: clean::Path {
res,
segments: thin_vec![clean::PathSegment {
name: prim_ty.as_sym(),
args: clean::GenericArgs::AngleBracketed {
args: Default::default(),
constraints: ThinVec::new(),
},
}],
},
true,
)),
cfg: None,
inline_stmt_id: None,
}),
});
} else if let Some(i) = try_inline(cx, res, item.ident.name, attrs, visited) {
items.extend(i)
did: None,
},
true,
)),
cfg: None,
inline_stmt_id: None,
}),
});
} else if let Some(def_id) = res.opt_def_id()
&& let Some(reexport) = item.reexport_chain.first()
Comment thread
Urgau marked this conversation as resolved.
&& let Some(reexport_def_id) = reexport.id()
&& find_attr!(
load_attrs(cx.tcx, reexport_def_id),
Doc(d)
if d.inline.first().is_some_and(|(inline, _)| *inline == hir::attrs::DocInline::NoInline)
)
{
if should_ignore_res(res) {
continue;
}
// This item is reexported as `no_inline` so it shouldn't be inlined.
let item = Item::from_def_id_and_parts(
module_def_id,
None,
clean::ImportItem(clean::Import::new_simple(
item.ident.name,
clean::ImportSource {
path: clean::Path {
res,
segments: thin_vec![
clean::PathSegment {
name: module_name,
args: clean::GenericArgs::AngleBracketed {
args: Default::default(),
constraints: ThinVec::new(),
},
},
clean::PathSegment {
name: cx.tcx.item_name(def_id),
args: clean::GenericArgs::AngleBracketed {
args: Default::default(),
constraints: ThinVec::new(),
},
},
],
},
did: None,
},
true,
)),
cx.tcx,
);
items.push(item);
} else if let Some(i) = try_inline(cx, res, item.ident.name, attrs, visited) {
items.extend(i)
}
}

Expand Down
2 changes: 2 additions & 0 deletions src/librustdoc/passes/stripper.rs
Original file line number Diff line number Diff line change
Expand Up @@ -287,12 +287,14 @@ impl DocFolder for ImportStripper<'_> {
clean::ImportItem(imp)
if !self.document_hidden && self.import_should_be_hidden(&i, imp) =>
{
debug!("ImportStripper: stripping {:?}", i.name);
None
}
// clean::ImportItem(_) if !self.document_hidden && i.is_doc_hidden() => None,
clean::ExternCrateItem { .. } | clean::ImportItem(..)
if i.visibility(self.tcx) != Some(Visibility::Public) =>
{
debug!("ImportStripper: stripping {:?}", i.name);
None
}
_ => Some(self.fold_item_recur(i)),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
#[doc(no_inline)]
pub use crate::{
future::{Future, FutureExt as _},
};

mod future {
pub struct Future;
pub trait FutureExt {}
}
22 changes: 22 additions & 0 deletions tests/rustdoc-html/reexport/inline-foreign-no_inline.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// This test ensures that an inlined foreign item with `#[doc(no_inline)]` is
// not inlined.
// This is a regression test for <https://github.com/rust-lang/rust/issues/92379>.

//@ aux-build: inline-foreign-no_inline.rs

#![crate_name = "foo"]

extern crate inline_foreign_no_inline;

// Since we cannot inline `inline_foreign_no_inline` because it has `no_inline`, there
// should have no other items than "Module".
//@ has 'foo/index.html'
//@ count - '//*[@id="main-content"]/h2' 1
//@ has - '//*[@id="main-content"]/h2' 'Module'
//@ has - '//*[@id="main-content"]/dl[@class="item-table"]/dt' 'dep'

//@ has 'foo/dep/index.html'
//@ has - '//*[@class="item-table reexports"]/dt' 'pub use dep::Future;'
//@ has - '//*[@class="item-table reexports"]/dt' 'pub use dep::FutureExt as _;'
#[doc(inline)]
pub use inline_foreign_no_inline as dep;
21 changes: 21 additions & 0 deletions tests/rustdoc-html/reexport/inline-no_inline.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// This test checks that inlining a `no_inline` is done correctly.

#![crate_name = "foo"]

//@ has 'foo/index.html'
// There should be `Re-exports` and `Structs`
//@ count - '//*[@id="main-content"]/h2' 2
//@ has - '//*[@id="main-content"]/h2[@class="section-header"]' 'Re-exports'
//@ has - '//*[@id="main-content"]/h2[@class="section-header"]' 'Structs'

//@ has - '//*[@id="main-content"]/dl[@class="item-table reexports"]/dt' 'pub use self::bar::A;'
//@ has - '//*[@id="main-content"]/dl[@class="item-table"]/dt' 'X'

mod bar {
pub struct A;
}

#[doc(no_inline)]
pub use self::bar::A;
#[doc(inline)]
pub use self::A as X;
Loading