From 147c4a50e0373dc29b8f3bd8d18a21407d9c8fa9 Mon Sep 17 00:00:00 2001 From: yukang Date: Mon, 30 Oct 2023 23:33:48 +0800 Subject: [PATCH] Refactor: move suggestion functions from demand to suggestions --- compiler/rustc_hir_typeck/src/demand.rs | 1276 +---------------- .../src/fn_ctxt/suggestions.rs | 1269 ++++++++++++++++ 2 files changed, 1272 insertions(+), 1273 deletions(-) diff --git a/compiler/rustc_hir_typeck/src/demand.rs b/compiler/rustc_hir_typeck/src/demand.rs index abb8322803010..acaa3e02f092f 100644 --- a/compiler/rustc_hir_typeck/src/demand.rs +++ b/compiler/rustc_hir_typeck/src/demand.rs @@ -1,30 +1,21 @@ use crate::FnCtxt; -use rustc_ast::util::parser::PREC_POSTFIX; use rustc_errors::MultiSpan; use rustc_errors::{Applicability, Diagnostic, DiagnosticBuilder, ErrorGuaranteed}; use rustc_hir as hir; -use rustc_hir::def::{CtorKind, Res}; +use rustc_hir::def::Res; use rustc_hir::intravisit::Visitor; -use rustc_hir::lang_items::LangItem; -use rustc_hir::{is_range_literal, Node}; use rustc_infer::infer::{DefineOpaqueTypes, InferOk}; -use rustc_middle::lint::in_external_macro; -use rustc_middle::middle::stability::EvalResult; use rustc_middle::ty::adjustment::AllowTwoPhase; use rustc_middle::ty::error::{ExpectedFound, TypeError}; use rustc_middle::ty::fold::BottomUpFolder; use rustc_middle::ty::print::with_no_trimmed_paths; -use rustc_middle::ty::{self, Article, AssocItem, Ty, TypeAndMut, TypeFoldable, TypeVisitableExt}; +use rustc_middle::ty::{self, AssocItem, Ty, TypeFoldable, TypeVisitableExt}; use rustc_span::symbol::sym; -use rustc_span::{BytePos, Span, DUMMY_SP}; -use rustc_trait_selection::infer::InferCtxtExt as _; +use rustc_span::{Span, DUMMY_SP}; use rustc_trait_selection::traits::ObligationCause; use super::method::probe; -use std::cmp::min; -use std::iter; - impl<'a, 'tcx> FnCtxt<'a, 'tcx> { pub fn emit_type_mismatch_suggestions( &self, @@ -955,301 +946,6 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { ); } - 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) - 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)> = 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 - } - - 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 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 - } - pub fn get_conversion_methods( &self, span: Span, @@ -1296,82 +992,6 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { } } - /// 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 - } - } - /// If the given `HirId` corresponds to a block with a trailing expression, return that expression pub(crate) fn maybe_get_block_expr( &self, @@ -1383,21 +1003,6 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { } } - /// Returns whether the given expression is an `else if`. - pub(crate) 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 - } - // Returns whether the given expression is a destruct assignment desugaring. // For example, `(a, b) = (1, &2);` // Here we try to find the pattern binding of the expression, @@ -1422,881 +1027,6 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { return false; } - /// 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 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` - 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 - } - - pub 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 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. - 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"); - } - } - fn note_wrong_return_ty_due_to_generic_arg( &self, err: &mut Diagnostic, diff --git a/compiler/rustc_hir_typeck/src/fn_ctxt/suggestions.rs b/compiler/rustc_hir_typeck/src/fn_ctxt/suggestions.rs index ca7dc298de64f..c43d4932fb9f2 100644 --- a/compiler/rustc_hir_typeck/src/fn_ctxt/suggestions.rs +++ b/compiler/rustc_hir_typeck/src/fn_ctxt/suggestions.rs @@ -2,7 +2,14 @@ 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; @@ -16,6 +23,7 @@ use rustc_hir::{ 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, @@ -1816,4 +1824,1265 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { ); 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)> = 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 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` + 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"); + } + } }