From a754fcaa43e02472ff9e1971e0e7085f58a6f041 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Esteban=20K=C3=BCber?= Date: Tue, 21 Nov 2023 01:09:11 +0000 Subject: [PATCH] On "this .clone() is on the reference", provide more info When encountering a case where `let x: T = (val: &T).clone();` and `T: !Clone`, already mention that the reference is being cloned. We now also suggest `#[derive(Clone)]` not only on `T` but also on type parameters to satisfy blanket implementations. ``` error[E0308]: mismatched types --> $DIR/assignment-of-clone-call-on-ref-due-to-missing-bound.rs:17:39 | LL | let mut x: HashSet = v.clone(); | ------------ ^^^^^^^^^ expected `HashSet`, found `&HashSet` | | | expected due to this | = note: expected struct `HashSet` found reference `&HashSet` note: `HashSet` does not implement `Clone`, so `&HashSet` was cloned instead --> $DIR/assignment-of-clone-call-on-ref-due-to-missing-bound.rs:17:39 | LL | let mut x: HashSet = v.clone(); | ^ = help: `Clone` is not implemented because the trait bound `Day: Clone` is not satisfied help: consider annotating `Day` with `#[derive(Clone)]` | LL + #[derive(Clone)] LL | enum Day { | ``` Case taken from # #41825. --- .../src/fn_ctxt/suggestions.rs | 75 ++++++++++++++++++- ...one-call-on-ref-due-to-missing-bound.fixed | 30 ++++++++ ...-clone-call-on-ref-due-to-missing-bound.rs | 29 +++++++ ...ne-call-on-ref-due-to-missing-bound.stderr | 25 +++++++ 4 files changed, 158 insertions(+), 1 deletion(-) create mode 100644 tests/ui/moves/assignment-of-clone-call-on-ref-due-to-missing-bound.fixed create mode 100644 tests/ui/moves/assignment-of-clone-call-on-ref-due-to-missing-bound.rs create mode 100644 tests/ui/moves/assignment-of-clone-call-on-ref-due-to-missing-bound.stderr diff --git a/compiler/rustc_hir_typeck/src/fn_ctxt/suggestions.rs b/compiler/rustc_hir_typeck/src/fn_ctxt/suggestions.rs index a904b419a94..b61b029813c 100644 --- a/compiler/rustc_hir_typeck/src/fn_ctxt/suggestions.rs +++ b/compiler/rustc_hir_typeck/src/fn_ctxt/suggestions.rs @@ -21,7 +21,7 @@ use rustc_hir::{ StmtKind, TyKind, WherePredicate, }; use rustc_hir_analysis::astconv::AstConv; -use rustc_infer::traits::{self, StatementAsExpression}; +use rustc_infer::traits::{self, StatementAsExpression, TraitEngineExt}; use rustc_middle::lint::in_external_macro; use rustc_middle::middle::stability::EvalResult; use rustc_middle::ty::print::with_no_trimmed_paths; @@ -34,6 +34,7 @@ use rustc_span::source_map::Spanned; use rustc_span::symbol::{sym, Ident}; use rustc_span::{Span, Symbol}; use rustc_trait_selection::infer::InferCtxtExt; +use rustc_trait_selection::solve::FulfillmentCtxt; use rustc_trait_selection::traits::error_reporting::suggestions::TypeErrCtxtExt; use rustc_trait_selection::traits::error_reporting::DefIdOrName; use rustc_trait_selection::traits::query::evaluate_obligation::InferCtxtExt as _; @@ -1619,6 +1620,78 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { None, ); } else { + self.infcx.probe(|_snapshot| { + if let ty::Adt(def, args) = expected_ty.kind() + && let Some((def_id, _imp)) = self + .tcx + .all_impls(clone_trait_did) + .filter_map(|def_id| { + self.tcx.impl_trait_ref(def_id).map(|r| (def_id, r)) + }) + .map(|(def_id, imp)| (def_id, imp.skip_binder())) + .filter(|(_, imp)| match imp.self_ty().peel_refs().kind() { + ty::Adt(i_def, _) if i_def.did() == def.did() => true, + _ => false, + }) + .next() + { + let mut fulfill_cx = FulfillmentCtxt::new(&self.infcx); + // We get all obligations from the impl to talk about specific + // trait bounds. + let obligations = self + .tcx + .predicates_of(def_id) + .instantiate(self.tcx, args) + .into_iter() + .map(|(clause, span)| { + traits::Obligation::new( + self.tcx, + traits::ObligationCause::misc(span, self.body_id), + self.param_env, + clause, + ) + }) + .collect::>(); + fulfill_cx.register_predicate_obligations(&self.infcx, obligations); + let errors = fulfill_cx.select_all_or_error(&self.infcx); + match &errors[..] { + [] => {} + [error] => { + diag.help(format!( + "`Clone` is not implemented because the trait bound `{}` is \ + not satisfied", + error.obligation.predicate, + )); + } + [errors @ .., last] => { + diag.help(format!( + "`Clone` is not implemented because the following trait bounds \ + could not be satisfied: {} and `{}`", + errors + .iter() + .map(|e| format!("`{}`", e.obligation.predicate)) + .collect::>() + .join(", "), + last.obligation.predicate, + )); + } + } + for error in errors { + if let traits::FulfillmentErrorCode::CodeSelectionError( + traits::SelectionError::Unimplemented, + ) = error.code + && let ty::PredicateKind::Clause(ty::ClauseKind::Trait(pred)) = + error.obligation.predicate.kind().skip_binder() + { + self.infcx.err_ctxt().suggest_derive( + &error.obligation, + diag, + error.obligation.predicate.kind().rebind(pred), + ); + } + } + } + }); self.suggest_derive(diag, &[(trait_ref.to_predicate(self.tcx), None, None)]); } } diff --git a/tests/ui/moves/assignment-of-clone-call-on-ref-due-to-missing-bound.fixed b/tests/ui/moves/assignment-of-clone-call-on-ref-due-to-missing-bound.fixed new file mode 100644 index 00000000000..e88ca6079ec --- /dev/null +++ b/tests/ui/moves/assignment-of-clone-call-on-ref-due-to-missing-bound.fixed @@ -0,0 +1,30 @@ +// run-rustfix +#![allow(unused_variables, dead_code)] +use std::collections::BTreeMap; +use std::collections::HashSet; + +#[derive(Debug,Eq,PartialEq,Hash)] +#[derive(Clone)] +enum Day { + Mon, +} + +struct Class { + days: BTreeMap> +} + +impl Class { + fn do_stuff(&self) { + for (_, v) in &self.days { + let mut x: HashSet = v.clone(); //~ ERROR + let y: Vec = x.drain().collect(); + println!("{:?}", x); + } + } +} + +fn fail() { + let c = Class { days: BTreeMap::new() }; + c.do_stuff(); +} +fn main() {} diff --git a/tests/ui/moves/assignment-of-clone-call-on-ref-due-to-missing-bound.rs b/tests/ui/moves/assignment-of-clone-call-on-ref-due-to-missing-bound.rs new file mode 100644 index 00000000000..ba277c4a9c4 --- /dev/null +++ b/tests/ui/moves/assignment-of-clone-call-on-ref-due-to-missing-bound.rs @@ -0,0 +1,29 @@ +// run-rustfix +#![allow(unused_variables, dead_code)] +use std::collections::BTreeMap; +use std::collections::HashSet; + +#[derive(Debug,Eq,PartialEq,Hash)] +enum Day { + Mon, +} + +struct Class { + days: BTreeMap> +} + +impl Class { + fn do_stuff(&self) { + for (_, v) in &self.days { + let mut x: HashSet = v.clone(); //~ ERROR + let y: Vec = x.drain().collect(); + println!("{:?}", x); + } + } +} + +fn fail() { + let c = Class { days: BTreeMap::new() }; + c.do_stuff(); +} +fn main() {} diff --git a/tests/ui/moves/assignment-of-clone-call-on-ref-due-to-missing-bound.stderr b/tests/ui/moves/assignment-of-clone-call-on-ref-due-to-missing-bound.stderr new file mode 100644 index 00000000000..3ca4bffd624 --- /dev/null +++ b/tests/ui/moves/assignment-of-clone-call-on-ref-due-to-missing-bound.stderr @@ -0,0 +1,25 @@ +error[E0308]: mismatched types + --> $DIR/assignment-of-clone-call-on-ref-due-to-missing-bound.rs:18:39 + | +LL | let mut x: HashSet = v.clone(); + | ------------ ^^^^^^^^^ expected `HashSet`, found `&HashSet` + | | + | expected due to this + | + = note: expected struct `HashSet` + found reference `&HashSet` +note: `HashSet` does not implement `Clone`, so `&HashSet` was cloned instead + --> $DIR/assignment-of-clone-call-on-ref-due-to-missing-bound.rs:18:39 + | +LL | let mut x: HashSet = v.clone(); + | ^ + = help: `Clone` is not implemented because the trait bound `Day: Clone` is not satisfied +help: consider annotating `Day` with `#[derive(Clone)]` + | +LL + #[derive(Clone)] +LL | enum Day { + | + +error: aborting due to previous error + +For more information about this error, try `rustc --explain E0308`.