Skip to content

Commit f88f9a9

Browse files
authored
Fix manual_let_else FN when diverges on simple enum variant (#14732)
Closes #14598 Implemented checks for simple enum variants when checking if match arms are disjointed. changelog: [`manual_let_else`] fix FN when diverges on simple enum variant
2 parents 50cff11 + d0af404 commit f88f9a9

File tree

3 files changed

+119
-6
lines changed

3 files changed

+119
-6
lines changed

clippy_lints/src/manual_let_else.rs

+66-5
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,14 @@ use clippy_utils::diagnostics::span_lint_and_then;
44
use clippy_utils::higher::IfLetOrMatch;
55
use clippy_utils::source::snippet_with_context;
66
use clippy_utils::ty::is_type_diagnostic_item;
7-
use clippy_utils::{is_lint_allowed, is_never_expr, msrvs, pat_and_expr_can_be_question_mark, peel_blocks};
7+
use clippy_utils::{
8+
MaybePath, is_lint_allowed, is_never_expr, is_wild, msrvs, pat_and_expr_can_be_question_mark, path_res, peel_blocks,
9+
};
810
use rustc_ast::BindingMode;
911
use rustc_data_structures::fx::FxHashMap;
1012
use rustc_errors::Applicability;
11-
use rustc_hir::{Expr, ExprKind, MatchSource, Pat, PatExpr, PatExprKind, PatKind, QPath, Stmt, StmtKind};
13+
use rustc_hir::def::{CtorOf, DefKind, Res};
14+
use rustc_hir::{Arm, Expr, ExprKind, HirId, MatchSource, Pat, PatExpr, PatExprKind, PatKind, QPath, Stmt, StmtKind};
1215
use rustc_lint::{LateContext, LintContext};
1316

1417
use rustc_span::Span;
@@ -91,14 +94,15 @@ impl<'tcx> QuestionMark {
9194
let Some((idx, diverging_arm)) = diverging_arm_opt else {
9295
return;
9396
};
97+
98+
let pat_arm = &arms[1 - idx];
9499
// If the non-diverging arm is the first one, its pattern can be reused in a let/else statement.
95100
// However, if it arrives in second position, its pattern may cover some cases already covered
96101
// by the diverging one.
97-
// TODO: accept the non-diverging arm as a second position if patterns are disjointed.
98-
if idx == 0 {
102+
if idx == 0 && !is_arms_disjointed(cx, diverging_arm, pat_arm) {
99103
return;
100104
}
101-
let pat_arm = &arms[1 - idx];
105+
102106
let Some(ident_map) = expr_simple_identity_map(local.pat, pat_arm.pat, pat_arm.body) else {
103107
return;
104108
};
@@ -110,6 +114,63 @@ impl<'tcx> QuestionMark {
110114
}
111115
}
112116

117+
/// Checks if the patterns of the arms are disjointed. Currently, we only support patterns of simple
118+
/// enum variants without nested patterns or bindings.
119+
///
120+
/// TODO: Support more complex patterns.
121+
fn is_arms_disjointed(cx: &LateContext<'_>, arm1: &Arm<'_>, arm2: &Arm<'_>) -> bool {
122+
if arm1.guard.is_some() || arm2.guard.is_some() {
123+
return false;
124+
}
125+
126+
if !is_enum_variant(cx, arm1.pat) || !is_enum_variant(cx, arm2.pat) {
127+
return false;
128+
}
129+
130+
true
131+
}
132+
133+
/// Returns `true` if the given pattern is a variant of an enum.
134+
pub fn is_enum_variant(cx: &LateContext<'_>, pat: &Pat<'_>) -> bool {
135+
struct Pat<'hir>(&'hir rustc_hir::Pat<'hir>);
136+
137+
impl<'hir> MaybePath<'hir> for Pat<'hir> {
138+
fn qpath_opt(&self) -> Option<&QPath<'hir>> {
139+
match self.0.kind {
140+
PatKind::Struct(ref qpath, fields, _)
141+
if fields
142+
.iter()
143+
.all(|field| is_wild(field.pat) || matches!(field.pat.kind, PatKind::Binding(..))) =>
144+
{
145+
Some(qpath)
146+
},
147+
PatKind::TupleStruct(ref qpath, pats, _)
148+
if pats
149+
.iter()
150+
.all(|pat| is_wild(pat) || matches!(pat.kind, PatKind::Binding(..))) =>
151+
{
152+
Some(qpath)
153+
},
154+
PatKind::Expr(&PatExpr {
155+
kind: PatExprKind::Path(ref qpath),
156+
..
157+
}) => Some(qpath),
158+
_ => None,
159+
}
160+
}
161+
162+
fn hir_id(&self) -> HirId {
163+
self.0.hir_id
164+
}
165+
}
166+
167+
let res = path_res(cx, &Pat(pat));
168+
matches!(
169+
res,
170+
Res::Def(DefKind::Variant, ..) | Res::Def(DefKind::Ctor(CtorOf::Variant, _), _)
171+
)
172+
}
173+
113174
fn emit_manual_let_else(
114175
cx: &LateContext<'_>,
115176
span: Span,

tests/ui/manual_let_else.rs

+32
Original file line numberDiff line numberDiff line change
@@ -514,3 +514,35 @@ mod issue13768 {
514514
};
515515
}
516516
}
517+
518+
mod issue14598 {
519+
fn bar() -> Result<bool, &'static str> {
520+
let value = match foo() {
521+
//~^ manual_let_else
522+
Err(_) => return Err("abc"),
523+
Ok(value) => value,
524+
};
525+
526+
let w = Some(0);
527+
let v = match w {
528+
//~^ manual_let_else
529+
None => return Err("abc"),
530+
Some(x) => x,
531+
};
532+
533+
enum Foo<T> {
534+
Foo(T),
535+
}
536+
537+
let v = match Foo::Foo(Some(())) {
538+
Foo::Foo(Some(_)) => return Err("abc"),
539+
Foo::Foo(v) => v,
540+
};
541+
542+
Ok(value == 42)
543+
}
544+
545+
fn foo() -> Result<u32, &'static str> {
546+
todo!()
547+
}
548+
}

tests/ui/manual_let_else.stderr

+21-1
Original file line numberDiff line numberDiff line change
@@ -529,5 +529,25 @@ LL + return;
529529
LL + };
530530
|
531531

532-
error: aborting due to 33 previous errors
532+
error: this could be rewritten as `let...else`
533+
--> tests/ui/manual_let_else.rs:520:9
534+
|
535+
LL | / let value = match foo() {
536+
LL | |
537+
LL | | Err(_) => return Err("abc"),
538+
LL | | Ok(value) => value,
539+
LL | | };
540+
| |__________^ help: consider writing: `let Ok(value) = foo() else { return Err("abc") };`
541+
542+
error: this could be rewritten as `let...else`
543+
--> tests/ui/manual_let_else.rs:527:9
544+
|
545+
LL | / let v = match w {
546+
LL | |
547+
LL | | None => return Err("abc"),
548+
LL | | Some(x) => x,
549+
LL | | };
550+
| |__________^ help: consider writing: `let Some(v) = w else { return Err("abc") };`
551+
552+
error: aborting due to 35 previous errors
533553

0 commit comments

Comments
 (0)