• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 use clippy_utils::{
2     diagnostics::span_lint_and_then, get_trait_def_id, higher::VecArgs, macros::root_macro_call_first_node,
3     source::snippet_opt, ty::implements_trait,
4 };
5 use rustc_ast::{LitIntType, LitKind, UintTy};
6 use rustc_errors::Applicability;
7 use rustc_hir::{Expr, ExprKind, LangItem, QPath};
8 use rustc_lint::{LateContext, LateLintPass};
9 use rustc_session::{declare_lint_pass, declare_tool_lint};
10 use std::fmt::{self, Display, Formatter};
11 
12 declare_clippy_lint! {
13     /// ### What it does
14     /// Checks for `Vec` or array initializations that contain only one range.
15     ///
16     /// ### Why is this bad?
17     /// This is almost always incorrect, as it will result in a `Vec` that has only one element.
18     /// Almost always, the programmer intended for it to include all elements in the range or for
19     /// the end of the range to be the length instead.
20     ///
21     /// ### Example
22     /// ```rust
23     /// let x = [0..200];
24     /// ```
25     /// Use instead:
26     /// ```rust
27     /// // If it was intended to include every element in the range...
28     /// let x = (0..200).collect::<Vec<i32>>();
29     /// // ...Or if 200 was meant to be the len
30     /// let x = [0; 200];
31     /// ```
32     #[clippy::version = "1.72.0"]
33     pub SINGLE_RANGE_IN_VEC_INIT,
34     suspicious,
35     "checks for initialization of `Vec` or arrays which consist of a single range"
36 }
37 declare_lint_pass!(SingleRangeInVecInit => [SINGLE_RANGE_IN_VEC_INIT]);
38 
39 enum SuggestedType {
40     Vec,
41     Array,
42 }
43 
44 impl SuggestedType {
starts_with(&self) -> &'static str45     fn starts_with(&self) -> &'static str {
46         if matches!(self, SuggestedType::Vec) {
47             "vec!"
48         } else {
49             "["
50         }
51     }
52 
ends_with(&self) -> &'static str53     fn ends_with(&self) -> &'static str {
54         if matches!(self, SuggestedType::Vec) { "" } else { "]" }
55     }
56 }
57 
58 impl Display for SuggestedType {
fmt(&self, f: &mut Formatter<'_>) -> fmt::Result59     fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
60         if matches!(&self, SuggestedType::Vec) {
61             write!(f, "a `Vec`")
62         } else {
63             write!(f, "an array")
64         }
65     }
66 }
67 
68 impl LateLintPass<'_> for SingleRangeInVecInit {
check_expr<'tcx>(&mut self, cx: &LateContext<'tcx>, expr: &Expr<'tcx>)69     fn check_expr<'tcx>(&mut self, cx: &LateContext<'tcx>, expr: &Expr<'tcx>) {
70         // inner_expr: `vec![0..200]` or `[0..200]`
71         //                   ^^^^^^       ^^^^^^^
72         // span: `vec![0..200]` or `[0..200]`
73         //        ^^^^^^^^^^^^      ^^^^^^^^
74         // suggested_type: What to print, "an array" or "a `Vec`"
75         let (inner_expr, span, suggested_type) = if let ExprKind::Array([inner_expr]) = expr.kind
76             && !expr.span.from_expansion()
77         {
78             (inner_expr, expr.span, SuggestedType::Array)
79         } else if let Some(macro_call) = root_macro_call_first_node(cx, expr)
80             && let Some(VecArgs::Vec([expr])) = VecArgs::hir(cx, expr)
81         {
82             (expr, macro_call.span, SuggestedType::Vec)
83         } else {
84             return;
85         };
86 
87         let ExprKind::Struct(QPath::LangItem(lang_item, ..), [start, end], None) = inner_expr.kind else {
88             return;
89         };
90 
91         if matches!(lang_item, LangItem::Range)
92             && let ty = cx.typeck_results().expr_ty(start.expr)
93             && let Some(snippet) = snippet_opt(cx, span)
94             // `is_from_proc_macro` will skip any `vec![]`. Let's not!
95             && snippet.starts_with(suggested_type.starts_with())
96             && snippet.ends_with(suggested_type.ends_with())
97             && let Some(start_snippet) = snippet_opt(cx, start.span)
98             && let Some(end_snippet) = snippet_opt(cx, end.span)
99         {
100             let should_emit_every_value = if let Some(step_def_id) = get_trait_def_id(cx, &["core", "iter", "Step"])
101                 && implements_trait(cx, ty, step_def_id, &[])
102             {
103                 true
104             } else {
105                 false
106             };
107             let should_emit_of_len = if let Some(copy_def_id) = cx.tcx.lang_items().copy_trait()
108                 && implements_trait(cx, ty, copy_def_id, &[])
109                 && let ExprKind::Lit(lit_kind) = end.expr.kind
110                 && let LitKind::Int(.., suffix_type) = lit_kind.node
111                 && let LitIntType::Unsigned(UintTy::Usize) | LitIntType::Unsuffixed = suffix_type
112             {
113                 true
114             } else {
115                 false
116             };
117 
118             if should_emit_every_value || should_emit_of_len {
119                 span_lint_and_then(
120                     cx,
121                     SINGLE_RANGE_IN_VEC_INIT,
122                     span,
123                     &format!("{suggested_type} of `Range` that is only one element"),
124                     |diag| {
125                         if should_emit_every_value {
126                             diag.span_suggestion(
127                                 span,
128                                 "if you wanted a `Vec` that contains the entire range, try",
129                                 format!("({start_snippet}..{end_snippet}).collect::<std::vec::Vec<{ty}>>()"),
130                                 Applicability::MaybeIncorrect,
131                             );
132                         }
133 
134                         if should_emit_of_len {
135                             diag.span_suggestion(
136                                 inner_expr.span,
137                                 format!("if you wanted {suggested_type} of len {end_snippet}, try"),
138                                 format!("{start_snippet}; {end_snippet}"),
139                                 Applicability::MaybeIncorrect,
140                             );
141                         }
142                     },
143                 );
144             }
145         }
146     }
147 }
148