Rollup merge of #109116 - MaciejWas:add-modifies-receiver-diagn-when-method-not-found, r=petrochenkov

Emit diagnostic when calling methods on the unit type in method chains

Fixes #104204.

What this PR does: If a method is not found somewhere in a call chain, we check if we called earlier a method with signature `(&mut T, ...) -> ()`. If this is the case then we emit a diagnostic message.

For example given input:

```
vec![1, 2, 3].into_iter().collect::<Vec<i32>>().sort_by_key(|i| i).sort();
```

the current output is:
```
error[E0599]: no method named `sort` found for unit type `()` in the current scope
 --> hello.rs:3:72
  |
3 |     vec![1, 2, 3].into_iter().collect::<Vec<i32>>().sort_by_key(|i| i).sort();
  |                                                                        ^^^^ method not found in `()`

```

after this PR it will be:
```
error[E0599]: no method named `sort` found for unit type `()` in the current scope
 --> ./hello.rs:3:72
  |
3 |     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.
 --> ./hello.rs:3:53
  |
3 |     vec![1, 2, 3].into_iter().collect::<Vec<i32>>().sort_by_key(|i| i).sort();
  |                                                     ^^^^^^^^^^^ this call modifies its receiver in-place
```
This commit is contained in:
Matthias Krüger 2023-03-14 17:40:07 +01:00 committed by GitHub
commit b17ee106d8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
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.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);

View File

@ -950,44 +950,75 @@ pub(in super::super) fn note_internal_mutation_in_method(
&self,
err: &mut Diagnostic,
expr: &hir::Expr<'_>,
expected: Ty<'tcx>,
expected: Option<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
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);
}
}

View File

@ -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;

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 {
s.push_str("asdf") //~ ERROR mismatched types
}

View File

@ -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<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 {
| ------ 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`.