2015-08-21 12:32:21 -05:00
use rustc ::lint ::* ;
2015-09-03 09:42:17 -05:00
use rustc_front ::hir ::* ;
2015-10-20 12:18:48 -05:00
use rustc ::middle ::ty ;
2015-10-21 01:24:56 -05:00
use syntax ::ast ::Lit_ ::LitBool ;
2015-11-24 22:57:50 -06:00
use syntax ::codemap ::Span ;
2015-08-21 12:32:21 -05:00
2015-08-29 04:41:06 -05:00
use utils ::{ snippet , span_lint , span_help_and_lint , in_external_macro , expr_block } ;
2015-08-21 12:32:21 -05:00
2015-12-10 18:22:27 -06:00
/// **What it does:** This lint checks for matches with a single arm where an `if let` will usually suffice. It is `Warn` by default.
///
/// **Why is this bad?** Just readability – `if let` nests less than a `match`.
///
/// **Known problems:** None
///
/// **Example:**
/// ```
/// match x {
/// Some(ref foo) -> bar(foo),
/// _ => ()
/// }
/// ```
2015-08-21 12:32:21 -05:00
declare_lint! ( pub SINGLE_MATCH , Warn ,
" a match statement with a single nontrivial arm (i.e, where the other arm \
is ` _ = > { } ` ) is used ; recommends ` if let ` instead " );
2015-12-10 18:22:27 -06:00
/// **What it does:** This lint checks for matches where all arms match a reference, suggesting to remove the reference and deref the matched expression instead. It is `Warn` by default.
///
/// **Why is this bad?** It just makes the code less readable. That reference destructuring adds nothing to the code.
///
/// **Known problems:** None
///
/// **Example:**
///
/// ```
/// match x {
/// &A(ref y) => foo(y),
/// &B => bar(),
/// _ => frob(&x),
/// }
/// ```
2015-08-21 12:49:00 -05:00
declare_lint! ( pub MATCH_REF_PATS , Warn ,
" a match has all arms prefixed with `&`; the match expression can be \
dereferenced instead " );
2015-12-10 18:22:27 -06:00
/// **What it does:** This lint checks for matches where match expression is a `bool`. It suggests to replace the expression with an `if...else` block. It is `Warn` by default.
///
/// **Why is this bad?** It makes the code less readable.
///
/// **Known problems:** None
///
/// **Example:**
///
/// ```
/// let condition: bool = true;
/// match condition {
/// true => foo(),
/// false => bar(),
/// }
/// ```
2015-10-20 12:18:48 -05:00
declare_lint! ( pub MATCH_BOOL , Warn ,
" a match on boolean expression; recommends `if..else` block instead " ) ;
2015-08-21 12:32:21 -05:00
#[ allow(missing_copy_implementations) ]
pub struct MatchPass ;
impl LintPass for MatchPass {
fn get_lints ( & self ) -> LintArray {
2015-10-20 12:18:48 -05:00
lint_array! ( SINGLE_MATCH , MATCH_REF_PATS , MATCH_BOOL )
2015-08-21 12:32:21 -05:00
}
2015-09-18 21:53:04 -05:00
}
2015-08-21 12:32:21 -05:00
2015-09-18 21:53:04 -05:00
impl LateLintPass for MatchPass {
fn check_expr ( & mut self , cx : & LateContext , expr : & Expr ) {
2015-11-24 11:47:17 -06:00
if in_external_macro ( cx , expr . span ) { return ; }
2015-09-03 09:42:17 -05:00
if let ExprMatch ( ref ex , ref arms , MatchSource ::Normal ) = expr . node {
2015-08-21 12:49:00 -05:00
// check preconditions for SINGLE_MATCH
// only two arms
2015-08-21 12:32:21 -05:00
if arms . len ( ) = = 2 & &
// both of the arms have a single pattern and no guard
arms [ 0 ] . pats . len ( ) = = 1 & & arms [ 0 ] . guard . is_none ( ) & &
arms [ 1 ] . pats . len ( ) = = 1 & & arms [ 1 ] . guard . is_none ( ) & &
// and the second pattern is a `_` wildcard: this is not strictly necessary,
// since the exhaustiveness check will ensure the last one is a catch-all,
// but in some cases, an explicit match is preferred to catch situations
// when an enum is extended, so we don't consider these cases
2015-11-04 20:50:28 -06:00
arms [ 1 ] . pats [ 0 ] . node = = PatWild & &
2015-10-28 22:26:48 -05:00
// we don't want any content in the second arm (unit or empty block)
is_unit_expr ( & arms [ 1 ] . body ) & &
// finally, MATCH_BOOL doesn't apply here
( cx . tcx . expr_ty ( ex ) . sty ! = ty ::TyBool | | cx . current_level ( MATCH_BOOL ) = = Allow )
2015-08-21 12:32:21 -05:00
{
span_help_and_lint ( cx , SINGLE_MATCH , expr . span ,
2015-09-02 09:46:12 -05:00
" you seem to be trying to use match for destructuring a \
single pattern . Consider using ` if let ` " ,
& format! ( " try \n if let {} = {} {} " ,
snippet ( cx , arms [ 0 ] . pats [ 0 ] . span , " .. " ) ,
snippet ( cx , ex . span , " .. " ) ,
2015-09-27 02:39:42 -05:00
expr_block ( cx , & arms [ 0 ] . body , None , " .. " ) ) ) ;
2015-08-21 12:32:21 -05:00
}
2015-08-21 12:49:00 -05:00
2015-10-20 12:18:48 -05:00
// check preconditions for MATCH_BOOL
// type of expression == bool
2015-10-20 13:26:54 -05:00
if cx . tcx . expr_ty ( ex ) . sty = = ty ::TyBool {
2015-10-21 01:24:56 -05:00
if arms . len ( ) = = 2 & & arms [ 0 ] . pats . len ( ) = = 1 { // no guards
let exprs = if let PatLit ( ref arm_bool ) = arms [ 0 ] . pats [ 0 ] . node {
if let ExprLit ( ref lit ) = arm_bool . node {
2015-11-18 05:35:18 -06:00
if let LitBool ( val ) = lit . node {
2015-10-21 01:24:56 -05:00
if val {
Some ( ( & * arms [ 0 ] . body , & * arms [ 1 ] . body ) )
} else {
Some ( ( & * arms [ 1 ] . body , & * arms [ 0 ] . body ) )
}
} else { None }
} else { None }
} else { None } ;
if let Some ( ( ref true_expr , ref false_expr ) ) = exprs {
if ! is_unit_expr ( true_expr ) {
if ! is_unit_expr ( false_expr ) {
span_help_and_lint ( cx , MATCH_BOOL , expr . span ,
" you seem to be trying to match on a boolean expression. \
Consider using an if .. else block :" ,
& format! ( " try \n if {} {} else {} " ,
snippet ( cx , ex . span , " b " ) ,
expr_block ( cx , true_expr , None , " .. " ) ,
expr_block ( cx , false_expr , None , " .. " ) ) ) ;
} else {
span_help_and_lint ( cx , MATCH_BOOL , expr . span ,
" you seem to be trying to match on a boolean expression. \
Consider using an if .. else block :" ,
& format! ( " try \n if {} {} " ,
snippet ( cx , ex . span , " b " ) ,
expr_block ( cx , true_expr , None , " .. " ) ) ) ;
}
} else if ! is_unit_expr ( false_expr ) {
span_help_and_lint ( cx , MATCH_BOOL , expr . span ,
" you seem to be trying to match on a boolean expression. \
Consider using an if .. else block :" ,
& format! ( " try \n if ! {} {} " ,
snippet ( cx , ex . span , " b " ) ,
expr_block ( cx , false_expr , None , " .. " ) ) ) ;
} else {
span_lint ( cx , MATCH_BOOL , expr . span ,
2015-10-20 12:18:48 -05:00
" you seem to be trying to match on a boolean expression. \
2015-10-20 13:26:54 -05:00
Consider using an if .. else block " );
2015-10-21 01:24:56 -05:00
}
} else {
span_lint ( cx , MATCH_BOOL , expr . span ,
" you seem to be trying to match on a boolean expression. \
Consider using an if .. else block " );
}
} else {
span_lint ( cx , MATCH_BOOL , expr . span ,
" you seem to be trying to match on a boolean expression. \
Consider using an if .. else block " );
}
2015-10-20 12:18:48 -05:00
}
2015-08-21 12:32:21 -05:00
}
2015-11-24 11:47:17 -06:00
if let ExprMatch ( ref ex , ref arms , source ) = expr . node {
// check preconditions for MATCH_REF_PATS
if has_only_ref_pats ( arms ) {
if let ExprAddrOf ( Mutability ::MutImmutable , ref inner ) = ex . node {
2015-11-24 22:57:50 -06:00
let template = match_template ( cx , expr . span , source , " " , inner ) ;
2015-11-24 11:47:17 -06:00
span_lint ( cx , MATCH_REF_PATS , expr . span , & format! (
" you don't need to add `&` to both the expression \
and the patterns : use ` { } ` " , template));
} else {
2015-11-24 22:57:50 -06:00
let template = match_template ( cx , expr . span , source , " * " , ex ) ;
2015-11-24 11:47:17 -06:00
span_lint ( cx , MATCH_REF_PATS , expr . span , & format! (
" instead of prefixing all patterns with `&`, you can dereference the \
expression : ` { } ` " , template));
}
}
}
2015-08-21 12:32:21 -05:00
}
}
fn is_unit_expr ( expr : & Expr ) -> bool {
match expr . node {
ExprTup ( ref v ) if v . is_empty ( ) = > true ,
ExprBlock ( ref b ) if b . stmts . is_empty ( ) & & b . expr . is_none ( ) = > true ,
_ = > false ,
}
}
2015-08-21 12:49:00 -05:00
fn has_only_ref_pats ( arms : & [ Arm ] ) -> bool {
2015-09-17 00:24:11 -05:00
let mapped = arms . iter ( ) . flat_map ( | a | & a . pats ) . map ( | p | match p . node {
PatRegion ( .. ) = > Some ( true ) , // &-patterns
2015-11-30 11:46:28 -06:00
PatWild = > Some ( false ) , // an "anything" wildcard is also fine
2015-09-17 00:24:11 -05:00
_ = > None , // any other pattern is not fine
} ) . collect ::< Option < Vec < bool > > > ( ) ;
// look for Some(v) where there's at least one true element
mapped . map_or ( false , | v | v . iter ( ) . any ( | el | * el ) )
2015-08-21 12:49:00 -05:00
}
2015-11-24 11:47:17 -06:00
2015-11-24 22:57:50 -06:00
fn match_template ( cx : & LateContext ,
span : Span ,
source : MatchSource ,
op : & str ,
expr : & Expr ) -> String {
let expr_snippet = snippet ( cx , expr . span , " .. " ) ;
2015-11-24 11:47:17 -06:00
match source {
MatchSource ::Normal = > {
2015-11-24 22:57:50 -06:00
format! ( " match {} {} {{ ... " , op , expr_snippet )
2015-11-24 11:47:17 -06:00
}
MatchSource ::IfLetDesugar { .. } = > {
2015-11-24 22:57:50 -06:00
format! ( " if let ... = {} {} {{ " , op , expr_snippet )
2015-11-24 11:47:17 -06:00
}
MatchSource ::WhileLetDesugar = > {
2015-11-24 22:57:50 -06:00
format! ( " while let ... = {} {} {{ " , op , expr_snippet )
2015-11-24 11:47:17 -06:00
}
MatchSource ::ForLoopDesugar = > {
2015-11-24 22:57:50 -06:00
cx . sess ( ) . span_bug ( span , " for loop desugared to match with &-patterns! " )
2015-11-24 11:47:17 -06:00
}
}
}