summaryrefslogtreecommitdiffstats
path: root/compiler/rustc_hir_typeck/src/method/suggest.rs
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--compiler/rustc_hir_typeck/src/method/suggest.rs (renamed from compiler/rustc_typeck/src/check/method/suggest.rs)388
1 files changed, 314 insertions, 74 deletions
diff --git a/compiler/rustc_typeck/src/check/method/suggest.rs b/compiler/rustc_hir_typeck/src/method/suggest.rs
index 2d459b2cc..6c21ed902 100644
--- a/compiler/rustc_typeck/src/check/method/suggest.rs
+++ b/compiler/rustc_hir_typeck/src/method/suggest.rs
@@ -1,7 +1,9 @@
//! Give useful errors and suggestions to users when an item can't be
//! found or is otherwise invalid.
-use crate::check::FnCtxt;
+use crate::errors;
+use crate::FnCtxt;
+use rustc_ast::ast::Mutability;
use rustc_data_structures::fx::{FxHashMap, FxHashSet};
use rustc_errors::{
pluralize, struct_span_err, Applicability, Diagnostic, DiagnosticBuilder, ErrorGuaranteed,
@@ -21,7 +23,7 @@ use rustc_middle::ty::{IsSuggestable, ToPolyTraitRef};
use rustc_span::symbol::{kw, sym, Ident};
use rustc_span::Symbol;
use rustc_span::{lev_distance, source_map, ExpnKind, FileName, MacroKind, Span};
-use rustc_trait_selection::traits::error_reporting::on_unimplemented::InferCtxtExt as _;
+use rustc_trait_selection::traits::error_reporting::on_unimplemented::TypeErrCtxtExt as _;
use rustc_trait_selection::traits::query::evaluate_obligation::InferCtxtExt as _;
use rustc_trait_selection::traits::{
FulfillmentError, Obligation, ObligationCause, ObligationCauseCode, OnUnimplementedNote,
@@ -30,7 +32,7 @@ use rustc_trait_selection::traits::{
use std::cmp::Ordering;
use std::iter;
-use super::probe::{IsSuggestion, Mode, ProbeScope};
+use super::probe::{AutorefOrPtrAdjustment, IsSuggestion, Mode, ProbeScope};
use super::{CandidateSource, MethodError, NoMatchData};
impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
@@ -104,7 +106,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
let report_candidates = |span: Span,
err: &mut Diagnostic,
- mut sources: Vec<CandidateSource>,
+ sources: &mut Vec<CandidateSource>,
sugg_span: Span| {
sources.sort();
sources.dedup();
@@ -246,7 +248,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
match error {
MethodError::NoMatch(NoMatchData {
- static_candidates: static_sources,
+ static_candidates: mut static_sources,
unsatisfied_predicates,
out_of_scope_traits,
lev_candidate,
@@ -270,7 +272,9 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
}
};
- if self.suggest_constraining_numerical_ty(
+ if self.suggest_wrapping_range_with_parens(
+ tcx, actual, source, span, item_name, &ty_str,
+ ) || self.suggest_constraining_numerical_ty(
tcx, actual, source, span, item_kind, item_name, &ty_str,
) {
return None;
@@ -418,9 +422,9 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
err.help(&format!("try with `{}::{}`", ty_str, item_name,));
}
- report_candidates(span, &mut err, static_sources, sugg_span);
+ report_candidates(span, &mut err, &mut static_sources, sugg_span);
} else if static_sources.len() > 1 {
- report_candidates(span, &mut err, static_sources, sugg_span);
+ report_candidates(span, &mut err, &mut static_sources, sugg_span);
}
let mut bound_spans = vec![];
@@ -854,8 +858,9 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
// Avoid crashing.
return (None, None);
}
- let OnUnimplementedNote { message, label, .. } =
- self.on_unimplemented_note(trait_ref, &obligation);
+ let OnUnimplementedNote { message, label, .. } = self
+ .err_ctxt()
+ .on_unimplemented_note(trait_ref, &obligation);
(message, label)
})
.unwrap_or((None, None))
@@ -983,7 +988,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
self.check_for_field_method(&mut err, source, span, actual, item_name);
}
- self.check_for_unwrap_self(&mut err, source, span, actual, item_name);
+ self.check_for_inner_self(&mut err, source, span, actual, item_name);
bound_spans.sort();
bound_spans.dedup();
@@ -1002,6 +1007,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
source,
out_of_scope_traits,
&unsatisfied_predicates,
+ &static_sources,
unsatisfied_bounds,
);
}
@@ -1074,7 +1080,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
return Some(err);
}
- MethodError::Ambiguity(sources) => {
+ MethodError::Ambiguity(mut sources) => {
let mut err = struct_span_err!(
self.sess(),
item_name.span,
@@ -1083,7 +1089,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
);
err.span_label(item_name.span, format!("multiple `{}` found", item_name));
- report_candidates(span, &mut err, sources, sugg_span);
+ report_candidates(span, &mut err, &mut sources, sugg_span);
err.emit();
}
@@ -1200,6 +1206,89 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
false
}
+ /// Suggest possible range with adding parentheses, for example:
+ /// when encountering `0..1.map(|i| i + 1)` suggest `(0..1).map(|i| i + 1)`.
+ fn suggest_wrapping_range_with_parens(
+ &self,
+ tcx: TyCtxt<'tcx>,
+ actual: Ty<'tcx>,
+ source: SelfSource<'tcx>,
+ span: Span,
+ item_name: Ident,
+ ty_str: &str,
+ ) -> bool {
+ if let SelfSource::MethodCall(expr) = source {
+ for (_, parent) in tcx.hir().parent_iter(expr.hir_id).take(5) {
+ if let Node::Expr(parent_expr) = parent {
+ let lang_item = match parent_expr.kind {
+ ExprKind::Struct(ref qpath, _, _) => match **qpath {
+ QPath::LangItem(LangItem::Range, ..) => Some(LangItem::Range),
+ QPath::LangItem(LangItem::RangeTo, ..) => Some(LangItem::RangeTo),
+ QPath::LangItem(LangItem::RangeToInclusive, ..) => {
+ Some(LangItem::RangeToInclusive)
+ }
+ _ => None,
+ },
+ ExprKind::Call(ref func, _) => match func.kind {
+ // `..=` desugars into `::std::ops::RangeInclusive::new(...)`.
+ ExprKind::Path(QPath::LangItem(LangItem::RangeInclusiveNew, ..)) => {
+ Some(LangItem::RangeInclusiveStruct)
+ }
+ _ => None,
+ },
+ _ => None,
+ };
+
+ if lang_item.is_none() {
+ continue;
+ }
+
+ let span_included = match parent_expr.kind {
+ hir::ExprKind::Struct(_, eps, _) => {
+ eps.len() > 0 && eps.last().map_or(false, |ep| ep.span.contains(span))
+ }
+ // `..=` desugars into `::std::ops::RangeInclusive::new(...)`.
+ hir::ExprKind::Call(ref func, ..) => func.span.contains(span),
+ _ => false,
+ };
+
+ if !span_included {
+ continue;
+ }
+
+ let range_def_id = self.tcx.require_lang_item(lang_item.unwrap(), None);
+ let range_ty =
+ self.tcx.bound_type_of(range_def_id).subst(self.tcx, &[actual.into()]);
+
+ let pick = self.probe_for_name(
+ span,
+ Mode::MethodCall,
+ item_name,
+ IsSuggestion(true),
+ range_ty,
+ expr.hir_id,
+ ProbeScope::AllTraits,
+ );
+ if pick.is_ok() {
+ let range_span = parent_expr.span.with_hi(expr.span.hi());
+ tcx.sess.emit_err(errors::MissingParentheseInRange {
+ span,
+ ty_str: ty_str.to_string(),
+ method_name: item_name.as_str().to_string(),
+ add_missing_parentheses: Some(errors::AddMissingParenthesesInRange {
+ func_name: item_name.name.as_str().to_string(),
+ left: range_span.shrink_to_lo(),
+ right: range_span.shrink_to_hi(),
+ }),
+ });
+ return true;
+ }
+ }
+ }
+ }
+ false
+ }
+
fn suggest_constraining_numerical_ty(
&self,
tcx: TyCtxt<'tcx>,
@@ -1262,7 +1351,6 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
// If this is a floating point literal that ends with '.',
// get rid of it to stop this from becoming a member access.
let snippet = snippet.strip_suffix('.').unwrap_or(&snippet);
-
err.span_suggestion(
lit.span,
&format!(
@@ -1395,7 +1483,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
}
}
- fn check_for_unwrap_self(
+ fn check_for_inner_self(
&self,
err: &mut Diagnostic,
source: SelfSource<'tcx>,
@@ -1408,81 +1496,168 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
let call_expr = tcx.hir().expect_expr(tcx.hir().get_parent_node(expr.hir_id));
let ty::Adt(kind, substs) = actual.kind() else { return; };
- if !kind.is_enum() {
- return;
- }
+ match kind.adt_kind() {
+ ty::AdtKind::Enum => {
+ let matching_variants: Vec<_> = kind
+ .variants()
+ .iter()
+ .flat_map(|variant| {
+ let [field] = &variant.fields[..] else { return None; };
+ let field_ty = field.ty(tcx, substs);
+
+ // Skip `_`, since that'll just lead to ambiguity.
+ if self.resolve_vars_if_possible(field_ty).is_ty_var() {
+ return None;
+ }
- let matching_variants: Vec<_> = kind
- .variants()
- .iter()
- .flat_map(|variant| {
- let [field] = &variant.fields[..] else { return None; };
- let field_ty = field.ty(tcx, substs);
+ self.lookup_probe(
+ span,
+ item_name,
+ field_ty,
+ call_expr,
+ ProbeScope::TraitsInScope,
+ )
+ .ok()
+ .map(|pick| (variant, field, pick))
+ })
+ .collect();
+
+ let ret_ty_matches = |diagnostic_item| {
+ if let Some(ret_ty) = self
+ .ret_coercion
+ .as_ref()
+ .map(|c| self.resolve_vars_if_possible(c.borrow().expected_ty()))
+ && let ty::Adt(kind, _) = ret_ty.kind()
+ && tcx.get_diagnostic_item(diagnostic_item) == Some(kind.did())
+ {
+ true
+ } else {
+ false
+ }
+ };
- // Skip `_`, since that'll just lead to ambiguity.
- if self.resolve_vars_if_possible(field_ty).is_ty_var() {
- return None;
+ match &matching_variants[..] {
+ [(_, field, pick)] => {
+ let self_ty = field.ty(tcx, substs);
+ err.span_note(
+ tcx.def_span(pick.item.def_id),
+ &format!("the method `{item_name}` exists on the type `{self_ty}`"),
+ );
+ let (article, kind, variant, question) =
+ if tcx.is_diagnostic_item(sym::Result, kind.did()) {
+ ("a", "Result", "Err", ret_ty_matches(sym::Result))
+ } else if tcx.is_diagnostic_item(sym::Option, kind.did()) {
+ ("an", "Option", "None", ret_ty_matches(sym::Option))
+ } else {
+ return;
+ };
+ if question {
+ err.span_suggestion_verbose(
+ expr.span.shrink_to_hi(),
+ format!(
+ "use the `?` operator to extract the `{self_ty}` value, propagating \
+ {article} `{kind}::{variant}` value to the caller"
+ ),
+ "?",
+ Applicability::MachineApplicable,
+ );
+ } else {
+ err.span_suggestion_verbose(
+ expr.span.shrink_to_hi(),
+ format!(
+ "consider using `{kind}::expect` to unwrap the `{self_ty}` value, \
+ panicking if the value is {article} `{kind}::{variant}`"
+ ),
+ ".expect(\"REASON\")",
+ Applicability::HasPlaceholders,
+ );
+ }
+ }
+ // FIXME(compiler-errors): Support suggestions for other matching enum variants
+ _ => {}
}
-
- self.lookup_probe(span, item_name, field_ty, call_expr, ProbeScope::AllTraits)
- .ok()
- .map(|pick| (variant, field, pick))
- })
- .collect();
-
- let ret_ty_matches = |diagnostic_item| {
- if let Some(ret_ty) = self
- .ret_coercion
- .as_ref()
- .map(|c| self.resolve_vars_if_possible(c.borrow().expected_ty()))
- && let ty::Adt(kind, _) = ret_ty.kind()
- && tcx.get_diagnostic_item(diagnostic_item) == Some(kind.did())
- {
- true
- } else {
- false
}
- };
+ // Target wrapper types - types that wrap or pretend to wrap another type,
+ // perhaps this inner type is meant to be called?
+ ty::AdtKind::Struct | ty::AdtKind::Union => {
+ let [first] = ***substs else { return; };
+ let ty::GenericArgKind::Type(ty) = first.unpack() else { return; };
+ let Ok(pick) = self.lookup_probe(
+ span,
+ item_name,
+ ty,
+ call_expr,
+ ProbeScope::TraitsInScope,
+ ) else { return; };
- match &matching_variants[..] {
- [(_, field, pick)] => {
- let self_ty = field.ty(tcx, substs);
- err.span_note(
- tcx.def_span(pick.item.def_id),
- &format!("the method `{item_name}` exists on the type `{self_ty}`"),
- );
- let (article, kind, variant, question) =
- if Some(kind.did()) == tcx.get_diagnostic_item(sym::Result) {
- ("a", "Result", "Err", ret_ty_matches(sym::Result))
- } else if Some(kind.did()) == tcx.get_diagnostic_item(sym::Option) {
- ("an", "Option", "None", ret_ty_matches(sym::Option))
- } else {
- return;
+ let name = self.ty_to_value_string(actual);
+ let inner_id = kind.did();
+ let mutable = if let Some(AutorefOrPtrAdjustment::Autoref { mutbl, .. }) =
+ pick.autoref_or_ptr_adjustment
+ {
+ Some(mutbl)
+ } else {
+ None
+ };
+
+ if tcx.is_diagnostic_item(sym::LocalKey, inner_id) {
+ err.help("use `with` or `try_with` to access thread local storage");
+ } else if Some(kind.did()) == tcx.lang_items().maybe_uninit() {
+ err.help(format!(
+ "if this `{name}` has been initialized, \
+ use one of the `assume_init` methods to access the inner value"
+ ));
+ } else if tcx.is_diagnostic_item(sym::RefCell, inner_id) {
+ let (suggestion, borrow_kind, panic_if) = match mutable {
+ Some(Mutability::Not) => (".borrow()", "borrow", "a mutable borrow exists"),
+ Some(Mutability::Mut) => {
+ (".borrow_mut()", "mutably borrow", "any borrows exist")
+ }
+ None => return,
};
- if question {
err.span_suggestion_verbose(
expr.span.shrink_to_hi(),
format!(
- "use the `?` operator to extract the `{self_ty}` value, propagating \
- {article} `{kind}::{variant}` value to the caller"
+ "use `{suggestion}` to {borrow_kind} the `{ty}`, \
+ panicking if {panic_if}"
),
- "?",
- Applicability::MachineApplicable,
+ suggestion,
+ Applicability::MaybeIncorrect,
);
- } else {
+ } else if tcx.is_diagnostic_item(sym::Mutex, inner_id) {
err.span_suggestion_verbose(
expr.span.shrink_to_hi(),
format!(
- "consider using `{kind}::expect` to unwrap the `{self_ty}` value, \
- panicking if the value is {article} `{kind}::{variant}`"
+ "use `.lock().unwrap()` to borrow the `{ty}`, \
+ blocking the current thread until it can be acquired"
),
- ".expect(\"REASON\")",
- Applicability::HasPlaceholders,
+ ".lock().unwrap()",
+ Applicability::MaybeIncorrect,
);
- }
+ } else if tcx.is_diagnostic_item(sym::RwLock, inner_id) {
+ let (suggestion, borrow_kind) = match mutable {
+ Some(Mutability::Not) => (".read().unwrap()", "borrow"),
+ Some(Mutability::Mut) => (".write().unwrap()", "mutably borrow"),
+ None => return,
+ };
+ err.span_suggestion_verbose(
+ expr.span.shrink_to_hi(),
+ format!(
+ "use `{suggestion}` to {borrow_kind} the `{ty}`, \
+ blocking the current thread until it can be acquired"
+ ),
+ suggestion,
+ Applicability::MaybeIncorrect,
+ );
+ } else {
+ return;
+ };
+
+ err.span_note(
+ tcx.def_span(pick.item.def_id),
+ &format!("the method `{item_name}` exists on the type `{ty}`"),
+ );
}
- // FIXME(compiler-errors): Support suggestions for other matching enum variants
- _ => {}
}
}
@@ -1841,6 +2016,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
Option<ty::Predicate<'tcx>>,
Option<ObligationCause<'tcx>>,
)],
+ static_candidates: &[CandidateSource],
unsatisfied_bounds: bool,
) {
let mut alt_rcvr_sugg = false;
@@ -1955,6 +2131,16 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
None => true,
})
.filter(|info| {
+ // Static candidates are already implemented, and known not to work
+ // Do not suggest them again
+ static_candidates.iter().all(|sc| match *sc {
+ CandidateSource::Trait(def_id) => def_id != info.def_id,
+ CandidateSource::Impl(def_id) => {
+ self.tcx.trait_id_of_impl(def_id) != Some(info.def_id)
+ }
+ })
+ })
+ .filter(|info| {
// We approximate the coherence rules to only suggest
// traits that are legal to implement by requiring that
// either the type or trait is local. Multi-dispatch means
@@ -2077,7 +2263,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
Colon,
Nothing,
}
- let ast_generics = hir.get_generics(id.owner).unwrap();
+ let ast_generics = hir.get_generics(id.owner.def_id).unwrap();
let (sp, mut introducer) = if let Some(span) =
ast_generics.bounds_span_for_suggestions(def_id)
{
@@ -2236,6 +2422,60 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
}
}
+ /// issue #102320, for `unwrap_or` with closure as argument, suggest `unwrap_or_else`
+ /// FIXME: currently not working for suggesting `map_or_else`, see #102408
+ pub(crate) fn suggest_else_fn_with_closure(
+ &self,
+ err: &mut Diagnostic,
+ expr: &hir::Expr<'_>,
+ found: Ty<'tcx>,
+ expected: Ty<'tcx>,
+ ) -> bool {
+ let Some((_def_id_or_name, output, _inputs)) = self.extract_callable_info(expr, found)
+ else { return false; };
+
+ if !self.can_coerce(output, expected) {
+ return false;
+ }
+
+ let parent = self.tcx.hir().get_parent_node(expr.hir_id);
+ if let Some(Node::Expr(call_expr)) = self.tcx.hir().find(parent) &&
+ let hir::ExprKind::MethodCall(
+ hir::PathSegment { ident: method_name, .. },
+ self_expr,
+ args,
+ ..,
+ ) = call_expr.kind &&
+ let Some(self_ty) = self.typeck_results.borrow().expr_ty_opt(self_expr) {
+ let new_name = Ident {
+ name: Symbol::intern(&format!("{}_else", method_name.as_str())),
+ span: method_name.span,
+ };
+ let probe = self.lookup_probe(
+ expr.span,
+ new_name,
+ self_ty,
+ self_expr,
+ ProbeScope::TraitsInScope,
+ );
+
+ // check the method arguments number
+ if let Ok(pick) = probe &&
+ let fn_sig = self.tcx.fn_sig(pick.item.def_id) &&
+ let fn_args = fn_sig.skip_binder().inputs() &&
+ fn_args.len() == args.len() + 1 {
+ err.span_suggestion_verbose(
+ method_name.span.shrink_to_hi(),
+ &format!("try calling `{}` instead", new_name.name.as_str()),
+ "_else",
+ Applicability::MaybeIncorrect,
+ );
+ return true;
+ }
+ }
+ false
+ }
+
/// Checks whether there is a local type somewhere in the chain of
/// autoderefs of `rcvr_ty`.
fn type_derefs_to_local(