Skip to content

Commit c8e05fc

Browse files
committed
Auto merge of #5881 - wiomoc:feature/single-char-push_str, r=ebroto,flip1995
Lint `push_str` with a single-character string literal Fixes #5875 changelog: `* [single_char_push_str]`
2 parents e522ca3 + b381ade commit c8e05fc

File tree

8 files changed

+151
-14
lines changed

8 files changed

+151
-14
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -1699,6 +1699,7 @@ Released 2018-09-13
16991699
[`should_implement_trait`]: https://rust-lang.github.io/rust-clippy/master/index.html#should_implement_trait
17001700
[`similar_names`]: https://rust-lang.github.io/rust-clippy/master/index.html#similar_names
17011701
[`single_char_pattern`]: https://rust-lang.github.io/rust-clippy/master/index.html#single_char_pattern
1702+
[`single_char_push_str`]: https://rust-lang.github.io/rust-clippy/master/index.html#single_char_push_str
17021703
[`single_component_path_imports`]: https://rust-lang.github.io/rust-clippy/master/index.html#single_component_path_imports
17031704
[`single_match`]: https://rust-lang.github.io/rust-clippy/master/index.html#single_match
17041705
[`single_match_else`]: https://rust-lang.github.io/rust-clippy/master/index.html#single_match_else

clippy_lints/src/lib.rs

+3
Original file line numberDiff line numberDiff line change
@@ -678,6 +678,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
678678
&methods::SEARCH_IS_SOME,
679679
&methods::SHOULD_IMPLEMENT_TRAIT,
680680
&methods::SINGLE_CHAR_PATTERN,
681+
&methods::SINGLE_CHAR_PUSH_STR,
681682
&methods::SKIP_WHILE_NEXT,
682683
&methods::STRING_EXTEND_CHARS,
683684
&methods::SUSPICIOUS_MAP,
@@ -1352,6 +1353,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
13521353
LintId::of(&methods::SEARCH_IS_SOME),
13531354
LintId::of(&methods::SHOULD_IMPLEMENT_TRAIT),
13541355
LintId::of(&methods::SINGLE_CHAR_PATTERN),
1356+
LintId::of(&methods::SINGLE_CHAR_PUSH_STR),
13551357
LintId::of(&methods::SKIP_WHILE_NEXT),
13561358
LintId::of(&methods::STRING_EXTEND_CHARS),
13571359
LintId::of(&methods::SUSPICIOUS_MAP),
@@ -1536,6 +1538,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
15361538
LintId::of(&methods::OPTION_MAP_OR_NONE),
15371539
LintId::of(&methods::RESULT_MAP_OR_INTO_OPTION),
15381540
LintId::of(&methods::SHOULD_IMPLEMENT_TRAIT),
1541+
LintId::of(&methods::SINGLE_CHAR_PUSH_STR),
15391542
LintId::of(&methods::STRING_EXTEND_CHARS),
15401543
LintId::of(&methods::UNNECESSARY_FOLD),
15411544
LintId::of(&methods::WRONG_SELF_CONVENTION),

clippy_lints/src/methods/mod.rs

+75-14
Original file line numberDiff line numberDiff line change
@@ -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+
13091332
declare_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(
3158+
cx: &LateContext<'_>,
3159+
arg: &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(cx: &LateContext<'_>, _expr: &hir::Expr<'_>, arg: &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(cx: &LateContext<'_>, expr: &hir::Expr<'_>, args: &[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.
31593220
fn 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"

clippy_lints/src/utils/paths.rs

+1
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ pub const POLL: [&str; 4] = ["core", "task", "poll", "Poll"];
8484
pub const PTR_EQ: [&str; 3] = ["core", "ptr", "eq"];
8585
pub const PTR_NULL: [&str; 2] = ["ptr", "null"];
8686
pub const PTR_NULL_MUT: [&str; 2] = ["ptr", "null_mut"];
87+
pub const PUSH_STR: [&str; 4] = ["alloc", "string", "String", "push_str"];
8788
pub const RANGE: [&str; 3] = ["core", "ops", "Range"];
8889
pub const RANGE_ARGUMENT_TRAIT: [&str; 3] = ["core", "ops", "RangeBounds"];
8990
pub const RANGE_FROM: [&str; 3] = ["core", "ops", "RangeFrom"];

src/lintlist/mod.rs

+7
Original file line numberDiff line numberDiff line change
@@ -2012,6 +2012,13 @@ pub static ref ALL_LINTS: Vec<Lint> = vec![
20122012
deprecation: None,
20132013
module: "methods",
20142014
},
2015+
Lint {
2016+
name: "single_char_push_str",
2017+
group: "style",
2018+
desc: "`push_str()` used with a single-character string literal as parameter",
2019+
deprecation: None,
2020+
module: "methods",
2021+
},
20152022
Lint {
20162023
name: "single_component_path_imports",
20172024
group: "style",

tests/ui/single_char_push_str.fixed

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
// run-rustfix
2+
#![warn(clippy::single_char_push_str)]
3+
4+
fn main() {
5+
let mut string = String::new();
6+
string.push('R');
7+
string.push('\'');
8+
9+
string.push('u');
10+
string.push_str("st");
11+
string.push_str("");
12+
string.push('\x52');
13+
string.push('\u{0052}');
14+
string.push('a');
15+
}

tests/ui/single_char_push_str.rs

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
// run-rustfix
2+
#![warn(clippy::single_char_push_str)]
3+
4+
fn main() {
5+
let mut string = String::new();
6+
string.push_str("R");
7+
string.push_str("'");
8+
9+
string.push('u');
10+
string.push_str("st");
11+
string.push_str("");
12+
string.push_str("\x52");
13+
string.push_str("\u{0052}");
14+
string.push_str(r##"a"##);
15+
}

tests/ui/single_char_push_str.stderr

+34
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
error: calling `push_str()` using a single-character string literal
2+
--> $DIR/single_char_push_str.rs:6:5
3+
|
4+
LL | string.push_str("R");
5+
| ^^^^^^^^^^^^^^^^^^^^ help: consider using `push` with a character literal: `string.push('R')`
6+
|
7+
= note: `-D clippy::single-char-push-str` implied by `-D warnings`
8+
9+
error: calling `push_str()` using a single-character string literal
10+
--> $DIR/single_char_push_str.rs:7:5
11+
|
12+
LL | string.push_str("'");
13+
| ^^^^^^^^^^^^^^^^^^^^ help: consider using `push` with a character literal: `string.push('/'')`
14+
15+
error: calling `push_str()` using a single-character string literal
16+
--> $DIR/single_char_push_str.rs:12:5
17+
|
18+
LL | string.push_str("/x52");
19+
| ^^^^^^^^^^^^^^^^^^^^^^^ help: consider using `push` with a character literal: `string.push('/x52')`
20+
21+
error: calling `push_str()` using a single-character string literal
22+
--> $DIR/single_char_push_str.rs:13:5
23+
|
24+
LL | string.push_str("/u{0052}");
25+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using `push` with a character literal: `string.push('/u{0052}')`
26+
27+
error: calling `push_str()` using a single-character string literal
28+
--> $DIR/single_char_push_str.rs:14:5
29+
|
30+
LL | string.push_str(r##"a"##);
31+
| ^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using `push` with a character literal: `string.push('a')`
32+
33+
error: aborting due to 5 previous errors
34+

0 commit comments

Comments
 (0)