Skip to content

Commit 5832db3

Browse files
Suggest using matches or adding == on x == a || b || c
1 parent 2ccafed commit 5832db3

File tree

5 files changed

+182
-7
lines changed

5 files changed

+182
-7
lines changed

compiler/rustc_hir/src/hir.rs

+11-6
Original file line numberDiff line numberDiff line change
@@ -1882,12 +1882,17 @@ impl Expr<'_> {
18821882
/// To a first-order approximation, is this a pattern?
18831883
pub fn is_approximately_pattern(&self) -> bool {
18841884
match &self.kind {
1885-
ExprKind::Array(_)
1886-
| ExprKind::Call(..)
1887-
| ExprKind::Tup(_)
1888-
| ExprKind::Lit(_)
1889-
| ExprKind::Path(_)
1890-
| ExprKind::Struct(..) => true,
1885+
ExprKind::Array(exprs) | ExprKind::Tup(exprs) => {
1886+
exprs.iter().all(|expr| expr.is_approximately_pattern())
1887+
}
1888+
ExprKind::Struct(_, fields, None) => {
1889+
fields.iter().all(|field| field.expr.is_approximately_pattern())
1890+
}
1891+
ExprKind::Call(callee, exprs) => {
1892+
callee.is_approximately_pattern()
1893+
&& exprs.iter().all(|expr| expr.is_approximately_pattern())
1894+
}
1895+
ExprKind::Lit(_) | ExprKind::Path(_) => true,
18911896
_ => false,
18921897
}
18931898
}

compiler/rustc_hir_typeck/src/demand.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
7474
pub fn emit_coerce_suggestions(
7575
&self,
7676
err: &mut Diag<'_>,
77-
expr: &hir::Expr<'tcx>,
77+
expr: &'tcx hir::Expr<'tcx>,
7878
expr_ty: Ty<'tcx>,
7979
expected: Ty<'tcx>,
8080
expected_ty_expr: Option<&'tcx hir::Expr<'tcx>>,
@@ -86,6 +86,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
8686

8787
self.annotate_expected_due_to_let_ty(err, expr, error);
8888
self.annotate_loop_expected_due_to_inference(err, expr, error);
89+
self.annotate_incorrect_or_expr(err, expr, expr_ty, expected);
8990

9091
// FIXME(#73154): For now, we do leak check when coercing function
9192
// pointers in typeck, instead of only during borrowck. This can lead

compiler/rustc_hir_typeck/src/fn_ctxt/suggestions.rs

+109
Original file line numberDiff line numberDiff line change
@@ -3365,4 +3365,113 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
33653365
err.span_label(block.span, "this block is missing a tail expression");
33663366
}
33673367
}
3368+
3369+
pub(crate) fn annotate_incorrect_or_expr(
3370+
&self,
3371+
diag: &mut Diag<'_>,
3372+
expr: &'tcx hir::Expr<'tcx>,
3373+
expr_ty: Ty<'tcx>,
3374+
expected_ty: Ty<'tcx>,
3375+
) {
3376+
if expected_ty != self.tcx.types.bool {
3377+
return;
3378+
}
3379+
let hir::Node::Expr(&hir::Expr {
3380+
kind:
3381+
hir::ExprKind::Binary(
3382+
hir::BinOp { node: hir::BinOpKind::Or, span: binop_span },
3383+
lhs,
3384+
rhs,
3385+
),
3386+
hir_id: parent_hir_id,
3387+
span: full_span,
3388+
..
3389+
}) = self.tcx.parent_hir_node(expr.hir_id)
3390+
else {
3391+
return;
3392+
};
3393+
if rhs.hir_id != expr.hir_id {
3394+
return;
3395+
}
3396+
let hir::Expr {
3397+
kind:
3398+
hir::ExprKind::Binary(hir::BinOp { node: hir::BinOpKind::Eq, span: eq_span }, lhs, _),
3399+
..
3400+
} = *lhs
3401+
else {
3402+
return;
3403+
};
3404+
let Some(lhs_ty) = self.typeck_results.borrow().expr_ty_opt(lhs) else {
3405+
return;
3406+
};
3407+
// Coercion here is not totally right, but w/e.
3408+
if !self.can_coerce(expr_ty, lhs_ty) {
3409+
return;
3410+
}
3411+
3412+
// Track whether all of the exprs to the right, i.e. `|| a || b || c` are all pattern-like.
3413+
let mut is_literal = rhs.is_approximately_pattern();
3414+
// Track the span of the outermost `||` expr.
3415+
let mut full_span = full_span;
3416+
3417+
// Walk up the expr tree gathering up the binop spans of any subsequent `|| a || b || c`.
3418+
let mut expr_hir_id = parent_hir_id;
3419+
let mut binop_spans = vec![binop_span];
3420+
while let hir::Node::Expr(&hir::Expr {
3421+
kind:
3422+
hir::ExprKind::Binary(
3423+
hir::BinOp { node: hir::BinOpKind::Or, span: binop_span },
3424+
lhs,
3425+
rhs,
3426+
),
3427+
hir_id: parent_hir_id,
3428+
span,
3429+
..
3430+
}) = self.tcx.parent_hir_node(expr_hir_id)
3431+
&& lhs.hir_id == expr_hir_id
3432+
{
3433+
binop_spans.push(binop_span);
3434+
expr_hir_id = parent_hir_id;
3435+
full_span = span;
3436+
is_literal |= rhs.is_approximately_pattern();
3437+
}
3438+
3439+
// If the type is structural peq, then suggest `matches!(x, a | b | c)`.
3440+
// Otherwise, suggest adding `x == ` to every `||`.
3441+
if is_literal
3442+
// I know this logic may look a bit sketchy, but int vars don't implement
3443+
// `StructuralPeq` b/c they're unconstrained, so just check for those manually.
3444+
&& (self.tcx.lang_items().structural_peq_trait().is_some_and(|structural_peq_def_id| {
3445+
self.type_implements_trait(structural_peq_def_id, [lhs_ty], self.param_env)
3446+
.must_apply_modulo_regions()
3447+
}) || lhs_ty.is_integral())
3448+
{
3449+
let eq_span = lhs.span.shrink_to_hi().to(eq_span);
3450+
diag.multipart_suggestion_verbose(
3451+
"use `matches!()` to match against multiple values",
3452+
[
3453+
(full_span.shrink_to_lo(), "matches!(".to_string()),
3454+
(full_span.shrink_to_hi(), ")".to_string()),
3455+
(eq_span, ",".to_string()),
3456+
]
3457+
.into_iter()
3458+
.chain(binop_spans.into_iter().map(|span| (span, "|".to_string())))
3459+
.collect(),
3460+
Applicability::MachineApplicable,
3461+
);
3462+
} else if let hir::ExprKind::Path(hir::QPath::Resolved(None, path)) = lhs.kind
3463+
&& let Res::Local(local) = path.res
3464+
{
3465+
let local = self.tcx.hir().name(local);
3466+
let local_name = format!(" {local} ==");
3467+
diag.multipart_suggestion_verbose(
3468+
"use `matches!()` to match against multiple values",
3469+
binop_spans
3470+
.into_iter()
3471+
.map(|span| (span.shrink_to_hi(), local_name.clone()))
3472+
.collect(),
3473+
Applicability::MachineApplicable,
3474+
);
3475+
}
3476+
}
33683477
}

tests/ui/binop/nested-or-of-eq.rs

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
fn main() {
2+
let x = 1;
3+
if x == 1 || 2 || 3 {
4+
//~^ ERROR mismatched types
5+
//~| ERROR mismatched types
6+
println!("Was 1 or 2 or 3");
7+
}
8+
9+
let x = 1.0;
10+
if x == 1.0 || 2.0 || 3.0 {
11+
//~^ ERROR mismatched types
12+
//~| ERROR mismatched types
13+
println!("Was 1.0 or 2.0 or 3.0");
14+
}
15+
}

tests/ui/binop/nested-or-of-eq.stderr

+45
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
error[E0308]: mismatched types
2+
--> $DIR/nested-or-of-eq.rs:3:18
3+
|
4+
LL | if x == 1 || 2 || 3 {
5+
| ------ ^ expected `bool`, found integer
6+
| |
7+
| expected because this is `bool`
8+
|
9+
help: use `matches!()` to match against multiple values
10+
|
11+
LL | if matches!(x, 1 | 2 | 3) {
12+
| +++++++++ ~ ~ ~ +
13+
14+
error[E0308]: mismatched types
15+
--> $DIR/nested-or-of-eq.rs:3:23
16+
|
17+
LL | if x == 1 || 2 || 3 {
18+
| ----------- ^ expected `bool`, found integer
19+
| |
20+
| expected because this is `bool`
21+
22+
error[E0308]: mismatched types
23+
--> $DIR/nested-or-of-eq.rs:10:20
24+
|
25+
LL | if x == 1.0 || 2.0 || 3.0 {
26+
| -------- ^^^ expected `bool`, found floating-point number
27+
| |
28+
| expected because this is `bool`
29+
|
30+
help: use `matches!()` to match against multiple values
31+
|
32+
LL | if x == 1.0 || x == 2.0 || x == 3.0 {
33+
| ++++ ++++
34+
35+
error[E0308]: mismatched types
36+
--> $DIR/nested-or-of-eq.rs:10:27
37+
|
38+
LL | if x == 1.0 || 2.0 || 3.0 {
39+
| --------------- ^^^ expected `bool`, found floating-point number
40+
| |
41+
| expected because this is `bool`
42+
43+
error: aborting due to 4 previous errors
44+
45+
For more information about this error, try `rustc --explain E0308`.

0 commit comments

Comments
 (0)