-
Notifications
You must be signed in to change notification settings - Fork 13.4k
Add unpolished, experimental support for AFIDT (async fn in dyn trait) #133122
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,95 @@ | ||
use rustc_hir::def_id::DefId; | ||
|
||
use crate::ty::{self, ExistentialPredicateStableCmpExt, TyCtxt}; | ||
|
||
impl<'tcx> TyCtxt<'tcx> { | ||
/// Given a `def_id` of a trait or impl method, compute whether that method needs to | ||
/// have an RPITIT shim applied to it for it to be object safe. If so, return the | ||
/// `def_id` of the RPITIT, and also the args of trait method that returns the RPITIT. | ||
/// | ||
/// NOTE that these args are not, in general, the same as than the RPITIT's args. They | ||
/// are a subset of those args, since they do not include the late-bound lifetimes of | ||
/// the RPITIT. Depending on the context, these will need to be dealt with in different | ||
/// ways -- in codegen, it's okay to fill them with ReErased. | ||
pub fn return_position_impl_trait_in_trait_shim_data( | ||
self, | ||
def_id: DefId, | ||
) -> Option<(DefId, ty::EarlyBinder<'tcx, ty::GenericArgsRef<'tcx>>)> { | ||
let assoc_item = self.opt_associated_item(def_id)?; | ||
|
||
let (trait_item_def_id, opt_impl_def_id) = match assoc_item.container { | ||
ty::AssocItemContainer::Impl => { | ||
(assoc_item.trait_item_def_id?, Some(self.parent(def_id))) | ||
} | ||
ty::AssocItemContainer::Trait => (def_id, None), | ||
}; | ||
|
||
let sig = self.fn_sig(trait_item_def_id); | ||
|
||
// Check if the trait returns an RPITIT. | ||
let ty::Alias(ty::Projection, ty::AliasTy { def_id, .. }) = | ||
*sig.skip_binder().skip_binder().output().kind() | ||
else { | ||
return None; | ||
}; | ||
if !self.is_impl_trait_in_trait(def_id) { | ||
return None; | ||
} | ||
|
||
let args = if let Some(impl_def_id) = opt_impl_def_id { | ||
// Rebase the args from the RPITIT onto the impl trait ref, so we can later | ||
// substitute them with the method args of the *impl* method, since that's | ||
// the instance we're building a vtable shim for. | ||
ty::GenericArgs::identity_for_item(self, trait_item_def_id).rebase_onto( | ||
self, | ||
self.parent(trait_item_def_id), | ||
self.impl_trait_ref(impl_def_id) | ||
.expect("expected impl trait ref from parent of impl item") | ||
.instantiate_identity() | ||
.args, | ||
) | ||
} else { | ||
// This is when we have a default trait implementation. | ||
ty::GenericArgs::identity_for_item(self, trait_item_def_id) | ||
}; | ||
|
||
Some((def_id, ty::EarlyBinder::bind(args))) | ||
} | ||
|
||
/// Given a `DefId` of an RPITIT and its args, return the existential predicates | ||
/// that corresponds to the RPITIT's bounds with the self type erased. | ||
pub fn item_bounds_to_existential_predicates( | ||
self, | ||
def_id: DefId, | ||
args: ty::GenericArgsRef<'tcx>, | ||
) -> &'tcx ty::List<ty::PolyExistentialPredicate<'tcx>> { | ||
let mut bounds: Vec<_> = self | ||
.item_super_predicates(def_id) | ||
.iter_instantiated(self, args) | ||
.filter_map(|clause| { | ||
clause | ||
.kind() | ||
.map_bound(|clause| match clause { | ||
ty::ClauseKind::Trait(trait_pred) => Some(ty::ExistentialPredicate::Trait( | ||
ty::ExistentialTraitRef::erase_self_ty(self, trait_pred.trait_ref), | ||
)), | ||
ty::ClauseKind::Projection(projection_pred) => { | ||
Some(ty::ExistentialPredicate::Projection( | ||
ty::ExistentialProjection::erase_self_ty(self, projection_pred), | ||
)) | ||
} | ||
ty::ClauseKind::TypeOutlives(_) => { | ||
// Type outlives bounds don't really turn into anything, | ||
// since we must use an intersection region for the `dyn*`'s | ||
// region anyways. | ||
None | ||
} | ||
_ => unreachable!("unexpected clause in item bounds: {clause:?}"), | ||
}) | ||
.transpose() | ||
}) | ||
.collect(); | ||
bounds.sort_by(|a, b| a.skip_binder().stable_cmp(self, &b.skip_binder())); | ||
self.mk_poly_existential_predicates(&bounds) | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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, | ||
|
@@ -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() { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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? There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 |
||
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) { | ||
|
Uh oh!
There was an error while loading. Please reload this page.