From 58be455e788f63aa76f5635ff193556fccec01a3 Mon Sep 17 00:00:00 2001 From: cijiugechu Date: Sun, 17 May 2026 11:50:11 +0800 Subject: [PATCH] rustdoc: fix delegated impl signatures --- src/librustdoc/clean/mod.rs | 82 +++++++++++++------ src/librustdoc/clean/simplify.rs | 21 +++-- tests/rustdoc-gui/go-to-collapsed-elem.goml | 5 ++ .../auxiliary/fn-delegation-impl-trait-aux.rs | 8 ++ .../rustdoc-html/fn-delegation-impl-trait.rs | 48 +++++++++++ 5 files changed, 132 insertions(+), 32 deletions(-) create mode 100644 tests/rustdoc-html/auxiliary/fn-delegation-impl-trait-aux.rs create mode 100644 tests/rustdoc-html/fn-delegation-impl-trait.rs diff --git a/src/librustdoc/clean/mod.rs b/src/librustdoc/clean/mod.rs index 42845124eda50..35e14b4c1f80c 100644 --- a/src/librustdoc/clean/mod.rs +++ b/src/librustdoc/clean/mod.rs @@ -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::>(); + + 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)) } @@ -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) @@ -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 { + 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); + } + } + + 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 }) @@ -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)) } @@ -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, @@ -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( diff --git a/src/librustdoc/clean/simplify.rs b/src/librustdoc/clean/simplify.rs index d529f2300e0c6..de8d970fdd70f 100644 --- a/src/librustdoc/clean/simplify.rs +++ b/src/librustdoc/clean/simplify.rs @@ -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)); + + 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)); + !bounds.is_empty() } else { - true + !bounds.is_empty() } }); diff --git a/tests/rustdoc-gui/go-to-collapsed-elem.goml b/tests/rustdoc-gui/go-to-collapsed-elem.goml index e56e7ba08cd81..a2e24f1b7ce1f 100644 --- a/tests/rustdoc-gui/go-to-collapsed-elem.goml +++ b/tests/rustdoc-gui/go-to-collapsed-elem.goml @@ -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 + 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"}) diff --git a/tests/rustdoc-html/auxiliary/fn-delegation-impl-trait-aux.rs b/tests/rustdoc-html/auxiliary/fn-delegation-impl-trait-aux.rs new file mode 100644 index 0000000000000..1e58cc40b5414 --- /dev/null +++ b/tests/rustdoc-html/auxiliary/fn-delegation-impl-trait-aux.rs @@ -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; diff --git a/tests/rustdoc-html/fn-delegation-impl-trait.rs b/tests/rustdoc-html/fn-delegation-impl-trait.rs new file mode 100644 index 0000000000000..790c8d30d98e7 --- /dev/null +++ b/tests/rustdoc-html/fn-delegation-impl-trait.rs @@ -0,0 +1,48 @@ +// Regression test for +// 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 +#![feature(fn_delegation)] +#![allow(incomplete_features)] + +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 ::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() {}