Skip to content

Commit 845b8c2

Browse files
committed
fix: manual_let_else FN when diverges on simple enum variant
1 parent 56f0182 commit 845b8c2

File tree

4 files changed

+127
-12
lines changed

4 files changed

+127
-12
lines changed

clippy_lints/src/manual_let_else.rs

Lines changed: 41 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,13 @@ 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+
is_enum_variant, is_lint_allowed, is_never_expr, is_wild, msrvs, pat_and_expr_can_be_question_mark, 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::{Arm, Expr, ExprKind, MatchSource, Pat, PatExpr, PatExprKind, PatKind, QPath, Stmt, StmtKind};
1214
use rustc_lint::{LateContext, LintContext};
1315

1416
use rustc_span::Span;
@@ -91,14 +93,15 @@ impl<'tcx> QuestionMark {
9193
let Some((idx, diverging_arm)) = diverging_arm_opt else {
9294
return;
9395
};
96+
97+
let pat_arm = &arms[1 - idx];
9498
// If the non-diverging arm is the first one, its pattern can be reused in a let/else statement.
9599
// However, if it arrives in second position, its pattern may cover some cases already covered
96100
// by the diverging one.
97-
// TODO: accept the non-diverging arm as a second position if patterns are disjointed.
98-
if idx == 0 {
101+
if idx == 0 && !is_arms_disjointed(cx, diverging_arm, pat_arm) {
99102
return;
100103
}
101-
let pat_arm = &arms[1 - idx];
104+
102105
let Some(ident_map) = expr_simple_identity_map(local.pat, pat_arm.pat, pat_arm.body) else {
103106
return;
104107
};
@@ -110,6 +113,39 @@ impl<'tcx> QuestionMark {
110113
}
111114
}
112115

116+
/// Checks if the patterns of the arms are disjointed. Currently, we only support patterns of simple
117+
/// enum variants without nested patterns or bindings.
118+
///
119+
/// TODO: Support more complex patterns.
120+
fn is_arms_disjointed(cx: &LateContext<'_>, arm1: &Arm<'_>, arm2: &Arm<'_>) -> bool {
121+
if arm1.guard.is_some() || arm2.guard.is_some() {
122+
return false;
123+
}
124+
125+
if !is_simple_enum_variant(cx, arm1.pat) || !is_simple_enum_variant(cx, arm2.pat) {
126+
return false;
127+
}
128+
129+
true
130+
}
131+
132+
fn is_simple_enum_variant(cx: &LateContext<'_>, pat: &Pat<'_>) -> bool {
133+
if !is_enum_variant(cx, pat) {
134+
return false;
135+
}
136+
137+
match pat.kind {
138+
PatKind::Struct(_, fields, _) => fields
139+
.iter()
140+
.all(|field| is_wild(field.pat) || matches!(field.pat.kind, PatKind::Binding(..))),
141+
PatKind::TupleStruct(_, pats, _) => pats
142+
.iter()
143+
.all(|pat| is_wild(pat) || matches!(pat.kind, PatKind::Binding(..))),
144+
PatKind::Expr(_) => true,
145+
_ => false,
146+
}
147+
}
148+
113149
fn emit_manual_let_else(
114150
cx: &LateContext<'_>,
115151
span: Span,

clippy_utils/src/lib.rs

Lines changed: 33 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ use rustc_data_structures::fx::FxHashMap;
9696
use rustc_data_structures::packed::Pu128;
9797
use rustc_data_structures::unhash::UnhashMap;
9898
use rustc_hir::LangItem::{OptionNone, OptionSome, ResultErr, ResultOk};
99-
use rustc_hir::def::{DefKind, Res};
99+
use rustc_hir::def::{CtorOf, DefKind, Res};
100100
use rustc_hir::def_id::{CrateNum, DefId, LOCAL_CRATE, LocalDefId, LocalModDefId};
101101
use rustc_hir::definitions::{DefPath, DefPathData};
102102
use rustc_hir::hir_id::{HirIdMap, HirIdSet};
@@ -106,7 +106,7 @@ use rustc_hir::{
106106
CoroutineDesugaring, CoroutineKind, Destination, Expr, ExprField, ExprKind, FnDecl, FnRetTy, GenericArg,
107107
GenericArgs, HirId, Impl, ImplItem, ImplItemKind, ImplItemRef, Item, ItemKind, LangItem, LetStmt, MatchSource,
108108
Mutability, Node, OwnerId, OwnerNode, Param, Pat, PatExpr, PatExprKind, PatKind, Path, PathSegment, PrimTy, QPath,
109-
Stmt, StmtKind, TraitFn, TraitItem, TraitItemKind, TraitItemRef, TraitRef, TyKind, UnOp, def,
109+
Stmt, StmtKind, TraitFn, TraitItem, TraitItemKind, TraitItemRef, TraitRef, TyKind, UnOp,
110110
};
111111
use rustc_lexer::{TokenKind, tokenize};
112112
use rustc_lint::{LateContext, Level, Lint, LintContext};
@@ -1838,14 +1838,15 @@ pub fn is_ctor_or_promotable_const_function(cx: &LateContext<'_>, expr: &Expr<'_
18381838
false
18391839
}
18401840

1841+
fn is_res_enum_variant(res: Res) -> bool {
1842+
matches!(res, Res::Def(DefKind::Variant | DefKind::Ctor(CtorOf::Variant, _), _))
1843+
}
1844+
18411845
/// Returns `true` if a pattern is refutable.
18421846
// TODO: should be implemented using rustc/mir_build/thir machinery
18431847
pub fn is_refutable(cx: &LateContext<'_>, pat: &Pat<'_>) -> bool {
18441848
fn is_enum_variant(cx: &LateContext<'_>, qpath: &QPath<'_>, id: HirId) -> bool {
1845-
matches!(
1846-
cx.qpath_res(qpath, id),
1847-
Res::Def(DefKind::Variant, ..) | Res::Def(DefKind::Ctor(def::CtorOf::Variant, _), _)
1848-
)
1849+
is_res_enum_variant(cx.qpath_res(qpath, id))
18491850
}
18501851

18511852
fn are_refutable<'a, I: IntoIterator<Item = &'a Pat<'a>>>(cx: &LateContext<'_>, i: I) -> bool {
@@ -3764,3 +3765,29 @@ pub fn desugar_await<'tcx>(expr: &'tcx Expr<'_>) -> Option<&'tcx Expr<'tcx>> {
37643765
None
37653766
}
37663767
}
3768+
3769+
/// Returns `true` if the given pattern is a variant of an enum.
3770+
pub fn is_enum_variant(cx: &LateContext<'_>, pat: &Pat<'_>) -> bool {
3771+
struct Pat<'hir>(&'hir rustc_hir::Pat<'hir>);
3772+
3773+
impl<'hir> MaybePath<'hir> for Pat<'hir> {
3774+
fn qpath_opt(&self) -> Option<&QPath<'hir>> {
3775+
match self.0.kind {
3776+
PatKind::Struct(ref qpath, _, _)
3777+
| PatKind::TupleStruct(ref qpath, _, _)
3778+
| PatKind::Expr(&PatExpr {
3779+
kind: PatExprKind::Path(ref qpath),
3780+
..
3781+
}) => Some(qpath),
3782+
_ => None,
3783+
}
3784+
}
3785+
3786+
fn hir_id(&self) -> HirId {
3787+
self.0.hir_id
3788+
}
3789+
}
3790+
3791+
let res = path_res(cx, &Pat(pat));
3792+
is_res_enum_variant(res)
3793+
}

tests/ui/manual_let_else.rs

Lines changed: 32 additions & 0 deletions
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

Lines changed: 21 additions & 1 deletion
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)