Suggest lifetime bound in illegal Copy impl

This commit is contained in:
Michael Goulet 2022-12-09 20:59:26 +00:00
parent 333c6bf523
commit 16cfadbfe8
3 changed files with 102 additions and 56 deletions

View File

@ -7,14 +7,14 @@ use rustc_hir as hir;
use rustc_hir::def_id::{DefId, LocalDefId}; use rustc_hir::def_id::{DefId, LocalDefId};
use rustc_hir::lang_items::LangItem; use rustc_hir::lang_items::LangItem;
use rustc_hir::ItemKind; use rustc_hir::ItemKind;
use rustc_infer::infer;
use rustc_infer::infer::outlives::env::OutlivesEnvironment; use rustc_infer::infer::outlives::env::OutlivesEnvironment;
use rustc_infer::infer::TyCtxtInferExt; use rustc_infer::infer::TyCtxtInferExt;
use rustc_infer::infer::{self, RegionResolutionError};
use rustc_middle::ty::adjustment::CoerceUnsizedInfo; use rustc_middle::ty::adjustment::CoerceUnsizedInfo;
use rustc_middle::ty::{self, suggest_constraining_type_params, Ty, TyCtxt, TypeVisitable}; use rustc_middle::ty::{self, suggest_constraining_type_params, Ty, TyCtxt, TypeVisitable};
use rustc_trait_selection::traits::error_reporting::TypeErrCtxtExt; use rustc_trait_selection::traits::error_reporting::TypeErrCtxtExt;
use rustc_trait_selection::traits::misc::{ use rustc_trait_selection::traits::misc::{
type_allowed_to_implement_copy, CopyImplementationError, type_allowed_to_implement_copy, CopyImplementationError, InfringingFieldsReason,
}; };
use rustc_trait_selection::traits::predicate_for_trait_def; use rustc_trait_selection::traits::predicate_for_trait_def;
use rustc_trait_selection::traits::{self, ObligationCause}; use rustc_trait_selection::traits::{self, ObligationCause};
@ -99,50 +99,70 @@ fn visit_implementation_of_copy(tcx: TyCtxt<'_>, impl_did: LocalDefId) {
let mut errors: BTreeMap<_, Vec<_>> = Default::default(); let mut errors: BTreeMap<_, Vec<_>> = Default::default();
let mut bounds = vec![]; let mut bounds = vec![];
for (field, ty) in fields { for (field, ty, reason) in fields {
let field_span = tcx.def_span(field.did); let field_span = tcx.def_span(field.did);
let field_ty_span = match tcx.hir().get_if_local(field.did) {
Some(hir::Node::Field(field_def)) => field_def.ty.span,
_ => field_span,
};
err.span_label(field_span, "this field does not implement `Copy`"); err.span_label(field_span, "this field does not implement `Copy`");
// Spin up a new FulfillmentContext, so we can get the _precise_ reason
// why this field does not implement Copy. This is useful because sometimes
// it is not immediately clear why Copy is not implemented for a field, since
// all we point at is the field itself.
let infcx = tcx.infer_ctxt().ignoring_regions().build();
for error in traits::fully_solve_bound(
&infcx,
traits::ObligationCause::dummy_with_span(field_ty_span),
param_env,
ty,
tcx.require_lang_item(LangItem::Copy, Some(span)),
) {
let error_predicate = error.obligation.predicate;
// Only note if it's not the root obligation, otherwise it's trivial and
// should be self-explanatory (i.e. a field literally doesn't implement Copy).
// FIXME: This error could be more descriptive, especially if the error_predicate match reason {
// contains a foreign type or if it's a deeply nested type... InfringingFieldsReason::Fulfill(fulfillment_errors) => {
if error_predicate != error.root_obligation.predicate { for error in fulfillment_errors {
errors let error_predicate = error.obligation.predicate;
.entry((ty.to_string(), error_predicate.to_string())) // Only note if it's not the root obligation, otherwise it's trivial and
.or_default() // should be self-explanatory (i.e. a field literally doesn't implement Copy).
.push(error.obligation.cause.span);
// FIXME: This error could be more descriptive, especially if the error_predicate
// contains a foreign type or if it's a deeply nested type...
if error_predicate != error.root_obligation.predicate {
errors
.entry((ty.to_string(), error_predicate.to_string()))
.or_default()
.push(error.obligation.cause.span);
}
if let ty::PredicateKind::Clause(ty::Clause::Trait(
ty::TraitPredicate {
trait_ref,
polarity: ty::ImplPolarity::Positive,
..
},
)) = error_predicate.kind().skip_binder()
{
let ty = trait_ref.self_ty();
if let ty::Param(_) = ty.kind() {
bounds.push((
format!("{ty}"),
trait_ref.print_only_trait_path().to_string(),
Some(trait_ref.def_id),
));
}
}
}
} }
if let ty::PredicateKind::Clause(ty::Clause::Trait(ty::TraitPredicate { InfringingFieldsReason::Regions(region_errors) => {
trait_ref, for error in region_errors {
polarity: ty::ImplPolarity::Positive, let ty = ty.to_string();
.. match error {
})) = error_predicate.kind().skip_binder() RegionResolutionError::ConcreteFailure(origin, a, b) => {
{ let predicate = format!("{b}: {a}");
let ty = trait_ref.self_ty(); errors
if let ty::Param(_) = ty.kind() { .entry((ty.clone(), predicate.clone()))
bounds.push(( .or_default()
format!("{ty}"), .push(origin.span());
trait_ref.print_only_trait_path().to_string(), if let ty::RegionKind::ReEarlyBound(ebr) = *b && ebr.has_name() {
Some(trait_ref.def_id), bounds.push((b.to_string(), a.to_string(), None));
)); }
}
RegionResolutionError::GenericBoundFailure(origin, a, b) => {
let predicate = format!("{a}: {b}");
errors
.entry((ty.clone(), predicate.clone()))
.or_default()
.push(origin.span());
if let infer::region_constraints::GenericKind::Param(_) = a {
bounds.push((a.to_string(), b.to_string(), None));
}
}
_ => continue,
}
} }
} }
} }

View File

@ -4,21 +4,25 @@ use crate::traits::{self, ObligationCause};
use rustc_data_structures::fx::FxIndexSet; use rustc_data_structures::fx::FxIndexSet;
use rustc_hir as hir; use rustc_hir as hir;
use rustc_infer::infer::outlives::env::OutlivesEnvironment; use rustc_infer::infer::{RegionResolutionError, TyCtxtInferExt};
use rustc_infer::infer::TyCtxtInferExt; use rustc_infer::{infer::outlives::env::OutlivesEnvironment, traits::FulfillmentError};
use rustc_middle::ty::{self, Ty, TyCtxt, TypeVisitable}; use rustc_middle::ty::{self, Ty, TyCtxt, TypeVisitable};
use crate::traits::error_reporting::TypeErrCtxtExt; use crate::traits::error_reporting::TypeErrCtxtExt;
use super::outlives_bounds::InferCtxtExt; use super::outlives_bounds::InferCtxtExt;
#[derive(Clone)]
pub enum CopyImplementationError<'tcx> { pub enum CopyImplementationError<'tcx> {
InfrigingFields(Vec<(&'tcx ty::FieldDef, Ty<'tcx>)>), InfrigingFields(Vec<(&'tcx ty::FieldDef, Ty<'tcx>, InfringingFieldsReason<'tcx>)>),
NotAnAdt, NotAnAdt,
HasDestructor, HasDestructor,
} }
pub enum InfringingFieldsReason<'tcx> {
Fulfill(Vec<FulfillmentError<'tcx>>),
Regions(Vec<RegionResolutionError<'tcx>>),
}
/// Checks that the fields of the type (an ADT) all implement copy. /// Checks that the fields of the type (an ADT) all implement copy.
/// ///
/// If fields don't implement copy, return an error containing a list of /// If fields don't implement copy, return an error containing a list of
@ -60,22 +64,27 @@ pub fn type_allowed_to_implement_copy<'tcx>(
if ty.references_error() { if ty.references_error() {
continue; continue;
} }
let span = tcx.def_span(field.did);
let field_span = tcx.def_span(field.did);
let field_ty_span = match tcx.hir().get_if_local(field.did) {
Some(hir::Node::Field(field_def)) => field_def.ty.span,
_ => field_span,
};
// FIXME(compiler-errors): This gives us better spans for bad // FIXME(compiler-errors): This gives us better spans for bad
// projection types like in issue-50480. // projection types like in issue-50480.
// If the ADT has substs, point to the cause we are given. // If the ADT has substs, point to the cause we are given.
// If it does not, then this field probably doesn't normalize // If it does not, then this field probably doesn't normalize
// to begin with, and point to the bad field's span instead. // to begin with, and point to the bad field's span instead.
let cause = if field let normalization_cause = if field
.ty(tcx, traits::InternalSubsts::identity_for_item(tcx, adt.did())) .ty(tcx, traits::InternalSubsts::identity_for_item(tcx, adt.did()))
.has_non_region_param() .has_non_region_param()
{ {
parent_cause.clone() parent_cause.clone()
} else { } else {
ObligationCause::dummy_with_span(span) ObligationCause::dummy_with_span(field_ty_span)
}; };
let ty = ocx.normalize(&normalization_cause, param_env, ty);
let ty = ocx.normalize(&cause, param_env, ty);
let normalization_errors = ocx.select_where_possible(); let normalization_errors = ocx.select_where_possible();
if !normalization_errors.is_empty() { if !normalization_errors.is_empty() {
// Don't report this as a field that doesn't implement Copy, // Don't report this as a field that doesn't implement Copy,
@ -84,9 +93,15 @@ pub fn type_allowed_to_implement_copy<'tcx>(
continue; continue;
} }
ocx.register_bound(cause, param_env, ty, copy_def_id); ocx.register_bound(
if !ocx.select_all_or_error().is_empty() { ObligationCause::dummy_with_span(field_ty_span),
infringing.push((field, ty)); param_env,
ty,
copy_def_id,
);
let errors = ocx.select_all_or_error();
if !errors.is_empty() {
infringing.push((field, ty, InfringingFieldsReason::Fulfill(errors)));
} }
// Check regions assuming the self type of the impl is WF // Check regions assuming the self type of the impl is WF
@ -103,8 +118,9 @@ pub fn type_allowed_to_implement_copy<'tcx>(
outlives_env.region_bound_pairs(), outlives_env.region_bound_pairs(),
param_env, param_env,
); );
if !infcx.resolve_regions(&outlives_env).is_empty() { let errors = infcx.resolve_regions(&outlives_env);
infringing.push((field, ty)); if !errors.is_empty() {
infringing.push((field, ty, InfringingFieldsReason::Regions(errors)));
} }
} }
} }

View File

@ -6,6 +6,16 @@ LL | struct Bar<'lt>(Foo<'lt>);
... ...
LL | impl<'any> Copy for Bar<'any> {} LL | impl<'any> Copy for Bar<'any> {}
| ^^^^^^^^^ | ^^^^^^^^^
|
note: the `Copy` impl for `Foo<'any>` requires that `'any: 'static`
--> $DIR/copy-is-not-modulo-regions.rs:10:17
|
LL | struct Bar<'lt>(Foo<'lt>);
| ^^^^^^^^
help: consider restricting type parameter `'any`
|
LL | impl<'any: 'static> Copy for Bar<'any> {}
| +++++++++
error: aborting due to previous error error: aborting due to previous error