3 use rustc::middle::const_val::ConstVal;
5 use rustc_const_eval::EvalHint::ExprTypeChecked;
6 use rustc_const_eval::eval_const_expr_partial;
7 use rustc_const_math::ConstInt;
8 use std::cmp::Ordering;
9 use syntax::ast::LitKind;
10 use syntax::codemap::Span;
12 use utils::{match_type, snippet, span_note_and_lint, span_lint_and_then, in_external_macro, expr_block};
13 use utils::sugg::Sugg;
15 /// **What it does:** Checks for matches with a single arm where an `if let`
16 /// will usually suffice.
18 /// **Why is this bad?** Just readability – `if let` nests less than a `match`.
20 /// **Known problems:** None.
25 /// Some(ref foo) => bar(foo),
32 "a match statement with a single nontrivial arm (i.e, where the other arm \
33 is `_ => {}`) instead of `if let`"
36 /// **What it does:** Checks for matches with a two arms where an `if let` will
39 /// **Why is this bad?** Just readability – `if let` nests less than a `match`.
41 /// **Known problems:** Personal style preferences may differ.
46 /// Some(ref foo) => bar(foo),
47 /// _ => bar(other_ref),
51 pub SINGLE_MATCH_ELSE,
53 "a match statement with a two arms where the second arm's pattern is a wildcard \
57 /// **What it does:** Checks for matches where all arms match a reference,
58 /// suggesting to remove the reference and deref the matched expression
59 /// instead. It also checks for `if let &foo = bar` blocks.
61 /// **Why is this bad?** It just makes the code less readable. That reference
62 /// destructuring adds nothing to the code.
64 /// **Known problems:** None.
69 /// &A(ref y) => foo(y),
77 "a match or `if let` with all arms prefixed with `&` instead of deref-ing the match expression"
80 /// **What it does:** Checks for matches where match expression is a `bool`. It
81 /// suggests to replace the expression with an `if...else` block.
83 /// **Why is this bad?** It makes the code less readable.
85 /// **Known problems:** None.
89 /// let condition: bool = true;
98 "a match on a boolean expression instead of an `if..else` block"
101 /// **What it does:** Checks for overlapping match arms.
103 /// **Why is this bad?** It is likely to be an error and if not, makes the code
106 /// **Known problems:** None.
112 /// 1 ... 10 => println!("1 ... 10"),
113 /// 5 ... 15 => println!("5 ... 15"),
118 pub MATCH_OVERLAPPING_ARM,
120 "a match with overlapping arms"
123 #[allow(missing_copy_implementations)]
124 pub struct MatchPass;
126 impl LintPass for MatchPass {
127 fn get_lints(&self) -> LintArray {
128 lint_array!(SINGLE_MATCH,
132 MATCH_OVERLAPPING_ARM)
136 impl<'a, 'tcx> LateLintPass<'a, 'tcx> for MatchPass {
137 fn check_expr(&mut self, cx: &LateContext<'a, 'tcx>, expr: &'tcx Expr) {
138 if in_external_macro(cx, expr.span) {
141 if let ExprMatch(ref ex, ref arms, MatchSource::Normal) = expr.node {
142 check_single_match(cx, ex, arms, expr);
143 check_match_bool(cx, ex, arms, expr);
144 check_overlapping_arms(cx, ex, arms);
146 if let ExprMatch(ref ex, ref arms, source) = expr.node {
147 check_match_ref_pats(cx, ex, arms, source, expr);
152 #[cfg_attr(rustfmt, rustfmt_skip)]
153 fn check_single_match(cx: &LateContext, ex: &Expr, arms: &[Arm], expr: &Expr) {
154 if arms.len() == 2 &&
155 arms[0].pats.len() == 1 && arms[0].guard.is_none() &&
156 arms[1].pats.len() == 1 && arms[1].guard.is_none() {
157 let els = if is_unit_expr(&arms[1].body) {
159 } else if let ExprBlock(_) = arms[1].body.node {
160 // matches with blocks that contain statements are prettier as `if let + else`
163 // allow match arms with just expressions
166 let ty = cx.tcx.tables().expr_ty(ex);
167 if ty.sty != ty::TyBool || cx.current_level(MATCH_BOOL) == Allow {
168 check_single_match_single_pattern(cx, ex, arms, expr, els);
169 check_single_match_opt_like(cx, ex, arms, expr, ty, els);
174 fn check_single_match_single_pattern(cx: &LateContext, ex: &Expr, arms: &[Arm], expr: &Expr, els: Option<&Expr>) {
175 if arms[1].pats[0].node == PatKind::Wild {
176 let lint = if els.is_some() {
181 let els_str = els.map_or(String::new(), |els| format!(" else {}", expr_block(cx, els, None, "..")));
182 span_lint_and_then(cx,
185 "you seem to be trying to use match for destructuring a single pattern. \
186 Consider using `if let`",
188 db.span_suggestion(expr.span,
190 format!("if let {} = {} {}{}",
191 snippet(cx, arms[0].pats[0].span, ".."),
192 snippet(cx, ex.span, ".."),
193 expr_block(cx, &arms[0].body, None, ".."),
199 fn check_single_match_opt_like(
207 // list of candidate Enums we know will never get any more members
208 let candidates = &[(&paths::COW, "Borrowed"),
209 (&paths::COW, "Cow::Borrowed"),
210 (&paths::COW, "Cow::Owned"),
211 (&paths::COW, "Owned"),
212 (&paths::OPTION, "None"),
213 (&paths::RESULT, "Err"),
214 (&paths::RESULT, "Ok")];
216 let path = match arms[1].pats[0].node {
217 PatKind::TupleStruct(ref path, ref inner, _) => {
218 // contains any non wildcard patterns? e.g. Err(err)
219 if inner.iter().any(|pat| pat.node != PatKind::Wild) {
222 print::to_string(print::NO_ANN, |s| s.print_qpath(path, false))
224 PatKind::Binding(BindByValue(MutImmutable), _, ident, None) => ident.node.to_string(),
225 PatKind::Path(ref path) => print::to_string(print::NO_ANN, |s| s.print_qpath(path, false)),
229 for &(ty_path, pat_path) in candidates {
230 if &path == pat_path && match_type(cx, ty, ty_path) {
231 let lint = if els.is_some() {
236 let els_str = els.map_or(String::new(), |els| format!(" else {}", expr_block(cx, els, None, "..")));
237 span_lint_and_then(cx,
240 "you seem to be trying to use match for destructuring a single pattern. Consider \
243 db.span_suggestion(expr.span,
245 format!("if let {} = {} {}{}",
246 snippet(cx, arms[0].pats[0].span, ".."),
247 snippet(cx, ex.span, ".."),
248 expr_block(cx, &arms[0].body, None, ".."),
255 fn check_match_bool(cx: &LateContext, ex: &Expr, arms: &[Arm], expr: &Expr) {
256 // type of expression == bool
257 if cx.tcx.tables().expr_ty(ex).sty == ty::TyBool {
258 span_lint_and_then(cx,
261 "you seem to be trying to match on a boolean expression",
263 if arms.len() == 2 && arms[0].pats.len() == 1 {
265 let exprs = if let PatKind::Lit(ref arm_bool) = arms[0].pats[0].node {
266 if let ExprLit(ref lit) = arm_bool.node {
268 LitKind::Bool(true) => Some((&*arms[0].body, &*arms[1].body)),
269 LitKind::Bool(false) => Some((&*arms[1].body, &*arms[0].body)),
279 if let Some((true_expr, false_expr)) = exprs {
280 let sugg = match (is_unit_expr(true_expr), is_unit_expr(false_expr)) {
282 Some(format!("if {} {} else {}",
283 snippet(cx, ex.span, "b"),
284 expr_block(cx, true_expr, None, ".."),
285 expr_block(cx, false_expr, None, "..")))
288 Some(format!("if {} {}", snippet(cx, ex.span, "b"), expr_block(cx, true_expr, None, "..")))
291 let test = Sugg::hir(cx, ex, "..");
292 Some(format!("if {} {}", !test, expr_block(cx, false_expr, None, "..")))
294 (true, true) => None,
297 if let Some(sugg) = sugg {
298 db.span_suggestion(expr.span, "consider using an if/else expression", sugg);
307 fn check_overlapping_arms(cx: &LateContext, ex: &Expr, arms: &[Arm]) {
308 if arms.len() >= 2 && cx.tcx.tables().expr_ty(ex).is_integral() {
309 let ranges = all_ranges(cx, arms);
310 let type_ranges = type_ranges(&ranges);
311 if !type_ranges.is_empty() {
312 if let Some((start, end)) = overlapping(&type_ranges) {
313 span_note_and_lint(cx,
314 MATCH_OVERLAPPING_ARM,
316 "some ranges overlap",
318 "overlaps with this");
324 fn check_match_ref_pats(cx: &LateContext, ex: &Expr, arms: &[Arm], source: MatchSource, expr: &Expr) {
325 if has_only_ref_pats(arms) {
326 if let ExprAddrOf(Mutability::MutImmutable, ref inner) = ex.node {
327 span_lint_and_then(cx,
330 "you don't need to add `&` to both the expression and the patterns",
332 let inner = Sugg::hir(cx, inner, "..");
333 let template = match_template(expr.span, source, inner);
334 db.span_suggestion(expr.span, "try", template);
337 span_lint_and_then(cx,
340 "you don't need to add `&` to all patterns",
342 let ex = Sugg::hir(cx, ex, "..");
343 let template = match_template(expr.span, source, ex.deref());
344 db.span_suggestion(expr.span,
345 "instead of prefixing all patterns with `&`, you can dereference the expression",
352 /// Get all arms that are unbounded `PatRange`s.
353 fn all_ranges(cx: &LateContext, arms: &[Arm]) -> Vec<SpannedRange<ConstVal>> {
356 if let Arm { ref pats, guard: None, .. } = *arm {
363 let PatKind::Range(ref lhs, ref rhs) = pat.node,
364 let Ok(lhs) = eval_const_expr_partial(cx.tcx, lhs, ExprTypeChecked, None),
365 let Ok(rhs) = eval_const_expr_partial(cx.tcx, rhs, ExprTypeChecked, None)
367 return Some(SpannedRange { span: pat.span, node: (lhs, rhs) });
371 let PatKind::Lit(ref value) = pat.node,
372 let Ok(value) = eval_const_expr_partial(cx.tcx, value, ExprTypeChecked, None)
374 return Some(SpannedRange { span: pat.span, node: (value.clone(), value) });
383 #[derive(Debug, Eq, PartialEq)]
384 pub struct SpannedRange<T> {
389 type TypedRanges = Vec<SpannedRange<ConstInt>>;
391 /// Get all `Int` ranges or all `Uint` ranges. Mixed types are an error anyway and other types than
392 /// `Uint` and `Int` probably don't make sense.
393 fn type_ranges(ranges: &[SpannedRange<ConstVal>]) -> TypedRanges {
395 .filter_map(|range| {
396 if let (ConstVal::Integral(start), ConstVal::Integral(end)) = range.node {
408 fn is_unit_expr(expr: &Expr) -> bool {
410 ExprTup(ref v) if v.is_empty() => true,
411 ExprBlock(ref b) if b.stmts.is_empty() && b.expr.is_none() => true,
416 fn has_only_ref_pats(arms: &[Arm]) -> bool {
417 let mapped = arms.iter()
418 .flat_map(|a| &a.pats)
421 PatKind::Ref(..) => Some(true), // &-patterns
422 PatKind::Wild => Some(false), // an "anything" wildcard is also fine
423 _ => None, // any other pattern is not fine
426 .collect::<Option<Vec<bool>>>();
427 // look for Some(v) where there's at least one true element
428 mapped.map_or(false, |v| v.iter().any(|el| *el))
431 fn match_template(span: Span, source: MatchSource, expr: Sugg) -> String {
433 MatchSource::Normal => format!("match {} {{ .. }}", expr),
434 MatchSource::IfLetDesugar { .. } => format!("if let .. = {} {{ .. }}", expr),
435 MatchSource::WhileLetDesugar => format!("while let .. = {} {{ .. }}", expr),
436 MatchSource::ForLoopDesugar => span_bug!(span, "for loop desugared to match with &-patterns!"),
437 MatchSource::TryDesugar => span_bug!(span, "`?` operator desugared to match with &-patterns!"),
441 pub fn overlapping<T>(ranges: &[SpannedRange<T>]) -> Option<(&SpannedRange<T>, &SpannedRange<T>)>
444 #[derive(Copy, Clone, Debug, Eq, PartialEq)]
445 enum Kind<'a, T: 'a> {
446 Start(T, &'a SpannedRange<T>),
447 End(T, &'a SpannedRange<T>),
450 impl<'a, T: Copy> Kind<'a, T> {
451 fn range(&self) -> &'a SpannedRange<T> {
454 Kind::End(_, r) => r,
458 fn value(self) -> T {
461 Kind::End(t, _) => t,
466 impl<'a, T: Copy + Ord> PartialOrd for Kind<'a, T> {
467 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
468 Some(self.cmp(other))
472 impl<'a, T: Copy + Ord> Ord for Kind<'a, T> {
473 fn cmp(&self, other: &Self) -> Ordering {
474 self.value().cmp(&other.value())
478 let mut values = Vec::with_capacity(2 * ranges.len());
481 values.push(Kind::Start(r.node.0, r));
482 values.push(Kind::End(r.node.1, r));
487 for (a, b) in values.iter().zip(values.iter().skip(1)) {
489 (&Kind::Start(_, ra), &Kind::End(_, rb)) => {
490 if ra.node != rb.node {
491 return Some((ra, rb));
494 (&Kind::End(a, _), &Kind::Start(b, _)) if a != b => (),
495 _ => return Some((a.range(), b.range())),