diff --git a/compiler/rustc_hir/src/hir.rs b/compiler/rustc_hir/src/hir.rs index 9dab577d84bce..48a41c8bd245a 100644 --- a/compiler/rustc_hir/src/hir.rs +++ b/compiler/rustc_hir/src/hir.rs @@ -1824,6 +1824,14 @@ impl Expr<'_> { _ => false, } } + + pub fn method_ident(&self) -> Option { + match self.kind { + ExprKind::MethodCall(receiver_method, ..) => Some(receiver_method.ident), + ExprKind::Unary(_, expr) | ExprKind::AddrOf(.., expr) => expr.method_ident(), + _ => None, + } + } } /// Checks if the specified expression is a built-in range literal. diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs index 0c271c047091f..d5e65c0b4420a 100644 --- a/compiler/rustc_span/src/symbol.rs +++ b/compiler/rustc_span/src/symbol.rs @@ -1435,6 +1435,8 @@ symbols! { thumb2, thumb_mode: "thumb-mode", tmm_reg, + to_string, + to_vec, todo_macro, tool_attributes, tool_lints, diff --git a/compiler/rustc_typeck/src/check/fn_ctxt/suggestions.rs b/compiler/rustc_typeck/src/check/fn_ctxt/suggestions.rs index ddbc16139734a..80feac184125c 100644 --- a/compiler/rustc_typeck/src/check/fn_ctxt/suggestions.rs +++ b/compiler/rustc_typeck/src/check/fn_ctxt/suggestions.rs @@ -18,8 +18,6 @@ use rustc_span::symbol::sym; use rustc_span::Span; use rustc_trait_selection::traits::query::evaluate_obligation::InferCtxtExt; -use std::iter; - impl<'a, 'tcx> FnCtxt<'a, 'tcx> { pub(in super::super) fn suggest_semicolon_at_end(&self, span: Span, err: &mut Diagnostic) { err.span_suggestion_short( @@ -187,55 +185,56 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { err.span_label(sp, format!("{found} defined here")); } } else if !self.check_for_cast(err, expr, found, expected, expected_ty_expr) { - let is_struct_pat_shorthand_field = - self.maybe_get_struct_pattern_shorthand_field(expr).is_some(); let methods = self.get_conversion_methods(expr.span, expected, found, expr.hir_id); if !methods.is_empty() { - if let Ok(expr_text) = self.sess().source_map().span_to_snippet(expr.span) { - let mut suggestions = iter::zip(iter::repeat(&expr_text), &methods) - .filter_map(|(receiver, method)| { - let method_call = format!(".{}()", method.name); - if receiver.ends_with(&method_call) { - None // do not suggest code that is already there (#53348) - } else { - let method_call_list = [".to_vec()", ".to_string()"]; - let mut sugg = if receiver.ends_with(".clone()") - && method_call_list.contains(&method_call.as_str()) - { - let max_len = receiver.rfind('.').unwrap(); - vec![( - expr.span, - format!("{}{}", &receiver[..max_len], method_call), - )] - } else { - if expr.precedence().order() - < ExprPrecedence::MethodCall.order() - { - vec![ - (expr.span.shrink_to_lo(), "(".to_string()), - (expr.span.shrink_to_hi(), format!("){}", method_call)), - ] - } else { - vec![(expr.span.shrink_to_hi(), method_call)] - } - }; - if is_struct_pat_shorthand_field { - sugg.insert( - 0, - (expr.span.shrink_to_lo(), format!("{}: ", receiver)), - ); - } - Some(sugg) - } - }) - .peekable(); - if suggestions.peek().is_some() { - err.multipart_suggestions( - "try using a conversion method", - suggestions, - Applicability::MaybeIncorrect, - ); - } + let mut suggestions = methods.iter() + .filter_map(|conversion_method| { + let receiver_method_ident = expr.method_ident(); + 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) + } + + 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() + { + vec![ + (expr.span.shrink_to_lo(), "(".to_string()), + (expr.span.shrink_to_hi(), format!(").{}()", conversion_method.name)), + ] + } else { + vec![(expr.span.shrink_to_hi(), format!(".{}()", conversion_method.name))] + }; + let struct_pat_shorthand_field = self.maybe_get_struct_pattern_shorthand_field(expr); + if let Some(name) = struct_pat_shorthand_field { + sugg.insert( + 0, + (expr.span.shrink_to_lo(), format!("{}: ", name)), + ); + } + Some(sugg) + }) + .peekable(); + if suggestions.peek().is_some() { + err.multipart_suggestions( + "try using a conversion method", + suggestions, + Applicability::MaybeIncorrect, + ); } } else if let ty::Adt(found_adt, found_substs) = found.kind() && self.tcx.is_diagnostic_item(sym::Option, found_adt.did()) diff --git a/src/test/ui/suggestions/issue-52820.fixed b/src/test/ui/suggestions/issue-52820.fixed new file mode 100644 index 0000000000000..514690de4d045 --- /dev/null +++ b/src/test/ui/suggestions/issue-52820.fixed @@ -0,0 +1,15 @@ +// run-rustfix +#![allow(dead_code)] + +struct Bravery { + guts: String, + brains: String, +} + +fn main() { + let guts = "mettle"; + let _ = Bravery { + guts: guts.to_string(), //~ ERROR mismatched types + brains: guts.to_string(), //~ ERROR mismatched types + }; +} diff --git a/src/test/ui/suggestions/issue-52820.rs b/src/test/ui/suggestions/issue-52820.rs index 075b07f565203..17cd9224c57ec 100644 --- a/src/test/ui/suggestions/issue-52820.rs +++ b/src/test/ui/suggestions/issue-52820.rs @@ -1,3 +1,6 @@ +// run-rustfix +#![allow(dead_code)] + struct Bravery { guts: String, brains: String, diff --git a/src/test/ui/suggestions/issue-52820.stderr b/src/test/ui/suggestions/issue-52820.stderr index 7b46584450192..09269ed4eee99 100644 --- a/src/test/ui/suggestions/issue-52820.stderr +++ b/src/test/ui/suggestions/issue-52820.stderr @@ -1,5 +1,5 @@ error[E0308]: mismatched types - --> $DIR/issue-52820.rs:9:9 + --> $DIR/issue-52820.rs:12:9 | LL | guts, | ^^^^ expected struct `String`, found `&str` @@ -10,13 +10,13 @@ LL | guts: guts.to_string(), | +++++ ++++++++++++ error[E0308]: mismatched types - --> $DIR/issue-52820.rs:10:17 + --> $DIR/issue-52820.rs:13:17 | LL | brains: guts.clone(), - | ^^^^^^^^^^^^ - | | + | ^^^^^-----^^ + | | | + | | help: try using a conversion method: `to_string` | expected struct `String`, found `&str` - | help: try using a conversion method: `guts.to_string()` error: aborting due to 2 previous errors diff --git a/src/test/ui/suggestions/issue-53692.fixed b/src/test/ui/suggestions/issue-53692.fixed new file mode 100644 index 0000000000000..35a677b476186 --- /dev/null +++ b/src/test/ui/suggestions/issue-53692.fixed @@ -0,0 +1,20 @@ +// run-rustfix +#![allow(unused_variables)] + +fn main() { + let items = vec![1, 2, 3]; + let ref_items: &[i32] = &items; + let items_clone: Vec = ref_items.to_vec(); + //~^ ERROR mismatched types + + // in that case no suggestion will be triggered + let items_clone_2: Vec = items.clone(); + + let s = "hi"; + let string: String = s.to_string(); + //~^ ERROR mismatched types + + // in that case no suggestion will be triggered + let s2 = "hi"; + let string_2: String = s2.to_string(); +} diff --git a/src/test/ui/suggestions/issue-53692.rs b/src/test/ui/suggestions/issue-53692.rs index 30f344e428248..6f6707be5f651 100644 --- a/src/test/ui/suggestions/issue-53692.rs +++ b/src/test/ui/suggestions/issue-53692.rs @@ -1,17 +1,20 @@ +// run-rustfix +#![allow(unused_variables)] + fn main() { - let items = vec![1, 2, 3]; - let ref_items: &[i32] = &items; - let items_clone: Vec = ref_items.clone(); -//~^ ERROR mismatched types + let items = vec![1, 2, 3]; + let ref_items: &[i32] = &items; + let items_clone: Vec = ref_items.clone(); + //~^ ERROR mismatched types - // in that case no suggestion will be triggered - let items_clone_2:Vec = items.clone(); + // in that case no suggestion will be triggered + let items_clone_2: Vec = items.clone(); - let s = "hi"; - let string: String = s.clone(); -//~^ ERROR mismatched types + let s = "hi"; + let string: String = s.clone(); + //~^ ERROR mismatched types - // in that case no suggestion will be triggered - let s2 = "hi"; - let string_2: String = s2.to_string(); + // in that case no suggestion will be triggered + let s2 = "hi"; + let string_2: String = s2.to_string(); } diff --git a/src/test/ui/suggestions/issue-53692.stderr b/src/test/ui/suggestions/issue-53692.stderr index 09c78da54bcd0..3a1b624f402f0 100644 --- a/src/test/ui/suggestions/issue-53692.stderr +++ b/src/test/ui/suggestions/issue-53692.stderr @@ -1,25 +1,25 @@ error[E0308]: mismatched types - --> $DIR/issue-53692.rs:4:37 + --> $DIR/issue-53692.rs:7:33 | -LL | let items_clone: Vec = ref_items.clone(); - | -------- ^^^^^^^^^^^^^^^^^ - | | | - | | expected struct `Vec`, found `&[i32]` - | | help: try using a conversion method: `ref_items.to_vec()` - | expected due to this +LL | let items_clone: Vec = ref_items.clone(); + | -------- ^^^^^^^^^^-----^^ + | | | | + | | | help: try using a conversion method: `to_vec` + | | expected struct `Vec`, found `&[i32]` + | expected due to this | = note: expected struct `Vec` found reference `&[i32]` error[E0308]: mismatched types - --> $DIR/issue-53692.rs:11:30 + --> $DIR/issue-53692.rs:14:26 | -LL | let string: String = s.clone(); - | ------ ^^^^^^^^^ - | | | - | | expected struct `String`, found `&str` - | | help: try using a conversion method: `s.to_string()` - | expected due to this +LL | let string: String = s.clone(); + | ------ ^^-----^^ + | | | | + | | | help: try using a conversion method: `to_string` + | | expected struct `String`, found `&str` + | expected due to this error: aborting due to 2 previous errors