diff options
Diffstat (limited to 'compiler')
| -rw-r--r-- | compiler/rustc_hir_typeck/src/demand.rs | 161 | ||||
| -rw-r--r-- | compiler/rustc_hir_typeck/src/method/probe.rs | 40 | ||||
| -rw-r--r-- | compiler/rustc_lint/src/unused.rs | 1 |
3 files changed, 196 insertions, 6 deletions
diff --git a/compiler/rustc_hir_typeck/src/demand.rs b/compiler/rustc_hir_typeck/src/demand.rs index 042ff0b46a5..0335c45a946 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; @@ -30,12 +31,14 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { expr_ty: Ty<'tcx>, expected: Ty<'tcx>, expected_ty_expr: Option<&'tcx hir::Expr<'tcx>>, - _error: Option<TypeError<'tcx>>, + error: Option<TypeError<'tcx>>, ) { if expr_ty == expected { 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,162 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { } } + fn annotate_alternative_method_deref( + &self, + err: &mut Diagnostic, + expr: &hir::Expr<'_>, + error: Option<TypeError<'tcx>>, + ) { + 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 Some(self_ty) = self.typeck_results.borrow().expr_ty_adjusted_opt(base) else { return; }; + + let Ok(pick) = self + .probe_for_name( + probe::Mode::MethodCall, + path.ident, + probe::IsSuggestion(true), + self_ty, + deref.hir_id, + probe::ProbeScope::TraitsInScope, + ) else { + return; + }; + let in_scope_methods = self.probe_for_name_many( + probe::Mode::MethodCall, + path.ident, + probe::IsSuggestion(true), + self_ty, + deref.hir_id, + probe::ProbeScope::TraitsInScope, + ); + let other_methods_in_scope: Vec<_> = + in_scope_methods.iter().filter(|c| c.item.def_id != pick.item.def_id).collect(); + + let all_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<_> = all_methods + .into_iter() + .filter(|c| c.item.def_id != pick.item.def_id) + .map(|c| { + let m = c.item; + 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.between(first.span), ", ".to_string()), + }, + ] + }) + .collect(); + if suggestions.is_empty() { + return; + } + let mut path_span: MultiSpan = path.ident.span.into(); + path_span.push_span_label( + path.ident.span, + with_no_trimmed_paths!(format!( + "refers to `{}`", + self.tcx.def_path_str(pick.item.def_id), + )), + ); + let container_id = pick.item.container_id(self.tcx); + let container = with_no_trimmed_paths!(self.tcx.def_path_str(container_id)); + for def_id in pick.import_ids { + let hir_id = self.tcx.hir().local_def_id_to_hir_id(def_id); + path_span.push_span_label( + self.tcx.hir().span(hir_id), + format!("`{container}` imported here"), + ); + } + let tail = with_no_trimmed_paths!(match &other_methods_in_scope[..] { + [] => return, + [candidate] => format!( + "the method of the same name on {} `{}`", + match candidate.kind { + probe::CandidateKind::InherentImplCandidate(..) => "the inherent impl for", + _ => "trait", + }, + self.tcx.def_path_str(candidate.item.container_id(self.tcx)) + ), + [.., last] if other_methods_in_scope.len() < 5 => { + format!( + "the methods of the same name on {} and `{}`", + other_methods_in_scope[..other_methods_in_scope.len() - 1] + .iter() + .map(|c| format!( + "`{}`", + self.tcx.def_path_str(c.item.container_id(self.tcx)) + )) + .collect::<Vec<String>>() + .join(", "), + self.tcx.def_path_str(last.item.container_id(self.tcx)) + ) + } + _ => format!( + "the methods of the same name on {} other traits", + other_methods_in_scope.len() + ), + }); + err.span_note( + path_span, + &format!( + "the `{}` call is resolved to the method in `{container}`, shadowing {tail}", + path.ident, + ), + ); + if suggestions.len() > other_methods_in_scope.len() { + err.note(&format!( + "additionally, there are {} other available methods that aren't in scope", + suggestions.len() - other_methods_in_scope.len() + )); + } + err.multipart_suggestions( + &format!( + "you might have meant to call {}; you can use the fully-qualified path to call {} \ + explicitly", + if suggestions.len() == 1 { + "the other method" + } else { + "one of the other methods" + }, + if suggestions.len() == 1 { "it" } else { "one of them" }, + ), + 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..6b3fa664d9c 100644 --- a/compiler/rustc_hir_typeck/src/method/probe.rs +++ b/compiler/rustc_hir_typeck/src/method/probe.rs @@ -97,7 +97,7 @@ impl<'a, 'tcx> Deref for ProbeContext<'a, 'tcx> { } #[derive(Debug, Clone)] -struct Candidate<'tcx> { +pub(crate) struct Candidate<'tcx> { // Candidates are (I'm not quite sure, but they are mostly) basically // some metadata on top of a `ty::AssocItem` (without substs). // @@ -131,13 +131,13 @@ struct Candidate<'tcx> { // if `T: Sized`. xform_self_ty: Ty<'tcx>, xform_ret_ty: Option<Ty<'tcx>>, - item: ty::AssocItem, - kind: CandidateKind<'tcx>, - import_ids: SmallVec<[LocalDefId; 1]>, + pub(crate) item: ty::AssocItem, + pub(crate) kind: CandidateKind<'tcx>, + pub(crate) import_ids: SmallVec<[LocalDefId; 1]>, } #[derive(Debug, Clone)] -enum CandidateKind<'tcx> { +pub(crate) enum CandidateKind<'tcx> { InherentImplCandidate( SubstsRef<'tcx>, // Normalize obligations @@ -322,6 +322,36 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { ) } + #[instrument(level = "debug", skip(self))] + pub(crate) 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<Candidate<'tcx>> { + 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 + .into_iter() + .chain(probe_cx.extension_candidates) + .collect()) + }, + ) + .unwrap() + } + fn probe_op<OP, R>( &'a self, span: Span, diff --git a/compiler/rustc_lint/src/unused.rs b/compiler/rustc_lint/src/unused.rs index 3b8df61a0ea..a7a5234049f 100644 --- a/compiler/rustc_lint/src/unused.rs +++ b/compiler/rustc_lint/src/unused.rs @@ -1142,6 +1142,7 @@ impl UnusedDelimLint for UnusedBraces { && !cx.sess().source_map().is_multiline(value.span) && value.attrs.is_empty() && !value.span.from_expansion() + && !inner.span.from_expansion() { self.emit_unused_delims_expr(cx, value, ctx, left_pos, right_pos) } |
