]>
Commit | Line | Data |
---|---|---|
f20569fa XL |
1 | //! lint on using `x.get(x.len() - 1)` instead of `x.last()` |
2 | ||
cdc7bbd5 XL |
3 | use clippy_utils::diagnostics::span_lint_and_sugg; |
4 | use clippy_utils::source::snippet_with_applicability; | |
5 | use clippy_utils::ty::is_type_diagnostic_item; | |
6 | use clippy_utils::SpanlessEq; | |
f20569fa XL |
7 | use if_chain::if_chain; |
8 | use rustc_ast::ast::LitKind; | |
9 | use rustc_errors::Applicability; | |
10 | use rustc_hir::{BinOpKind, Expr, ExprKind}; | |
11 | use rustc_lint::{LateContext, LateLintPass}; | |
12 | use rustc_session::{declare_lint_pass, declare_tool_lint}; | |
13 | use rustc_span::source_map::Spanned; | |
14 | use rustc_span::sym; | |
15 | ||
16 | declare_clippy_lint! { | |
17 | /// **What it does:** Checks for using `x.get(x.len() - 1)` instead of | |
18 | /// `x.last()`. | |
19 | /// | |
20 | /// **Why is this bad?** Using `x.last()` is easier to read and has the same | |
21 | /// result. | |
22 | /// | |
23 | /// Note that using `x[x.len() - 1]` is semantically different from | |
24 | /// `x.last()`. Indexing into the array will panic on out-of-bounds | |
25 | /// accesses, while `x.get()` and `x.last()` will return `None`. | |
26 | /// | |
27 | /// There is another lint (get_unwrap) that covers the case of using | |
28 | /// `x.get(index).unwrap()` instead of `x[index]`. | |
29 | /// | |
30 | /// **Known problems:** None. | |
31 | /// | |
32 | /// **Example:** | |
33 | /// | |
34 | /// ```rust | |
35 | /// // Bad | |
36 | /// let x = vec![2, 3, 5]; | |
37 | /// let last_element = x.get(x.len() - 1); | |
38 | /// | |
39 | /// // Good | |
40 | /// let x = vec![2, 3, 5]; | |
41 | /// let last_element = x.last(); | |
42 | /// ``` | |
43 | pub GET_LAST_WITH_LEN, | |
44 | complexity, | |
45 | "Using `x.get(x.len() - 1)` when `x.last()` is correct and simpler" | |
46 | } | |
47 | ||
48 | declare_lint_pass!(GetLastWithLen => [GET_LAST_WITH_LEN]); | |
49 | ||
50 | impl<'tcx> LateLintPass<'tcx> for GetLastWithLen { | |
51 | fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { | |
52 | if_chain! { | |
53 | // Is a method call | |
cdc7bbd5 | 54 | if let ExprKind::MethodCall(path, _, args, _) = expr.kind; |
f20569fa XL |
55 | |
56 | // Method name is "get" | |
57 | if path.ident.name == sym!(get); | |
58 | ||
59 | // Argument 0 (the struct we're calling the method on) is a vector | |
60 | if let Some(struct_calling_on) = args.get(0); | |
61 | let struct_ty = cx.typeck_results().expr_ty(struct_calling_on); | |
62 | if is_type_diagnostic_item(cx, struct_ty, sym::vec_type); | |
63 | ||
64 | // Argument to "get" is a subtraction | |
65 | if let Some(get_index_arg) = args.get(1); | |
66 | if let ExprKind::Binary( | |
67 | Spanned { | |
68 | node: BinOpKind::Sub, | |
69 | .. | |
70 | }, | |
71 | lhs, | |
72 | rhs, | |
73 | ) = &get_index_arg.kind; | |
74 | ||
75 | // LHS of subtraction is "x.len()" | |
76 | if let ExprKind::MethodCall(arg_lhs_path, _, lhs_args, _) = &lhs.kind; | |
77 | if arg_lhs_path.ident.name == sym!(len); | |
78 | if let Some(arg_lhs_struct) = lhs_args.get(0); | |
79 | ||
80 | // The two vectors referenced (x in x.get(...) and in x.len()) | |
81 | if SpanlessEq::new(cx).eq_expr(struct_calling_on, arg_lhs_struct); | |
82 | ||
83 | // RHS of subtraction is 1 | |
84 | if let ExprKind::Lit(rhs_lit) = &rhs.kind; | |
85 | if let LitKind::Int(1, ..) = rhs_lit.node; | |
86 | ||
87 | then { | |
88 | let mut applicability = Applicability::MachineApplicable; | |
89 | let vec_name = snippet_with_applicability( | |
90 | cx, | |
91 | struct_calling_on.span, "vec", | |
92 | &mut applicability, | |
93 | ); | |
94 | ||
95 | span_lint_and_sugg( | |
96 | cx, | |
97 | GET_LAST_WITH_LEN, | |
98 | expr.span, | |
99 | &format!("accessing last element with `{0}.get({0}.len() - 1)`", vec_name), | |
100 | "try", | |
101 | format!("{}.last()", vec_name), | |
102 | applicability, | |
103 | ); | |
104 | } | |
105 | } | |
106 | } | |
107 | } |