Skip to content
Open
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
82 changes: 58 additions & 24 deletions src/librustdoc/clean/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -858,6 +858,25 @@ pub(crate) fn clean_generics<'tcx>(
}
}

fn clean_generics_without_impl_trait_params<'tcx>(
gens: &hir::Generics<'tcx>,
cx: &mut DocContext<'tcx>,
) -> Generics {
let mut generics = clean_generics(gens, cx);
let impl_trait_param_def_ids = generics
.params
.iter()
.filter(|param| param.is_synthetic_param())
.map(|param| param.def_id)
.collect::<Vec<_>>();

for def_id in impl_trait_param_def_ids {
cx.impl_trait_bounds.remove(&def_id.into());
}
generics.params.retain(|param| !param.is_synthetic_param());
generics
}

fn clean_ty_generics<'tcx>(cx: &mut DocContext<'tcx>, def_id: DefId) -> Generics {
clean_ty_generics_inner(cx, cx.tcx.generics_of(def_id), cx.tcx.explicit_predicates_of(def_id))
}
Expand Down Expand Up @@ -1091,10 +1110,10 @@ fn clean_fn_or_proc_macro<'tcx>(
None => {
let mut func = clean_function(
cx,
item.owner_id.to_def_id(),
sig,
generics,
ParamsSrc::Body(body_id),
item.owner_id.to_def_id(),
);
clean_fn_decl_legacy_const_generics(&mut func, attrs);
FunctionItem(func)
Expand Down Expand Up @@ -1130,33 +1149,48 @@ enum ParamsSrc<'tcx> {

fn clean_function<'tcx>(
cx: &mut DocContext<'tcx>,
def_id: DefId,
sig: &hir::FnSig<'tcx>,
generics: &hir::Generics<'tcx>,
params: ParamsSrc<'tcx>,
def_id: DefId,
) -> Box<Function> {
if sig.decl.opt_delegation_sig_id().is_some() {
return enter_impl_trait(cx, |cx| {
// A delegation item (`reuse path::method`) has no resolved signature in the
// HIR: its inputs and return type are `InferDelegation` nodes. The resolved
// signature and `impl Trait` bounds only exist on the ty side, but some
// delegation generics, like the generated `Self` in `reuse Trait::method`,
// are still only represented in HIR.
let mut function = inline::build_function(cx, def_id);
let hir_generics = clean_generics_without_impl_trait_params(generics, cx);

for param in hir_generics.params {
if !function.generics.params.iter().any(|existing| existing.def_id == param.def_id)
{
function.generics.params.push(param);
}
}
for pred in hir_generics.where_predicates {
if !function.generics.where_predicates.contains(&pred) {
function.generics.where_predicates.push(pred);
}
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

does this code reorder generic parameters under certain situations? it would be problematic if it did, since the order of generic parameters is important when using turbofish syntax. Even if there's no situation where this can cause problems currently, substituting the placeholders in a single pass would likely be more future proof than removing then re-adding them.


function
});
}

let (generics, decl) = enter_impl_trait(cx, |cx| {
// NOTE: Generics must be cleaned before params.
let generics = clean_generics(generics, cx);
let decl = if sig.decl.opt_delegation_sig_id().is_some() {
// A delegation item (`reuse path::method`) has no resolved signature in the
// HIR: its inputs and return type are `InferDelegation` nodes that clean to
// `_`, and an `async` header over that inferred return type would panic in
// `sugared_async_return_type`. The resolved signature only exists on the ty
// side, so clean that instead, exactly like an inlined item. This both fixes
// the rendered `-> _` / `self: _` and makes the async sugaring well-defined.
let sig = cx.tcx.fn_sig(def_id).instantiate_identity().skip_norm_wip();
clean_poly_fn_sig(cx, Some(def_id), sig)
} else {
let params = match params {
ParamsSrc::Body(body_id) => clean_params_via_body(cx, sig.decl.inputs, body_id),
// Let's not perpetuate anon params from Rust 2015; use `_` for them.
ParamsSrc::Idents(idents) => clean_params(cx, sig.decl.inputs, idents, |ident| {
Some(ident.map_or(kw::Underscore, |ident| ident.name))
}),
};
clean_fn_decl_with_params(cx, sig.decl, Some(&sig.header), params)
let params = match params {
ParamsSrc::Body(body_id) => clean_params_via_body(cx, sig.decl.inputs, body_id),
// Let's not perpetuate anon params from Rust 2015; use `_` for them.
ParamsSrc::Idents(idents) => clean_params(cx, sig.decl.inputs, idents, |ident| {
Some(ident.map_or(kw::Underscore, |ident| ident.name))
}),
};
let decl = clean_fn_decl_with_params(cx, sig.decl, Some(&sig.header), params);
(generics, decl)
});
Box::new(Function { decl, generics })
Expand Down Expand Up @@ -1289,16 +1323,16 @@ fn clean_trait_item<'tcx>(trait_item: &hir::TraitItem<'tcx>, cx: &mut DocContext
}
hir::TraitItemKind::Fn(ref sig, hir::TraitFn::Provided(body)) => {
let m =
clean_function(cx, sig, trait_item.generics, ParamsSrc::Body(body), local_did);
clean_function(cx, local_did, sig, trait_item.generics, ParamsSrc::Body(body));
MethodItem(m, Defaultness::from_trait_item(trait_item.defaultness))
}
hir::TraitItemKind::Fn(ref sig, hir::TraitFn::Required(idents)) => {
let m = clean_function(
cx,
local_did,
sig,
trait_item.generics,
ParamsSrc::Idents(idents),
local_did,
);
RequiredMethodItem(m, Defaultness::from_trait_item(trait_item.defaultness))
}
Expand Down Expand Up @@ -1340,7 +1374,7 @@ pub(crate) fn clean_impl_item<'tcx>(
type_: clean_ty(ty, cx),
})),
hir::ImplItemKind::Fn(ref sig, body) => {
let m = clean_function(cx, sig, impl_.generics, ParamsSrc::Body(body), local_did);
let m = clean_function(cx, local_did, sig, impl_.generics, ParamsSrc::Body(body));
let defaultness = match impl_.impl_kind {
hir::ImplItemImplKind::Inherent { .. } => hir::Defaultness::Final,
hir::ImplItemImplKind::Trait { defaultness, .. } => defaultness,
Expand Down Expand Up @@ -3279,7 +3313,7 @@ fn clean_maybe_renamed_foreign_item<'tcx>(
cx.with_param_env(def_id, |cx| {
let kind = match item.kind {
hir::ForeignItemKind::Fn(sig, idents, generics) => ForeignFunctionItem(
clean_function(cx, &sig, generics, ParamsSrc::Idents(idents), def_id),
clean_function(cx, def_id, &sig, generics, ParamsSrc::Idents(idents)),
sig.header.safety(),
),
hir::ForeignItemKind::Static(ty, mutability, safety) => ForeignStaticItem(
Expand Down
21 changes: 13 additions & 8 deletions src/librustdoc/clean/simplify.rs
Comment thread
cijiugechu marked this conversation as resolved.
Original file line number Diff line number Diff line change
Expand Up @@ -136,20 +136,25 @@ pub(crate) fn sized_bounds(cx: &mut DocContext<'_>, generics: &mut clean::Generi
// Note that associated types also have an implicit `Sized` bound but we
// don't actually know the set of associated types right here so that
// should be handled when cleaning associated types.
generics.where_predicates.retain(|pred| {
let WP::BoundPredicate { ty: clean::Generic(param), bounds, .. } = pred else {
generics.where_predicates.retain_mut(|pred| {
let WP::BoundPredicate { ty, bounds, .. } = pred else {
return true;
};

// FIXME(sized-hierarchy): Always skip `MetaSized` bounds so that only `?Sized`
// is shown and none of the new sizedness traits leak into documentation.
bounds.retain(|b| !b.is_meta_sized_bound(cx.tcx));
Copy link
Copy Markdown
Member

@fmease fmease Jun 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can bounds.len() > 1 with this PR? In my open PR #157262 I actually make rustdoc crash here if it has more than one bound.

View changes since the review


let clean::Generic(param) = ty else {
return !bounds.is_empty();
};

if bounds.iter().any(|b| b.is_sized_bound(cx.tcx)) {
sized_params.insert(*param);
false
} else if bounds.iter().any(|b| b.is_meta_sized_bound(cx.tcx)) {
// FIXME(sized-hierarchy): Always skip `MetaSized` bounds so that only `?Sized`
// is shown and none of the new sizedness traits leak into documentation.
false
bounds.retain(|b| !b.is_sized_bound(cx.tcx));
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this looks like a fix for an unrelated bug, could also make a regression test for that bug?

!bounds.is_empty()
} else {
true
!bounds.is_empty()
}
});

Expand Down
5 changes: 5 additions & 0 deletions tests/rustdoc-gui/go-to-collapsed-elem.goml
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
// This test ensures that when clicking on a link which leads to an item inside a collapsed element,
// the collapsed element will be expanded.

// We need to disable this check because `trait.impl/lib2/scroll_traits/trait.Iterator.js`
// doesn't exist.
fail-on-request-error: false
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why does it not exist? is this a bug? if so it at least deserves a FIXME, if not a github issue.


go-to: "file://" + |DOC_PATH| + "/test_docs/struct.Foo.html"
// We check that the implementors block is expanded.
assert-property: ("#implementations-list .implementors-toggle", {"open": "true"})
Expand Down
8 changes: 8 additions & 0 deletions tests/rustdoc-html/auxiliary/fn-delegation-impl-trait-aux.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
#![feature(fn_delegation)]
#![allow(incomplete_features)]

pub fn external(_: impl FnOnce()) {}

fn delegated_to(_: impl FnOnce()) {}

pub reuse delegated_to as delegated;
48 changes: 48 additions & 0 deletions tests/rustdoc-html/fn-delegation-impl-trait.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// Regression test for <https://github.com/rust-lang/rust/issues/155728>
// Make sure delegating functions with `impl Trait` in argument position works correctly.
//@ aux-crate:fn_delegation_impl_trait_aux=fn-delegation-impl-trait-aux.rs
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
//@ aux-crate:fn_delegation_impl_trait_aux=fn-delegation-impl-trait-aux.rs
//@ aux-crate:aux=fn-delegation-impl-trait-aux.rs

this should allow us to completely remove the extern crate line, since aux-crate uses the extern prelude.

#![feature(fn_delegation)]
#![allow(incomplete_features)]

Comment thread
cijiugechu marked this conversation as resolved.
extern crate fn_delegation_impl_trait_aux as aux;

fn foo(_: impl FnOnce()) {}

//@ has fn_delegation_impl_trait/fn.top_level.html '//pre[@class="rust item-decl"]' 'pub fn top_level(arg0: impl FnOnce())'
pub reuse foo as top_level;

pub struct S;

//@ has fn_delegation_impl_trait/struct.S.html '//*[@id="method.method"]' 'pub fn method(arg0: impl FnOnce())'
impl S {
pub reuse foo as method;
}

pub trait A {
fn f(&self, _: impl FnOnce()) {}
}

impl A for S {}

//@ has fn_delegation_impl_trait/struct.S.html '//*[@id="method.f"]' 'pub fn f(self: &S, arg1: impl FnOnce())'
//@ !has fn_delegation_impl_trait/struct.S.html '//*[@id="method.f"]' 'MetaSized'
impl S {
pub reuse <S as A>::f;
}

//@ has fn_delegation_impl_trait/trait.T.html '//*[@id="method.provided"]' 'fn provided(arg0: impl FnOnce())'
pub trait T {
reuse foo as provided;
}

//@ has fn_delegation_impl_trait/fn.cross_crate.html '//pre[@class="rust item-decl"]' 'pub fn cross_crate(arg0: impl FnOnce())'
pub reuse aux::external as cross_crate;

//@ has fn_delegation_impl_trait/fn.inlined_cross_crate_delegated.html '//pre[@class="rust item-decl"]' 'pub fn inlined_cross_crate_delegated(arg0: impl FnOnce())'
#[doc(inline)]
pub use aux::delegated as inlined_cross_crate_delegated;

//@ has fn_delegation_impl_trait/fn.redelegated_cross_crate.html '//pre[@class="rust item-decl"]' 'pub fn redelegated_cross_crate(arg0: impl FnOnce())'
pub reuse aux::delegated as redelegated_cross_crate;

pub fn main() {}
Loading