Rollup merge of #133122 - compiler-errors:afidt, r=oli-obk
Add unpolished, experimental support for AFIDT (async fn in dyn trait) This allows us to begin messing around `async fn` in `dyn Trait`. Calling an async fn from a trait object always returns a `dyn* Future<Output = ...>`. To make it work, Implementations are currently required to return something that can be coerced to a `dyn* Future` (see the example in `tests/ui/async-await/dyn/works.rs`). If it's not the right size, then it'll raise an error at the coercion site (see the example in `tests/ui/async-await/dyn/wrong-size.rs`). Currently the only practical way of doing this is wrapping the body in `Box::pin(async move { .. })`. This PR does not implement a helper type like a "`Boxing`"[^boxing] adapter, and I'll probably follow-up with another PR to improve the error message for the `PointerLike` trait (something that explains in just normal prose what is happening here, rather than a trait error). [^boxing]: https://rust-lang.github.io/async-fundamentals-initiative/explainer/user_guide_future.html#the-boxing-adapter This PR also does not implement new trait solver support for AFIDT; I'll need to think how best to integrate it into candidate assembly, and that's a bit of a matter of taste, but I don't think it will be difficult to do. This could also be generalized: * To work on functions that are `-> impl Future` (soon). * To work on functions that are `-> impl Iterator` and other "dyn rpitit safe" traits. We still need to nail down exactly what is needed for this to be okay (not soon). Tracking: * https://github.com/rust-lang/rust/issues/133119
This commit is contained in:
commit
2e8807d87c
23 changed files with 624 additions and 36 deletions
compiler
rustc_feature/src
rustc_middle/src/ty
rustc_mir_transform/src
rustc_monomorphize/src
rustc_span/src
rustc_trait_selection/src/traits
rustc_ty_utils/src
tests/ui
async-await/dyn
auxiliary
mut-is-pointer-like.rsmut-is-pointer-like.run.stdoutmut-is-pointer-like.stderrworks.rsworks.run.stdoutworks.stderrwrong-size.rswrong-size.stderrfeature-gates
impl-trait/in-trait
|
@ -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.
|
||||
|
|
|
@ -677,23 +677,26 @@ impl<'tcx> Instance<'tcx> {
|
|||
//
|
||||
// 1) The underlying method expects a caller location parameter
|
||||
// in the ABI
|
||||
if resolved.def.requires_caller_location(tcx)
|
||||
// 2) The caller location parameter comes from having `#[track_caller]`
|
||||
// on the implementation, and *not* on the trait method.
|
||||
&& !tcx.should_inherit_track_caller(def)
|
||||
// If the method implementation comes from the trait definition itself
|
||||
// (e.g. `trait Foo { #[track_caller] my_fn() { /* impl */ } }`),
|
||||
// then we don't need to generate a shim. This check is needed because
|
||||
// `should_inherit_track_caller` returns `false` if our method
|
||||
// implementation comes from the trait block, and not an impl block
|
||||
&& !matches!(
|
||||
tcx.opt_associated_item(def),
|
||||
Some(ty::AssocItem {
|
||||
container: ty::AssocItemContainer::Trait,
|
||||
..
|
||||
})
|
||||
)
|
||||
{
|
||||
let needs_track_caller_shim = resolved.def.requires_caller_location(tcx)
|
||||
// 2) The caller location parameter comes from having `#[track_caller]`
|
||||
// on the implementation, and *not* on the trait method.
|
||||
&& !tcx.should_inherit_track_caller(def)
|
||||
// If the method implementation comes from the trait definition itself
|
||||
// (e.g. `trait Foo { #[track_caller] my_fn() { /* impl */ } }`),
|
||||
// then we don't need to generate a shim. This check is needed because
|
||||
// `should_inherit_track_caller` returns `false` if our method
|
||||
// implementation comes from the trait block, and not an impl block
|
||||
&& !matches!(
|
||||
tcx.opt_associated_item(def),
|
||||
Some(ty::AssocItem {
|
||||
container: ty::AssocItemContainer::Trait,
|
||||
..
|
||||
})
|
||||
);
|
||||
// We also need to generate a shim if this is an AFIT.
|
||||
let needs_rpitit_shim =
|
||||
tcx.return_position_impl_trait_in_trait_shim_data(def).is_some();
|
||||
if needs_track_caller_shim || needs_rpitit_shim {
|
||||
if tcx.is_closure_like(def) {
|
||||
debug!(
|
||||
" => vtable fn pointer created for closure with #[track_caller]: {:?} for method {:?} {:?}",
|
||||
|
|
|
@ -146,6 +146,7 @@ mod opaque_types;
|
|||
mod parameterized;
|
||||
mod predicate;
|
||||
mod region;
|
||||
mod return_position_impl_trait_in_trait;
|
||||
mod rvalue_scopes;
|
||||
mod structural_impls;
|
||||
#[allow(hidden_glob_reexports)]
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -9,6 +9,7 @@ use rustc_index::{Idx, IndexVec};
|
|||
use rustc_middle::mir::patch::MirPatch;
|
||||
use rustc_middle::mir::*;
|
||||
use rustc_middle::query::Providers;
|
||||
use rustc_middle::ty::adjustment::PointerCoercion;
|
||||
use rustc_middle::ty::{
|
||||
self, CoroutineArgs, CoroutineArgsExt, EarlyBinder, GenericArgs, Ty, TyCtxt,
|
||||
};
|
||||
|
@ -710,6 +711,13 @@ fn build_call_shim<'tcx>(
|
|||
};
|
||||
|
||||
let def_id = instance.def_id();
|
||||
|
||||
let rpitit_shim = if let ty::InstanceKind::ReifyShim(..) = instance {
|
||||
tcx.return_position_impl_trait_in_trait_shim_data(def_id)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let sig = tcx.fn_sig(def_id);
|
||||
let sig = sig.map_bound(|sig| tcx.instantiate_bound_regions_with_erased(sig));
|
||||
|
||||
|
@ -765,9 +773,34 @@ fn build_call_shim<'tcx>(
|
|||
let mut local_decls = local_decls_for_sig(&sig, span);
|
||||
let source_info = SourceInfo::outermost(span);
|
||||
|
||||
let mut destination = Place::return_place();
|
||||
if let Some((rpitit_def_id, fn_args)) = rpitit_shim {
|
||||
let rpitit_args =
|
||||
fn_args.instantiate_identity().extend_to(tcx, rpitit_def_id, |param, _| {
|
||||
match param.kind {
|
||||
ty::GenericParamDefKind::Lifetime => tcx.lifetimes.re_erased.into(),
|
||||
ty::GenericParamDefKind::Type { .. }
|
||||
| ty::GenericParamDefKind::Const { .. } => {
|
||||
unreachable!("rpitit should have no addition ty/ct")
|
||||
}
|
||||
}
|
||||
});
|
||||
let dyn_star_ty = Ty::new_dynamic(
|
||||
tcx,
|
||||
tcx.item_bounds_to_existential_predicates(rpitit_def_id, rpitit_args),
|
||||
tcx.lifetimes.re_erased,
|
||||
ty::DynStar,
|
||||
);
|
||||
destination = local_decls.push(local_decls[RETURN_PLACE].clone()).into();
|
||||
local_decls[RETURN_PLACE].ty = dyn_star_ty;
|
||||
let mut inputs_and_output = sig.inputs_and_output.to_vec();
|
||||
*inputs_and_output.last_mut().unwrap() = dyn_star_ty;
|
||||
sig.inputs_and_output = tcx.mk_type_list(&inputs_and_output);
|
||||
}
|
||||
|
||||
let rcvr_place = || {
|
||||
assert!(rcvr_adjustment.is_some());
|
||||
Place::from(Local::new(1 + 0))
|
||||
Place::from(Local::new(1))
|
||||
};
|
||||
let mut statements = vec![];
|
||||
|
||||
|
@ -854,7 +887,7 @@ fn build_call_shim<'tcx>(
|
|||
TerminatorKind::Call {
|
||||
func: callee,
|
||||
args,
|
||||
destination: Place::return_place(),
|
||||
destination,
|
||||
target: Some(BasicBlock::new(1)),
|
||||
unwind: if let Some(Adjustment::RefMut) = rcvr_adjustment {
|
||||
UnwindAction::Cleanup(BasicBlock::new(3))
|
||||
|
@ -882,7 +915,24 @@ fn build_call_shim<'tcx>(
|
|||
);
|
||||
}
|
||||
// BB #1/#2 - return
|
||||
block(&mut blocks, vec![], TerminatorKind::Return, false);
|
||||
// NOTE: If this is an RPITIT in dyn, we also want to coerce
|
||||
// the return type of the function into a `dyn*`.
|
||||
let stmts = if rpitit_shim.is_some() {
|
||||
vec![Statement {
|
||||
source_info,
|
||||
kind: StatementKind::Assign(Box::new((
|
||||
Place::return_place(),
|
||||
Rvalue::Cast(
|
||||
CastKind::PointerCoercion(PointerCoercion::DynStar, CoercionSource::Implicit),
|
||||
Operand::Move(destination),
|
||||
sig.output(),
|
||||
),
|
||||
))),
|
||||
}]
|
||||
} else {
|
||||
vec![]
|
||||
};
|
||||
block(&mut blocks, stmts, TerminatorKind::Return, false);
|
||||
if let Some(Adjustment::RefMut) = rcvr_adjustment {
|
||||
// BB #3 - drop if closure panics
|
||||
block(
|
||||
|
|
|
@ -42,7 +42,10 @@ fn custom_coerce_unsize_info<'tcx>(
|
|||
..
|
||||
})) => Ok(tcx.coerce_unsized_info(impl_def_id)?.custom_kind.unwrap()),
|
||||
impl_source => {
|
||||
bug!("invalid `CoerceUnsized` impl_source: {:?}", impl_source);
|
||||
bug!(
|
||||
"invalid `CoerceUnsized` from {source_ty} to {target_ty}: impl_source: {:?}",
|
||||
impl_source
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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.
|
||||
if tcx.asyncness(fn_def_id).is_async() {
|
||||
return Some(MethodViolationCode::AsyncFn);
|
||||
}
|
||||
let ty = tcx.liberate_late_bound_regions(fn_def_id, ty);
|
||||
|
||||
if tcx.asyncness(fn_def_id).is_async() {
|
||||
// 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() {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
// 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)
|
||||
{
|
||||
Some(MethodViolationCode::ReferencesImplTraitInTrait(tcx.def_span(proj.def_id)))
|
||||
} else {
|
||||
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>;
|
||||
|
||||
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)
|
||||
{
|
||||
ControlFlow::Break(MethodViolationCode::ReferencesImplTraitInTrait(
|
||||
self.tcx.def_span(proj.def_id),
|
||||
))
|
||||
} else {
|
||||
ty.super_visit_with(self)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn provide(providers: &mut Providers) {
|
||||
|
|
|
@ -7,8 +7,8 @@ use rustc_data_structures::stack::ensure_sufficient_stack;
|
|||
use rustc_errors::ErrorGuaranteed;
|
||||
use rustc_hir::def::DefKind;
|
||||
use rustc_hir::lang_items::LangItem;
|
||||
use rustc_infer::infer::DefineOpaqueTypes;
|
||||
use rustc_infer::infer::resolve::OpportunisticRegionResolver;
|
||||
use rustc_infer::infer::{DefineOpaqueTypes, RegionVariableOrigin};
|
||||
use rustc_infer::traits::{ObligationCauseCode, PredicateObligations};
|
||||
use rustc_middle::traits::select::OverflowError;
|
||||
use rustc_middle::traits::{BuiltinImplSource, ImplSource, ImplSourceUserDefinedData};
|
||||
|
@ -18,6 +18,7 @@ use rustc_middle::ty::visit::TypeVisitableExt;
|
|||
use rustc_middle::ty::{self, Term, Ty, TyCtxt, TypingMode, Upcast};
|
||||
use rustc_middle::{bug, span_bug};
|
||||
use rustc_span::symbol::sym;
|
||||
use thin_vec::thin_vec;
|
||||
use tracing::{debug, instrument};
|
||||
|
||||
use super::{
|
||||
|
@ -61,6 +62,9 @@ enum ProjectionCandidate<'tcx> {
|
|||
/// Bounds specified on an object type
|
||||
Object(ty::PolyProjectionPredicate<'tcx>),
|
||||
|
||||
/// Built-in bound for a dyn async fn in trait
|
||||
ObjectRpitit,
|
||||
|
||||
/// From an "impl" (or a "pseudo-impl" returned by select)
|
||||
Select(Selection<'tcx>),
|
||||
}
|
||||
|
@ -827,6 +831,17 @@ fn assemble_candidates_from_object_ty<'cx, 'tcx>(
|
|||
env_predicates,
|
||||
false,
|
||||
);
|
||||
|
||||
// `dyn Trait` automagically project their AFITs to `dyn* Future`.
|
||||
if tcx.is_impl_trait_in_trait(obligation.predicate.def_id)
|
||||
&& let Some(out_trait_def_id) = data.principal_def_id()
|
||||
&& let rpitit_trait_def_id = tcx.parent(obligation.predicate.def_id)
|
||||
&& tcx
|
||||
.supertrait_def_ids(out_trait_def_id)
|
||||
.any(|trait_def_id| trait_def_id == rpitit_trait_def_id)
|
||||
{
|
||||
candidate_set.push_candidate(ProjectionCandidate::ObjectRpitit);
|
||||
}
|
||||
}
|
||||
|
||||
#[instrument(
|
||||
|
@ -1247,6 +1262,8 @@ fn confirm_candidate<'cx, 'tcx>(
|
|||
ProjectionCandidate::Select(impl_source) => {
|
||||
confirm_select_candidate(selcx, obligation, impl_source)
|
||||
}
|
||||
|
||||
ProjectionCandidate::ObjectRpitit => confirm_object_rpitit_candidate(selcx, obligation),
|
||||
};
|
||||
|
||||
// When checking for cycle during evaluation, we compare predicates with
|
||||
|
@ -2034,6 +2051,45 @@ fn confirm_impl_candidate<'cx, 'tcx>(
|
|||
}
|
||||
}
|
||||
|
||||
fn confirm_object_rpitit_candidate<'cx, 'tcx>(
|
||||
selcx: &mut SelectionContext<'cx, 'tcx>,
|
||||
obligation: &ProjectionTermObligation<'tcx>,
|
||||
) -> Progress<'tcx> {
|
||||
let tcx = selcx.tcx();
|
||||
let mut obligations = thin_vec![];
|
||||
|
||||
// Compute an intersection lifetime for all the input components of this GAT.
|
||||
let intersection =
|
||||
selcx.infcx.next_region_var(RegionVariableOrigin::MiscVariable(obligation.cause.span));
|
||||
for component in obligation.predicate.args {
|
||||
match component.unpack() {
|
||||
ty::GenericArgKind::Lifetime(lt) => {
|
||||
obligations.push(obligation.with(tcx, ty::OutlivesPredicate(lt, intersection)));
|
||||
}
|
||||
ty::GenericArgKind::Type(ty) => {
|
||||
obligations.push(obligation.with(tcx, ty::OutlivesPredicate(ty, intersection)));
|
||||
}
|
||||
ty::GenericArgKind::Const(_ct) => {
|
||||
// Consts have no outlives...
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Progress {
|
||||
term: Ty::new_dynamic(
|
||||
tcx,
|
||||
tcx.item_bounds_to_existential_predicates(
|
||||
obligation.predicate.def_id,
|
||||
obligation.predicate.args,
|
||||
),
|
||||
intersection,
|
||||
ty::DynStar,
|
||||
)
|
||||
.into(),
|
||||
obligations,
|
||||
}
|
||||
}
|
||||
|
||||
// Get obligations corresponding to the predicates from the where-clause of the
|
||||
// associated type itself.
|
||||
fn assoc_ty_own_obligations<'cx, 'tcx>(
|
||||
|
|
|
@ -19,6 +19,7 @@ use rustc_middle::traits::{BuiltinImplSource, SignatureMismatchData};
|
|||
use rustc_middle::ty::{self, GenericArgsRef, ToPolyTraitRef, Ty, TyCtxt, Upcast};
|
||||
use rustc_middle::{bug, span_bug};
|
||||
use rustc_span::def_id::DefId;
|
||||
use rustc_type_ir::elaborate;
|
||||
use tracing::{debug, instrument};
|
||||
|
||||
use super::SelectionCandidate::{self, *};
|
||||
|
@ -624,6 +625,12 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> {
|
|||
for assoc_type in assoc_types {
|
||||
let defs: &ty::Generics = tcx.generics_of(assoc_type);
|
||||
|
||||
// When `async_fn_in_dyn_trait` is enabled, we don't need to check the
|
||||
// RPITIT for compatibility, since it's not provided by the user.
|
||||
if tcx.features().async_fn_in_dyn_trait() && tcx.is_impl_trait_in_trait(assoc_type) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if !defs.own_params.is_empty() {
|
||||
tcx.dcx().span_delayed_bug(
|
||||
obligation.cause.span,
|
||||
|
@ -1175,6 +1182,38 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> {
|
|||
ty::ClauseKind::TypeOutlives(outlives).upcast(tcx),
|
||||
));
|
||||
|
||||
// Require that all AFIT will return something that can be coerced into `dyn*`
|
||||
// -- a shim will be responsible for doing the actual coercion to `dyn*`.
|
||||
if let Some(principal) = data.principal() {
|
||||
for supertrait in
|
||||
elaborate::supertraits(tcx, principal.with_self_ty(tcx, source))
|
||||
{
|
||||
if tcx.is_trait_alias(supertrait.def_id()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for &assoc_item in tcx.associated_item_def_ids(supertrait.def_id()) {
|
||||
if !tcx.is_impl_trait_in_trait(assoc_item) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// RPITITs with `Self: Sized` don't need to be checked.
|
||||
if tcx.generics_require_sized_self(assoc_item) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let pointer_like_goal = pointer_like_goal_for_rpitit(
|
||||
tcx,
|
||||
supertrait,
|
||||
assoc_item,
|
||||
&obligation.cause,
|
||||
);
|
||||
|
||||
nested.push(predicate_to_obligation(pointer_like_goal.upcast(tcx)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ImplSource::Builtin(BuiltinImplSource::Misc, nested)
|
||||
}
|
||||
|
||||
|
@ -1280,3 +1319,43 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Compute a goal that some RPITIT (right now, only RPITITs corresponding to Futures)
|
||||
/// implements the `PointerLike` trait, which is a requirement for the RPITIT to be
|
||||
/// coercible to `dyn* Future`, which is itself a requirement for the RPITIT's parent
|
||||
/// trait to be coercible to `dyn Trait`.
|
||||
///
|
||||
/// We do this given a supertrait's substitutions, and then augment the substitutions
|
||||
/// with bound variables to compute the goal universally. Given that `PointerLike` has
|
||||
/// no region requirements (at least for the built-in pointer types), this shouldn't
|
||||
/// *really* matter, but it is the best choice for soundness.
|
||||
fn pointer_like_goal_for_rpitit<'tcx>(
|
||||
tcx: TyCtxt<'tcx>,
|
||||
supertrait: ty::PolyTraitRef<'tcx>,
|
||||
rpitit_item: DefId,
|
||||
cause: &ObligationCause<'tcx>,
|
||||
) -> ty::PolyTraitRef<'tcx> {
|
||||
let mut bound_vars = supertrait.bound_vars().to_vec();
|
||||
|
||||
let args = supertrait.skip_binder().args.extend_to(tcx, rpitit_item, |arg, _| match arg.kind {
|
||||
ty::GenericParamDefKind::Lifetime => {
|
||||
let kind = ty::BoundRegionKind::Named(arg.def_id, tcx.item_name(arg.def_id));
|
||||
bound_vars.push(ty::BoundVariableKind::Region(kind));
|
||||
ty::Region::new_bound(tcx, ty::INNERMOST, ty::BoundRegion {
|
||||
var: ty::BoundVar::from_usize(bound_vars.len() - 1),
|
||||
kind,
|
||||
})
|
||||
.into()
|
||||
}
|
||||
ty::GenericParamDefKind::Type { .. } | ty::GenericParamDefKind::Const { .. } => {
|
||||
unreachable!()
|
||||
}
|
||||
});
|
||||
|
||||
ty::Binder::bind_with_vars(
|
||||
ty::TraitRef::new(tcx, tcx.require_lang_item(LangItem::PointerLike, Some(cause.span)), [
|
||||
Ty::new_projection_from_args(tcx, rpitit_item, args),
|
||||
]),
|
||||
tcx.mk_bound_variable_kinds(&bound_vars),
|
||||
)
|
||||
}
|
||||
|
|
|
@ -48,12 +48,38 @@ fn fn_sig_for_fn_abi<'tcx>(
|
|||
let mut sig = tcx
|
||||
.instantiate_bound_regions_with_erased(tcx.fn_sig(def_id).instantiate(tcx, args));
|
||||
|
||||
// Modify `fn(self, ...)` to `fn(self: *mut Self, ...)`.
|
||||
if let ty::InstanceKind::VTableShim(..) = instance.def {
|
||||
let mut inputs_and_output = sig.inputs_and_output.to_vec();
|
||||
inputs_and_output[0] = Ty::new_mut_ptr(tcx, inputs_and_output[0]);
|
||||
sig.inputs_and_output = tcx.mk_type_list(&inputs_and_output);
|
||||
}
|
||||
|
||||
// Modify `fn() -> impl Future` to `fn() -> dyn* Future`.
|
||||
if let ty::InstanceKind::ReifyShim(def_id, _) = instance.def
|
||||
&& let Some((rpitit_def_id, fn_args)) =
|
||||
tcx.return_position_impl_trait_in_trait_shim_data(def_id)
|
||||
{
|
||||
let fn_args = fn_args.instantiate(tcx, args);
|
||||
let rpitit_args =
|
||||
fn_args.extend_to(tcx, rpitit_def_id, |param, _| match param.kind {
|
||||
ty::GenericParamDefKind::Lifetime => tcx.lifetimes.re_erased.into(),
|
||||
ty::GenericParamDefKind::Type { .. }
|
||||
| ty::GenericParamDefKind::Const { .. } => {
|
||||
unreachable!("rpitit should have no addition ty/ct")
|
||||
}
|
||||
});
|
||||
let dyn_star_ty = Ty::new_dynamic(
|
||||
tcx,
|
||||
tcx.item_bounds_to_existential_predicates(rpitit_def_id, rpitit_args),
|
||||
tcx.lifetimes.re_erased,
|
||||
ty::DynStar,
|
||||
);
|
||||
let mut inputs_and_output = sig.inputs_and_output.to_vec();
|
||||
*inputs_and_output.last_mut().unwrap() = dyn_star_ty;
|
||||
sig.inputs_and_output = tcx.mk_type_list(&inputs_and_output);
|
||||
}
|
||||
|
||||
sig
|
||||
}
|
||||
ty::Closure(def_id, args) => {
|
||||
|
|
20
tests/ui/async-await/dyn/auxiliary/block-on.rs
Normal file
20
tests/ui/async-await/dyn/auxiliary/block-on.rs
Normal file
|
@ -0,0 +1,20 @@
|
|||
//@ edition: 2021
|
||||
|
||||
#![feature(async_closure, noop_waker)]
|
||||
|
||||
use std::future::Future;
|
||||
use std::pin::pin;
|
||||
use std::task::*;
|
||||
|
||||
pub fn block_on<T>(fut: impl Future<Output = T>) -> T {
|
||||
let mut fut = pin!(fut);
|
||||
// Poll loop, just to test the future...
|
||||
let ctx = &mut Context::from_waker(Waker::noop());
|
||||
|
||||
loop {
|
||||
match unsafe { fut.as_mut().poll(ctx) } {
|
||||
Poll::Pending => {}
|
||||
Poll::Ready(t) => break t,
|
||||
}
|
||||
}
|
||||
}
|
40
tests/ui/async-await/dyn/mut-is-pointer-like.rs
Normal file
40
tests/ui/async-await/dyn/mut-is-pointer-like.rs
Normal file
|
@ -0,0 +1,40 @@
|
|||
//@ aux-build:block-on.rs
|
||||
//@ edition: 2021
|
||||
//@ run-pass
|
||||
//@ check-run-results
|
||||
|
||||
#![allow(refining_impl_trait)]
|
||||
#![feature(async_fn_in_dyn_trait)]
|
||||
//~^ WARN the feature `async_fn_in_dyn_trait` is incomplete
|
||||
|
||||
extern crate block_on;
|
||||
|
||||
use std::future::Future;
|
||||
use std::pin::Pin;
|
||||
|
||||
trait AsyncTrait {
|
||||
type Output;
|
||||
|
||||
async fn async_dispatch(self: Pin<&mut Self>) -> Self::Output;
|
||||
}
|
||||
|
||||
impl<F> AsyncTrait for F
|
||||
where
|
||||
F: Future,
|
||||
{
|
||||
type Output = F::Output;
|
||||
|
||||
fn async_dispatch(self: Pin<&mut Self>) -> Pin<&mut Self> {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
block_on::block_on(async {
|
||||
let f = std::pin::pin!(async {
|
||||
println!("hello, world");
|
||||
});
|
||||
let x: Pin<&mut dyn AsyncTrait<Output = ()>> = f;
|
||||
x.async_dispatch().await;
|
||||
});
|
||||
}
|
1
tests/ui/async-await/dyn/mut-is-pointer-like.run.stdout
Normal file
1
tests/ui/async-await/dyn/mut-is-pointer-like.run.stdout
Normal file
|
@ -0,0 +1 @@
|
|||
hello, world
|
11
tests/ui/async-await/dyn/mut-is-pointer-like.stderr
Normal file
11
tests/ui/async-await/dyn/mut-is-pointer-like.stderr
Normal file
|
@ -0,0 +1,11 @@
|
|||
warning: the feature `async_fn_in_dyn_trait` is incomplete and may not be safe to use and/or cause compiler crashes
|
||||
--> $DIR/mut-is-pointer-like.rs:7:12
|
||||
|
|
||||
LL | #![feature(async_fn_in_dyn_trait)]
|
||||
| ^^^^^^^^^^^^^^^^^^^^^
|
||||
|
|
||||
= note: see issue #133119 <https://github.com/rust-lang/rust/issues/133119> for more information
|
||||
= note: `#[warn(incomplete_features)]` on by default
|
||||
|
||||
warning: 1 warning emitted
|
||||
|
32
tests/ui/async-await/dyn/works.rs
Normal file
32
tests/ui/async-await/dyn/works.rs
Normal file
|
@ -0,0 +1,32 @@
|
|||
//@ aux-build:block-on.rs
|
||||
//@ edition: 2021
|
||||
//@ run-pass
|
||||
//@ check-run-results
|
||||
|
||||
#![allow(refining_impl_trait)]
|
||||
#![feature(async_fn_in_dyn_trait)]
|
||||
//~^ WARN the feature `async_fn_in_dyn_trait` is incomplete
|
||||
|
||||
extern crate block_on;
|
||||
|
||||
use std::pin::Pin;
|
||||
use std::future::Future;
|
||||
|
||||
trait AsyncTrait {
|
||||
async fn async_dispatch(&self);
|
||||
}
|
||||
|
||||
impl AsyncTrait for &'static str {
|
||||
fn async_dispatch(&self) -> Pin<Box<impl Future<Output = ()>>> {
|
||||
Box::pin(async move {
|
||||
println!("message from the aether: {self}");
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
block_on::block_on(async {
|
||||
let x: &dyn AsyncTrait = &"hello, world!";
|
||||
x.async_dispatch().await;
|
||||
});
|
||||
}
|
1
tests/ui/async-await/dyn/works.run.stdout
Normal file
1
tests/ui/async-await/dyn/works.run.stdout
Normal file
|
@ -0,0 +1 @@
|
|||
message from the aether: hello, world!
|
11
tests/ui/async-await/dyn/works.stderr
Normal file
11
tests/ui/async-await/dyn/works.stderr
Normal file
|
@ -0,0 +1,11 @@
|
|||
warning: the feature `async_fn_in_dyn_trait` is incomplete and may not be safe to use and/or cause compiler crashes
|
||||
--> $DIR/works.rs:7:12
|
||||
|
|
||||
LL | #![feature(async_fn_in_dyn_trait)]
|
||||
| ^^^^^^^^^^^^^^^^^^^^^
|
||||
|
|
||||
= note: see issue #133119 <https://github.com/rust-lang/rust/issues/133119> for more information
|
||||
= note: `#[warn(incomplete_features)]` on by default
|
||||
|
||||
warning: 1 warning emitted
|
||||
|
23
tests/ui/async-await/dyn/wrong-size.rs
Normal file
23
tests/ui/async-await/dyn/wrong-size.rs
Normal file
|
@ -0,0 +1,23 @@
|
|||
//@ edition: 2021
|
||||
|
||||
#![feature(async_fn_in_dyn_trait)]
|
||||
//~^ WARN the feature `async_fn_in_dyn_trait` is incomplete
|
||||
|
||||
use std::future::Future;
|
||||
|
||||
trait AsyncTrait {
|
||||
async fn async_dispatch(&self);
|
||||
}
|
||||
|
||||
impl AsyncTrait for &'static str {
|
||||
fn async_dispatch(&self) -> impl Future<Output = ()> {
|
||||
async move {
|
||||
// The implementor must box the future...
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let x: &dyn AsyncTrait = &"hello, world!";
|
||||
//~^ ERROR `impl Future<Output = ()>` needs to have the same ABI as a pointer
|
||||
}
|
21
tests/ui/async-await/dyn/wrong-size.stderr
Normal file
21
tests/ui/async-await/dyn/wrong-size.stderr
Normal file
|
@ -0,0 +1,21 @@
|
|||
warning: the feature `async_fn_in_dyn_trait` is incomplete and may not be safe to use and/or cause compiler crashes
|
||||
--> $DIR/wrong-size.rs:3:12
|
||||
|
|
||||
LL | #![feature(async_fn_in_dyn_trait)]
|
||||
| ^^^^^^^^^^^^^^^^^^^^^
|
||||
|
|
||||
= note: see issue #133119 <https://github.com/rust-lang/rust/issues/133119> for more information
|
||||
= note: `#[warn(incomplete_features)]` on by default
|
||||
|
||||
error[E0277]: `impl Future<Output = ()>` needs to have the same ABI as a pointer
|
||||
--> $DIR/wrong-size.rs:21:30
|
||||
|
|
||||
LL | let x: &dyn AsyncTrait = &"hello, world!";
|
||||
| ^^^^^^^^^^^^^^^^ `impl Future<Output = ()>` needs to be a pointer-like type
|
||||
|
|
||||
= help: the trait `for<'a> PointerLike` is not implemented for `impl Future<Output = ()>`
|
||||
= note: required for the cast from `&&'static str` to `&dyn AsyncTrait`
|
||||
|
||||
error: aborting due to 1 previous error; 1 warning emitted
|
||||
|
||||
For more information about this error, try `rustc --explain E0277`.
|
14
tests/ui/feature-gates/feature-gate-async-fn-in-dyn-trait.rs
Normal file
14
tests/ui/feature-gates/feature-gate-async-fn-in-dyn-trait.rs
Normal file
|
@ -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() {}
|
|
@ -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`.
|
|
@ -0,0 +1,13 @@
|
|||
//@ check-pass
|
||||
|
||||
// Make sure that we don't enforce that an RPIT that has `where Self: Sized` is pointer-like.
|
||||
|
||||
trait Foo {
|
||||
fn foo() -> impl Sized where Self: Sized {}
|
||||
}
|
||||
|
||||
impl Foo for () {}
|
||||
|
||||
fn main() {
|
||||
let x: &dyn Foo = &();
|
||||
}
|
Loading…
Add table
Reference in a new issue