Skip to content

Commit 166484c

Browse files
committed
Add shorter and more direct error for dyn AsyncFn
Fix #132713
1 parent 03ee484 commit 166484c

File tree

11 files changed

+279
-91
lines changed

11 files changed

+279
-91
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
The `Async{Fn, FnMut, FnOnce}` traits are not yet dyn-compatible.
2+
3+
Erroneous code example:
4+
5+
```compile_fail,E0802,edition2018
6+
#![feature(async_closure)]
7+
use core::ops::AsyncFn;
8+
9+
async fn call_async_fn_twice(some_fn: &dyn AsyncFn()) {
10+
some_fn().await;
11+
some_fn().await;
12+
}
13+
```
14+
15+
One workaround to this issue is to use `impl Async...` instead:
16+
17+
```edition2018
18+
#![feature(async_closure)]
19+
use core::ops::AsyncFn;
20+
21+
async fn call_async_fn_twice(some_fn: &impl AsyncFn()) {
22+
some_fn().await;
23+
some_fn().await;
24+
}
25+
```
26+
27+
This error indicates that you attempted to use `dyn AsyncFn`,
28+
`dyn AsyncFnMut`, or `dyn AsyncFnOnce`.
29+
30+
This is not yet possible because the `Async...` traits internally return
31+
a concrete `Future` associated type. For dynamic callsites, it is impossible
32+
to know the size of the returned `Future` object since different
33+
`Async...` implementations may return differently-sized `Future` objects.
34+
35+
This is analogous to the more general issue of creating a `dyn` type without
36+
specifying associated types, e.g. `dyn Iterator` as opposed to
37+
`dyn Iterator<Item = SomeItemType>`.

compiler/rustc_error_codes/src/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -541,6 +541,7 @@ E0798: 0798,
541541
E0799: 0799,
542542
E0800: 0800,
543543
E0801: 0801,
544+
E0802: 0802,
544545
);
545546
)
546547
}

compiler/rustc_hir_analysis/src/hir_ty_lowering/dyn_compatibility.rs

+5-10
Original file line numberDiff line numberDiff line change
@@ -103,17 +103,12 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ {
103103
// most importantly, that the supertraits don't contain `Self`,
104104
// to avoid ICEs.
105105
for item in &regular_traits {
106-
let violations =
107-
hir_ty_lowering_dyn_compatibility_violations(tcx, item.trait_ref().def_id());
106+
let item_def_id = item.trait_ref().def_id();
107+
let violations = hir_ty_lowering_dyn_compatibility_violations(tcx, item_def_id);
108108
if !violations.is_empty() {
109-
let reported = report_dyn_incompatibility(
110-
tcx,
111-
span,
112-
Some(hir_id),
113-
item.trait_ref().def_id(),
114-
&violations,
115-
)
116-
.emit();
109+
let reported =
110+
report_dyn_incompatibility(tcx, span, Some(hir_id), item_def_id, &violations)
111+
.emit();
117112
return Ty::new_error(tcx, reported);
118113
}
119114
}

compiler/rustc_hir_analysis/src/hir_ty_lowering/errors.rs

+1
Original file line numberDiff line numberDiff line change
@@ -733,6 +733,7 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ {
733733
(span, def_ids.into_iter().map(|did| tcx.associated_item(did)).collect())
734734
})
735735
.collect();
736+
736737
let mut names: FxIndexMap<String, Vec<Symbol>> = Default::default();
737738
let mut names_len = 0;
738739

compiler/rustc_middle/src/traits/mod.rs

+21-1
Original file line numberDiff line numberDiff line change
@@ -712,6 +712,22 @@ pub enum DynCompatibilityViolation {
712712

713713
/// GAT
714714
GAT(Symbol, Span),
715+
716+
/// Async{Fn, FnMut, FnOnce}
717+
///
718+
/// `fn_trait` is the name of the `AsyncFn...` trait,
719+
AsyncFnTrait {
720+
/// The `AsyncFn...` trait referenced.
721+
///
722+
/// This is useful for better error reporting in cases where the
723+
/// `dyn`-incompatible trait inherits from `Async...`.
724+
//
725+
// FIXME(cramertj): I'd love for this to be a DefId for proper comparison
726+
// in the error reporting stage, but sadly this isn't possible because
727+
// DefIds cannot be stored at this stage. Is there a better way to handle
728+
// catching the supertrait case than string comparison?
729+
fn_trait: Symbol,
730+
},
715731
}
716732

717733
impl DynCompatibilityViolation {
@@ -779,14 +795,18 @@ impl DynCompatibilityViolation {
779795
DynCompatibilityViolation::GAT(name, _) => {
780796
format!("it contains the generic associated type `{name}`").into()
781797
}
798+
DynCompatibilityViolation::AsyncFnTrait { .. } => {
799+
"`async` function traits are not yet dyn-compatible".into()
800+
}
782801
}
783802
}
784803

785804
pub fn solution(&self) -> DynCompatibilityViolationSolution {
786805
match self {
787806
DynCompatibilityViolation::SizedSelf(_)
788807
| DynCompatibilityViolation::SupertraitSelf(_)
789-
| DynCompatibilityViolation::SupertraitNonLifetimeBinder(..) => {
808+
| DynCompatibilityViolation::SupertraitNonLifetimeBinder(..)
809+
| DynCompatibilityViolation::AsyncFnTrait { .. } => {
790810
DynCompatibilityViolationSolution::None
791811
}
792812
DynCompatibilityViolation::Method(

compiler/rustc_trait_selection/src/error_reporting/traits/mod.rs

+63-19
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ pub mod suggestions;
77
use std::{fmt, iter};
88

99
use rustc_data_structures::fx::{FxIndexMap, FxIndexSet};
10-
use rustc_errors::{Applicability, Diag, E0038, E0276, MultiSpan, struct_span_code_err};
10+
use rustc_errors::{Applicability, Diag, E0038, E0276, E0802, MultiSpan, struct_span_code_err};
1111
use rustc_hir::def_id::{DefId, LocalDefId};
1212
use rustc_hir::intravisit::Visitor;
1313
use rustc_hir::{self as hir, LangItem};
@@ -428,10 +428,46 @@ pub fn report_dyn_incompatibility<'tcx>(
428428
violations: &[DynCompatibilityViolation],
429429
) -> Diag<'tcx> {
430430
let trait_str = tcx.def_path_str(trait_def_id);
431+
432+
// Avoid errors diving into the details of the `AsyncFn` traits if this is
433+
// a straightforward "`AsyncFn` is not yet dyn-compatible" error.
434+
if let Some(async_fn_trait_kind) = tcx.async_fn_trait_kind_from_def_id(trait_def_id) {
435+
let async_fn_trait_name = match async_fn_trait_kind {
436+
ty::ClosureKind::Fn => "AsyncFn",
437+
ty::ClosureKind::FnMut => "AsyncFnMut",
438+
ty::ClosureKind::FnOnce => "AsyncFnOnce",
439+
};
440+
441+
let mut err = struct_span_code_err!(
442+
tcx.dcx(),
443+
span,
444+
E0802,
445+
"the trait `{}` is not yet dyn-compatible",
446+
async_fn_trait_name
447+
);
448+
// Note: this check is quite imprecise.
449+
// Comparing the DefIds or similar would be better, but we can't store
450+
// DefIds in `DynCompatibilityViolation`.
451+
if async_fn_trait_name == trait_str {
452+
err.span_label(span, format!("`{async_fn_trait_name}` is not yet dyn compatible"));
453+
} else {
454+
let trait_str = tcx.def_path_str(trait_def_id);
455+
err.span_label(
456+
span,
457+
format!(
458+
"`{trait_str}` inherits from `{async_fn_trait_name}` which is not yet dyn-compatible'"
459+
),
460+
);
461+
}
462+
attempt_dyn_to_impl_suggestion(tcx, hir_id, &mut err);
463+
return err;
464+
}
465+
431466
let trait_span = tcx.hir().get_if_local(trait_def_id).and_then(|node| match node {
432467
hir::Node::Item(item) => Some(item.ident.span),
433468
_ => None,
434469
});
470+
435471
let mut err = struct_span_code_err!(
436472
tcx.dcx(),
437473
span,
@@ -441,24 +477,8 @@ pub fn report_dyn_incompatibility<'tcx>(
441477
);
442478
err.span_label(span, format!("`{trait_str}` cannot be made into an object"));
443479

444-
if let Some(hir_id) = hir_id
445-
&& let hir::Node::Ty(ty) = tcx.hir_node(hir_id)
446-
&& let hir::TyKind::TraitObject([trait_ref, ..], ..) = ty.kind
447-
{
448-
let mut hir_id = hir_id;
449-
while let hir::Node::Ty(ty) = tcx.parent_hir_node(hir_id) {
450-
hir_id = ty.hir_id;
451-
}
452-
if tcx.parent_hir_node(hir_id).fn_sig().is_some() {
453-
// Do not suggest `impl Trait` when dealing with things like super-traits.
454-
err.span_suggestion_verbose(
455-
ty.span.until(trait_ref.span),
456-
"consider using an opaque type instead",
457-
"impl ",
458-
Applicability::MaybeIncorrect,
459-
);
460-
}
461-
}
480+
attempt_dyn_to_impl_suggestion(tcx, hir_id, &mut err);
481+
462482
let mut reported_violations = FxIndexSet::default();
463483
let mut multi_span = vec![];
464484
let mut messages = vec![];
@@ -583,3 +603,27 @@ pub fn report_dyn_incompatibility<'tcx>(
583603

584604
err
585605
}
606+
607+
fn attempt_dyn_to_impl_suggestion(tcx: TyCtxt<'_>, hir_id: Option<hir::HirId>, err: &mut Diag<'_>) {
608+
let Some(hir_id) = hir_id else { return };
609+
let hir::Node::Ty(ty) = tcx.hir_node(hir_id) else { return };
610+
let hir::TyKind::TraitObject([trait_ref, ..], ..) = ty.kind else { return };
611+
// Get the top-most parent element which is a type.
612+
let parent_ty_hir_id = tcx
613+
.hir()
614+
.parent_iter(hir_id)
615+
.take_while(|(_id, node)| matches!(node, hir::Node::Ty(_)))
616+
.last()
617+
.map(|(id, _node)| id)
618+
.unwrap_or(hir_id);
619+
if tcx.parent_hir_node(parent_ty_hir_id).fn_sig().is_none() {
620+
// Do not suggest `impl Trait` when dealing with things like super-traits.
621+
return;
622+
}
623+
err.span_suggestion_verbose(
624+
ty.span.until(trait_ref.span),
625+
"consider using an opaque type instead",
626+
"impl ",
627+
Applicability::MaybeIncorrect,
628+
);
629+
}

compiler/rustc_trait_selection/src/traits/dyn_compatibility.rs

+21
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,16 @@ pub fn hir_ty_lowering_dyn_compatibility_violations(
3939
trait_def_id: DefId,
4040
) -> Vec<DynCompatibilityViolation> {
4141
debug_assert!(tcx.generics_of(trait_def_id).has_self);
42+
43+
// Check for `AsyncFn` traits first to avoid reporting various other
44+
// errors about the details of why these traits aren't dyn-compatible.
45+
for supertrait in tcx.supertrait_def_ids(trait_def_id) {
46+
if tcx.async_fn_trait_kind_from_def_id(supertrait).is_some() {
47+
let fn_trait = tcx.item_name(supertrait);
48+
return vec![DynCompatibilityViolation::AsyncFnTrait { fn_trait }];
49+
}
50+
}
51+
4252
tcx.supertrait_def_ids(trait_def_id)
4353
.map(|def_id| predicates_reference_self(tcx, def_id, true))
4454
.filter(|spans| !spans.is_empty())
@@ -53,6 +63,17 @@ fn dyn_compatibility_violations(
5363
debug_assert!(tcx.generics_of(trait_def_id).has_self);
5464
debug!("dyn_compatibility_violations: {:?}", trait_def_id);
5565

66+
// Check for `AsyncFn` traits first to avoid reporting various other
67+
// errors about the details of why these traits aren't dyn-compatible.
68+
for supertrait in tcx.supertrait_def_ids(trait_def_id) {
69+
if tcx.async_fn_trait_kind_from_def_id(supertrait).is_some() {
70+
let fn_trait = tcx.item_name(supertrait);
71+
return core::slice::from_ref(
72+
tcx.arena.alloc(DynCompatibilityViolation::AsyncFnTrait { fn_trait }),
73+
);
74+
}
75+
}
76+
5677
tcx.arena.alloc_from_iter(
5778
tcx.supertrait_def_ids(trait_def_id)
5879
.flat_map(|def_id| dyn_compatibility_violations_for_trait(tcx, def_id)),
+39
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
// Test the diagnostic output (error descriptions) resulting from `dyn AsyncFn{,Mut,Once}`.
2+
//@ edition:2018
3+
4+
#![feature(async_closure)]
5+
6+
use core::ops::{AsyncFn, AsyncFnMut, AsyncFnOnce};
7+
8+
// --- Explicit `dyn` ---
9+
10+
fn takes_async_fn(_: &dyn AsyncFn()) {}
11+
//~^ ERROR the trait `AsyncFn` is not yet dyn-compatible
12+
13+
fn takes_async_fn_mut(_: &mut dyn AsyncFnMut()) {}
14+
//~^ ERROR the trait `AsyncFnMut` is not yet dyn-compatible
15+
16+
fn takes_async_fn_once(_: Box<dyn AsyncFnOnce()>) {}
17+
//~^ ERROR the trait `AsyncFnOnce` is not yet dyn-compatible
18+
19+
// --- Non-explicit `dyn` ---
20+
21+
#[allow(bare_trait_objects)]
22+
fn takes_async_fn_implicit_dyn(_: &AsyncFn()) {}
23+
//~^ ERROR the trait `AsyncFn` is not yet dyn-compatible
24+
25+
#[allow(bare_trait_objects)]
26+
fn takes_async_fn_mut_implicit_dyn(_: &mut AsyncFnMut()) {}
27+
//~^ ERROR the trait `AsyncFnMut` is not yet dyn-compatible
28+
29+
#[allow(bare_trait_objects)]
30+
fn takes_async_fn_once_implicit_dyn(_: Box<AsyncFnOnce()>) {}
31+
//~^ ERROR the trait `AsyncFnOnce` is not yet dyn-compatible
32+
33+
// --- Supertrait ---
34+
35+
trait SubAsyncFn: AsyncFn() {}
36+
fn takes_sub_async_fn(_: &dyn SubAsyncFn) {}
37+
//~^ ERROR the trait `SubAsyncFn` cannot be made into an object
38+
39+
fn main() {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
error[E0802]: the trait `AsyncFn` is not yet dyn-compatible
2+
--> $DIR/dyn.rs:10:23
3+
|
4+
LL | fn takes_async_fn(_: &dyn AsyncFn()) {}
5+
| ^^^^^^^^^^^^^ `AsyncFn` is not yet dyn compatible
6+
|
7+
help: consider using an opaque type instead
8+
|
9+
LL | fn takes_async_fn(_: &impl AsyncFn()) {}
10+
| ~~~~
11+
12+
error[E0802]: the trait `AsyncFnMut` is not yet dyn-compatible
13+
--> $DIR/dyn.rs:13:31
14+
|
15+
LL | fn takes_async_fn_mut(_: &mut dyn AsyncFnMut()) {}
16+
| ^^^^^^^^^^^^^^^^ `AsyncFnMut` is not yet dyn compatible
17+
|
18+
help: consider using an opaque type instead
19+
|
20+
LL | fn takes_async_fn_mut(_: &mut impl AsyncFnMut()) {}
21+
| ~~~~
22+
23+
error[E0802]: the trait `AsyncFnOnce` is not yet dyn-compatible
24+
--> $DIR/dyn.rs:16:31
25+
|
26+
LL | fn takes_async_fn_once(_: Box<dyn AsyncFnOnce()>) {}
27+
| ^^^^^^^^^^^^^^^^^ `AsyncFnOnce` is not yet dyn compatible
28+
|
29+
help: consider using an opaque type instead
30+
|
31+
LL | fn takes_async_fn_once(_: Box<impl AsyncFnOnce()>) {}
32+
| ~~~~
33+
34+
error[E0802]: the trait `AsyncFn` is not yet dyn-compatible
35+
--> $DIR/dyn.rs:22:36
36+
|
37+
LL | fn takes_async_fn_implicit_dyn(_: &AsyncFn()) {}
38+
| ^^^^^^^^^ `AsyncFn` is not yet dyn compatible
39+
|
40+
help: consider using an opaque type instead
41+
|
42+
LL | fn takes_async_fn_implicit_dyn(_: &impl AsyncFn()) {}
43+
| ++++
44+
45+
error[E0802]: the trait `AsyncFnMut` is not yet dyn-compatible
46+
--> $DIR/dyn.rs:26:44
47+
|
48+
LL | fn takes_async_fn_mut_implicit_dyn(_: &mut AsyncFnMut()) {}
49+
| ^^^^^^^^^^^^ `AsyncFnMut` is not yet dyn compatible
50+
|
51+
help: consider using an opaque type instead
52+
|
53+
LL | fn takes_async_fn_mut_implicit_dyn(_: &mut impl AsyncFnMut()) {}
54+
| ++++
55+
56+
error[E0802]: the trait `AsyncFnOnce` is not yet dyn-compatible
57+
--> $DIR/dyn.rs:30:44
58+
|
59+
LL | fn takes_async_fn_once_implicit_dyn(_: Box<AsyncFnOnce()>) {}
60+
| ^^^^^^^^^^^^^ `AsyncFnOnce` is not yet dyn compatible
61+
|
62+
help: consider using an opaque type instead
63+
|
64+
LL | fn takes_async_fn_once_implicit_dyn(_: Box<impl AsyncFnOnce()>) {}
65+
| ++++
66+
67+
error[E0038]: the trait `SubAsyncFn` cannot be made into an object
68+
--> $DIR/dyn.rs:36:27
69+
|
70+
LL | fn takes_sub_async_fn(_: &dyn SubAsyncFn) {}
71+
| ^^^^^^^^^^^^^^ `SubAsyncFn` cannot be made into an object
72+
|
73+
= note: the trait cannot be made into an object because `async` function traits are not yet dyn-compatible
74+
= note: for a trait to be "dyn-compatible" it needs to allow building a vtable to allow the call to be resolvable dynamically; for more information visit <https://doc.rust-lang.org/reference/items/traits.html#object-safety>
75+
help: consider using an opaque type instead
76+
|
77+
LL | fn takes_sub_async_fn(_: &impl SubAsyncFn) {}
78+
| ~~~~
79+
80+
error: aborting due to 7 previous errors
81+
82+
Some errors have detailed explanations: E0038, E0802.
83+
For more information about an error, try `rustc --explain E0038`.

0 commit comments

Comments
 (0)