diff --git a/src/librustdoc/clean/mod.rs b/src/librustdoc/clean/mod.rs index cf6deebe8e1d7..236d959b7e190 100644 --- a/src/librustdoc/clean/mod.rs +++ b/src/librustdoc/clean/mod.rs @@ -1089,7 +1089,13 @@ fn clean_fn_or_proc_macro<'tcx>( match macro_kind { Some(kind) => clean_proc_macro(item, name, kind, cx.tcx), None => { - let mut func = clean_function(cx, sig, generics, ParamsSrc::Body(body_id)); + let mut func = clean_function( + cx, + sig, + generics, + ParamsSrc::Body(body_id), + item.owner_id.to_def_id(), + ); clean_fn_decl_legacy_const_generics(&mut func, attrs); FunctionItem(func) } @@ -1127,18 +1133,30 @@ fn clean_function<'tcx>( sig: &hir::FnSig<'tcx>, generics: &hir::Generics<'tcx>, params: ParamsSrc<'tcx>, + def_id: DefId, ) -> Box { let (generics, decl) = enter_impl_trait(cx, |cx| { // NOTE: Generics must be cleaned before params. let generics = clean_generics(generics, cx); - 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 = 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 decl = clean_fn_decl_with_params(cx, sig.decl, Some(&sig.header), params); (generics, decl) }); Box::new(Function { decl, generics }) @@ -1270,11 +1288,18 @@ fn clean_trait_item<'tcx>(trait_item: &hir::TraitItem<'tcx>, cx: &mut DocContext RequiredAssocConstItem(generics, Box::new(clean_ty(ty, cx))) } hir::TraitItemKind::Fn(ref sig, hir::TraitFn::Provided(body)) => { - let m = clean_function(cx, sig, trait_item.generics, ParamsSrc::Body(body)); + let m = + clean_function(cx, sig, trait_item.generics, ParamsSrc::Body(body), local_did); MethodItem(m, Defaultness::from_trait_item(trait_item.defaultness)) } hir::TraitItemKind::Fn(ref sig, hir::TraitFn::Required(idents)) => { - let m = clean_function(cx, sig, trait_item.generics, ParamsSrc::Idents(idents)); + let m = clean_function( + cx, + sig, + trait_item.generics, + ParamsSrc::Idents(idents), + local_did, + ); RequiredMethodItem(m, Defaultness::from_trait_item(trait_item.defaultness)) } hir::TraitItemKind::Type(bounds, Some(default)) => { @@ -1315,7 +1340,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)); + let m = clean_function(cx, sig, impl_.generics, ParamsSrc::Body(body), local_did); let defaultness = match impl_.impl_kind { hir::ImplItemImplKind::Inherent { .. } => hir::Defaultness::Final, hir::ImplItemImplKind::Trait { defaultness, .. } => defaultness, @@ -3254,7 +3279,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)), + clean_function(cx, &sig, generics, ParamsSrc::Idents(idents), def_id), sig.header.safety(), ), hir::ForeignItemKind::Static(ty, mutability, safety) => ForeignStaticItem( diff --git a/tests/rustdoc-html/async/async-fn-delegation.rs b/tests/rustdoc-html/async/async-fn-delegation.rs new file mode 100644 index 0000000000000..7d891ac8be637 --- /dev/null +++ b/tests/rustdoc-html/async/async-fn-delegation.rs @@ -0,0 +1,42 @@ +//@ edition: 2021 + +// Regression test for . +// +// rustdoc used to ICE with "unexpected async fn return type" when cleaning a +// delegated (`reuse`) async fn: the delegation's HIR signature is unresolved +// (`InferDelegation`), so its return type cleaned to `_` even though the header +// is `async`, and unconditionally sugaring that inferred type panicked. +// +// We now clean the resolved (ty-side) signature for delegation items, like we +// already do for inlined items. That both avoids the ICE and renders the real +// return type and `self` parameter instead of `-> _` / `self: _`. +// +// Note: the `` generic on the free-function variants is a pre-existing +// quirk of how delegation generics are rendered (plain sync delegation prints it +// too); it is tracked separately and is not what this test is about. + +#![feature(fn_delegation)] +#![allow(incomplete_features)] +#![crate_name = "async_delegation"] + +pub trait Trait { + async fn unit(&self) {} + async fn nonunit(&self) -> i32 { + 0 + } +} + +//@ has async_delegation/fn.unit.html '//pre[@class="rust item-decl"]' 'pub async fn unit(&self)' +pub reuse Trait::unit; +//@ has async_delegation/fn.nonunit.html '//pre[@class="rust item-decl"]' 'pub async fn nonunit(&self) -> i32' +pub reuse Trait::nonunit; + +pub struct S; +impl Trait for S {} + +//@ has async_delegation/struct.S.html '//*[@class="code-header"]' 'pub async fn unit(self: &S)' +//@ has async_delegation/struct.S.html '//*[@class="code-header"]' 'pub async fn nonunit(self: &S) -> i32' +impl S { + pub reuse Trait::unit { self } + pub reuse Trait::nonunit { self } +}