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