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

Check ADT fields for copy implementations considering regions #105102

Merged
merged 5 commits into from
Jan 21, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
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
110 changes: 66 additions & 44 deletions compiler/rustc_hir_analysis/src/coherence/builtin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,15 @@ use rustc_hir as hir;
use rustc_hir::def_id::{DefId, LocalDefId};
use rustc_hir::lang_items::LangItem;
use rustc_hir::ItemKind;
use rustc_infer::infer;
use rustc_infer::infer::outlives::env::OutlivesEnvironment;
use rustc_infer::infer::TyCtxtInferExt;
use rustc_infer::infer::{self, RegionResolutionError};
use rustc_middle::ty::adjustment::CoerceUnsizedInfo;
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::misc::{can_type_implement_copy, CopyImplementationError};
use rustc_trait_selection::traits::misc::{
type_allowed_to_implement_copy, CopyImplementationError, InfringingFieldsReason,
};
use rustc_trait_selection::traits::predicate_for_trait_def;
use rustc_trait_selection::traits::{self, ObligationCause};
use std::collections::BTreeMap;
Expand Down Expand Up @@ -82,7 +84,7 @@ fn visit_implementation_of_copy(tcx: TyCtxt<'_>, impl_did: LocalDefId) {
};

let cause = traits::ObligationCause::misc(span, impl_hir_id);
match can_type_implement_copy(tcx, param_env, self_type, cause) {
match type_allowed_to_implement_copy(tcx, param_env, self_type, cause) {
Ok(()) => {}
Err(CopyImplementationError::InfrigingFields(fields)) => {
let mut err = struct_span_err!(
Expand All @@ -97,50 +99,70 @@ fn visit_implementation_of_copy(tcx: TyCtxt<'_>, impl_did: LocalDefId) {
let mut errors: BTreeMap<_, Vec<_>> = Default::default();
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_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`");
// 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
// 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);

match reason {
InfringingFieldsReason::Fulfill(fulfillment_errors) => {
for error in fulfillment_errors {
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
// 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 {
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),
));
InfringingFieldsReason::Regions(region_errors) => {
for error in region_errors {
let ty = ty.to_string();
match error {
RegionResolutionError::ConcreteFailure(origin, a, b) => {
let predicate = format!("{b}: {a}");
errors
.entry((ty.clone(), predicate.clone()))
.or_default()
.push(origin.span());
if let ty::RegionKind::ReEarlyBound(ebr) = *b && ebr.has_name() {
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,
}
}
}
}
Expand Down
18 changes: 10 additions & 8 deletions compiler/rustc_lint/src/builtin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ use rustc_span::symbol::{kw, sym, Ident, Symbol};
use rustc_span::{BytePos, InnerSpan, Span};
use rustc_target::abi::{Abi, VariantIdx};
use rustc_trait_selection::infer::{InferCtxtExt, TyCtxtInferExt};
use rustc_trait_selection::traits::{self, misc::can_type_implement_copy, EvaluationResult};
use rustc_trait_selection::traits::{self, misc::type_allowed_to_implement_copy};

use crate::nonstandard_style::{method_context, MethodLateContext};

Expand Down Expand Up @@ -709,12 +709,14 @@ impl<'tcx> LateLintPass<'tcx> for MissingCopyImplementations {

// We shouldn't recommend implementing `Copy` on stateful things,
// such as iterators.
if let Some(iter_trait) = cx.tcx.get_diagnostic_item(sym::Iterator) {
if cx.tcx.infer_ctxt().build().type_implements_trait(iter_trait, [ty], param_env)
== EvaluationResult::EvaluatedToOk
{
return;
}
if let Some(iter_trait) = cx.tcx.get_diagnostic_item(sym::Iterator)
&& cx.tcx
.infer_ctxt()
.build()
.type_implements_trait(iter_trait, [ty], param_env)
.must_apply_modulo_regions()
{
return;
}

// Default value of clippy::trivially_copy_pass_by_ref
Expand All @@ -726,7 +728,7 @@ impl<'tcx> LateLintPass<'tcx> for MissingCopyImplementations {
}
}

if can_type_implement_copy(
if type_allowed_to_implement_copy(
cx.tcx,
param_env,
ty,
Expand Down
93 changes: 70 additions & 23 deletions compiler/rustc_trait_selection/src/traits/misc.rs
Original file line number Diff line number Diff line change
@@ -1,29 +1,36 @@
//! Miscellaneous type-system utilities that are too small to deserve their own modules.

use crate::infer::InferCtxtExt as _;
use crate::traits::{self, ObligationCause};

use rustc_data_structures::fx::FxIndexSet;
use rustc_hir as hir;
use rustc_infer::infer::TyCtxtInferExt;
use rustc_infer::infer::{RegionResolutionError, TyCtxtInferExt};
use rustc_infer::{infer::outlives::env::OutlivesEnvironment, traits::FulfillmentError};
use rustc_middle::ty::{self, Ty, TyCtxt, TypeVisitable};

use crate::traits::error_reporting::TypeErrCtxtExt;
use super::outlives_bounds::InferCtxtExt;

#[derive(Clone)]
pub enum CopyImplementationError<'tcx> {
InfrigingFields(Vec<(&'tcx ty::FieldDef, Ty<'tcx>)>),
InfrigingFields(Vec<(&'tcx ty::FieldDef, Ty<'tcx>, InfringingFieldsReason<'tcx>)>),
NotAnAdt,
HasDestructor,
}

pub fn can_type_implement_copy<'tcx>(
pub enum InfringingFieldsReason<'tcx> {
Fulfill(Vec<FulfillmentError<'tcx>>),
Regions(Vec<RegionResolutionError<'tcx>>),
}

/// 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
/// those violating fields. If it's not an ADT, returns `Err(NotAnAdt)`.
pub fn type_allowed_to_implement_copy<'tcx>(
tcx: TyCtxt<'tcx>,
param_env: ty::ParamEnv<'tcx>,
self_type: Ty<'tcx>,
parent_cause: ObligationCause<'tcx>,
) -> Result<(), CopyImplementationError<'tcx>> {
// FIXME: (@jroesch) float this code up
let infcx = tcx.infer_ctxt().build();
let (adt, substs) = match self_type.kind() {
// These types used to have a builtin impl.
// Now libcore provides that impl.
Expand All @@ -42,42 +49,82 @@ pub fn can_type_implement_copy<'tcx>(
_ => return Err(CopyImplementationError::NotAnAdt),
};

let copy_def_id = tcx.require_lang_item(hir::LangItem::Copy, Some(parent_cause.span));

let mut infringing = Vec::new();
for variant in adt.variants() {
for field in &variant.fields {
let ty = field.ty(tcx, substs);
if ty.references_error() {
// Do this per-field to get better error messages.
let infcx = tcx.infer_ctxt().build();
let ocx = traits::ObligationCtxt::new(&infcx);

let unnormalized_ty = field.ty(tcx, substs);
if unnormalized_ty.references_error() {
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
// projection types like in issue-50480.
// If the ADT has substs, point to the cause we are given.
// If it does not, then this field probably doesn't normalize
// 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()))
.has_non_region_param()
{
parent_cause.clone()
} else {
ObligationCause::dummy_with_span(span)
};
match traits::fully_normalize(&infcx, cause, param_env, ty) {
Ok(ty) => {
if !infcx.type_is_copy_modulo_regions(param_env, ty, span) {
infringing.push((field, ty));
}
}
Err(errors) => {
infcx.err_ctxt().report_fulfillment_errors(&errors, None);
}
ObligationCause::dummy_with_span(field_ty_span)
};
let ty = ocx.normalize(&normalization_cause, param_env, unnormalized_ty);
let normalization_errors = ocx.select_where_possible();
if !normalization_errors.is_empty() {
tcx.sess.delay_span_bug(field_span, format!("couldn't normalize struct field `{unnormalized_ty}` when checking Copy implementation"));
continue;
}

ocx.register_bound(
ObligationCause::dummy_with_span(field_ty_span),
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
let outlives_env = OutlivesEnvironment::with_bounds(
param_env,
Some(&infcx),
infcx.implied_bounds_tys(
param_env,
parent_cause.body_id,
FxIndexSet::from_iter([self_type]),
),
);
infcx.process_registered_region_obligations(
outlives_env.region_bound_pairs(),
param_env,
);
let errors = infcx.resolve_regions(&outlives_env);
if !errors.is_empty() {
infringing.push((field, ty, InfringingFieldsReason::Regions(errors)));
}
}
}

if !infringing.is_empty() {
return Err(CopyImplementationError::InfrigingFields(infringing));
}

if adt.has_dtor(tcx) {
return Err(CopyImplementationError::HasDestructor);
}
Expand Down
4 changes: 2 additions & 2 deletions src/tools/clippy/clippy_lints/src/needless_pass_by_value.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ use rustc_span::symbol::kw;
use rustc_span::{sym, Span};
use rustc_target::spec::abi::Abi;
use rustc_trait_selection::traits;
use rustc_trait_selection::traits::misc::can_type_implement_copy;
use rustc_trait_selection::traits::misc::type_allowed_to_implement_copy;
use std::borrow::Cow;

declare_clippy_lint! {
Expand Down Expand Up @@ -200,7 +200,7 @@ impl<'tcx> LateLintPass<'tcx> for NeedlessPassByValue {
let sugg = |diag: &mut Diagnostic| {
if let ty::Adt(def, ..) = ty.kind() {
if let Some(span) = cx.tcx.hir().span_if_local(def.did()) {
if can_type_implement_copy(
if type_allowed_to_implement_copy(
cx.tcx,
cx.param_env,
ty,
Expand Down
10 changes: 10 additions & 0 deletions tests/ui/traits/copy-impl-cannot-normalize.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,16 @@ error[E0277]: the trait bound `T: TraitFoo` is not satisfied
LL | impl<T> Copy for Foo<T> {}
| ^^^^^^ the trait `TraitFoo` is not implemented for `T`
|
note: required for `Foo<T>` to implement `Clone`
--> $DIR/copy-impl-cannot-normalize.rs:12:9
|
LL | impl<T> Clone for Foo<T>
| ^^^^^ ^^^^^^
LL | where
LL | T: TraitFoo,
| -------- unsatisfied trait bound introduced here
note: required by a bound in `Copy`
--> $SRC_DIR/core/src/marker.rs:LL:COL
help: consider restricting type parameter `T`
|
LL | impl<T: TraitFoo> Copy for Foo<T> {}
Expand Down
22 changes: 22 additions & 0 deletions tests/ui/traits/copy-is-not-modulo-regions.not_static.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
error[E0204]: the trait `Copy` may not be implemented for this type
--> $DIR/copy-is-not-modulo-regions.rs:13:21
|
LL | struct Bar<'lt>(Foo<'lt>);
| -------- this field does not implement `Copy`
...
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

For more information about this error, try `rustc --explain E0204`.
19 changes: 19 additions & 0 deletions tests/ui/traits/copy-is-not-modulo-regions.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// revisions: not_static yes_static
//[yes_static] check-pass

#[derive(Clone)]
struct Foo<'lt>(&'lt ());

impl Copy for Foo<'static> {}

#[derive(Clone)]
struct Bar<'lt>(Foo<'lt>);

#[cfg(not_static)]
impl<'any> Copy for Bar<'any> {}
//[not_static]~^ the trait `Copy` may not be implemented for this type

#[cfg(yes_static)]
impl<'any> Copy for Bar<'static> {}

fn main() {}
Loading