mirror of
https://github.com/rust-lang/rust-clippy
synced 2024-11-24 05:33:27 +00:00
Don't suggest let else in match if the else arm explicitly mentions non obvious paths
This commit is contained in:
parent
c5a7696231
commit
5da7a176b7
3 changed files with 106 additions and 29 deletions
|
@ -1,14 +1,16 @@
|
||||||
use clippy_utils::diagnostics::span_lint;
|
use clippy_utils::diagnostics::span_lint;
|
||||||
use clippy_utils::higher::IfLetOrMatch;
|
use clippy_utils::higher::IfLetOrMatch;
|
||||||
|
use clippy_utils::ty::is_type_diagnostic_item;
|
||||||
use clippy_utils::visitors::{for_each_expr, Descend};
|
use clippy_utils::visitors::{for_each_expr, Descend};
|
||||||
use clippy_utils::{meets_msrv, msrvs, peel_blocks};
|
use clippy_utils::{meets_msrv, msrvs, peel_blocks};
|
||||||
use if_chain::if_chain;
|
use if_chain::if_chain;
|
||||||
use rustc_data_structures::fx::FxHashSet;
|
use rustc_data_structures::fx::FxHashSet;
|
||||||
use rustc_hir::{Expr, ExprKind, MatchSource, Pat, QPath, Stmt, StmtKind};
|
use rustc_hir::{Expr, ExprKind, MatchSource, Pat, PatKind, QPath, Stmt, StmtKind};
|
||||||
use rustc_lint::{LateContext, LateLintPass, LintContext};
|
use rustc_lint::{LateContext, LateLintPass, LintContext};
|
||||||
use rustc_middle::lint::in_external_macro;
|
use rustc_middle::lint::in_external_macro;
|
||||||
use rustc_semver::RustcVersion;
|
use rustc_semver::RustcVersion;
|
||||||
use rustc_session::{declare_tool_lint, impl_lint_pass};
|
use rustc_session::{declare_tool_lint, impl_lint_pass};
|
||||||
|
use rustc_span::symbol::sym;
|
||||||
use rustc_span::Span;
|
use rustc_span::Span;
|
||||||
use std::ops::ControlFlow;
|
use std::ops::ControlFlow;
|
||||||
|
|
||||||
|
@ -108,7 +110,7 @@ impl<'tcx> LateLintPass<'tcx> for ManualLetElse {
|
||||||
}
|
}
|
||||||
if expr_is_simple_identity(arm.pat, arm.body) {
|
if expr_is_simple_identity(arm.pat, arm.body) {
|
||||||
found_identity_arm = true;
|
found_identity_arm = true;
|
||||||
} else if expr_diverges(cx, arm.body) && pat_has_no_bindings(arm.pat) {
|
} else if expr_diverges(cx, arm.body) && pat_allowed_for_else(cx, arm.pat) {
|
||||||
found_diverging_arm = true;
|
found_diverging_arm = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -194,10 +196,39 @@ fn from_different_macros(span_a: Span, span_b: Span) -> bool {
|
||||||
data_for_comparison(span_a) != data_for_comparison(span_b)
|
data_for_comparison(span_a) != data_for_comparison(span_b)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn pat_has_no_bindings(pat: &'_ Pat<'_>) -> bool {
|
fn pat_allowed_for_else(cx: &LateContext<'_>, pat: &'_ Pat<'_>) -> bool {
|
||||||
let mut has_no_bindings = true;
|
// Check whether the pattern contains any bindings, as the
|
||||||
pat.each_binding_or_first(&mut |_, _, _, _| has_no_bindings = false);
|
// binding might potentially be used in the body.
|
||||||
has_no_bindings
|
// TODO: only look for *used* bindings.
|
||||||
|
let mut has_bindings = false;
|
||||||
|
pat.each_binding_or_first(&mut |_, _, _, _| has_bindings = true);
|
||||||
|
if has_bindings {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check whether any possibly "unknown" patterns are included,
|
||||||
|
// because users might not know which values some enum has.
|
||||||
|
// Well-known enums are excepted, as we assume people know them.
|
||||||
|
// We do a deep check, to be able to disallow Err(En::Foo(_))
|
||||||
|
// for usage of the En::Foo variant, as we disallow En::Foo(_),
|
||||||
|
// but we allow Err(_).
|
||||||
|
let typeck_results = cx.typeck_results();
|
||||||
|
let mut has_disallowed = false;
|
||||||
|
pat.walk_always(|pat| {
|
||||||
|
// Only do the check if the type is "spelled out" in the pattern
|
||||||
|
if !matches!(
|
||||||
|
pat.kind,
|
||||||
|
PatKind::Struct(..) | PatKind::TupleStruct(..) | PatKind::Path(..)
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
let ty = typeck_results.pat_ty(pat);
|
||||||
|
// Option and Result are allowed, everything else isn't.
|
||||||
|
if !(is_type_diagnostic_item(cx, ty, sym::Option) || is_type_diagnostic_item(cx, ty, sym::Result)) {
|
||||||
|
has_disallowed = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
!has_disallowed
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Checks if the passed block is a simple identity referring to bindings created by the pattern
|
/// Checks if the passed block is a simple identity referring to bindings created by the pattern
|
||||||
|
|
|
@ -4,12 +4,6 @@
|
||||||
// Ensure that we don't conflict with match -> if let lints
|
// Ensure that we don't conflict with match -> if let lints
|
||||||
#![warn(clippy::single_match_else, clippy::single_match)]
|
#![warn(clippy::single_match_else, clippy::single_match)]
|
||||||
|
|
||||||
enum Variant {
|
|
||||||
Foo,
|
|
||||||
Bar(u32),
|
|
||||||
Baz(u32),
|
|
||||||
}
|
|
||||||
|
|
||||||
fn f() -> Result<u32, u32> {
|
fn f() -> Result<u32, u32> {
|
||||||
Ok(0)
|
Ok(0)
|
||||||
}
|
}
|
||||||
|
@ -18,7 +12,17 @@ fn g() -> Option<()> {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
fn h() -> Variant {
|
fn h() -> (Option<()>, Option<()>) {
|
||||||
|
(None, None)
|
||||||
|
}
|
||||||
|
|
||||||
|
enum Variant {
|
||||||
|
Foo,
|
||||||
|
Bar(u32),
|
||||||
|
Baz(u32),
|
||||||
|
}
|
||||||
|
|
||||||
|
fn build_enum() -> Variant {
|
||||||
Variant::Foo
|
Variant::Foo
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -36,9 +40,14 @@ fn fire() {
|
||||||
};
|
};
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
// More complex pattern for the identity arm
|
// More complex pattern for the identity arm and diverging arm
|
||||||
let v = match h() {
|
let v = match h() {
|
||||||
Variant::Foo => continue,
|
(Some(_), Some(_)) | (None, None) => continue,
|
||||||
|
(Some(v), None) | (None, Some(v)) => v,
|
||||||
|
};
|
||||||
|
// Custom enums are supported as long as the "else" arm is a simple _
|
||||||
|
let v = match build_enum() {
|
||||||
|
_ => continue,
|
||||||
Variant::Bar(v) | Variant::Baz(v) => v,
|
Variant::Bar(v) | Variant::Baz(v) => v,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -49,21 +58,27 @@ fn fire() {
|
||||||
Ok(v) => v,
|
Ok(v) => v,
|
||||||
Err(_) => return,
|
Err(_) => return,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Err(()) is an allowed pattern
|
||||||
|
let v = match f().map_err(|_| ()) {
|
||||||
|
Ok(v) => v,
|
||||||
|
Err(()) => return,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
fn not_fire() {
|
fn not_fire() {
|
||||||
// Multiple diverging arms
|
// Multiple diverging arms
|
||||||
let v = match h() {
|
let v = match h() {
|
||||||
Variant::Foo => panic!(),
|
_ => panic!(),
|
||||||
Variant::Bar(_v) => return,
|
(None, Some(_v)) => return,
|
||||||
Variant::Baz(v) => v,
|
(Some(v), None) => v,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Multiple identity arms
|
// Multiple identity arms
|
||||||
let v = match h() {
|
let v = match h() {
|
||||||
Variant::Foo => panic!(),
|
_ => panic!(),
|
||||||
Variant::Bar(v) => v,
|
(None, Some(v)) => v,
|
||||||
Variant::Baz(v) => v,
|
(Some(v), None) => v,
|
||||||
};
|
};
|
||||||
|
|
||||||
// No diverging arm at all, only identity arms.
|
// No diverging arm at all, only identity arms.
|
||||||
|
@ -74,8 +89,8 @@ fn not_fire() {
|
||||||
};
|
};
|
||||||
|
|
||||||
// The identity arm has a guard
|
// The identity arm has a guard
|
||||||
let v = match h() {
|
let v = match g() {
|
||||||
Variant::Bar(v) if g().is_none() => v,
|
Some(v) if g().is_none() => v,
|
||||||
_ => return,
|
_ => return,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -90,4 +105,17 @@ fn not_fire() {
|
||||||
Ok(v) => v,
|
Ok(v) => v,
|
||||||
Err(e) => panic!("error: {e}"),
|
Err(e) => panic!("error: {e}"),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Custom enum where the diverging arm
|
||||||
|
// explicitly mentions the variant
|
||||||
|
let v = match build_enum() {
|
||||||
|
Variant::Foo => return,
|
||||||
|
Variant::Bar(v) | Variant::Baz(v) => v,
|
||||||
|
};
|
||||||
|
|
||||||
|
// The custom enum is surrounded by an Err()
|
||||||
|
let v = match Err(build_enum()) {
|
||||||
|
Ok(v) | Err(Variant::Bar(v) | Variant::Baz(v)) => v,
|
||||||
|
Err(Variant::Foo) => return,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
error: this could be rewritten as `let else`
|
error: this could be rewritten as `let else`
|
||||||
--> $DIR/manual_let_else_match.rs:28:5
|
--> $DIR/manual_let_else_match.rs:32:5
|
||||||
|
|
|
|
||||||
LL | / let v = match g() {
|
LL | / let v = match g() {
|
||||||
LL | | Some(v_some) => v_some,
|
LL | | Some(v_some) => v_some,
|
||||||
|
@ -10,7 +10,7 @@ LL | | };
|
||||||
= note: `-D clippy::manual-let-else` implied by `-D warnings`
|
= note: `-D clippy::manual-let-else` implied by `-D warnings`
|
||||||
|
|
||||||
error: this could be rewritten as `let else`
|
error: this could be rewritten as `let else`
|
||||||
--> $DIR/manual_let_else_match.rs:33:5
|
--> $DIR/manual_let_else_match.rs:37:5
|
||||||
|
|
|
|
||||||
LL | / let v = match g() {
|
LL | / let v = match g() {
|
||||||
LL | | Some(v_some) => v_some,
|
LL | | Some(v_some) => v_some,
|
||||||
|
@ -19,16 +19,25 @@ LL | | };
|
||||||
| |______^
|
| |______^
|
||||||
|
|
||||||
error: this could be rewritten as `let else`
|
error: this could be rewritten as `let else`
|
||||||
--> $DIR/manual_let_else_match.rs:40:9
|
--> $DIR/manual_let_else_match.rs:44:9
|
||||||
|
|
|
|
||||||
LL | / let v = match h() {
|
LL | / let v = match h() {
|
||||||
LL | | Variant::Foo => continue,
|
LL | | (Some(_), Some(_)) | (None, None) => continue,
|
||||||
|
LL | | (Some(v), None) | (None, Some(v)) => v,
|
||||||
|
LL | | };
|
||||||
|
| |__________^
|
||||||
|
|
||||||
|
error: this could be rewritten as `let else`
|
||||||
|
--> $DIR/manual_let_else_match.rs:49:9
|
||||||
|
|
|
||||||
|
LL | / let v = match build_enum() {
|
||||||
|
LL | | _ => continue,
|
||||||
LL | | Variant::Bar(v) | Variant::Baz(v) => v,
|
LL | | Variant::Bar(v) | Variant::Baz(v) => v,
|
||||||
LL | | };
|
LL | | };
|
||||||
| |__________^
|
| |__________^
|
||||||
|
|
||||||
error: this could be rewritten as `let else`
|
error: this could be rewritten as `let else`
|
||||||
--> $DIR/manual_let_else_match.rs:48:5
|
--> $DIR/manual_let_else_match.rs:57:5
|
||||||
|
|
|
|
||||||
LL | / let v = match f() {
|
LL | / let v = match f() {
|
||||||
LL | | Ok(v) => v,
|
LL | | Ok(v) => v,
|
||||||
|
@ -36,5 +45,14 @@ LL | | Err(_) => return,
|
||||||
LL | | };
|
LL | | };
|
||||||
| |______^
|
| |______^
|
||||||
|
|
||||||
error: aborting due to 4 previous errors
|
error: this could be rewritten as `let else`
|
||||||
|
--> $DIR/manual_let_else_match.rs:63:5
|
||||||
|
|
|
||||||
|
LL | / let v = match f().map_err(|_| ()) {
|
||||||
|
LL | | Ok(v) => v,
|
||||||
|
LL | | Err(()) => return,
|
||||||
|
LL | | };
|
||||||
|
| |______^
|
||||||
|
|
||||||
|
error: aborting due to 6 previous errors
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue