Add async_fn_in_trait lint · rust-lang/rust@ec79720
@@ -4000,14 +4000,6 @@ impl<'tcx> TypeErrCtxtExt<'tcx> for TypeErrCtxt<'_, 'tcx> {
4000400040014001// ... whose signature is `async` (i.e. this is an AFIT)
40024002let (sig, body) = item.expect_fn();
4003-let hir::IsAsync::Async(async_span) = sig.header.asyncness else {
4004-return;
4005-};
4006-let Ok(async_span) =
4007-self.tcx.sess.source_map().span_extend_while(async_span, |c| c.is_whitespace())
4008-else {
4009-return;
4010-};
40114003let hir::FnRetTy::Return(hir::Ty { kind: hir::TyKind::OpaqueDef(def, ..), .. }) =
40124004 sig.decl.output
40134005else {
@@ -4021,55 +4013,17 @@ impl<'tcx> TypeErrCtxtExt<'tcx> for TypeErrCtxt<'_, 'tcx> {
40214013return;
40224014}
402340154024-let future = self.tcx.hir().item(*def).expect_opaque_ty();
4025-let Some(hir::GenericBound::LangItemTrait(_, _, _, generics)) = future.bounds.get(0) else {
4026-// `async fn` should always lower to a lang item bound... but don't ICE.
4027-return;
4028-};
4029-let Some(hir::TypeBindingKind::Equality { term: hir::Term::Ty(future_output_ty) }) =
4030- generics.bindings.get(0).map(|binding| binding.kind)
4031-else {
4032-// Also should never happen.
4016+let Some(sugg) = suggest_desugaring_async_fn_to_impl_future_in_trait(
4017+self.tcx,
4018+*sig,
4019+*body,
4020+ opaque_def_id.expect_local(),
4021+&format!(" + {auto_trait}"),
4022+) else {
40334023return;
40344024};
4035402540364026let function_name = self.tcx.def_path_str(fn_def_id);
4037-4038-let mut sugg = if future_output_ty.span.is_empty() {
4039-vec![
4040-(async_span, String::new()),
4041-(
4042- future_output_ty.span,
4043- format!(" -> impl std::future::Future<Output = ()> + {auto_trait}"),
4044-),
4045-]
4046-} else {
4047-vec![
4048-(
4049- future_output_ty.span.shrink_to_lo(),
4050-"impl std::future::Future<Output = ".to_owned(),
4051-),
4052-(future_output_ty.span.shrink_to_hi(), format!("> + {auto_trait}")),
4053-(async_span, String::new()),
4054-]
4055-};
4056-4057-// If there's a body, we also need to wrap it in `async {}`
4058-if let hir::TraitFn::Provided(body) = body {
4059-let body = self.tcx.hir().body(*body);
4060-let body_span = body.value.span;
4061-let body_span_without_braces =
4062- body_span.with_lo(body_span.lo() + BytePos(1)).with_hi(body_span.hi() - BytePos(1));
4063-if body_span_without_braces.is_empty() {
4064- sugg.push((body_span_without_braces, " async {} ".to_owned()));
4065-} else {
4066- sugg.extend([
4067-(body_span_without_braces.shrink_to_lo(), "async {".to_owned()),
4068-(body_span_without_braces.shrink_to_hi(), "} ".to_owned()),
4069-]);
4070-}
4071-}
4072-40734027 err.multipart_suggestion(
40744028format!(
40754029"`{auto_trait}` can be made part of the associated future's \
@@ -4321,3 +4275,65 @@ impl<'tcx> TypeFolder<TyCtxt<'tcx>> for ReplaceImplTraitFolder<'tcx> {
43214275self.tcx
43224276}
43234277}
4278+4279+pub fn suggest_desugaring_async_fn_to_impl_future_in_trait<'tcx>(
4280+tcx: TyCtxt<'tcx>,
4281+sig: hir::FnSig<'tcx>,
4282+body: hir::TraitFn<'tcx>,
4283+opaque_def_id: LocalDefId,
4284+add_bounds: &str,
4285+) -> Option<Vec<(Span, String)>> {
4286+let hir::IsAsync::Async(async_span) = sig.header.asyncness else {
4287+return None;
4288+};
4289+let Ok(async_span) = tcx.sess.source_map().span_extend_while(async_span, |c| c.is_whitespace())
4290+else {
4291+return None;
4292+};
4293+4294+let future = tcx.hir().get_by_def_id(opaque_def_id).expect_item().expect_opaque_ty();
4295+let Some(hir::GenericBound::LangItemTrait(_, _, _, generics)) = future.bounds.get(0) else {
4296+// `async fn` should always lower to a lang item bound... but don't ICE.
4297+return None;
4298+};
4299+let Some(hir::TypeBindingKind::Equality { term: hir::Term::Ty(future_output_ty) }) =
4300+ generics.bindings.get(0).map(|binding| binding.kind)
4301+else {
4302+// Also should never happen.
4303+return None;
4304+};
4305+4306+let mut sugg = if future_output_ty.span.is_empty() {
4307+vec![
4308+(async_span, String::new()),
4309+(
4310+ future_output_ty.span,
4311+ format!(" -> impl std::future::Future<Output = ()>{add_bounds}"),
4312+),
4313+]
4314+} else {
4315+vec![
4316+(future_output_ty.span.shrink_to_lo(), "impl std::future::Future<Output = ".to_owned()),
4317+(future_output_ty.span.shrink_to_hi(), format!(">{add_bounds}")),
4318+(async_span, String::new()),
4319+]
4320+};
4321+4322+// If there's a body, we also need to wrap it in `async {}`
4323+if let hir::TraitFn::Provided(body) = body {
4324+let body = tcx.hir().body(body);
4325+let body_span = body.value.span;
4326+let body_span_without_braces =
4327+ body_span.with_lo(body_span.lo() + BytePos(1)).with_hi(body_span.hi() - BytePos(1));
4328+if body_span_without_braces.is_empty() {
4329+ sugg.push((body_span_without_braces, " async {} ".to_owned()));
4330+} else {
4331+ sugg.extend([
4332+(body_span_without_braces.shrink_to_lo(), "async {".to_owned()),
4333+(body_span_without_braces.shrink_to_hi(), "} ".to_owned()),
4334+]);
4335+}
4336+}
4337+4338+Some(sugg)
4339+}