Skip to content

Commit 4d68619

Browse files
committed
Auto merge of #6863 - Jarcho:wild_enum_match, r=llogiq
`match_wildcard` improvements fixes: #6604 fixes: #5733 fixes: #6862 #5733 is only fixed in the normal case, if different paths are used for the variants then the same problem will occur. It's cause by `def_path_str` returning an utterly useless result. I haven't dug into why yet. For #6604 there should be some discussion before accepting this. It's easy enough to change the message rather than disable the lint for `Option` and `Result`. changelog: Attempt to find a common path prefix for `match_wildcard_for_single_variants` and `wildcard_enum_match_arm` changelog: Don't lint op `Option` and `Result` for `match_wildcard_for_single_variants` and `wildcard_enum_match_arm` changelog: Consider `or` patterns and `Self` prefix for `match_wildcard_for_single_variants` and `wildcard_enum_match_arm`
2 parents ba13311 + d5a7941 commit 4d68619

14 files changed

+322
-117
lines changed

clippy_lints/src/attrs.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -430,7 +430,7 @@ fn is_relevant_block(cx: &LateContext<'_>, typeck_results: &ty::TypeckResults<'_
430430
|stmt| match &stmt.kind {
431431
StmtKind::Local(_) => true,
432432
StmtKind::Expr(expr) | StmtKind::Semi(expr) => is_relevant_expr(cx, typeck_results, expr),
433-
_ => false,
433+
StmtKind::Item(_) => false,
434434
},
435435
)
436436
}
@@ -613,7 +613,7 @@ fn check_mismatched_target_os(cx: &EarlyContext<'_>, attr: &Attribute) {
613613
}
614614
}
615615
},
616-
_ => {},
616+
MetaItemKind::NameValue(..) => {},
617617
}
618618
}
619619
}

clippy_lints/src/doc.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -583,7 +583,7 @@ fn check_code(cx: &LateContext<'_>, text: &str, edition: Edition, span: Span) {
583583
let returns_nothing = match &sig.decl.output {
584584
FnRetTy::Default(..) => true,
585585
FnRetTy::Ty(ty) if ty.kind.is_unit() => true,
586-
_ => false,
586+
FnRetTy::Ty(_) => false,
587587
};
588588

589589
if returns_nothing && !is_async && !block.stmts.is_empty() {

clippy_lints/src/eval_order_dependence.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -273,7 +273,7 @@ fn check_stmt<'a, 'tcx>(vis: &mut ReadVisitor<'a, 'tcx>, stmt: &'tcx Stmt<'_>) -
273273
.init
274274
.as_ref()
275275
.map_or(StopEarly::KeepGoing, |expr| check_expr(vis, expr)),
276-
_ => StopEarly::KeepGoing,
276+
StmtKind::Item(..) => StopEarly::KeepGoing,
277277
}
278278
}
279279

clippy_lints/src/loops/never_loop.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ fn stmt_to_expr<'tcx>(stmt: &Stmt<'tcx>) -> Option<&'tcx Expr<'tcx>> {
7878
match stmt.kind {
7979
StmtKind::Semi(ref e, ..) | StmtKind::Expr(ref e, ..) => Some(e),
8080
StmtKind::Local(ref local) => local.init.as_deref(),
81-
_ => None,
81+
StmtKind::Item(..) => None,
8282
}
8383
}
8484

clippy_lints/src/matches.rs

Lines changed: 156 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -8,21 +8,21 @@ use clippy_utils::ty::{implements_trait, is_type_diagnostic_item, match_type, pe
88
use clippy_utils::visitors::LocalUsedVisitor;
99
use clippy_utils::{
1010
get_parent_expr, in_macro, is_allowed, is_expn_of, is_refutable, is_wild, match_qpath, meets_msrv, path_to_local,
11-
path_to_local_id, peel_hir_pat_refs, peel_n_hir_expr_refs, remove_blocks, strip_pat_refs,
11+
path_to_local_id, peel_hir_pat_refs, peel_n_hir_expr_refs, recurse_or_patterns, remove_blocks, strip_pat_refs,
1212
};
1313
use clippy_utils::{paths, search_same, SpanlessEq, SpanlessHash};
1414
use if_chain::if_chain;
1515
use rustc_ast::ast::LitKind;
1616
use rustc_data_structures::fx::{FxHashMap, FxHashSet};
1717
use rustc_errors::Applicability;
18-
use rustc_hir::def::CtorKind;
18+
use rustc_hir::def::{CtorKind, DefKind, Res};
1919
use rustc_hir::{
20-
Arm, BindingAnnotation, Block, BorrowKind, Expr, ExprKind, Guard, HirId, Local, MatchSource, Mutability, Node, Pat,
21-
PatKind, QPath, RangeEnd,
20+
self as hir, Arm, BindingAnnotation, Block, BorrowKind, Expr, ExprKind, Guard, HirId, Local, MatchSource,
21+
Mutability, Node, Pat, PatKind, PathSegment, QPath, RangeEnd, TyKind,
2222
};
2323
use rustc_lint::{LateContext, LateLintPass, LintContext};
2424
use rustc_middle::lint::in_external_macro;
25-
use rustc_middle::ty::{self, Ty, TyS};
25+
use rustc_middle::ty::{self, Ty, TyS, VariantDef};
2626
use rustc_semver::RustcVersion;
2727
use rustc_session::{declare_tool_lint, impl_lint_pass};
2828
use rustc_span::source_map::{Span, Spanned};
@@ -956,114 +956,181 @@ fn check_wild_err_arm<'tcx>(cx: &LateContext<'tcx>, ex: &Expr<'tcx>, arms: &[Arm
956956
}
957957
}
958958

959-
fn check_wild_enum_match(cx: &LateContext<'_>, ex: &Expr<'_>, arms: &[Arm<'_>]) {
960-
let ty = cx.typeck_results().expr_ty(ex);
961-
if !ty.is_enum() {
962-
// If there isn't a nice closed set of possible values that can be conveniently enumerated,
963-
// don't complain about not enumerating the mall.
964-
return;
959+
enum CommonPrefixSearcher<'a> {
960+
None,
961+
Path(&'a [PathSegment<'a>]),
962+
Mixed,
963+
}
964+
impl CommonPrefixSearcher<'a> {
965+
fn with_path(&mut self, path: &'a [PathSegment<'a>]) {
966+
match path {
967+
[path @ .., _] => self.with_prefix(path),
968+
[] => (),
969+
}
965970
}
966971

972+
fn with_prefix(&mut self, path: &'a [PathSegment<'a>]) {
973+
match self {
974+
Self::None => *self = Self::Path(path),
975+
Self::Path(self_path)
976+
if path
977+
.iter()
978+
.map(|p| p.ident.name)
979+
.eq(self_path.iter().map(|p| p.ident.name)) => {},
980+
Self::Path(_) => *self = Self::Mixed,
981+
Self::Mixed => (),
982+
}
983+
}
984+
}
985+
986+
#[allow(clippy::too_many_lines)]
987+
fn check_wild_enum_match(cx: &LateContext<'_>, ex: &Expr<'_>, arms: &[Arm<'_>]) {
988+
let ty = cx.typeck_results().expr_ty(ex).peel_refs();
989+
let adt_def = match ty.kind() {
990+
ty::Adt(adt_def, _)
991+
if adt_def.is_enum()
992+
&& !(is_type_diagnostic_item(cx, ty, sym::option_type)
993+
|| is_type_diagnostic_item(cx, ty, sym::result_type)) =>
994+
{
995+
adt_def
996+
},
997+
_ => return,
998+
};
999+
9671000
// First pass - check for violation, but don't do much book-keeping because this is hopefully
9681001
// the uncommon case, and the book-keeping is slightly expensive.
9691002
let mut wildcard_span = None;
9701003
let mut wildcard_ident = None;
1004+
let mut has_non_wild = false;
9711005
for arm in arms {
972-
if let PatKind::Wild = arm.pat.kind {
973-
wildcard_span = Some(arm.pat.span);
974-
} else if let PatKind::Binding(_, _, ident, None) = arm.pat.kind {
975-
wildcard_span = Some(arm.pat.span);
976-
wildcard_ident = Some(ident);
1006+
match peel_hir_pat_refs(arm.pat).0.kind {
1007+
PatKind::Wild => wildcard_span = Some(arm.pat.span),
1008+
PatKind::Binding(_, _, ident, None) => {
1009+
wildcard_span = Some(arm.pat.span);
1010+
wildcard_ident = Some(ident);
1011+
},
1012+
_ => has_non_wild = true,
9771013
}
9781014
}
1015+
let wildcard_span = match wildcard_span {
1016+
Some(x) if has_non_wild => x,
1017+
_ => return,
1018+
};
9791019

980-
if let Some(wildcard_span) = wildcard_span {
981-
// Accumulate the variants which should be put in place of the wildcard because they're not
982-
// already covered.
1020+
// Accumulate the variants which should be put in place of the wildcard because they're not
1021+
// already covered.
1022+
let mut missing_variants: Vec<_> = adt_def.variants.iter().collect();
9831023

984-
let mut missing_variants = vec![];
985-
if let ty::Adt(def, _) = ty.kind() {
986-
for variant in &def.variants {
987-
missing_variants.push(variant);
1024+
let mut path_prefix = CommonPrefixSearcher::None;
1025+
for arm in arms {
1026+
// Guards mean that this case probably isn't exhaustively covered. Technically
1027+
// this is incorrect, as we should really check whether each variant is exhaustively
1028+
// covered by the set of guards that cover it, but that's really hard to do.
1029+
recurse_or_patterns(arm.pat, |pat| {
1030+
let path = match &peel_hir_pat_refs(pat).0.kind {
1031+
PatKind::Path(path) => {
1032+
#[allow(clippy::match_same_arms)]
1033+
let id = match cx.qpath_res(path, pat.hir_id) {
1034+
Res::Def(DefKind::Const | DefKind::ConstParam | DefKind::AnonConst, _) => return,
1035+
Res::Def(_, id) => id,
1036+
_ => return,
1037+
};
1038+
if arm.guard.is_none() {
1039+
missing_variants.retain(|e| e.ctor_def_id != Some(id));
1040+
}
1041+
path
1042+
},
1043+
PatKind::TupleStruct(path, patterns, ..) => {
1044+
if arm.guard.is_none() && patterns.iter().all(|p| !is_refutable(cx, p)) {
1045+
let id = cx.qpath_res(path, pat.hir_id).def_id();
1046+
missing_variants.retain(|e| e.ctor_def_id != Some(id));
1047+
}
1048+
path
1049+
},
1050+
PatKind::Struct(path, patterns, ..) => {
1051+
if arm.guard.is_none() && patterns.iter().all(|p| !is_refutable(cx, p.pat)) {
1052+
let id = cx.qpath_res(path, pat.hir_id).def_id();
1053+
missing_variants.retain(|e| e.def_id != id);
1054+
}
1055+
path
1056+
},
1057+
_ => return,
1058+
};
1059+
match path {
1060+
QPath::Resolved(_, path) => path_prefix.with_path(path.segments),
1061+
QPath::TypeRelative(
1062+
hir::Ty {
1063+
kind: TyKind::Path(QPath::Resolved(_, path)),
1064+
..
1065+
},
1066+
_,
1067+
) => path_prefix.with_prefix(path.segments),
1068+
_ => (),
9881069
}
989-
}
1070+
});
1071+
}
9901072

991-
for arm in arms {
992-
if arm.guard.is_some() {
993-
// Guards mean that this case probably isn't exhaustively covered. Technically
994-
// this is incorrect, as we should really check whether each variant is exhaustively
995-
// covered by the set of guards that cover it, but that's really hard to do.
996-
continue;
997-
}
998-
if let PatKind::Path(ref path) = arm.pat.kind {
999-
if let QPath::Resolved(_, p) = path {
1000-
missing_variants.retain(|e| e.ctor_def_id != Some(p.res.def_id()));
1001-
}
1002-
} else if let PatKind::TupleStruct(QPath::Resolved(_, p), ref patterns, ..) = arm.pat.kind {
1003-
// Some simple checks for exhaustive patterns.
1004-
// There is a room for improvements to detect more cases,
1005-
// but it can be more expensive to do so.
1006-
let is_pattern_exhaustive =
1007-
|pat: &&Pat<'_>| matches!(pat.kind, PatKind::Wild | PatKind::Binding(.., None));
1008-
if patterns.iter().all(is_pattern_exhaustive) {
1009-
missing_variants.retain(|e| e.ctor_def_id != Some(p.res.def_id()));
1073+
let format_suggestion = |variant: &VariantDef| {
1074+
format!(
1075+
"{}{}{}{}",
1076+
if let Some(ident) = wildcard_ident {
1077+
format!("{} @ ", ident.name)
1078+
} else {
1079+
String::new()
1080+
},
1081+
if let CommonPrefixSearcher::Path(path_prefix) = path_prefix {
1082+
let mut s = String::new();
1083+
for seg in path_prefix {
1084+
s.push_str(&seg.ident.as_str());
1085+
s.push_str("::");
10101086
}
1087+
s
1088+
} else {
1089+
let mut s = cx.tcx.def_path_str(adt_def.did);
1090+
s.push_str("::");
1091+
s
1092+
},
1093+
variant.ident.name,
1094+
match variant.ctor_kind {
1095+
CtorKind::Fn if variant.fields.len() == 1 => "(_)",
1096+
CtorKind::Fn => "(..)",
1097+
CtorKind::Const => "",
1098+
CtorKind::Fictive => "{ .. }",
10111099
}
1012-
}
1013-
1014-
let mut suggestion: Vec<String> = missing_variants
1015-
.iter()
1016-
.map(|v| {
1017-
let suffix = match v.ctor_kind {
1018-
CtorKind::Fn => "(..)",
1019-
CtorKind::Const | CtorKind::Fictive => "",
1020-
};
1021-
let ident_str = if let Some(ident) = wildcard_ident {
1022-
format!("{} @ ", ident.name)
1023-
} else {
1024-
String::new()
1025-
};
1026-
// This path assumes that the enum type is imported into scope.
1027-
format!("{}{}{}", ident_str, cx.tcx.def_path_str(v.def_id), suffix)
1028-
})
1029-
.collect();
1030-
1031-
if suggestion.is_empty() {
1032-
return;
1033-
}
1034-
1035-
let mut message = "wildcard match will miss any future added variants";
1100+
)
1101+
};
10361102

1037-
if let ty::Adt(def, _) = ty.kind() {
1038-
if def.is_variant_list_non_exhaustive() {
1039-
message = "match on non-exhaustive enum doesn't explicitly match all known variants";
1040-
suggestion.push(String::from("_"));
1041-
}
1042-
}
1103+
match missing_variants.as_slice() {
1104+
[] => (),
1105+
[x] if !adt_def.is_variant_list_non_exhaustive() => span_lint_and_sugg(
1106+
cx,
1107+
MATCH_WILDCARD_FOR_SINGLE_VARIANTS,
1108+
wildcard_span,
1109+
"wildcard matches only a single variant and will also match any future added variants",
1110+
"try this",
1111+
format_suggestion(x),
1112+
Applicability::MaybeIncorrect,
1113+
),
1114+
variants => {
1115+
let mut suggestions: Vec<_> = variants.iter().cloned().map(format_suggestion).collect();
1116+
let message = if adt_def.is_variant_list_non_exhaustive() {
1117+
suggestions.push("_".into());
1118+
"wildcard matches known variants and will also match future added variants"
1119+
} else {
1120+
"wildcard match will also match any future added variants"
1121+
};
10431122

1044-
if suggestion.len() == 1 {
1045-
// No need to check for non-exhaustive enum as in that case len would be greater than 1
10461123
span_lint_and_sugg(
10471124
cx,
1048-
MATCH_WILDCARD_FOR_SINGLE_VARIANTS,
1125+
WILDCARD_ENUM_MATCH_ARM,
10491126
wildcard_span,
10501127
message,
10511128
"try this",
1052-
suggestion[0].clone(),
1129+
suggestions.join(" | "),
10531130
Applicability::MaybeIncorrect,
10541131
)
1055-
};
1056-
1057-
span_lint_and_sugg(
1058-
cx,
1059-
WILDCARD_ENUM_MATCH_ARM,
1060-
wildcard_span,
1061-
message,
1062-
"try this",
1063-
suggestion.join(" | "),
1064-
Applicability::MaybeIncorrect,
1065-
)
1066-
}
1132+
},
1133+
};
10671134
}
10681135

10691136
// If the block contains only a `panic!` macro (as expression or statement)

clippy_lints/src/redundant_clone.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -556,7 +556,7 @@ impl<'a, 'tcx> mir::visit::Visitor<'tcx> for PossibleBorrowerVisitor<'a, 'tcx> {
556556
mir::Operand::Copy(p) | mir::Operand::Move(p) => {
557557
self.possible_borrower.add(p.local, *dest);
558558
},
559-
_ => (),
559+
mir::Operand::Constant(..) => (),
560560
}
561561
}
562562
}
@@ -578,7 +578,7 @@ fn rvalue_locals(rvalue: &mir::Rvalue<'_>, mut visit: impl FnMut(mir::Local)) {
578578

579579
let mut visit_op = |op: &mir::Operand<'_>| match op {
580580
mir::Operand::Copy(p) | mir::Operand::Move(p) => visit(p.local),
581-
_ => (),
581+
mir::Operand::Constant(..) => (),
582582
};
583583

584584
match rvalue {

clippy_lints/src/types/mod.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -276,7 +276,7 @@ impl<'tcx> LateLintPass<'tcx> for Types {
276276
match item.kind {
277277
TraitItemKind::Const(ref ty, _) | TraitItemKind::Type(_, Some(ref ty)) => self.check_ty(cx, ty, false),
278278
TraitItemKind::Fn(ref sig, _) => self.check_fn_decl(cx, &sig.decl),
279-
_ => (),
279+
TraitItemKind::Type(..) => (),
280280
}
281281
}
282282

@@ -452,15 +452,15 @@ impl<'tcx> LateLintPass<'tcx> for TypeComplexity {
452452
TraitItemKind::Const(ref ty, _) | TraitItemKind::Type(_, Some(ref ty)) => self.check_type(cx, ty),
453453
TraitItemKind::Fn(FnSig { ref decl, .. }, TraitFn::Required(_)) => self.check_fndecl(cx, decl),
454454
// methods with default impl are covered by check_fn
455-
_ => (),
455+
TraitItemKind::Type(..) | TraitItemKind::Fn(_, TraitFn::Provided(_)) => (),
456456
}
457457
}
458458

459459
fn check_impl_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx ImplItem<'_>) {
460460
match item.kind {
461461
ImplItemKind::Const(ref ty, _) | ImplItemKind::TyAlias(ref ty) => self.check_type(cx, ty),
462462
// methods are covered by check_fn
463-
_ => (),
463+
ImplItemKind::Fn(..) => (),
464464
}
465465
}
466466

clippy_lints/src/unnecessary_wraps.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ impl<'tcx> LateLintPass<'tcx> for UnnecessaryWraps {
7171
}
7272
},
7373
FnKind::Closure => return,
74-
_ => (),
74+
FnKind::Method(..) => (),
7575
}
7676

7777
// Abort if the method is implementing a trait or of it a trait method.

0 commit comments

Comments
 (0)