Improve error message for AsyncFn trait failure for RPIT

This commit is contained in:
Michael Goulet 2025-03-03 04:09:43 +00:00
parent daf59857d6
commit e213f4beea
5 changed files with 101 additions and 54 deletions

View File

@ -829,7 +829,9 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> {
&& let ty::Closure(closure_def_id, _) | ty::CoroutineClosure(closure_def_id, _) = && let ty::Closure(closure_def_id, _) | ty::CoroutineClosure(closure_def_id, _) =
*typeck_results.node_type(arg_hir_id).kind() *typeck_results.node_type(arg_hir_id).kind()
{ {
// Otherwise, extract the closure kind from the obligation. // Otherwise, extract the closure kind from the obligation,
// but only if we actually have an argument to deduce the
// closure type from...
let mut err = self.report_closure_error( let mut err = self.report_closure_error(
&obligation, &obligation,
closure_def_id, closure_def_id,
@ -844,63 +846,72 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> {
let self_ty = trait_pred.self_ty().skip_binder(); let self_ty = trait_pred.self_ty().skip_binder();
if let Some(expected_kind) = self.tcx.fn_trait_kind_from_def_id(trait_pred.def_id()) { let (expected_kind, trait_prefix) =
let (closure_def_id, found_args, by_ref_captures) = match *self_ty.kind() { if let Some(expected_kind) = self.tcx.fn_trait_kind_from_def_id(trait_pred.def_id()) {
ty::Closure(def_id, args) => { (expected_kind, "")
(def_id, args.as_closure().sig().map_bound(|sig| sig.inputs()[0]), None) } else if let Some(expected_kind) =
} self.tcx.async_fn_trait_kind_from_def_id(trait_pred.def_id())
ty::CoroutineClosure(def_id, args) => ( {
def_id, (expected_kind, "Async")
args.as_coroutine_closure() } else {
.coroutine_closure_sig() return None;
.map_bound(|sig| sig.tupled_inputs_ty),
Some(args.as_coroutine_closure().coroutine_captures_by_ref_ty()),
),
_ => return None,
}; };
let expected_args = let (closure_def_id, found_args, by_ref_captures) = match *self_ty.kind() {
trait_pred.map_bound(|trait_pred| trait_pred.trait_ref.args.type_at(1)); ty::Closure(def_id, args) => {
(def_id, args.as_closure().sig().map_bound(|sig| sig.inputs()[0]), None)
// Verify that the arguments are compatible. If the signature is
// mismatched, then we have a totally different error to report.
if self.enter_forall(found_args, |found_args| {
self.enter_forall(expected_args, |expected_args| {
!self.can_eq(obligation.param_env, expected_args, found_args)
})
}) {
return None;
} }
ty::CoroutineClosure(def_id, args) => (
def_id,
args.as_coroutine_closure()
.coroutine_closure_sig()
.map_bound(|sig| sig.tupled_inputs_ty),
Some(args.as_coroutine_closure().coroutine_captures_by_ref_ty()),
),
_ => return None,
};
if let Some(found_kind) = self.closure_kind(self_ty) let expected_args = trait_pred.map_bound(|trait_pred| trait_pred.trait_ref.args.type_at(1));
&& !found_kind.extends(expected_kind)
{
let mut err = self.report_closure_error(
&obligation,
closure_def_id,
found_kind,
expected_kind,
"",
);
self.note_obligation_cause(&mut err, &obligation);
return Some(err.emit());
}
// If the closure has captures, then perhaps the reason that the trait // Verify that the arguments are compatible. If the signature is
// is unimplemented is because async closures don't implement `Fn`/`FnMut` // mismatched, then we have a totally different error to report.
// if they have captures. if self.enter_forall(found_args, |found_args| {
if let Some(by_ref_captures) = by_ref_captures self.enter_forall(expected_args, |expected_args| {
&& let ty::FnPtr(sig_tys, _) = by_ref_captures.kind() !self.can_eq(obligation.param_env, expected_args, found_args)
&& !sig_tys.skip_binder().output().is_unit() })
{ }) {
let mut err = self.dcx().create_err(AsyncClosureNotFn { return None;
span: self.tcx.def_span(closure_def_id),
kind: expected_kind.as_str(),
});
self.note_obligation_cause(&mut err, &obligation);
return Some(err.emit());
}
} }
if let Some(found_kind) = self.closure_kind(self_ty)
&& !found_kind.extends(expected_kind)
{
let mut err = self.report_closure_error(
&obligation,
closure_def_id,
found_kind,
expected_kind,
trait_prefix,
);
self.note_obligation_cause(&mut err, &obligation);
return Some(err.emit());
}
// If the closure has captures, then perhaps the reason that the trait
// is unimplemented is because async closures don't implement `Fn`/`FnMut`
// if they have captures.
if let Some(by_ref_captures) = by_ref_captures
&& let ty::FnPtr(sig_tys, _) = by_ref_captures.kind()
&& !sig_tys.skip_binder().output().is_unit()
{
let mut err = self.dcx().create_err(AsyncClosureNotFn {
span: self.tcx.def_span(closure_def_id),
kind: expected_kind.as_str(),
});
self.note_obligation_cause(&mut err, &obligation);
return Some(err.emit());
}
None None
} }

View File

@ -3191,7 +3191,10 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> {
false false
}; };
if !is_upvar_tys_infer_tuple { let is_builtin_async_fn_trait =
tcx.async_fn_trait_kind_from_def_id(data.parent_trait_pred.def_id()).is_some();
if !is_upvar_tys_infer_tuple && !is_builtin_async_fn_trait {
let ty_str = tcx.short_string(ty, err.long_ty_path()); let ty_str = tcx.short_string(ty, err.long_ty_path());
let msg = format!("required because it appears within the type `{ty_str}`"); let msg = format!("required because it appears within the type `{ty_str}`");
match ty.kind() { match ty.kind() {

View File

@ -984,8 +984,10 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> {
return Err(SelectionError::Unimplemented); return Err(SelectionError::Unimplemented);
} }
} else { } else {
nested.push(obligation.with( nested.push(Obligation::new(
self.tcx(), self.tcx(),
obligation.derived_cause(ObligationCauseCode::BuiltinDerived),
obligation.param_env,
ty::TraitRef::new( ty::TraitRef::new(
self.tcx(), self.tcx(),
self.tcx().require_lang_item( self.tcx().require_lang_item(

View File

@ -0,0 +1,14 @@
//@ edition: 2024
// Make sure the error message is understandable when an `AsyncFn` goal is not satisfied
// (due to closure kind), and that goal originates from an RPIT.
fn repro(foo: impl Into<bool>) -> impl AsyncFn() {
let inner_fn = async move || {
//~^ ERROR expected a closure that implements the `AsyncFn` trait
let _ = foo.into();
};
inner_fn
}
fn main() {}

View File

@ -0,0 +1,17 @@
error[E0525]: expected a closure that implements the `AsyncFn` trait, but this closure only implements `AsyncFnOnce`
--> $DIR/kind-due-to-rpit.rs:7:20
|
LL | fn repro(foo: impl Into<bool>) -> impl AsyncFn() {
| -------------- the requirement to implement `AsyncFn` derives from here
LL | let inner_fn = async move || {
| ^^^^^^^^^^^^^ this closure implements `AsyncFnOnce`, not `AsyncFn`
LL |
LL | let _ = foo.into();
| --- closure is `AsyncFnOnce` because it moves the variable `foo` out of its environment
LL | };
LL | inner_fn
| -------- return type was inferred to be `{async closure@$DIR/kind-due-to-rpit.rs:7:20: 7:33}` here
error: aborting due to 1 previous error
For more information about this error, try `rustc --explain E0525`.