]> git.lizzy.rs Git - rust.git/blob - clippy_lints/src/matches.rs
non_copy_const: remove incorrect suggestion
[rust.git] / clippy_lints / src / matches.rs
1 use crate::consts::{constant, Constant};
2 use crate::utils::paths;
3 use crate::utils::sugg::Sugg;
4 use crate::utils::{
5     expr_block, is_allowed, is_expn_of, match_qpath, match_type, multispan_sugg, remove_blocks, snippet,
6     snippet_with_applicability, span_lint_and_sugg, span_lint_and_then, span_note_and_lint, walk_ptrs_ty,
7 };
8 use if_chain::if_chain;
9 use rustc::hir::def::CtorKind;
10 use rustc::hir::*;
11 use rustc::lint::{in_external_macro, LateContext, LateLintPass, LintArray, LintContext, LintPass};
12 use rustc::ty::{self, Ty};
13 use rustc::{declare_lint_pass, declare_tool_lint};
14 use rustc_errors::Applicability;
15 use std::cmp::Ordering;
16 use std::collections::Bound;
17 use syntax::ast::LitKind;
18 use syntax::source_map::Span;
19
20 declare_clippy_lint! {
21     /// **What it does:** Checks for matches with a single arm where an `if let`
22     /// will usually suffice.
23     ///
24     /// **Why is this bad?** Just readability – `if let` nests less than a `match`.
25     ///
26     /// **Known problems:** None.
27     ///
28     /// **Example:**
29     /// ```rust
30     /// # fn bar(stool: &str) {}
31     /// # let x = Some("abc");
32     /// match x {
33     ///     Some(ref foo) => bar(foo),
34     ///     _ => (),
35     /// }
36     /// ```
37     pub SINGLE_MATCH,
38     style,
39     "a match statement with a single nontrivial arm (i.e., where the other arm is `_ => {}`) instead of `if let`"
40 }
41
42 declare_clippy_lint! {
43     /// **What it does:** Checks for matches with two arms where an `if let else` will
44     /// usually suffice.
45     ///
46     /// **Why is this bad?** Just readability – `if let` nests less than a `match`.
47     ///
48     /// **Known problems:** Personal style preferences may differ.
49     ///
50     /// **Example:**
51     ///
52     /// Using `match`:
53     ///
54     /// ```rust
55     /// # fn bar(foo: &usize) {}
56     /// # let other_ref: usize = 1;
57     /// # let x: Option<&usize> = Some(&1);
58     /// match x {
59     ///     Some(ref foo) => bar(foo),
60     ///     _ => bar(&other_ref),
61     /// }
62     /// ```
63     ///
64     /// Using `if let` with `else`:
65     ///
66     /// ```rust
67     /// # fn bar(foo: &usize) {}
68     /// # let other_ref: usize = 1;
69     /// # let x: Option<&usize> = Some(&1);
70     /// if let Some(ref foo) = x {
71     ///     bar(foo);
72     /// } else {
73     ///     bar(&other_ref);
74     /// }
75     /// ```
76     pub SINGLE_MATCH_ELSE,
77     pedantic,
78     "a match statement with two arms where the second arm's pattern is a placeholder instead of a specific match pattern"
79 }
80
81 declare_clippy_lint! {
82     /// **What it does:** Checks for matches where all arms match a reference,
83     /// suggesting to remove the reference and deref the matched expression
84     /// instead. It also checks for `if let &foo = bar` blocks.
85     ///
86     /// **Why is this bad?** It just makes the code less readable. That reference
87     /// destructuring adds nothing to the code.
88     ///
89     /// **Known problems:** None.
90     ///
91     /// **Example:**
92     /// ```rust,ignore
93     /// match x {
94     ///     &A(ref y) => foo(y),
95     ///     &B => bar(),
96     ///     _ => frob(&x),
97     /// }
98     /// ```
99     pub MATCH_REF_PATS,
100     style,
101     "a match or `if let` with all arms prefixed with `&` instead of deref-ing the match expression"
102 }
103
104 declare_clippy_lint! {
105     /// **What it does:** Checks for matches where match expression is a `bool`. It
106     /// suggests to replace the expression with an `if...else` block.
107     ///
108     /// **Why is this bad?** It makes the code less readable.
109     ///
110     /// **Known problems:** None.
111     ///
112     /// **Example:**
113     /// ```rust
114     /// # fn foo() {}
115     /// # fn bar() {}
116     /// let condition: bool = true;
117     /// match condition {
118     ///     true => foo(),
119     ///     false => bar(),
120     /// }
121     /// ```
122     /// Use if/else instead:
123     /// ```rust
124     /// # fn foo() {}
125     /// # fn bar() {}
126     /// let condition: bool = true;
127     /// if condition {
128     ///     foo();
129     /// } else {
130     ///     bar();
131     /// }
132     /// ```
133     pub MATCH_BOOL,
134     style,
135     "a match on a boolean expression instead of an `if..else` block"
136 }
137
138 declare_clippy_lint! {
139     /// **What it does:** Checks for overlapping match arms.
140     ///
141     /// **Why is this bad?** It is likely to be an error and if not, makes the code
142     /// less obvious.
143     ///
144     /// **Known problems:** None.
145     ///
146     /// **Example:**
147     /// ```rust
148     /// let x = 5;
149     /// match x {
150     ///     1...10 => println!("1 ... 10"),
151     ///     5...15 => println!("5 ... 15"),
152     ///     _ => (),
153     /// }
154     /// ```
155     pub MATCH_OVERLAPPING_ARM,
156     style,
157     "a match with overlapping arms"
158 }
159
160 declare_clippy_lint! {
161     /// **What it does:** Checks for arm which matches all errors with `Err(_)`
162     /// and take drastic actions like `panic!`.
163     ///
164     /// **Why is this bad?** It is generally a bad practice, just like
165     /// catching all exceptions in java with `catch(Exception)`
166     ///
167     /// **Known problems:** None.
168     ///
169     /// **Example:**
170     /// ```rust
171     /// let x: Result<i32, &str> = Ok(3);
172     /// match x {
173     ///     Ok(_) => println!("ok"),
174     ///     Err(_) => panic!("err"),
175     /// }
176     /// ```
177     pub MATCH_WILD_ERR_ARM,
178     style,
179     "a match with `Err(_)` arm and take drastic actions"
180 }
181
182 declare_clippy_lint! {
183     /// **What it does:** Checks for match which is used to add a reference to an
184     /// `Option` value.
185     ///
186     /// **Why is this bad?** Using `as_ref()` or `as_mut()` instead is shorter.
187     ///
188     /// **Known problems:** None.
189     ///
190     /// **Example:**
191     /// ```rust
192     /// let x: Option<()> = None;
193     /// let r: Option<&()> = match x {
194     ///     None => None,
195     ///     Some(ref v) => Some(v),
196     /// };
197     /// ```
198     pub MATCH_AS_REF,
199     complexity,
200     "a match on an Option value instead of using `as_ref()` or `as_mut`"
201 }
202
203 declare_clippy_lint! {
204     /// **What it does:** Checks for wildcard enum matches using `_`.
205     ///
206     /// **Why is this bad?** New enum variants added by library updates can be missed.
207     ///
208     /// **Known problems:** Suggested replacements may be incorrect if guards exhaustively cover some
209     /// variants, and also may not use correct path to enum if it's not present in the current scope.
210     ///
211     /// **Example:**
212     /// ```rust
213     /// # enum Foo { A(usize), B(usize) }
214     /// # let x = Foo::B(1);
215     /// match x {
216     ///     A => {},
217     ///     _ => {},
218     /// }
219     /// ```
220     pub WILDCARD_ENUM_MATCH_ARM,
221     restriction,
222     "a wildcard enum match arm using `_`"
223 }
224
225 declare_lint_pass!(Matches => [
226     SINGLE_MATCH,
227     MATCH_REF_PATS,
228     MATCH_BOOL,
229     SINGLE_MATCH_ELSE,
230     MATCH_OVERLAPPING_ARM,
231     MATCH_WILD_ERR_ARM,
232     MATCH_AS_REF,
233     WILDCARD_ENUM_MATCH_ARM
234 ]);
235
236 impl<'a, 'tcx> LateLintPass<'a, 'tcx> for Matches {
237     fn check_expr(&mut self, cx: &LateContext<'a, 'tcx>, expr: &'tcx Expr) {
238         if in_external_macro(cx.sess(), expr.span) {
239             return;
240         }
241         if let ExprKind::Match(ref ex, ref arms, MatchSource::Normal) = expr.node {
242             check_single_match(cx, ex, arms, expr);
243             check_match_bool(cx, ex, arms, expr);
244             check_overlapping_arms(cx, ex, arms);
245             check_wild_err_arm(cx, ex, arms);
246             check_wild_enum_match(cx, ex, arms);
247             check_match_as_ref(cx, ex, arms, expr);
248         }
249         if let ExprKind::Match(ref ex, ref arms, _) = expr.node {
250             check_match_ref_pats(cx, ex, arms, expr);
251         }
252     }
253 }
254
255 #[rustfmt::skip]
256 fn check_single_match(cx: &LateContext<'_, '_>, ex: &Expr, arms: &[Arm], expr: &Expr) {
257     if arms.len() == 2 && arms[0].guard.is_none() && arms[1].guard.is_none() {
258         if let PatKind::Or(..) = arms[0].pat.node {
259             // don't lint for or patterns for now, this makes
260             // the lint noisy in unnecessary situations
261             return;
262         }
263         let els = remove_blocks(&arms[1].body);
264         let els = if is_unit_expr(els) {
265             None
266         } else if let ExprKind::Block(_, _) = els.node {
267             // matches with blocks that contain statements are prettier as `if let + else`
268             Some(els)
269         } else {
270             // allow match arms with just expressions
271             return;
272         };
273         let ty = cx.tables.expr_ty(ex);
274         if ty.sty != ty::Bool || is_allowed(cx, MATCH_BOOL, ex.hir_id) {
275             check_single_match_single_pattern(cx, ex, arms, expr, els);
276             check_single_match_opt_like(cx, ex, arms, expr, ty, els);
277         }
278     }
279 }
280
281 fn check_single_match_single_pattern(
282     cx: &LateContext<'_, '_>,
283     ex: &Expr,
284     arms: &[Arm],
285     expr: &Expr,
286     els: Option<&Expr>,
287 ) {
288     if is_wild(&arms[1].pat) {
289         report_single_match_single_pattern(cx, ex, arms, expr, els);
290     }
291 }
292
293 fn report_single_match_single_pattern(
294     cx: &LateContext<'_, '_>,
295     ex: &Expr,
296     arms: &[Arm],
297     expr: &Expr,
298     els: Option<&Expr>,
299 ) {
300     let lint = if els.is_some() { SINGLE_MATCH_ELSE } else { SINGLE_MATCH };
301     let els_str = els.map_or(String::new(), |els| {
302         format!(" else {}", expr_block(cx, els, None, ".."))
303     });
304     span_lint_and_sugg(
305         cx,
306         lint,
307         expr.span,
308         "you seem to be trying to use match for destructuring a single pattern. Consider using `if \
309          let`",
310         "try this",
311         format!(
312             "if let {} = {} {}{}",
313             snippet(cx, arms[0].pat.span, ".."),
314             snippet(cx, ex.span, ".."),
315             expr_block(cx, &arms[0].body, None, ".."),
316             els_str,
317         ),
318         Applicability::HasPlaceholders,
319     );
320 }
321
322 fn check_single_match_opt_like(
323     cx: &LateContext<'_, '_>,
324     ex: &Expr,
325     arms: &[Arm],
326     expr: &Expr,
327     ty: Ty<'_>,
328     els: Option<&Expr>,
329 ) {
330     // list of candidate `Enum`s we know will never get any more members
331     let candidates = &[
332         (&paths::COW, "Borrowed"),
333         (&paths::COW, "Cow::Borrowed"),
334         (&paths::COW, "Cow::Owned"),
335         (&paths::COW, "Owned"),
336         (&paths::OPTION, "None"),
337         (&paths::RESULT, "Err"),
338         (&paths::RESULT, "Ok"),
339     ];
340
341     let path = match arms[1].pat.node {
342         PatKind::TupleStruct(ref path, ref inner, _) => {
343             // Contains any non wildcard patterns (e.g., `Err(err)`)?
344             if !inner.iter().all(is_wild) {
345                 return;
346             }
347             print::to_string(print::NO_ANN, |s| s.print_qpath(path, false))
348         },
349         PatKind::Binding(BindingAnnotation::Unannotated, .., ident, None) => ident.to_string(),
350         PatKind::Path(ref path) => print::to_string(print::NO_ANN, |s| s.print_qpath(path, false)),
351         _ => return,
352     };
353
354     for &(ty_path, pat_path) in candidates {
355         if path == *pat_path && match_type(cx, ty, ty_path) {
356             report_single_match_single_pattern(cx, ex, arms, expr, els);
357         }
358     }
359 }
360
361 fn check_match_bool(cx: &LateContext<'_, '_>, ex: &Expr, arms: &[Arm], expr: &Expr) {
362     // Type of expression is `bool`.
363     if cx.tables.expr_ty(ex).sty == ty::Bool {
364         span_lint_and_then(
365             cx,
366             MATCH_BOOL,
367             expr.span,
368             "you seem to be trying to match on a boolean expression",
369             move |db| {
370                 if arms.len() == 2 {
371                     // no guards
372                     let exprs = if let PatKind::Lit(ref arm_bool) = arms[0].pat.node {
373                         if let ExprKind::Lit(ref lit) = arm_bool.node {
374                             match lit.node {
375                                 LitKind::Bool(true) => Some((&*arms[0].body, &*arms[1].body)),
376                                 LitKind::Bool(false) => Some((&*arms[1].body, &*arms[0].body)),
377                                 _ => None,
378                             }
379                         } else {
380                             None
381                         }
382                     } else {
383                         None
384                     };
385
386                     if let Some((true_expr, false_expr)) = exprs {
387                         let sugg = match (is_unit_expr(true_expr), is_unit_expr(false_expr)) {
388                             (false, false) => Some(format!(
389                                 "if {} {} else {}",
390                                 snippet(cx, ex.span, "b"),
391                                 expr_block(cx, true_expr, None, ".."),
392                                 expr_block(cx, false_expr, None, "..")
393                             )),
394                             (false, true) => Some(format!(
395                                 "if {} {}",
396                                 snippet(cx, ex.span, "b"),
397                                 expr_block(cx, true_expr, None, "..")
398                             )),
399                             (true, false) => {
400                                 let test = Sugg::hir(cx, ex, "..");
401                                 Some(format!("if {} {}", !test, expr_block(cx, false_expr, None, "..")))
402                             },
403                             (true, true) => None,
404                         };
405
406                         if let Some(sugg) = sugg {
407                             db.span_suggestion(
408                                 expr.span,
409                                 "consider using an if/else expression",
410                                 sugg,
411                                 Applicability::HasPlaceholders,
412                             );
413                         }
414                     }
415                 }
416             },
417         );
418     }
419 }
420
421 fn check_overlapping_arms<'a, 'tcx>(cx: &LateContext<'a, 'tcx>, ex: &'tcx Expr, arms: &'tcx [Arm]) {
422     if arms.len() >= 2 && cx.tables.expr_ty(ex).is_integral() {
423         let ranges = all_ranges(cx, arms);
424         let type_ranges = type_ranges(&ranges);
425         if !type_ranges.is_empty() {
426             if let Some((start, end)) = overlapping(&type_ranges) {
427                 span_note_and_lint(
428                     cx,
429                     MATCH_OVERLAPPING_ARM,
430                     start.span,
431                     "some ranges overlap",
432                     end.span,
433                     "overlaps with this",
434                 );
435             }
436         }
437     }
438 }
439
440 fn is_wild(pat: &impl std::ops::Deref<Target = Pat>) -> bool {
441     match pat.node {
442         PatKind::Wild => true,
443         _ => false,
444     }
445 }
446
447 fn check_wild_err_arm(cx: &LateContext<'_, '_>, ex: &Expr, arms: &[Arm]) {
448     let ex_ty = walk_ptrs_ty(cx.tables.expr_ty(ex));
449     if match_type(cx, ex_ty, &paths::RESULT) {
450         for arm in arms {
451             if let PatKind::TupleStruct(ref path, ref inner, _) = arm.pat.node {
452                 let path_str = print::to_string(print::NO_ANN, |s| s.print_qpath(path, false));
453                 if_chain! {
454                     if path_str == "Err";
455                     if inner.iter().any(is_wild);
456                     if let ExprKind::Block(ref block, _) = arm.body.node;
457                     if is_panic_block(block);
458                     then {
459                         // `Err(_)` arm with `panic!` found
460                         span_note_and_lint(cx,
461                                            MATCH_WILD_ERR_ARM,
462                                            arm.pat.span,
463                                            "Err(_) will match all errors, maybe not a good idea",
464                                            arm.pat.span,
465                                            "to remove this warning, match each error separately \
466                                             or use unreachable macro");
467                     }
468                 }
469             }
470         }
471     }
472 }
473
474 fn check_wild_enum_match(cx: &LateContext<'_, '_>, ex: &Expr, arms: &[Arm]) {
475     let ty = cx.tables.expr_ty(ex);
476     if !ty.is_enum() {
477         // If there isn't a nice closed set of possible values that can be conveniently enumerated,
478         // don't complain about not enumerating the mall.
479         return;
480     }
481
482     // First pass - check for violation, but don't do much book-keeping because this is hopefully
483     // the uncommon case, and the book-keeping is slightly expensive.
484     let mut wildcard_span = None;
485     let mut wildcard_ident = None;
486     for arm in arms {
487         if let PatKind::Wild = arm.pat.node {
488             wildcard_span = Some(arm.pat.span);
489         } else if let PatKind::Binding(_, _, ident, None) = arm.pat.node {
490             wildcard_span = Some(arm.pat.span);
491             wildcard_ident = Some(ident);
492         }
493     }
494
495     if let Some(wildcard_span) = wildcard_span {
496         // Accumulate the variants which should be put in place of the wildcard because they're not
497         // already covered.
498
499         let mut missing_variants = vec![];
500         if let ty::Adt(def, _) = ty.sty {
501             for variant in &def.variants {
502                 missing_variants.push(variant);
503             }
504         }
505
506         for arm in arms {
507             if arm.guard.is_some() {
508                 // Guards mean that this case probably isn't exhaustively covered. Technically
509                 // this is incorrect, as we should really check whether each variant is exhaustively
510                 // covered by the set of guards that cover it, but that's really hard to do.
511                 continue;
512             }
513             if let PatKind::Path(ref path) = arm.pat.node {
514                 if let QPath::Resolved(_, p) = path {
515                     missing_variants.retain(|e| e.ctor_def_id != Some(p.res.def_id()));
516                 }
517             } else if let PatKind::TupleStruct(ref path, ..) = arm.pat.node {
518                 if let QPath::Resolved(_, p) = path {
519                     missing_variants.retain(|e| e.ctor_def_id != Some(p.res.def_id()));
520                 }
521             }
522         }
523
524         let suggestion: Vec<String> = missing_variants
525             .iter()
526             .map(|v| {
527                 let suffix = match v.ctor_kind {
528                     CtorKind::Fn => "(..)",
529                     CtorKind::Const | CtorKind::Fictive => "",
530                 };
531                 let ident_str = if let Some(ident) = wildcard_ident {
532                     format!("{} @ ", ident.name)
533                 } else {
534                     String::new()
535                 };
536                 // This path assumes that the enum type is imported into scope.
537                 format!("{}{}{}", ident_str, cx.tcx.def_path_str(v.def_id), suffix)
538             })
539             .collect();
540
541         if suggestion.is_empty() {
542             return;
543         }
544
545         span_lint_and_sugg(
546             cx,
547             WILDCARD_ENUM_MATCH_ARM,
548             wildcard_span,
549             "wildcard match will miss any future added variants.",
550             "try this",
551             suggestion.join(" | "),
552             Applicability::MachineApplicable,
553         )
554     }
555 }
556
557 // If the block contains only a `panic!` macro (as expression or statement)
558 fn is_panic_block(block: &Block) -> bool {
559     match (&block.expr, block.stmts.len(), block.stmts.first()) {
560         (&Some(ref exp), 0, _) => {
561             is_expn_of(exp.span, "panic").is_some() && is_expn_of(exp.span, "unreachable").is_none()
562         },
563         (&None, 1, Some(stmt)) => {
564             is_expn_of(stmt.span, "panic").is_some() && is_expn_of(stmt.span, "unreachable").is_none()
565         },
566         _ => false,
567     }
568 }
569
570 fn check_match_ref_pats(cx: &LateContext<'_, '_>, ex: &Expr, arms: &[Arm], expr: &Expr) {
571     if has_only_ref_pats(arms) {
572         let mut suggs = Vec::new();
573         let (title, msg) = if let ExprKind::AddrOf(Mutability::MutImmutable, ref inner) = ex.node {
574             let span = ex.span.source_callsite();
575             suggs.push((span, Sugg::hir_with_macro_callsite(cx, inner, "..").to_string()));
576             (
577                 "you don't need to add `&` to both the expression and the patterns",
578                 "try",
579             )
580         } else {
581             let span = ex.span.source_callsite();
582             suggs.push((span, Sugg::hir_with_macro_callsite(cx, ex, "..").deref().to_string()));
583             (
584                 "you don't need to add `&` to all patterns",
585                 "instead of prefixing all patterns with `&`, you can dereference the expression",
586             )
587         };
588
589         suggs.extend(arms.iter().filter_map(|a| {
590             if let PatKind::Ref(ref refp, _) = a.pat.node {
591                 Some((a.pat.span, snippet(cx, refp.span, "..").to_string()))
592             } else {
593                 None
594             }
595         }));
596
597         span_lint_and_then(cx, MATCH_REF_PATS, expr.span, title, |db| {
598             if !expr.span.from_expansion() {
599                 multispan_sugg(db, msg.to_owned(), suggs);
600             }
601         });
602     }
603 }
604
605 fn check_match_as_ref(cx: &LateContext<'_, '_>, ex: &Expr, arms: &[Arm], expr: &Expr) {
606     if arms.len() == 2 && arms[0].guard.is_none() && arms[1].guard.is_none() {
607         let arm_ref: Option<BindingAnnotation> = if is_none_arm(&arms[0]) {
608             is_ref_some_arm(&arms[1])
609         } else if is_none_arm(&arms[1]) {
610             is_ref_some_arm(&arms[0])
611         } else {
612             None
613         };
614         if let Some(rb) = arm_ref {
615             let suggestion = if rb == BindingAnnotation::Ref {
616                 "as_ref"
617             } else {
618                 "as_mut"
619             };
620
621             let output_ty = cx.tables.expr_ty(expr);
622             let input_ty = cx.tables.expr_ty(ex);
623
624             let cast = if_chain! {
625                 if let ty::Adt(_, substs) = input_ty.sty;
626                 let input_ty = substs.type_at(0);
627                 if let ty::Adt(_, substs) = output_ty.sty;
628                 let output_ty = substs.type_at(0);
629                 if let ty::Ref(_, output_ty, _) = output_ty.sty;
630                 if input_ty != output_ty;
631                 then {
632                     ".map(|x| x as _)"
633                 } else {
634                     ""
635                 }
636             };
637
638             let mut applicability = Applicability::MachineApplicable;
639             span_lint_and_sugg(
640                 cx,
641                 MATCH_AS_REF,
642                 expr.span,
643                 &format!("use {}() instead", suggestion),
644                 "try this",
645                 format!(
646                     "{}.{}(){}",
647                     snippet_with_applicability(cx, ex.span, "_", &mut applicability),
648                     suggestion,
649                     cast,
650                 ),
651                 applicability,
652             )
653         }
654     }
655 }
656
657 /// Gets all arms that are unbounded `PatRange`s.
658 fn all_ranges<'a, 'tcx>(cx: &LateContext<'a, 'tcx>, arms: &'tcx [Arm]) -> Vec<SpannedRange<Constant>> {
659     arms.iter()
660         .flat_map(|arm| {
661             if let Arm {
662                 ref pat, guard: None, ..
663             } = *arm
664             {
665                 if let PatKind::Range(ref lhs, ref rhs, ref range_end) = pat.node {
666                     let lhs = constant(cx, cx.tables, lhs)?.0;
667                     let rhs = constant(cx, cx.tables, rhs)?.0;
668                     let rhs = match *range_end {
669                         RangeEnd::Included => Bound::Included(rhs),
670                         RangeEnd::Excluded => Bound::Excluded(rhs),
671                     };
672                     return Some(SpannedRange {
673                         span: pat.span,
674                         node: (lhs, rhs),
675                     });
676                 }
677
678                 if let PatKind::Lit(ref value) = pat.node {
679                     let value = constant(cx, cx.tables, value)?.0;
680                     return Some(SpannedRange {
681                         span: pat.span,
682                         node: (value.clone(), Bound::Included(value)),
683                     });
684                 }
685             }
686             None
687         })
688         .collect()
689 }
690
691 #[derive(Debug, Eq, PartialEq)]
692 pub struct SpannedRange<T> {
693     pub span: Span,
694     pub node: (T, Bound<T>),
695 }
696
697 type TypedRanges = Vec<SpannedRange<u128>>;
698
699 /// Gets all `Int` ranges or all `Uint` ranges. Mixed types are an error anyway
700 /// and other types than
701 /// `Uint` and `Int` probably don't make sense.
702 fn type_ranges(ranges: &[SpannedRange<Constant>]) -> TypedRanges {
703     ranges
704         .iter()
705         .filter_map(|range| match range.node {
706             (Constant::Int(start), Bound::Included(Constant::Int(end))) => Some(SpannedRange {
707                 span: range.span,
708                 node: (start, Bound::Included(end)),
709             }),
710             (Constant::Int(start), Bound::Excluded(Constant::Int(end))) => Some(SpannedRange {
711                 span: range.span,
712                 node: (start, Bound::Excluded(end)),
713             }),
714             (Constant::Int(start), Bound::Unbounded) => Some(SpannedRange {
715                 span: range.span,
716                 node: (start, Bound::Unbounded),
717             }),
718             _ => None,
719         })
720         .collect()
721 }
722
723 fn is_unit_expr(expr: &Expr) -> bool {
724     match expr.node {
725         ExprKind::Tup(ref v) if v.is_empty() => true,
726         ExprKind::Block(ref b, _) if b.stmts.is_empty() && b.expr.is_none() => true,
727         _ => false,
728     }
729 }
730
731 // Checks if arm has the form `None => None`
732 fn is_none_arm(arm: &Arm) -> bool {
733     match arm.pat.node {
734         PatKind::Path(ref path) if match_qpath(path, &paths::OPTION_NONE) => true,
735         _ => false,
736     }
737 }
738
739 // Checks if arm has the form `Some(ref v) => Some(v)` (checks for `ref` and `ref mut`)
740 fn is_ref_some_arm(arm: &Arm) -> Option<BindingAnnotation> {
741     if_chain! {
742         if let PatKind::TupleStruct(ref path, ref pats, _) = arm.pat.node;
743         if pats.len() == 1 && match_qpath(path, &paths::OPTION_SOME);
744         if let PatKind::Binding(rb, .., ident, _) = pats[0].node;
745         if rb == BindingAnnotation::Ref || rb == BindingAnnotation::RefMut;
746         if let ExprKind::Call(ref e, ref args) = remove_blocks(&arm.body).node;
747         if let ExprKind::Path(ref some_path) = e.node;
748         if match_qpath(some_path, &paths::OPTION_SOME) && args.len() == 1;
749         if let ExprKind::Path(ref qpath) = args[0].node;
750         if let &QPath::Resolved(_, ref path2) = qpath;
751         if path2.segments.len() == 1 && ident.name == path2.segments[0].ident.name;
752         then {
753             return Some(rb)
754         }
755     }
756     None
757 }
758
759 fn has_only_ref_pats(arms: &[Arm]) -> bool {
760     let mapped = arms
761         .iter()
762         .map(|a| {
763             match a.pat.node {
764                 PatKind::Ref(..) => Some(true), // &-patterns
765                 PatKind::Wild => Some(false),   // an "anything" wildcard is also fine
766                 _ => None,                      // any other pattern is not fine
767             }
768         })
769         .collect::<Option<Vec<bool>>>();
770     // look for Some(v) where there's at least one true element
771     mapped.map_or(false, |v| v.iter().any(|el| *el))
772 }
773
774 pub fn overlapping<T>(ranges: &[SpannedRange<T>]) -> Option<(&SpannedRange<T>, &SpannedRange<T>)>
775 where
776     T: Copy + Ord,
777 {
778     #[derive(Copy, Clone, Debug, Eq, PartialEq)]
779     enum Kind<'a, T> {
780         Start(T, &'a SpannedRange<T>),
781         End(Bound<T>, &'a SpannedRange<T>),
782     }
783
784     impl<'a, T: Copy> Kind<'a, T> {
785         fn range(&self) -> &'a SpannedRange<T> {
786             match *self {
787                 Kind::Start(_, r) | Kind::End(_, r) => r,
788             }
789         }
790
791         fn value(self) -> Bound<T> {
792             match self {
793                 Kind::Start(t, _) => Bound::Included(t),
794                 Kind::End(t, _) => t,
795             }
796         }
797     }
798
799     impl<'a, T: Copy + Ord> PartialOrd for Kind<'a, T> {
800         fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
801             Some(self.cmp(other))
802         }
803     }
804
805     impl<'a, T: Copy + Ord> Ord for Kind<'a, T> {
806         fn cmp(&self, other: &Self) -> Ordering {
807             match (self.value(), other.value()) {
808                 (Bound::Included(a), Bound::Included(b)) | (Bound::Excluded(a), Bound::Excluded(b)) => a.cmp(&b),
809                 // Range patterns cannot be unbounded (yet)
810                 (Bound::Unbounded, _) | (_, Bound::Unbounded) => unimplemented!(),
811                 (Bound::Included(a), Bound::Excluded(b)) => match a.cmp(&b) {
812                     Ordering::Equal => Ordering::Greater,
813                     other => other,
814                 },
815                 (Bound::Excluded(a), Bound::Included(b)) => match a.cmp(&b) {
816                     Ordering::Equal => Ordering::Less,
817                     other => other,
818                 },
819             }
820         }
821     }
822
823     let mut values = Vec::with_capacity(2 * ranges.len());
824
825     for r in ranges {
826         values.push(Kind::Start(r.node.0, r));
827         values.push(Kind::End(r.node.1, r));
828     }
829
830     values.sort();
831
832     for (a, b) in values.iter().zip(values.iter().skip(1)) {
833         match (a, b) {
834             (&Kind::Start(_, ra), &Kind::End(_, rb)) => {
835                 if ra.node != rb.node {
836                     return Some((ra, rb));
837                 }
838             },
839             (&Kind::End(a, _), &Kind::Start(b, _)) if a != Bound::Included(b) => (),
840             _ => return Some((a.range(), b.range())),
841         }
842     }
843
844     None
845 }