diff options
Diffstat (limited to 'compiler/rustc_hir_typeck/src/fn_ctxt/suggestions.rs')
-rw-r--r-- | compiler/rustc_hir_typeck/src/fn_ctxt/suggestions.rs | 1616 |
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"); + } + } } |