Skip to content

Commit 1e17c14

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

File tree

14 files changed

+290
-92
lines changed

14 files changed

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

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_middle/src/ty/context.rs

+4
Original file line numberDiff line numberDiff line change
@@ -560,6 +560,10 @@ impl<'tcx> Interner for TyCtxt<'tcx> {
560560
self.trait_is_alias(trait_def_id)
561561
}
562562

563+
fn trait_is_async_fn(self, trait_def_id: DefId) -> bool {
564+
self.trait_is_async_fn(trait_def_id)
565+
}
566+
563567
fn trait_is_dyn_compatible(self, trait_def_id: DefId) -> bool {
564568
self.is_dyn_compatible(trait_def_id)
565569
}

compiler/rustc_middle/src/ty/mod.rs

+11
Original file line numberDiff line numberDiff line change
@@ -1830,6 +1830,17 @@ impl<'tcx> TyCtxt<'tcx> {
18301830
self.trait_def(trait_def_id).has_auto_impl
18311831
}
18321832

1833+
/// Returns `true` if this is an `AsyncFn`, `AsyncFnMut`, or `AsyncFnOnce` trait.
1834+
pub fn trait_is_async_fn(self, trait_def_id: DefId) -> bool {
1835+
let lang_items = self.lang_items();
1836+
[
1837+
lang_items.async_fn_trait(),
1838+
lang_items.async_fn_mut_trait(),
1839+
lang_items.async_fn_once_trait(),
1840+
]
1841+
.contains(&Some(trait_def_id))
1842+
}
1843+
18331844
/// Returns `true` if this is coinductive, either because it is
18341845
/// an auto trait or because it has the `#[rustc_coinductive]` attribute.
18351846
pub fn trait_is_coinductive(self, trait_def_id: DefId) -> bool {

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

+64-20
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};
@@ -17,7 +17,7 @@ use rustc_infer::traits::{
1717
};
1818
use rustc_middle::ty::print::{PrintTraitRefExt as _, with_no_trimmed_paths};
1919
use rustc_middle::ty::{self, Ty, TyCtxt};
20-
use rustc_span::{ErrorGuaranteed, ExpnKind, Span};
20+
use rustc_span::{ErrorGuaranteed, ExpnKind, Span, Symbol};
2121
use tracing::{info, instrument};
2222

2323
pub use self::overflow::*;
@@ -428,10 +428,50 @@ 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 tcx.trait_is_async_fn(trait_def_id) {
435+
let fn_trait: Symbol = violations
436+
.iter()
437+
.find_map(|violation| {
438+
// Try to find the original trait that caused the violation.
439+
match *violation {
440+
DynCompatibilityViolation::AsyncFnTrait { fn_trait } => Some(fn_trait),
441+
_ => None,
442+
}
443+
})
444+
.expect("AsyncFn traits should report a corresponding DynCompatibilityViolation");
445+
let mut err = struct_span_code_err!(
446+
tcx.dcx(),
447+
span,
448+
E0802,
449+
"the trait `{}` is not yet `dyn`-compatible",
450+
fn_trait
451+
);
452+
// Note: this check is quite imprecise.
453+
// Comparing the DefIds or similar would be better, but we can't store
454+
// DefIds in `DynCompatibilityViolation`.
455+
if fn_trait.as_str() == trait_str {
456+
err.span_label(span, format!("`{fn_trait}` is not yet `dyn`-compatible"));
457+
} else {
458+
let trait_str = tcx.def_path_str(trait_def_id);
459+
err.span_label(
460+
span,
461+
format!(
462+
"`{trait_str}` inherits from `{fn_trait}` which is not yet `dyn`-compatible'"
463+
),
464+
);
465+
}
466+
attempt_dyn_to_impl_suggestion(tcx, hir_id, &mut err);
467+
return err;
468+
}
469+
431470
let trait_span = tcx.hir().get_if_local(trait_def_id).and_then(|node| match node {
432471
hir::Node::Item(item) => Some(item.ident.span),
433472
_ => None,
434473
});
474+
435475
let mut err = struct_span_code_err!(
436476
tcx.dcx(),
437477
span,
@@ -441,24 +481,8 @@ pub fn report_dyn_incompatibility<'tcx>(
441481
);
442482
err.span_label(span, format!("`{trait_str}` cannot be made into an object"));
443483

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-
}
484+
attempt_dyn_to_impl_suggestion(tcx, hir_id, &mut err);
485+
462486
let mut reported_violations = FxIndexSet::default();
463487
let mut multi_span = vec![];
464488
let mut messages = vec![];
@@ -583,3 +607,23 @@ pub fn report_dyn_incompatibility<'tcx>(
583607

584608
err
585609
}
610+
611+
fn attempt_dyn_to_impl_suggestion(tcx: TyCtxt<'_>, hir_id: Option<hir::HirId>, err: &mut Diag<'_>) {
612+
let Some(hir_id) = hir_id else { return };
613+
let hir::Node::Ty(ty) = tcx.hir_node(hir_id) else { return };
614+
let hir::TyKind::TraitObject([trait_ref, ..], ..) = ty.kind else { return };
615+
let mut hir_id = hir_id;
616+
while let hir::Node::Ty(ty) = tcx.parent_hir_node(hir_id) {
617+
hir_id = ty.hir_id;
618+
}
619+
if tcx.parent_hir_node(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 object-safe.
45+
for supertrait in tcx.supertrait_def_ids(trait_def_id) {
46+
if tcx.trait_is_async_fn(supertrait) {
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 object-safe.
68+
for supertrait in tcx.supertrait_def_ids(trait_def_id) {
69+
if tcx.trait_is_async_fn(supertrait) {
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)),

compiler/rustc_type_ir/src/interner.rs

+2
Original file line numberDiff line numberDiff line change
@@ -262,6 +262,8 @@ pub trait Interner:
262262

263263
fn trait_is_alias(self, trait_def_id: Self::DefId) -> bool;
264264

265+
fn trait_is_async_fn(self, trait_def_id: Self::DefId) -> bool;
266+
265267
fn trait_is_dyn_compatible(self, trait_def_id: Self::DefId) -> bool;
266268

267269
fn trait_is_fundamental(self, def_id: Self::DefId) -> bool;
+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() {}

0 commit comments

Comments
 (0)