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)

40024002

let (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-

};

40114003

let hir::FnRetTy::Return(hir::Ty { kind: hir::TyKind::OpaqueDef(def, ..), .. }) =

40124004

sig.decl.output

40134005

else {

@@ -4021,55 +4013,17 @@ impl<'tcx> TypeErrCtxtExt<'tcx> for TypeErrCtxt<'_, 'tcx> {

40214013

return;

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 {

40334023

return;

40344024

};

4035402540364026

let 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(

40744028

format!(

40754029

"`{auto_trait}` can be made part of the associated future's \

@@ -4321,3 +4275,65 @@ impl<'tcx> TypeFolder<TyCtxt<'tcx>> for ReplaceImplTraitFolder<'tcx> {

43214275

self.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+

}