about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG.md1
-rw-r--r--clippy_lints/src/from_str_radix_10.rs101
-rw-r--r--clippy_lints/src/lib.rs5
-rw-r--r--tests/ui/from_str_radix_10.rs52
-rw-r--r--tests/ui/from_str_radix_10.stderr52
5 files changed, 211 insertions, 0 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index c59eae5d1c2..2ec8b3d98f8 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2093,6 +2093,7 @@ Released 2018-09-13
 [`forget_ref`]: https://rust-lang.github.io/rust-clippy/master/index.html#forget_ref
 [`from_iter_instead_of_collect`]: https://rust-lang.github.io/rust-clippy/master/index.html#from_iter_instead_of_collect
 [`from_over_into`]: https://rust-lang.github.io/rust-clippy/master/index.html#from_over_into
+[`from_str_radix_10`]: https://rust-lang.github.io/rust-clippy/master/index.html#from_str_radix_10
 [`future_not_send`]: https://rust-lang.github.io/rust-clippy/master/index.html#future_not_send
 [`get_last_with_len`]: https://rust-lang.github.io/rust-clippy/master/index.html#get_last_with_len
 [`get_unwrap`]: https://rust-lang.github.io/rust-clippy/master/index.html#get_unwrap
diff --git a/clippy_lints/src/from_str_radix_10.rs b/clippy_lints/src/from_str_radix_10.rs
new file mode 100644
index 00000000000..0933f983014
--- /dev/null
+++ b/clippy_lints/src/from_str_radix_10.rs
@@ -0,0 +1,101 @@
+use if_chain::if_chain;
+use rustc_errors::Applicability;
+use rustc_hir::{def, Expr, ExprKind, PrimTy, QPath, TyKind};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_middle::ty::Ty;
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_span::symbol::sym;
+
+use crate::utils::is_type_diagnostic_item;
+use crate::utils::span_lint_and_sugg;
+use crate::utils::sugg::Sugg;
+
+declare_clippy_lint! {
+    /// **What it does:**
+    /// Checks for function invocations of the form `primitive::from_str_radix(s, 10)`
+    ///
+    /// **Why is this bad?**
+    /// This specific common use case can be rewritten as `s.parse::<primitive>()`
+    /// (and in most cases, the turbofish can be removed), which reduces code length
+    /// and complexity.
+    ///
+    /// **Known problems:**
+    /// This lint may suggest using (&<expression>).parse() instead of <expression>.parse() directly
+    /// in some cases, which is correct but adds unnecessary complexity to the code.
+    ///
+    /// **Example:**
+    ///
+    /// ```ignore
+    /// let input: &str = get_input();
+    /// let num = u16::from_str_radix(input, 10)?;
+    /// ```
+    /// Use instead:
+    /// ```ignore
+    /// let input: &str = get_input();
+    /// let num: u16 = input.parse()?;
+    /// ```
+    pub FROM_STR_RADIX_10,
+    style,
+    "from_str_radix with radix 10"
+}
+
+declare_lint_pass!(FromStrRadix10 => [FROM_STR_RADIX_10]);
+
+impl LateLintPass<'tcx> for FromStrRadix10 {
+    fn check_expr(&mut self, cx: &LateContext<'tcx>, exp: &Expr<'tcx>) {
+        if_chain! {
+            if let ExprKind::Call(maybe_path, arguments) = &exp.kind;
+            if let ExprKind::Path(QPath::TypeRelative(ty, pathseg)) = &maybe_path.kind;
+
+            // check if the first part of the path is some integer primitive
+            if let TyKind::Path(ty_qpath) = &ty.kind;
+            let ty_res = cx.qpath_res(ty_qpath, ty.hir_id);
+            if let def::Res::PrimTy(prim_ty) = ty_res;
+            if matches!(prim_ty, PrimTy::Int(_) | PrimTy::Uint(_));
+
+            // check if the second part of the path indeed calls the associated
+            // function `from_str_radix`
+            if pathseg.ident.name.as_str() == "from_str_radix";
+
+            // check if the second argument is a primitive `10`
+            if arguments.len() == 2;
+            if let ExprKind::Lit(lit) = &arguments[1].kind;
+            if let rustc_ast::ast::LitKind::Int(10, _) = lit.node;
+
+            then {
+                let expr = if let ExprKind::AddrOf(_, _, expr) = &arguments[0].kind {
+                    let ty = cx.typeck_results().expr_ty(expr);
+                    if is_ty_stringish(cx, ty) {
+                        expr
+                    } else {
+                        &arguments[0]
+                    }
+                } else {
+                    &arguments[0]
+                };
+
+                let sugg = Sugg::hir_with_applicability(
+                    cx,
+                    expr,
+                    "<string>",
+                    &mut Applicability::MachineApplicable
+                ).maybe_par();
+
+                span_lint_and_sugg(
+                    cx,
+                    FROM_STR_RADIX_10,
+                    exp.span,
+                    "this call to `from_str_radix` can be replaced with a call to `str::parse`",
+                    "try",
+                    format!("{}.parse::<{}>()", sugg, prim_ty.name_str()),
+                    Applicability::MaybeIncorrect
+                );
+            }
+        }
+    }
+}
+
+/// Checks if a Ty is `String` or `&str`
+fn is_ty_stringish(cx: &LateContext<'_>, ty: Ty<'_>) -> bool {
+    is_type_diagnostic_item(cx, ty, sym::string_type) || is_type_diagnostic_item(cx, ty, sym::str)
+}
diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs
index 40727980693..67e490584e8 100644
--- a/clippy_lints/src/lib.rs
+++ b/clippy_lints/src/lib.rs
@@ -211,6 +211,7 @@ mod floating_point_arithmetic;
 mod format;
 mod formatting;
 mod from_over_into;
+mod from_str_radix_10;
 mod functions;
 mod future_not_send;
 mod get_last_with_len;
@@ -639,6 +640,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
         &formatting::SUSPICIOUS_ELSE_FORMATTING,
         &formatting::SUSPICIOUS_UNARY_OP_FORMATTING,
         &from_over_into::FROM_OVER_INTO,
+        &from_str_radix_10::FROM_STR_RADIX_10,
         &functions::DOUBLE_MUST_USE,
         &functions::MUST_USE_CANDIDATE,
         &functions::MUST_USE_UNIT,
@@ -1259,6 +1261,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
     store.register_late_pass(move || box types::PtrAsPtr::new(msrv));
     store.register_late_pass(|| box case_sensitive_file_extension_comparisons::CaseSensitiveFileExtensionComparisons);
     store.register_late_pass(|| box redundant_slicing::RedundantSlicing);
+    store.register_late_pass(|| box from_str_radix_10::FromStrRadix10);
 
     store.register_group(true, "clippy::restriction", Some("clippy_restriction"), vec![
         LintId::of(&arithmetic::FLOAT_ARITHMETIC),
@@ -1472,6 +1475,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
         LintId::of(&formatting::SUSPICIOUS_ELSE_FORMATTING),
         LintId::of(&formatting::SUSPICIOUS_UNARY_OP_FORMATTING),
         LintId::of(&from_over_into::FROM_OVER_INTO),
+        LintId::of(&from_str_radix_10::FROM_STR_RADIX_10),
         LintId::of(&functions::DOUBLE_MUST_USE),
         LintId::of(&functions::MUST_USE_UNIT),
         LintId::of(&functions::NOT_UNSAFE_PTR_ARG_DEREF),
@@ -1728,6 +1732,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
         LintId::of(&formatting::SUSPICIOUS_ELSE_FORMATTING),
         LintId::of(&formatting::SUSPICIOUS_UNARY_OP_FORMATTING),
         LintId::of(&from_over_into::FROM_OVER_INTO),
+        LintId::of(&from_str_radix_10::FROM_STR_RADIX_10),
         LintId::of(&functions::DOUBLE_MUST_USE),
         LintId::of(&functions::MUST_USE_UNIT),
         LintId::of(&functions::RESULT_UNIT_ERR),
diff --git a/tests/ui/from_str_radix_10.rs b/tests/ui/from_str_radix_10.rs
new file mode 100644
index 00000000000..2f2ea04847a
--- /dev/null
+++ b/tests/ui/from_str_radix_10.rs
@@ -0,0 +1,52 @@
+#![warn(clippy::from_str_radix_10)]
+
+mod some_mod {
+    // fake function that shouldn't trigger the lint
+    pub fn from_str_radix(_: &str, _: u32) -> Result<(), std::num::ParseIntError> {
+        unimplemented!()
+    }
+}
+
+// fake function that shouldn't trigger the lint
+fn from_str_radix(_: &str, _: u32) -> Result<(), std::num::ParseIntError> {
+    unimplemented!()
+}
+
+// to test parenthesis addition
+struct Test;
+
+impl std::ops::Add<Test> for Test {
+    type Output = &'static str;
+
+    fn add(self, _: Self) -> Self::Output {
+        "304"
+    }
+}
+
+fn main() -> Result<(), Box<dyn std::error::Error>> {
+    // all of these should trigger the lint
+    u32::from_str_radix("30", 10)?;
+    i64::from_str_radix("24", 10)?;
+    isize::from_str_radix("100", 10)?;
+    u8::from_str_radix("7", 10)?;
+    u16::from_str_radix(&("10".to_owned() + "5"), 10)?;
+    i128::from_str_radix(Test + Test, 10)?;
+
+    let string = "300";
+    i32::from_str_radix(string, 10)?;
+
+    let stringier = "400".to_string();
+    i32::from_str_radix(&stringier, 10)?;
+
+    // none of these should trigger the lint
+    u16::from_str_radix("20", 3)?;
+    i32::from_str_radix("45", 12)?;
+    usize::from_str_radix("10", 16)?;
+    i128::from_str_radix("10", 13)?;
+    some_mod::from_str_radix("50", 10)?;
+    some_mod::from_str_radix("50", 6)?;
+    from_str_radix("50", 10)?;
+    from_str_radix("50", 6)?;
+
+    Ok(())
+}
diff --git a/tests/ui/from_str_radix_10.stderr b/tests/ui/from_str_radix_10.stderr
new file mode 100644
index 00000000000..471bf52a9a7
--- /dev/null
+++ b/tests/ui/from_str_radix_10.stderr
@@ -0,0 +1,52 @@
+error: this call to `from_str_radix` can be replaced with a call to `str::parse`
+  --> $DIR/from_str_radix_10.rs:28:5
+   |
+LL |     u32::from_str_radix("30", 10)?;
+   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `"30".parse::<u32>()`
+   |
+   = note: `-D clippy::from-str-radix-10` implied by `-D warnings`
+
+error: this call to `from_str_radix` can be replaced with a call to `str::parse`
+  --> $DIR/from_str_radix_10.rs:29:5
+   |
+LL |     i64::from_str_radix("24", 10)?;
+   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `"24".parse::<i64>()`
+
+error: this call to `from_str_radix` can be replaced with a call to `str::parse`
+  --> $DIR/from_str_radix_10.rs:30:5
+   |
+LL |     isize::from_str_radix("100", 10)?;
+   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `"100".parse::<isize>()`
+
+error: this call to `from_str_radix` can be replaced with a call to `str::parse`
+  --> $DIR/from_str_radix_10.rs:31:5
+   |
+LL |     u8::from_str_radix("7", 10)?;
+   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `"7".parse::<u8>()`
+
+error: this call to `from_str_radix` can be replaced with a call to `str::parse`
+  --> $DIR/from_str_radix_10.rs:32:5
+   |
+LL |     u16::from_str_radix(&("10".to_owned() + "5"), 10)?;
+   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `(("10".to_owned() + "5")).parse::<u16>()`
+
+error: this call to `from_str_radix` can be replaced with a call to `str::parse`
+  --> $DIR/from_str_radix_10.rs:33:5
+   |
+LL |     i128::from_str_radix(Test + Test, 10)?;
+   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `(Test + Test).parse::<i128>()`
+
+error: this call to `from_str_radix` can be replaced with a call to `str::parse`
+  --> $DIR/from_str_radix_10.rs:36:5
+   |
+LL |     i32::from_str_radix(string, 10)?;
+   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `string.parse::<i32>()`
+
+error: this call to `from_str_radix` can be replaced with a call to `str::parse`
+  --> $DIR/from_str_radix_10.rs:39:5
+   |
+LL |     i32::from_str_radix(&stringier, 10)?;
+   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `stringier.parse::<i32>()`
+
+error: aborting due to 8 previous errors
+