diff --git a/compiler/rustc_hir_typeck/src/demand.rs b/compiler/rustc_hir_typeck/src/demand.rs index 7ba57b3b7a2..0ec10dc9ea3 100644 --- a/compiler/rustc_hir_typeck/src/demand.rs +++ b/compiler/rustc_hir_typeck/src/demand.rs @@ -83,7 +83,7 @@ pub fn emit_coerce_suggestions( self.annotate_expected_due_to_let_ty(err, expr, error); self.emit_type_mismatch_suggestions(err, expr, expr_ty, expected, expected_ty_expr, error); self.note_type_is_not_clone(err, expected, expr_ty, expr); - self.note_internal_mutation_in_method(err, expr, expected, expr_ty); + self.note_internal_mutation_in_method(err, expr, Some(expected), expr_ty); self.check_for_range_as_method_call(err, expr, expr_ty, expected); self.check_for_binding_assigned_block_without_tail_expression(err, expr, expr_ty, expected); self.check_wrong_return_type_due_to_generic_arg(err, expr, expr_ty); diff --git a/compiler/rustc_hir_typeck/src/fn_ctxt/_impl.rs b/compiler/rustc_hir_typeck/src/fn_ctxt/_impl.rs index 2075537cad7..578e5b01046 100644 --- a/compiler/rustc_hir_typeck/src/fn_ctxt/_impl.rs +++ b/compiler/rustc_hir_typeck/src/fn_ctxt/_impl.rs @@ -955,44 +955,75 @@ pub(in super::super) fn note_internal_mutation_in_method( &self, err: &mut Diagnostic, expr: &hir::Expr<'_>, - expected: Ty<'tcx>, + expected: Option>, found: Ty<'tcx>, ) { if found != self.tcx.types.unit { return; } - if let ExprKind::MethodCall(path_segment, rcvr, ..) = expr.kind { - if self - .typeck_results + + let ExprKind::MethodCall(path_segment, rcvr, ..) = expr.kind else { + return; + }; + + let rcvr_has_the_expected_type = self + .typeck_results + .borrow() + .expr_ty_adjusted_opt(rcvr) + .and_then(|ty| expected.map(|expected_ty| expected_ty.peel_refs() == ty.peel_refs())) + .unwrap_or(false); + + let prev_call_mutates_and_returns_unit = || { + self.typeck_results .borrow() - .expr_ty_adjusted_opt(rcvr) - .map_or(true, |ty| expected.peel_refs() != ty.peel_refs()) - { - return; - } - let mut sp = MultiSpan::from_span(path_segment.ident.span); - sp.push_span_label( - path_segment.ident.span, - format!( - "this call modifies {} in-place", - match rcvr.kind { - ExprKind::Path(QPath::Resolved( - None, - hir::Path { segments: [segment], .. }, - )) => format!("`{}`", segment.ident), - _ => "its receiver".to_string(), - } - ), - ); + .type_dependent_def_id(expr.hir_id) + .map(|def_id| self.tcx.fn_sig(def_id).skip_binder().skip_binder()) + .and_then(|sig| sig.inputs_and_output.split_last()) + .map(|(output, inputs)| { + output.is_unit() + && inputs + .get(0) + .and_then(|self_ty| self_ty.ref_mutability()) + .map_or(false, rustc_ast::Mutability::is_mut) + }) + .unwrap_or(false) + }; + + if !(rcvr_has_the_expected_type || prev_call_mutates_and_returns_unit()) { + return; + } + + let mut sp = MultiSpan::from_span(path_segment.ident.span); + sp.push_span_label( + path_segment.ident.span, + format!( + "this call modifies {} in-place", + match rcvr.kind { + ExprKind::Path(QPath::Resolved( + None, + hir::Path { segments: [segment], .. }, + )) => format!("`{}`", segment.ident), + _ => "its receiver".to_string(), + } + ), + ); + + let modifies_rcvr_note = + format!("method `{}` modifies its receiver in-place", path_segment.ident); + if rcvr_has_the_expected_type { sp.push_span_label( rcvr.span, "you probably want to use this value after calling the method...", ); + err.span_note(sp, &modifies_rcvr_note); + err.note(&format!("...instead of the `()` output of method `{}`", path_segment.ident)); + } else if let ExprKind::MethodCall(..) = rcvr.kind { err.span_note( sp, - &format!("method `{}` modifies its receiver in-place", path_segment.ident), + modifies_rcvr_note.clone() + ", it is not meant to be used in method chains.", ); - err.note(&format!("...instead of the `()` output of method `{}`", path_segment.ident)); + } else { + err.span_note(sp, &modifies_rcvr_note); } } diff --git a/compiler/rustc_hir_typeck/src/method/suggest.rs b/compiler/rustc_hir_typeck/src/method/suggest.rs index 7055d9257ec..50f2b71250c 100644 --- a/compiler/rustc_hir_typeck/src/method/suggest.rs +++ b/compiler/rustc_hir_typeck/src/method/suggest.rs @@ -416,6 +416,13 @@ pub fn report_no_match_method_error( ); probe.is_ok() }); + + self.note_internal_mutation_in_method( + &mut err, + rcvr_expr, + expected.to_option(&self), + rcvr_ty, + ); } let mut custom_span_label = false; diff --git a/tests/ui/suggestions/chain-method-call-mutation-in-place.rs b/tests/ui/suggestions/chain-method-call-mutation-in-place.rs index cb92ab87a8f..7a4c747961c 100644 --- a/tests/ui/suggestions/chain-method-call-mutation-in-place.rs +++ b/tests/ui/suggestions/chain-method-call-mutation-in-place.rs @@ -1,4 +1,8 @@ -fn main() {} +fn main() { + let x: Vec = vec![1, 2, 3].into_iter().collect::>().sort_by_key(|i| i); //~ ERROR mismatched types + vec![1, 2, 3].into_iter().collect::>().sort_by_key(|i| i).sort(); //~ ERROR no method named `sort` found for unit type `()` in the current scope +} + fn foo(mut s: String) -> String { s.push_str("asdf") //~ ERROR mismatched types } diff --git a/tests/ui/suggestions/chain-method-call-mutation-in-place.stderr b/tests/ui/suggestions/chain-method-call-mutation-in-place.stderr index 11d9b8391f6..128160f10ad 100644 --- a/tests/ui/suggestions/chain-method-call-mutation-in-place.stderr +++ b/tests/ui/suggestions/chain-method-call-mutation-in-place.stderr @@ -1,5 +1,33 @@ error[E0308]: mismatched types - --> $DIR/chain-method-call-mutation-in-place.rs:3:5 + --> $DIR/chain-method-call-mutation-in-place.rs:2:23 + | +LL | let x: Vec = vec![1, 2, 3].into_iter().collect::>().sort_by_key(|i| i); + | -------- ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected `Vec`, found `()` + | | + | expected due to this + | + = note: expected struct `Vec` + found unit type `()` +note: method `sort_by_key` modifies its receiver in-place, it is not meant to be used in method chains. + --> $DIR/chain-method-call-mutation-in-place.rs:2:71 + | +LL | let x: Vec = vec![1, 2, 3].into_iter().collect::>().sort_by_key(|i| i); + | ^^^^^^^^^^^ this call modifies its receiver in-place + +error[E0599]: no method named `sort` found for unit type `()` in the current scope + --> $DIR/chain-method-call-mutation-in-place.rs:3:72 + | +LL | vec![1, 2, 3].into_iter().collect::>().sort_by_key(|i| i).sort(); + | ^^^^ method not found in `()` + | +note: method `sort_by_key` modifies its receiver in-place, it is not meant to be used in method chains. + --> $DIR/chain-method-call-mutation-in-place.rs:3:53 + | +LL | vec![1, 2, 3].into_iter().collect::>().sort_by_key(|i| i).sort(); + | ^^^^^^^^^^^ this call modifies its receiver in-place + +error[E0308]: mismatched types + --> $DIR/chain-method-call-mutation-in-place.rs:7:5 | LL | fn foo(mut s: String) -> String { | ------ expected `String` because of return type @@ -7,7 +35,7 @@ LL | s.push_str("asdf") | ^^^^^^^^^^^^^^^^^^ expected `String`, found `()` | note: method `push_str` modifies its receiver in-place - --> $DIR/chain-method-call-mutation-in-place.rs:3:7 + --> $DIR/chain-method-call-mutation-in-place.rs:7:7 | LL | s.push_str("asdf") | - ^^^^^^^^ this call modifies `s` in-place @@ -15,6 +43,7 @@ LL | s.push_str("asdf") | you probably want to use this value after calling the method... = note: ...instead of the `()` output of method `push_str` -error: aborting due to previous error +error: aborting due to 3 previous errors -For more information about this error, try `rustc --explain E0308`. +Some errors have detailed explanations: E0308, E0599. +For more information about an error, try `rustc --explain E0308`.