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.rs1616
1 files changed, 1507 insertions, 109 deletions
diff --git a/compiler/rustc_hir_typeck/src/fn_ctxt/suggestions.rs b/compiler/rustc_hir_typeck/src/fn_ctxt/suggestions.rs
index abb689892..c43d4932f 100644
--- a/compiler/rustc_hir_typeck/src/fn_ctxt/suggestions.rs
+++ b/compiler/rustc_hir_typeck/src/fn_ctxt/suggestions.rs
@@ -2,19 +2,28 @@ use super::FnCtxt;
use crate::errors;
use crate::fluent_generated as fluent;
+use crate::fn_ctxt::rustc_span::BytePos;
+use crate::hir::is_range_literal;
+use crate::method::probe;
use crate::method::probe::{IsSuggestion, Mode, ProbeScope};
+use crate::rustc_middle::ty::Article;
+use crate::ty::TypeAndMut;
+use core::cmp::min;
+use core::iter;
use rustc_ast::util::parser::{ExprPrecedence, PREC_POSTFIX};
use rustc_errors::{Applicability, Diagnostic, MultiSpan};
use rustc_hir as hir;
+use rustc_hir::def::Res;
use rustc_hir::def::{CtorKind, CtorOf, DefKind};
use rustc_hir::lang_items::LangItem;
use rustc_hir::{
- AsyncGeneratorKind, Expr, ExprKind, GeneratorKind, GenericBound, HirId, Node, Path, QPath,
- Stmt, StmtKind, TyKind, WherePredicate,
+ CoroutineKind, CoroutineSource, Expr, ExprKind, GenericBound, HirId, Node, Path, QPath, Stmt,
+ StmtKind, TyKind, WherePredicate,
};
use rustc_hir_analysis::astconv::AstConv;
use rustc_infer::traits::{self, StatementAsExpression};
use rustc_middle::lint::in_external_macro;
+use rustc_middle::middle::stability::EvalResult;
use rustc_middle::ty::print::with_no_trimmed_paths;
use rustc_middle::ty::{
self, suggest_constraining_type_params, Binder, IsSuggestable, ToPredicate, Ty,
@@ -254,22 +263,24 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
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;
- }
+ 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
}
@@ -347,10 +358,16 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
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",
- }));
+ 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 = self.tcx.def_kind_descr(kind, def_id);
err.span_label(sp, format!("{descr} `{name}` defined here"));
@@ -370,25 +387,20 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
if let Some(method_ident) = receiver_method_ident
&& method_ident.name == conversion_method.name
{
- return None // do not suggest code that is already there (#53348)
+ return None; // do not suggest code that is already there (#53348)
}
let method_call_list = [sym::to_vec, sym::to_string];
let mut sugg = if let ExprKind::MethodCall(receiver_method, ..) = expr.kind
&& receiver_method.ident.name == sym::clone
&& 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,
- // so we remove the user's `clone` call.
- {
- vec![(
- receiver_method.ident.span,
- conversion_method.name.to_string()
- )]
- } else if expr.precedence().order()
- < ExprPrecedence::MethodCall.order()
+ // 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,
+ // so we remove the user's `clone` call.
{
+ vec![(receiver_method.ident.span, conversion_method.name.to_string())]
+ } else if expr.precedence().order() < ExprPrecedence::MethodCall.order() {
vec![
(expr.span.shrink_to_lo(), "(".to_string()),
(expr.span.shrink_to_hi(), format!(").{}()", conversion_method.name)),
@@ -431,7 +443,11 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
// Given `Result<_, E>`, check our expected ty is `Result<_, &E>` for
// `as_ref` and `as_deref` compatibility.
let error_tys_equate_as_ref = error_tys.map_or(true, |(found, expected)| {
- self.can_eq(self.param_env, Ty::new_imm_ref(self.tcx,self.tcx.lifetimes.re_erased, found), expected)
+ self.can_eq(
+ self.param_env,
+ Ty::new_imm_ref(self.tcx, self.tcx.lifetimes.re_erased, found),
+ expected,
+ )
});
// FIXME: This could/should be extended to suggest `as_mut` and `as_deref_mut`,
// but those checks need to be a bit more delicate and the benefit is diminishing.
@@ -525,10 +541,10 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
ty::Tuple(tuple) if tuple.is_empty() => {
errors::SuggestBoxing::Unit { start: span.shrink_to_lo(), end: span }
}
- ty::Generator(def_id, ..)
+ ty::Coroutine(def_id, ..)
if matches!(
- self.tcx.generator_kind(def_id),
- Some(GeneratorKind::Async(AsyncGeneratorKind::Closure))
+ self.tcx.coroutine_kind(def_id),
+ Some(CoroutineKind::Async(CoroutineSource::Closure))
) =>
{
errors::SuggestBoxing::AsyncBody
@@ -604,8 +620,12 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
return false;
}
let box_found = Ty::new_box(self.tcx, found);
- let pin_box_found = Ty::new_lang_item(self.tcx, box_found, LangItem::Pin).unwrap();
- let pin_found = Ty::new_lang_item(self.tcx, found, LangItem::Pin).unwrap();
+ let Some(pin_box_found) = Ty::new_lang_item(self.tcx, box_found, LangItem::Pin) else {
+ return false;
+ };
+ let Some(pin_found) = Ty::new_lang_item(self.tcx, found, LangItem::Pin) else {
+ return false;
+ };
match expected.kind() {
ty::Adt(def, _) if Some(def.did()) == pin_did => {
if self.can_coerce(pin_box_found, expected) {
@@ -766,61 +786,75 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
}
&hir::FnRetTy::DefaultReturn(span) if expected.is_unit() => {
if let Some(found) = found.make_suggestable(self.tcx, false) {
- err.subdiagnostic(errors::AddReturnTypeSuggestion::Add { span, found: found.to_string() });
+ err.subdiagnostic(errors::AddReturnTypeSuggestion::Add {
+ span,
+ found: found.to_string(),
+ });
return true;
} else if let ty::Closure(_, args) = found.kind()
// FIXME(compiler-errors): Get better at printing binders...
&& let closure = args.as_closure()
&& closure.sig().is_suggestable(self.tcx, false)
{
- err.subdiagnostic(errors::AddReturnTypeSuggestion::Add { span, found: closure.print_as_impl_trait().to_string() });
+ err.subdiagnostic(errors::AddReturnTypeSuggestion::Add {
+ span,
+ found: closure.print_as_impl_trait().to_string(),
+ });
return true;
} else {
// FIXME: if `found` could be `impl Iterator` we should suggest that.
err.subdiagnostic(errors::AddReturnTypeSuggestion::MissingHere { span });
- return true
+ return true;
}
}
hir::FnRetTy::Return(hir_ty) => {
- let span = hir_ty.span;
-
if let hir::TyKind::OpaqueDef(item_id, ..) = hir_ty.kind
&& let hir::Node::Item(hir::Item {
- kind: hir::ItemKind::OpaqueTy(op_ty),
- ..
+ kind: hir::ItemKind::OpaqueTy(op_ty), ..
}) = self.tcx.hir().get(item_id.hir_id())
- && let [hir::GenericBound::LangItemTrait(
- hir::LangItem::Future, _, _, generic_args)] = op_ty.bounds
+ && let [
+ hir::GenericBound::LangItemTrait(hir::LangItem::Future, _, _, generic_args),
+ ] = op_ty.bounds
&& let hir::GenericArgs { bindings: [ty_binding], .. } = generic_args
- && let hir::TypeBindingKind::Equality { term: hir::Term::Ty(term) } = ty_binding.kind
+ && let hir::TypeBindingKind::Equality { term: hir::Term::Ty(term) } =
+ ty_binding.kind
{
// Check if async function's return type was omitted.
// Don't emit suggestions if the found type is `impl Future<...>`.
debug!(?found);
if found.is_suggestable(self.tcx, false) {
if term.span.is_empty() {
- err.subdiagnostic(errors::AddReturnTypeSuggestion::Add { span, found: found.to_string() });
+ err.subdiagnostic(errors::AddReturnTypeSuggestion::Add {
+ span: term.span,
+ found: found.to_string(),
+ });
return true;
} else {
- err.subdiagnostic(errors::ExpectedReturnTypeLabel::Other { span, expected });
+ err.subdiagnostic(errors::ExpectedReturnTypeLabel::Other {
+ span: term.span,
+ expected,
+ });
}
}
- }
-
- // 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!("return type {:?}", hir_ty);
- let ty = self.astconv().ast_ty_to_ty(hir_ty);
- debug!("return type {:?}", ty);
- debug!("expected type {:?}", expected);
- let bound_vars = self.tcx.late_bound_vars(hir_ty.hir_id.owner.into());
- let ty = Binder::bind_with_vars(ty, bound_vars);
- let ty = self.normalize(span, ty);
- let ty = self.tcx.erase_late_bound_regions(ty);
- if self.can_coerce(expected, ty) {
- err.subdiagnostic(errors::ExpectedReturnTypeLabel::Other { span, expected });
- self.try_suggest_return_impl_trait(err, expected, ty, fn_id);
- return true;
+ } else {
+ // 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!("return type {:?}", hir_ty);
+ let ty = self.astconv().ast_ty_to_ty(hir_ty);
+ debug!("return type {:?}", ty);
+ debug!("expected type {:?}", expected);
+ let bound_vars = self.tcx.late_bound_vars(hir_ty.hir_id.owner.into());
+ let ty = Binder::bind_with_vars(ty, bound_vars);
+ let ty = self.normalize(hir_ty.span, ty);
+ let ty = self.tcx.erase_late_bound_regions(ty);
+ if self.can_coerce(expected, ty) {
+ err.subdiagnostic(errors::ExpectedReturnTypeLabel::Other {
+ span: hir_ty.span,
+ expected,
+ });
+ self.try_suggest_return_impl_trait(err, expected, ty, fn_id);
+ return true;
+ }
}
}
_ => {}
@@ -1075,13 +1109,13 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
.type_implements_trait(
clone_trait_def,
[self.tcx.erase_regions(expected_ty)],
- self.param_env
+ self.param_env,
)
.must_apply_modulo_regions()
- {
+ {
let suggestion = match self.tcx.hir().maybe_get_struct_pattern_shorthand_field(expr) {
Some(ident) => format!(": {ident}.clone()"),
- None => ".clone()".to_string()
+ None => ".clone()".to_string(),
};
diag.span_suggestion_verbose(
@@ -1091,7 +1125,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
Applicability::MachineApplicable,
);
return true;
- }
+ }
false
}
@@ -1119,31 +1153,27 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
let expr_inner_ty = args.type_at(0);
let expected_inner_ty = expected_args.type_at(0);
if let &ty::Ref(_, ty, _mutability) = expr_inner_ty.kind()
- && self.can_eq(self.param_env, ty, expected_inner_ty)
+ && self.can_eq(self.param_env, ty, expected_inner_ty)
+ {
+ let def_path = self.tcx.def_path_str(adt_def.did());
+ let span = expr.span.shrink_to_hi();
+ let subdiag = if self.type_is_copy_modulo_regions(self.param_env, ty) {
+ errors::OptionResultRefMismatch::Copied { span, def_path }
+ } else if let Some(clone_did) = self.tcx.lang_items().clone_trait()
+ && rustc_trait_selection::traits::type_known_to_meet_bound_modulo_regions(
+ self,
+ self.param_env,
+ ty,
+ clone_did,
+ )
{
- let def_path = self.tcx.def_path_str(adt_def.did());
- let span = expr.span.shrink_to_hi();
- let subdiag = if self.type_is_copy_modulo_regions(self.param_env, ty) {
- errors::OptionResultRefMismatch::Copied {
- span, def_path
- }
- } else if let Some(clone_did) = self.tcx.lang_items().clone_trait()
- && rustc_trait_selection::traits::type_known_to_meet_bound_modulo_regions(
- self,
- self.param_env,
- ty,
- clone_did,
- )
- {
- errors::OptionResultRefMismatch::Cloned {
- span, def_path
- }
- } else {
- return false;
- };
- diag.subdiagnostic(subdiag);
- return true;
- }
+ errors::OptionResultRefMismatch::Cloned { span, def_path }
+ } else {
+ return false;
+ };
+ diag.subdiagnostic(subdiag);
+ return true;
+ }
}
false
@@ -1179,14 +1209,12 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
self.tcx,
self.misc(expr.span),
self.param_env,
- ty::TraitRef::new(self.tcx,
- into_def_id,
- [expr_ty, expected_ty]
- ),
+ ty::TraitRef::new(self.tcx, into_def_id, [expr_ty, expected_ty]),
))
{
let mut span = expr.span;
- while expr.span.eq_ctxt(span) && let Some(parent_callsite) = span.parent_callsite()
+ while expr.span.eq_ctxt(span)
+ && let Some(parent_callsite) = span.parent_callsite()
{
span = parent_callsite;
}
@@ -1194,7 +1222,10 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
let sugg = if expr.precedence().order() >= PREC_POSTFIX {
vec![(span.shrink_to_hi(), ".into()".to_owned())]
} else {
- vec![(span.shrink_to_lo(), "(".to_owned()), (span.shrink_to_hi(), ").into()".to_owned())]
+ vec![
+ (span.shrink_to_lo(), "(".to_owned()),
+ (span.shrink_to_hi(), ").into()".to_owned()),
+ ]
};
diag.multipart_suggestion(
format!("call `Into::into` on this expression to convert `{expr_ty}` into `{expected_ty}`"),
@@ -1236,9 +1267,12 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
// since the user probably just misunderstood how `let else`
// and `&&` work together.
if let Some((_, hir::Node::Local(local))) = cond_parent
- && let hir::PatKind::Path(qpath) | hir::PatKind::TupleStruct(qpath, _, _) = &local.pat.kind
+ && let hir::PatKind::Path(qpath) | hir::PatKind::TupleStruct(qpath, _, _) =
+ &local.pat.kind
&& let hir::QPath::Resolved(None, path) = qpath
- && let Some(did) = path.res.opt_def_id()
+ && let Some(did) = path
+ .res
+ .opt_def_id()
.and_then(|did| self.tcx.opt_parent(did))
.and_then(|did| self.tcx.opt_parent(did))
&& self.tcx.is_diagnostic_item(sym::Option, did)
@@ -1605,7 +1639,8 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
..
}) => {
let Some(hir::Node::Local(hir::Local { init: Some(init), .. })) =
- self.tcx.hir().find(self.tcx.hir().parent_id(*pat_hir_id)) else {
+ self.tcx.hir().find(self.tcx.hir().parent_id(*pat_hir_id))
+ else {
return expr;
};
@@ -1632,12 +1667,18 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
// to worry if it's a call to a typed function or closure as this would ne handled
// previously.
hir::ExprKind::Call(Expr { kind: call_expr_kind, .. }, _) => {
- if let hir::ExprKind::Path(hir::QPath::Resolved(None, call_expr_path)) = call_expr_kind
- && let hir::Path { segments: [_], res: crate::Res::Local(binding), .. } = call_expr_path
- && let Some(hir::Node::Pat(hir::Pat { hir_id, .. })) = self.tcx.hir().find(*binding)
+ if let hir::ExprKind::Path(hir::QPath::Resolved(None, call_expr_path)) =
+ call_expr_kind
+ && let hir::Path { segments: [_], res: crate::Res::Local(binding), .. } =
+ call_expr_path
+ && let Some(hir::Node::Pat(hir::Pat { hir_id, .. })) =
+ self.tcx.hir().find(*binding)
&& let Some(closure) = self.tcx.hir().find(self.tcx.hir().parent_id(*hir_id))
&& let hir::Node::Local(hir::Local { init: Some(init), .. }) = closure
- && let Expr { kind: hir::ExprKind::Closure(hir::Closure { body: body_id, .. }), ..} = init
+ && let Expr {
+ kind: hir::ExprKind::Closure(hir::Closure { body: body_id, .. }),
+ ..
+ } = init
{
let hir::Body { value: body_expr, .. } = self.tcx.hir().body(*body_id);
self.note_type_is_not_clone_inner_expr(body_expr)
@@ -1687,4 +1728,1361 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
false
}
}
+
+ pub(crate) fn is_field_suggestable(
+ &self,
+ field: &ty::FieldDef,
+ hir_id: HirId,
+ span: Span,
+ ) -> bool {
+ // The field must be visible in the containing module.
+ field.vis.is_accessible_from(self.tcx.parent_module(hir_id), self.tcx)
+ // The field must not be unstable.
+ && !matches!(
+ self.tcx.eval_stability(field.did, None, rustc_span::DUMMY_SP, None),
+ rustc_middle::middle::stability::EvalResult::Deny { .. }
+ )
+ // If the field is from an external crate it must not be `doc(hidden)`.
+ && (field.did.is_local() || !self.tcx.is_doc_hidden(field.did))
+ // If the field is hygienic it must come from the same syntax context.
+ && self.tcx.def_ident_span(field.did).unwrap().normalize_to_macros_2_0().eq_ctxt(span)
+ }
+
+ pub(crate) fn suggest_missing_unwrap_expect(
+ &self,
+ err: &mut Diagnostic,
+ expr: &hir::Expr<'tcx>,
+ expected: Ty<'tcx>,
+ found: Ty<'tcx>,
+ ) -> bool {
+ let ty::Adt(adt, args) = found.kind() else {
+ return false;
+ };
+ let ret_ty_matches = |diagnostic_item| {
+ let Some(sig) = self.body_fn_sig() else {
+ return false;
+ };
+ let ty::Adt(kind, _) = sig.output().kind() else {
+ return false;
+ };
+ self.tcx.is_diagnostic_item(diagnostic_item, kind.did())
+ };
+
+ // don't suggest anything like `Ok(ok_val).unwrap()` , `Some(some_val).unwrap()`,
+ // `None.unwrap()` etc.
+ let is_ctor = matches!(
+ expr.kind,
+ hir::ExprKind::Call(
+ hir::Expr {
+ kind: hir::ExprKind::Path(hir::QPath::Resolved(
+ None,
+ hir::Path { res: Res::Def(hir::def::DefKind::Ctor(_, _), _), .. },
+ )),
+ ..
+ },
+ ..,
+ ) | hir::ExprKind::Path(hir::QPath::Resolved(
+ None,
+ hir::Path { res: Res::Def(hir::def::DefKind::Ctor(_, _), _), .. },
+ )),
+ );
+
+ let (article, kind, variant, sugg_operator) =
+ if self.tcx.is_diagnostic_item(sym::Result, adt.did()) {
+ ("a", "Result", "Err", ret_ty_matches(sym::Result))
+ } else if self.tcx.is_diagnostic_item(sym::Option, adt.did()) {
+ ("an", "Option", "None", ret_ty_matches(sym::Option))
+ } else {
+ return false;
+ };
+ if is_ctor || !self.can_coerce(args.type_at(0), expected) {
+ return false;
+ }
+
+ let (msg, sugg) = if sugg_operator {
+ (
+ format!(
+ "use the `?` operator to extract the `{found}` value, propagating \
+ {article} `{kind}::{variant}` value to the caller"
+ ),
+ "?",
+ )
+ } else {
+ (
+ format!(
+ "consider using `{kind}::expect` to unwrap the `{found}` value, \
+ panicking if the value is {article} `{kind}::{variant}`"
+ ),
+ ".expect(\"REASON\")",
+ )
+ };
+ err.span_suggestion_verbose(
+ expr.span.shrink_to_hi(),
+ msg,
+ sugg,
+ Applicability::HasPlaceholders,
+ );
+ return true;
+ }
+
+ pub(crate) fn suggest_coercing_result_via_try_operator(
+ &self,
+ err: &mut Diagnostic,
+ expr: &hir::Expr<'tcx>,
+ expected: Ty<'tcx>,
+ found: Ty<'tcx>,
+ ) -> bool {
+ let map = self.tcx.hir();
+ let returned = matches!(
+ map.find_parent(expr.hir_id),
+ Some(hir::Node::Expr(hir::Expr { kind: hir::ExprKind::Ret(_), .. }))
+ ) || map.get_return_block(expr.hir_id).is_some();
+ if returned
+ && let ty::Adt(e, args_e) = expected.kind()
+ && let ty::Adt(f, args_f) = found.kind()
+ && e.did() == f.did()
+ && Some(e.did()) == self.tcx.get_diagnostic_item(sym::Result)
+ && let e_ok = args_e.type_at(0)
+ && let f_ok = args_f.type_at(0)
+ && self.infcx.can_eq(self.param_env, f_ok, e_ok)
+ && let e_err = args_e.type_at(1)
+ && let f_err = args_f.type_at(1)
+ && self
+ .infcx
+ .type_implements_trait(
+ self.tcx.get_diagnostic_item(sym::Into).unwrap(),
+ [f_err, e_err],
+ self.param_env,
+ )
+ .must_apply_modulo_regions()
+ {
+ err.multipart_suggestion(
+ "use `?` to coerce and return an appropriate `Err`, and wrap the resulting value \
+ in `Ok` so the expression remains of type `Result`",
+ vec![
+ (expr.span.shrink_to_lo(), "Ok(".to_string()),
+ (expr.span.shrink_to_hi(), "?)".to_string()),
+ ],
+ Applicability::MaybeIncorrect,
+ );
+ return true;
+ }
+ false
+ }
+
+ /// 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)
+ pub(crate) fn suggest_compatible_variants(
+ &self,
+ err: &mut Diagnostic,
+ expr: &hir::Expr<'_>,
+ expected: Ty<'tcx>,
+ expr_ty: Ty<'tcx>,
+ ) -> bool {
+ if in_external_macro(self.tcx.sess, expr.span) {
+ return false;
+ }
+ if let ty::Adt(expected_adt, args) = expected.kind() {
+ if let hir::ExprKind::Field(base, ident) = expr.kind {
+ let base_ty = self.typeck_results.borrow().expr_ty(base);
+ if self.can_eq(self.param_env, base_ty, expected)
+ && let Some(base_span) = base.span.find_ancestor_inside(expr.span)
+ {
+ err.span_suggestion_verbose(
+ expr.span.with_lo(base_span.hi()),
+ format!("consider removing the tuple struct field `{ident}`"),
+ "",
+ Applicability::MaybeIncorrect,
+ );
+ return true;
+ }
+ }
+
+ // If the expression is of type () and it's the return expression of a block,
+ // we suggest adding a separate return expression instead.
+ // (To avoid things like suggesting `Ok(while .. { .. })`.)
+ if expr_ty.is_unit() {
+ let mut id = expr.hir_id;
+ let mut parent;
+
+ // Unroll desugaring, to make sure this works for `for` loops etc.
+ loop {
+ parent = self.tcx.hir().parent_id(id);
+ if let Some(parent_span) = self.tcx.hir().opt_span(parent) {
+ if parent_span.find_ancestor_inside(expr.span).is_some() {
+ // The parent node is part of the same span, so is the result of the
+ // same expansion/desugaring and not the 'real' parent node.
+ id = parent;
+ continue;
+ }
+ }
+ break;
+ }
+
+ if let Some(hir::Node::Block(&hir::Block {
+ span: block_span, expr: Some(e), ..
+ })) = self.tcx.hir().find(parent)
+ {
+ if e.hir_id == id {
+ if let Some(span) = expr.span.find_ancestor_inside(block_span) {
+ let return_suggestions = if self
+ .tcx
+ .is_diagnostic_item(sym::Result, expected_adt.did())
+ {
+ vec!["Ok(())"]
+ } else if self.tcx.is_diagnostic_item(sym::Option, expected_adt.did()) {
+ vec!["None", "Some(())"]
+ } else {
+ return false;
+ };
+ if let Some(indent) =
+ self.tcx.sess.source_map().indentation_before(span.shrink_to_lo())
+ {
+ // Add a semicolon, except after `}`.
+ let semicolon =
+ match self.tcx.sess.source_map().span_to_snippet(span) {
+ Ok(s) if s.ends_with('}') => "",
+ _ => ";",
+ };
+ err.span_suggestions(
+ span.shrink_to_hi(),
+ "try adding an expression at the end of the block",
+ return_suggestions
+ .into_iter()
+ .map(|r| format!("{semicolon}\n{indent}{r}")),
+ Applicability::MaybeIncorrect,
+ );
+ }
+ return true;
+ }
+ }
+ }
+ }
+
+ let compatible_variants: Vec<(String, _, _, Option<String>)> = expected_adt
+ .variants()
+ .iter()
+ .filter(|variant| {
+ variant.fields.len() == 1
+ })
+ .filter_map(|variant| {
+ let sole_field = &variant.single_field();
+
+ let field_is_local = sole_field.did.is_local();
+ let field_is_accessible =
+ sole_field.vis.is_accessible_from(expr.hir_id.owner.def_id, self.tcx)
+ // Skip suggestions for unstable public fields (for example `Pin::pointer`)
+ && matches!(self.tcx.eval_stability(sole_field.did, None, expr.span, None), EvalResult::Allow | EvalResult::Unmarked);
+
+ if !field_is_local && !field_is_accessible {
+ return None;
+ }
+
+ let note_about_variant_field_privacy = (field_is_local && !field_is_accessible)
+ .then(|| " (its field is private, but it's local to this crate and its privacy can be changed)".to_string());
+
+ let sole_field_ty = sole_field.ty(self.tcx, args);
+ if self.can_coerce(expr_ty, sole_field_ty) {
+ let variant_path =
+ with_no_trimmed_paths!(self.tcx.def_path_str(variant.def_id));
+ // FIXME #56861: DRYer prelude filtering
+ if let Some(path) = variant_path.strip_prefix("std::prelude::")
+ && let Some((_, path)) = path.split_once("::")
+ {
+ return Some((path.to_string(), variant.ctor_kind(), sole_field.name, note_about_variant_field_privacy));
+ }
+ Some((variant_path, variant.ctor_kind(), sole_field.name, note_about_variant_field_privacy))
+ } else {
+ None
+ }
+ })
+ .collect();
+
+ let suggestions_for = |variant: &_, ctor_kind, field_name| {
+ let prefix = match self.tcx.hir().maybe_get_struct_pattern_shorthand_field(expr) {
+ Some(ident) => format!("{ident}: "),
+ None => String::new(),
+ };
+
+ let (open, close) = match ctor_kind {
+ Some(CtorKind::Fn) => ("(".to_owned(), ")"),
+ None => (format!(" {{ {field_name}: "), " }"),
+
+ // unit variants don't have fields
+ Some(CtorKind::Const) => unreachable!(),
+ };
+
+ // Suggest constructor as deep into the block tree as possible.
+ // This fixes https://github.com/rust-lang/rust/issues/101065,
+ // and also just helps make the most minimal suggestions.
+ let mut expr = expr;
+ while let hir::ExprKind::Block(block, _) = &expr.kind
+ && let Some(expr_) = &block.expr
+ {
+ expr = expr_
+ }
+
+ vec![
+ (expr.span.shrink_to_lo(), format!("{prefix}{variant}{open}")),
+ (expr.span.shrink_to_hi(), close.to_owned()),
+ ]
+ };
+
+ match &compatible_variants[..] {
+ [] => { /* No variants to format */ }
+ [(variant, ctor_kind, field_name, note)] => {
+ // Just a single matching variant.
+ err.multipart_suggestion_verbose(
+ format!(
+ "try wrapping the expression in `{variant}`{note}",
+ note = note.as_deref().unwrap_or("")
+ ),
+ suggestions_for(&**variant, *ctor_kind, *field_name),
+ Applicability::MaybeIncorrect,
+ );
+ return true;
+ }
+ _ => {
+ // More than one matching variant.
+ err.multipart_suggestions(
+ format!(
+ "try wrapping the expression in a variant of `{}`",
+ self.tcx.def_path_str(expected_adt.did())
+ ),
+ compatible_variants.into_iter().map(
+ |(variant, ctor_kind, field_name, _)| {
+ suggestions_for(&variant, ctor_kind, field_name)
+ },
+ ),
+ Applicability::MaybeIncorrect,
+ );
+ return true;
+ }
+ }
+ }
+
+ false
+ }
+
+ pub(crate) fn suggest_non_zero_new_unwrap(
+ &self,
+ err: &mut Diagnostic,
+ expr: &hir::Expr<'_>,
+ expected: Ty<'tcx>,
+ expr_ty: Ty<'tcx>,
+ ) -> bool {
+ let tcx = self.tcx;
+ let (adt, unwrap) = match expected.kind() {
+ // In case Option<NonZero*> is wanted, but * is provided, suggest calling new
+ ty::Adt(adt, args) if tcx.is_diagnostic_item(sym::Option, adt.did()) => {
+ // Unwrap option
+ let ty::Adt(adt, _) = args.type_at(0).kind() else {
+ return false;
+ };
+
+ (adt, "")
+ }
+ // In case NonZero* is wanted, but * is provided also add `.unwrap()` to satisfy types
+ ty::Adt(adt, _) => (adt, ".unwrap()"),
+ _ => return false,
+ };
+
+ let map = [
+ (sym::NonZeroU8, tcx.types.u8),
+ (sym::NonZeroU16, tcx.types.u16),
+ (sym::NonZeroU32, tcx.types.u32),
+ (sym::NonZeroU64, tcx.types.u64),
+ (sym::NonZeroU128, tcx.types.u128),
+ (sym::NonZeroI8, tcx.types.i8),
+ (sym::NonZeroI16, tcx.types.i16),
+ (sym::NonZeroI32, tcx.types.i32),
+ (sym::NonZeroI64, tcx.types.i64),
+ (sym::NonZeroI128, tcx.types.i128),
+ ];
+
+ let Some((s, _)) = map.iter().find(|&&(s, t)| {
+ self.tcx.is_diagnostic_item(s, adt.did()) && self.can_coerce(expr_ty, t)
+ }) else {
+ return false;
+ };
+
+ let path = self.tcx.def_path_str(adt.non_enum_variant().def_id);
+
+ err.multipart_suggestion(
+ format!("consider calling `{s}::new`"),
+ vec![
+ (expr.span.shrink_to_lo(), format!("{path}::new(")),
+ (expr.span.shrink_to_hi(), format!("){unwrap}")),
+ ],
+ Applicability::MaybeIncorrect,
+ );
+
+ true
+ }
+
+ /// Identify some cases where `as_ref()` would be appropriate and suggest it.
+ ///
+ /// Given the following code:
+ /// ```compile_fail,E0308
+ /// struct Foo;
+ /// fn takes_ref(_: &Foo) {}
+ /// let ref opt = Some(Foo);
+ ///
+ /// opt.map(|param| takes_ref(param));
+ /// ```
+ /// Suggest using `opt.as_ref().map(|param| takes_ref(param));` instead.
+ ///
+ /// It only checks for `Option` and `Result` and won't work with
+ /// ```ignore (illustrative)
+ /// opt.map(|param| { takes_ref(param) });
+ /// ```
+ fn can_use_as_ref(&self, expr: &hir::Expr<'_>) -> Option<(Vec<(Span, String)>, &'static str)> {
+ let hir::ExprKind::Path(hir::QPath::Resolved(_, ref path)) = expr.kind else {
+ return None;
+ };
+
+ let hir::def::Res::Local(local_id) = path.res else {
+ return None;
+ };
+
+ let local_parent = self.tcx.hir().parent_id(local_id);
+ let Some(Node::Param(hir::Param { hir_id: param_hir_id, .. })) =
+ self.tcx.hir().find(local_parent)
+ else {
+ return None;
+ };
+
+ let param_parent = self.tcx.hir().parent_id(*param_hir_id);
+ let Some(Node::Expr(hir::Expr {
+ hir_id: expr_hir_id,
+ kind: hir::ExprKind::Closure(hir::Closure { fn_decl: closure_fn_decl, .. }),
+ ..
+ })) = self.tcx.hir().find(param_parent)
+ else {
+ return None;
+ };
+
+ let expr_parent = self.tcx.hir().parent_id(*expr_hir_id);
+ let hir = self.tcx.hir().find(expr_parent);
+ let closure_params_len = closure_fn_decl.inputs.len();
+ let (
+ Some(Node::Expr(hir::Expr {
+ kind: hir::ExprKind::MethodCall(method_path, receiver, ..),
+ ..
+ })),
+ 1,
+ ) = (hir, closure_params_len)
+ else {
+ return None;
+ };
+
+ let self_ty = self.typeck_results.borrow().expr_ty(receiver);
+ let name = method_path.ident.name;
+ let is_as_ref_able = match self_ty.peel_refs().kind() {
+ ty::Adt(def, _) => {
+ (self.tcx.is_diagnostic_item(sym::Option, def.did())
+ || self.tcx.is_diagnostic_item(sym::Result, def.did()))
+ && (name == sym::map || name == sym::and_then)
+ }
+ _ => false,
+ };
+ if is_as_ref_able {
+ Some((
+ vec![(method_path.ident.span.shrink_to_lo(), "as_ref().".to_string())],
+ "consider using `as_ref` instead",
+ ))
+ } else {
+ None
+ }
+ }
+
+ /// This function is used to determine potential "simple" improvements or users' errors and
+ /// provide them useful help. For example:
+ ///
+ /// ```compile_fail,E0308
+ /// fn some_fn(s: &str) {}
+ ///
+ /// let x = "hey!".to_owned();
+ /// some_fn(x); // error
+ /// ```
+ ///
+ /// No need to find every potential function which could make a coercion to transform a
+ /// `String` into a `&str` since a `&` would do the trick!
+ ///
+ /// In addition of this check, it also checks between references mutability state. If the
+ /// expected is mutable but the provided isn't, maybe we could just say "Hey, try with
+ /// `&mut`!".
+ pub(crate) fn suggest_deref_or_ref(
+ &self,
+ expr: &hir::Expr<'tcx>,
+ checked_ty: Ty<'tcx>,
+ expected: Ty<'tcx>,
+ ) -> Option<(
+ Vec<(Span, String)>,
+ String,
+ Applicability,
+ bool, /* verbose */
+ bool, /* suggest `&` or `&mut` type annotation */
+ )> {
+ let sess = self.sess();
+ let sp = expr.span;
+
+ // If the span is from an external macro, there's no suggestion we can make.
+ if in_external_macro(sess, sp) {
+ return None;
+ }
+
+ let sm = sess.source_map();
+
+ let replace_prefix = |s: &str, old: &str, new: &str| {
+ s.strip_prefix(old).map(|stripped| new.to_string() + stripped)
+ };
+
+ // `ExprKind::DropTemps` is semantically irrelevant for these suggestions.
+ let expr = expr.peel_drop_temps();
+
+ match (&expr.kind, expected.kind(), checked_ty.kind()) {
+ (_, &ty::Ref(_, exp, _), &ty::Ref(_, check, _)) => match (exp.kind(), check.kind()) {
+ (&ty::Str, &ty::Array(arr, _) | &ty::Slice(arr)) if arr == self.tcx.types.u8 => {
+ if let hir::ExprKind::Lit(_) = expr.kind
+ && let Ok(src) = sm.span_to_snippet(sp)
+ && replace_prefix(&src, "b\"", "\"").is_some()
+ {
+ let pos = sp.lo() + BytePos(1);
+ return Some((
+ vec![(sp.with_hi(pos), String::new())],
+ "consider removing the leading `b`".to_string(),
+ Applicability::MachineApplicable,
+ true,
+ false,
+ ));
+ }
+ }
+ (&ty::Array(arr, _) | &ty::Slice(arr), &ty::Str) if arr == self.tcx.types.u8 => {
+ if let hir::ExprKind::Lit(_) = expr.kind
+ && let Ok(src) = sm.span_to_snippet(sp)
+ && replace_prefix(&src, "\"", "b\"").is_some()
+ {
+ return Some((
+ vec![(sp.shrink_to_lo(), "b".to_string())],
+ "consider adding a leading `b`".to_string(),
+ Applicability::MachineApplicable,
+ true,
+ false,
+ ));
+ }
+ }
+ _ => {}
+ },
+ (_, &ty::Ref(_, _, mutability), _) => {
+ // Check if it can work when put into a ref. For example:
+ //
+ // ```
+ // fn bar(x: &mut i32) {}
+ //
+ // let x = 0u32;
+ // bar(&x); // error, expected &mut
+ // ```
+ let ref_ty = match mutability {
+ hir::Mutability::Mut => {
+ Ty::new_mut_ref(self.tcx, self.tcx.lifetimes.re_static, checked_ty)
+ }
+ hir::Mutability::Not => {
+ Ty::new_imm_ref(self.tcx, self.tcx.lifetimes.re_static, checked_ty)
+ }
+ };
+ if self.can_coerce(ref_ty, expected) {
+ let mut sugg_sp = sp;
+ if let hir::ExprKind::MethodCall(ref segment, receiver, args, _) = expr.kind {
+ let clone_trait =
+ self.tcx.require_lang_item(LangItem::Clone, Some(segment.ident.span));
+ if args.is_empty()
+ && self.typeck_results.borrow().type_dependent_def_id(expr.hir_id).map(
+ |did| {
+ let ai = self.tcx.associated_item(did);
+ ai.trait_container(self.tcx) == Some(clone_trait)
+ },
+ ) == Some(true)
+ && segment.ident.name == sym::clone
+ {
+ // If this expression had a clone call when suggesting borrowing
+ // we want to suggest removing it because it'd now be unnecessary.
+ sugg_sp = receiver.span;
+ }
+ }
+
+ if let hir::ExprKind::Unary(hir::UnOp::Deref, ref inner) = expr.kind
+ && let Some(1) = self.deref_steps(expected, checked_ty)
+ {
+ // We have `*&T`, check if what was expected was `&T`.
+ // If so, we may want to suggest removing a `*`.
+ sugg_sp = sugg_sp.with_hi(inner.span.lo());
+ return Some((
+ vec![(sugg_sp, String::new())],
+ "consider removing deref here".to_string(),
+ Applicability::MachineApplicable,
+ true,
+ false,
+ ));
+ }
+
+ let needs_parens = match expr.kind {
+ // parenthesize if needed (Issue #46756)
+ hir::ExprKind::Cast(_, _) | hir::ExprKind::Binary(_, _, _) => true,
+ // parenthesize borrows of range literals (Issue #54505)
+ _ if is_range_literal(expr) => true,
+ _ => false,
+ };
+
+ if let Some((sugg, msg)) = self.can_use_as_ref(expr) {
+ return Some((
+ sugg,
+ msg.to_string(),
+ Applicability::MachineApplicable,
+ true,
+ false,
+ ));
+ }
+
+ let prefix = match self.tcx.hir().maybe_get_struct_pattern_shorthand_field(expr)
+ {
+ Some(ident) => format!("{ident}: "),
+ None => String::new(),
+ };
+
+ if let Some(hir::Node::Expr(hir::Expr {
+ kind: hir::ExprKind::Assign(..),
+ ..
+ })) = self.tcx.hir().find_parent(expr.hir_id)
+ {
+ if mutability.is_mut() {
+ // Suppressing this diagnostic, we'll properly print it in `check_expr_assign`
+ return None;
+ }
+ }
+
+ let sugg = mutability.ref_prefix_str();
+ let (sugg, verbose) = if needs_parens {
+ (
+ vec![
+ (sp.shrink_to_lo(), format!("{prefix}{sugg}(")),
+ (sp.shrink_to_hi(), ")".to_string()),
+ ],
+ false,
+ )
+ } else {
+ (vec![(sp.shrink_to_lo(), format!("{prefix}{sugg}"))], true)
+ };
+ return Some((
+ sugg,
+ format!("consider {}borrowing here", mutability.mutably_str()),
+ Applicability::MachineApplicable,
+ verbose,
+ false,
+ ));
+ }
+ }
+ (
+ hir::ExprKind::AddrOf(hir::BorrowKind::Ref, _, ref expr),
+ _,
+ &ty::Ref(_, checked, _),
+ ) if self.can_sub(self.param_env, checked, expected) => {
+ let make_sugg = |start: Span, end: BytePos| {
+ // skip `(` for tuples such as `(c) = (&123)`.
+ // make sure we won't suggest like `(c) = 123)` which is incorrect.
+ let sp = sm
+ .span_extend_while(start.shrink_to_lo(), |c| c == '(' || c.is_whitespace())
+ .map_or(start, |s| s.shrink_to_hi());
+ Some((
+ vec![(sp.with_hi(end), String::new())],
+ "consider removing the borrow".to_string(),
+ Applicability::MachineApplicable,
+ true,
+ true,
+ ))
+ };
+
+ // We have `&T`, check if what was expected was `T`. If so,
+ // we may want to suggest removing a `&`.
+ if sm.is_imported(expr.span) {
+ // Go through the spans from which this span was expanded,
+ // and find the one that's pointing inside `sp`.
+ //
+ // E.g. for `&format!("")`, where we want the span to the
+ // `format!()` invocation instead of its expansion.
+ if let Some(call_span) =
+ iter::successors(Some(expr.span), |s| s.parent_callsite())
+ .find(|&s| sp.contains(s))
+ && sm.is_span_accessible(call_span)
+ {
+ return make_sugg(sp, call_span.lo());
+ }
+ return None;
+ }
+ if sp.contains(expr.span) && sm.is_span_accessible(expr.span) {
+ return make_sugg(sp, expr.span.lo());
+ }
+ }
+ (
+ _,
+ &ty::RawPtr(TypeAndMut { ty: ty_b, mutbl: mutbl_b }),
+ &ty::Ref(_, ty_a, mutbl_a),
+ ) => {
+ if let Some(steps) = self.deref_steps(ty_a, ty_b)
+ // Only suggest valid if dereferencing needed.
+ && steps > 0
+ // The pointer type implements `Copy` trait so the suggestion is always valid.
+ && let Ok(src) = sm.span_to_snippet(sp)
+ {
+ let derefs = "*".repeat(steps);
+ let old_prefix = mutbl_a.ref_prefix_str();
+ let new_prefix = mutbl_b.ref_prefix_str().to_owned() + &derefs;
+
+ let suggestion = replace_prefix(&src, old_prefix, &new_prefix).map(|_| {
+ // skip `&` or `&mut ` if both mutabilities are mutable
+ let lo = sp.lo()
+ + BytePos(min(old_prefix.len(), mutbl_b.ref_prefix_str().len()) as _);
+ // skip `&` or `&mut `
+ let hi = sp.lo() + BytePos(old_prefix.len() as _);
+ let sp = sp.with_lo(lo).with_hi(hi);
+
+ (
+ sp,
+ format!(
+ "{}{derefs}",
+ if mutbl_a != mutbl_b { mutbl_b.prefix_str() } else { "" }
+ ),
+ if mutbl_b <= mutbl_a {
+ Applicability::MachineApplicable
+ } else {
+ Applicability::MaybeIncorrect
+ },
+ )
+ });
+
+ if let Some((span, src, applicability)) = suggestion {
+ return Some((
+ vec![(span, src)],
+ "consider dereferencing".to_string(),
+ applicability,
+ true,
+ false,
+ ));
+ }
+ }
+ }
+ _ if sp == expr.span => {
+ if let Some(mut steps) = self.deref_steps(checked_ty, expected) {
+ let mut expr = expr.peel_blocks();
+ let mut prefix_span = expr.span.shrink_to_lo();
+ let mut remove = String::new();
+
+ // Try peeling off any existing `&` and `&mut` to reach our target type
+ while steps > 0 {
+ if let hir::ExprKind::AddrOf(_, mutbl, inner) = expr.kind {
+ // If the expression has `&`, removing it would fix the error
+ prefix_span = prefix_span.with_hi(inner.span.lo());
+ expr = inner;
+ remove.push_str(mutbl.ref_prefix_str());
+ steps -= 1;
+ } else {
+ break;
+ }
+ }
+ // If we've reached our target type with just removing `&`, then just print now.
+ if steps == 0 && !remove.trim().is_empty() {
+ return Some((
+ vec![(prefix_span, String::new())],
+ format!("consider removing the `{}`", remove.trim()),
+ // Do not remove `&&` to get to bool, because it might be something like
+ // { a } && b, which we have a separate fixup suggestion that is more
+ // likely correct...
+ if remove.trim() == "&&" && expected == self.tcx.types.bool {
+ Applicability::MaybeIncorrect
+ } else {
+ Applicability::MachineApplicable
+ },
+ true,
+ false,
+ ));
+ }
+
+ // For this suggestion to make sense, the type would need to be `Copy`,
+ // or we have to be moving out of a `Box<T>`
+ if self.type_is_copy_modulo_regions(self.param_env, expected)
+ // FIXME(compiler-errors): We can actually do this if the checked_ty is
+ // `steps` layers of boxes, not just one, but this is easier and most likely.
+ || (checked_ty.is_box() && steps == 1)
+ // We can always deref a binop that takes its arguments by ref.
+ || matches!(
+ self.tcx.hir().get_parent(expr.hir_id),
+ hir::Node::Expr(hir::Expr { kind: hir::ExprKind::Binary(op, ..), .. })
+ if !op.node.is_by_value()
+ )
+ {
+ let deref_kind = if checked_ty.is_box() {
+ "unboxing the value"
+ } else if checked_ty.is_ref() {
+ "dereferencing the borrow"
+ } else {
+ "dereferencing the type"
+ };
+
+ // Suggest removing `&` if we have removed any, otherwise suggest just
+ // dereferencing the remaining number of steps.
+ let message = if remove.is_empty() {
+ format!("consider {deref_kind}")
+ } else {
+ format!(
+ "consider removing the `{}` and {} instead",
+ remove.trim(),
+ deref_kind
+ )
+ };
+
+ let prefix =
+ match self.tcx.hir().maybe_get_struct_pattern_shorthand_field(expr) {
+ Some(ident) => format!("{ident}: "),
+ None => String::new(),
+ };
+
+ let (span, suggestion) = if self.is_else_if_block(expr) {
+ // Don't suggest nonsense like `else *if`
+ return None;
+ } else if let Some(expr) = self.maybe_get_block_expr(expr) {
+ // prefix should be empty here..
+ (expr.span.shrink_to_lo(), "*".to_string())
+ } else {
+ (prefix_span, format!("{}{}", prefix, "*".repeat(steps)))
+ };
+ if suggestion.trim().is_empty() {
+ return None;
+ }
+
+ return Some((
+ vec![(span, suggestion)],
+ message,
+ Applicability::MachineApplicable,
+ true,
+ false,
+ ));
+ }
+ }
+ }
+ _ => {}
+ }
+ None
+ }
+
+ /// Returns whether the given expression is an `else if`.
+ fn is_else_if_block(&self, expr: &hir::Expr<'_>) -> bool {
+ if let hir::ExprKind::If(..) = expr.kind {
+ let parent_id = self.tcx.hir().parent_id(expr.hir_id);
+ if let Some(Node::Expr(hir::Expr {
+ kind: hir::ExprKind::If(_, _, Some(else_expr)),
+ ..
+ })) = self.tcx.hir().find(parent_id)
+ {
+ return else_expr.hir_id == expr.hir_id;
+ }
+ }
+ false
+ }
+
+ pub(crate) fn suggest_cast(
+ &self,
+ err: &mut Diagnostic,
+ expr: &hir::Expr<'_>,
+ checked_ty: Ty<'tcx>,
+ expected_ty: Ty<'tcx>,
+ expected_ty_expr: Option<&'tcx hir::Expr<'tcx>>,
+ ) -> bool {
+ if self.tcx.sess.source_map().is_imported(expr.span) {
+ // Ignore if span is from within a macro.
+ return false;
+ }
+
+ let Ok(src) = self.tcx.sess.source_map().span_to_snippet(expr.span) else {
+ return false;
+ };
+
+ // If casting this expression to a given numeric type would be appropriate in case of a type
+ // mismatch.
+ //
+ // We want to minimize the amount of casting operations that are suggested, as it can be a
+ // lossy operation with potentially bad side effects, so we only suggest when encountering
+ // an expression that indicates that the original type couldn't be directly changed.
+ //
+ // For now, don't suggest casting with `as`.
+ let can_cast = false;
+
+ let mut sugg = vec![];
+
+ if let Some(hir::Node::ExprField(field)) = self.tcx.hir().find_parent(expr.hir_id) {
+ // `expr` is a literal field for a struct, only suggest if appropriate
+ if field.is_shorthand {
+ // This is a field literal
+ sugg.push((field.ident.span.shrink_to_lo(), format!("{}: ", field.ident)));
+ } else {
+ // Likely a field was meant, but this field wasn't found. Do not suggest anything.
+ return false;
+ }
+ };
+
+ if let hir::ExprKind::Call(path, args) = &expr.kind
+ && let (hir::ExprKind::Path(hir::QPath::TypeRelative(base_ty, path_segment)), 1) =
+ (&path.kind, args.len())
+ // `expr` is a conversion like `u32::from(val)`, do not suggest anything (#63697).
+ && let (hir::TyKind::Path(hir::QPath::Resolved(None, base_ty_path)), sym::from) =
+ (&base_ty.kind, path_segment.ident.name)
+ {
+ if let Some(ident) = &base_ty_path.segments.iter().map(|s| s.ident).next() {
+ match ident.name {
+ sym::i128
+ | sym::i64
+ | sym::i32
+ | sym::i16
+ | sym::i8
+ | sym::u128
+ | sym::u64
+ | sym::u32
+ | sym::u16
+ | sym::u8
+ | sym::isize
+ | sym::usize
+ if base_ty_path.segments.len() == 1 =>
+ {
+ return false;
+ }
+ _ => {}
+ }
+ }
+ }
+
+ let msg = format!(
+ "you can convert {} `{}` to {} `{}`",
+ checked_ty.kind().article(),
+ checked_ty,
+ expected_ty.kind().article(),
+ expected_ty,
+ );
+ let cast_msg = format!(
+ "you can cast {} `{}` to {} `{}`",
+ checked_ty.kind().article(),
+ checked_ty,
+ expected_ty.kind().article(),
+ expected_ty,
+ );
+ let lit_msg = format!(
+ "change the type of the numeric literal from `{checked_ty}` to `{expected_ty}`",
+ );
+
+ let close_paren = if expr.precedence().order() < PREC_POSTFIX {
+ sugg.push((expr.span.shrink_to_lo(), "(".to_string()));
+ ")"
+ } else {
+ ""
+ };
+
+ let mut cast_suggestion = sugg.clone();
+ cast_suggestion.push((expr.span.shrink_to_hi(), format!("{close_paren} as {expected_ty}")));
+ let mut into_suggestion = sugg.clone();
+ into_suggestion.push((expr.span.shrink_to_hi(), format!("{close_paren}.into()")));
+ let mut suffix_suggestion = sugg.clone();
+ suffix_suggestion.push((
+ if matches!(
+ (&expected_ty.kind(), &checked_ty.kind()),
+ (ty::Int(_) | ty::Uint(_), ty::Float(_))
+ ) {
+ // Remove fractional part from literal, for example `42.0f32` into `42`
+ let src = src.trim_end_matches(&checked_ty.to_string());
+ let len = src.split('.').next().unwrap().len();
+ expr.span.with_lo(expr.span.lo() + BytePos(len as u32))
+ } else {
+ let len = src.trim_end_matches(&checked_ty.to_string()).len();
+ expr.span.with_lo(expr.span.lo() + BytePos(len as u32))
+ },
+ if expr.precedence().order() < PREC_POSTFIX {
+ // Readd `)`
+ format!("{expected_ty})")
+ } else {
+ expected_ty.to_string()
+ },
+ ));
+ let literal_is_ty_suffixed = |expr: &hir::Expr<'_>| {
+ if let hir::ExprKind::Lit(lit) = &expr.kind { lit.node.is_suffixed() } else { false }
+ };
+ let is_negative_int =
+ |expr: &hir::Expr<'_>| matches!(expr.kind, hir::ExprKind::Unary(hir::UnOp::Neg, ..));
+ let is_uint = |ty: Ty<'_>| matches!(ty.kind(), ty::Uint(..));
+
+ let in_const_context = self.tcx.hir().is_inside_const_context(expr.hir_id);
+
+ let suggest_fallible_into_or_lhs_from =
+ |err: &mut Diagnostic, exp_to_found_is_fallible: bool| {
+ // If we know the expression the expected type is derived from, we might be able
+ // to suggest a widening conversion rather than a narrowing one (which may
+ // panic). For example, given x: u8 and y: u32, if we know the span of "x",
+ // x > y
+ // can be given the suggestion "u32::from(x) > y" rather than
+ // "x > y.try_into().unwrap()".
+ let lhs_expr_and_src = expected_ty_expr.and_then(|expr| {
+ self.tcx
+ .sess
+ .source_map()
+ .span_to_snippet(expr.span)
+ .ok()
+ .map(|src| (expr, src))
+ });
+ let (msg, suggestion) = if let (Some((lhs_expr, lhs_src)), false) =
+ (lhs_expr_and_src, exp_to_found_is_fallible)
+ {
+ let msg = format!(
+ "you can convert `{lhs_src}` from `{expected_ty}` to `{checked_ty}`, matching the type of `{src}`",
+ );
+ let suggestion = vec![
+ (lhs_expr.span.shrink_to_lo(), format!("{checked_ty}::from(")),
+ (lhs_expr.span.shrink_to_hi(), ")".to_string()),
+ ];
+ (msg, suggestion)
+ } else {
+ let msg =
+ format!("{} and panic if the converted value doesn't fit", msg.clone());
+ let mut suggestion = sugg.clone();
+ suggestion.push((
+ expr.span.shrink_to_hi(),
+ format!("{close_paren}.try_into().unwrap()"),
+ ));
+ (msg, suggestion)
+ };
+ err.multipart_suggestion_verbose(msg, suggestion, Applicability::MachineApplicable);
+ };
+
+ let suggest_to_change_suffix_or_into =
+ |err: &mut Diagnostic,
+ found_to_exp_is_fallible: bool,
+ exp_to_found_is_fallible: bool| {
+ let exp_is_lhs = expected_ty_expr.is_some_and(|e| self.tcx.hir().is_lhs(e.hir_id));
+
+ if exp_is_lhs {
+ return;
+ }
+
+ let always_fallible = found_to_exp_is_fallible
+ && (exp_to_found_is_fallible || expected_ty_expr.is_none());
+ let msg = if literal_is_ty_suffixed(expr) {
+ lit_msg.clone()
+ } else if always_fallible && (is_negative_int(expr) && is_uint(expected_ty)) {
+ // We now know that converting either the lhs or rhs is fallible. Before we
+ // suggest a fallible conversion, check if the value can never fit in the
+ // expected type.
+ let msg = format!("`{src}` cannot fit into type `{expected_ty}`");
+ err.note(msg);
+ return;
+ } else if in_const_context {
+ // Do not recommend `into` or `try_into` in const contexts.
+ return;
+ } else if found_to_exp_is_fallible {
+ return suggest_fallible_into_or_lhs_from(err, exp_to_found_is_fallible);
+ } else {
+ msg.clone()
+ };
+ let suggestion = if literal_is_ty_suffixed(expr) {
+ suffix_suggestion.clone()
+ } else {
+ into_suggestion.clone()
+ };
+ err.multipart_suggestion_verbose(msg, suggestion, Applicability::MachineApplicable);
+ };
+
+ match (&expected_ty.kind(), &checked_ty.kind()) {
+ (ty::Int(exp), ty::Int(found)) => {
+ let (f2e_is_fallible, e2f_is_fallible) = match (exp.bit_width(), found.bit_width())
+ {
+ (Some(exp), Some(found)) if exp < found => (true, false),
+ (Some(exp), Some(found)) if exp > found => (false, true),
+ (None, Some(8 | 16)) => (false, true),
+ (Some(8 | 16), None) => (true, false),
+ (None, _) | (_, None) => (true, true),
+ _ => (false, false),
+ };
+ suggest_to_change_suffix_or_into(err, f2e_is_fallible, e2f_is_fallible);
+ true
+ }
+ (ty::Uint(exp), ty::Uint(found)) => {
+ let (f2e_is_fallible, e2f_is_fallible) = match (exp.bit_width(), found.bit_width())
+ {
+ (Some(exp), Some(found)) if exp < found => (true, false),
+ (Some(exp), Some(found)) if exp > found => (false, true),
+ (None, Some(8 | 16)) => (false, true),
+ (Some(8 | 16), None) => (true, false),
+ (None, _) | (_, None) => (true, true),
+ _ => (false, false),
+ };
+ suggest_to_change_suffix_or_into(err, f2e_is_fallible, e2f_is_fallible);
+ true
+ }
+ (&ty::Int(exp), &ty::Uint(found)) => {
+ let (f2e_is_fallible, e2f_is_fallible) = match (exp.bit_width(), found.bit_width())
+ {
+ (Some(exp), Some(found)) if found < exp => (false, true),
+ (None, Some(8)) => (false, true),
+ _ => (true, true),
+ };
+ suggest_to_change_suffix_or_into(err, f2e_is_fallible, e2f_is_fallible);
+ true
+ }
+ (&ty::Uint(exp), &ty::Int(found)) => {
+ let (f2e_is_fallible, e2f_is_fallible) = match (exp.bit_width(), found.bit_width())
+ {
+ (Some(exp), Some(found)) if found > exp => (true, false),
+ (Some(8), None) => (true, false),
+ _ => (true, true),
+ };
+ suggest_to_change_suffix_or_into(err, f2e_is_fallible, e2f_is_fallible);
+ true
+ }
+ (ty::Float(exp), ty::Float(found)) => {
+ if found.bit_width() < exp.bit_width() {
+ suggest_to_change_suffix_or_into(err, false, true);
+ } else if literal_is_ty_suffixed(expr) {
+ err.multipart_suggestion_verbose(
+ lit_msg,
+ suffix_suggestion,
+ Applicability::MachineApplicable,
+ );
+ } else if can_cast {
+ // Missing try_into implementation for `f64` to `f32`
+ err.multipart_suggestion_verbose(
+ format!("{cast_msg}, producing the closest possible value"),
+ cast_suggestion,
+ Applicability::MaybeIncorrect, // lossy conversion
+ );
+ }
+ true
+ }
+ (&ty::Uint(_) | &ty::Int(_), &ty::Float(_)) => {
+ if literal_is_ty_suffixed(expr) {
+ err.multipart_suggestion_verbose(
+ lit_msg,
+ suffix_suggestion,
+ Applicability::MachineApplicable,
+ );
+ } else if can_cast {
+ // Missing try_into implementation for `{float}` to `{integer}`
+ err.multipart_suggestion_verbose(
+ format!("{msg}, rounding the float towards zero"),
+ cast_suggestion,
+ Applicability::MaybeIncorrect, // lossy conversion
+ );
+ }
+ true
+ }
+ (ty::Float(exp), ty::Uint(found)) => {
+ // if `found` is `None` (meaning found is `usize`), don't suggest `.into()`
+ if exp.bit_width() > found.bit_width().unwrap_or(256) {
+ err.multipart_suggestion_verbose(
+ format!(
+ "{msg}, producing the floating point representation of the integer",
+ ),
+ into_suggestion,
+ Applicability::MachineApplicable,
+ );
+ } else if literal_is_ty_suffixed(expr) {
+ err.multipart_suggestion_verbose(
+ lit_msg,
+ suffix_suggestion,
+ Applicability::MachineApplicable,
+ );
+ } else {
+ // Missing try_into implementation for `{integer}` to `{float}`
+ err.multipart_suggestion_verbose(
+ format!(
+ "{cast_msg}, producing the floating point representation of the integer, \
+ rounded if necessary",
+ ),
+ cast_suggestion,
+ Applicability::MaybeIncorrect, // lossy conversion
+ );
+ }
+ true
+ }
+ (ty::Float(exp), ty::Int(found)) => {
+ // if `found` is `None` (meaning found is `isize`), don't suggest `.into()`
+ if exp.bit_width() > found.bit_width().unwrap_or(256) {
+ err.multipart_suggestion_verbose(
+ format!(
+ "{}, producing the floating point representation of the integer",
+ msg.clone(),
+ ),
+ into_suggestion,
+ Applicability::MachineApplicable,
+ );
+ } else if literal_is_ty_suffixed(expr) {
+ err.multipart_suggestion_verbose(
+ lit_msg,
+ suffix_suggestion,
+ Applicability::MachineApplicable,
+ );
+ } else {
+ // Missing try_into implementation for `{integer}` to `{float}`
+ err.multipart_suggestion_verbose(
+ format!(
+ "{}, producing the floating point representation of the integer, \
+ rounded if necessary",
+ &msg,
+ ),
+ cast_suggestion,
+ Applicability::MaybeIncorrect, // lossy conversion
+ );
+ }
+ true
+ }
+ (
+ &ty::Uint(ty::UintTy::U32 | ty::UintTy::U64 | ty::UintTy::U128)
+ | &ty::Int(ty::IntTy::I32 | ty::IntTy::I64 | ty::IntTy::I128),
+ &ty::Char,
+ ) => {
+ err.multipart_suggestion_verbose(
+ format!("{cast_msg}, since a `char` always occupies 4 bytes"),
+ cast_suggestion,
+ Applicability::MachineApplicable,
+ );
+ true
+ }
+ _ => false,
+ }
+ }
+
+ /// Identify when the user has written `foo..bar()` instead of `foo.bar()`.
+ pub(crate) fn suggest_method_call_on_range_literal(
+ &self,
+ err: &mut Diagnostic,
+ expr: &hir::Expr<'tcx>,
+ checked_ty: Ty<'tcx>,
+ expected_ty: Ty<'tcx>,
+ ) {
+ if !hir::is_range_literal(expr) {
+ return;
+ }
+ let hir::ExprKind::Struct(hir::QPath::LangItem(LangItem::Range, ..), [start, end], _) =
+ expr.kind
+ else {
+ return;
+ };
+ let parent = self.tcx.hir().parent_id(expr.hir_id);
+ if let Some(hir::Node::ExprField(_)) = self.tcx.hir().find(parent) {
+ // Ignore `Foo { field: a..Default::default() }`
+ return;
+ }
+ let mut expr = end.expr;
+ let mut expectation = Some(expected_ty);
+ while let hir::ExprKind::MethodCall(_, rcvr, ..) = expr.kind {
+ // Getting to the root receiver and asserting it is a fn call let's us ignore cases in
+ // `tests/ui/methods/issues/issue-90315.stderr`.
+ expr = rcvr;
+ // If we have more than one layer of calls, then the expected ty
+ // cannot guide the method probe.
+ expectation = None;
+ }
+ let hir::ExprKind::Call(method_name, _) = expr.kind else {
+ return;
+ };
+ let ty::Adt(adt, _) = checked_ty.kind() else {
+ return;
+ };
+ if self.tcx.lang_items().range_struct() != Some(adt.did()) {
+ return;
+ }
+ if let ty::Adt(adt, _) = expected_ty.kind()
+ && self.tcx.lang_items().range_struct() == Some(adt.did())
+ {
+ return;
+ }
+ // Check if start has method named end.
+ let hir::ExprKind::Path(hir::QPath::Resolved(None, p)) = method_name.kind else {
+ return;
+ };
+ let [hir::PathSegment { ident, .. }] = p.segments else {
+ return;
+ };
+ let self_ty = self.typeck_results.borrow().expr_ty(start.expr);
+ let Ok(_pick) = self.lookup_probe_for_diagnostic(
+ *ident,
+ self_ty,
+ expr,
+ probe::ProbeScope::AllTraits,
+ expectation,
+ ) else {
+ return;
+ };
+ let mut sugg = ".";
+ let mut span = start.expr.span.between(end.expr.span);
+ if span.lo() + BytePos(2) == span.hi() {
+ // There's no space between the start, the range op and the end, suggest removal which
+ // will be more noticeable than the replacement of `..` with `.`.
+ span = span.with_lo(span.lo() + BytePos(1));
+ sugg = "";
+ }
+ err.span_suggestion_verbose(
+ span,
+ "you likely meant to write a method call instead of a range",
+ sugg,
+ Applicability::MachineApplicable,
+ );
+ }
+
+ /// Identify when the type error is because `()` is found in a binding that was assigned a
+ /// block without a tail expression.
+ pub(crate) fn suggest_return_binding_for_missing_tail_expr(
+ &self,
+ err: &mut Diagnostic,
+ expr: &hir::Expr<'_>,
+ checked_ty: Ty<'tcx>,
+ expected_ty: Ty<'tcx>,
+ ) {
+ if !checked_ty.is_unit() {
+ return;
+ }
+ let hir::ExprKind::Path(hir::QPath::Resolved(None, path)) = expr.kind else {
+ return;
+ };
+ let hir::def::Res::Local(hir_id) = path.res else {
+ return;
+ };
+ let Some(hir::Node::Pat(pat)) = self.tcx.hir().find(hir_id) else {
+ return;
+ };
+ let Some(hir::Node::Local(hir::Local { ty: None, init: Some(init), .. })) =
+ self.tcx.hir().find_parent(pat.hir_id)
+ else {
+ return;
+ };
+ let hir::ExprKind::Block(block, None) = init.kind else {
+ return;
+ };
+ if block.expr.is_some() {
+ return;
+ }
+ let [.., stmt] = block.stmts else {
+ err.span_label(block.span, "this empty block is missing a tail expression");
+ return;
+ };
+ let hir::StmtKind::Semi(tail_expr) = stmt.kind else {
+ return;
+ };
+ let Some(ty) = self.node_ty_opt(tail_expr.hir_id) else {
+ return;
+ };
+ if self.can_eq(self.param_env, expected_ty, ty) {
+ err.span_suggestion_short(
+ stmt.span.with_lo(tail_expr.span.hi()),
+ "remove this semicolon",
+ "",
+ Applicability::MachineApplicable,
+ );
+ } else {
+ err.span_label(block.span, "this block is missing a tail expression");
+ }
+ }
}