summaryrefslogtreecommitdiffstats
path: root/compiler/rustc_hir_typeck/src/fn_ctxt/suggestions.rs
diff options
context:
space:
mode:
Diffstat (limited to 'compiler/rustc_hir_typeck/src/fn_ctxt/suggestions.rs')
-rw-r--r--compiler/rustc_hir_typeck/src/fn_ctxt/suggestions.rs354
1 files changed, 221 insertions, 133 deletions
diff --git a/compiler/rustc_hir_typeck/src/fn_ctxt/suggestions.rs b/compiler/rustc_hir_typeck/src/fn_ctxt/suggestions.rs
index 4f92477b5..4d673ac91 100644
--- a/compiler/rustc_hir_typeck/src/fn_ctxt/suggestions.rs
+++ b/compiler/rustc_hir_typeck/src/fn_ctxt/suggestions.rs
@@ -1,33 +1,37 @@
use super::FnCtxt;
use crate::errors::{AddReturnTypeSuggestion, ExpectedReturnTypeLabel};
+use crate::method::probe::{IsSuggestion, Mode, ProbeScope};
use rustc_ast::util::parser::{ExprPrecedence, PREC_POSTFIX};
use rustc_errors::{Applicability, Diagnostic, MultiSpan};
use rustc_hir as hir;
-use rustc_hir::def::{CtorOf, DefKind};
+use rustc_hir::def::{CtorKind, CtorOf, DefKind};
use rustc_hir::lang_items::LangItem;
use rustc_hir::{
Expr, ExprKind, GenericBound, Node, Path, QPath, Stmt, StmtKind, TyKind, WherePredicate,
};
use rustc_hir_analysis::astconv::AstConv;
-use rustc_infer::infer;
use rustc_infer::traits::{self, StatementAsExpression};
use rustc_middle::lint::in_external_macro;
-use rustc_middle::ty::{self, Binder, DefIdTree, IsSuggestable, ToPredicate, Ty};
+use rustc_middle::ty::{
+ self, suggest_constraining_type_params, Binder, DefIdTree, IsSuggestable, ToPredicate, Ty,
+ TypeVisitable,
+};
use rustc_session::errors::ExprParenthesesNeeded;
-use rustc_span::symbol::sym;
-use rustc_span::Span;
+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::traits::error_reporting::suggestions::TypeErrCtxtExt;
use rustc_trait_selection::traits::error_reporting::DefIdOrName;
use rustc_trait_selection::traits::query::evaluate_obligation::InferCtxtExt as _;
-use rustc_trait_selection::traits::NormalizeExt;
impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
pub(crate) fn body_fn_sig(&self) -> Option<ty::FnSig<'tcx>> {
self.typeck_results
.borrow()
.liberated_fn_sigs()
- .get(self.tcx.hir().get_parent_node(self.body_id))
+ .get(self.tcx.hir().parent_id(self.body_id))
.copied()
}
@@ -89,7 +93,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
found: Ty<'tcx>,
can_satisfy: impl FnOnce(Ty<'tcx>) -> bool,
) -> bool {
- let Some((def_id_or_name, output, inputs)) = self.extract_callable_info(expr, found)
+ let Some((def_id_or_name, output, inputs)) = self.extract_callable_info(found)
else { return false; };
if can_satisfy(output) {
let (sugg_call, mut applicability) = match inputs.len() {
@@ -158,99 +162,9 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
/// because the callable type must also be well-formed to be called.
pub(in super::super) fn extract_callable_info(
&self,
- expr: &Expr<'_>,
- found: Ty<'tcx>,
+ ty: Ty<'tcx>,
) -> Option<(DefIdOrName, Ty<'tcx>, Vec<Ty<'tcx>>)> {
- // Autoderef is useful here because sometimes we box callables, etc.
- let Some((def_id_or_name, output, inputs)) = self.autoderef(expr.span, found).silence_errors().find_map(|(found, _)| {
- match *found.kind() {
- ty::FnPtr(fn_sig) =>
- Some((DefIdOrName::Name("function pointer"), fn_sig.output(), fn_sig.inputs())),
- ty::FnDef(def_id, _) => {
- let fn_sig = found.fn_sig(self.tcx);
- Some((DefIdOrName::DefId(def_id), fn_sig.output(), fn_sig.inputs()))
- }
- ty::Closure(def_id, substs) => {
- let fn_sig = substs.as_closure().sig();
- Some((DefIdOrName::DefId(def_id), fn_sig.output(), fn_sig.inputs().map_bound(|inputs| &inputs[1..])))
- }
- ty::Opaque(def_id, substs) => {
- self.tcx.bound_item_bounds(def_id).subst(self.tcx, substs).iter().find_map(|pred| {
- if let ty::PredicateKind::Clause(ty::Clause::Projection(proj)) = pred.kind().skip_binder()
- && Some(proj.projection_ty.item_def_id) == self.tcx.lang_items().fn_once_output()
- // args tuple will always be substs[1]
- && let ty::Tuple(args) = proj.projection_ty.substs.type_at(1).kind()
- {
- Some((
- DefIdOrName::DefId(def_id),
- pred.kind().rebind(proj.term.ty().unwrap()),
- pred.kind().rebind(args.as_slice()),
- ))
- } else {
- None
- }
- })
- }
- ty::Dynamic(data, _, ty::Dyn) => {
- data.iter().find_map(|pred| {
- if let ty::ExistentialPredicate::Projection(proj) = pred.skip_binder()
- && Some(proj.item_def_id) == self.tcx.lang_items().fn_once_output()
- // for existential projection, substs are shifted over by 1
- && let ty::Tuple(args) = proj.substs.type_at(0).kind()
- {
- Some((
- DefIdOrName::Name("trait object"),
- pred.rebind(proj.term.ty().unwrap()),
- pred.rebind(args.as_slice()),
- ))
- } else {
- None
- }
- })
- }
- ty::Param(param) => {
- let def_id = self.tcx.generics_of(self.body_id.owner).type_param(&param, self.tcx).def_id;
- self.tcx.predicates_of(self.body_id.owner).predicates.iter().find_map(|(pred, _)| {
- if let ty::PredicateKind::Clause(ty::Clause::Projection(proj)) = pred.kind().skip_binder()
- && Some(proj.projection_ty.item_def_id) == self.tcx.lang_items().fn_once_output()
- && proj.projection_ty.self_ty() == found
- // args tuple will always be substs[1]
- && let ty::Tuple(args) = proj.projection_ty.substs.type_at(1).kind()
- {
- Some((
- DefIdOrName::DefId(def_id),
- pred.kind().rebind(proj.term.ty().unwrap()),
- pred.kind().rebind(args.as_slice()),
- ))
- } else {
- None
- }
- })
- }
- _ => None,
- }
- }) else { return None; };
-
- let output = self.replace_bound_vars_with_fresh_vars(expr.span, infer::FnCall, output);
- let inputs = inputs
- .skip_binder()
- .iter()
- .map(|ty| {
- self.replace_bound_vars_with_fresh_vars(
- expr.span,
- infer::FnCall,
- inputs.rebind(*ty),
- )
- })
- .collect();
-
- // We don't want to register any extra obligations, which should be
- // implied by wf, but also because that would possibly result in
- // erroneous errors later on.
- let infer::InferOk { value: output, obligations: _ } =
- self.at(&self.misc(expr.span), self.param_env).normalize(output);
-
- if output.is_ty_var() { None } else { Some((def_id_or_name, output, inputs)) }
+ self.err_ctxt().extract_callable_info(self.body_id, self.param_env, ty)
}
pub fn suggest_two_fn_call(
@@ -262,9 +176,9 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
rhs_ty: Ty<'tcx>,
can_satisfy: impl FnOnce(Ty<'tcx>, Ty<'tcx>) -> bool,
) -> bool {
- let Some((_, lhs_output_ty, lhs_inputs)) = self.extract_callable_info(lhs_expr, lhs_ty)
+ let Some((_, lhs_output_ty, lhs_inputs)) = self.extract_callable_info(lhs_ty)
else { return false; };
- let Some((_, rhs_output_ty, rhs_inputs)) = self.extract_callable_info(rhs_expr, rhs_ty)
+ let Some((_, rhs_output_ty, rhs_inputs)) = self.extract_callable_info(rhs_ty)
else { return false; };
if can_satisfy(lhs_output_ty, rhs_output_ty) {
@@ -317,11 +231,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
}
}
- err.multipart_suggestion_verbose(
- format!("use parentheses to call these"),
- sugg,
- applicability,
- );
+ err.multipart_suggestion_verbose("use parentheses to call these", sugg, applicability);
true
} else {
@@ -329,6 +239,31 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
}
}
+ pub fn suggest_remove_last_method_call(
+ &self,
+ err: &mut Diagnostic,
+ expr: &hir::Expr<'tcx>,
+ expected: Ty<'tcx>,
+ ) -> bool {
+ if let hir::ExprKind::MethodCall(hir::PathSegment { ident: method, .. }, recv_expr, &[], _) = expr.kind &&
+ let Some(recv_ty) = self.typeck_results.borrow().expr_ty_opt(recv_expr) &&
+ self.can_coerce(recv_ty, expected) {
+ let span = if let Some(recv_span) = recv_expr.span.find_ancestor_inside(expr.span) {
+ expr.span.with_lo(recv_span.hi())
+ } else {
+ expr.span.with_lo(method.span.lo() - rustc_span::BytePos(1))
+ };
+ err.span_suggestion_verbose(
+ span,
+ "try removing the method call",
+ "",
+ Applicability::MachineApplicable,
+ );
+ return true;
+ }
+ false
+ }
+
pub fn suggest_deref_ref_or_into(
&self,
err: &mut Diagnostic,
@@ -391,10 +326,20 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
} else if self.suggest_else_fn_with_closure(err, expr, found, expected) {
return true;
} else if self.suggest_fn_call(err, expr, found, |output| self.can_coerce(output, expected))
- && let ty::FnDef(def_id, ..) = &found.kind()
- && let Some(sp) = self.tcx.hir().span_if_local(*def_id)
+ && let ty::FnDef(def_id, ..) = *found.kind()
+ && let Some(sp) = self.tcx.hir().span_if_local(def_id)
{
- err.span_label(sp, format!("{found} defined here"));
+ let name = self.tcx.item_name(def_id);
+ let kind = self.tcx.def_kind(def_id);
+ if let DefKind::Ctor(of, CtorKind::Fn) = kind {
+ err.span_label(sp, format!("`{name}` defines {} constructor here, which should be called", match of {
+ CtorOf::Struct => "a struct",
+ CtorOf::Variant => "an enum variant",
+ }));
+ } else {
+ let descr = kind.descr(def_id);
+ err.span_label(sp, format!("{descr} `{name}` defined here"));
+ }
return true;
} else if self.check_for_cast(err, expr, found, expected, expected_ty_expr) {
return true;
@@ -416,7 +361,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
&& method_call_list.contains(&conversion_method.name)
// If receiver is `.clone()` and found type has one of those methods,
// we guess that the user wants to convert from a slice type (`&[]` or `&str`)
- // to an owned type (`Vec` or `String`). These conversions clone internally,
+ // to an owned type (`Vec` or `String`). These conversions clone internally,
// so we remove the user's `clone` call.
{
vec![(
@@ -613,10 +558,10 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
}
}
ty::Adt(def, _) if def.is_box() && self.can_coerce(box_found, expected) => {
- // Check if the parent expression is a call to Pin::new. If it
+ // Check if the parent expression is a call to Pin::new. If it
// is and we were expecting a Box, ergo Pin<Box<expected>>, we
// can suggest Box::pin.
- let parent = self.tcx.hir().get_parent_node(expr.hir_id);
+ let parent = self.tcx.hir().parent_id(expr.hir_id);
let Some(Node::Expr(Expr { kind: ExprKind::Call(fn_name, _), .. })) = self.tcx.hir().find(parent) else {
return false;
};
@@ -752,12 +697,12 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
return true
}
}
- &hir::FnRetTy::Return(ref ty) => {
+ hir::FnRetTy::Return(ty) => {
// Only point to return type if the expected type is the return type, as if they
// are not, the expectation must have been caused by something else.
debug!("suggest_missing_return_type: return type {:?} node {:?}", ty, ty.kind);
let span = ty.span;
- let ty = <dyn AstConv<'_>>::ast_ty_to_ty(self, ty);
+ let ty = self.astconv().ast_ty_to_ty(ty);
debug!("suggest_missing_return_type: return type {:?}", ty);
debug!("suggest_missing_return_type: expected type {:?}", ty);
let bound_vars = self.tcx.late_bound_vars(fn_id);
@@ -828,7 +773,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
..
}) => {
// FIXME: Maybe these calls to `ast_ty_to_ty` can be removed (and the ones below)
- let ty = <dyn AstConv<'_>>::ast_ty_to_ty(self, bounded_ty);
+ let ty = self.astconv().ast_ty_to_ty(bounded_ty);
Some((ty, bounds))
}
_ => None,
@@ -866,7 +811,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
let all_bounds_str = all_matching_bounds_strs.join(" + ");
let ty_param_used_in_fn_params = fn_parameters.iter().any(|param| {
- let ty = <dyn AstConv<'_>>::ast_ty_to_ty(self, param);
+ let ty = self.astconv().ast_ty_to_ty( param);
matches!(ty.kind(), ty::Param(fn_param_ty_param) if expected_ty_as_param == fn_param_ty_param)
});
@@ -920,7 +865,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
}
if let hir::FnRetTy::Return(ty) = fn_decl.output {
- let ty = <dyn AstConv<'_>>::ast_ty_to_ty(self, ty);
+ let ty = self.astconv().ast_ty_to_ty(ty);
let bound_vars = self.tcx.late_bound_vars(fn_id);
let ty = self.tcx.erase_late_bound_regions(Binder::bind_with_vars(ty, bound_vars));
let ty = match self.tcx.asyncness(fn_id.owner) {
@@ -948,7 +893,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
err: &mut Diagnostic,
expr: &hir::Expr<'_>,
) -> bool {
- let sp = self.tcx.sess.source_map().start_point(expr.span);
+ let sp = self.tcx.sess.source_map().start_point(expr.span).with_parent(None);
if let Some(sp) = self.tcx.sess.parse_sess.ambiguous_block_expr_parse.borrow().get(&sp) {
// `{ 42 } &&x` (#61475) or `{ 42 } && if x { 1 } else { 0 }`
err.subdiagnostic(ExprParenthesesNeeded::surrounding(*sp));
@@ -988,6 +933,36 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
}
}
+ pub(crate) fn suggest_clone_for_ref(
+ &self,
+ diag: &mut Diagnostic,
+ expr: &hir::Expr<'_>,
+ expr_ty: Ty<'tcx>,
+ expected_ty: Ty<'tcx>,
+ ) -> bool {
+ if let ty::Ref(_, inner_ty, hir::Mutability::Not) = expr_ty.kind()
+ && let Some(clone_trait_def) = self.tcx.lang_items().clone_trait()
+ && expected_ty == *inner_ty
+ && self
+ .infcx
+ .type_implements_trait(
+ clone_trait_def,
+ [self.tcx.erase_regions(expected_ty)],
+ self.param_env
+ )
+ .must_apply_modulo_regions()
+ {
+ diag.span_suggestion_verbose(
+ expr.span.shrink_to_hi(),
+ "consider using clone here",
+ ".clone()",
+ Applicability::MachineApplicable,
+ );
+ return true;
+ }
+ false
+ }
+
pub(crate) fn suggest_copied_or_cloned(
&self,
diag: &mut Diagnostic,
@@ -1126,9 +1101,9 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
}
let hir = self.tcx.hir();
- let cond_parent = hir.parent_iter(expr.hir_id).skip_while(|(_, node)| {
- matches!(node, hir::Node::Expr(hir::Expr { kind: hir::ExprKind::Binary(op, _, _), .. }) if op.node == hir::BinOpKind::And)
- }).next();
+ let cond_parent = hir.parent_iter(expr.hir_id).find(|(_, node)| {
+ !matches!(node, hir::Node::Expr(hir::Expr { kind: hir::ExprKind::Binary(op, _, _), .. }) if op.node == hir::BinOpKind::And)
+ });
// Don't suggest:
// `let Some(_) = a.is_some() && b`
// ++++++++++
@@ -1234,10 +1209,114 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
);
true
}
+ ExprKind::Lit(Spanned {
+ node: rustc_ast::LitKind::Int(lit, rustc_ast::LitIntType::Unsuffixed),
+ span,
+ }) => {
+ let Ok(snippet) = self.tcx.sess.source_map().span_to_snippet(span) else { return false; };
+ if !(snippet.starts_with("0x") || snippet.starts_with("0X")) {
+ return false;
+ }
+ if snippet.len() <= 5 || !snippet.is_char_boundary(snippet.len() - 3) {
+ return false;
+ }
+ let (_, suffix) = snippet.split_at(snippet.len() - 3);
+ let value = match suffix {
+ "f32" => (lit - 0xf32) / (16 * 16 * 16),
+ "f64" => (lit - 0xf64) / (16 * 16 * 16),
+ _ => return false,
+ };
+ err.span_suggestions(
+ expr.span,
+ "rewrite this as a decimal floating point literal, or use `as` to turn a hex literal into a float",
+ [format!("0x{value:X} as {suffix}"), format!("{value}_{suffix}")],
+ Applicability::MaybeIncorrect,
+ );
+ true
+ }
_ => false,
}
}
+ pub(crate) fn suggest_associated_const(
+ &self,
+ err: &mut Diagnostic,
+ expr: &hir::Expr<'_>,
+ expected_ty: Ty<'tcx>,
+ ) -> bool {
+ let Some((DefKind::AssocFn, old_def_id)) = self.typeck_results.borrow().type_dependent_def(expr.hir_id) else {
+ return false;
+ };
+ let old_item_name = self.tcx.item_name(old_def_id);
+ let capitalized_name = Symbol::intern(&old_item_name.as_str().to_uppercase());
+ if old_item_name == capitalized_name {
+ return false;
+ }
+ let (item, segment) = match expr.kind {
+ hir::ExprKind::Path(QPath::Resolved(
+ Some(ty),
+ hir::Path { segments: [segment], .. },
+ ))
+ | hir::ExprKind::Path(QPath::TypeRelative(ty, segment)) => {
+ let self_ty = self.astconv().ast_ty_to_ty(ty);
+ if let Ok(pick) = self.probe_for_name(
+ Mode::Path,
+ Ident::new(capitalized_name, segment.ident.span),
+ Some(expected_ty),
+ IsSuggestion(true),
+ self_ty,
+ expr.hir_id,
+ ProbeScope::TraitsInScope,
+ ) {
+ (pick.item, segment)
+ } else {
+ return false;
+ }
+ }
+ hir::ExprKind::Path(QPath::Resolved(
+ None,
+ hir::Path { segments: [.., segment], .. },
+ )) => {
+ // we resolved through some path that doesn't end in the item name,
+ // better not do a bad suggestion by accident.
+ if old_item_name != segment.ident.name {
+ return false;
+ }
+ if let Some(item) = self
+ .tcx
+ .associated_items(self.tcx.parent(old_def_id))
+ .filter_by_name_unhygienic(capitalized_name)
+ .next()
+ {
+ (*item, segment)
+ } else {
+ return false;
+ }
+ }
+ _ => return false,
+ };
+ if item.def_id == old_def_id || self.tcx.def_kind(item.def_id) != DefKind::AssocConst {
+ // Same item
+ return false;
+ }
+ let item_ty = self.tcx.type_of(item.def_id);
+ // FIXME(compiler-errors): This check is *so* rudimentary
+ if item_ty.needs_subst() {
+ return false;
+ }
+ if self.can_coerce(item_ty, expected_ty) {
+ err.span_suggestion_verbose(
+ segment.ident.span,
+ format!("try referring to the associated const `{capitalized_name}` instead",),
+ capitalized_name,
+ Applicability::MachineApplicable,
+ );
+ true
+ } else {
+ false
+ }
+ }
+
fn is_loop(&self, id: hir::HirId) -> bool {
let node = self.tcx.hir().get(id);
matches!(node, Node::Expr(Expr { kind: ExprKind::Loop(..), .. }))
@@ -1276,18 +1355,14 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
&& !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)
+ && let trait_ref = ty::Binder::dummy(self.tcx.mk_trait_ref(clone_trait_did, [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),
- })
+ && !self.predicate_must_hold_considering_regions(&traits::Obligation::new(
+ self.tcx,
+ traits::ObligationCause::dummy(),
+ self.param_env,
+ trait_ref,
+ ))
{
diag.span_note(
callee_expr.span,
@@ -1295,6 +1370,19 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
"`{expected_ty}` does not implement `Clone`, so `{found_ty}` was cloned instead"
),
);
+ let owner = self.tcx.hir().enclosing_body_owner(expr.hir_id);
+ if let ty::Param(param) = expected_ty.kind()
+ && let Some(generics) = self.tcx.hir().get_generics(owner)
+ {
+ suggest_constraining_type_params(
+ self.tcx,
+ generics,
+ diag,
+ vec![(param.name.as_str(), "Clone", Some(clone_trait_did))].into_iter(),
+ );
+ } else {
+ self.suggest_derive(diag, &[(trait_ref.to_predicate(self.tcx), None, None)]);
+ }
}
}