2015-08-21 17:32:21 +00:00
use rustc ::lint ::* ;
2016-03-31 15:05:43 +00:00
use rustc ::middle ::const_val ::ConstVal ;
2016-03-27 18:59:02 +00:00
use rustc ::ty ;
2016-03-31 15:05:43 +00:00
use rustc_const_eval ::EvalHint ::ExprTypeChecked ;
use rustc_const_eval ::eval_const_expr_partial ;
use rustc_const_math ::ConstInt ;
2015-12-23 00:14:10 +00:00
use rustc_front ::hir ::* ;
use std ::cmp ::Ordering ;
2016-02-12 17:35:44 +00:00
use syntax ::ast ::LitKind ;
2015-11-25 04:57:50 +00:00
use syntax ::codemap ::Span ;
2016-01-13 00:19:27 +00:00
use utils ::{ COW_PATH , OPTION_PATH , RESULT_PATH } ;
2016-03-09 15:22:31 +00:00
use utils ::{ match_type , snippet , span_note_and_lint , span_lint_and_then , in_external_macro , expr_block } ;
2015-08-21 17:32:21 +00:00
2016-02-05 23:41:54 +00:00
/// **What it does:** This lint checks for matches with a single arm where an `if let` will usually suffice.
2015-12-11 00:22:27 +00:00
///
/// **Why is this bad?** Just readability – `if let` nests less than a `match`.
///
/// **Known problems:** None
///
/// **Example:**
/// ```
/// match x {
/// Some(ref foo) -> bar(foo),
/// _ => ()
/// }
/// ```
2016-02-05 23:13:29 +00: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-23 00:14:10 +00:00
2016-02-05 23:41:54 +00:00
/// **What it does:** This lint checks for matches with a two arms where an `if let` will usually suffice.
2016-01-24 11:24:16 +00:00
///
/// **Why is this bad?** Just readability – `if let` nests less than a `match`.
///
/// **Known problems:** Personal style preferences may differ
///
/// **Example:**
/// ```
/// match x {
/// Some(ref foo) -> bar(foo),
/// _ => bar(other_ref),
/// }
/// ```
2016-02-05 23:13:29 +00:00
declare_lint! {
pub SINGLE_MATCH_ELSE , Allow ,
" a match statement with a two arms where the second arm's pattern is a wildcard; \
recommends ` if let ` instead "
}
2016-01-24 11:24:16 +00:00
2016-02-05 23:41:54 +00: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 also checks for `if let &foo = bar` blocks.
2015-12-11 00:22:27 +00:00
///
/// **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),
/// }
/// ```
2016-02-05 23:13:29 +00:00
declare_lint! {
pub MATCH_REF_PATS , Warn ,
" a match or `if let` has all arms prefixed with `&`; the match expression can be \
dereferenced instead "
}
2015-12-23 00:14:10 +00:00
2016-02-05 23:41:54 +00: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.
2015-12-11 00:22:27 +00:00
///
/// **Why is this bad?** It makes the code less readable.
///
/// **Known problems:** None
///
/// **Example:**
///
/// ```
/// let condition: bool = true;
/// match condition {
/// true => foo(),
/// false => bar(),
/// }
/// ```
2016-02-05 23:13:29 +00:00
declare_lint! {
pub MATCH_BOOL , Warn ,
" a match on boolean expression; recommends `if..else` block instead "
}
2015-08-21 17:32:21 +00:00
2016-02-05 23:41:54 +00:00
/// **What it does:** This lint checks for overlapping match arms.
2015-12-23 00:14:10 +00:00
///
/// **Why is this bad?** It is likely to be an error and if not, makes the code less obvious.
///
/// **Known problems:** None
///
/// **Example:**
///
/// ```
/// let x = 5;
/// match x {
/// 1 ... 10 => println!("1 ... 10"),
/// 5 ... 15 => println!("5 ... 15"),
/// _ => (),
/// }
/// ```
2016-02-05 23:13:29 +00:00
declare_lint! {
pub MATCH_OVERLAPPING_ARM , Warn , " a match has overlapping arms "
}
2015-12-23 00:14:10 +00:00
2015-08-21 17:32:21 +00:00
#[ allow(missing_copy_implementations) ]
pub struct MatchPass ;
impl LintPass for MatchPass {
fn get_lints ( & self ) -> LintArray {
2016-01-24 11:24:16 +00:00
lint_array! ( SINGLE_MATCH , MATCH_REF_PATS , MATCH_BOOL , SINGLE_MATCH_ELSE )
2015-08-21 17:32:21 +00:00
}
2015-09-19 02:53:04 +00:00
}
2015-08-21 17:32:21 +00:00
2015-09-19 02:53:04 +00:00
impl LateLintPass for MatchPass {
fn check_expr ( & mut self , cx : & LateContext , expr : & Expr ) {
2016-01-04 04:26:12 +00:00
if in_external_macro ( cx , expr . span ) {
return ;
}
2015-09-03 14:42:17 +00:00
if let ExprMatch ( ref ex , ref arms , MatchSource ::Normal ) = expr . node {
2015-12-23 01:19:32 +00:00
check_single_match ( cx , ex , arms , expr ) ;
check_match_bool ( cx , ex , arms , expr ) ;
2015-12-23 10:25:44 +00:00
check_overlapping_arms ( cx , ex , arms ) ;
2015-12-23 01:19:32 +00:00
}
if let ExprMatch ( ref ex , ref arms , source ) = expr . node {
check_match_ref_pats ( cx , ex , arms , source , expr ) ;
}
}
}
2015-08-21 17:49:00 +00:00
2016-02-29 11:19:32 +00:00
#[ cfg_attr(rustfmt, rustfmt_skip) ]
2015-12-23 01:19:32 +00:00
fn check_single_match ( cx : & LateContext , ex : & Expr , arms : & [ Arm ] , expr : & Expr ) {
2016-01-13 00:19:27 +00:00
if arms . len ( ) = = 2 & &
2016-02-29 11:19:32 +00:00
arms [ 0 ] . pats . len ( ) = = 1 & & arms [ 0 ] . guard . is_none ( ) & &
arms [ 1 ] . pats . len ( ) = = 1 & & arms [ 1 ] . guard . is_none ( ) {
2016-02-24 16:38:57 +00:00
let els = if is_unit_expr ( & arms [ 1 ] . body ) {
None
} else if let ExprBlock ( _ ) = arms [ 1 ] . body . node {
// matches with blocks that contain statements are prettier as `if let + else`
Some ( & * arms [ 1 ] . body )
} else {
// allow match arms with just expressions
return ;
} ;
let ty = cx . tcx . expr_ty ( ex ) ;
if ty . sty ! = ty ::TyBool | | cx . current_level ( MATCH_BOOL ) = = Allow {
check_single_match_single_pattern ( cx , ex , arms , expr , els ) ;
check_single_match_opt_like ( cx , ex , arms , expr , ty , els ) ;
}
2016-01-13 00:19:27 +00:00
}
}
2016-01-24 11:24:16 +00:00
fn check_single_match_single_pattern ( cx : & LateContext , ex : & Expr , arms : & [ Arm ] , expr : & Expr , els : Option < & Expr > ) {
2016-02-18 20:16:39 +00:00
if arms [ 1 ] . pats [ 0 ] . node = = PatKind ::Wild {
2016-01-24 11:24:16 +00:00
let lint = if els . is_some ( ) {
SINGLE_MATCH_ELSE
} else {
SINGLE_MATCH
} ;
let els_str = els . map_or ( String ::new ( ) , | els | format! ( " else {} " , expr_block ( cx , els , None , " .. " ) ) ) ;
2016-01-13 11:57:44 +00:00
span_lint_and_then ( cx ,
2016-01-24 11:24:16 +00:00
lint ,
2016-01-04 04:26:12 +00:00
expr . span ,
2016-01-13 11:57:44 +00:00
" you seem to be trying to use match for destructuring a single pattern. \
2016-02-29 11:19:32 +00:00
Consider using ` if let ` " ,
| db | {
db . span_suggestion ( expr . span ,
" try this " ,
format! ( " if let {} = {} {} {} " ,
snippet ( cx , arms [ 0 ] . pats [ 0 ] . span , " .. " ) ,
snippet ( cx , ex . span , " .. " ) ,
expr_block ( cx , & arms [ 0 ] . body , None , " .. " ) ,
els_str ) ) ;
} ) ;
2015-12-23 01:19:32 +00:00
}
}
2016-01-24 11:24:16 +00:00
fn check_single_match_opt_like ( cx : & LateContext , ex : & Expr , arms : & [ Arm ] , expr : & Expr , ty : ty ::Ty , els : Option < & Expr > ) {
// list of candidate Enums we know will never get any more members
2016-02-29 11:19:32 +00:00
let candidates = & [ ( & COW_PATH , " Borrowed " ) ,
( & COW_PATH , " Cow::Borrowed " ) ,
( & COW_PATH , " Cow::Owned " ) ,
( & COW_PATH , " Owned " ) ,
( & OPTION_PATH , " None " ) ,
( & RESULT_PATH , " Err " ) ,
( & RESULT_PATH , " Ok " ) ] ;
2016-01-13 00:19:27 +00:00
let path = match arms [ 1 ] . pats [ 0 ] . node {
2016-02-18 20:16:39 +00:00
PatKind ::TupleStruct ( ref path , Some ( ref inner ) ) = > {
2016-01-24 11:24:16 +00:00
// contains any non wildcard patterns? e.g. Err(err)
2016-02-24 16:38:57 +00:00
if inner . iter ( ) . any ( | pat | pat . node ! = PatKind ::Wild ) {
2016-01-24 11:24:16 +00:00
return ;
}
path . to_string ( )
2016-02-24 16:38:57 +00:00
}
2016-02-18 20:16:39 +00:00
PatKind ::TupleStruct ( ref path , None ) = > path . to_string ( ) ,
PatKind ::Ident ( BindByValue ( MutImmutable ) , ident , None ) = > ident . node . to_string ( ) ,
2016-01-30 12:48:39 +00:00
_ = > return ,
2016-01-13 00:19:27 +00:00
} ;
for & ( ty_path , pat_path ) in candidates {
if & path = = pat_path & & match_type ( cx , ty , ty_path ) {
2016-01-24 11:24:16 +00:00
let lint = if els . is_some ( ) {
SINGLE_MATCH_ELSE
} else {
SINGLE_MATCH
} ;
let els_str = els . map_or ( String ::new ( ) , | els | format! ( " else {} " , expr_block ( cx , els , None , " .. " ) ) ) ;
2016-01-13 11:57:44 +00:00
span_lint_and_then ( cx ,
2016-01-24 11:24:16 +00:00
lint ,
2016-01-13 00:19:27 +00:00
expr . span ,
2016-01-30 12:48:39 +00:00
" you seem to be trying to use match for destructuring a single pattern. Consider \
using ` if let ` " ,
| db | {
db . span_suggestion ( expr . span ,
" try this " ,
format! ( " if let {} = {} {} {} " ,
snippet ( cx , arms [ 0 ] . pats [ 0 ] . span , " .. " ) ,
snippet ( cx , ex . span , " .. " ) ,
expr_block ( cx , & arms [ 0 ] . body , None , " .. " ) ,
els_str ) ) ;
} ) ;
2016-01-13 00:19:27 +00:00
}
}
}
2015-12-23 01:19:32 +00:00
fn check_match_bool ( cx : & LateContext , ex : & Expr , arms : & [ Arm ] , expr : & Expr ) {
// type of expression == bool
if cx . tcx . expr_ty ( ex ) . sty = = ty ::TyBool {
2016-01-13 11:57:44 +00:00
let sugg = if arms . len ( ) = = 2 & & arms [ 0 ] . pats . len ( ) = = 1 {
2016-01-04 04:26:12 +00:00
// no guards
2016-02-18 20:16:39 +00:00
let exprs = if let PatKind ::Lit ( ref arm_bool ) = arms [ 0 ] . pats [ 0 ] . node {
2015-12-23 01:19:32 +00:00
if let ExprLit ( ref lit ) = arm_bool . node {
match lit . node {
2016-02-12 17:35:44 +00:00
LitKind ::Bool ( true ) = > Some ( ( & * arms [ 0 ] . body , & * arms [ 1 ] . body ) ) ,
LitKind ::Bool ( false ) = > Some ( ( & * arms [ 1 ] . body , & * arms [ 0 ] . body ) ) ,
2015-12-23 01:19:32 +00:00
_ = > None ,
}
2016-01-04 04:26:12 +00:00
} else {
None
}
} else {
None
} ;
2016-01-13 11:57:44 +00:00
2015-12-23 01:19:32 +00:00
if let Some ( ( ref true_expr , ref false_expr ) ) = exprs {
2016-02-29 08:45:36 +00:00
match ( is_unit_expr ( true_expr ) , is_unit_expr ( false_expr ) ) {
( false , false ) = >
2016-01-13 11:57:44 +00:00
Some ( format! ( " if {} {} else {} " ,
snippet ( cx , ex . span , " b " ) ,
expr_block ( cx , true_expr , None , " .. " ) ,
2016-02-29 08:45:36 +00:00
expr_block ( cx , false_expr , None , " .. " ) ) ) ,
( false , true ) = >
2016-01-13 11:57:44 +00:00
Some ( format! ( " if {} {} " ,
snippet ( cx , ex . span , " b " ) ,
2016-02-29 08:45:36 +00:00
expr_block ( cx , true_expr , None , " .. " ) ) ) ,
( true , false ) = >
Some ( format! ( " try \n if ! {} {} " ,
snippet ( cx , ex . span , " b " ) ,
expr_block ( cx , false_expr , None , " .. " ) ) ) ,
( true , true ) = > None ,
2015-10-21 06:24:56 +00:00
}
2015-12-23 01:19:32 +00:00
} else {
2016-01-13 11:57:44 +00:00
None
2015-10-20 17:18:48 +00:00
}
2015-12-23 01:19:32 +00:00
} else {
2016-01-13 11:57:44 +00:00
None
} ;
span_lint_and_then ( cx ,
MATCH_BOOL ,
expr . span ,
2016-01-30 12:48:39 +00:00
" you seem to be trying to match on a boolean expression. Consider using an if..else block: " ,
move | db | {
if let Some ( sugg ) = sugg {
db . span_suggestion ( expr . span , " try this " , sugg ) ;
}
} ) ;
2015-12-23 01:19:32 +00:00
}
}
2015-12-23 00:14:10 +00:00
2015-12-23 10:25:44 +00:00
fn check_overlapping_arms ( cx : & LateContext , ex : & Expr , arms : & [ Arm ] ) {
2016-01-04 04:26:12 +00:00
if arms . len ( ) > = 2 & & cx . tcx . expr_ty ( ex ) . is_integral ( ) {
2015-12-23 01:19:32 +00:00
let ranges = all_ranges ( cx , arms ) ;
2016-03-15 19:09:53 +00:00
let type_ranges = type_ranges ( & ranges ) ;
if ! type_ranges . is_empty ( ) {
if let Some ( ( start , end ) ) = overlapping ( & type_ranges ) {
span_note_and_lint ( cx ,
MATCH_OVERLAPPING_ARM ,
start . span ,
" some ranges overlap " ,
end . span ,
" overlaps with this " ) ;
}
2015-08-21 17:32:21 +00:00
}
2015-12-23 01:19:32 +00:00
}
}
fn check_match_ref_pats ( cx : & LateContext , ex : & Expr , arms : & [ Arm ] , source : MatchSource , expr : & Expr ) {
if has_only_ref_pats ( arms ) {
if let ExprAddrOf ( Mutability ::MutImmutable , ref inner ) = ex . node {
let template = match_template ( cx , expr . span , source , " " , inner ) ;
2016-03-09 15:22:31 +00:00
span_lint_and_then ( cx ,
MATCH_REF_PATS ,
expr . span ,
" you don't need to add `&` to both the expression and the patterns " ,
| db | {
db . span_suggestion ( expr . span ,
" try " ,
template ) ;
} ) ;
2015-12-23 01:19:32 +00:00
} else {
let template = match_template ( cx , expr . span , source , " * " , ex ) ;
2016-03-09 15:22:31 +00:00
span_lint_and_then ( cx ,
MATCH_REF_PATS ,
expr . span ,
" you don't need to add `&` to all patterns " ,
| db | {
db . span_suggestion ( expr . span ,
" instead of prefixing all patterns with `&`, you can dereference the expression " ,
template ) ;
} ) ;
2015-11-24 17:47:17 +00:00
}
2015-08-21 17:32:21 +00:00
}
}
2016-03-19 16:48:29 +00:00
/// Get all arms that are unbounded `PatRange`s.
2015-12-23 00:14:10 +00:00
fn all_ranges ( cx : & LateContext , arms : & [ Arm ] ) -> Vec < SpannedRange < ConstVal > > {
arms . iter ( )
. filter_map ( | arm | {
if let Arm { ref pats , guard : None , .. } = * arm {
Some ( pats . iter ( ) . filter_map ( | pat | {
if_let_chain! { [
2016-02-18 20:16:39 +00:00
let PatKind ::Range ( ref lhs , ref rhs ) = pat . node ,
2015-12-23 00:14:10 +00:00
let Ok ( lhs ) = eval_const_expr_partial ( cx . tcx , & lhs , ExprTypeChecked , None ) ,
let Ok ( rhs ) = eval_const_expr_partial ( cx . tcx , & rhs , ExprTypeChecked , None )
] , {
return Some ( SpannedRange { span : pat . span , node : ( lhs , rhs ) } ) ;
} }
2015-12-23 01:06:18 +00:00
if_let_chain! { [
2016-02-18 20:16:39 +00:00
let PatKind ::Lit ( ref value ) = pat . node ,
2015-12-23 01:06:18 +00:00
let Ok ( value ) = eval_const_expr_partial ( cx . tcx , & value , ExprTypeChecked , None )
] , {
return Some ( SpannedRange { span : pat . span , node : ( value . clone ( ) , value ) } ) ;
} }
2015-12-23 00:14:10 +00:00
None
} ) )
2016-01-04 04:26:12 +00:00
} else {
2015-12-23 00:14:10 +00:00
None
}
} )
. flat_map ( IntoIterator ::into_iter )
. collect ( )
}
#[ derive(Debug, Eq, PartialEq) ]
2015-12-23 16:48:30 +00:00
pub struct SpannedRange < T > {
pub span : Span ,
pub node : ( T , T ) ,
2015-12-23 00:14:10 +00:00
}
2016-03-15 19:09:53 +00:00
type TypedRanges = Vec < SpannedRange < ConstInt > > ;
2015-12-23 00:14:10 +00:00
/// Get all `Int` ranges or all `Uint` ranges. Mixed types are an error anyway and other types than
/// `Uint` and `Int` probably don't make sense.
fn type_ranges ( ranges : & [ SpannedRange < ConstVal > ] ) -> TypedRanges {
2016-03-15 19:09:53 +00:00
ranges . iter ( ) . filter_map ( | range | {
if let ( ConstVal ::Integral ( start ) , ConstVal ::Integral ( end ) ) = range . node {
Some ( SpannedRange {
span : range . span ,
node : ( start , end ) ,
} )
} else {
None
2015-12-23 00:14:10 +00:00
}
2016-03-15 19:09:53 +00:00
} )
. collect ( )
2015-12-23 00:14:10 +00:00
}
2015-08-21 17:32:21 +00: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 17:49:00 +00:00
fn has_only_ref_pats ( arms : & [ Arm ] ) -> bool {
2016-01-04 04:26:12 +00:00
let mapped = arms . iter ( )
. flat_map ( | a | & a . pats )
. map ( | p | {
match p . node {
2016-02-18 20:16:39 +00:00
PatKind ::Ref ( .. ) = > Some ( true ) , // &-patterns
PatKind ::Wild = > Some ( false ) , // an "anything" wildcard is also fine
2016-01-04 04:26:12 +00:00
_ = > None , // any other pattern is not fine
}
} )
. collect ::< Option < Vec < bool > > > ( ) ;
2015-09-17 05:24:11 +00:00
// 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 17:49:00 +00:00
}
2015-11-24 17:47:17 +00:00
2016-01-04 04:26:12 +00:00
fn match_template ( cx : & LateContext , span : Span , source : MatchSource , op : & str , expr : & Expr ) -> String {
2015-11-25 04:57:50 +00:00
let expr_snippet = snippet ( cx , expr . span , " .. " ) ;
2015-11-24 17:47:17 +00:00
match source {
2016-03-09 15:22:31 +00:00
MatchSource ::Normal = > format! ( " match {} {} {{ .. }} " , op , expr_snippet ) ,
MatchSource ::IfLetDesugar { .. } = > format! ( " if let .. = {} {} {{ .. }} " , op , expr_snippet ) ,
MatchSource ::WhileLetDesugar = > format! ( " while let .. = {} {} {{ .. }} " , op , expr_snippet ) ,
2016-04-03 15:16:53 +00:00
MatchSource ::ForLoopDesugar = > span_bug! ( span , " for loop desugared to match with &-patterns! " ) ,
MatchSource ::TryDesugar = > span_bug! ( span , " `?` operator desugared to match with &-patterns! " )
2015-11-24 17:47:17 +00:00
}
}
2015-12-23 00:14:10 +00:00
2015-12-23 16:48:30 +00:00
pub fn overlapping < T > ( ranges : & [ SpannedRange < T > ] ) -> Option < ( & SpannedRange < T > , & SpannedRange < T > ) >
2016-01-04 04:26:12 +00:00
where T : Copy + Ord
{
2015-12-23 00:14:10 +00:00
#[ derive(Copy, Clone, Debug, Eq, PartialEq) ]
enum Kind < ' a , T : ' a > {
Start ( T , & ' a SpannedRange < T > ) ,
End ( T , & ' a SpannedRange < T > ) ,
}
impl < ' a , T : Copy > Kind < ' a , T > {
fn range ( & self ) -> & ' a SpannedRange < T > {
match * self {
2016-01-04 04:26:12 +00:00
Kind ::Start ( _ , r ) | Kind ::End ( _ , r ) = > r ,
2015-12-23 00:14:10 +00:00
}
}
fn value ( self ) -> T {
match self {
2016-01-04 04:26:12 +00:00
Kind ::Start ( t , _ ) | Kind ::End ( t , _ ) = > t ,
2015-12-23 00:14:10 +00:00
}
}
}
impl < ' a , T : Copy + Ord > PartialOrd for Kind < ' a , T > {
fn partial_cmp ( & self , other : & Self ) -> Option < Ordering > {
Some ( self . cmp ( other ) )
}
}
impl < ' a , T : Copy + Ord > Ord for Kind < ' a , T > {
fn cmp ( & self , other : & Self ) -> Ordering {
self . value ( ) . cmp ( & other . value ( ) )
}
}
2016-01-04 04:26:12 +00:00
let mut values = Vec ::with_capacity ( 2 * ranges . len ( ) ) ;
2015-12-23 00:14:10 +00:00
for r in ranges {
values . push ( Kind ::Start ( r . node . 0 , & r ) ) ;
values . push ( Kind ::End ( r . node . 1 , & r ) ) ;
}
values . sort ( ) ;
for ( a , b ) in values . iter ( ) . zip ( values . iter ( ) . skip ( 1 ) ) {
match ( a , b ) {
2016-01-04 04:26:12 +00:00
( & Kind ::Start ( _ , ra ) , & Kind ::End ( _ , rb ) ) = > {
if ra . node ! = rb . node {
return Some ( ( ra , rb ) ) ;
}
}
2015-12-23 00:14:10 +00:00
( & Kind ::End ( a , _ ) , & Kind ::Start ( b , _ ) ) if a ! = b = > ( ) ,
_ = > return Some ( ( & a . range ( ) , & b . range ( ) ) ) ,
}
}
None
}