2015-08-05 13:10:45 +00:00
//! This LintPass catches both string addition and string addition + assignment
2015-08-11 18:22:20 +00:00
//!
2015-08-05 13:10:45 +00:00
//! Note that since we have two lints where one subsumes the other, we try to
//! disable the subsumed lint unless it has a higher level
use rustc ::lint ::* ;
2015-09-03 14:42:17 +00:00
use rustc_front ::hir ::* ;
2015-08-16 06:54:43 +00:00
use syntax ::codemap ::Spanned ;
2015-08-05 13:10:45 +00:00
use eq_op ::is_exp_equal ;
2015-08-21 17:00:33 +00:00
use utils ::{ match_type , span_lint , walk_ptrs_ty , get_parent_expr } ;
2015-08-21 16:48:36 +00:00
use utils ::STRING_PATH ;
2015-08-05 13:10:45 +00:00
2016-01-01 16:48:19 +00:00
/// **What it does:** This lint matches code of the form `x = x + y` (without `let`!). It is `Allow` by default.
2015-12-11 00:22:27 +00:00
///
/// **Why is this bad?** Because this expression needs another copy as opposed to `x.push_str(y)` (in practice LLVM will usually elide it, though). Despite [llogiq](https://github.com/llogiq)'s reservations, this lint also is `allow` by default, as some people opine that it's more readable.
///
/// **Known problems:** None. Well apart from the lint being `allow` by default. :smile:
///
/// **Example:**
///
/// ```
/// let mut x = "Hello".to_owned();
/// x = x + ", World";
/// ```
2015-08-05 13:10:45 +00:00
declare_lint! {
pub STRING_ADD_ASSIGN ,
2015-08-12 19:17:21 +00:00
Allow ,
2015-08-13 08:32:35 +00:00
" using `x = x + ..` where x is a `String`; suggests using `push_str()` instead "
2015-08-05 13:10:45 +00:00
}
2015-12-11 00:22:27 +00:00
/// **What it does:** The `string_add` lint matches all instances of `x + _` where `x` is of type `String`, but only if [`string_add_assign`](#string_add_assign) does *not* match. It is `Allow` by default.
///
/// **Why is this bad?** It's not bad in and of itself. However, this particular `Add` implementation is asymmetric (the other operand need not be `String`, but `x` does), while addition as mathematically defined is symmetric, also the `String::push_str(_)` function is a perfectly good replacement. Therefore some dislike it and wish not to have it in their code.
///
/// That said, other people think that String addition, having a long tradition in other languages is actually fine, which is why we decided to make this particular lint `allow` by default.
///
/// **Known problems:** None
///
/// **Example:**
///
/// ```
/// let x = "Hello".to_owned();
/// x + ", World"
/// ```
2015-08-12 13:50:56 +00:00
declare_lint! {
2015-08-12 13:57:50 +00:00
pub STRING_ADD ,
Allow ,
2015-08-13 08:32:35 +00:00
" using `x + ..` where x is a `String`; suggests using `push_str()` instead "
2015-08-12 13:50:56 +00:00
}
#[ derive(Copy, Clone) ]
2015-08-05 13:10:45 +00:00
pub struct StringAdd ;
impl LintPass for StringAdd {
2015-08-12 13:50:56 +00:00
fn get_lints ( & self ) -> LintArray {
2015-08-12 14:42:42 +00:00
lint_array! ( STRING_ADD , STRING_ADD_ASSIGN )
2015-08-12 13:50:56 +00:00
}
2015-09-19 02:53:04 +00:00
}
2015-08-12 13:50:56 +00:00
2015-09-19 02:53:04 +00:00
impl LateLintPass for StringAdd {
fn check_expr ( & mut self , cx : & LateContext , e : & Expr ) {
2015-11-24 17:44:40 +00:00
if let ExprBinary ( Spanned { node : BiAdd , .. } , ref left , _ ) = e . node {
2015-08-12 13:57:50 +00:00
if is_string ( cx , left ) {
if let Allow = cx . current_level ( STRING_ADD_ASSIGN ) {
// the string_add_assign is allow, so no duplicates
} else {
let parent = get_parent_expr ( cx , e ) ;
if let Some ( ref p ) = parent {
2015-11-24 17:44:40 +00:00
if let ExprAssign ( ref target , _ ) = p . node {
2015-08-12 13:57:50 +00:00
// avoid duplicate matches
2015-08-21 10:19:07 +00:00
if is_exp_equal ( cx , target , left ) { return ; }
2015-08-12 13:57:50 +00:00
}
}
}
2015-08-27 05:39:40 +00:00
span_lint ( cx , STRING_ADD , e . span ,
2015-08-26 12:26:43 +00:00
" you added something to a string. \
2015-12-31 20:39:03 +00:00
Consider using ` String ::push_str ( ) ` instead " );
2015-08-12 13:57:50 +00:00
}
2015-11-24 17:44:40 +00:00
} else if let ExprAssign ( ref target , ref src ) = e . node {
2015-08-21 10:19:07 +00:00
if is_string ( cx , target ) & & is_add ( cx , src , target ) {
2015-08-11 18:22:20 +00:00
span_lint ( cx , STRING_ADD_ASSIGN , e . span ,
2015-08-12 19:17:21 +00:00
" you assigned the result of adding something to this string. \
2015-12-31 20:39:03 +00:00
Consider using ` String ::push_str ( ) ` instead " );
2015-08-05 13:10:45 +00:00
}
}
}
}
2015-09-19 02:53:04 +00:00
fn is_string ( cx : & LateContext , e : & Expr ) -> bool {
2015-08-21 17:00:33 +00:00
match_type ( cx , walk_ptrs_ty ( cx . tcx . expr_ty ( e ) ) , & STRING_PATH )
2015-08-05 13:10:45 +00:00
}
2015-09-19 02:53:04 +00:00
fn is_add ( cx : & LateContext , src : & Expr , target : & Expr ) -> bool {
2015-08-21 18:44:48 +00:00
match src . node {
ExprBinary ( Spanned { node : BiAdd , .. } , ref left , _ ) = >
2015-08-21 10:19:07 +00:00
is_exp_equal ( cx , target , left ) ,
2015-08-21 18:44:48 +00:00
ExprBlock ( ref block ) = > block . stmts . is_empty ( ) & &
2015-08-21 10:19:07 +00:00
block . expr . as_ref ( ) . map_or ( false ,
2015-08-25 12:41:35 +00:00
| expr | is_add ( cx , expr , target ) ) ,
2015-08-05 13:10:45 +00:00
_ = > false
}
}