]> git.lizzy.rs Git - rust.git/blob - src/tools/clippy/clippy_lints/src/matches/match_wild_enum.rs
Rollup merge of #93613 - crlf0710:rename_to_async_iter, r=yaahc
[rust.git] / src / tools / clippy / clippy_lints / src / matches / match_wild_enum.rs
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};
9 use rustc_span::sym;
10
11 use super::{MATCH_WILDCARD_FOR_SINGLE_VARIANTS, WILDCARD_ENUM_MATCH_ARM};
12
13 #[allow(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() {
17         ty::Adt(adt_def, _)
18             if adt_def.is_enum()
19                 && !(is_type_diagnostic_item(cx, ty, sym::Option) || is_type_diagnostic_item(cx, ty, sym::Result)) =>
20         {
21             adt_def
22         },
23         _ => return,
24     };
25
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;
31     for arm in arms {
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);
37             },
38             _ => has_non_wild = true,
39         }
40     }
41     let wildcard_span = match wildcard_span {
42         Some(x) if has_non_wild => x,
43         _ => return,
44     };
45
46     // Accumulate the variants which should be put in place of the wildcard because they're not
47     // already covered.
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();
50
51     let mut path_prefix = CommonPrefixSearcher::None;
52     for arm in arms {
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                     #[allow(clippy::match_same_arms)]
60                     let id = match cx.qpath_res(path, pat.hir_id) {
61                         Res::Def(
62                             DefKind::Const | DefKind::ConstParam | DefKind::AnonConst | DefKind::InlineConst,
63                             _,
64                         ) => return,
65                         Res::Def(_, id) => id,
66                         _ => return,
67                     };
68                     if arm.guard.is_none() {
69                         missing_variants.retain(|e| e.ctor_def_id != Some(id));
70                     }
71                     path
72                 },
73                 PatKind::TupleStruct(path, patterns, ..) => {
74                     if let Some(id) = cx.qpath_res(path, pat.hir_id).opt_def_id() {
75                         if arm.guard.is_none() && patterns.iter().all(|p| !is_refutable(cx, p)) {
76                             missing_variants.retain(|e| e.ctor_def_id != Some(id));
77                         }
78                     }
79                     path
80                 },
81                 PatKind::Struct(path, patterns, ..) => {
82                     if let Some(id) = cx.qpath_res(path, pat.hir_id).opt_def_id() {
83                         if arm.guard.is_none() && patterns.iter().all(|p| !is_refutable(cx, p.pat)) {
84                             missing_variants.retain(|e| e.def_id != id);
85                         }
86                     }
87                     path
88                 },
89                 _ => return,
90             };
91             match path {
92                 QPath::Resolved(_, path) => path_prefix.with_path(path.segments),
93                 QPath::TypeRelative(
94                     Ty {
95                         kind: TyKind::Path(QPath::Resolved(_, path)),
96                         ..
97                     },
98                     _,
99                 ) => path_prefix.with_prefix(path.segments),
100                 _ => (),
101             }
102         });
103     }
104
105     let format_suggestion = |variant: &VariantDef| {
106         format!(
107             "{}{}{}{}",
108             if let Some(ident) = wildcard_ident {
109                 format!("{} @ ", ident.name)
110             } else {
111                 String::new()
112             },
113             if let CommonPrefixSearcher::Path(path_prefix) = path_prefix {
114                 let mut s = String::new();
115                 for seg in path_prefix {
116                     s.push_str(seg.ident.as_str());
117                     s.push_str("::");
118                 }
119                 s
120             } else {
121                 let mut s = cx.tcx.def_path_str(adt_def.did);
122                 s.push_str("::");
123                 s
124             },
125             variant.name,
126             match variant.ctor_kind {
127                 CtorKind::Fn if variant.fields.len() == 1 => "(_)",
128                 CtorKind::Fn => "(..)",
129                 CtorKind::Const => "",
130                 CtorKind::Fictive => "{ .. }",
131             }
132         )
133     };
134
135     match missing_variants.as_slice() {
136         [] => (),
137         [x] if !adt_def.is_variant_list_non_exhaustive() && !has_hidden => span_lint_and_sugg(
138             cx,
139             MATCH_WILDCARD_FOR_SINGLE_VARIANTS,
140             wildcard_span,
141             "wildcard matches only a single variant and will also match any future added variants",
142             "try this",
143             format_suggestion(x),
144             Applicability::MaybeIncorrect,
145         ),
146         variants => {
147             let mut suggestions: Vec<_> = variants.iter().copied().map(format_suggestion).collect();
148             let message = if adt_def.is_variant_list_non_exhaustive() || has_hidden {
149                 suggestions.push("_".into());
150                 "wildcard matches known variants and will also match future added variants"
151             } else {
152                 "wildcard match will also match any future added variants"
153             };
154
155             span_lint_and_sugg(
156                 cx,
157                 WILDCARD_ENUM_MATCH_ARM,
158                 wildcard_span,
159                 message,
160                 "try this",
161                 suggestions.join(" | "),
162                 Applicability::MaybeIncorrect,
163             );
164         },
165     };
166 }
167
168 enum CommonPrefixSearcher<'a> {
169     None,
170     Path(&'a [PathSegment<'a>]),
171     Mixed,
172 }
173 impl<'a> CommonPrefixSearcher<'a> {
174     fn with_path(&mut self, path: &'a [PathSegment<'a>]) {
175         match path {
176             [path @ .., _] => self.with_prefix(path),
177             [] => (),
178         }
179     }
180
181     fn with_prefix(&mut self, path: &'a [PathSegment<'a>]) {
182         match self {
183             Self::None => *self = Self::Path(path),
184             Self::Path(self_path)
185                 if path
186                     .iter()
187                     .map(|p| p.ident.name)
188                     .eq(self_path.iter().map(|p| p.ident.name)) => {},
189             Self::Path(_) => *self = Self::Mixed,
190             Self::Mixed => (),
191         }
192     }
193 }
194
195 fn is_hidden(cx: &LateContext<'_>, variant_def: &VariantDef) -> bool {
196     let attrs = cx.tcx.get_attrs(variant_def.def_id);
197     clippy_utils::attrs::is_doc_hidden(attrs) || clippy_utils::attrs::is_unstable(attrs)
198 }