Emit "modifies receiver" diagnostic when no method is found

If no method is found when checking method call, we check  if we called a method with signature (&mut T, ...) -> (). If this is the case then we emit a diagnostic message
This commit is contained in:
Maciej Wasilewski 2023-03-14 14:12:47 +01:00
parent bd43458d4c
commit 6a2a6feca8
5 changed files with 102 additions and 31 deletions

View File

@ -83,7 +83,7 @@ pub fn emit_coerce_suggestions(
self.annotate_expected_due_to_let_ty(err, expr, error); 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.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_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_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_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); self.check_wrong_return_type_due_to_generic_arg(err, expr, expr_ty);

View File

@ -955,44 +955,75 @@ pub(in super::super) fn note_internal_mutation_in_method(
&self, &self,
err: &mut Diagnostic, err: &mut Diagnostic,
expr: &hir::Expr<'_>, expr: &hir::Expr<'_>,
expected: Ty<'tcx>, expected: Option<Ty<'tcx>>,
found: Ty<'tcx>, found: Ty<'tcx>,
) { ) {
if found != self.tcx.types.unit { if found != self.tcx.types.unit {
return; return;
} }
if let ExprKind::MethodCall(path_segment, rcvr, ..) = expr.kind {
if self let ExprKind::MethodCall(path_segment, rcvr, ..) = expr.kind else {
.typeck_results 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() .borrow()
.expr_ty_adjusted_opt(rcvr) .type_dependent_def_id(expr.hir_id)
.map_or(true, |ty| expected.peel_refs() != ty.peel_refs()) .map(|def_id| self.tcx.fn_sig(def_id).skip_binder().skip_binder())
{ .and_then(|sig| sig.inputs_and_output.split_last())
return; .map(|(output, inputs)| {
} output.is_unit()
let mut sp = MultiSpan::from_span(path_segment.ident.span); && inputs
sp.push_span_label( .get(0)
path_segment.ident.span, .and_then(|self_ty| self_ty.ref_mutability())
format!( .map_or(false, rustc_ast::Mutability::is_mut)
"this call modifies {} in-place", })
match rcvr.kind { .unwrap_or(false)
ExprKind::Path(QPath::Resolved( };
None,
hir::Path { segments: [segment], .. }, if !(rcvr_has_the_expected_type || prev_call_mutates_and_returns_unit()) {
)) => format!("`{}`", segment.ident), return;
_ => "its receiver".to_string(), }
}
), 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( sp.push_span_label(
rcvr.span, rcvr.span,
"you probably want to use this value after calling the method...", "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( err.span_note(
sp, 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);
} }
} }

View File

@ -416,6 +416,13 @@ pub fn report_no_match_method_error(
); );
probe.is_ok() 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; let mut custom_span_label = false;

View File

@ -1,4 +1,8 @@
fn main() {} fn main() {
let x: Vec<i32> = vec![1, 2, 3].into_iter().collect::<Vec<i32>>().sort_by_key(|i| i); //~ ERROR mismatched types
vec![1, 2, 3].into_iter().collect::<Vec<i32>>().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 { fn foo(mut s: String) -> String {
s.push_str("asdf") //~ ERROR mismatched types s.push_str("asdf") //~ ERROR mismatched types
} }

View File

@ -1,5 +1,33 @@
error[E0308]: mismatched types 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<i32> = vec![1, 2, 3].into_iter().collect::<Vec<i32>>().sort_by_key(|i| i);
| -------- ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected `Vec<i32>`, found `()`
| |
| expected due to this
|
= note: expected struct `Vec<i32>`
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<i32> = vec![1, 2, 3].into_iter().collect::<Vec<i32>>().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::<Vec<i32>>().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::<Vec<i32>>().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 { LL | fn foo(mut s: String) -> String {
| ------ expected `String` because of return type | ------ expected `String` because of return type
@ -7,7 +35,7 @@ LL | s.push_str("asdf")
| ^^^^^^^^^^^^^^^^^^ expected `String`, found `()` | ^^^^^^^^^^^^^^^^^^ expected `String`, found `()`
| |
note: method `push_str` modifies its receiver in-place 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") LL | s.push_str("asdf")
| - ^^^^^^^^ this call modifies `s` in-place | - ^^^^^^^^ 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... | you probably want to use this value after calling the method...
= note: ...instead of the `()` output of method `push_str` = 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`.