1
Fork 0

Auto merge of #115864 - compiler-errors:rpitit-sugg, r=estebank

Suggest desugaring to return-position `impl Future` when an `async fn` in trait fails an auto trait bound

First commit allows us to store the span of the `async` keyword in HIR.

Second commit implements a suggestion to desugar an `async fn` to a return-position `impl Future` in trait to slightly improve the `Send` situation being discussed in #115822.

This suggestion is only made when `#![feature(return_type_notation)]` is not enabled -- if it is, we should instead suggest an appropriate where-clause bound.
This commit is contained in:
bors 2023-09-21 21:12:32 +00:00
commit b3aa8e7168
31 changed files with 348 additions and 54 deletions

View file

@ -1308,7 +1308,7 @@ impl<'hir> LoweringContext<'_, 'hir> {
fn lower_asyncness(&mut self, a: Async) -> hir::IsAsync { fn lower_asyncness(&mut self, a: Async) -> hir::IsAsync {
match a { match a {
Async::Yes { .. } => hir::IsAsync::Async, Async::Yes { span, .. } => hir::IsAsync::Async(span),
Async::No => hir::IsAsync::NotAsync, Async::No => hir::IsAsync::NotAsync,
} }
} }

View file

@ -302,7 +302,7 @@ impl<'tcx> MirBorrowckCtxt<'_, 'tcx> {
if free_region.bound_region.is_named() { if free_region.bound_region.is_named() {
// A named region that is actually named. // A named region that is actually named.
Some(RegionName { name, source: RegionNameSource::NamedFreeRegion(span) }) Some(RegionName { name, source: RegionNameSource::NamedFreeRegion(span) })
} else if let hir::IsAsync::Async = tcx.asyncness(self.mir_hir_id().owner) { } else if tcx.asyncness(self.mir_hir_id().owner).is_async() {
// If we spuriously thought that the region is named, we should let the // If we spuriously thought that the region is named, we should let the
// system generate a true name for error messages. Currently this can // system generate a true name for error messages. Currently this can
// happen if we have an elided name in an async fn for example: the // happen if we have an elided name in an async fn for example: the

View file

@ -2853,13 +2853,13 @@ impl ImplicitSelfKind {
#[derive(Copy, Clone, PartialEq, Eq, Encodable, Decodable, Debug)] #[derive(Copy, Clone, PartialEq, Eq, Encodable, Decodable, Debug)]
#[derive(HashStable_Generic)] #[derive(HashStable_Generic)]
pub enum IsAsync { pub enum IsAsync {
Async, Async(Span),
NotAsync, NotAsync,
} }
impl IsAsync { impl IsAsync {
pub fn is_async(self) -> bool { pub fn is_async(self) -> bool {
self == IsAsync::Async matches!(self, IsAsync::Async(_))
} }
} }
@ -3296,7 +3296,7 @@ pub struct FnHeader {
impl FnHeader { impl FnHeader {
pub fn is_async(&self) -> bool { pub fn is_async(&self) -> bool {
matches!(&self.asyncness, IsAsync::Async) matches!(&self.asyncness, IsAsync::Async(_))
} }
pub fn is_const(&self) -> bool { pub fn is_const(&self) -> bool {
@ -4091,10 +4091,10 @@ mod size_asserts {
static_assert_size!(GenericBound<'_>, 48); static_assert_size!(GenericBound<'_>, 48);
static_assert_size!(Generics<'_>, 56); static_assert_size!(Generics<'_>, 56);
static_assert_size!(Impl<'_>, 80); static_assert_size!(Impl<'_>, 80);
static_assert_size!(ImplItem<'_>, 80); static_assert_size!(ImplItem<'_>, 88);
static_assert_size!(ImplItemKind<'_>, 32); static_assert_size!(ImplItemKind<'_>, 40);
static_assert_size!(Item<'_>, 80); static_assert_size!(Item<'_>, 88);
static_assert_size!(ItemKind<'_>, 48); static_assert_size!(ItemKind<'_>, 56);
static_assert_size!(Local<'_>, 64); static_assert_size!(Local<'_>, 64);
static_assert_size!(Param<'_>, 32); static_assert_size!(Param<'_>, 32);
static_assert_size!(Pat<'_>, 72); static_assert_size!(Pat<'_>, 72);
@ -4105,8 +4105,8 @@ mod size_asserts {
static_assert_size!(Res, 12); static_assert_size!(Res, 12);
static_assert_size!(Stmt<'_>, 32); static_assert_size!(Stmt<'_>, 32);
static_assert_size!(StmtKind<'_>, 16); static_assert_size!(StmtKind<'_>, 16);
static_assert_size!(TraitItem<'_>, 80); static_assert_size!(TraitItem<'_>, 88);
static_assert_size!(TraitItemKind<'_>, 40); static_assert_size!(TraitItemKind<'_>, 48);
static_assert_size!(Ty<'_>, 48); static_assert_size!(Ty<'_>, 48);
static_assert_size!(TyKind<'_>, 32); static_assert_size!(TyKind<'_>, 32);
// tidy-alphabetical-end // tidy-alphabetical-end

View file

@ -595,7 +595,7 @@ fn compare_asyncness<'tcx>(
trait_m: ty::AssocItem, trait_m: ty::AssocItem,
delay: bool, delay: bool,
) -> Result<(), ErrorGuaranteed> { ) -> Result<(), ErrorGuaranteed> {
if tcx.asyncness(trait_m.def_id) == hir::IsAsync::Async { if tcx.asyncness(trait_m.def_id).is_async() {
match tcx.fn_sig(impl_m.def_id).skip_binder().skip_binder().output().kind() { match tcx.fn_sig(impl_m.def_id).skip_binder().skip_binder().output().kind() {
ty::Alias(ty::Opaque, ..) => { ty::Alias(ty::Opaque, ..) => {
// allow both `async fn foo()` and `fn foo() -> impl Future` // allow both `async fn foo()` and `fn foo() -> impl Future`

View file

@ -112,7 +112,7 @@ fn check_main_fn_ty(tcx: TyCtxt<'_>, main_def_id: DefId) {
} }
let main_asyncness = tcx.asyncness(main_def_id); let main_asyncness = tcx.asyncness(main_def_id);
if let hir::IsAsync::Async = main_asyncness { if main_asyncness.is_async() {
let asyncness_span = main_fn_asyncness_span(tcx, main_def_id); let asyncness_span = main_fn_asyncness_span(tcx, main_def_id);
tcx.sess.emit_err(errors::MainFunctionAsync { span: main_span, asyncness: asyncness_span }); tcx.sess.emit_err(errors::MainFunctionAsync { span: main_span, asyncness: asyncness_span });
error = true; error = true;
@ -212,7 +212,7 @@ fn check_start_fn_ty(tcx: TyCtxt<'_>, start_def_id: DefId) {
}); });
error = true; error = true;
} }
if let hir::IsAsync::Async = sig.header.asyncness { if sig.header.asyncness.is_async() {
let span = tcx.def_span(it.owner_id); let span = tcx.def_span(it.owner_id);
tcx.sess.emit_err(errors::StartAsync { span: span }); tcx.sess.emit_err(errors::StartAsync { span: span });
error = true; error = true;

View file

@ -1213,7 +1213,7 @@ impl<'a, 'tcx> BoundVarContext<'a, 'tcx> {
&& let Some(generics) = self.tcx.hir().get_generics(self.tcx.local_parent(param_id)) && let Some(generics) = self.tcx.hir().get_generics(self.tcx.local_parent(param_id))
&& let Some(param) = generics.params.iter().find(|p| p.def_id == param_id) && let Some(param) = generics.params.iter().find(|p| p.def_id == param_id)
&& param.is_elided_lifetime() && param.is_elided_lifetime()
&& let hir::IsAsync::NotAsync = self.tcx.asyncness(lifetime_ref.hir_id.owner.def_id) && !self.tcx.asyncness(lifetime_ref.hir_id.owner.def_id).is_async()
&& !self.tcx.features().anonymous_lifetime_in_impl_trait && !self.tcx.features().anonymous_lifetime_in_impl_trait
{ {
let mut diag = rustc_session::parse::feature_err( let mut diag = rustc_session::parse::feature_err(

View file

@ -2304,7 +2304,7 @@ impl<'a> State<'a> {
match header.asyncness { match header.asyncness {
hir::IsAsync::NotAsync => {} hir::IsAsync::NotAsync => {}
hir::IsAsync::Async => self.word_nbsp("async"), hir::IsAsync::Async(_) => self.word_nbsp("async"),
} }
self.print_unsafety(header.unsafety); self.print_unsafety(header.unsafety);

View file

@ -987,10 +987,10 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
let bound_vars = self.tcx.late_bound_vars(fn_id); let bound_vars = self.tcx.late_bound_vars(fn_id);
let ty = self.tcx.erase_late_bound_regions(Binder::bind_with_vars(ty, bound_vars)); let ty = self.tcx.erase_late_bound_regions(Binder::bind_with_vars(ty, bound_vars));
let ty = match self.tcx.asyncness(fn_id.owner) { let ty = match self.tcx.asyncness(fn_id.owner) {
hir::IsAsync::Async => self.get_impl_future_output_ty(ty).unwrap_or_else(|| { ty::Asyncness::Yes => self.get_impl_future_output_ty(ty).unwrap_or_else(|| {
span_bug!(fn_decl.output.span(), "failed to get output type of async function") span_bug!(fn_decl.output.span(), "failed to get output type of async function")
}), }),
hir::IsAsync::NotAsync => ty, ty::Asyncness::No => ty,
}; };
let ty = self.normalize(expr.span, ty); let ty = self.normalize(expr.span, ty);
if self.can_coerce(found, ty) { if self.can_coerce(found, ty) {

View file

@ -41,7 +41,6 @@ use crate::{
}, },
EarlyContext, EarlyLintPass, LateContext, LateLintPass, Level, LintContext, EarlyContext, EarlyLintPass, LateContext, LateLintPass, Level, LintContext,
}; };
use hir::IsAsync;
use rustc_ast::attr; use rustc_ast::attr;
use rustc_ast::tokenstream::{TokenStream, TokenTree}; use rustc_ast::tokenstream::{TokenStream, TokenTree};
use rustc_ast::visit::{FnCtxt, FnKind}; use rustc_ast::visit::{FnCtxt, FnKind};
@ -1294,7 +1293,7 @@ impl<'tcx> LateLintPass<'tcx> for UngatedAsyncFnTrackCaller {
span: Span, span: Span,
def_id: LocalDefId, def_id: LocalDefId,
) { ) {
if fn_kind.asyncness() == IsAsync::Async if fn_kind.asyncness().is_async()
&& !cx.tcx.features().async_fn_track_caller && !cx.tcx.features().async_fn_track_caller
// Now, check if the function has the `#[track_caller]` attribute // Now, check if the function has the `#[track_caller]` attribute
&& let Some(attr) = cx.tcx.get_attr(def_id, sym::track_caller) && let Some(attr) = cx.tcx.get_attr(def_id, sym::track_caller)

View file

@ -439,7 +439,7 @@ define_tables! {
coerce_unsized_info: Table<DefIndex, LazyValue<ty::adjustment::CoerceUnsizedInfo>>, coerce_unsized_info: Table<DefIndex, LazyValue<ty::adjustment::CoerceUnsizedInfo>>,
mir_const_qualif: Table<DefIndex, LazyValue<mir::ConstQualifs>>, mir_const_qualif: Table<DefIndex, LazyValue<mir::ConstQualifs>>,
rendered_const: Table<DefIndex, LazyValue<String>>, rendered_const: Table<DefIndex, LazyValue<String>>,
asyncness: Table<DefIndex, hir::IsAsync>, asyncness: Table<DefIndex, ty::Asyncness>,
fn_arg_names: Table<DefIndex, LazyArray<Ident>>, fn_arg_names: Table<DefIndex, LazyArray<Ident>>,
generator_kind: Table<DefIndex, LazyValue<hir::GeneratorKind>>, generator_kind: Table<DefIndex, LazyValue<hir::GeneratorKind>>,
trait_def: Table<DefIndex, LazyValue<ty::TraitDef>>, trait_def: Table<DefIndex, LazyValue<ty::TraitDef>>,

View file

@ -205,9 +205,9 @@ fixed_size_enum! {
} }
fixed_size_enum! { fixed_size_enum! {
hir::IsAsync { ty::Asyncness {
( NotAsync ) ( Yes )
( Async ) ( No )
} }
} }

View file

@ -265,6 +265,7 @@ trivial! {
rustc_middle::ty::adjustment::CoerceUnsizedInfo, rustc_middle::ty::adjustment::CoerceUnsizedInfo,
rustc_middle::ty::AssocItem, rustc_middle::ty::AssocItem,
rustc_middle::ty::AssocItemContainer, rustc_middle::ty::AssocItemContainer,
rustc_middle::ty::Asyncness,
rustc_middle::ty::BoundVariableKind, rustc_middle::ty::BoundVariableKind,
rustc_middle::ty::DeducedParamAttrs, rustc_middle::ty::DeducedParamAttrs,
rustc_middle::ty::Destructor, rustc_middle::ty::Destructor,

View file

@ -731,7 +731,7 @@ rustc_queries! {
separate_provide_extern separate_provide_extern
} }
query asyncness(key: DefId) -> hir::IsAsync { query asyncness(key: DefId) -> ty::Asyncness {
desc { |tcx| "checking if the function is async: `{}`", tcx.def_path_str(key) } desc { |tcx| "checking if the function is async: `{}`", tcx.def_path_str(key) }
separate_provide_extern separate_provide_extern
} }

View file

@ -280,6 +280,19 @@ impl fmt::Display for ImplPolarity {
} }
} }
#[derive(Copy, Clone, PartialEq, Eq, Hash, TyEncodable, TyDecodable, HashStable, Debug)]
#[derive(TypeFoldable, TypeVisitable)]
pub enum Asyncness {
Yes,
No,
}
impl Asyncness {
pub fn is_async(self) -> bool {
matches!(self, Asyncness::Yes)
}
}
#[derive(Clone, Debug, PartialEq, Eq, Copy, Hash, Encodable, Decodable, HashStable)] #[derive(Clone, Debug, PartialEq, Eq, Copy, Hash, Encodable, Decodable, HashStable)]
pub enum Visibility<Id = LocalDefId> { pub enum Visibility<Id = LocalDefId> {
/// Visible everywhere (including in other crates). /// Visible everywhere (including in other crates).

View file

@ -62,6 +62,7 @@ trivially_parameterized_over_tcx! {
crate::middle::resolve_bound_vars::ObjectLifetimeDefault, crate::middle::resolve_bound_vars::ObjectLifetimeDefault,
crate::mir::ConstQualifs, crate::mir::ConstQualifs,
ty::AssocItemContainer, ty::AssocItemContainer,
ty::Asyncness,
ty::DeducedParamAttrs, ty::DeducedParamAttrs,
ty::Generics, ty::Generics,
ty::ImplPolarity, ty::ImplPolarity,

View file

@ -987,6 +987,7 @@ impl<'tcx> TypeErrCtxtExt<'tcx> for TypeErrCtxt<'_, 'tcx> {
} }
self.explain_hrtb_projection(&mut err, trait_predicate, obligation.param_env, &obligation.cause); self.explain_hrtb_projection(&mut err, trait_predicate, obligation.param_env, &obligation.cause);
self.suggest_desugaring_async_fn_in_trait(&mut err, trait_ref);
// Return early if the trait is Debug or Display and the invocation // Return early if the trait is Debug or Display and the invocation
// originates within a standard library macro, because the output // originates within a standard library macro, because the output

View file

@ -104,7 +104,9 @@ impl<'tcx> TypeErrCtxtExt<'tcx> for TypeErrCtxt<'_, 'tcx> {
hir::Node::Item(hir::Item { kind: hir::ItemKind::Fn(sig, _, body_id), .. }) => { hir::Node::Item(hir::Item { kind: hir::ItemKind::Fn(sig, _, body_id), .. }) => {
self.describe_generator(*body_id).or_else(|| { self.describe_generator(*body_id).or_else(|| {
Some(match sig.header { Some(match sig.header {
hir::FnHeader { asyncness: hir::IsAsync::Async, .. } => "an async function", hir::FnHeader { asyncness: hir::IsAsync::Async(_), .. } => {
"an async function"
}
_ => "a function", _ => "a function",
}) })
}) })
@ -118,7 +120,7 @@ impl<'tcx> TypeErrCtxtExt<'tcx> for TypeErrCtxt<'_, 'tcx> {
.. ..
}) => self.describe_generator(*body_id).or_else(|| { }) => self.describe_generator(*body_id).or_else(|| {
Some(match sig.header { Some(match sig.header {
hir::FnHeader { asyncness: hir::IsAsync::Async, .. } => "an async method", hir::FnHeader { asyncness: hir::IsAsync::Async(_), .. } => "an async method",
_ => "a method", _ => "a method",
}) })
}), }),

View file

@ -414,6 +414,12 @@ pub trait TypeErrCtxtExt<'tcx> {
param_env: ty::ParamEnv<'tcx>, param_env: ty::ParamEnv<'tcx>,
cause: &ObligationCause<'tcx>, cause: &ObligationCause<'tcx>,
); );
fn suggest_desugaring_async_fn_in_trait(
&self,
err: &mut Diagnostic,
trait_ref: ty::PolyTraitRef<'tcx>,
);
} }
fn predicate_constraint(generics: &hir::Generics<'_>, pred: ty::Predicate<'_>) -> (Span, String) { fn predicate_constraint(generics: &hir::Generics<'_>, pred: ty::Predicate<'_>) -> (Span, String) {
@ -4100,6 +4106,136 @@ impl<'tcx> TypeErrCtxtExt<'tcx> for TypeErrCtxt<'_, 'tcx> {
}); });
} }
} }
fn suggest_desugaring_async_fn_in_trait(
&self,
err: &mut Diagnostic,
trait_ref: ty::PolyTraitRef<'tcx>,
) {
// Don't suggest if RTN is active -- we should prefer a where-clause bound instead.
if self.tcx.features().return_type_notation {
return;
}
let trait_def_id = trait_ref.def_id();
// Only suggest specifying auto traits
if !self.tcx.trait_is_auto(trait_def_id) {
return;
}
// Look for an RPITIT
let ty::Alias(ty::Projection, alias_ty) = trait_ref.self_ty().skip_binder().kind() else {
return;
};
let Some(ty::ImplTraitInTraitData::Trait { fn_def_id, opaque_def_id }) =
self.tcx.opt_rpitit_info(alias_ty.def_id)
else {
return;
};
let auto_trait = self.tcx.def_path_str(trait_def_id);
// ... which is a local function
let Some(fn_def_id) = fn_def_id.as_local() else {
// If it's not local, we can at least mention that the method is async, if it is.
if self.tcx.asyncness(fn_def_id).is_async() {
err.span_note(
self.tcx.def_span(fn_def_id),
format!(
"`{}::{}` is an `async fn` in trait, which does not \
automatically imply that its future is `{auto_trait}`",
alias_ty.trait_ref(self.tcx),
self.tcx.item_name(fn_def_id)
),
);
}
return;
};
let Some(hir::Node::TraitItem(item)) = self.tcx.hir().find_by_def_id(fn_def_id) else {
return;
};
// ... whose signature is `async` (i.e. this is an AFIT)
let (sig, body) = item.expect_fn();
let hir::IsAsync::Async(async_span) = sig.header.asyncness else {
return;
};
let Ok(async_span) =
self.tcx.sess.source_map().span_extend_while(async_span, |c| c.is_whitespace())
else {
return;
};
let hir::FnRetTy::Return(hir::Ty { kind: hir::TyKind::OpaqueDef(def, ..), .. }) =
sig.decl.output
else {
// This should never happen, but let's not ICE.
return;
};
// Check that this is *not* a nested `impl Future` RPIT in an async fn
// (i.e. `async fn foo() -> impl Future`)
if def.owner_id.to_def_id() != opaque_def_id {
return;
}
let future = self.tcx.hir().item(*def).expect_opaque_ty();
let Some(hir::GenericBound::LangItemTrait(_, _, _, generics)) = future.bounds.get(0) else {
// `async fn` should always lower to a lang item bound... but don't ICE.
return;
};
let Some(hir::TypeBindingKind::Equality { term: hir::Term::Ty(future_output_ty) }) =
generics.bindings.get(0).map(|binding| binding.kind)
else {
// Also should never happen.
return;
};
let function_name = self.tcx.def_path_str(fn_def_id);
let mut sugg = if future_output_ty.span.is_empty() {
vec![
(async_span, String::new()),
(
future_output_ty.span,
format!(" -> impl std::future::Future<Output = ()> + {auto_trait}"),
),
]
} else {
vec![
(
future_output_ty.span.shrink_to_lo(),
"impl std::future::Future<Output = ".to_owned(),
),
(future_output_ty.span.shrink_to_hi(), format!("> + {auto_trait}")),
(async_span, String::new()),
]
};
// If there's a body, we also need to wrap it in `async {}`
if let hir::TraitFn::Provided(body) = body {
let body = self.tcx.hir().body(*body);
let body_span = body.value.span;
let body_span_without_braces =
body_span.with_lo(body_span.lo() + BytePos(1)).with_hi(body_span.hi() - BytePos(1));
if body_span_without_braces.is_empty() {
sugg.push((body_span_without_braces, " async {} ".to_owned()));
} else {
sugg.extend([
(body_span_without_braces.shrink_to_lo(), "async {".to_owned()),
(body_span_without_braces.shrink_to_hi(), "} ".to_owned()),
]);
}
}
err.multipart_suggestion(
format!(
"`{auto_trait}` can be made part of the associated future's \
guarantees for all implementations of `{function_name}`"
),
sugg,
Applicability::MachineApplicable,
);
}
} }
/// Add a hint to add a missing borrow or remove an unnecessary one. /// Add a hint to add a missing borrow or remove an unnecessary one.

View file

@ -296,9 +296,12 @@ fn issue33140_self_ty(tcx: TyCtxt<'_>, def_id: DefId) -> Option<EarlyBinder<Ty<'
} }
/// Check if a function is async. /// Check if a function is async.
fn asyncness(tcx: TyCtxt<'_>, def_id: LocalDefId) -> hir::IsAsync { fn asyncness(tcx: TyCtxt<'_>, def_id: LocalDefId) -> ty::Asyncness {
let node = tcx.hir().get_by_def_id(def_id); let node = tcx.hir().get_by_def_id(def_id);
node.fn_sig().map_or(hir::IsAsync::NotAsync, |sig| sig.header.asyncness) node.fn_sig().map_or(ty::Asyncness::No, |sig| match sig.header.asyncness {
hir::IsAsync::Async(_) => ty::Asyncness::Yes,
hir::IsAsync::NotAsync => ty::Asyncness::No,
})
} }
fn unsizing_params_for_adt<'tcx>(tcx: TyCtxt<'tcx>, def_id: DefId) -> BitSet<u32> { fn unsizing_params_for_adt<'tcx>(tcx: TyCtxt<'tcx>, def_id: DefId) -> BitSet<u32> {

View file

@ -31,7 +31,7 @@ use rustc_resolve::rustdoc::{
use rustc_session::Session; use rustc_session::Session;
use rustc_span::hygiene::MacroKind; use rustc_span::hygiene::MacroKind;
use rustc_span::symbol::{kw, sym, Ident, Symbol}; use rustc_span::symbol::{kw, sym, Ident, Symbol};
use rustc_span::{self, FileName, Loc}; use rustc_span::{self, FileName, Loc, DUMMY_SP};
use rustc_target::abi::VariantIdx; use rustc_target::abi::VariantIdx;
use rustc_target::spec::abi::Abi; use rustc_target::spec::abi::Abi;
@ -622,7 +622,7 @@ impl Item {
fn build_fn_header( fn build_fn_header(
def_id: DefId, def_id: DefId,
tcx: TyCtxt<'_>, tcx: TyCtxt<'_>,
asyncness: hir::IsAsync, asyncness: ty::Asyncness,
) -> hir::FnHeader { ) -> hir::FnHeader {
let sig = tcx.fn_sig(def_id).skip_binder(); let sig = tcx.fn_sig(def_id).skip_binder();
let constness = let constness =
@ -631,6 +631,10 @@ impl Item {
} else { } else {
hir::Constness::NotConst hir::Constness::NotConst
}; };
let asyncness = match asyncness {
ty::Asyncness::Yes => hir::IsAsync::Async(DUMMY_SP),
ty::Asyncness::No => hir::IsAsync::NotAsync,
};
hir::FnHeader { unsafety: sig.unsafety(), abi: sig.abi(), constness, asyncness } hir::FnHeader { unsafety: sig.unsafety(), abi: sig.abi(), constness, asyncness }
} }
let header = match *self.kind { let header = match *self.kind {

View file

@ -1599,7 +1599,7 @@ impl PrintWithSpace for hir::Unsafety {
impl PrintWithSpace for hir::IsAsync { impl PrintWithSpace for hir::IsAsync {
fn print_with_space(&self) -> &str { fn print_with_space(&self) -> &str {
match self { match self {
hir::IsAsync::Async => "async ", hir::IsAsync::Async(_) => "async ",
hir::IsAsync::NotAsync => "", hir::IsAsync::NotAsync => "",
} }
} }

View file

@ -10,6 +10,7 @@ use rustc_hir::intravisit::{Visitor as HirVisitor, Visitor};
use rustc_lint::{LateContext, LateLintPass}; use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::hir::nested_filter; use rustc_middle::hir::nested_filter;
use rustc_middle::lint::in_external_macro; use rustc_middle::lint::in_external_macro;
use rustc_middle::ty;
use rustc_session::{declare_lint_pass, declare_tool_lint}; use rustc_session::{declare_lint_pass, declare_tool_lint};
declare_clippy_lint! { declare_clippy_lint! {
@ -84,7 +85,7 @@ fn find_innermost_closure<'tcx>(
cx: &LateContext<'tcx>, cx: &LateContext<'tcx>,
mut expr: &'tcx hir::Expr<'tcx>, mut expr: &'tcx hir::Expr<'tcx>,
mut steps: usize, mut steps: usize,
) -> Option<(&'tcx hir::Expr<'tcx>, &'tcx hir::FnDecl<'tcx>, hir::IsAsync)> { ) -> Option<(&'tcx hir::Expr<'tcx>, &'tcx hir::FnDecl<'tcx>, ty::Asyncness)> {
let mut data = None; let mut data = None;
while let hir::ExprKind::Closure(closure) = expr.kind while let hir::ExprKind::Closure(closure) = expr.kind
@ -98,9 +99,9 @@ fn find_innermost_closure<'tcx>(
{ {
expr = body.value; expr = body.value;
data = Some((body.value, closure.fn_decl, if is_async_closure(body) { data = Some((body.value, closure.fn_decl, if is_async_closure(body) {
hir::IsAsync::Async ty::Asyncness::Yes
} else { } else {
hir::IsAsync::NotAsync ty::Asyncness::No
})); }));
steps -= 1; steps -= 1;
} }

View file

@ -90,7 +90,7 @@ use rustc_hir::intravisit::{walk_expr, FnKind, Visitor};
use rustc_hir::LangItem::{OptionNone, OptionSome, ResultErr, ResultOk}; use rustc_hir::LangItem::{OptionNone, OptionSome, ResultErr, ResultOk};
use rustc_hir::{ use rustc_hir::{
self as hir, def, Arm, ArrayLen, BindingAnnotation, Block, BlockCheckMode, Body, Closure, Destination, Expr, self as hir, def, Arm, ArrayLen, BindingAnnotation, Block, BlockCheckMode, Body, Closure, Destination, Expr,
ExprField, ExprKind, FnDecl, FnRetTy, GenericArgs, HirId, Impl, ImplItem, ImplItemKind, ImplItemRef, IsAsync, Item, ExprField, ExprKind, FnDecl, FnRetTy, GenericArgs, HirId, Impl, ImplItem, ImplItemKind, ImplItemRef, Item,
ItemKind, LangItem, Local, MatchSource, Mutability, Node, OwnerId, Param, Pat, PatKind, Path, PathSegment, PrimTy, ItemKind, LangItem, Local, MatchSource, Mutability, Node, OwnerId, Param, Pat, PatKind, Path, PathSegment, PrimTy,
QPath, Stmt, StmtKind, TraitItem, TraitItemKind, TraitItemRef, TraitRef, TyKind, UnOp, QPath, Stmt, StmtKind, TraitItem, TraitItemKind, TraitItemRef, TraitRef, TyKind, UnOp,
}; };
@ -1958,8 +1958,8 @@ pub fn if_sequence<'tcx>(mut expr: &'tcx Expr<'tcx>) -> (Vec<&'tcx Expr<'tcx>>,
/// Checks if the given function kind is an async function. /// Checks if the given function kind is an async function.
pub fn is_async_fn(kind: FnKind<'_>) -> bool { pub fn is_async_fn(kind: FnKind<'_>) -> bool {
match kind { match kind {
FnKind::ItemFn(_, _, header) => header.asyncness == IsAsync::Async, FnKind::ItemFn(_, _, header) => header.asyncness .is_async(),
FnKind::Method(_, sig) => sig.header.asyncness == IsAsync::Async, FnKind::Method(_, sig) => sig.header.asyncness.is_async(),
FnKind::Closure => false, FnKind::Closure => false,
} }
} }

View file

@ -0,0 +1,7 @@
// edition:2021
#![feature(async_fn_in_trait)]
pub trait Foo {
async fn test();
}

View file

@ -15,6 +15,11 @@ note: required by a bound in `assert_is_send`
| |
LL | fn assert_is_send(_: impl Send) {} LL | fn assert_is_send(_: impl Send) {}
| ^^^^ required by this bound in `assert_is_send` | ^^^^ required by this bound in `assert_is_send`
help: `Send` can be made part of the associated future's guarantees for all implementations of `Foo::bar`
|
LL - async fn bar();
LL + fn bar() -> impl std::future::Future<Output = ()> + Send;
|
error: aborting due to previous error error: aborting due to previous error

View file

@ -0,0 +1,20 @@
// run-rustfix
// edition: 2021
#![feature(async_fn_in_trait, return_position_impl_trait_in_trait)]
#![allow(unused)]
trait Foo {
fn test() -> impl std::future::Future<Output = ()> + Send { async {} }
fn test2() -> impl std::future::Future<Output = i32> + Send {async { 1 + 2 } }
}
fn bar<T: Foo>() {
fn needs_send(_: impl Send) {}
needs_send(T::test());
//~^ ERROR `impl Future<Output = ()>` cannot be sent between threads safely
needs_send(T::test2());
//~^ ERROR `impl Future<Output = i32>` cannot be sent between threads safely
}
fn main() {}

View file

@ -0,0 +1,20 @@
// run-rustfix
// edition: 2021
#![feature(async_fn_in_trait, return_position_impl_trait_in_trait)]
#![allow(unused)]
trait Foo {
async fn test() -> () {}
async fn test2() -> i32 { 1 + 2 }
}
fn bar<T: Foo>() {
fn needs_send(_: impl Send) {}
needs_send(T::test());
//~^ ERROR `impl Future<Output = ()>` cannot be sent between threads safely
needs_send(T::test2());
//~^ ERROR `impl Future<Output = i32>` cannot be sent between threads safely
}
fn main() {}

View file

@ -0,0 +1,43 @@
error[E0277]: `impl Future<Output = ()>` cannot be sent between threads safely
--> $DIR/send-on-async-fn-in-trait.rs:14:16
|
LL | needs_send(T::test());
| ---------- ^^^^^^^^^ `impl Future<Output = ()>` cannot be sent between threads safely
| |
| required by a bound introduced by this call
|
= help: the trait `Send` is not implemented for `impl Future<Output = ()>`
note: required by a bound in `needs_send`
--> $DIR/send-on-async-fn-in-trait.rs:13:27
|
LL | fn needs_send(_: impl Send) {}
| ^^^^ required by this bound in `needs_send`
help: `Send` can be made part of the associated future's guarantees for all implementations of `Foo::test`
|
LL - async fn test() -> () {}
LL + fn test() -> impl std::future::Future<Output = ()> + Send { async {} }
|
error[E0277]: `impl Future<Output = i32>` cannot be sent between threads safely
--> $DIR/send-on-async-fn-in-trait.rs:16:16
|
LL | needs_send(T::test2());
| ---------- ^^^^^^^^^^ `impl Future<Output = i32>` cannot be sent between threads safely
| |
| required by a bound introduced by this call
|
= help: the trait `Send` is not implemented for `impl Future<Output = i32>`
note: required by a bound in `needs_send`
--> $DIR/send-on-async-fn-in-trait.rs:13:27
|
LL | fn needs_send(_: impl Send) {}
| ^^^^ required by this bound in `needs_send`
help: `Send` can be made part of the associated future's guarantees for all implementations of `Foo::test2`
|
LL - async fn test2() -> i32 { 1 + 2 }
LL + fn test2() -> impl std::future::Future<Output = i32> + Send {async { 1 + 2 } }
|
error: aborting due to 2 previous errors
For more information about this error, try `rustc --explain E0277`.

View file

@ -0,0 +1,15 @@
// aux-build:foreign-async-fn.rs
// edition:2021
#![feature(async_fn_in_trait)]
extern crate foreign_async_fn;
use foreign_async_fn::Foo;
fn bar<T: Foo>() {
fn needs_send(_: impl Send) {}
needs_send(T::test());
//~^ ERROR `impl Future<Output = ()>` cannot be sent between threads safely
}
fn main() {}

View file

@ -0,0 +1,23 @@
error[E0277]: `impl Future<Output = ()>` cannot be sent between threads safely
--> $DIR/send-on-foreign-async-fn-in-trait.rs:11:16
|
LL | needs_send(T::test());
| ---------- ^^^^^^^^^ `impl Future<Output = ()>` cannot be sent between threads safely
| |
| required by a bound introduced by this call
|
= help: the trait `Send` is not implemented for `impl Future<Output = ()>`
note: `<T as Foo>::test` is an `async fn` in trait, which does not automatically imply that its future is `Send`
--> $DIR/auxiliary/foreign-async-fn.rs:6:5
|
LL | async fn test();
| ^^^^^^^^^^^^^^^^
note: required by a bound in `needs_send`
--> $DIR/send-on-foreign-async-fn-in-trait.rs:10:27
|
LL | fn needs_send(_: impl Send) {}
| ^^^^ required by this bound in `needs_send`
error: aborting due to previous error
For more information about this error, try `rustc --explain E0277`.

View file

@ -146,33 +146,33 @@ hir-stats - Trait 192 ( 2.1%) 4
hir-stats WherePredicate 192 ( 2.1%) 3 64 hir-stats WherePredicate 192 ( 2.1%) 3 64
hir-stats - BoundPredicate 192 ( 2.1%) 3 hir-stats - BoundPredicate 192 ( 2.1%) 3
hir-stats Block 288 ( 3.2%) 6 48 hir-stats Block 288 ( 3.2%) 6 48
hir-stats Pat 360 ( 4.0%) 5 72 hir-stats Pat 360 ( 3.9%) 5 72
hir-stats - Wild 72 ( 0.8%) 1 hir-stats - Wild 72 ( 0.8%) 1
hir-stats - Struct 72 ( 0.8%) 1 hir-stats - Struct 72 ( 0.8%) 1
hir-stats - Binding 216 ( 2.4%) 3 hir-stats - Binding 216 ( 2.4%) 3
hir-stats GenericParam 400 ( 4.4%) 5 80 hir-stats GenericParam 400 ( 4.4%) 5 80
hir-stats Generics 560 ( 6.2%) 10 56 hir-stats Generics 560 ( 6.1%) 10 56
hir-stats Ty 720 ( 8.0%) 15 48 hir-stats Ty 720 ( 7.9%) 15 48
hir-stats - Ptr 48 ( 0.5%) 1 hir-stats - Ptr 48 ( 0.5%) 1
hir-stats - Ref 48 ( 0.5%) 1 hir-stats - Ref 48 ( 0.5%) 1
hir-stats - Path 624 ( 6.9%) 13 hir-stats - Path 624 ( 6.8%) 13
hir-stats Expr 768 ( 8.5%) 12 64 hir-stats Expr 768 ( 8.4%) 12 64
hir-stats - Path 64 ( 0.7%) 1 hir-stats - Path 64 ( 0.7%) 1
hir-stats - Struct 64 ( 0.7%) 1 hir-stats - Struct 64 ( 0.7%) 1
hir-stats - Match 64 ( 0.7%) 1 hir-stats - Match 64 ( 0.7%) 1
hir-stats - InlineAsm 64 ( 0.7%) 1 hir-stats - InlineAsm 64 ( 0.7%) 1
hir-stats - Lit 128 ( 1.4%) 2 hir-stats - Lit 128 ( 1.4%) 2
hir-stats - Block 384 ( 4.2%) 6 hir-stats - Block 384 ( 4.2%) 6
hir-stats Item 880 ( 9.7%) 11 80 hir-stats Item 968 (10.6%) 11 88
hir-stats - Trait 80 ( 0.9%) 1 hir-stats - Trait 88 ( 1.0%) 1
hir-stats - Enum 80 ( 0.9%) 1 hir-stats - Enum 88 ( 1.0%) 1
hir-stats - ExternCrate 80 ( 0.9%) 1 hir-stats - ExternCrate 88 ( 1.0%) 1
hir-stats - ForeignMod 80 ( 0.9%) 1 hir-stats - ForeignMod 88 ( 1.0%) 1
hir-stats - Impl 80 ( 0.9%) 1 hir-stats - Impl 88 ( 1.0%) 1
hir-stats - Fn 160 ( 1.8%) 2 hir-stats - Fn 176 ( 1.9%) 2
hir-stats - Use 320 ( 3.5%) 4 hir-stats - Use 352 ( 3.9%) 4
hir-stats Path 1_240 (13.7%) 31 40 hir-stats Path 1_240 (13.6%) 31 40
hir-stats PathSegment 1_920 (21.2%) 40 48 hir-stats PathSegment 1_920 (21.0%) 40 48
hir-stats ---------------------------------------------------------------- hir-stats ----------------------------------------------------------------
hir-stats Total 9_048 hir-stats Total 9_136
hir-stats hir-stats