Descriptive error when users try to combine RPITIT/AFIT with specialization

This commit is contained in:
Michael Goulet 2023-02-28 02:03:26 +00:00
parent 6290ae92b2
commit ecac8fd5af
7 changed files with 83 additions and 65 deletions

View File

@ -792,8 +792,10 @@ fn check_impl_items_against_trait<'tcx>(
trait_def.must_implement_one_of.as_deref(); trait_def.must_implement_one_of.as_deref();
for &trait_item_id in tcx.associated_item_def_ids(impl_trait_ref.def_id) { for &trait_item_id in tcx.associated_item_def_ids(impl_trait_ref.def_id) {
let is_implemented = ancestors let leaf_def = ancestors.leaf_def(tcx, trait_item_id);
.leaf_def(tcx, trait_item_id)
let is_implemented = leaf_def
.as_ref()
.map_or(false, |node_item| node_item.item.defaultness(tcx).has_value()); .map_or(false, |node_item| node_item.item.defaultness(tcx).has_value());
if !is_implemented && tcx.impl_defaultness(impl_id).is_final() { if !is_implemented && tcx.impl_defaultness(impl_id).is_final() {
@ -801,8 +803,8 @@ fn check_impl_items_against_trait<'tcx>(
} }
// true if this item is specifically implemented in this impl // true if this item is specifically implemented in this impl
let is_implemented_here = ancestors let is_implemented_here = leaf_def
.leaf_def(tcx, trait_item_id) .as_ref()
.map_or(false, |node_item| !node_item.defining_node.is_from_trait()); .map_or(false, |node_item| !node_item.defining_node.is_from_trait());
if !is_implemented_here { if !is_implemented_here {
@ -831,6 +833,36 @@ fn check_impl_items_against_trait<'tcx>(
} }
} }
} }
if let Some(leaf_def) = &leaf_def
&& !leaf_def.is_final()
&& let def_id = leaf_def.item.def_id
&& tcx.impl_method_has_trait_impl_trait_tys(def_id)
{
let def_kind = tcx.def_kind(def_id);
let descr = tcx.def_kind_descr(def_kind, def_id);
let (msg, feature) = if tcx.asyncness(def_id).is_async() {
(
format!("async {descr} in trait cannot be specialized"),
sym::async_fn_in_trait,
)
} else {
(
format!(
"{descr} with return-position `impl Trait` in trait cannot be specialized"
),
sym::return_position_impl_trait_in_trait,
)
};
tcx.sess
.struct_span_err(tcx.def_span(def_id), msg)
.note(format!(
"specialization behaves in inconsistent and \
surprising ways with `#![feature({feature})]`, \
and for now is disallowed"
))
.emit();
}
} }
if !missing_items.is_empty() { if !missing_items.is_empty() {

View File

@ -1101,34 +1101,6 @@ fn should_encode_const(def_kind: DefKind) -> bool {
} }
} }
fn should_encode_trait_impl_trait_tys(tcx: TyCtxt<'_>, def_id: DefId) -> bool {
if tcx.def_kind(def_id) != DefKind::AssocFn {
return false;
}
let Some(item) = tcx.opt_associated_item(def_id) else { return false; };
if item.container != ty::AssocItemContainer::ImplContainer {
return false;
}
let Some(trait_item_def_id) = item.trait_item_def_id else { return false; };
// FIXME(RPITIT): This does a somewhat manual walk through the signature
// of the trait fn to look for any RPITITs, but that's kinda doing a lot
// of work. We can probably remove this when we refactor RPITITs to be
// associated types.
tcx.fn_sig(trait_item_def_id).subst_identity().skip_binder().output().walk().any(|arg| {
if let ty::GenericArgKind::Type(ty) = arg.unpack()
&& let ty::Alias(ty::Projection, data) = ty.kind()
&& tcx.def_kind(data.def_id) == DefKind::ImplTraitPlaceholder
{
true
} else {
false
}
})
}
// Return `false` to avoid encoding impl trait in trait, while we don't use the query. // Return `false` to avoid encoding impl trait in trait, while we don't use the query.
fn should_encode_fn_impl_trait_in_trait<'tcx>(_tcx: TyCtxt<'tcx>, _def_id: DefId) -> bool { fn should_encode_fn_impl_trait_in_trait<'tcx>(_tcx: TyCtxt<'tcx>, _def_id: DefId) -> bool {
false false
@ -1211,7 +1183,7 @@ impl<'a, 'tcx> EncodeContext<'a, 'tcx> {
if let DefKind::Enum | DefKind::Struct | DefKind::Union = def_kind { if let DefKind::Enum | DefKind::Struct | DefKind::Union = def_kind {
self.encode_info_for_adt(def_id); self.encode_info_for_adt(def_id);
} }
if should_encode_trait_impl_trait_tys(tcx, def_id) if tcx.impl_method_has_trait_impl_trait_tys(def_id)
&& let Ok(table) = self.tcx.collect_return_position_impl_trait_in_trait_tys(def_id) && let Ok(table) = self.tcx.collect_return_position_impl_trait_in_trait_tys(def_id)
{ {
record!(self.tables.trait_impl_trait_tys[def_id] <- table); record!(self.tables.trait_impl_trait_tys[def_id] <- table);

View File

@ -2541,6 +2541,34 @@ impl<'tcx> TyCtxt<'tcx> {
} }
def_id def_id
} }
pub fn impl_method_has_trait_impl_trait_tys(self, def_id: DefId) -> bool {
if self.def_kind(def_id) != DefKind::AssocFn {
return false;
}
let Some(item) = self.opt_associated_item(def_id) else { return false; };
if item.container != ty::AssocItemContainer::ImplContainer {
return false;
}
let Some(trait_item_def_id) = item.trait_item_def_id else { return false; };
// FIXME(RPITIT): This does a somewhat manual walk through the signature
// of the trait fn to look for any RPITITs, but that's kinda doing a lot
// of work. We can probably remove this when we refactor RPITITs to be
// associated types.
self.fn_sig(trait_item_def_id).subst_identity().skip_binder().output().walk().any(|arg| {
if let ty::GenericArgKind::Type(ty) = arg.unpack()
&& let ty::Alias(ty::Projection, data) = ty.kind()
&& self.def_kind(data.def_id) == DefKind::ImplTraitPlaceholder
{
true
} else {
false
}
})
}
} }
/// Yields the parent function's `LocalDefId` if `def_id` is an `impl Trait` definition. /// Yields the parent function's `LocalDefId` if `def_id` is an `impl Trait` definition.

View File

@ -1307,25 +1307,8 @@ fn assemble_candidate_for_impl_trait_in_trait<'cx, 'tcx>(
let _ = selcx.infcx.commit_if_ok(|_| { let _ = selcx.infcx.commit_if_ok(|_| {
match selcx.select(&obligation.with(tcx, trait_predicate)) { match selcx.select(&obligation.with(tcx, trait_predicate)) {
Ok(Some(super::ImplSource::UserDefined(data))) => { Ok(Some(super::ImplSource::UserDefined(data))) => {
let Ok(leaf_def) = specialization_graph::assoc_def(tcx, data.impl_def_id, trait_fn_def_id) else {
return Err(());
};
// Only reveal a specializable default if we're past type-checking
// and the obligation is monomorphic, otherwise passes such as
// transmute checking and polymorphic MIR optimizations could
// get a result which isn't correct for all monomorphizations.
if leaf_def.is_final()
|| (obligation.param_env.reveal() == Reveal::All
&& !selcx
.infcx
.resolve_vars_if_possible(obligation.predicate.trait_ref(tcx))
.still_further_specializable())
{
candidate_set.push_candidate(ProjectionCandidate::ImplTraitInTrait(data)); candidate_set.push_candidate(ProjectionCandidate::ImplTraitInTrait(data));
Ok(()) Ok(())
} else {
Err(())
}
} }
Ok(None) => { Ok(None) => {
candidate_set.mark_ambiguous(); candidate_set.mark_ambiguous();
@ -2216,7 +2199,8 @@ fn confirm_impl_trait_in_trait_candidate<'tcx>(
Ok(assoc_ty) => assoc_ty, Ok(assoc_ty) => assoc_ty,
Err(guar) => return Progress::error(tcx, guar), Err(guar) => return Progress::error(tcx, guar),
}; };
if !leaf_def.item.defaultness(tcx).has_value() { // We don't support specialization for RPITITs anyways... yet.
if !leaf_def.is_final() {
return Progress { term: tcx.ty_error_misc().into(), obligations }; return Progress { term: tcx.ty_error_misc().into(), obligations };
} }

View File

@ -7,20 +7,13 @@ LL | #![feature(async_fn_in_trait)]
= note: see issue #91611 <https://github.com/rust-lang/rust/issues/91611> for more information = note: see issue #91611 <https://github.com/rust-lang/rust/issues/91611> for more information
= note: `#[warn(incomplete_features)]` on by default = note: `#[warn(incomplete_features)]` on by default
error[E0053]: method `foo` has an incompatible type for trait error: async associated function in trait cannot be specialized
--> $DIR/dont-project-to-specializable-projection.rs:14:35 --> $DIR/dont-project-to-specializable-projection.rs:14:5
| |
LL | default async fn foo(_: T) -> &'static str { LL | default async fn foo(_: T) -> &'static str {
| ^^^^^^^^^^^^ expected associated type, found future | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
| |
note: type in trait = note: specialization behaves in inconsistent and surprising ways with `#![feature(async_fn_in_trait)]`, and for now is disallowed
--> $DIR/dont-project-to-specializable-projection.rs:10:27
|
LL | async fn foo(_: T) -> &'static str;
| ^^^^^^^^^^^^
= note: expected signature `fn(_) -> impl Future<Output = &'static str>`
found signature `fn(_) -> impl Future<Output = &'static str>`
error: aborting due to previous error; 1 warning emitted error: aborting due to previous error; 1 warning emitted
For more information about this error, try `rustc --explain E0053`.

View File

@ -15,6 +15,7 @@ where
{ {
fn bar(&self) -> U { fn bar(&self) -> U {
//~^ ERROR method `bar` has an incompatible type for trait //~^ ERROR method `bar` has an incompatible type for trait
//~| ERROR method with return-position `impl Trait` in trait cannot be specialized
*self *self
} }
} }

View File

@ -18,6 +18,14 @@ LL | fn bar(&self) -> impl Sized;
= note: expected signature `fn(&U) -> impl Sized` = note: expected signature `fn(&U) -> impl Sized`
found signature `fn(&U) -> U` found signature `fn(&U) -> U`
error: aborting due to previous error error: method with return-position `impl Trait` in trait cannot be specialized
--> $DIR/specialization-broken.rs:16:5
|
LL | fn bar(&self) -> U {
| ^^^^^^^^^^^^^^^^^^
|
= note: specialization behaves in inconsistent and surprising ways with `#![feature(return_position_impl_trait_in_trait)]`, and for now is disallowed
error: aborting due to 2 previous errors
For more information about this error, try `rustc --explain E0053`. For more information about this error, try `rustc --explain E0053`.