-
Notifications
You must be signed in to change notification settings - Fork 1.7k
Add new trivial_map_over_range
lint
#13034
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,134 @@ | ||
use crate::methods::MAP_WITH_UNUSED_ARGUMENT_OVER_RANGES; | ||
use clippy_config::msrvs::{self, Msrv}; | ||
use clippy_utils::diagnostics::span_lint_and_then; | ||
use clippy_utils::source::snippet_with_applicability; | ||
use clippy_utils::sugg::Sugg; | ||
use clippy_utils::{eager_or_lazy, higher, usage}; | ||
use rustc_ast::LitKind; | ||
use rustc_ast::ast::RangeLimits; | ||
use rustc_data_structures::packed::Pu128; | ||
use rustc_errors::Applicability; | ||
use rustc_hir::{Body, Closure, Expr, ExprKind}; | ||
use rustc_lint::LateContext; | ||
use rustc_span::Span; | ||
|
||
fn extract_count_with_applicability( | ||
cx: &LateContext<'_>, | ||
range: higher::Range<'_>, | ||
applicability: &mut Applicability, | ||
) -> Option<String> { | ||
let start = range.start?; | ||
let end = range.end?; | ||
// TODO: This doens't handle if either the start or end are negative literals, or if the start is | ||
// not a literal. In the first case, we need to be careful about how we handle computing the | ||
// count to avoid overflows. In the second, we may need to add parenthesis to make the | ||
// suggestion correct. | ||
if let ExprKind::Lit(lit) = start.kind | ||
&& let LitKind::Int(Pu128(lower_bound), _) = lit.node | ||
{ | ||
if let ExprKind::Lit(lit) = end.kind | ||
&& let LitKind::Int(Pu128(upper_bound), _) = lit.node | ||
{ | ||
// Here we can explicitly calculate the number of iterations | ||
let count = if upper_bound >= lower_bound { | ||
match range.limits { | ||
RangeLimits::HalfOpen => upper_bound - lower_bound, | ||
RangeLimits::Closed => (upper_bound - lower_bound).checked_add(1)?, | ||
} | ||
} else { | ||
0 | ||
}; | ||
return Some(format!("{count}")); | ||
} | ||
let end_snippet = Sugg::hir_with_applicability(cx, end, "...", applicability) | ||
.maybe_par() | ||
.into_string(); | ||
if lower_bound == 0 { | ||
if range.limits == RangeLimits::Closed { | ||
return Some(format!("{end_snippet} + 1")); | ||
rspencer01 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
return Some(end_snippet); | ||
} | ||
if range.limits == RangeLimits::Closed { | ||
return Some(format!("{end_snippet} - {}", lower_bound - 1)); | ||
} | ||
return Some(format!("{end_snippet} - {lower_bound}")); | ||
rspencer01 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
None | ||
} | ||
|
||
pub(super) fn check( | ||
cx: &LateContext<'_>, | ||
ex: &Expr<'_>, | ||
receiver: &Expr<'_>, | ||
arg: &Expr<'_>, | ||
msrv: &Msrv, | ||
method_call_span: Span, | ||
) { | ||
let mut applicability = Applicability::MaybeIncorrect; | ||
if let Some(range) = higher::Range::hir(receiver) | ||
&& let ExprKind::Closure(Closure { body, .. }) = arg.kind | ||
&& let body_hir = cx.tcx.hir().body(*body) | ||
&& let Body { | ||
params: [param], | ||
value: body_expr, | ||
} = body_hir | ||
&& !usage::BindingUsageFinder::are_params_used(cx, body_hir) | ||
&& let Some(count) = extract_count_with_applicability(cx, range, &mut applicability) | ||
{ | ||
let method_to_use_name; | ||
let new_span; | ||
let use_take; | ||
|
||
if eager_or_lazy::switch_to_eager_eval(cx, body_expr) { | ||
if msrv.meets(msrvs::REPEAT_N) { | ||
method_to_use_name = "repeat_n"; | ||
let body_snippet = snippet_with_applicability(cx, body_expr.span, "..", &mut applicability); | ||
new_span = (arg.span, format!("{body_snippet}, {count}")); | ||
use_take = false; | ||
} else { | ||
method_to_use_name = "repeat"; | ||
let body_snippet = snippet_with_applicability(cx, body_expr.span, "..", &mut applicability); | ||
new_span = (arg.span, body_snippet.to_string()); | ||
use_take = true; | ||
} | ||
} else if msrv.meets(msrvs::REPEAT_WITH) { | ||
method_to_use_name = "repeat_with"; | ||
new_span = (param.span, String::new()); | ||
use_take = true; | ||
} else { | ||
return; | ||
} | ||
|
||
// We need to provide nonempty parts to diag.multipart_suggestion so we | ||
// collate all our parts here and then remove those that are empty. | ||
let mut parts = vec![ | ||
( | ||
receiver.span.to(method_call_span), | ||
format!("std::iter::{method_to_use_name}"), | ||
), | ||
new_span, | ||
]; | ||
if use_take { | ||
parts.push((ex.span.shrink_to_hi(), format!(".take({count})"))); | ||
} | ||
|
||
span_lint_and_then( | ||
cx, | ||
MAP_WITH_UNUSED_ARGUMENT_OVER_RANGES, | ||
ex.span, | ||
"map of a closure that does not depend on its parameter over a range", | ||
|diag| { | ||
diag.multipart_suggestion( | ||
if use_take { | ||
format!("remove the explicit range and use `{method_to_use_name}` and `take`") | ||
} else { | ||
format!("remove the explicit range and use `{method_to_use_name}`") | ||
}, | ||
parts, | ||
applicability, | ||
); | ||
}, | ||
); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -67,6 +67,7 @@ mod map_err_ignore; | |
mod map_flatten; | ||
mod map_identity; | ||
mod map_unwrap_or; | ||
mod map_with_unused_argument_over_ranges; | ||
mod mut_mutex_lock; | ||
mod needless_as_bytes; | ||
mod needless_character_iteration; | ||
|
@@ -4218,6 +4219,40 @@ declare_clippy_lint! { | |
"combine `.map(_)` followed by `.all(identity)`/`.any(identity)` into a single call" | ||
} | ||
|
||
declare_clippy_lint! { | ||
/// ### What it does | ||
/// | ||
/// Checks for `Iterator::map` over ranges without using the parameter which | ||
/// could be more clearly expressed using `std::iter::repeat(...).take(...)` | ||
/// or `std::iter::repeat_n`. | ||
/// | ||
/// ### Why is this bad? | ||
/// | ||
/// It expresses the intent more clearly to `take` the correct number of times | ||
/// from a generating function than to apply a closure to each number in a | ||
/// range only to discard them. | ||
/// | ||
y21 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
/// ### Example | ||
/// | ||
/// ```no_run | ||
/// let random_numbers : Vec<_> = (0..10).map(|_| { 3 + 1 }).collect(); | ||
/// ``` | ||
/// Use instead: | ||
/// ```no_run | ||
/// let f : Vec<_> = std::iter::repeat( 3 + 1 ).take(10).collect(); | ||
/// ``` | ||
/// | ||
/// ### Known Issues | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good news: There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah, I'd missed that in the notes. Does There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Right, it doesn't. I only actually looked at ExactSizeIterator and made wrong assumptions about DoubleEndedIterator |
||
/// | ||
/// This lint may suggest replacing a `Map<Range>` with a `Take<RepeatWith>`. | ||
/// The former implements some traits that the latter does not, such as | ||
/// `DoubleEndedIterator`. | ||
#[clippy::version = "1.84.0"] | ||
pub MAP_WITH_UNUSED_ARGUMENT_OVER_RANGES, | ||
restriction, | ||
"map of a trivial closure (not dependent on parameter) over a range" | ||
} | ||
|
||
pub struct Methods { | ||
avoid_breaking_exported_api: bool, | ||
msrv: Msrv, | ||
|
@@ -4381,6 +4416,7 @@ impl_lint_pass!(Methods => [ | |
UNNECESSARY_MIN_OR_MAX, | ||
NEEDLESS_AS_BYTES, | ||
MAP_ALL_ANY_IDENTITY, | ||
MAP_WITH_UNUSED_ARGUMENT_OVER_RANGES, | ||
]); | ||
|
||
/// Extracts a method call name, args, and `Span` of the method name. | ||
|
@@ -4876,6 +4912,7 @@ impl Methods { | |
if name == "map" { | ||
unused_enumerate_index::check(cx, expr, recv, m_arg); | ||
map_clone::check(cx, expr, recv, m_arg, &self.msrv); | ||
map_with_unused_argument_over_ranges::check(cx, expr, recv, m_arg, &self.msrv, span); | ||
match method_call(recv) { | ||
Some((map_name @ ("iter" | "into_iter"), recv2, _, _, _)) => { | ||
iter_kv_map::check(cx, map_name, expr, recv2, m_arg, &self.msrv); | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
#![allow( | ||
unused, | ||
clippy::redundant_closure, | ||
clippy::reversed_empty_ranges, | ||
clippy::identity_op | ||
)] | ||
#![warn(clippy::map_with_unused_argument_over_ranges)] | ||
|
||
fn do_something() -> usize { | ||
todo!() | ||
} | ||
|
||
fn do_something_interesting(x: usize, y: usize) -> usize { | ||
todo!() | ||
} | ||
|
||
macro_rules! gen { | ||
() => { | ||
(0..10).map(|_| do_something()); | ||
}; | ||
} | ||
|
||
fn main() { | ||
// These should be raised | ||
std::iter::repeat_with(|| do_something()).take(10); | ||
std::iter::repeat_with(|| do_something()).take(10); | ||
std::iter::repeat_with(|| do_something()).take(11); | ||
std::iter::repeat_with(|| do_something()).take(7); | ||
std::iter::repeat_with(|| do_something()).take(8); | ||
std::iter::repeat_n(3, 10); | ||
std::iter::repeat_with(|| { | ||
let x = 3; | ||
x + 2 | ||
}).take(10); | ||
std::iter::repeat_with(|| do_something()).take(10).collect::<Vec<_>>(); | ||
let upper = 4; | ||
std::iter::repeat_with(|| do_something()).take(upper); | ||
let upper_fn = || 4; | ||
std::iter::repeat_with(|| do_something()).take(upper_fn()); | ||
std::iter::repeat_with(|| do_something()).take(upper_fn() + 1); | ||
std::iter::repeat_with(|| do_something()).take(upper_fn() - 2); | ||
std::iter::repeat_with(|| do_something()).take(upper_fn() - 1); | ||
(-3..9).map(|_| do_something()); | ||
std::iter::repeat_with(|| do_something()).take(0); | ||
std::iter::repeat_with(|| do_something()).take(1); | ||
std::iter::repeat_with(|| do_something()).take((1 << 4) - 0); | ||
// These should not be raised | ||
gen!(); | ||
let lower = 2; | ||
let lower_fn = || 2; | ||
(lower..upper_fn()).map(|_| do_something()); // Ranges not starting at zero not yet handled | ||
(lower_fn()..upper_fn()).map(|_| do_something()); // Ranges not starting at zero not yet handled | ||
(lower_fn()..=upper_fn()).map(|_| do_something()); // Ranges not starting at zero not yet handled | ||
(0..10).map(|x| do_something_interesting(x, 4)); // Actual map over range | ||
"Foobar".chars().map(|_| do_something()); // Not a map over range | ||
// i128::MAX == 340282366920938463463374607431768211455 | ||
(0..=340282366920938463463374607431768211455).map(|_: u128| do_something()); // Can't be replaced due to overflow | ||
} | ||
|
||
#[clippy::msrv = "1.27"] | ||
fn msrv_1_27() { | ||
(0..10).map(|_| do_something()); | ||
} | ||
|
||
#[clippy::msrv = "1.28"] | ||
fn msrv_1_28() { | ||
std::iter::repeat_with(|| do_something()).take(10); | ||
} | ||
|
||
#[clippy::msrv = "1.81"] | ||
fn msrv_1_82() { | ||
std::iter::repeat(3).take(10); | ||
} |
Uh oh!
There was an error while loading. Please reload this page.