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:
commit
b17ee106d8
@ -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);
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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`.
|
||||
|
Loading…
Reference in New Issue
Block a user