Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make async Fn trait kind errors better #121119

Merged
merged 1 commit into from
Feb 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 7 additions & 5 deletions compiler/rustc_trait_selection/messages.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,16 @@ trait_selection_adjust_signature_remove_borrow = consider adjusting the signatur
*[other] arguments
}
trait_selection_closure_fn_mut_label = closure is `FnMut` because it mutates the variable `{$place}` here
trait_selection_async_closure_not_fn = async closure does not implement `{$kind}` because it captures state from its environment
trait_selection_closure_fn_once_label = closure is `FnOnce` because it moves the variable `{$place}` out of its environment
trait_selection_closure_fn_mut_label = closure is `{$trait_prefix}FnMut` because it mutates the variable `{$place}` here
trait_selection_closure_kind_mismatch = expected a closure that implements the `{$expected}` trait, but this closure only implements `{$found}`
.label = this closure implements `{$found}`, not `{$expected}`
trait_selection_closure_fn_once_label = closure is `{$trait_prefix}FnOnce` because it moves the variable `{$place}` out of its environment
trait_selection_closure_kind_requirement = the requirement to implement `{$expected}` derives from here
trait_selection_closure_kind_mismatch = expected a closure that implements the `{$trait_prefix}{$expected}` trait, but this closure only implements `{$trait_prefix}{$found}`
.label = this closure implements `{$trait_prefix}{$found}`, not `{$trait_prefix}{$expected}`
trait_selection_closure_kind_requirement = the requirement to implement `{$trait_prefix}{$expected}` derives from here
trait_selection_dump_vtable_entries = vtable entries for `{$trait_ref}`: {$entries}
Expand Down
10 changes: 10 additions & 0 deletions compiler/rustc_trait_selection/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,8 @@ pub struct ClosureKindMismatch {
#[label(trait_selection_closure_kind_requirement)]
pub cause_span: Span,

pub trait_prefix: &'static str,

#[subdiagnostic]
pub fn_once_label: Option<ClosureFnOnceLabel>,

Expand All @@ -157,3 +159,11 @@ pub struct ClosureFnMutLabel {
pub span: Span,
pub place: String,
}

#[derive(Diagnostic)]
#[diag(trait_selection_async_closure_not_fn)]
pub(crate) struct AsyncClosureNotFn {
#[primary_span]
pub span: Span,
pub kind: &'static str,
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@

use super::on_unimplemented::{AppendConstMessage, OnUnimplementedNote, TypeErrCtxtExt as _};
use super::suggestions::{get_explanation_based_on_obligation, TypeErrCtxtExt as _};
use crate::errors::{ClosureFnMutLabel, ClosureFnOnceLabel, ClosureKindMismatch};
use crate::errors::{
AsyncClosureNotFn, ClosureFnMutLabel, ClosureFnOnceLabel, ClosureKindMismatch,
};
use crate::infer::error_reporting::{TyCategory, TypeAnnotationNeeded as ErrorCode};
use crate::infer::type_variable::{TypeVariableOrigin, TypeVariableOriginKind};
use crate::infer::InferCtxtExt as _;
Expand Down Expand Up @@ -959,34 +961,102 @@ impl<'tcx> TypeErrCtxtExt<'tcx> for TypeErrCtxt<'_, 'tcx> {
fn emit_specialized_closure_kind_error(
&self,
obligation: &PredicateObligation<'tcx>,
trait_ref: ty::PolyTraitRef<'tcx>,
mut trait_ref: ty::PolyTraitRef<'tcx>,
) -> Option<ErrorGuaranteed> {
let self_ty = trait_ref.self_ty().skip_binder();
if let ty::Closure(closure_def_id, closure_args) = *self_ty.kind()
&& let Some(expected_kind) = self.tcx.fn_trait_kind_from_def_id(trait_ref.def_id())
&& let Some(found_kind) = self.closure_kind(self_ty)
// If `AsyncFnKindHelper` is not implemented, that means that the closure kind
// doesn't extend the goal kind. This is worth reporting, but we can only do so
// if we actually know which closure this goal comes from, so look at the cause
// to see if we can extract that information.
if Some(trait_ref.def_id()) == self.tcx.lang_items().async_fn_kind_helper()
&& let Some(found_kind) = trait_ref.skip_binder().args.type_at(0).to_opt_closure_kind()
&& let Some(expected_kind) =
trait_ref.skip_binder().args.type_at(1).to_opt_closure_kind()
&& !found_kind.extends(expected_kind)
&& let sig = closure_args.as_closure().sig()
&& self.can_sub(
obligation.param_env,
trait_ref,
sig.map_bound(|sig| {
ty::TraitRef::new(
self.tcx,
trait_ref.def_id(),
[trait_ref.self_ty().skip_binder(), sig.inputs()[0]],
)
}),
)
{
let mut err =
self.report_closure_error(&obligation, closure_def_id, found_kind, expected_kind);
self.note_obligation_cause(&mut err, &obligation);
self.point_at_returns_when_relevant(&mut err, &obligation);
Some(err.emit())
} else {
None
if let Some((_, Some(parent))) = obligation.cause.code().parent() {
// If we have a derived obligation, then the parent will be a `AsyncFn*` goal.
trait_ref = parent.to_poly_trait_ref();
} else if let &ObligationCauseCode::FunctionArgumentObligation { arg_hir_id, .. } =
obligation.cause.code()
&& let Some(typeck_results) = &self.typeck_results
&& let ty::Closure(closure_def_id, _) | ty::CoroutineClosure(closure_def_id, _) =
*typeck_results.node_type(arg_hir_id).kind()
{
// Otherwise, extract the closure kind from the obligation.
let mut err = self.report_closure_error(
&obligation,
closure_def_id,
found_kind,
expected_kind,
"async ",
);
self.note_obligation_cause(&mut err, &obligation);
self.point_at_returns_when_relevant(&mut err, &obligation);
return Some(err.emit());
}
}

let self_ty = trait_ref.self_ty().skip_binder();

if let Some(expected_kind) = self.tcx.fn_trait_kind_from_def_id(trait_ref.def_id()) {
let (closure_def_id, found_args, by_ref_captures) = match *self_ty.kind() {
ty::Closure(def_id, args) => {
(def_id, args.as_closure().sig().map_bound(|sig| sig.inputs()[0]), 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,
};

let expected_args = trait_ref.map_bound(|trait_ref| trait_ref.args.type_at(1));

// 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_sub(obligation.param_env, expected_args, found_args)
})
}) {
return None;
}

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,
"",
);
self.note_obligation_cause(&mut err, &obligation);
self.point_at_returns_when_relevant(&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) = by_ref_captures.kind()
&& !sig.skip_binder().output().is_unit()
{
let mut err = self.tcx.dcx().create_err(AsyncClosureNotFn {
span: self.tcx.def_span(closure_def_id),
kind: expected_kind.as_str(),
});
self.note_obligation_cause(&mut err, &obligation);
self.point_at_returns_when_relevant(&mut err, &obligation);
return Some(err.emit());
}
}
None
}

fn fn_arg_obligation(
Expand Down Expand Up @@ -1493,6 +1563,7 @@ pub(super) trait InferCtxtPrivExt<'tcx> {
closure_def_id: DefId,
found_kind: ty::ClosureKind,
kind: ty::ClosureKind,
trait_prefix: &'static str,
) -> DiagnosticBuilder<'tcx>;

fn report_cyclic_signature_error(
Expand Down Expand Up @@ -3376,6 +3447,7 @@ impl<'tcx> InferCtxtPrivExt<'tcx> for TypeErrCtxt<'_, 'tcx> {
closure_def_id: DefId,
found_kind: ty::ClosureKind,
kind: ty::ClosureKind,
trait_prefix: &'static str,
) -> DiagnosticBuilder<'tcx> {
let closure_span = self.tcx.def_span(closure_def_id);

Expand All @@ -3384,6 +3456,7 @@ impl<'tcx> InferCtxtPrivExt<'tcx> for TypeErrCtxt<'_, 'tcx> {
expected: kind,
found: found_kind,
cause_span: obligation.cause.span,
trait_prefix,
fn_once_label: None,
fn_mut_label: None,
};
Expand Down
15 changes: 15 additions & 0 deletions tests/ui/async-await/async-closures/not-fn.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// edition:2021

// FIXME(async_closures): This needs a better error message!

#![feature(async_closure)]

fn main() {
fn needs_fn<T>(_: impl FnMut() -> T) {}

let mut x = 1;
needs_fn(async || {
//~^ ERROR async closure does not implement `FnMut` because it captures state from its environment
x += 1;
});
}
16 changes: 16 additions & 0 deletions tests/ui/async-await/async-closures/not-fn.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
error: async closure does not implement `FnMut` because it captures state from its environment
--> $DIR/not-fn.rs:11:14
|
LL | needs_fn(async || {
| -------- ^^^^^^^^
| |
| required by a bound introduced by this call
|
note: required by a bound in `needs_fn`
--> $DIR/not-fn.rs:8:28
|
LL | fn needs_fn<T>(_: impl FnMut() -> T) {}
| ^^^^^^^^^^^^ required by this bound in `needs_fn`

error: aborting due to 1 previous error

3 changes: 1 addition & 2 deletions tests/ui/async-await/async-closures/wrong-fn-kind.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,7 @@ fn main() {

let mut x = 1;
needs_async_fn(async || {
//~^ ERROR i16: ops::async_function::internal_implementation_detail::AsyncFnKindHelper<i8>
// FIXME: Should say "closure is `async FnMut` but it needs `async Fn`" or sth.
//~^ ERROR expected a closure that implements the `async Fn` trait, but this closure only implements `async FnMut`
x += 1;
});
}
12 changes: 7 additions & 5 deletions tests/ui/async-await/async-closures/wrong-fn-kind.stderr
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
error[E0277]: the trait bound `i16: ops::async_function::internal_implementation_detail::AsyncFnKindHelper<i8>` is not satisfied
error[E0525]: expected a closure that implements the `async Fn` trait, but this closure only implements `async FnMut`
--> $DIR/wrong-fn-kind.rs:11:20
|
LL | needs_async_fn(async || {
| _____--------------_^
| -------------- -^^^^^^^
| | |
| _____|______________this closure implements `async FnMut`, not `async Fn`
| | |
| | required by a bound introduced by this call
LL | |
LL | | // FIXME: Should say "closure is `async FnMut` but it needs `async Fn`" or sth.
LL | | x += 1;
| | - closure is `async FnMut` because it mutates the variable `x` here
LL | | });
| |_____^ the trait `ops::async_function::internal_implementation_detail::AsyncFnKindHelper<i8>` is not implemented for `i16`
| |_____- the requirement to implement `async Fn` derives from here
|
note: required by a bound in `needs_async_fn`
--> $DIR/wrong-fn-kind.rs:8:31
Expand All @@ -19,4 +21,4 @@ LL | fn needs_async_fn(_: impl async Fn()) {}

error: aborting due to 1 previous error

For more information about this error, try `rustc --explain E0277`.
For more information about this error, try `rustc --explain E0525`.
Loading