Skip to content

Commit 67087a1

Browse files
committed
Auto merge of rust-lang#6717 - booleancoercion:master, r=llogiq
Add the from_str_radix_10 lint changelog: added the new `from_str_radix_10` which sometimes replaces calls to `primitive::from_str_radix` to `str::parse` This is ready to be merged, although maybe the category should be `pedantic` instead of `style`? I'm not sure where it fits better. Closes rust-lang#6713.
2 parents 2746632 + c4b8d87 commit 67087a1

File tree

5 files changed

+211
-0
lines changed

5 files changed

+211
-0
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2093,6 +2093,7 @@ Released 2018-09-13
20932093
[`forget_ref`]: https://rust-lang.github.io/rust-clippy/master/index.html#forget_ref
20942094
[`from_iter_instead_of_collect`]: https://rust-lang.github.io/rust-clippy/master/index.html#from_iter_instead_of_collect
20952095
[`from_over_into`]: https://rust-lang.github.io/rust-clippy/master/index.html#from_over_into
2096+
[`from_str_radix_10`]: https://rust-lang.github.io/rust-clippy/master/index.html#from_str_radix_10
20962097
[`future_not_send`]: https://rust-lang.github.io/rust-clippy/master/index.html#future_not_send
20972098
[`get_last_with_len`]: https://rust-lang.github.io/rust-clippy/master/index.html#get_last_with_len
20982099
[`get_unwrap`]: https://rust-lang.github.io/rust-clippy/master/index.html#get_unwrap

clippy_lints/src/from_str_radix_10.rs

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
use if_chain::if_chain;
2+
use rustc_errors::Applicability;
3+
use rustc_hir::{def, Expr, ExprKind, PrimTy, QPath, TyKind};
4+
use rustc_lint::{LateContext, LateLintPass};
5+
use rustc_middle::ty::Ty;
6+
use rustc_session::{declare_lint_pass, declare_tool_lint};
7+
use rustc_span::symbol::sym;
8+
9+
use crate::utils::is_type_diagnostic_item;
10+
use crate::utils::span_lint_and_sugg;
11+
use crate::utils::sugg::Sugg;
12+
13+
declare_clippy_lint! {
14+
/// **What it does:**
15+
/// Checks for function invocations of the form `primitive::from_str_radix(s, 10)`
16+
///
17+
/// **Why is this bad?**
18+
/// This specific common use case can be rewritten as `s.parse::<primitive>()`
19+
/// (and in most cases, the turbofish can be removed), which reduces code length
20+
/// and complexity.
21+
///
22+
/// **Known problems:**
23+
/// This lint may suggest using (&<expression>).parse() instead of <expression>.parse() directly
24+
/// in some cases, which is correct but adds unnecessary complexity to the code.
25+
///
26+
/// **Example:**
27+
///
28+
/// ```ignore
29+
/// let input: &str = get_input();
30+
/// let num = u16::from_str_radix(input, 10)?;
31+
/// ```
32+
/// Use instead:
33+
/// ```ignore
34+
/// let input: &str = get_input();
35+
/// let num: u16 = input.parse()?;
36+
/// ```
37+
pub FROM_STR_RADIX_10,
38+
style,
39+
"from_str_radix with radix 10"
40+
}
41+
42+
declare_lint_pass!(FromStrRadix10 => [FROM_STR_RADIX_10]);
43+
44+
impl LateLintPass<'tcx> for FromStrRadix10 {
45+
fn check_expr(&mut self, cx: &LateContext<'tcx>, exp: &Expr<'tcx>) {
46+
if_chain! {
47+
if let ExprKind::Call(maybe_path, arguments) = &exp.kind;
48+
if let ExprKind::Path(QPath::TypeRelative(ty, pathseg)) = &maybe_path.kind;
49+
50+
// check if the first part of the path is some integer primitive
51+
if let TyKind::Path(ty_qpath) = &ty.kind;
52+
let ty_res = cx.qpath_res(ty_qpath, ty.hir_id);
53+
if let def::Res::PrimTy(prim_ty) = ty_res;
54+
if matches!(prim_ty, PrimTy::Int(_) | PrimTy::Uint(_));
55+
56+
// check if the second part of the path indeed calls the associated
57+
// function `from_str_radix`
58+
if pathseg.ident.name.as_str() == "from_str_radix";
59+
60+
// check if the second argument is a primitive `10`
61+
if arguments.len() == 2;
62+
if let ExprKind::Lit(lit) = &arguments[1].kind;
63+
if let rustc_ast::ast::LitKind::Int(10, _) = lit.node;
64+
65+
then {
66+
let expr = if let ExprKind::AddrOf(_, _, expr) = &arguments[0].kind {
67+
let ty = cx.typeck_results().expr_ty(expr);
68+
if is_ty_stringish(cx, ty) {
69+
expr
70+
} else {
71+
&arguments[0]
72+
}
73+
} else {
74+
&arguments[0]
75+
};
76+
77+
let sugg = Sugg::hir_with_applicability(
78+
cx,
79+
expr,
80+
"<string>",
81+
&mut Applicability::MachineApplicable
82+
).maybe_par();
83+
84+
span_lint_and_sugg(
85+
cx,
86+
FROM_STR_RADIX_10,
87+
exp.span,
88+
"this call to `from_str_radix` can be replaced with a call to `str::parse`",
89+
"try",
90+
format!("{}.parse::<{}>()", sugg, prim_ty.name_str()),
91+
Applicability::MaybeIncorrect
92+
);
93+
}
94+
}
95+
}
96+
}
97+
98+
/// Checks if a Ty is `String` or `&str`
99+
fn is_ty_stringish(cx: &LateContext<'_>, ty: Ty<'_>) -> bool {
100+
is_type_diagnostic_item(cx, ty, sym::string_type) || is_type_diagnostic_item(cx, ty, sym::str)
101+
}

clippy_lints/src/lib.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,7 @@ mod floating_point_arithmetic;
211211
mod format;
212212
mod formatting;
213213
mod from_over_into;
214+
mod from_str_radix_10;
214215
mod functions;
215216
mod future_not_send;
216217
mod get_last_with_len;
@@ -639,6 +640,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
639640
&formatting::SUSPICIOUS_ELSE_FORMATTING,
640641
&formatting::SUSPICIOUS_UNARY_OP_FORMATTING,
641642
&from_over_into::FROM_OVER_INTO,
643+
&from_str_radix_10::FROM_STR_RADIX_10,
642644
&functions::DOUBLE_MUST_USE,
643645
&functions::MUST_USE_CANDIDATE,
644646
&functions::MUST_USE_UNIT,
@@ -1259,6 +1261,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
12591261
store.register_late_pass(move || box types::PtrAsPtr::new(msrv));
12601262
store.register_late_pass(|| box case_sensitive_file_extension_comparisons::CaseSensitiveFileExtensionComparisons);
12611263
store.register_late_pass(|| box redundant_slicing::RedundantSlicing);
1264+
store.register_late_pass(|| box from_str_radix_10::FromStrRadix10);
12621265

12631266
store.register_group(true, "clippy::restriction", Some("clippy_restriction"), vec![
12641267
LintId::of(&arithmetic::FLOAT_ARITHMETIC),
@@ -1472,6 +1475,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
14721475
LintId::of(&formatting::SUSPICIOUS_ELSE_FORMATTING),
14731476
LintId::of(&formatting::SUSPICIOUS_UNARY_OP_FORMATTING),
14741477
LintId::of(&from_over_into::FROM_OVER_INTO),
1478+
LintId::of(&from_str_radix_10::FROM_STR_RADIX_10),
14751479
LintId::of(&functions::DOUBLE_MUST_USE),
14761480
LintId::of(&functions::MUST_USE_UNIT),
14771481
LintId::of(&functions::NOT_UNSAFE_PTR_ARG_DEREF),
@@ -1728,6 +1732,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
17281732
LintId::of(&formatting::SUSPICIOUS_ELSE_FORMATTING),
17291733
LintId::of(&formatting::SUSPICIOUS_UNARY_OP_FORMATTING),
17301734
LintId::of(&from_over_into::FROM_OVER_INTO),
1735+
LintId::of(&from_str_radix_10::FROM_STR_RADIX_10),
17311736
LintId::of(&functions::DOUBLE_MUST_USE),
17321737
LintId::of(&functions::MUST_USE_UNIT),
17331738
LintId::of(&functions::RESULT_UNIT_ERR),

tests/ui/from_str_radix_10.rs

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
#![warn(clippy::from_str_radix_10)]
2+
3+
mod some_mod {
4+
// fake function that shouldn't trigger the lint
5+
pub fn from_str_radix(_: &str, _: u32) -> Result<(), std::num::ParseIntError> {
6+
unimplemented!()
7+
}
8+
}
9+
10+
// fake function that shouldn't trigger the lint
11+
fn from_str_radix(_: &str, _: u32) -> Result<(), std::num::ParseIntError> {
12+
unimplemented!()
13+
}
14+
15+
// to test parenthesis addition
16+
struct Test;
17+
18+
impl std::ops::Add<Test> for Test {
19+
type Output = &'static str;
20+
21+
fn add(self, _: Self) -> Self::Output {
22+
"304"
23+
}
24+
}
25+
26+
fn main() -> Result<(), Box<dyn std::error::Error>> {
27+
// all of these should trigger the lint
28+
u32::from_str_radix("30", 10)?;
29+
i64::from_str_radix("24", 10)?;
30+
isize::from_str_radix("100", 10)?;
31+
u8::from_str_radix("7", 10)?;
32+
u16::from_str_radix(&("10".to_owned() + "5"), 10)?;
33+
i128::from_str_radix(Test + Test, 10)?;
34+
35+
let string = "300";
36+
i32::from_str_radix(string, 10)?;
37+
38+
let stringier = "400".to_string();
39+
i32::from_str_radix(&stringier, 10)?;
40+
41+
// none of these should trigger the lint
42+
u16::from_str_radix("20", 3)?;
43+
i32::from_str_radix("45", 12)?;
44+
usize::from_str_radix("10", 16)?;
45+
i128::from_str_radix("10", 13)?;
46+
some_mod::from_str_radix("50", 10)?;
47+
some_mod::from_str_radix("50", 6)?;
48+
from_str_radix("50", 10)?;
49+
from_str_radix("50", 6)?;
50+
51+
Ok(())
52+
}

tests/ui/from_str_radix_10.stderr

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
error: this call to `from_str_radix` can be replaced with a call to `str::parse`
2+
--> $DIR/from_str_radix_10.rs:28:5
3+
|
4+
LL | u32::from_str_radix("30", 10)?;
5+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `"30".parse::<u32>()`
6+
|
7+
= note: `-D clippy::from-str-radix-10` implied by `-D warnings`
8+
9+
error: this call to `from_str_radix` can be replaced with a call to `str::parse`
10+
--> $DIR/from_str_radix_10.rs:29:5
11+
|
12+
LL | i64::from_str_radix("24", 10)?;
13+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `"24".parse::<i64>()`
14+
15+
error: this call to `from_str_radix` can be replaced with a call to `str::parse`
16+
--> $DIR/from_str_radix_10.rs:30:5
17+
|
18+
LL | isize::from_str_radix("100", 10)?;
19+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `"100".parse::<isize>()`
20+
21+
error: this call to `from_str_radix` can be replaced with a call to `str::parse`
22+
--> $DIR/from_str_radix_10.rs:31:5
23+
|
24+
LL | u8::from_str_radix("7", 10)?;
25+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `"7".parse::<u8>()`
26+
27+
error: this call to `from_str_radix` can be replaced with a call to `str::parse`
28+
--> $DIR/from_str_radix_10.rs:32:5
29+
|
30+
LL | u16::from_str_radix(&("10".to_owned() + "5"), 10)?;
31+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `(("10".to_owned() + "5")).parse::<u16>()`
32+
33+
error: this call to `from_str_radix` can be replaced with a call to `str::parse`
34+
--> $DIR/from_str_radix_10.rs:33:5
35+
|
36+
LL | i128::from_str_radix(Test + Test, 10)?;
37+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `(Test + Test).parse::<i128>()`
38+
39+
error: this call to `from_str_radix` can be replaced with a call to `str::parse`
40+
--> $DIR/from_str_radix_10.rs:36:5
41+
|
42+
LL | i32::from_str_radix(string, 10)?;
43+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `string.parse::<i32>()`
44+
45+
error: this call to `from_str_radix` can be replaced with a call to `str::parse`
46+
--> $DIR/from_str_radix_10.rs:39:5
47+
|
48+
LL | i32::from_str_radix(&stringier, 10)?;
49+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `stringier.parse::<i32>()`
50+
51+
error: aborting due to 8 previous errors
52+

0 commit comments

Comments
 (0)