Auto merge of #8322 - jubnzv:8282-single-match, r=llogiq
single_match: Don't lint non-exhaustive matches; support tuples `single_match` lint: * Don't lint exhaustive enum patterns without a wild. Rationale: The definition of the enum could be changed, so the user can get non-exhaustive match after applying the suggested lint (see https://github.com/rust-lang/rust-clippy/issues/8282#issuecomment-1013566068 for context). * Lint `match` constructions with tuples (as suggested at https://github.com/rust-lang/rust-clippy/issues/8282#issuecomment-1015621148) Closes #8282 --- changelog: [`single_match`]: Don't lint exhaustive enum patterns without a wild. changelog: [`single_match`]: Lint `match` constructions with tuples
This commit is contained in:
commit
0ed8ca45f4
@ -31,7 +31,7 @@ use rustc_semver::RustcVersion;
|
||||
use rustc_session::{declare_tool_lint, impl_lint_pass};
|
||||
use rustc_span::source_map::{Span, Spanned};
|
||||
use rustc_span::{sym, symbol::kw};
|
||||
use std::cmp::Ordering;
|
||||
use std::cmp::{max, Ordering};
|
||||
use std::collections::hash_map::Entry;
|
||||
|
||||
declare_clippy_lint! {
|
||||
@ -830,12 +830,12 @@ fn report_single_match_single_pattern(
|
||||
);
|
||||
}
|
||||
|
||||
fn check_single_match_opt_like(
|
||||
cx: &LateContext<'_>,
|
||||
fn check_single_match_opt_like<'a>(
|
||||
cx: &LateContext<'a>,
|
||||
ex: &Expr<'_>,
|
||||
arms: &[Arm<'_>],
|
||||
expr: &Expr<'_>,
|
||||
ty: Ty<'_>,
|
||||
ty: Ty<'a>,
|
||||
els: Option<&Expr<'_>>,
|
||||
) {
|
||||
// list of candidate `Enum`s we know will never get any more members
|
||||
@ -849,25 +849,120 @@ fn check_single_match_opt_like(
|
||||
(&paths::RESULT, "Ok"),
|
||||
];
|
||||
|
||||
let path = match arms[1].pat.kind {
|
||||
PatKind::TupleStruct(ref path, inner, _) => {
|
||||
// Contains any non wildcard patterns (e.g., `Err(err)`)?
|
||||
if !inner.iter().all(is_wild) {
|
||||
return;
|
||||
}
|
||||
rustc_hir_pretty::to_string(rustc_hir_pretty::NO_ANN, |s| s.print_qpath(path, false))
|
||||
},
|
||||
PatKind::Binding(BindingAnnotation::Unannotated, .., ident, None) => ident.to_string(),
|
||||
PatKind::Path(ref path) => {
|
||||
rustc_hir_pretty::to_string(rustc_hir_pretty::NO_ANN, |s| s.print_qpath(path, false))
|
||||
},
|
||||
_ => return,
|
||||
};
|
||||
// We want to suggest to exclude an arm that contains only wildcards or forms the exhaustive
|
||||
// match with the second branch, without enum variants in matches.
|
||||
if !contains_only_wilds(arms[1].pat) && !form_exhaustive_matches(arms[0].pat, arms[1].pat) {
|
||||
return;
|
||||
}
|
||||
|
||||
for &(ty_path, pat_path) in candidates {
|
||||
if path == *pat_path && match_type(cx, ty, ty_path) {
|
||||
report_single_match_single_pattern(cx, ex, arms, expr, els);
|
||||
let mut paths_and_types = Vec::new();
|
||||
if !collect_pat_paths(&mut paths_and_types, cx, arms[1].pat, ty) {
|
||||
return;
|
||||
}
|
||||
|
||||
let in_candidate_enum = |path_info: &(String, &TyS<'_>)| -> bool {
|
||||
let (path, ty) = path_info;
|
||||
for &(ty_path, pat_path) in candidates {
|
||||
if path == pat_path && match_type(cx, ty, ty_path) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
false
|
||||
};
|
||||
if paths_and_types.iter().all(in_candidate_enum) {
|
||||
report_single_match_single_pattern(cx, ex, arms, expr, els);
|
||||
}
|
||||
}
|
||||
|
||||
/// Collects paths and their types from the given patterns. Returns true if the given pattern could
|
||||
/// be simplified, false otherwise.
|
||||
fn collect_pat_paths<'a>(acc: &mut Vec<(String, Ty<'a>)>, cx: &LateContext<'a>, pat: &Pat<'_>, ty: Ty<'a>) -> bool {
|
||||
match pat.kind {
|
||||
PatKind::Wild => true,
|
||||
PatKind::Tuple(inner, _) => inner.iter().all(|p| {
|
||||
let p_ty = cx.typeck_results().pat_ty(p);
|
||||
collect_pat_paths(acc, cx, p, p_ty)
|
||||
}),
|
||||
PatKind::TupleStruct(ref path, ..) => {
|
||||
let path = rustc_hir_pretty::to_string(rustc_hir_pretty::NO_ANN, |s| {
|
||||
s.print_qpath(path, false);
|
||||
});
|
||||
acc.push((path, ty));
|
||||
true
|
||||
},
|
||||
PatKind::Binding(BindingAnnotation::Unannotated, .., ident, None) => {
|
||||
acc.push((ident.to_string(), ty));
|
||||
true
|
||||
},
|
||||
PatKind::Path(ref path) => {
|
||||
let path = rustc_hir_pretty::to_string(rustc_hir_pretty::NO_ANN, |s| {
|
||||
s.print_qpath(path, false);
|
||||
});
|
||||
acc.push((path, ty));
|
||||
true
|
||||
},
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns true if the given arm of pattern matching contains wildcard patterns.
|
||||
fn contains_only_wilds(pat: &Pat<'_>) -> bool {
|
||||
match pat.kind {
|
||||
PatKind::Wild => true,
|
||||
PatKind::Tuple(inner, _) | PatKind::TupleStruct(_, inner, ..) => inner.iter().all(contains_only_wilds),
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns true if the given patterns forms only exhaustive matches that don't contain enum
|
||||
/// patterns without a wildcard.
|
||||
fn form_exhaustive_matches(left: &Pat<'_>, right: &Pat<'_>) -> bool {
|
||||
match (&left.kind, &right.kind) {
|
||||
(PatKind::Wild, _) | (_, PatKind::Wild) => true,
|
||||
(PatKind::Tuple(left_in, left_pos), PatKind::Tuple(right_in, right_pos)) => {
|
||||
// We don't actually know the position and the presence of the `..` (dotdot) operator
|
||||
// in the arms, so we need to evaluate the correct offsets here in order to iterate in
|
||||
// both arms at the same time.
|
||||
let len = max(
|
||||
left_in.len() + {
|
||||
if left_pos.is_some() { 1 } else { 0 }
|
||||
},
|
||||
right_in.len() + {
|
||||
if right_pos.is_some() { 1 } else { 0 }
|
||||
},
|
||||
);
|
||||
let mut left_pos = left_pos.unwrap_or(usize::MAX);
|
||||
let mut right_pos = right_pos.unwrap_or(usize::MAX);
|
||||
let mut left_dot_space = 0;
|
||||
let mut right_dot_space = 0;
|
||||
for i in 0..len {
|
||||
let mut found_dotdot = false;
|
||||
if i == left_pos {
|
||||
left_dot_space += 1;
|
||||
if left_dot_space < len - left_in.len() {
|
||||
left_pos += 1;
|
||||
}
|
||||
found_dotdot = true;
|
||||
}
|
||||
if i == right_pos {
|
||||
right_dot_space += 1;
|
||||
if right_dot_space < len - right_in.len() {
|
||||
right_pos += 1;
|
||||
}
|
||||
found_dotdot = true;
|
||||
}
|
||||
if found_dotdot {
|
||||
continue;
|
||||
}
|
||||
if !contains_only_wilds(&left_in[i - left_dot_space])
|
||||
&& !contains_only_wilds(&right_in[i - right_dot_space])
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
true
|
||||
},
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -145,6 +145,84 @@ fn if_suggestion() {
|
||||
};
|
||||
}
|
||||
|
||||
// See: issue #8282
|
||||
fn ranges() {
|
||||
enum E {
|
||||
V,
|
||||
}
|
||||
let x = (Some(E::V), Some(42));
|
||||
|
||||
// Don't lint, because the `E` enum can be extended with additional fields later. Thus, the
|
||||
// proposed replacement to `if let Some(E::V)` may hide non-exhaustive warnings that appeared
|
||||
// because of `match` construction.
|
||||
match x {
|
||||
(Some(E::V), _) => {},
|
||||
(None, _) => {},
|
||||
}
|
||||
|
||||
// lint
|
||||
match x {
|
||||
(Some(_), _) => {},
|
||||
(None, _) => {},
|
||||
}
|
||||
|
||||
// lint
|
||||
match x {
|
||||
(Some(E::V), _) => todo!(),
|
||||
(_, _) => {},
|
||||
}
|
||||
|
||||
// lint
|
||||
match (Some(42), Some(E::V), Some(42)) {
|
||||
(.., Some(E::V), _) => {},
|
||||
(..) => {},
|
||||
}
|
||||
|
||||
// Don't lint, see above.
|
||||
match (Some(E::V), Some(E::V), Some(E::V)) {
|
||||
(.., Some(E::V), _) => {},
|
||||
(.., None, _) => {},
|
||||
}
|
||||
|
||||
// Don't lint, see above.
|
||||
match (Some(E::V), Some(E::V), Some(E::V)) {
|
||||
(Some(E::V), ..) => {},
|
||||
(None, ..) => {},
|
||||
}
|
||||
|
||||
// Don't lint, see above.
|
||||
match (Some(E::V), Some(E::V), Some(E::V)) {
|
||||
(_, Some(E::V), ..) => {},
|
||||
(_, None, ..) => {},
|
||||
}
|
||||
}
|
||||
|
||||
fn skip_type_aliases() {
|
||||
enum OptionEx {
|
||||
Some(i32),
|
||||
None,
|
||||
}
|
||||
enum ResultEx {
|
||||
Err(i32),
|
||||
Ok(i32),
|
||||
}
|
||||
|
||||
use OptionEx::{None, Some};
|
||||
use ResultEx::{Err, Ok};
|
||||
|
||||
// don't lint
|
||||
match Err(42) {
|
||||
Ok(_) => dummy(),
|
||||
Err(_) => (),
|
||||
};
|
||||
|
||||
// don't lint
|
||||
match Some(1i32) {
|
||||
Some(_) => dummy(),
|
||||
None => (),
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! single_match {
|
||||
($num:literal) => {
|
||||
match $num {
|
||||
|
@ -38,15 +38,6 @@ LL | | _ => {},
|
||||
LL | | };
|
||||
| |_____^ help: try this: `if let (2..=3, 7..=9) = z { dummy() }`
|
||||
|
||||
error: you seem to be trying to use `match` for destructuring a single pattern. Consider using `if let`
|
||||
--> $DIR/single_match.rs:54:5
|
||||
|
|
||||
LL | / match x {
|
||||
LL | | Some(y) => dummy(),
|
||||
LL | | None => (),
|
||||
LL | | };
|
||||
| |_____^ help: try this: `if let Some(y) = x { dummy() }`
|
||||
|
||||
error: you seem to be trying to use `match` for destructuring a single pattern. Consider using `if let`
|
||||
--> $DIR/single_match.rs:59:5
|
||||
|
|
||||
@ -128,5 +119,32 @@ LL | | _ => (),
|
||||
LL | | };
|
||||
| |_____^ help: try this: `if let None = x { println!() }`
|
||||
|
||||
error: aborting due to 13 previous errors
|
||||
error: you seem to be trying to use `match` for destructuring a single pattern. Consider using `if let`
|
||||
--> $DIR/single_match.rs:164:5
|
||||
|
|
||||
LL | / match x {
|
||||
LL | | (Some(_), _) => {},
|
||||
LL | | (None, _) => {},
|
||||
LL | | }
|
||||
| |_____^ help: try this: `if let (Some(_), _) = x {}`
|
||||
|
||||
error: you seem to be trying to use `match` for destructuring a single pattern. Consider using `if let`
|
||||
--> $DIR/single_match.rs:170:5
|
||||
|
|
||||
LL | / match x {
|
||||
LL | | (Some(E::V), _) => todo!(),
|
||||
LL | | (_, _) => {},
|
||||
LL | | }
|
||||
| |_____^ help: try this: `if let (Some(E::V), _) = x { todo!() }`
|
||||
|
||||
error: you seem to be trying to use `match` for destructuring a single pattern. Consider using `if let`
|
||||
--> $DIR/single_match.rs:176:5
|
||||
|
|
||||
LL | / match (Some(42), Some(E::V), Some(42)) {
|
||||
LL | | (.., Some(E::V), _) => {},
|
||||
LL | | (..) => {},
|
||||
LL | | }
|
||||
| |_____^ help: try this: `if let (.., Some(E::V), _) = (Some(42), Some(E::V), Some(42)) {}`
|
||||
|
||||
error: aborting due to 15 previous errors
|
||||
|
||||
|
@ -19,45 +19,5 @@ LL + None
|
||||
LL + }
|
||||
|
|
||||
|
||||
error: you seem to be trying to use `match` for destructuring a single pattern. Consider using `if let`
|
||||
--> $DIR/single_match_else.rs:70:5
|
||||
|
|
||||
LL | / match Some(1) {
|
||||
LL | | Some(a) => println!("${:?}", a),
|
||||
LL | | None => {
|
||||
LL | | println!("else block");
|
||||
LL | | return
|
||||
LL | | },
|
||||
LL | | }
|
||||
| |_____^
|
||||
|
|
||||
help: try this
|
||||
|
|
||||
LL ~ if let Some(a) = Some(1) { println!("${:?}", a) } else {
|
||||
LL + println!("else block");
|
||||
LL + return
|
||||
LL + }
|
||||
|
|
||||
|
||||
error: you seem to be trying to use `match` for destructuring a single pattern. Consider using `if let`
|
||||
--> $DIR/single_match_else.rs:79:5
|
||||
|
|
||||
LL | / match Some(1) {
|
||||
LL | | Some(a) => println!("${:?}", a),
|
||||
LL | | None => {
|
||||
LL | | println!("else block");
|
||||
LL | | return;
|
||||
LL | | },
|
||||
LL | | }
|
||||
| |_____^
|
||||
|
|
||||
help: try this
|
||||
|
|
||||
LL ~ if let Some(a) = Some(1) { println!("${:?}", a) } else {
|
||||
LL + println!("else block");
|
||||
LL + return;
|
||||
LL + }
|
||||
|
|
||||
|
||||
error: aborting due to 3 previous errors
|
||||
error: aborting due to previous error
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user