Skip to content

Commit 5307cb5

Browse files
committed
Add a lint for .repeat(1)
fix rust-lang#3028.
1 parent ac85692 commit 5307cb5

File tree

7 files changed

+211
-0
lines changed

7 files changed

+211
-0
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1616,6 +1616,7 @@ Released 2018-09-13
16161616
[`redundant_static_lifetimes`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_static_lifetimes
16171617
[`ref_in_deref`]: https://rust-lang.github.io/rust-clippy/master/index.html#ref_in_deref
16181618
[`regex_macro`]: https://rust-lang.github.io/rust-clippy/master/index.html#regex_macro
1619+
[`repeat_once`]: https://rust-lang.github.io/rust-clippy/master/index.html#repeat_once
16191620
[`replace_consts`]: https://rust-lang.github.io/rust-clippy/master/index.html#replace_consts
16201621
[`rest_pat_in_fully_bound_structs`]: https://rust-lang.github.io/rust-clippy/master/index.html#rest_pat_in_fully_bound_structs
16211622
[`result_map_or_into_option`]: https://rust-lang.github.io/rust-clippy/master/index.html#result_map_or_into_option

clippy_lints/src/lib.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -282,6 +282,7 @@ mod redundant_pub_crate;
282282
mod redundant_static_lifetimes;
283283
mod reference;
284284
mod regex;
285+
mod repeat_once;
285286
mod returns;
286287
mod serde_api;
287288
mod shadow;
@@ -764,6 +765,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
764765
&reference::REF_IN_DEREF,
765766
&regex::INVALID_REGEX,
766767
&regex::TRIVIAL_REGEX,
768+
&repeat_once::REPEAT_ONCE,
767769
&returns::NEEDLESS_RETURN,
768770
&returns::UNUSED_UNIT,
769771
&serde_api::SERDE_API_MISUSE,
@@ -1071,6 +1073,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
10711073
store.register_late_pass(|| box macro_use::MacroUseImports::default());
10721074
store.register_late_pass(|| box map_identity::MapIdentity);
10731075
store.register_late_pass(|| box pattern_type_mismatch::PatternTypeMismatch);
1076+
store.register_late_pass(|| box repeat_once::RepeatOnce);
10741077

10751078
store.register_group(true, "clippy::restriction", Some("clippy_restriction"), vec![
10761079
LintId::of(&arithmetic::FLOAT_ARITHMETIC),
@@ -1393,6 +1396,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
13931396
LintId::of(&reference::REF_IN_DEREF),
13941397
LintId::of(&regex::INVALID_REGEX),
13951398
LintId::of(&regex::TRIVIAL_REGEX),
1399+
LintId::of(&repeat_once::REPEAT_ONCE),
13961400
LintId::of(&returns::NEEDLESS_RETURN),
13971401
LintId::of(&returns::UNUSED_UNIT),
13981402
LintId::of(&serde_api::SERDE_API_MISUSE),
@@ -1602,6 +1606,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
16021606
LintId::of(&ranges::RANGE_ZIP_WITH_LEN),
16031607
LintId::of(&reference::DEREF_ADDROF),
16041608
LintId::of(&reference::REF_IN_DEREF),
1609+
LintId::of(&repeat_once::REPEAT_ONCE),
16051610
LintId::of(&swap::MANUAL_SWAP),
16061611
LintId::of(&temporary_assignment::TEMPORARY_ASSIGNMENT),
16071612
LintId::of(&transmute::CROSSPOINTER_TRANSMUTE),

clippy_lints/src/repeat_once.rs

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
use crate::consts::{miri_to_const, Constant};
2+
use crate::utils::{in_macro, is_type_diagnostic_item, snippet, span_lint_and_sugg, walk_ptrs_ty};
3+
use if_chain::if_chain;
4+
use rustc_ast::ast::LitKind;
5+
use rustc_errors::Applicability;
6+
use rustc_hir::def::{DefKind, Res};
7+
use rustc_hir::{Expr, ExprKind};
8+
use rustc_lint::{LateContext, LateLintPass};
9+
use rustc_middle::ty::{self, Ty};
10+
use rustc_session::{declare_lint_pass, declare_tool_lint};
11+
12+
declare_clippy_lint! {
13+
/// **What it does:** Checks for usage of `.repeat(1)` and suggest the following method for each types.
14+
/// - `.to_string()` for `str`
15+
/// - `.clone()` for `String`
16+
/// - `.to_vec()` for `slice`
17+
///
18+
/// **Why is this bad?** For example, `String.repeat(1)` is equivalent to `.clone()`. If cloning the string is the intention behind thi, `clone()` should be used.
19+
///
20+
/// **Known problems:** None.
21+
///
22+
/// **Example:**
23+
///
24+
/// ```rust
25+
/// fn main() {
26+
/// let x = String::from("hello world").repeat(1);
27+
/// }
28+
/// ```
29+
/// Use instead:
30+
/// ```rust
31+
/// fn main() {
32+
/// let x = String::from("hello world").clone();
33+
/// }
34+
/// ```
35+
pub REPEAT_ONCE,
36+
complexity,
37+
"using `.repeat(1)` instead of `String.clone()`, `str.to_string()` or `slice.to_vec()` "
38+
}
39+
40+
declare_lint_pass!(RepeatOnce => [REPEAT_ONCE]);
41+
42+
impl<'tcx> LateLintPass<'tcx> for RepeatOnce {
43+
fn check_expr(&mut self, cx: &LateContext<'_>, expr: &'tcx Expr<'_>) {
44+
if_chain! {
45+
if let ExprKind::MethodCall(ref path, _, ref args, _) = expr.kind;
46+
if path.ident.name == sym!(repeat);
47+
if is_once(cx, &args[1]) && !in_macro(args[0].span);
48+
then {
49+
let ty = walk_ptrs_ty(cx.tables().expr_ty(&args[0]));
50+
if is_str(ty){
51+
span_lint_and_sugg(
52+
cx,
53+
REPEAT_ONCE,
54+
expr.span,
55+
"calling `repeat(1)` on str",
56+
"consider using `.to_string()` instead",
57+
format!("{}.to_string()", snippet(cx, args[0].span, r#""...""#)),
58+
Applicability::MachineApplicable,
59+
);
60+
} else if is_slice(ty) {
61+
span_lint_and_sugg(
62+
cx,
63+
REPEAT_ONCE,
64+
expr.span,
65+
"calling `repeat(1)` on slice",
66+
"consider using `.to_vec()` instead",
67+
format!("{}.to_vec()", snippet(cx, args[0].span, r#""...""#)),
68+
Applicability::MachineApplicable,
69+
);
70+
} else if is_type_diagnostic_item(cx, ty, sym!(string_type)) {
71+
span_lint_and_sugg(
72+
cx,
73+
REPEAT_ONCE,
74+
expr.span,
75+
"calling `repeat(1)` on a string literal",
76+
"consider using `.clone()` instead",
77+
format!("{}.clone()", snippet(cx, args[0].span, r#""...""#)),
78+
Applicability::MachineApplicable,
79+
);
80+
}
81+
}
82+
}
83+
}
84+
}
85+
86+
fn is_once<'tcx>(cx: &LateContext<'_>, expr: &'tcx Expr<'_>) -> bool {
87+
match expr.kind {
88+
ExprKind::Lit(ref lit) => {
89+
if let LitKind::Int(ref lit_content, _) = lit.node {
90+
*lit_content == 1
91+
} else {
92+
false
93+
}
94+
},
95+
ExprKind::Path(rustc_hir::QPath::Resolved(None, path)) => {
96+
if let Res::Def(DefKind::Const, def_id) = path.res {
97+
let ty = cx.tcx.type_of(def_id);
98+
let con = cx
99+
.tcx
100+
.const_eval_poly(def_id)
101+
.ok()
102+
.map(|val| rustc_middle::ty::Const::from_value(cx.tcx, val, ty))
103+
.unwrap();
104+
let con = miri_to_const(con);
105+
con == Some(Constant::Int(1))
106+
} else {
107+
false
108+
}
109+
},
110+
_ => false,
111+
}
112+
}
113+
114+
fn is_str(ty: Ty<'_>) -> bool {
115+
match ty.kind {
116+
ty::Str => true,
117+
_ => false,
118+
}
119+
}
120+
121+
fn is_slice(ty: Ty<'_>) -> bool {
122+
match ty.kind {
123+
ty::Slice(..) | ty::Array(..) => true,
124+
_ => false,
125+
}
126+
}

src/lintlist/mod.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1879,6 +1879,13 @@ pub static ref ALL_LINTS: Vec<Lint> = vec![
18791879
deprecation: None,
18801880
module: "reference",
18811881
},
1882+
Lint {
1883+
name: "repeat_once",
1884+
group: "complexity",
1885+
desc: "using `.repeat(1)` instead of `String.clone()`, `str.to_string()` or `slice.to_vec()` ",
1886+
deprecation: None,
1887+
module: "repeat_once",
1888+
},
18821889
Lint {
18831890
name: "rest_pat_in_fully_bound_structs",
18841891
group: "restriction",

tests/ui/repeat_once.fixed

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
// run-rustfix
2+
#![warn(clippy::repeat_once)]
3+
#[allow(unused, clippy::many_single_char_names, clippy::redundant_clone)]
4+
fn main() {
5+
const N: usize = 1;
6+
let s = "str";
7+
let string = "String".to_string();
8+
let slice = [1; 5];
9+
10+
let a = [1; 5].to_vec();
11+
let b = slice.to_vec();
12+
let c = "hello".to_string();
13+
let d = "hi".to_string();
14+
let e = s.to_string();
15+
let f = string.clone();
16+
}

tests/ui/repeat_once.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
// run-rustfix
2+
#![warn(clippy::repeat_once)]
3+
#[allow(unused, clippy::many_single_char_names, clippy::redundant_clone)]
4+
fn main() {
5+
const N: usize = 1;
6+
let s = "str";
7+
let string = "String".to_string();
8+
let slice = [1; 5];
9+
10+
let a = [1; 5].repeat(1);
11+
let b = slice.repeat(1);
12+
let c = "hello".repeat(N);
13+
let d = "hi".repeat(1);
14+
let e = s.repeat(1);
15+
let f = string.repeat(1);
16+
}

tests/ui/repeat_once.stderr

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
error: calling `repeat(1)` on slice
2+
--> $DIR/repeat_once.rs:10:13
3+
|
4+
LL | let a = [1; 5].repeat(1);
5+
| ^^^^^^^^^^^^^^^^ help: consider using `.to_vec()` instead: `[1; 5].to_vec()`
6+
|
7+
= note: `-D clippy::repeat-once` implied by `-D warnings`
8+
9+
error: calling `repeat(1)` on slice
10+
--> $DIR/repeat_once.rs:11:13
11+
|
12+
LL | let b = slice.repeat(1);
13+
| ^^^^^^^^^^^^^^^ help: consider using `.to_vec()` instead: `slice.to_vec()`
14+
15+
error: calling `repeat(1)` on str
16+
--> $DIR/repeat_once.rs:12:13
17+
|
18+
LL | let c = "hello".repeat(N);
19+
| ^^^^^^^^^^^^^^^^^ help: consider using `.to_string()` instead: `"hello".to_string()`
20+
21+
error: calling `repeat(1)` on str
22+
--> $DIR/repeat_once.rs:13:13
23+
|
24+
LL | let d = "hi".repeat(1);
25+
| ^^^^^^^^^^^^^^ help: consider using `.to_string()` instead: `"hi".to_string()`
26+
27+
error: calling `repeat(1)` on str
28+
--> $DIR/repeat_once.rs:14:13
29+
|
30+
LL | let e = s.repeat(1);
31+
| ^^^^^^^^^^^ help: consider using `.to_string()` instead: `s.to_string()`
32+
33+
error: calling `repeat(1)` on a string literal
34+
--> $DIR/repeat_once.rs:15:13
35+
|
36+
LL | let f = string.repeat(1);
37+
| ^^^^^^^^^^^^^^^^ help: consider using `.clone()` instead: `string.clone()`
38+
39+
error: aborting due to 6 previous errors
40+

0 commit comments

Comments
 (0)