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

Add unpolished, experimental support for AFIDT (async fn in dyn trait) #133122

Merged
merged 3 commits into from
Dec 12, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
Next Next commit
Add feature gate, not working yet
  • Loading branch information
compiler-errors committed Dec 10, 2024
commit 3b057796264e637b31c28809322028a32d22ae6f
2 changes: 2 additions & 0 deletions compiler/rustc_feature/src/unstable.rs
Original file line number Diff line number Diff line change
Expand Up @@ -390,6 +390,8 @@ declare_features! (
(unstable, associated_type_defaults, "1.2.0", Some(29661)),
/// Allows `async || body` closures.
(unstable, async_closure, "1.37.0", Some(62290)),
/// Allows async functions to be called from `dyn Trait`.
(incomplete, async_fn_in_dyn_trait, "CURRENT_RUSTC_VERSION", Some(133119)),
/// Allows `#[track_caller]` on async functions.
(unstable, async_fn_track_caller, "1.73.0", Some(110011)),
/// Allows `for await` loops.
Expand Down
1 change: 1 addition & 0 deletions compiler/rustc_span/src/symbol.rs
Original file line number Diff line number Diff line change
Expand Up @@ -461,6 +461,7 @@ symbols! {
async_drop_slice,
async_drop_surface_drop_in_place,
async_fn,
async_fn_in_dyn_trait,
async_fn_in_trait,
async_fn_kind_helper,
async_fn_kind_upvars,
Expand Down
59 changes: 48 additions & 11 deletions compiler/rustc_trait_selection/src/traits/dyn_compatibility.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ use rustc_abi::BackendRepr;
use rustc_errors::FatalError;
use rustc_hir as hir;
use rustc_hir::def_id::DefId;
use rustc_middle::bug;
use rustc_middle::query::Providers;
use rustc_middle::ty::{
self, EarlyBinder, ExistentialPredicateStableCmpExt as _, GenericArgs, Ty, TyCtxt,
Expand Down Expand Up @@ -901,23 +902,59 @@ fn contains_illegal_impl_trait_in_trait<'tcx>(
fn_def_id: DefId,
ty: ty::Binder<'tcx, Ty<'tcx>>,
) -> Option<MethodViolationCode> {
// This would be caught below, but rendering the error as a separate
// `async-specific` message is better.
let ty = tcx.liberate_late_bound_regions(fn_def_id, ty);

if tcx.asyncness(fn_def_id).is_async() {
return Some(MethodViolationCode::AsyncFn);
// FIXME(async_fn_in_dyn_trait): Think of a better way to unify these code paths
// to issue an appropriate feature suggestion when users try to use AFIDT.
// Obviously we must only do this once AFIDT is finished enough to actually be usable.
if tcx.features().async_fn_in_dyn_trait() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

instead of rejecting if the feature is not enabled, we could always use code path for when the feature is enabled and just emit a feature gate error. Or are you trying to not impact stable code for now in diagnostics?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess we could. I'm gonna leave it as a follow-up, since this needs to be reworked once again to support -> impl Future style RPITITs.

let ty::Alias(ty::Projection, proj) = *ty.kind() else {
bug!("expected async fn in trait to return an RPITIT");
};
assert!(tcx.is_impl_trait_in_trait(proj.def_id));

// FIXME(async_fn_in_dyn_trait): We should check that this bound is legal too,
// and stop relying on `async fn` in the definition.
for bound in tcx.item_bounds(proj.def_id).instantiate(tcx, proj.args) {
if let Some(violation) = bound
.visit_with(&mut IllegalRpititVisitor { tcx, allowed: Some(proj) })
.break_value()
{
return Some(violation);
}
}

None
} else {
// Rendering the error as a separate `async-specific` message is better.
Some(MethodViolationCode::AsyncFn)
}
} else {
ty.visit_with(&mut IllegalRpititVisitor { tcx, allowed: None }).break_value()
}
}

struct IllegalRpititVisitor<'tcx> {
tcx: TyCtxt<'tcx>,
allowed: Option<ty::AliasTy<'tcx>>,
}

impl<'tcx> TypeVisitor<TyCtxt<'tcx>> for IllegalRpititVisitor<'tcx> {
type Result = ControlFlow<MethodViolationCode>;

// FIXME(RPITIT): Perhaps we should use a visitor here?
ty.skip_binder().walk().find_map(|arg| {
if let ty::GenericArgKind::Type(ty) = arg.unpack()
&& let ty::Alias(ty::Projection, proj) = ty.kind()
&& tcx.is_impl_trait_in_trait(proj.def_id)
fn visit_ty(&mut self, ty: Ty<'tcx>) -> Self::Result {
if let ty::Alias(ty::Projection, proj) = *ty.kind()
&& Some(proj) != self.allowed
&& self.tcx.is_impl_trait_in_trait(proj.def_id)
{
Some(MethodViolationCode::ReferencesImplTraitInTrait(tcx.def_span(proj.def_id)))
ControlFlow::Break(MethodViolationCode::ReferencesImplTraitInTrait(
self.tcx.def_span(proj.def_id),
))
} else {
None
ty.super_visit_with(self)
}
})
}
}

pub(crate) fn provide(providers: &mut Providers) {
Expand Down
14 changes: 14 additions & 0 deletions tests/ui/feature-gates/feature-gate-async-fn-in-dyn-trait.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
//@ edition: 2021

trait Foo {
async fn bar(&self);
}

async fn takes_dyn_trait(x: &dyn Foo) {
//~^ ERROR the trait `Foo` cannot be made into an object
x.bar().await;
//~^ ERROR the trait `Foo` cannot be made into an object
//~| ERROR the trait `Foo` cannot be made into an object
}

fn main() {}
48 changes: 48 additions & 0 deletions tests/ui/feature-gates/feature-gate-async-fn-in-dyn-trait.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
error[E0038]: the trait `Foo` cannot be made into an object
--> $DIR/feature-gate-async-fn-in-dyn-trait.rs:7:30
|
LL | async fn takes_dyn_trait(x: &dyn Foo) {
| ^^^^^^^ `Foo` cannot be made into an object
|
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>
--> $DIR/feature-gate-async-fn-in-dyn-trait.rs:4:14
|
LL | trait Foo {
| --- this trait cannot be made into an object...
LL | async fn bar(&self);
| ^^^ ...because method `bar` is `async`
= help: consider moving `bar` to another trait

error[E0038]: the trait `Foo` cannot be made into an object
--> $DIR/feature-gate-async-fn-in-dyn-trait.rs:9:7
|
LL | x.bar().await;
| ^^^ `Foo` cannot be made into an object
|
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>
--> $DIR/feature-gate-async-fn-in-dyn-trait.rs:4:14
|
LL | trait Foo {
| --- this trait cannot be made into an object...
LL | async fn bar(&self);
| ^^^ ...because method `bar` is `async`
= help: consider moving `bar` to another trait

error[E0038]: the trait `Foo` cannot be made into an object
--> $DIR/feature-gate-async-fn-in-dyn-trait.rs:9:5
|
LL | x.bar().await;
| ^^^^^^^ `Foo` cannot be made into an object
|
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>
--> $DIR/feature-gate-async-fn-in-dyn-trait.rs:4:14
|
LL | trait Foo {
| --- this trait cannot be made into an object...
LL | async fn bar(&self);
| ^^^ ...because method `bar` is `async`
= help: consider moving `bar` to another trait

error: aborting due to 3 previous errors

For more information about this error, try `rustc --explain E0038`.