diff --git a/src/librustc_typeck/check/demand.rs b/src/librustc_typeck/check/demand.rs index ad97dbe63d8..aa92d8b8b2b 100644 --- a/src/librustc_typeck/check/demand.rs +++ b/src/librustc_typeck/check/demand.rs @@ -36,6 +36,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { self.suggest_missing_await(err, expr, expected, expr_ty); self.suggest_missing_parentheses(err, expr); self.note_need_for_fn_pointer(err, expected, expr_ty); + self.note_internal_mutation_in_method(err, expr, expected, expr_ty); } // Requires that the two types unify, and prints an error message if diff --git a/src/librustc_typeck/check/mod.rs b/src/librustc_typeck/check/mod.rs index c46d2388f3d..91573767389 100644 --- a/src/librustc_typeck/check/mod.rs +++ b/src/librustc_typeck/check/mod.rs @@ -5198,6 +5198,51 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { } } + fn note_internal_mutation_in_method( + &self, + err: &mut DiagnosticBuilder<'_>, + expr: &hir::Expr<'_>, + expected: Ty<'tcx>, + found: Ty<'tcx>, + ) { + if found != self.tcx.types.unit { + return; + } + if let ExprKind::MethodCall(path_segment, _, [rcvr, ..], _) = expr.kind { + if 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(), + } + ), + ); + sp.push_span_label( + rcvr.span, + "you probably want to use this value after calling the method...".to_string(), + ); + err.span_note( + sp, + &format!("method `{}` modifies its receiver in-place", path_segment.ident), + ); + err.note(&format!("...instead of the `()` output of method `{}`", path_segment.ident)); + } + } + /// When encountering an `impl Future` where `BoxFuture` is expected, suggest `Box::pin`. fn suggest_calling_boxed_future_when_appropriate( &self, diff --git a/src/test/ui/suggestions/chain-method-call-mutation-in-place.rs b/src/test/ui/suggestions/chain-method-call-mutation-in-place.rs new file mode 100644 index 00000000000..cb92ab87a8f --- /dev/null +++ b/src/test/ui/suggestions/chain-method-call-mutation-in-place.rs @@ -0,0 +1,4 @@ +fn main() {} +fn foo(mut s: String) -> String { + s.push_str("asdf") //~ ERROR mismatched types +} diff --git a/src/test/ui/suggestions/chain-method-call-mutation-in-place.stderr b/src/test/ui/suggestions/chain-method-call-mutation-in-place.stderr new file mode 100644 index 00000000000..63e3bb78954 --- /dev/null +++ b/src/test/ui/suggestions/chain-method-call-mutation-in-place.stderr @@ -0,0 +1,20 @@ +error[E0308]: mismatched types + --> $DIR/chain-method-call-mutation-in-place.rs:3:5 + | +LL | fn foo(mut s: String) -> String { + | ------ expected `std::string::String` because of return type +LL | s.push_str("asdf") + | ^^^^^^^^^^^^^^^^^^ expected struct `std::string::String`, found `()` + | +note: method `push_str` modifies its receiver in-place + --> $DIR/chain-method-call-mutation-in-place.rs:3:7 + | +LL | s.push_str("asdf") + | - ^^^^^^^^ this call modifies `s` in-place + | | + | 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 + +For more information about this error, try `rustc --explain E0308`.