1 use clippy_utils::diagnostics::span_lint_and_sugg;
2 use clippy_utils::ty::is_type_diagnostic_item;
3 use clippy_utils::{is_refutable, peel_hir_pat_refs, recurse_or_patterns};
4 use rustc_errors::Applicability;
5 use rustc_hir::def::{CtorKind, DefKind, Res};
6 use rustc_hir::{Arm, Expr, PatKind, PathSegment, QPath, Ty, TyKind};
7 use rustc_lint::LateContext;
8 use rustc_middle::ty::{self, VariantDef};
11 use super::{MATCH_WILDCARD_FOR_SINGLE_VARIANTS, WILDCARD_ENUM_MATCH_ARM};
13 #[expect(clippy::too_many_lines)]
14 pub(crate) fn check(cx: &LateContext<'_>, ex: &Expr<'_>, arms: &[Arm<'_>]) {
15 let ty = cx.typeck_results().expr_ty(ex).peel_refs();
16 let adt_def = match ty.kind() {
19 && !(is_type_diagnostic_item(cx, ty, sym::Option) || is_type_diagnostic_item(cx, ty, sym::Result)) =>
26 // First pass - check for violation, but don't do much book-keeping because this is hopefully
27 // the uncommon case, and the book-keeping is slightly expensive.
28 let mut wildcard_span = None;
29 let mut wildcard_ident = None;
30 let mut has_non_wild = false;
32 match peel_hir_pat_refs(arm.pat).0.kind {
33 PatKind::Wild => wildcard_span = Some(arm.pat.span),
34 PatKind::Binding(_, _, ident, None) => {
35 wildcard_span = Some(arm.pat.span);
36 wildcard_ident = Some(ident);
38 _ => has_non_wild = true,
41 let wildcard_span = match wildcard_span {
42 Some(x) if has_non_wild => x,
46 // Accumulate the variants which should be put in place of the wildcard because they're not
48 let has_hidden = adt_def.variants().iter().any(|x| is_hidden(cx, x));
49 let mut missing_variants: Vec<_> = adt_def.variants().iter().filter(|x| !is_hidden(cx, x)).collect();
51 let mut path_prefix = CommonPrefixSearcher::None;
53 // Guards mean that this case probably isn't exhaustively covered. Technically
54 // this is incorrect, as we should really check whether each variant is exhaustively
55 // covered by the set of guards that cover it, but that's really hard to do.
56 recurse_or_patterns(arm.pat, |pat| {
57 let path = match &peel_hir_pat_refs(pat).0.kind {
58 PatKind::Path(path) => {
59 let id = match cx.qpath_res(path, pat.hir_id) {
61 DefKind::Const | DefKind::ConstParam | DefKind::AnonConst | DefKind::InlineConst,
64 Res::Def(_, id) => id,
67 if arm.guard.is_none() {
68 missing_variants.retain(|e| e.ctor_def_id != Some(id));
72 PatKind::TupleStruct(path, patterns, ..) => {
73 if let Some(id) = cx.qpath_res(path, pat.hir_id).opt_def_id() {
74 if arm.guard.is_none() && patterns.iter().all(|p| !is_refutable(cx, p)) {
75 missing_variants.retain(|e| e.ctor_def_id != Some(id));
80 PatKind::Struct(path, patterns, ..) => {
81 if let Some(id) = cx.qpath_res(path, pat.hir_id).opt_def_id() {
82 if arm.guard.is_none() && patterns.iter().all(|p| !is_refutable(cx, p.pat)) {
83 missing_variants.retain(|e| e.def_id != id);
91 QPath::Resolved(_, path) => path_prefix.with_path(path.segments),
94 kind: TyKind::Path(QPath::Resolved(_, path)),
98 ) => path_prefix.with_prefix(path.segments),
104 let format_suggestion = |variant: &VariantDef| {
107 if let Some(ident) = wildcard_ident {
108 format!("{} @ ", ident.name)
112 if let CommonPrefixSearcher::Path(path_prefix) = path_prefix {
113 let mut s = String::new();
114 for seg in path_prefix {
115 s.push_str(seg.ident.as_str());
120 let mut s = cx.tcx.def_path_str(adt_def.did());
125 match variant.ctor_kind {
126 CtorKind::Fn if variant.fields.len() == 1 => "(_)",
127 CtorKind::Fn => "(..)",
128 CtorKind::Const => "",
129 CtorKind::Fictive => "{ .. }",
134 match missing_variants.as_slice() {
136 [x] if !adt_def.is_variant_list_non_exhaustive() && !has_hidden => span_lint_and_sugg(
138 MATCH_WILDCARD_FOR_SINGLE_VARIANTS,
140 "wildcard matches only a single variant and will also match any future added variants",
142 format_suggestion(x),
143 Applicability::MaybeIncorrect,
146 let mut suggestions: Vec<_> = variants.iter().copied().map(format_suggestion).collect();
147 let message = if adt_def.is_variant_list_non_exhaustive() || has_hidden {
148 suggestions.push("_".into());
149 "wildcard matches known variants and will also match future added variants"
151 "wildcard match will also match any future added variants"
156 WILDCARD_ENUM_MATCH_ARM,
160 suggestions.join(" | "),
161 Applicability::MaybeIncorrect,
167 enum CommonPrefixSearcher<'a> {
169 Path(&'a [PathSegment<'a>]),
172 impl<'a> CommonPrefixSearcher<'a> {
173 fn with_path(&mut self, path: &'a [PathSegment<'a>]) {
175 [path @ .., _] => self.with_prefix(path),
180 fn with_prefix(&mut self, path: &'a [PathSegment<'a>]) {
182 Self::None => *self = Self::Path(path),
183 Self::Path(self_path)
186 .map(|p| p.ident.name)
187 .eq(self_path.iter().map(|p| p.ident.name)) => {},
188 Self::Path(_) => *self = Self::Mixed,
194 fn is_hidden(cx: &LateContext<'_>, variant_def: &VariantDef) -> bool {
195 cx.tcx.is_doc_hidden(variant_def.def_id) || cx.tcx.has_attr(variant_def.def_id, sym::unstable)