Auto merge of #10970 - y21:read_line_without_trim, r=giraffate

new lint: `read_line_without_trim`

This adds a new lint that checks for calls to `Stdin::read_line` with a reference to a string that is then attempted to parse into an integer type without first trimming it, which is always going to fail at runtime.
This is something that I've seen happen a lot to beginners, because it's easy to run into when following the example of chapter 2 in the book where it shows how to program a guessing game.
It would be nice if we could point beginners to clippy and tell them "let's see what clippy has to say" and have clippy explain to them why it fails 👀

I think this lint can later be "generalized" to work not just for `Stdin` but also any `BufRead` (which seems to be where the guarantee about the trailing newline comes from) and also, matching/comparing it to a string slice that doesn't end in a newline character (e.g. `input == "foo"` is always going to fail)

changelog: new lint: [`read_line_without_trim`]
This commit is contained in:
bors 2023-07-05 00:13:59 +00:00
commit 1e656d8d6d
7 changed files with 255 additions and 0 deletions

View file

@ -0,0 +1,36 @@
//@run-rustfix
#![allow(unused)]
#![warn(clippy::read_line_without_trim)]
fn main() {
let mut input = String::new();
std::io::stdin().read_line(&mut input).unwrap();
input.pop();
let _x: i32 = input.parse().unwrap(); // don't trigger here, newline character is popped
let mut input = String::new();
std::io::stdin().read_line(&mut input).unwrap();
let _x: i32 = input.trim_end().parse().unwrap();
let mut input = String::new();
std::io::stdin().read_line(&mut input).unwrap();
let _x = input.trim_end().parse::<i32>().unwrap();
let mut input = String::new();
std::io::stdin().read_line(&mut input).unwrap();
let _x = input.trim_end().parse::<u32>().unwrap();
let mut input = String::new();
std::io::stdin().read_line(&mut input).unwrap();
let _x = input.trim_end().parse::<f32>().unwrap();
let mut input = String::new();
std::io::stdin().read_line(&mut input).unwrap();
let _x = input.trim_end().parse::<bool>().unwrap();
let mut input = String::new();
std::io::stdin().read_line(&mut input).unwrap();
// this is actually ok, so don't lint here
let _x = input.parse::<String>().unwrap();
}

View file

@ -0,0 +1,36 @@
//@run-rustfix
#![allow(unused)]
#![warn(clippy::read_line_without_trim)]
fn main() {
let mut input = String::new();
std::io::stdin().read_line(&mut input).unwrap();
input.pop();
let _x: i32 = input.parse().unwrap(); // don't trigger here, newline character is popped
let mut input = String::new();
std::io::stdin().read_line(&mut input).unwrap();
let _x: i32 = input.parse().unwrap();
let mut input = String::new();
std::io::stdin().read_line(&mut input).unwrap();
let _x = input.parse::<i32>().unwrap();
let mut input = String::new();
std::io::stdin().read_line(&mut input).unwrap();
let _x = input.parse::<u32>().unwrap();
let mut input = String::new();
std::io::stdin().read_line(&mut input).unwrap();
let _x = input.parse::<f32>().unwrap();
let mut input = String::new();
std::io::stdin().read_line(&mut input).unwrap();
let _x = input.parse::<bool>().unwrap();
let mut input = String::new();
std::io::stdin().read_line(&mut input).unwrap();
// this is actually ok, so don't lint here
let _x = input.parse::<String>().unwrap();
}

View file

@ -0,0 +1,73 @@
error: calling `.parse()` without trimming the trailing newline character
--> $DIR/read_line_without_trim.rs:14:25
|
LL | let _x: i32 = input.parse().unwrap();
| ----- ^^^^^^^
| |
| help: try: `input.trim_end()`
|
note: call to `.read_line()` here, which leaves a trailing newline character in the buffer, which in turn will cause `.parse()` to fail
--> $DIR/read_line_without_trim.rs:13:5
|
LL | std::io::stdin().read_line(&mut input).unwrap();
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
= note: `-D clippy::read-line-without-trim` implied by `-D warnings`
error: calling `.parse()` without trimming the trailing newline character
--> $DIR/read_line_without_trim.rs:18:20
|
LL | let _x = input.parse::<i32>().unwrap();
| ----- ^^^^^^^^^^^^^^
| |
| help: try: `input.trim_end()`
|
note: call to `.read_line()` here, which leaves a trailing newline character in the buffer, which in turn will cause `.parse()` to fail
--> $DIR/read_line_without_trim.rs:17:5
|
LL | std::io::stdin().read_line(&mut input).unwrap();
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
error: calling `.parse()` without trimming the trailing newline character
--> $DIR/read_line_without_trim.rs:22:20
|
LL | let _x = input.parse::<u32>().unwrap();
| ----- ^^^^^^^^^^^^^^
| |
| help: try: `input.trim_end()`
|
note: call to `.read_line()` here, which leaves a trailing newline character in the buffer, which in turn will cause `.parse()` to fail
--> $DIR/read_line_without_trim.rs:21:5
|
LL | std::io::stdin().read_line(&mut input).unwrap();
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
error: calling `.parse()` without trimming the trailing newline character
--> $DIR/read_line_without_trim.rs:26:20
|
LL | let _x = input.parse::<f32>().unwrap();
| ----- ^^^^^^^^^^^^^^
| |
| help: try: `input.trim_end()`
|
note: call to `.read_line()` here, which leaves a trailing newline character in the buffer, which in turn will cause `.parse()` to fail
--> $DIR/read_line_without_trim.rs:25:5
|
LL | std::io::stdin().read_line(&mut input).unwrap();
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
error: calling `.parse()` without trimming the trailing newline character
--> $DIR/read_line_without_trim.rs:30:20
|
LL | let _x = input.parse::<bool>().unwrap();
| ----- ^^^^^^^^^^^^^^^
| |
| help: try: `input.trim_end()`
|
note: call to `.read_line()` here, which leaves a trailing newline character in the buffer, which in turn will cause `.parse()` to fail
--> $DIR/read_line_without_trim.rs:29:5
|
LL | std::io::stdin().read_line(&mut input).unwrap();
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
error: aborting due to 5 previous errors