Skip to content

Commit 3de72f6

Browse files
authored
Rollup merge of #147951 - Kivooeo:plus-equal-let-chains, r=davidtwco
Add check for `+=` typo in let chains Fixes #147664 it does affect only cases where variable exist in scope, because if the variable is not exist in scope, the suggestion will not make any sense I wanted to add suggestion for case where variable does not in scope to fix `y += 1` to `let y = 1` but I guess it's too much (not too much work, but too much wild predict of what user wants)? if it's good addition in your opinion I can add this in follow up in other things I guess impl is pretty much self-explanatory, if you see there is some possibilities to improve code or/and some _edge-cases_ that I could overlooked feel free to tell about it ah, also about why I think this change is good and why I originally took it, so it seems to me that this is possible to make this typo (I explained this in comment a little), like, both `+` and `=` is the same button (in most of layouts) and for this reasons I didn't added something like `-=` it seems more harder to make this typo r? diagnostics
2 parents 049092a + d472d91 commit 3de72f6

File tree

6 files changed

+215
-18
lines changed

6 files changed

+215
-18
lines changed

compiler/rustc_hir_typeck/src/errors.rs

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
use std::borrow::Cow;
44

55
use rustc_abi::ExternAbi;
6-
use rustc_ast::Label;
6+
use rustc_ast::{AssignOpKind, Label};
77
use rustc_errors::codes::*;
88
use rustc_errors::{
99
Applicability, Diag, DiagArgValue, DiagCtxtHandle, DiagSymbolList, Diagnostic,
@@ -14,9 +14,10 @@ use rustc_hir::ExprKind;
1414
use rustc_macros::{Diagnostic, LintDiagnostic, Subdiagnostic};
1515
use rustc_middle::ty::{self, Ty};
1616
use rustc_span::edition::{Edition, LATEST_STABLE_EDITION};
17+
use rustc_span::source_map::Spanned;
1718
use rustc_span::{Ident, Span, Symbol};
1819

19-
use crate::fluent_generated as fluent;
20+
use crate::{FnCtxt, fluent_generated as fluent};
2021

2122
#[derive(Diagnostic)]
2223
#[diag(hir_typeck_base_expression_double_dot, code = E0797)]
@@ -1144,6 +1145,42 @@ impl<G: EmissionGuarantee> Diagnostic<'_, G> for NakedFunctionsAsmBlock {
11441145
}
11451146
}
11461147

1148+
pub(crate) fn maybe_emit_plus_equals_diagnostic<'a>(
1149+
fnctxt: &FnCtxt<'a, '_>,
1150+
assign_op: Spanned<AssignOpKind>,
1151+
lhs_expr: &hir::Expr<'_>,
1152+
) -> Result<(), Diag<'a>> {
1153+
if assign_op.node == hir::AssignOpKind::AddAssign
1154+
&& let hir::ExprKind::Binary(bin_op, left, right) = &lhs_expr.kind
1155+
&& bin_op.node == hir::BinOpKind::And
1156+
&& crate::op::contains_let_in_chain(left)
1157+
&& let hir::ExprKind::Path(hir::QPath::Resolved(_, path)) = &right.kind
1158+
&& matches!(path.res, hir::def::Res::Local(_))
1159+
{
1160+
let mut err = fnctxt.dcx().struct_span_err(
1161+
assign_op.span,
1162+
"binary assignment operation `+=` cannot be used in a let chain",
1163+
);
1164+
1165+
err.span_label(
1166+
lhs_expr.span,
1167+
"you are add-assigning the right-hand side expression to the result of this let-chain",
1168+
);
1169+
1170+
err.span_label(assign_op.span, "cannot use `+=` in a let chain");
1171+
1172+
err.span_suggestion(
1173+
assign_op.span,
1174+
"you might have meant to compare with `==` instead of assigning with `+=`",
1175+
"==",
1176+
Applicability::MaybeIncorrect,
1177+
);
1178+
1179+
return Err(err);
1180+
}
1181+
Ok(())
1182+
}
1183+
11471184
#[derive(Diagnostic)]
11481185
#[diag(hir_typeck_naked_functions_must_naked_asm, code = E0787)]
11491186
pub(crate) struct NakedFunctionsMustNakedAsm {

compiler/rustc_hir_typeck/src/expr.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ use crate::errors::{
4848
NoFieldOnVariant, ReturnLikeStatementKind, ReturnStmtOutsideOfFnBody, StructExprNonExhaustive,
4949
TypeMismatchFruTypo, YieldExprOutsideOfCoroutine,
5050
};
51+
use crate::op::contains_let_in_chain;
5152
use crate::{
5253
BreakableCtxt, CoroutineTypes, Diverges, FnCtxt, GatherLocalsVisitor, Needs,
5354
TupleArgumentsFlag, cast, fatally_break_rust, report_unexpected_variant_res, type_error_struct,
@@ -1277,6 +1278,12 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
12771278
return;
12781279
}
12791280

1281+
// Skip suggestion if LHS contains a let-chain at this would likely be spurious
1282+
// cc: https://github.com/rust-lang/rust/issues/147664
1283+
if contains_let_in_chain(lhs) {
1284+
return;
1285+
}
1286+
12801287
let mut err = self.dcx().struct_span_err(op_span, "invalid left-hand side of assignment");
12811288
err.code(code);
12821289
err.span_label(lhs.span, "cannot assign to this expression");

compiler/rustc_hir_typeck/src/op.rs

Lines changed: 36 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,8 @@ use {rustc_ast as ast, rustc_hir as hir};
2020

2121
use super::FnCtxt;
2222
use super::method::MethodCallee;
23-
use crate::Expectation;
2423
use crate::method::TreatNotYetDefinedOpaques;
24+
use crate::{Expectation, errors};
2525

2626
impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
2727
/// Checks a `a <op>= b`
@@ -308,23 +308,32 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
308308
let mut path = None;
309309
let lhs_ty_str = self.tcx.short_string(lhs_ty, &mut path);
310310
let rhs_ty_str = self.tcx.short_string(rhs_ty, &mut path);
311+
311312
let (mut err, output_def_id) = match op {
313+
// Try and detect when `+=` was incorrectly
314+
// used instead of `==` in a let-chain
312315
Op::AssignOp(assign_op) => {
313-
let s = assign_op.node.as_str();
314-
let mut err = struct_span_code_err!(
315-
self.dcx(),
316-
expr.span,
317-
E0368,
318-
"binary assignment operation `{}` cannot be applied to type `{}`",
319-
s,
320-
lhs_ty_str,
321-
);
322-
err.span_label(
323-
lhs_expr.span,
324-
format!("cannot use `{}` on type `{}`", s, lhs_ty_str),
325-
);
326-
self.note_unmet_impls_on_type(&mut err, &errors, false);
327-
(err, None)
316+
if let Err(e) =
317+
errors::maybe_emit_plus_equals_diagnostic(&self, assign_op, lhs_expr)
318+
{
319+
(e, None)
320+
} else {
321+
let s = assign_op.node.as_str();
322+
let mut err = struct_span_code_err!(
323+
self.dcx(),
324+
expr.span,
325+
E0368,
326+
"binary assignment operation `{}` cannot be applied to type `{}`",
327+
s,
328+
lhs_ty_str,
329+
);
330+
err.span_label(
331+
lhs_expr.span,
332+
format!("cannot use `{}` on type `{}`", s, lhs_ty_str),
333+
);
334+
self.note_unmet_impls_on_type(&mut err, &errors, false);
335+
(err, None)
336+
}
328337
}
329338
Op::BinOp(bin_op) => {
330339
let message = match bin_op.node {
@@ -1084,6 +1093,17 @@ fn lang_item_for_unop(tcx: TyCtxt<'_>, op: hir::UnOp) -> (Symbol, Option<hir::de
10841093
}
10851094
}
10861095

1096+
/// Check if `expr` contains a `let` or `&&`, indicating presence of a let-chain
1097+
pub(crate) fn contains_let_in_chain(expr: &hir::Expr<'_>) -> bool {
1098+
match &expr.kind {
1099+
hir::ExprKind::Let(..) => true,
1100+
hir::ExprKind::Binary(Spanned { node: hir::BinOpKind::And, .. }, left, right) => {
1101+
contains_let_in_chain(left) || contains_let_in_chain(right)
1102+
}
1103+
_ => false,
1104+
}
1105+
}
1106+
10871107
// Binary operator categories. These categories summarize the behavior
10881108
// with respect to the builtin operations supported.
10891109
#[derive(Clone, Copy)]
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
//@ edition:2024
2+
//@ run-rustfix
3+
4+
#![allow(irrefutable_let_patterns)]
5+
6+
fn test_where_left_is_not_let() {
7+
let y = 2;
8+
if let _ = 1 && true && y == 2 {};
9+
//~^ ERROR expected expression, found `let` statement
10+
//~| NOTE only supported directly in conditions of `if` and `while` expressions
11+
//~| ERROR mismatched types
12+
//~| NOTE expected `bool`, found integer
13+
//~| NOTE you are add-assigning the right-hand side expression to the result of this let-chain
14+
//~| NOTE expected because this is `bool`
15+
//~| ERROR binary assignment operation `+=` cannot be used in a let chain
16+
//~| NOTE cannot use `+=` in a let chain
17+
//~| HELP you might have meant to compare with `==` instead of assigning with `+=`
18+
}
19+
20+
fn test_where_left_is_let() {
21+
let y = 2;
22+
if let _ = 1 && y == 2 {};
23+
//~^ ERROR expected expression, found `let` statement
24+
//~| NOTE only supported directly in conditions of `if` and `while` expressions
25+
//~| ERROR mismatched types
26+
//~| NOTE expected `bool`, found integer
27+
//~| NOTE you are add-assigning the right-hand side expression to the result of this let-chain
28+
//~| ERROR binary assignment operation `+=` cannot be used in a let chain
29+
//~| NOTE cannot use `+=` in a let chain
30+
//~| HELP you might have meant to compare with `==` instead of assigning with `+=`
31+
}
32+
33+
fn main() {
34+
test_where_left_is_let();
35+
test_where_left_is_not_let()
36+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
//@ edition:2024
2+
//@ run-rustfix
3+
4+
#![allow(irrefutable_let_patterns)]
5+
6+
fn test_where_left_is_not_let() {
7+
let y = 2;
8+
if let _ = 1 && true && y += 2 {};
9+
//~^ ERROR expected expression, found `let` statement
10+
//~| NOTE only supported directly in conditions of `if` and `while` expressions
11+
//~| ERROR mismatched types
12+
//~| NOTE expected `bool`, found integer
13+
//~| NOTE you are add-assigning the right-hand side expression to the result of this let-chain
14+
//~| NOTE expected because this is `bool`
15+
//~| ERROR binary assignment operation `+=` cannot be used in a let chain
16+
//~| NOTE cannot use `+=` in a let chain
17+
//~| HELP you might have meant to compare with `==` instead of assigning with `+=`
18+
}
19+
20+
fn test_where_left_is_let() {
21+
let y = 2;
22+
if let _ = 1 && y += 2 {};
23+
//~^ ERROR expected expression, found `let` statement
24+
//~| NOTE only supported directly in conditions of `if` and `while` expressions
25+
//~| ERROR mismatched types
26+
//~| NOTE expected `bool`, found integer
27+
//~| NOTE you are add-assigning the right-hand side expression to the result of this let-chain
28+
//~| ERROR binary assignment operation `+=` cannot be used in a let chain
29+
//~| NOTE cannot use `+=` in a let chain
30+
//~| HELP you might have meant to compare with `==` instead of assigning with `+=`
31+
}
32+
33+
fn main() {
34+
test_where_left_is_let();
35+
test_where_left_is_not_let()
36+
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
error: expected expression, found `let` statement
2+
--> $DIR/let-chains-assign-add-incorrect.rs:8:8
3+
|
4+
LL | if let _ = 1 && true && y += 2 {};
5+
| ^^^^^^^^^
6+
|
7+
= note: only supported directly in conditions of `if` and `while` expressions
8+
9+
error: expected expression, found `let` statement
10+
--> $DIR/let-chains-assign-add-incorrect.rs:22:8
11+
|
12+
LL | if let _ = 1 && y += 2 {};
13+
| ^^^^^^^^^
14+
|
15+
= note: only supported directly in conditions of `if` and `while` expressions
16+
17+
error[E0308]: mismatched types
18+
--> $DIR/let-chains-assign-add-incorrect.rs:8:29
19+
|
20+
LL | if let _ = 1 && true && y += 2 {};
21+
| ----------------- ^ expected `bool`, found integer
22+
| |
23+
| expected because this is `bool`
24+
25+
error: binary assignment operation `+=` cannot be used in a let chain
26+
--> $DIR/let-chains-assign-add-incorrect.rs:8:31
27+
|
28+
LL | if let _ = 1 && true && y += 2 {};
29+
| ---------------------- ^^ cannot use `+=` in a let chain
30+
| |
31+
| you are add-assigning the right-hand side expression to the result of this let-chain
32+
|
33+
help: you might have meant to compare with `==` instead of assigning with `+=`
34+
|
35+
LL - if let _ = 1 && true && y += 2 {};
36+
LL + if let _ = 1 && true && y == 2 {};
37+
|
38+
39+
error[E0308]: mismatched types
40+
--> $DIR/let-chains-assign-add-incorrect.rs:22:21
41+
|
42+
LL | if let _ = 1 && y += 2 {};
43+
| ^ expected `bool`, found integer
44+
45+
error: binary assignment operation `+=` cannot be used in a let chain
46+
--> $DIR/let-chains-assign-add-incorrect.rs:22:23
47+
|
48+
LL | if let _ = 1 && y += 2 {};
49+
| -------------- ^^ cannot use `+=` in a let chain
50+
| |
51+
| you are add-assigning the right-hand side expression to the result of this let-chain
52+
|
53+
help: you might have meant to compare with `==` instead of assigning with `+=`
54+
|
55+
LL - if let _ = 1 && y += 2 {};
56+
LL + if let _ = 1 && y == 2 {};
57+
|
58+
59+
error: aborting due to 6 previous errors
60+
61+
For more information about this error, try `rustc --explain E0308`.

0 commit comments

Comments
 (0)