Skip to content

Commit a8750e6

Browse files
fix: single_range_in_vec_init now detects all kind of ranges (#16508)
1 parent 15e0ce9 commit a8750e6

File tree

5 files changed

+169
-15
lines changed

5 files changed

+169
-15
lines changed

clippy_lints/src/single_range_in_vec_init.rs

Lines changed: 55 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
use clippy_utils::diagnostics::span_lint_and_then;
2-
use clippy_utils::higher::VecArgs;
2+
use clippy_utils::higher::{Range, VecArgs};
33
use clippy_utils::macros::root_macro_call_first_node;
44
use clippy_utils::source::{SpanRangeExt, snippet_with_context};
55
use clippy_utils::ty::implements_trait;
66
use clippy_utils::{is_no_std_crate, sym};
7-
use rustc_ast::{LitIntType, LitKind, UintTy};
7+
use rustc_ast::{LitIntType, LitKind, RangeLimits, UintTy};
88
use rustc_errors::Applicability;
9-
use rustc_hir::{Expr, ExprKind, StructTailExpr};
9+
use rustc_hir::{Expr, ExprKind};
1010
use rustc_lint::{LateContext, LateLintPass};
1111
use rustc_session::declare_lint_pass;
1212
use 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
}

tests/ui/single_range_in_vec_init.1.fixed

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,3 +82,18 @@ fn issue16044() {
8282
let input = (0..as_i32!(10)).collect::<std::vec::Vec<i32>>();
8383
//~^ single_range_in_vec_init
8484
}
85+
86+
// Test cases for all range types
87+
fn issue16508() {
88+
// Half-open ranges with end only
89+
(0..10).collect::<std::vec::Vec<i32>>();
90+
//~^ single_range_in_vec_init
91+
(0..10).collect::<std::vec::Vec<i32>>();
92+
//~^ single_range_in_vec_init
93+
94+
// Closed ranges
95+
(0..=10).collect::<std::vec::Vec<i32>>();
96+
//~^ single_range_in_vec_init
97+
(0..=10).collect::<std::vec::Vec<i32>>();
98+
//~^ single_range_in_vec_init
99+
}

tests/ui/single_range_in_vec_init.2.fixed

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,3 +82,18 @@ fn issue16044() {
8282
let input = (0..as_i32!(10)).collect::<std::vec::Vec<i32>>();
8383
//~^ single_range_in_vec_init
8484
}
85+
86+
// Test cases for all range types
87+
fn issue16508() {
88+
// Half-open ranges with end only
89+
[0; 10];
90+
//~^ single_range_in_vec_init
91+
vec![0; 10];
92+
//~^ single_range_in_vec_init
93+
94+
// Closed ranges
95+
[0; 10];
96+
//~^ single_range_in_vec_init
97+
vec![0; 10];
98+
//~^ single_range_in_vec_init
99+
}

tests/ui/single_range_in_vec_init.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,3 +82,18 @@ fn issue16044() {
8282
let input = vec![0..as_i32!(10)];
8383
//~^ single_range_in_vec_init
8484
}
85+
86+
// Test cases for all range types
87+
fn issue16508() {
88+
// Half-open ranges with end only
89+
[..10];
90+
//~^ single_range_in_vec_init
91+
vec![..10];
92+
//~^ single_range_in_vec_init
93+
94+
// Closed ranges
95+
[0..=10];
96+
//~^ single_range_in_vec_init
97+
vec![0..=10];
98+
//~^ single_range_in_vec_init
99+
}

tests/ui/single_range_in_vec_init.stderr

Lines changed: 69 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -172,5 +172,73 @@ LL - let input = vec![0..as_i32!(10)];
172172
LL + let input = (0..as_i32!(10)).collect::<std::vec::Vec<i32>>();
173173
|
174174

175-
error: aborting due to 11 previous errors
175+
error: an array of `Range` that is only one element
176+
--> tests/ui/single_range_in_vec_init.rs:89:5
177+
|
178+
LL | [..10];
179+
| ^^^^^^
180+
|
181+
help: if you wanted a `Vec` that contains the entire range, try
182+
|
183+
LL - [..10];
184+
LL + (0..10).collect::<std::vec::Vec<i32>>();
185+
|
186+
help: if you wanted an array of len 10, try
187+
|
188+
LL - [..10];
189+
LL + [0; 10];
190+
|
191+
192+
error: a `Vec` of `Range` that is only one element
193+
--> tests/ui/single_range_in_vec_init.rs:91:5
194+
|
195+
LL | vec![..10];
196+
| ^^^^^^^^^^
197+
|
198+
help: if you wanted a `Vec` that contains the entire range, try
199+
|
200+
LL - vec![..10];
201+
LL + (0..10).collect::<std::vec::Vec<i32>>();
202+
|
203+
help: if you wanted a `Vec` of len 10, try
204+
|
205+
LL - vec![..10];
206+
LL + vec![0; 10];
207+
|
208+
209+
error: an array of `Range` that is only one element
210+
--> tests/ui/single_range_in_vec_init.rs:95:5
211+
|
212+
LL | [0..=10];
213+
| ^^^^^^^^
214+
|
215+
help: if you wanted a `Vec` that contains the entire range, try
216+
|
217+
LL - [0..=10];
218+
LL + (0..=10).collect::<std::vec::Vec<i32>>();
219+
|
220+
help: if you wanted an array of len 10, try
221+
|
222+
LL - [0..=10];
223+
LL + [0; 10];
224+
|
225+
226+
error: a `Vec` of `Range` that is only one element
227+
--> tests/ui/single_range_in_vec_init.rs:97:5
228+
|
229+
LL | vec![0..=10];
230+
| ^^^^^^^^^^^^
231+
|
232+
help: if you wanted a `Vec` that contains the entire range, try
233+
|
234+
LL - vec![0..=10];
235+
LL + (0..=10).collect::<std::vec::Vec<i32>>();
236+
|
237+
help: if you wanted a `Vec` of len 10, try
238+
|
239+
LL - vec![0..=10];
240+
LL + vec![0; 10];
241+
|
242+
243+
error: aborting due to 15 previous errors
176244

0 commit comments

Comments
 (0)