Skip to content

Commit

Permalink
Add explanation for &mut self method call when expecting -> Self
Browse files Browse the repository at this point in the history
When a user tries to use a method as if it returned a new value of the
same type as its receiver, we will emit a type error. Try to detect this
and provide extra explanation that the method modifies the receiver
in-place.

This has confused people in the wild, like in
https://users.rust-lang.org/t/newbie-why-the-commented-line-stops-the-snippet-from-compiling/47322
  • Loading branch information
estebank committed Aug 17, 2020
1 parent 3df25ae commit ac73474
Show file tree
Hide file tree
Showing 4 changed files with 70 additions and 0 deletions.
1 change: 1 addition & 0 deletions src/librustc_typeck/check/demand.rs
Expand Up @@ -35,6 +35,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
self.suggest_boxing_when_appropriate(err, expr, expected, expr_ty);
self.suggest_missing_await(err, expr, expected, expr_ty);
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
Expand Down
45 changes: 45 additions & 0 deletions src/librustc_typeck/check/mod.rs
Expand Up @@ -5176,6 +5176,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,
Expand Down
@@ -0,0 +1,4 @@
fn main() {}
fn foo(mut s: String) -> String {
s.push_str("asdf") //~ ERROR mismatched types
}
20 changes: 20 additions & 0 deletions 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`.

0 comments on commit ac73474

Please sign in to comment.