]>
Commit | Line | Data |
---|---|---|
487cf647 FG |
1 | use clippy_utils::diagnostics::span_lint_and_then; |
2 | use clippy_utils::higher::IfLetOrMatch; | |
3 | use clippy_utils::msrvs::{self, Msrv}; | |
4 | use clippy_utils::peel_blocks; | |
5 | use clippy_utils::source::snippet_with_context; | |
6 | use clippy_utils::ty::is_type_diagnostic_item; | |
9ffffee4 | 7 | use clippy_utils::visitors::{Descend, Visitable}; |
487cf647 | 8 | use if_chain::if_chain; |
fe692bf9 | 9 | use rustc_data_structures::fx::{FxHashMap, FxHashSet}; |
487cf647 | 10 | use rustc_errors::Applicability; |
9ffffee4 FG |
11 | use rustc_hir::intravisit::{walk_expr, Visitor}; |
12 | use rustc_hir::{Expr, ExprKind, HirId, ItemId, Local, MatchSource, Pat, PatKind, QPath, Stmt, StmtKind, Ty}; | |
487cf647 FG |
13 | use rustc_lint::{LateContext, LateLintPass, LintContext}; |
14 | use rustc_middle::lint::in_external_macro; | |
15 | use rustc_session::{declare_tool_lint, impl_lint_pass}; | |
fe692bf9 | 16 | use rustc_span::symbol::{sym, Symbol}; |
487cf647 FG |
17 | use rustc_span::Span; |
18 | use serde::Deserialize; | |
19 | use std::ops::ControlFlow; | |
fe692bf9 | 20 | use std::slice; |
487cf647 FG |
21 | |
22 | declare_clippy_lint! { | |
23 | /// ### What it does | |
24 | /// | |
25 | /// Warn of cases where `let...else` could be used | |
26 | /// | |
27 | /// ### Why is this bad? | |
28 | /// | |
29 | /// `let...else` provides a standard construct for this pattern | |
30 | /// that people can easily recognize. It's also more compact. | |
31 | /// | |
32 | /// ### Example | |
33 | /// | |
34 | /// ```rust | |
35 | /// # let w = Some(0); | |
36 | /// let v = if let Some(v) = w { v } else { return }; | |
37 | /// ``` | |
38 | /// | |
39 | /// Could be written: | |
40 | /// | |
41 | /// ```rust | |
487cf647 FG |
42 | /// # fn main () { |
43 | /// # let w = Some(0); | |
44 | /// let Some(v) = w else { return }; | |
45 | /// # } | |
46 | /// ``` | |
47 | #[clippy::version = "1.67.0"] | |
48 | pub MANUAL_LET_ELSE, | |
49 | pedantic, | |
50 | "manual implementation of a let...else statement" | |
51 | } | |
52 | ||
53 | pub struct ManualLetElse { | |
54 | msrv: Msrv, | |
55 | matches_behaviour: MatchLintBehaviour, | |
56 | } | |
57 | ||
58 | impl ManualLetElse { | |
59 | #[must_use] | |
60 | pub fn new(msrv: Msrv, matches_behaviour: MatchLintBehaviour) -> Self { | |
61 | Self { | |
62 | msrv, | |
63 | matches_behaviour, | |
64 | } | |
65 | } | |
66 | } | |
67 | ||
68 | impl_lint_pass!(ManualLetElse => [MANUAL_LET_ELSE]); | |
69 | ||
70 | impl<'tcx> LateLintPass<'tcx> for ManualLetElse { | |
71 | fn check_stmt(&mut self, cx: &LateContext<'_>, stmt: &'tcx Stmt<'tcx>) { | |
49aad941 FG |
72 | if !self.msrv.meets(msrvs::LET_ELSE) || in_external_macro(cx.sess(), stmt.span) { |
73 | return; | |
74 | } | |
487cf647 | 75 | |
49aad941 FG |
76 | if let StmtKind::Local(local) = stmt.kind && |
77 | let Some(init) = local.init && | |
78 | local.els.is_none() && | |
79 | local.ty.is_none() && | |
80 | init.span.ctxt() == stmt.span.ctxt() && | |
fe692bf9 FG |
81 | let Some(if_let_or_match) = IfLetOrMatch::parse(cx, init) |
82 | { | |
83 | match if_let_or_match { | |
84 | IfLetOrMatch::IfLet(if_let_expr, let_pat, if_then, if_else) => if_chain! { | |
85 | if let Some(ident_map) = expr_simple_identity_map(local.pat, let_pat, if_then); | |
86 | if let Some(if_else) = if_else; | |
87 | if expr_diverges(cx, if_else); | |
88 | then { | |
89 | emit_manual_let_else(cx, stmt.span, if_let_expr, &ident_map, let_pat, if_else); | |
90 | } | |
91 | }, | |
92 | IfLetOrMatch::Match(match_expr, arms, source) => { | |
93 | if self.matches_behaviour == MatchLintBehaviour::Never { | |
94 | return; | |
95 | } | |
96 | if source != MatchSource::Normal { | |
97 | return; | |
98 | } | |
99 | // Any other number than two arms doesn't (necessarily) | |
100 | // have a trivial mapping to let else. | |
101 | if arms.len() != 2 { | |
102 | return; | |
103 | } | |
104 | // Guards don't give us an easy mapping either | |
105 | if arms.iter().any(|arm| arm.guard.is_some()) { | |
106 | return; | |
107 | } | |
108 | let check_types = self.matches_behaviour == MatchLintBehaviour::WellKnownTypes; | |
109 | let diverging_arm_opt = arms | |
110 | .iter() | |
111 | .enumerate() | |
112 | .find(|(_, arm)| expr_diverges(cx, arm.body) && pat_allowed_for_else(cx, arm.pat, check_types)); | |
113 | let Some((idx, diverging_arm)) = diverging_arm_opt else { return; }; | |
114 | // If the non-diverging arm is the first one, its pattern can be reused in a let/else statement. | |
115 | // However, if it arrives in second position, its pattern may cover some cases already covered | |
116 | // by the diverging one. | |
117 | // TODO: accept the non-diverging arm as a second position if patterns are disjointed. | |
118 | if idx == 0 { | |
119 | return; | |
120 | } | |
121 | let pat_arm = &arms[1 - idx]; | |
122 | let Some(ident_map) = expr_simple_identity_map(local.pat, pat_arm.pat, pat_arm.body) else { | |
123 | return | |
124 | }; | |
487cf647 | 125 | |
fe692bf9 FG |
126 | emit_manual_let_else(cx, stmt.span, match_expr, &ident_map, pat_arm.pat, diverging_arm.body); |
127 | }, | |
128 | } | |
49aad941 | 129 | }; |
487cf647 FG |
130 | } |
131 | ||
132 | extract_msrv_attr!(LateContext); | |
133 | } | |
134 | ||
49aad941 FG |
135 | fn emit_manual_let_else( |
136 | cx: &LateContext<'_>, | |
137 | span: Span, | |
138 | expr: &Expr<'_>, | |
fe692bf9 | 139 | ident_map: &FxHashMap<Symbol, &Pat<'_>>, |
49aad941 FG |
140 | pat: &Pat<'_>, |
141 | else_body: &Expr<'_>, | |
142 | ) { | |
487cf647 FG |
143 | span_lint_and_then( |
144 | cx, | |
145 | MANUAL_LET_ELSE, | |
146 | span, | |
147 | "this could be rewritten as `let...else`", | |
148 | |diag| { | |
149 | // This is far from perfect, for example there needs to be: | |
fe692bf9 FG |
150 | // * renamings of the bindings for many `PatKind`s like slices, etc. |
151 | // * limitations in the existing replacement algorithms | |
487cf647 | 152 | // * unused binding collision detection with existing ones |
487cf647 FG |
153 | // for this to be machine applicable. |
154 | let mut app = Applicability::HasPlaceholders; | |
487cf647 FG |
155 | let (sn_expr, _) = snippet_with_context(cx, expr.span, span.ctxt(), "", &mut app); |
156 | let (sn_else, _) = snippet_with_context(cx, else_body.span, span.ctxt(), "", &mut app); | |
157 | ||
158 | let else_bl = if matches!(else_body.kind, ExprKind::Block(..)) { | |
159 | sn_else.into_owned() | |
160 | } else { | |
161 | format!("{{ {sn_else} }}") | |
162 | }; | |
fe692bf9 | 163 | let sn_bl = replace_in_pattern(cx, span, ident_map, pat, &mut app, true); |
9c376795 | 164 | let sugg = format!("let {sn_bl} = {sn_expr} else {else_bl};"); |
487cf647 FG |
165 | diag.span_suggestion(span, "consider writing", sugg, app); |
166 | }, | |
167 | ); | |
168 | } | |
169 | ||
fe692bf9 FG |
170 | /// Replaces the locals in the pattern |
171 | /// | |
172 | /// For this example: | |
173 | /// | |
174 | /// ```ignore | |
175 | /// let (a, FooBar { b, c }) = if let Bar { Some(a_i), b_i } = ex { (a_i, b_i) } else { return }; | |
176 | /// ``` | |
177 | /// | |
178 | /// We have: | |
179 | /// | |
180 | /// ```ignore | |
181 | /// pat: Bar { Some(a_i), b_i } | |
182 | /// ident_map: (a_i) -> (a), (b_i) -> (FooBar { b, c }) | |
183 | /// ``` | |
184 | /// | |
185 | /// We return: | |
186 | /// | |
187 | /// ```ignore | |
188 | /// Bar { Some(a), b_i: FooBar { b, c } } | |
189 | /// ``` | |
190 | fn replace_in_pattern( | |
191 | cx: &LateContext<'_>, | |
192 | span: Span, | |
193 | ident_map: &FxHashMap<Symbol, &Pat<'_>>, | |
194 | pat: &Pat<'_>, | |
195 | app: &mut Applicability, | |
196 | top_level: bool, | |
197 | ) -> String { | |
198 | // We put a labeled block here so that we can implement the fallback in this function. | |
199 | // As the function has multiple call sites, implementing the fallback via an Option<T> | |
200 | // return type and unwrap_or_else would cause repetition. Similarly, the function also | |
201 | // invokes the fall back multiple times. | |
202 | 'a: { | |
203 | // If the ident map is empty, there is no replacement to do. | |
204 | // The code following this if assumes a non-empty ident_map. | |
205 | if ident_map.is_empty() { | |
206 | break 'a; | |
207 | } | |
208 | ||
209 | match pat.kind { | |
210 | PatKind::Binding(_ann, _id, binding_name, opt_subpt) => { | |
211 | let Some(pat_to_put) = ident_map.get(&binding_name.name) else { break 'a }; | |
212 | let (sn_ptp, _) = snippet_with_context(cx, pat_to_put.span, span.ctxt(), "", app); | |
213 | if let Some(subpt) = opt_subpt { | |
214 | let subpt = replace_in_pattern(cx, span, ident_map, subpt, app, false); | |
215 | return format!("{sn_ptp} @ {subpt}"); | |
216 | } | |
217 | return sn_ptp.to_string(); | |
218 | }, | |
219 | PatKind::Or(pats) => { | |
220 | let patterns = pats | |
221 | .iter() | |
222 | .map(|pat| replace_in_pattern(cx, span, ident_map, pat, app, false)) | |
223 | .collect::<Vec<_>>(); | |
224 | let or_pat = patterns.join(" | "); | |
225 | if top_level { | |
226 | return format!("({or_pat})"); | |
227 | } | |
228 | return or_pat; | |
229 | }, | |
230 | PatKind::Struct(path, fields, has_dot_dot) => { | |
231 | let fields = fields | |
232 | .iter() | |
233 | .map(|fld| { | |
234 | if let PatKind::Binding(_, _, name, None) = fld.pat.kind && | |
235 | let Some(pat_to_put) = ident_map.get(&name.name) | |
236 | { | |
237 | let (sn_fld_name, _) = snippet_with_context(cx, fld.ident.span, span.ctxt(), "", app); | |
238 | let (sn_ptp, _) = snippet_with_context(cx, pat_to_put.span, span.ctxt(), "", app); | |
239 | // TODO: this is a bit of a hack, but it does its job. Ideally, we'd check if pat_to_put is | |
240 | // a PatKind::Binding but that is also hard to get right. | |
241 | if sn_fld_name == sn_ptp { | |
242 | // Field init shorthand | |
243 | return format!("{sn_fld_name}"); | |
244 | } | |
245 | return format!("{sn_fld_name}: {sn_ptp}"); | |
246 | } | |
247 | let (sn_fld, _) = snippet_with_context(cx, fld.span, span.ctxt(), "", app); | |
248 | sn_fld.into_owned() | |
249 | }) | |
250 | .collect::<Vec<_>>(); | |
251 | let fields_string = fields.join(", "); | |
252 | ||
253 | let dot_dot_str = if has_dot_dot { " .." } else { "" }; | |
254 | let (sn_pth, _) = snippet_with_context(cx, path.span(), span.ctxt(), "", app); | |
255 | return format!("{sn_pth} {{ {fields_string}{dot_dot_str} }}"); | |
256 | }, | |
257 | // Replace the variable name iff `TupleStruct` has one argument like `Variant(v)`. | |
258 | PatKind::TupleStruct(ref w, args, dot_dot_pos) => { | |
259 | let mut args = args | |
260 | .iter() | |
261 | .map(|pat| replace_in_pattern(cx, span, ident_map, pat, app, false)) | |
262 | .collect::<Vec<_>>(); | |
263 | if let Some(pos) = dot_dot_pos.as_opt_usize() { | |
264 | args.insert(pos, "..".to_owned()); | |
265 | } | |
266 | let args = args.join(", "); | |
267 | let sn_wrapper = cx.sess().source_map().span_to_snippet(w.span()).unwrap_or_default(); | |
268 | return format!("{sn_wrapper}({args})"); | |
269 | }, | |
270 | PatKind::Tuple(args, dot_dot_pos) => { | |
271 | let mut args = args | |
272 | .iter() | |
273 | .map(|pat| replace_in_pattern(cx, span, ident_map, pat, app, false)) | |
274 | .collect::<Vec<_>>(); | |
275 | if let Some(pos) = dot_dot_pos.as_opt_usize() { | |
276 | args.insert(pos, "..".to_owned()); | |
277 | } | |
278 | let args = args.join(", "); | |
279 | return format!("({args})"); | |
280 | }, | |
281 | _ => {}, | |
282 | } | |
283 | } | |
284 | let (sn_pat, _) = snippet_with_context(cx, pat.span, span.ctxt(), "", app); | |
285 | sn_pat.into_owned() | |
286 | } | |
287 | ||
9ffffee4 FG |
288 | /// Check whether an expression is divergent. May give false negatives. |
289 | fn expr_diverges(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { | |
290 | struct V<'cx, 'tcx> { | |
291 | cx: &'cx LateContext<'tcx>, | |
292 | res: ControlFlow<(), Descend>, | |
487cf647 | 293 | } |
9ffffee4 FG |
294 | impl<'tcx> Visitor<'tcx> for V<'_, '_> { |
295 | fn visit_expr(&mut self, e: &'tcx Expr<'tcx>) { | |
296 | fn is_never(cx: &LateContext<'_>, expr: &'_ Expr<'_>) -> bool { | |
297 | if let Some(ty) = cx.typeck_results().expr_ty_opt(expr) { | |
298 | return ty.is_never(); | |
487cf647 | 299 | } |
9ffffee4 FG |
300 | false |
301 | } | |
302 | ||
303 | if self.res.is_break() { | |
304 | return; | |
305 | } | |
306 | ||
307 | // We can't just call is_never on expr and be done, because the type system | |
308 | // sometimes coerces the ! type to something different before we can get | |
309 | // our hands on it. So instead, we do a manual search. We do fall back to | |
310 | // is_never in some places when there is no better alternative. | |
311 | self.res = match e.kind { | |
312 | ExprKind::Continue(_) | ExprKind::Break(_, _) | ExprKind::Ret(_) => ControlFlow::Break(()), | |
313 | ExprKind::Call(call, _) => { | |
314 | if is_never(self.cx, e) || is_never(self.cx, call) { | |
315 | ControlFlow::Break(()) | |
316 | } else { | |
317 | ControlFlow::Continue(Descend::Yes) | |
318 | } | |
319 | }, | |
320 | ExprKind::MethodCall(..) => { | |
321 | if is_never(self.cx, e) { | |
322 | ControlFlow::Break(()) | |
323 | } else { | |
324 | ControlFlow::Continue(Descend::Yes) | |
325 | } | |
326 | }, | |
327 | ExprKind::If(if_expr, if_then, if_else) => { | |
328 | let else_diverges = if_else.map_or(false, |ex| expr_diverges(self.cx, ex)); | |
329 | let diverges = | |
330 | expr_diverges(self.cx, if_expr) || (else_diverges && expr_diverges(self.cx, if_then)); | |
331 | if diverges { | |
332 | ControlFlow::Break(()) | |
333 | } else { | |
334 | ControlFlow::Continue(Descend::No) | |
335 | } | |
336 | }, | |
337 | ExprKind::Match(match_expr, match_arms, _) => { | |
338 | let diverges = expr_diverges(self.cx, match_expr) | |
339 | || match_arms.iter().all(|arm| { | |
340 | let guard_diverges = arm.guard.as_ref().map_or(false, |g| expr_diverges(self.cx, g.body())); | |
341 | guard_diverges || expr_diverges(self.cx, arm.body) | |
342 | }); | |
343 | if diverges { | |
344 | ControlFlow::Break(()) | |
345 | } else { | |
346 | ControlFlow::Continue(Descend::No) | |
347 | } | |
348 | }, | |
487cf647 | 349 | |
9ffffee4 FG |
350 | // Don't continue into loops or labeled blocks, as they are breakable, |
351 | // and we'd have to start checking labels. | |
352 | ExprKind::Block(_, Some(_)) | ExprKind::Loop(..) => ControlFlow::Continue(Descend::No), | |
353 | ||
354 | // Default: descend | |
355 | _ => ControlFlow::Continue(Descend::Yes), | |
356 | }; | |
357 | if let ControlFlow::Continue(Descend::Yes) = self.res { | |
358 | walk_expr(self, e); | |
359 | } | |
360 | } | |
487cf647 | 361 | |
9ffffee4 FG |
362 | fn visit_local(&mut self, local: &'tcx Local<'_>) { |
363 | // Don't visit the else block of a let/else statement as it will not make | |
364 | // the statement divergent even though the else block is divergent. | |
365 | if let Some(init) = local.init { | |
366 | self.visit_expr(init); | |
367 | } | |
487cf647 | 368 | } |
9ffffee4 FG |
369 | |
370 | // Avoid unnecessary `walk_*` calls. | |
371 | fn visit_ty(&mut self, _: &'tcx Ty<'tcx>) {} | |
372 | fn visit_pat(&mut self, _: &'tcx Pat<'tcx>) {} | |
373 | fn visit_qpath(&mut self, _: &'tcx QPath<'tcx>, _: HirId, _: Span) {} | |
374 | // Avoid monomorphising all `visit_*` functions. | |
375 | fn visit_nested_item(&mut self, _: ItemId) {} | |
376 | } | |
377 | ||
378 | let mut v = V { | |
379 | cx, | |
380 | res: ControlFlow::Continue(Descend::Yes), | |
381 | }; | |
382 | expr.visit(&mut v); | |
383 | v.res.is_break() | |
487cf647 FG |
384 | } |
385 | ||
386 | fn pat_allowed_for_else(cx: &LateContext<'_>, pat: &'_ Pat<'_>, check_types: bool) -> bool { | |
387 | // Check whether the pattern contains any bindings, as the | |
388 | // binding might potentially be used in the body. | |
389 | // TODO: only look for *used* bindings. | |
390 | let mut has_bindings = false; | |
391 | pat.each_binding_or_first(&mut |_, _, _, _| has_bindings = true); | |
392 | if has_bindings { | |
393 | return false; | |
394 | } | |
395 | ||
396 | // If we shouldn't check the types, exit early. | |
397 | if !check_types { | |
398 | return true; | |
399 | } | |
400 | ||
401 | // Check whether any possibly "unknown" patterns are included, | |
402 | // because users might not know which values some enum has. | |
403 | // Well-known enums are excepted, as we assume people know them. | |
404 | // We do a deep check, to be able to disallow Err(En::Foo(_)) | |
405 | // for usage of the En::Foo variant, as we disallow En::Foo(_), | |
406 | // but we allow Err(_). | |
407 | let typeck_results = cx.typeck_results(); | |
408 | let mut has_disallowed = false; | |
409 | pat.walk_always(|pat| { | |
410 | // Only do the check if the type is "spelled out" in the pattern | |
411 | if !matches!( | |
412 | pat.kind, | |
413 | PatKind::Struct(..) | PatKind::TupleStruct(..) | PatKind::Path(..) | |
414 | ) { | |
415 | return; | |
416 | }; | |
417 | let ty = typeck_results.pat_ty(pat); | |
418 | // Option and Result are allowed, everything else isn't. | |
419 | if !(is_type_diagnostic_item(cx, ty, sym::Option) || is_type_diagnostic_item(cx, ty, sym::Result)) { | |
420 | has_disallowed = true; | |
421 | } | |
422 | }); | |
423 | !has_disallowed | |
424 | } | |
425 | ||
fe692bf9 FG |
426 | /// Checks if the passed block is a simple identity referring to bindings created by the pattern, |
427 | /// and if yes, returns a mapping between the relevant sub-pattern and the identifier it corresponds | |
428 | /// to. | |
429 | /// | |
430 | /// We support patterns with multiple bindings and tuples, e.g.: | |
431 | /// | |
432 | /// ```ignore | |
433 | /// let (foo_o, bar_o) = if let (Some(foo), bar) = g() { (foo, bar) } else { ... } | |
434 | /// ``` | |
435 | /// | |
436 | /// The expected params would be: | |
437 | /// | |
438 | /// ```ignore | |
439 | /// local_pat: (foo_o, bar_o) | |
440 | /// let_pat: (Some(foo), bar) | |
441 | /// expr: (foo, bar) | |
442 | /// ``` | |
443 | /// | |
444 | /// We build internal `sub_pats` so that it looks like `[foo_o, bar_o]` and `paths` so that it looks | |
445 | /// like `[foo, bar]`. Then we turn that into `FxHashMap [(foo) -> (foo_o), (bar) -> (bar_o)]` which | |
446 | /// we return. | |
447 | fn expr_simple_identity_map<'a, 'hir>( | |
448 | local_pat: &'a Pat<'hir>, | |
449 | let_pat: &'_ Pat<'hir>, | |
450 | expr: &'_ Expr<'hir>, | |
451 | ) -> Option<FxHashMap<Symbol, &'a Pat<'hir>>> { | |
487cf647 | 452 | let peeled = peel_blocks(expr); |
fe692bf9 FG |
453 | let (sub_pats, paths) = match (local_pat.kind, peeled.kind) { |
454 | (PatKind::Tuple(pats, _), ExprKind::Tup(exprs)) | (PatKind::Slice(pats, ..), ExprKind::Array(exprs)) => { | |
455 | (pats, exprs) | |
456 | }, | |
457 | (_, ExprKind::Path(_)) => (slice::from_ref(local_pat), slice::from_ref(peeled)), | |
458 | _ => return None, | |
487cf647 | 459 | }; |
fe692bf9 FG |
460 | |
461 | // There is some length mismatch, which indicates usage of .. in the patterns above e.g.: | |
462 | // let (a, ..) = if let [a, b, _c] = ex { (a, b) } else { ... }; | |
463 | // We bail in these cases as they should be rare. | |
464 | if paths.len() != sub_pats.len() { | |
465 | return None; | |
466 | } | |
467 | ||
487cf647 | 468 | let mut pat_bindings = FxHashSet::default(); |
fe692bf9 | 469 | let_pat.each_binding_or_first(&mut |_ann, _hir_id, _sp, ident| { |
487cf647 FG |
470 | pat_bindings.insert(ident); |
471 | }); | |
472 | if pat_bindings.len() < paths.len() { | |
fe692bf9 FG |
473 | // This rebinds some bindings from the outer scope, or it repeats some copy-able bindings multiple |
474 | // times. We don't support these cases so we bail here. E.g.: | |
475 | // let foo = 0; | |
476 | // let (new_foo, bar, bar_copied) = if let Some(bar) = Some(0) { (foo, bar, bar) } else { .. }; | |
477 | return None; | |
487cf647 | 478 | } |
fe692bf9 FG |
479 | let mut ident_map = FxHashMap::default(); |
480 | for (sub_pat, path) in sub_pats.iter().zip(paths.iter()) { | |
481 | if let ExprKind::Path(QPath::Resolved(_ty, path)) = path.kind && | |
482 | let [path_seg] = path.segments | |
483 | { | |
484 | let ident = path_seg.ident; | |
485 | if !pat_bindings.remove(&ident) { | |
486 | return None; | |
487cf647 | 487 | } |
fe692bf9 FG |
488 | ident_map.insert(ident.name, sub_pat); |
489 | } else { | |
490 | return None; | |
487cf647 FG |
491 | } |
492 | } | |
fe692bf9 | 493 | Some(ident_map) |
487cf647 FG |
494 | } |
495 | ||
496 | #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Deserialize)] | |
497 | pub enum MatchLintBehaviour { | |
498 | AllTypes, | |
499 | WellKnownTypes, | |
500 | Never, | |
501 | } |