@@ -1306,6 +1306,29 @@ declare_clippy_lint! {
13061306 "using `.iter().next()` on a sliced array, which can be shortened to just `.get()`"
13071307}
13081308
1309+ declare_clippy_lint ! {
1310+ /// **What it does:** Warns when using push_str with a single-character string literal,
1311+ /// and push with a char would work fine.
1312+ ///
1313+ /// **Why is this bad?** it's less clear that we are pushing a single character
1314+ ///
1315+ /// **Known problems:** None
1316+ ///
1317+ /// **Example:**
1318+ /// ```
1319+ /// let mut string = String::new();
1320+ /// string.push_str("R");
1321+ /// ```
1322+ /// Could be written as
1323+ /// ```
1324+ /// let mut string = String::new();
1325+ /// string.push('R');
1326+ /// ```
1327+ pub SINGLE_CHAR_PUSH_STR ,
1328+ style,
1329+ "`push_str()` used with a single-character string literal as parameter"
1330+ }
1331+
13091332declare_lint_pass ! ( Methods => [
13101333 UNWRAP_USED ,
13111334 EXPECT_USED ,
@@ -1327,6 +1350,7 @@ declare_lint_pass!(Methods => [
13271350 INEFFICIENT_TO_STRING ,
13281351 NEW_RET_NO_SELF ,
13291352 SINGLE_CHAR_PATTERN ,
1353+ SINGLE_CHAR_PUSH_STR ,
13301354 SEARCH_IS_SOME ,
13311355 TEMPORARY_CSTRING_AS_PTR ,
13321356 FILTER_NEXT ,
@@ -1441,6 +1465,12 @@ impl<'tcx> LateLintPass<'tcx> for Methods {
14411465 inefficient_to_string:: lint ( cx, expr, & args[ 0 ] , self_ty) ;
14421466 }
14431467
1468+ if let Some ( fn_def_id) = cx. typeck_results ( ) . type_dependent_def_id ( expr. hir_id ) {
1469+ if match_def_path ( cx, fn_def_id, & paths:: PUSH_STR ) {
1470+ lint_single_char_push_string ( cx, expr, args) ;
1471+ }
1472+ }
1473+
14441474 match self_ty. kind {
14451475 ty:: Ref ( _, ty, _) if ty. kind == ty:: Str => {
14461476 for & ( method, pos) in & PATTERN_METHODS {
@@ -3124,15 +3154,18 @@ fn lint_chars_last_cmp_with_unwrap<'tcx>(cx: &LateContext<'tcx>, info: &BinaryEx
31243154 }
31253155}
31263156
3127- /// lint for length-1 `str`s for methods in `PATTERN_METHODS`
3128- fn lint_single_char_pattern < ' tcx > ( cx : & LateContext < ' tcx > , _expr : & ' tcx hir:: Expr < ' _ > , arg : & ' tcx hir:: Expr < ' _ > ) {
3157+ fn get_hint_if_single_char_arg < ' tcx > (
3158+ cx : & LateContext < ' tcx > ,
3159+ arg : & ' tcx hir:: Expr < ' _ > ,
3160+ applicability : & mut Applicability ,
3161+ ) -> Option < String > {
31293162 if_chain ! {
31303163 if let hir:: ExprKind :: Lit ( lit) = & arg. kind;
31313164 if let ast:: LitKind :: Str ( r, style) = lit. node;
3132- if r. as_str( ) . len( ) == 1 ;
3165+ let string = r. as_str( ) ;
3166+ if string. len( ) == 1 ;
31333167 then {
3134- let mut applicability = Applicability :: MachineApplicable ;
3135- let snip = snippet_with_applicability( cx, arg. span, ".." , & mut applicability) ;
3168+ let snip = snippet_with_applicability( cx, arg. span, & string, applicability) ;
31363169 let ch = if let ast:: StrStyle :: Raw ( nhash) = style {
31373170 let nhash = nhash as usize ;
31383171 // for raw string: r##"a"##
@@ -3142,19 +3175,47 @@ fn lint_single_char_pattern<'tcx>(cx: &LateContext<'tcx>, _expr: &'tcx hir::Expr
31423175 & snip[ 1 ..( snip. len( ) - 1 ) ]
31433176 } ;
31443177 let hint = format!( "'{}'" , if ch == "'" { "\\ '" } else { ch } ) ;
3145- span_lint_and_sugg(
3146- cx,
3147- SINGLE_CHAR_PATTERN ,
3148- arg. span,
3149- "single-character string constant used as pattern" ,
3150- "try using a `char` instead" ,
3151- hint,
3152- applicability,
3153- ) ;
3178+ Some ( hint)
3179+ } else {
3180+ None
31543181 }
31553182 }
31563183}
31573184
3185+ /// lint for length-1 `str`s for methods in `PATTERN_METHODS`
3186+ fn lint_single_char_pattern < ' tcx > ( cx : & LateContext < ' tcx > , _expr : & ' tcx hir:: Expr < ' _ > , arg : & ' tcx hir:: Expr < ' _ > ) {
3187+ let mut applicability = Applicability :: MachineApplicable ;
3188+ if let Some ( hint) = get_hint_if_single_char_arg ( cx, arg, & mut applicability) {
3189+ span_lint_and_sugg (
3190+ cx,
3191+ SINGLE_CHAR_PATTERN ,
3192+ arg. span ,
3193+ "single-character string constant used as pattern" ,
3194+ "try using a `char` instead" ,
3195+ hint,
3196+ applicability,
3197+ ) ;
3198+ }
3199+ }
3200+
3201+ /// lint for length-1 `str`s as argument for `push_str`
3202+ fn lint_single_char_push_string < ' tcx > ( cx : & LateContext < ' tcx > , expr : & ' tcx hir:: Expr < ' _ > , args : & ' tcx [ hir:: Expr < ' _ > ] ) {
3203+ let mut applicability = Applicability :: MachineApplicable ;
3204+ if let Some ( extension_string) = get_hint_if_single_char_arg ( cx, & args[ 1 ] , & mut applicability) {
3205+ let base_string_snippet = snippet_with_applicability ( cx, args[ 0 ] . span , "_" , & mut applicability) ;
3206+ let sugg = format ! ( "{}.push({})" , base_string_snippet, extension_string) ;
3207+ span_lint_and_sugg (
3208+ cx,
3209+ SINGLE_CHAR_PUSH_STR ,
3210+ expr. span ,
3211+ "calling `push_str()` using a single-character string literal" ,
3212+ "consider using `push` with a character literal" ,
3213+ sugg,
3214+ applicability,
3215+ ) ;
3216+ }
3217+ }
3218+
31583219/// Checks for the `USELESS_ASREF` lint.
31593220fn lint_asref ( cx : & LateContext < ' _ > , expr : & hir:: Expr < ' _ > , call_name : & str , as_ref_args : & [ hir:: Expr < ' _ > ] ) {
31603221 // when we get here, we've already checked that the call name is "as_ref" or "as_mut"
0 commit comments