From 34b9594f6d7cecb748a7a88c27fc23137898f417 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Esteban=20K=C3=BCber?= Date: Sun, 25 Dec 2022 21:50:30 -0800 Subject: [PATCH] Detect when method call on LHS might be shadowed Address #39232. --- compiler/rustc_hir_typeck/src/demand.rs | 92 +++++++++++++++++++ compiler/rustc_hir_typeck/src/method/probe.rs | 32 +++++++ .../suggestions/shadowed-lplace-method.fixed | 10 ++ .../ui/suggestions/shadowed-lplace-method.rs | 10 ++ .../suggestions/shadowed-lplace-method.stderr | 23 +++++ 5 files changed, 167 insertions(+) create mode 100644 src/test/ui/suggestions/shadowed-lplace-method.fixed create mode 100644 src/test/ui/suggestions/shadowed-lplace-method.rs create mode 100644 src/test/ui/suggestions/shadowed-lplace-method.stderr diff --git a/compiler/rustc_hir_typeck/src/demand.rs b/compiler/rustc_hir_typeck/src/demand.rs index 042ff0b46a5..f7f492863ab 100644 --- a/compiler/rustc_hir_typeck/src/demand.rs +++ b/compiler/rustc_hir_typeck/src/demand.rs @@ -1,5 +1,6 @@ use crate::FnCtxt; use rustc_ast::util::parser::PREC_POSTFIX; +use rustc_errors::MultiSpan; use rustc_errors::{Applicability, Diagnostic, DiagnosticBuilder, ErrorGuaranteed}; use rustc_hir as hir; use rustc_hir::def::CtorKind; @@ -36,6 +37,8 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { return; } + self.annotate_alternative_method_deref(err, expr, error); + // Use `||` to give these suggestions a precedence let _ = self.suggest_missing_parentheses(err, expr) || self.suggest_remove_last_method_call(err, expr, expected) @@ -316,6 +319,95 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { } } + fn annotate_alternative_method_deref( + &self, + err: &mut Diagnostic, + expr: &hir::Expr<'_>, + error: Option>, + ) { + let parent = self.tcx.hir().get_parent_node(expr.hir_id); + let Some(TypeError::Sorts(ExpectedFound { expected, .. })) = error else {return;}; + let Some(hir::Node::Expr(hir::Expr { + kind: hir::ExprKind::Assign(lhs, rhs, _), .. + })) = self.tcx.hir().find(parent) else {return; }; + if rhs.hir_id != expr.hir_id || expected.is_closure() { + return; + } + let hir::ExprKind::Unary(hir::UnOp::Deref, deref) = lhs.kind else { return; }; + let hir::ExprKind::MethodCall(path, base, args, _) = deref.kind else { return; }; + let self_ty = self.typeck_results.borrow().expr_ty_adjusted_opt(base).unwrap(); + let pick = self + .probe_for_name( + probe::Mode::MethodCall, + path.ident, + probe::IsSuggestion(true), + self_ty, + deref.hir_id, + probe::ProbeScope::TraitsInScope, + ) + .unwrap(); + let methods = self.probe_for_name_many( + probe::Mode::MethodCall, + path.ident, + probe::IsSuggestion(true), + self_ty, + deref.hir_id, + probe::ProbeScope::AllTraits, + ); + let suggestions: Vec<_> = methods + .into_iter() + .filter(|m| m.def_id != pick.item.def_id) + .map(|m| { + let substs = ty::InternalSubsts::for_item(self.tcx, m.def_id, |param, _| { + self.var_for_def(deref.span, param) + }); + vec![ + ( + deref.span.until(base.span), + format!( + "{}({}", + with_no_trimmed_paths!( + self.tcx.def_path_str_with_substs(m.def_id, substs,) + ), + match self.tcx.fn_sig(m.def_id).input(0).skip_binder().kind() { + ty::Ref(_, _, hir::Mutability::Mut) => "&mut ", + ty::Ref(_, _, _) => "&", + _ => "", + }, + ), + ), + match &args[..] { + [] => (base.span.shrink_to_hi().with_hi(deref.span.hi()), ")".to_string()), + [first, ..] => (base.span.until(first.span), String::new()), + }, + ] + }) + .collect(); + if suggestions.is_empty() { + return; + } + let mut path_span: MultiSpan = path.ident.span.into(); + path_span.push_span_label( + path.ident.span, + format!( + "refers to `{}`", + with_no_trimmed_paths!(self.tcx.def_path_str(pick.item.def_id)), + ), + ); + err.span_note( + path_span, + &format!( + "there are multiple methods with the same name, `{}` refers to `{}` in the method call", + path.ident, + with_no_trimmed_paths!(self.tcx.def_path_str(pick.item.def_id)), + )); + err.multipart_suggestions( + "you might have meant to invoke a different method, you can use the fully-qualified path", + suggestions, + Applicability::MaybeIncorrect, + ); + } + /// If the expected type is an enum (Issue #55250) with any variants whose /// sole field is of the found type, suggest such variants. (Issue #42764) fn suggest_compatible_variants( diff --git a/compiler/rustc_hir_typeck/src/method/probe.rs b/compiler/rustc_hir_typeck/src/method/probe.rs index b9e7830bf07..a7574d4e1af 100644 --- a/compiler/rustc_hir_typeck/src/method/probe.rs +++ b/compiler/rustc_hir_typeck/src/method/probe.rs @@ -322,6 +322,38 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { ) } + #[instrument(level = "debug", skip(self))] + pub fn probe_for_name_many( + &self, + mode: Mode, + item_name: Ident, + is_suggestion: IsSuggestion, + self_ty: Ty<'tcx>, + scope_expr_id: hir::HirId, + scope: ProbeScope, + ) -> Vec { + self.probe_op( + item_name.span, + mode, + Some(item_name), + None, + is_suggestion, + self_ty, + scope_expr_id, + scope, + |probe_cx| { + Ok(probe_cx + .inherent_candidates + .iter() + .chain(&probe_cx.extension_candidates) + // .filter(|candidate| candidate_filter(&candidate.item)) + .map(|candidate| candidate.item) + .collect()) + }, + ) + .unwrap() + } + fn probe_op( &'a self, span: Span, diff --git a/src/test/ui/suggestions/shadowed-lplace-method.fixed b/src/test/ui/suggestions/shadowed-lplace-method.fixed new file mode 100644 index 00000000000..740ac77ee0c --- /dev/null +++ b/src/test/ui/suggestions/shadowed-lplace-method.fixed @@ -0,0 +1,10 @@ +// run-rustfix +#![allow(unused_imports)] +use std::borrow::BorrowMut; +use std::cell::RefCell; +use std::rc::Rc; + +fn main() { + let rc = Rc::new(RefCell::new(true)); + *std::cell::RefCell::<_>::borrow_mut(&rc) = false; //~ ERROR E0308 +} diff --git a/src/test/ui/suggestions/shadowed-lplace-method.rs b/src/test/ui/suggestions/shadowed-lplace-method.rs new file mode 100644 index 00000000000..6bf12879e6f --- /dev/null +++ b/src/test/ui/suggestions/shadowed-lplace-method.rs @@ -0,0 +1,10 @@ +// run-rustfix +#![allow(unused_imports)] +use std::borrow::BorrowMut; +use std::cell::RefCell; +use std::rc::Rc; + +fn main() { + let rc = Rc::new(RefCell::new(true)); + *rc.borrow_mut() = false; //~ ERROR E0308 +} diff --git a/src/test/ui/suggestions/shadowed-lplace-method.stderr b/src/test/ui/suggestions/shadowed-lplace-method.stderr new file mode 100644 index 00000000000..080600128a3 --- /dev/null +++ b/src/test/ui/suggestions/shadowed-lplace-method.stderr @@ -0,0 +1,23 @@ +error[E0308]: mismatched types + --> $DIR/shadowed-lplace-method.rs:9:24 + | +LL | *rc.borrow_mut() = false; + | ---------------- ^^^^^ expected struct `Rc`, found `bool` + | | + | expected due to the type of this binding + | + = note: expected struct `Rc>` + found type `bool` +note: there are multiple methods with the same name, `borrow_mut` refers to `std::borrow::BorrowMut::borrow_mut` in the method call + --> $DIR/shadowed-lplace-method.rs:9:9 + | +LL | *rc.borrow_mut() = false; + | ^^^^^^^^^^ refers to `std::borrow::BorrowMut::borrow_mut` +help: you might have meant to invoke a different method, you can use the fully-qualified path + | +LL | *std::cell::RefCell::<_>::borrow_mut(&rc) = false; + | +++++++++++++++++++++++++++++++++++++ ~ + +error: aborting due to previous error + +For more information about this error, try `rustc --explain E0308`.