11use clippy_utils:: diagnostics:: span_lint_and_then;
2- use clippy_utils:: higher:: VecArgs ;
2+ use clippy_utils:: higher:: { Range , VecArgs } ;
33use clippy_utils:: macros:: root_macro_call_first_node;
44use clippy_utils:: source:: { SpanRangeExt , snippet_with_context} ;
55use clippy_utils:: ty:: implements_trait;
66use clippy_utils:: { is_no_std_crate, sym} ;
7- use rustc_ast:: { LitIntType , LitKind , UintTy } ;
7+ use rustc_ast:: { LitIntType , LitKind , RangeLimits , UintTy } ;
88use rustc_errors:: Applicability ;
9- use rustc_hir:: { Expr , ExprKind , StructTailExpr } ;
9+ use rustc_hir:: { Expr , ExprKind } ;
1010use rustc_lint:: { LateContext , LateLintPass } ;
1111use rustc_session:: declare_lint_pass;
1212use rustc_span:: DesugaringKind ;
@@ -87,31 +87,50 @@ impl LateLintPass<'_> for SingleRangeInVecInit {
8787 return ;
8888 } ;
8989
90- let ExprKind :: Struct ( _, [ start, end] , StructTailExpr :: None ) = inner_expr. kind else {
90+ // Use higher::Range to support all range types: a..b, ..b, a.., a..=b
91+ let Some ( range) = Range :: hir ( cx, inner_expr) else {
9192 return ;
9293 } ;
9394
94- if inner_expr. span . is_desugaring ( DesugaringKind :: RangeExpr )
95- && let ty = cx. typeck_results ( ) . expr_ty ( start. expr )
96- && let Some ( snippet) = span. get_source_text ( cx)
95+ if !inner_expr. span . is_desugaring ( DesugaringKind :: RangeExpr ) {
96+ return ;
97+ }
98+
99+ // Get type from whichever part exists
100+ let ty = range. start . or ( range. end )
101+ . map ( |e| cx. typeck_results ( ) . expr_ty ( e) )
102+ . unwrap_or_else ( || cx. typeck_results ( ) . expr_ty ( inner_expr) ) ;
103+
104+ if let Some ( snippet) = span. get_source_text ( cx)
97105 // `is_from_proc_macro` will skip any `vec![]`. Let's not!
98106 && snippet. starts_with ( suggested_type. starts_with ( ) )
99107 && snippet. ends_with ( suggested_type. ends_with ( ) )
100108 {
101109 let mut applicability = Applicability :: MaybeIncorrect ;
102- let ( start_snippet, _) = snippet_with_context ( cx, start. expr . span , span. ctxt ( ) , ".." , & mut applicability) ;
103- let ( end_snippet, _) = snippet_with_context ( cx, end. expr . span , span. ctxt ( ) , ".." , & mut applicability) ;
104110
111+ // Generate snippets for start and end
112+ let start_snippet = range. start . map ( |e| {
113+ snippet_with_context ( cx, e. span , span. ctxt ( ) , ".." , & mut applicability) . 0
114+ } ) ;
115+ let end_snippet = range. end . map ( |e| {
116+ snippet_with_context ( cx, e. span , span. ctxt ( ) , ".." , & mut applicability) . 0
117+ } ) ;
118+
119+ // Determine if we can suggest collect (for ranges that implement RangeStepIterator)
105120 let should_emit_every_value = if let Some ( step_def_id) = cx. tcx . get_diagnostic_item ( sym:: range_step)
106121 && implements_trait ( cx, ty, step_def_id, & [ ] )
107122 {
108123 true
109124 } else {
110125 false
111126 } ;
112- let should_emit_of_len = if let Some ( copy_def_id) = cx. tcx . lang_items ( ) . copy_trait ( )
127+
128+ // Determine if we can suggest [start; len] or vec![start; len]
129+ // For this, we need an end value that is a usize literal
130+ let should_emit_of_len = if let Some ( end_expr) = range. end
131+ && let Some ( copy_def_id) = cx. tcx . lang_items ( ) . copy_trait ( )
113132 && implements_trait ( cx, ty, copy_def_id, & [ ] )
114- && let ExprKind :: Lit ( lit_kind) = end . expr . kind
133+ && let ExprKind :: Lit ( lit_kind) = end_expr . kind
115134 && let LitKind :: Int ( .., suffix_type) = lit_kind. node
116135 && let LitIntType :: Unsigned ( UintTy :: Usize ) | LitIntType :: Unsuffixed = suffix_type
117136 {
@@ -127,20 +146,42 @@ impl LateLintPass<'_> for SingleRangeInVecInit {
127146 span,
128147 format ! ( "{suggested_type} of `Range` that is only one element" ) ,
129148 |diag| {
149+ // Build the collect suggestion text
150+ // Note: RangeTo (..end) and RangeFrom (start..) need special handling:
151+ // - ..end is not an iterator, so we use (0..end)
152+ // - start.. is infinite, so we don't suggest collect for it
153+ let collect_range_text = match ( start_snippet. as_deref ( ) , end_snippet. as_deref ( ) , range. limits ) {
154+ ( Some ( start) , Some ( end) , RangeLimits :: HalfOpen ) => format ! ( "{start}..{end}" ) ,
155+ ( Some ( start) , Some ( end) , RangeLimits :: Closed ) => format ! ( "{start}..={end}" ) ,
156+ ( None , Some ( end) , _) => format ! ( "0..{end}" ) , // ..end becomes 0..end
157+ // start.. is infinite, skip collect suggestion for it
158+ _ => return ,
159+ } ;
160+
161+ // Suggest using .collect() for the entire range
130162 if should_emit_every_value && !is_no_std_crate ( cx) {
131163 diag. span_suggestion (
132164 span,
133165 "if you wanted a `Vec` that contains the entire range, try" ,
134- format ! ( "({start_snippet}..{end_snippet }).collect::<std::vec::Vec<{ty}>>()" ) ,
166+ format ! ( "({collect_range_text }).collect::<std::vec::Vec<{ty}>>()" ) ,
135167 applicability,
136168 ) ;
137169 }
138170
171+ // Suggest using array/vec initialization for length
139172 if should_emit_of_len {
173+ // For ranges like `..end`, use 0 as default start
174+ // For ranges like `start..end`, use the start value
175+ let start_value = start_snippet. as_deref ( ) . unwrap_or ( "0" ) ;
176+
177+ // For `..end`, end is the length
178+ // For `start..end`, end is the length (and start is the value to repeat)
179+ let len_value = end_snippet. as_deref ( ) . expect ( "should have end value" ) ;
180+
140181 diag. span_suggestion (
141182 inner_expr. span ,
142- format ! ( "if you wanted {suggested_type} of len {end_snippet }, try" ) ,
143- format ! ( "{start_snippet }; {end_snippet }" ) ,
183+ format ! ( "if you wanted {suggested_type} of len {len_value }, try" ) ,
184+ format ! ( "{start_value }; {len_value }" ) ,
144185 applicability,
145186 ) ;
146187 }
0 commit comments