From d12689c022d56d63c03a73925054c95d060e34c4 Mon Sep 17 00:00:00 2001 From: Michael Goulet Date: Sat, 2 Apr 2022 00:23:52 -0700 Subject: [PATCH] Explain why `&T` is cloned when `T` is not `Clone` --- compiler/rustc_typeck/src/check/demand.rs | 2 +- .../src/check/fn_ctxt/suggestions.rs | 59 +++++++++++++++++-- src/test/ui/typeck/explain_clone_autoref.rs | 13 ++++ .../ui/typeck/explain_clone_autoref.stderr | 18 ++++++ 4 files changed, 86 insertions(+), 6 deletions(-) create mode 100644 src/test/ui/typeck/explain_clone_autoref.rs create mode 100644 src/test/ui/typeck/explain_clone_autoref.stderr diff --git a/compiler/rustc_typeck/src/check/demand.rs b/compiler/rustc_typeck/src/check/demand.rs index 00bc16452b9b9..83e535b3c3247 100644 --- a/compiler/rustc_typeck/src/check/demand.rs +++ b/compiler/rustc_typeck/src/check/demand.rs @@ -40,6 +40,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { self.suggest_boxing_when_appropriate(err, expr, expected, expr_ty); self.suggest_missing_parentheses(err, expr); self.suggest_block_to_brackets_peeling_refs(err, expr, expr_ty, expected); + self.note_type_is_not_clone(err, expected, expr_ty, expr); self.note_need_for_fn_pointer(err, expected, expr_ty); self.note_internal_mutation_in_method(err, expr, expected, expr_ty); self.report_closure_inferred_return_type(err, expected); @@ -630,7 +631,6 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { Applicability::MachineApplicable, true, )); - } } _ => {} diff --git a/compiler/rustc_typeck/src/check/fn_ctxt/suggestions.rs b/compiler/rustc_typeck/src/check/fn_ctxt/suggestions.rs index 1ccdbb0aa500b..93a0900c7e80d 100644 --- a/compiler/rustc_typeck/src/check/fn_ctxt/suggestions.rs +++ b/compiler/rustc_typeck/src/check/fn_ctxt/suggestions.rs @@ -2,8 +2,6 @@ use super::FnCtxt; use crate::astconv::AstConv; use rustc_ast::util::parser::ExprPrecedence; -use rustc_span::{self, Span}; - use rustc_errors::{Applicability, Diagnostic, MultiSpan}; use rustc_hir as hir; use rustc_hir::def::{CtorOf, DefKind}; @@ -13,12 +11,14 @@ use rustc_hir::{ WherePredicate, }; use rustc_infer::infer::{self, TyCtxtInferExt}; - +use rustc_infer::traits; use rustc_middle::lint::in_external_macro; -use rustc_middle::ty::{self, Binder, Ty}; +use rustc_middle::ty::subst::GenericArgKind; +use rustc_middle::ty::{self, Binder, ToPredicate, Ty}; use rustc_span::symbol::{kw, sym}; +use rustc_span::Span; +use rustc_trait_selection::traits::query::evaluate_obligation::InferCtxtExt; -use rustc_middle::ty::subst::GenericArgKind; use std::iter; impl<'a, 'tcx> FnCtxt<'a, 'tcx> { @@ -846,4 +846,53 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { let node = self.tcx.hir().get(id); matches!(node, Node::Stmt(Stmt { kind: StmtKind::Local(..), .. })) } + + /// Suggest that `&T` was cloned instead of `T` because `T` does not implement `Clone`, + /// which is a side-effect of autoref. + pub(crate) fn note_type_is_not_clone( + &self, + diag: &mut Diagnostic, + expected_ty: Ty<'tcx>, + found_ty: Ty<'tcx>, + expr: &hir::Expr<'_>, + ) { + let hir::ExprKind::MethodCall(segment, &[ref callee_expr], _) = expr.kind else { return; }; + let Some(clone_trait_did) = self.tcx.lang_items().clone_trait() else { return; }; + let ty::Ref(_, pointee_ty, _) = found_ty.kind() else { return }; + let results = self.typeck_results.borrow(); + // First, look for a `Clone::clone` call + if segment.ident.name == sym::clone + && results.type_dependent_def_id(expr.hir_id).map_or( + false, + |did| { + self.tcx.associated_item(did).container + == ty::AssocItemContainer::TraitContainer(clone_trait_did) + }, + ) + // If that clone call hasn't already dereferenced the self type (i.e. don't give this + // diagnostic in cases where we have `(&&T).clone()` and we expect `T`). + && !results.expr_adjustments(callee_expr).iter().any(|adj| matches!(adj.kind, ty::adjustment::Adjust::Deref(..))) + // Check that we're in fact trying to clone into the expected type + && self.can_coerce(*pointee_ty, expected_ty) + // And the expected type doesn't implement `Clone` + && !self.predicate_must_hold_considering_regions(&traits::Obligation { + cause: traits::ObligationCause::dummy(), + param_env: self.param_env, + recursion_depth: 0, + predicate: ty::Binder::dummy(ty::TraitRef { + def_id: clone_trait_did, + substs: self.tcx.mk_substs([expected_ty.into()].iter()), + }) + .without_const() + .to_predicate(self.tcx), + }) + { + diag.span_note( + callee_expr.span, + &format!( + "`{expected_ty}` does not implement `Clone`, so `{found_ty}` was cloned instead" + ), + ); + } + } } diff --git a/src/test/ui/typeck/explain_clone_autoref.rs b/src/test/ui/typeck/explain_clone_autoref.rs new file mode 100644 index 0000000000000..9279e4c3901db --- /dev/null +++ b/src/test/ui/typeck/explain_clone_autoref.rs @@ -0,0 +1,13 @@ +struct NotClone; + +fn main() { + clone_thing(&NotClone); +} + +fn clone_thing(nc: &NotClone) -> NotClone { + //~^ NOTE expected `NotClone` because of return type + nc.clone() + //~^ ERROR mismatched type + //~| NOTE `NotClone` does not implement `Clone`, so `&NotClone` was cloned instead + //~| NOTE expected struct `NotClone`, found `&NotClone` +} diff --git a/src/test/ui/typeck/explain_clone_autoref.stderr b/src/test/ui/typeck/explain_clone_autoref.stderr new file mode 100644 index 0000000000000..faac680ea1931 --- /dev/null +++ b/src/test/ui/typeck/explain_clone_autoref.stderr @@ -0,0 +1,18 @@ +error[E0308]: mismatched types + --> $DIR/explain_clone_autoref.rs:9:5 + | +LL | fn clone_thing(nc: &NotClone) -> NotClone { + | -------- expected `NotClone` because of return type +LL | +LL | nc.clone() + | ^^^^^^^^^^ expected struct `NotClone`, found `&NotClone` + | +note: `NotClone` does not implement `Clone`, so `&NotClone` was cloned instead + --> $DIR/explain_clone_autoref.rs:9:5 + | +LL | nc.clone() + | ^^ + +error: aborting due to previous error + +For more information about this error, try `rustc --explain E0308`.