Skip to content

Commit 8d6ce31

Browse files
committed
Add new lint rewind_instead_of_seek_to_start
Signed-off-by: Doru-Florin Blanzeanu <[email protected]>
1 parent 5b09d4e commit 8d6ce31

9 files changed

+311
-0
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4195,6 +4195,7 @@ Released 2018-09-13
41954195
[`result_unwrap_used`]: https://rust-lang.github.io/rust-clippy/master/index.html#result_unwrap_used
41964196
[`return_self_not_must_use`]: https://rust-lang.github.io/rust-clippy/master/index.html#return_self_not_must_use
41974197
[`reversed_empty_ranges`]: https://rust-lang.github.io/rust-clippy/master/index.html#reversed_empty_ranges
4198+
[`rewind_instead_of_seek_to_start`]: https://rust-lang.github.io/rust-clippy/master/index.html#rewind_instead_of_seek_to_start
41984199
[`same_functions_in_if_condition`]: https://rust-lang.github.io/rust-clippy/master/index.html#same_functions_in_if_condition
41994200
[`same_item_push`]: https://rust-lang.github.io/rust-clippy/master/index.html#same_item_push
42004201
[`same_name_method`]: https://rust-lang.github.io/rust-clippy/master/index.html#same_name_method

clippy_lints/src/declared_lints.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -361,6 +361,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
361361
crate::methods::RANGE_ZIP_WITH_LEN_INFO,
362362
crate::methods::REPEAT_ONCE_INFO,
363363
crate::methods::RESULT_MAP_OR_INTO_OPTION_INFO,
364+
crate::methods::REWIND_INSTEAD_OF_SEEK_TO_START_INFO,
364365
crate::methods::SEARCH_IS_SOME_INFO,
365366
crate::methods::SHOULD_IMPLEMENT_TRAIT_INFO,
366367
crate::methods::SINGLE_CHAR_ADD_STR_INFO,

clippy_lints/src/methods/mod.rs

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ mod or_then_unwrap;
6868
mod path_buf_push_overwrite;
6969
mod range_zip_with_len;
7070
mod repeat_once;
71+
mod rewind_instead_of_seek_to_start;
7172
mod search_is_some;
7273
mod single_char_add_str;
7374
mod single_char_insert_string;
@@ -3066,6 +3067,37 @@ declare_clippy_lint! {
30663067
"iterating on map using `iter` when `keys` or `values` would do"
30673068
}
30683069

3070+
declare_clippy_lint! {
3071+
/// ### What it does
3072+
///
3073+
/// Checks for jumps to the start of a stream that implements `Seek`
3074+
/// and uses the `seek` method providing `Start` as parameter.
3075+
///
3076+
/// ### Why is this bad?
3077+
///
3078+
/// Readability. There is a specific method that was implemented for
3079+
/// this exact scenario.
3080+
///
3081+
/// ### Example
3082+
/// ```rust
3083+
/// # use std::io;
3084+
/// fn foo<T: io::Seek>(t: &mut T) {
3085+
/// t.seek(io::SeekFrom::Start(0));
3086+
/// }
3087+
/// ```
3088+
/// Use instead:
3089+
/// ```rust
3090+
/// # use std::io;
3091+
/// fn foo<T: io::Seek>(t: &mut T) {
3092+
/// t.rewind();
3093+
/// }
3094+
/// ```
3095+
#[clippy::version = "1.66.0"]
3096+
pub REWIND_INSTEAD_OF_SEEK_TO_START,
3097+
complexity,
3098+
"jumping to the start of stream using `seek` method"
3099+
}
3100+
30693101
pub struct Methods {
30703102
avoid_breaking_exported_api: bool,
30713103
msrv: Option<RustcVersion>,
@@ -3190,6 +3222,7 @@ impl_lint_pass!(Methods => [
31903222
VEC_RESIZE_TO_ZERO,
31913223
VERBOSE_FILE_READS,
31923224
ITER_KV_MAP,
3225+
REWIND_INSTEAD_OF_SEEK_TO_START,
31933226
]);
31943227

31953228
/// Extracts a method call name, args, and `Span` of the method name.
@@ -3604,6 +3637,9 @@ impl Methods {
36043637
("resize", [count_arg, default_arg]) => {
36053638
vec_resize_to_zero::check(cx, expr, count_arg, default_arg, span);
36063639
},
3640+
("seek", [arg]) => {
3641+
rewind_instead_of_seek_to_start::check(cx, expr, recv, arg, span);
3642+
},
36073643
("sort", []) => {
36083644
stable_sort_primitive::check(cx, expr, recv);
36093645
},
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
use clippy_utils::diagnostics::span_lint_and_then;
2+
use clippy_utils::ty::implements_trait;
3+
use clippy_utils::{get_trait_def_id, match_def_path, paths};
4+
use rustc_ast::ast::{LitIntType, LitKind};
5+
use rustc_errors::Applicability;
6+
use rustc_hir::{Expr, ExprKind};
7+
use rustc_lint::LateContext;
8+
use rustc_span::Span;
9+
10+
use super::REWIND_INSTEAD_OF_SEEK_TO_START;
11+
12+
pub(super) fn check<'tcx>(
13+
cx: &LateContext<'tcx>,
14+
expr: &'tcx Expr<'_>,
15+
recv: &'tcx Expr<'_>,
16+
arg: &'tcx Expr<'_>,
17+
name_span: Span,
18+
) {
19+
// Get receiver type
20+
let ty = cx.typeck_results().expr_ty(recv).peel_refs();
21+
22+
if let Some(seek_trait_id) = get_trait_def_id(cx, &paths::STD_IO_SEEK) &&
23+
implements_trait(cx, ty, seek_trait_id, &[]) &&
24+
let ExprKind::Call(func, args1) = arg.kind &&
25+
let ExprKind::Path(ref path) = func.kind &&
26+
let Some(def_id) = cx.qpath_res(path, func.hir_id).opt_def_id() &&
27+
match_def_path(cx, def_id, &paths::STD_IO_SEEKFROM_START) &&
28+
args1.len() == 1 &&
29+
let ExprKind::Lit(ref lit) = args1[0].kind &&
30+
let LitKind::Int(0, LitIntType::Unsuffixed) = lit.node
31+
{
32+
let method_call_span = expr.span.with_lo(name_span.lo());
33+
span_lint_and_then(
34+
cx,
35+
REWIND_INSTEAD_OF_SEEK_TO_START,
36+
method_call_span,
37+
"used `seek` to go to the start of the stream",
38+
|diag| {
39+
let app = Applicability::MachineApplicable;
40+
41+
diag.span_suggestion(method_call_span, "replace with", "rewind()", app);
42+
},
43+
);
44+
}
45+
}

clippy_utils/src/paths.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,8 @@ pub const STDERR: [&str; 4] = ["std", "io", "stdio", "stderr"];
115115
pub const STDOUT: [&str; 4] = ["std", "io", "stdio", "stdout"];
116116
pub const CONVERT_IDENTITY: [&str; 3] = ["core", "convert", "identity"];
117117
pub const STD_FS_CREATE_DIR: [&str; 3] = ["std", "fs", "create_dir"];
118+
pub const STD_IO_SEEK: [&str; 3] = ["std", "io", "Seek"];
119+
pub const STD_IO_SEEKFROM_START: [&str; 4] = ["std", "io", "SeekFrom", "Start"];
118120
pub const STRING_AS_MUT_STR: [&str; 4] = ["alloc", "string", "String", "as_mut_str"];
119121
pub const STRING_AS_STR: [&str; 4] = ["alloc", "string", "String", "as_str"];
120122
pub const STRING_NEW: [&str; 4] = ["alloc", "string", "String", "new"];
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
### What it does
2+
3+
Checks for jumps to the start of a stream that implements `Seek`
4+
and uses the `seek` method providing `Start` as parameter.
5+
6+
### Why is this bad?
7+
8+
Readability. There is a specific method that was implemented for
9+
this exact scenario.
10+
11+
### Example
12+
```
13+
fn foo<T: io::Seek>(t: &mut T) {
14+
t.seek(io::SeekFrom::Start(0));
15+
}
16+
```
17+
Use instead:
18+
```
19+
fn foo<T: io::Seek>(t: &mut T) {
20+
t.rewind();
21+
}
22+
```
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
// run-rustfix
2+
#![allow(unused)]
3+
#![warn(clippy::rewind_instead_of_seek_to_start)]
4+
5+
use std::fs::OpenOptions;
6+
use std::io::{Read, Seek, SeekFrom, Write};
7+
8+
struct StructWithSeekMethod {}
9+
10+
impl StructWithSeekMethod {
11+
fn seek(&mut self, from: SeekFrom) {}
12+
}
13+
14+
trait MySeekTrait {
15+
fn seek(&mut self, from: SeekFrom) {}
16+
}
17+
18+
struct StructWithSeekTrait {}
19+
impl MySeekTrait for StructWithSeekTrait {}
20+
21+
// This should NOT trigger clippy warning because
22+
// StructWithSeekMethod does not implement std::io::Seek;
23+
fn seek_to_start_false_method(t: &mut StructWithSeekMethod) {
24+
t.seek(SeekFrom::Start(0));
25+
}
26+
27+
// This should NOT trigger clippy warning because
28+
// StructWithSeekMethod does not implement std::io::Seek;
29+
fn seek_to_start_method_owned_false<T>(mut t: StructWithSeekMethod) {
30+
t.seek(SeekFrom::Start(0));
31+
}
32+
33+
// This should NOT trigger clippy warning because
34+
// StructWithSeekMethod does not implement std::io::Seek;
35+
fn seek_to_start_false_trait(t: &mut StructWithSeekTrait) {
36+
t.seek(SeekFrom::Start(0));
37+
}
38+
39+
// This should NOT trigger clippy warning because
40+
// StructWithSeekMethod does not implement std::io::Seek;
41+
fn seek_to_start_false_trait_owned<T>(mut t: StructWithSeekTrait) {
42+
t.seek(SeekFrom::Start(0));
43+
}
44+
45+
// This should NOT trigger clippy warning because
46+
// StructWithSeekMethod does not implement std::io::Seek;
47+
fn seek_to_start_false_trait_bound<T: MySeekTrait>(t: &mut T) {
48+
t.seek(SeekFrom::Start(0));
49+
}
50+
51+
// This should trigger clippy warning
52+
fn seek_to_start<T: Seek>(t: &mut T) {
53+
t.rewind();
54+
}
55+
56+
// This should trigger clippy warning
57+
fn owned_seek_to_start<T: Seek>(mut t: T) {
58+
t.rewind();
59+
}
60+
61+
// This should NOT trigger clippy warning because
62+
// it does not seek to start
63+
fn seek_to_5<T: Seek>(t: &mut T) {
64+
t.seek(SeekFrom::Start(5));
65+
}
66+
67+
// This should NOT trigger clippy warning because
68+
// it does not seek to start
69+
fn seek_to_end<T: Seek>(t: &mut T) {
70+
t.seek(SeekFrom::End(0));
71+
}
72+
73+
fn main() {
74+
let mut f = OpenOptions::new()
75+
.write(true)
76+
.read(true)
77+
.create(true)
78+
.open("foo.txt")
79+
.unwrap();
80+
81+
let mut my_struct_trait = StructWithSeekTrait {};
82+
seek_to_start_false_trait_bound(&mut my_struct_trait);
83+
84+
let hello = "Hello!\n";
85+
write!(f, "{hello}").unwrap();
86+
seek_to_5(&mut f);
87+
seek_to_end(&mut f);
88+
seek_to_start(&mut f);
89+
90+
let mut buf = String::new();
91+
f.read_to_string(&mut buf).unwrap();
92+
93+
assert_eq!(&buf, hello);
94+
}
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
// run-rustfix
2+
#![allow(unused)]
3+
#![warn(clippy::rewind_instead_of_seek_to_start)]
4+
5+
use std::fs::OpenOptions;
6+
use std::io::{Read, Seek, SeekFrom, Write};
7+
8+
struct StructWithSeekMethod {}
9+
10+
impl StructWithSeekMethod {
11+
fn seek(&mut self, from: SeekFrom) {}
12+
}
13+
14+
trait MySeekTrait {
15+
fn seek(&mut self, from: SeekFrom) {}
16+
}
17+
18+
struct StructWithSeekTrait {}
19+
impl MySeekTrait for StructWithSeekTrait {}
20+
21+
// This should NOT trigger clippy warning because
22+
// StructWithSeekMethod does not implement std::io::Seek;
23+
fn seek_to_start_false_method(t: &mut StructWithSeekMethod) {
24+
t.seek(SeekFrom::Start(0));
25+
}
26+
27+
// This should NOT trigger clippy warning because
28+
// StructWithSeekMethod does not implement std::io::Seek;
29+
fn seek_to_start_method_owned_false<T>(mut t: StructWithSeekMethod) {
30+
t.seek(SeekFrom::Start(0));
31+
}
32+
33+
// This should NOT trigger clippy warning because
34+
// StructWithSeekMethod does not implement std::io::Seek;
35+
fn seek_to_start_false_trait(t: &mut StructWithSeekTrait) {
36+
t.seek(SeekFrom::Start(0));
37+
}
38+
39+
// This should NOT trigger clippy warning because
40+
// StructWithSeekMethod does not implement std::io::Seek;
41+
fn seek_to_start_false_trait_owned<T>(mut t: StructWithSeekTrait) {
42+
t.seek(SeekFrom::Start(0));
43+
}
44+
45+
// This should NOT trigger clippy warning because
46+
// StructWithSeekMethod does not implement std::io::Seek;
47+
fn seek_to_start_false_trait_bound<T: MySeekTrait>(t: &mut T) {
48+
t.seek(SeekFrom::Start(0));
49+
}
50+
51+
// This should trigger clippy warning
52+
fn seek_to_start<T: Seek>(t: &mut T) {
53+
t.seek(SeekFrom::Start(0));
54+
}
55+
56+
// This should trigger clippy warning
57+
fn owned_seek_to_start<T: Seek>(mut t: T) {
58+
t.seek(SeekFrom::Start(0));
59+
}
60+
61+
// This should NOT trigger clippy warning because
62+
// it does not seek to start
63+
fn seek_to_5<T: Seek>(t: &mut T) {
64+
t.seek(SeekFrom::Start(5));
65+
}
66+
67+
// This should NOT trigger clippy warning because
68+
// it does not seek to start
69+
fn seek_to_end<T: Seek>(t: &mut T) {
70+
t.seek(SeekFrom::End(0));
71+
}
72+
73+
fn main() {
74+
let mut f = OpenOptions::new()
75+
.write(true)
76+
.read(true)
77+
.create(true)
78+
.open("foo.txt")
79+
.unwrap();
80+
81+
let mut my_struct_trait = StructWithSeekTrait {};
82+
seek_to_start_false_trait_bound(&mut my_struct_trait);
83+
84+
let hello = "Hello!\n";
85+
write!(f, "{hello}").unwrap();
86+
seek_to_5(&mut f);
87+
seek_to_end(&mut f);
88+
seek_to_start(&mut f);
89+
90+
let mut buf = String::new();
91+
f.read_to_string(&mut buf).unwrap();
92+
93+
assert_eq!(&buf, hello);
94+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
error: used `seek` to go to the start of the stream
2+
--> $DIR/rewind_instead_of_seek_to_start.rs:53:7
3+
|
4+
LL | t.seek(SeekFrom::Start(0));
5+
| ^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `rewind()`
6+
|
7+
= note: `-D clippy::rewind-instead-of-seek-to-start` implied by `-D warnings`
8+
9+
error: used `seek` to go to the start of the stream
10+
--> $DIR/rewind_instead_of_seek_to_start.rs:58:7
11+
|
12+
LL | t.seek(SeekFrom::Start(0));
13+
| ^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `rewind()`
14+
15+
error: aborting due to 2 previous errors
16+

0 commit comments

Comments
 (0)