]>
Commit | Line | Data |
---|---|---|
f20569fa XL |
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 | } |