Merge commit 'f4850f7292' into clippyup
This commit is contained in:
parent
3597ed5a09
commit
46c5a5d234
895 changed files with 8247 additions and 18379 deletions
|
|
@ -9,8 +9,8 @@ use rustc_lint::LateContext;
|
|||
use rustc_lint::Lint;
|
||||
|
||||
/// Wrapper fn for `CHARS_NEXT_CMP` and `CHARS_LAST_CMP` lints with `unwrap()`.
|
||||
pub(super) fn check<'tcx>(
|
||||
cx: &LateContext<'tcx>,
|
||||
pub(super) fn check(
|
||||
cx: &LateContext<'_>,
|
||||
info: &crate::methods::BinaryExprInfo<'_>,
|
||||
chain_methods: &[&str],
|
||||
lint: &'static Lint,
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ use rustc_lint::LateContext;
|
|||
use super::CHARS_LAST_CMP;
|
||||
|
||||
/// Checks for the `CHARS_LAST_CMP` lint.
|
||||
pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, info: &crate::methods::BinaryExprInfo<'_>) -> bool {
|
||||
pub(super) fn check(cx: &LateContext<'_>, info: &crate::methods::BinaryExprInfo<'_>) -> bool {
|
||||
if chars_cmp::check(cx, info, &["chars", "last"], CHARS_LAST_CMP, "ends_with") {
|
||||
true
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ use rustc_lint::LateContext;
|
|||
use super::CHARS_LAST_CMP;
|
||||
|
||||
/// Checks for the `CHARS_LAST_CMP` lint with `unwrap()`.
|
||||
pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, info: &crate::methods::BinaryExprInfo<'_>) -> bool {
|
||||
pub(super) fn check(cx: &LateContext<'_>, info: &crate::methods::BinaryExprInfo<'_>) -> bool {
|
||||
if chars_cmp_with_unwrap::check(cx, info, &["chars", "last", "unwrap"], CHARS_LAST_CMP, "ends_with") {
|
||||
true
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -3,6 +3,6 @@ use rustc_lint::LateContext;
|
|||
use super::CHARS_NEXT_CMP;
|
||||
|
||||
/// Checks for the `CHARS_NEXT_CMP` lint.
|
||||
pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, info: &crate::methods::BinaryExprInfo<'_>) -> bool {
|
||||
pub(super) fn check(cx: &LateContext<'_>, info: &crate::methods::BinaryExprInfo<'_>) -> bool {
|
||||
crate::methods::chars_cmp::check(cx, info, &["chars", "next"], CHARS_NEXT_CMP, "starts_with")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,6 @@ use rustc_lint::LateContext;
|
|||
use super::CHARS_NEXT_CMP;
|
||||
|
||||
/// Checks for the `CHARS_NEXT_CMP` lint with `unwrap()`.
|
||||
pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, info: &crate::methods::BinaryExprInfo<'_>) -> bool {
|
||||
pub(super) fn check(cx: &LateContext<'_>, info: &crate::methods::BinaryExprInfo<'_>) -> bool {
|
||||
crate::methods::chars_cmp_with_unwrap::check(cx, info, &["chars", "next", "unwrap"], CHARS_NEXT_CMP, "starts_with")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ pub(super) fn check<'tcx>(
|
|||
// If the parent node's `to` argument is the same as the `to` argument
|
||||
// of the last replace call in the current chain, don't lint as it was already linted
|
||||
if let Some(parent) = get_parent_expr(cx, expr)
|
||||
&& let Some(("replace", _, [current_from, current_to], _)) = method_call(parent)
|
||||
&& let Some(("replace", _, [current_from, current_to], _, _)) = method_call(parent)
|
||||
&& eq_expr_value(cx, to, current_to)
|
||||
&& from_kind == cx.typeck_results().expr_ty(current_from).peel_refs().kind()
|
||||
{
|
||||
|
|
@ -48,7 +48,7 @@ fn collect_replace_calls<'tcx>(
|
|||
let mut from_args = VecDeque::new();
|
||||
|
||||
let _: Option<()> = for_each_expr(expr, |e| {
|
||||
if let Some(("replace", _, [from, to], _)) = method_call(e) {
|
||||
if let Some(("replace", _, [from, to], _, _)) = method_call(e) {
|
||||
if eq_expr_value(cx, to_arg, to) && cx.typeck_results().expr_ty(from).peel_refs().is_char() {
|
||||
methods.push_front(e);
|
||||
from_args.push_front(from);
|
||||
|
|
@ -78,7 +78,7 @@ fn check_consecutive_replace_calls<'tcx>(
|
|||
.collect();
|
||||
let app = Applicability::MachineApplicable;
|
||||
let earliest_replace_call = replace_methods.methods.front().unwrap();
|
||||
if let Some((_, _, [..], span_lo)) = method_call(earliest_replace_call) {
|
||||
if let Some((_, _, [..], span_lo, _)) = method_call(earliest_replace_call) {
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
COLLAPSIBLE_STR_REPLACE,
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
use clippy_utils::diagnostics::span_lint_and_help;
|
||||
use clippy_utils::is_in_test_function;
|
||||
use clippy_utils::is_in_cfg_test;
|
||||
use clippy_utils::ty::is_type_diagnostic_item;
|
||||
use rustc_hir as hir;
|
||||
use rustc_lint::LateContext;
|
||||
|
|
@ -18,16 +18,16 @@ pub(super) fn check(
|
|||
let obj_ty = cx.typeck_results().expr_ty(recv).peel_refs();
|
||||
|
||||
let mess = if is_type_diagnostic_item(cx, obj_ty, sym::Option) && !is_err {
|
||||
Some((EXPECT_USED, "an Option", "None", ""))
|
||||
Some((EXPECT_USED, "an `Option`", "None", ""))
|
||||
} else if is_type_diagnostic_item(cx, obj_ty, sym::Result) {
|
||||
Some((EXPECT_USED, "a Result", if is_err { "Ok" } else { "Err" }, "an "))
|
||||
Some((EXPECT_USED, "a `Result`", if is_err { "Ok" } else { "Err" }, "an "))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let method = if is_err { "expect_err" } else { "expect" };
|
||||
|
||||
if allow_expect_in_tests && is_in_test_function(cx.tcx, expr.hir_id) {
|
||||
if allow_expect_in_tests && is_in_cfg_test(cx.tcx, expr.hir_id) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -36,7 +36,7 @@ pub(super) fn check(
|
|||
cx,
|
||||
lint,
|
||||
expr.span,
|
||||
&format!("used `{method}()` on `{kind}` value"),
|
||||
&format!("used `{method}()` on {kind} value"),
|
||||
None,
|
||||
&format!("if this value is {none_prefix}`{none_value}`, it will panic"),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ use super::MANUAL_FILTER_MAP;
|
|||
use super::MANUAL_FIND_MAP;
|
||||
use super::OPTION_FILTER_MAP;
|
||||
|
||||
fn is_method<'tcx>(cx: &LateContext<'tcx>, expr: &hir::Expr<'_>, method_name: Symbol) -> bool {
|
||||
fn is_method(cx: &LateContext<'_>, expr: &hir::Expr<'_>, method_name: Symbol) -> bool {
|
||||
match &expr.kind {
|
||||
hir::ExprKind::Path(QPath::TypeRelative(_, mname)) => mname.ident.name == method_name,
|
||||
hir::ExprKind::Path(QPath::Resolved(_, segments)) => {
|
||||
|
|
@ -46,7 +46,7 @@ fn is_method<'tcx>(cx: &LateContext<'tcx>, expr: &hir::Expr<'_>, method_name: Sy
|
|||
}
|
||||
}
|
||||
|
||||
fn is_option_filter_map<'tcx>(cx: &LateContext<'tcx>, filter_arg: &hir::Expr<'_>, map_arg: &hir::Expr<'_>) -> bool {
|
||||
fn is_option_filter_map(cx: &LateContext<'_>, filter_arg: &hir::Expr<'_>, map_arg: &hir::Expr<'_>) -> bool {
|
||||
is_method(cx, map_arg, sym::unwrap) && is_method(cx, filter_arg, sym!(is_some))
|
||||
}
|
||||
|
||||
|
|
@ -66,8 +66,8 @@ fn is_filter_some_map_unwrap(
|
|||
|
||||
/// lint use of `filter().map()` or `find().map()` for `Iterators`
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub(super) fn check<'tcx>(
|
||||
cx: &LateContext<'tcx>,
|
||||
pub(super) fn check(
|
||||
cx: &LateContext<'_>,
|
||||
expr: &hir::Expr<'_>,
|
||||
filter_recv: &hir::Expr<'_>,
|
||||
filter_arg: &hir::Expr<'_>,
|
||||
|
|
|
|||
|
|
@ -12,8 +12,8 @@ use rustc_span::symbol::{Symbol, sym};
|
|||
use super::INEFFICIENT_TO_STRING;
|
||||
|
||||
/// Checks for the `INEFFICIENT_TO_STRING` lint
|
||||
pub fn check<'tcx>(
|
||||
cx: &LateContext<'tcx>,
|
||||
pub fn check(
|
||||
cx: &LateContext<'_>,
|
||||
expr: &hir::Expr<'_>,
|
||||
method_name: Symbol,
|
||||
receiver: &hir::Expr<'_>,
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ use rustc_span::sym;
|
|||
|
||||
use super::ITER_NTH_ZERO;
|
||||
|
||||
pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &hir::Expr<'_>, recv: &hir::Expr<'_>, arg: &hir::Expr<'_>) {
|
||||
pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, recv: &hir::Expr<'_>, arg: &hir::Expr<'_>) {
|
||||
if_chain! {
|
||||
if is_trait_method(cx, expr, sym::Iterator);
|
||||
if let Some((Constant::Int(0), _)) = constant(cx, cx.typeck_results(), arg);
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ impl IterType {
|
|||
}
|
||||
}
|
||||
|
||||
pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'_>, method_name: &str, recv: &Expr<'_>) {
|
||||
pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, method_name: &str, recv: &Expr<'_>) {
|
||||
let item = match recv.kind {
|
||||
ExprKind::Array([]) => None,
|
||||
ExprKind::Array([e]) => Some(e),
|
||||
|
|
|
|||
|
|
@ -67,7 +67,7 @@ enum MinMax {
|
|||
Max,
|
||||
}
|
||||
|
||||
fn is_min_or_max<'tcx>(cx: &LateContext<'tcx>, expr: &hir::Expr<'_>) -> Option<MinMax> {
|
||||
fn is_min_or_max(cx: &LateContext<'_>, expr: &hir::Expr<'_>) -> Option<MinMax> {
|
||||
// `T::max_value()` `T::min_value()` inherent methods
|
||||
if_chain! {
|
||||
if let hir::ExprKind::Call(func, args) = &expr.kind;
|
||||
|
|
|
|||
|
|
@ -59,10 +59,8 @@ pub(super) fn check(
|
|||
if let ExprKind::Call(repeat_fn, [repeat_arg]) = take_self_arg.kind;
|
||||
if is_path_diagnostic_item(cx, repeat_fn, sym::iter_repeat);
|
||||
if is_type_lang_item(cx, cx.typeck_results().expr_ty(collect_expr), LangItem::String);
|
||||
if let Some(collect_id) = cx.typeck_results().type_dependent_def_id(collect_expr.hir_id);
|
||||
if let Some(take_id) = cx.typeck_results().type_dependent_def_id(take_expr.hir_id);
|
||||
if let Some(iter_trait_id) = cx.tcx.get_diagnostic_item(sym::Iterator);
|
||||
if cx.tcx.trait_of_item(collect_id) == Some(iter_trait_id);
|
||||
if cx.tcx.trait_of_item(take_id) == Some(iter_trait_id);
|
||||
if let Some(repeat_kind) = parse_repeat_arg(cx, repeat_arg);
|
||||
let ctxt = collect_expr.span.ctxt();
|
||||
|
|
|
|||
|
|
@ -15,11 +15,11 @@ use rustc_span::{sym, Span};
|
|||
|
||||
use super::MAP_CLONE;
|
||||
|
||||
pub(super) fn check<'tcx>(
|
||||
pub(super) fn check(
|
||||
cx: &LateContext<'_>,
|
||||
e: &hir::Expr<'_>,
|
||||
recv: &hir::Expr<'_>,
|
||||
arg: &'tcx hir::Expr<'_>,
|
||||
arg: &hir::Expr<'_>,
|
||||
msrv: Option<RustcVersion>,
|
||||
) {
|
||||
if_chain! {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
use clippy_utils::diagnostics::span_lint_and_sugg;
|
||||
use clippy_utils::is_trait_method;
|
||||
use clippy_utils::source::snippet;
|
||||
use clippy_utils::ty::is_type_diagnostic_item;
|
||||
use if_chain::if_chain;
|
||||
|
|
@ -11,18 +10,10 @@ use rustc_span::symbol::sym;
|
|||
|
||||
use super::MAP_COLLECT_RESULT_UNIT;
|
||||
|
||||
pub(super) fn check(
|
||||
cx: &LateContext<'_>,
|
||||
expr: &hir::Expr<'_>,
|
||||
iter: &hir::Expr<'_>,
|
||||
map_fn: &hir::Expr<'_>,
|
||||
collect_recv: &hir::Expr<'_>,
|
||||
) {
|
||||
pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, iter: &hir::Expr<'_>, map_fn: &hir::Expr<'_>) {
|
||||
// return of collect `Result<(),_>`
|
||||
let collect_ret_ty = cx.typeck_results().expr_ty(expr);
|
||||
if_chain! {
|
||||
// called on Iterator
|
||||
if is_trait_method(cx, collect_recv, sym::Iterator);
|
||||
// return of collect `Result<(),_>`
|
||||
let collect_ret_ty = cx.typeck_results().expr_ty(expr);
|
||||
if is_type_diagnostic_item(cx, collect_ret_ty, sym::Result);
|
||||
if let ty::Adt(_, substs) = collect_ret_ty.kind();
|
||||
if let Some(result_t) = substs.types().next();
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ use rustc_span::sym;
|
|||
|
||||
use super::MAP_ERR_IGNORE;
|
||||
|
||||
pub(super) fn check<'tcx>(cx: &LateContext<'_>, e: &Expr<'_>, arg: &'tcx Expr<'_>) {
|
||||
pub(super) fn check(cx: &LateContext<'_>, e: &Expr<'_>, arg: &Expr<'_>) {
|
||||
if let Some(method_id) = cx.typeck_results().type_dependent_def_id(e.hir_id)
|
||||
&& let Some(impl_id) = cx.tcx.impl_of_method(method_id)
|
||||
&& is_type_diagnostic_item(cx, cx.tcx.type_of(impl_id), sym::Result)
|
||||
|
|
|
|||
|
|
@ -54,6 +54,7 @@ mod map_flatten;
|
|||
mod map_identity;
|
||||
mod map_unwrap_or;
|
||||
mod mut_mutex_lock;
|
||||
mod needless_collect;
|
||||
mod needless_option_as_deref;
|
||||
mod needless_option_take;
|
||||
mod no_effect_replace;
|
||||
|
|
@ -69,6 +70,8 @@ mod path_buf_push_overwrite;
|
|||
mod range_zip_with_len;
|
||||
mod repeat_once;
|
||||
mod search_is_some;
|
||||
mod seek_from_current;
|
||||
mod seek_to_start_instead_of_rewind;
|
||||
mod single_char_add_str;
|
||||
mod single_char_insert_string;
|
||||
mod single_char_pattern;
|
||||
|
|
@ -101,12 +104,11 @@ mod zst_offset;
|
|||
use bind_instead_of_map::BindInsteadOfMap;
|
||||
use clippy_utils::consts::{constant, Constant};
|
||||
use clippy_utils::diagnostics::{span_lint, span_lint_and_help};
|
||||
use clippy_utils::ty::{contains_adt_constructor, implements_trait, is_copy, is_type_diagnostic_item};
|
||||
use clippy_utils::{contains_return, is_trait_method, iter_input_pats, meets_msrv, msrvs, return_ty};
|
||||
use clippy_utils::ty::{contains_ty_adt_constructor_opaque, implements_trait, is_copy, is_type_diagnostic_item};
|
||||
use clippy_utils::{contains_return, is_bool, is_trait_method, iter_input_pats, meets_msrv, msrvs, return_ty};
|
||||
use if_chain::if_chain;
|
||||
use rustc_hir as hir;
|
||||
use rustc_hir::def::Res;
|
||||
use rustc_hir::{Expr, ExprKind, PrimTy, QPath, TraitItem, TraitItemKind};
|
||||
use rustc_hir::{Expr, ExprKind, TraitItem, TraitItemKind};
|
||||
use rustc_hir_analysis::hir_ty_to_ty;
|
||||
use rustc_lint::{LateContext, LateLintPass, LintContext};
|
||||
use rustc_middle::lint::in_external_macro;
|
||||
|
|
@ -156,9 +158,9 @@ declare_clippy_lint! {
|
|||
/// ```
|
||||
/// Use instead:
|
||||
/// ```rust
|
||||
/// let hello = "hesuo worpd".replace(&['s', 'u', 'p'], "l");
|
||||
/// let hello = "hesuo worpd".replace(['s', 'u', 'p'], "l");
|
||||
/// ```
|
||||
#[clippy::version = "1.64.0"]
|
||||
#[clippy::version = "1.65.0"]
|
||||
pub COLLAPSIBLE_STR_REPLACE,
|
||||
perf,
|
||||
"collapse consecutive calls to str::replace (2 or more) into a single call"
|
||||
|
|
@ -829,32 +831,30 @@ declare_clippy_lint! {
|
|||
/// etc. instead.
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
/// The function will always be called and potentially
|
||||
/// allocate an object acting as the default.
|
||||
/// The function will always be called. This is only bad if it allocates or
|
||||
/// does some non-trivial amount of work.
|
||||
///
|
||||
/// ### Known problems
|
||||
/// If the function has side-effects, not calling it will
|
||||
/// change the semantic of the program, but you shouldn't rely on that anyway.
|
||||
/// If the function has side-effects, not calling it will change the
|
||||
/// semantic of the program, but you shouldn't rely on that.
|
||||
///
|
||||
/// The lint also cannot figure out whether the function you call is
|
||||
/// actually expensive to call or not.
|
||||
///
|
||||
/// ### Example
|
||||
/// ```rust
|
||||
/// # let foo = Some(String::new());
|
||||
/// foo.unwrap_or(String::new());
|
||||
/// foo.unwrap_or(String::from("empty"));
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```rust
|
||||
/// # let foo = Some(String::new());
|
||||
/// foo.unwrap_or_else(String::new);
|
||||
///
|
||||
/// // or
|
||||
///
|
||||
/// # let foo = Some(String::new());
|
||||
/// foo.unwrap_or_default();
|
||||
/// foo.unwrap_or_else(|| String::from("empty"));
|
||||
/// ```
|
||||
#[clippy::version = "pre 1.29.0"]
|
||||
pub OR_FUN_CALL,
|
||||
perf,
|
||||
nursery,
|
||||
"using any `*or` method with a function call, which suggests `*or_else`"
|
||||
}
|
||||
|
||||
|
|
@ -1728,7 +1728,7 @@ declare_clippy_lint! {
|
|||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// Checks for usage of `_.as_ref().map(Deref::deref)` or it's aliases (such as String::as_str).
|
||||
/// Checks for usage of `_.as_ref().map(Deref::deref)` or its aliases (such as String::as_str).
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
/// Readability, this can be written more concisely as
|
||||
|
|
@ -2094,8 +2094,7 @@ declare_clippy_lint! {
|
|||
/// let s = "Hello world!";
|
||||
/// let cow = Cow::Borrowed(s);
|
||||
///
|
||||
/// let data = cow.into_owned();
|
||||
/// assert!(matches!(data, String))
|
||||
/// let _data: String = cow.into_owned();
|
||||
/// ```
|
||||
#[clippy::version = "1.65.0"]
|
||||
pub SUSPICIOUS_TO_OWNED,
|
||||
|
|
@ -2426,7 +2425,7 @@ declare_clippy_lint! {
|
|||
/// ### Known problems
|
||||
///
|
||||
/// The type of the resulting iterator might become incompatible with its usage
|
||||
#[clippy::version = "1.64.0"]
|
||||
#[clippy::version = "1.65.0"]
|
||||
pub ITER_ON_SINGLE_ITEMS,
|
||||
nursery,
|
||||
"Iterator for array of length 1"
|
||||
|
|
@ -2458,7 +2457,7 @@ declare_clippy_lint! {
|
|||
/// ### Known problems
|
||||
///
|
||||
/// The type of the resulting iterator might become incompatible with its usage
|
||||
#[clippy::version = "1.64.0"]
|
||||
#[clippy::version = "1.65.0"]
|
||||
pub ITER_ON_EMPTY_COLLECTIONS,
|
||||
nursery,
|
||||
"Iterator for empty array"
|
||||
|
|
@ -3066,6 +3065,102 @@ declare_clippy_lint! {
|
|||
"iterating on map using `iter` when `keys` or `values` would do"
|
||||
}
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
///
|
||||
/// Checks an argument of `seek` method of `Seek` trait
|
||||
/// and if it start seek from `SeekFrom::Current(0)`, suggests `stream_position` instead.
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
///
|
||||
/// Readability. Use dedicated method.
|
||||
///
|
||||
/// ### Example
|
||||
///
|
||||
/// ```rust,no_run
|
||||
/// use std::fs::File;
|
||||
/// use std::io::{self, Write, Seek, SeekFrom};
|
||||
///
|
||||
/// fn main() -> io::Result<()> {
|
||||
/// let mut f = File::create("foo.txt")?;
|
||||
/// f.write_all(b"Hello")?;
|
||||
/// eprintln!("Written {} bytes", f.seek(SeekFrom::Current(0))?);
|
||||
///
|
||||
/// Ok(())
|
||||
/// }
|
||||
/// ```
|
||||
/// Use instead:
|
||||
/// ```rust,no_run
|
||||
/// use std::fs::File;
|
||||
/// use std::io::{self, Write, Seek, SeekFrom};
|
||||
///
|
||||
/// fn main() -> io::Result<()> {
|
||||
/// let mut f = File::create("foo.txt")?;
|
||||
/// f.write_all(b"Hello")?;
|
||||
/// eprintln!("Written {} bytes", f.stream_position()?);
|
||||
///
|
||||
/// Ok(())
|
||||
/// }
|
||||
/// ```
|
||||
#[clippy::version = "1.66.0"]
|
||||
pub SEEK_FROM_CURRENT,
|
||||
complexity,
|
||||
"use dedicated method for seek from current position"
|
||||
}
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
///
|
||||
/// Checks for jumps to the start of a stream that implements `Seek`
|
||||
/// and uses the `seek` method providing `Start` as parameter.
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
///
|
||||
/// Readability. There is a specific method that was implemented for
|
||||
/// this exact scenario.
|
||||
///
|
||||
/// ### Example
|
||||
/// ```rust
|
||||
/// # use std::io;
|
||||
/// fn foo<T: io::Seek>(t: &mut T) {
|
||||
/// t.seek(io::SeekFrom::Start(0));
|
||||
/// }
|
||||
/// ```
|
||||
/// Use instead:
|
||||
/// ```rust
|
||||
/// # use std::io;
|
||||
/// fn foo<T: io::Seek>(t: &mut T) {
|
||||
/// t.rewind();
|
||||
/// }
|
||||
/// ```
|
||||
#[clippy::version = "1.66.0"]
|
||||
pub SEEK_TO_START_INSTEAD_OF_REWIND,
|
||||
complexity,
|
||||
"jumping to the start of stream using `seek` method"
|
||||
}
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// Checks for functions collecting an iterator when collect
|
||||
/// is not needed.
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
/// `collect` causes the allocation of a new data structure,
|
||||
/// when this allocation may not be needed.
|
||||
///
|
||||
/// ### Example
|
||||
/// ```rust
|
||||
/// # let iterator = vec![1].into_iter();
|
||||
/// let len = iterator.clone().collect::<Vec<_>>().len();
|
||||
/// // should be
|
||||
/// let len = iterator.count();
|
||||
/// ```
|
||||
#[clippy::version = "1.30.0"]
|
||||
pub NEEDLESS_COLLECT,
|
||||
nursery,
|
||||
"collecting an iterator when collect is not needed"
|
||||
}
|
||||
|
||||
pub struct Methods {
|
||||
avoid_breaking_exported_api: bool,
|
||||
msrv: Option<RustcVersion>,
|
||||
|
|
@ -3190,16 +3285,19 @@ impl_lint_pass!(Methods => [
|
|||
VEC_RESIZE_TO_ZERO,
|
||||
VERBOSE_FILE_READS,
|
||||
ITER_KV_MAP,
|
||||
SEEK_FROM_CURRENT,
|
||||
SEEK_TO_START_INSTEAD_OF_REWIND,
|
||||
NEEDLESS_COLLECT,
|
||||
]);
|
||||
|
||||
/// Extracts a method call name, args, and `Span` of the method name.
|
||||
fn method_call<'tcx>(
|
||||
recv: &'tcx hir::Expr<'tcx>,
|
||||
) -> Option<(&'tcx str, &'tcx hir::Expr<'tcx>, &'tcx [hir::Expr<'tcx>], Span)> {
|
||||
if let ExprKind::MethodCall(path, receiver, args, _) = recv.kind {
|
||||
) -> Option<(&'tcx str, &'tcx hir::Expr<'tcx>, &'tcx [hir::Expr<'tcx>], Span, Span)> {
|
||||
if let ExprKind::MethodCall(path, receiver, args, call_span) = recv.kind {
|
||||
if !args.iter().any(|e| e.span.from_expansion()) && !receiver.span.from_expansion() {
|
||||
let name = path.ident.name.as_str();
|
||||
return Some((name, receiver, args, path.ident.span));
|
||||
return Some((name, receiver, args, path.ident.span, call_span));
|
||||
}
|
||||
}
|
||||
None
|
||||
|
|
@ -3316,36 +3414,10 @@ impl<'tcx> LateLintPass<'tcx> for Methods {
|
|||
if let hir::ImplItemKind::Fn(_, _) = impl_item.kind {
|
||||
let ret_ty = return_ty(cx, impl_item.hir_id());
|
||||
|
||||
// walk the return type and check for Self (this does not check associated types)
|
||||
if let Some(self_adt) = self_ty.ty_adt_def() {
|
||||
if contains_adt_constructor(ret_ty, self_adt) {
|
||||
return;
|
||||
}
|
||||
} else if ret_ty.contains(self_ty) {
|
||||
if contains_ty_adt_constructor_opaque(cx, ret_ty, self_ty) {
|
||||
return;
|
||||
}
|
||||
|
||||
// if return type is impl trait, check the associated types
|
||||
if let ty::Opaque(def_id, _) = *ret_ty.kind() {
|
||||
// one of the associated types must be Self
|
||||
for &(predicate, _span) in cx.tcx.explicit_item_bounds(def_id) {
|
||||
if let ty::PredicateKind::Projection(projection_predicate) = predicate.kind().skip_binder() {
|
||||
let assoc_ty = match projection_predicate.term.unpack() {
|
||||
ty::TermKind::Ty(ty) => ty,
|
||||
ty::TermKind::Const(_c) => continue,
|
||||
};
|
||||
// walk the associated type and check for Self
|
||||
if let Some(self_adt) = self_ty.ty_adt_def() {
|
||||
if contains_adt_constructor(assoc_ty, self_adt) {
|
||||
return;
|
||||
}
|
||||
} else if assoc_ty.contains(self_ty) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if name == "new" && ret_ty != self_ty {
|
||||
span_lint(
|
||||
cx,
|
||||
|
|
@ -3411,7 +3483,7 @@ impl<'tcx> LateLintPass<'tcx> for Methods {
|
|||
impl Methods {
|
||||
#[allow(clippy::too_many_lines)]
|
||||
fn check_methods<'tcx>(&self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
|
||||
if let Some((name, recv, args, span)) = method_call(expr) {
|
||||
if let Some((name, recv, args, span, call_span)) = method_call(expr) {
|
||||
match (name, args) {
|
||||
("add" | "offset" | "sub" | "wrapping_offset" | "wrapping_add" | "wrapping_sub", [_arg]) => {
|
||||
zst_offset::check(cx, expr, recv);
|
||||
|
|
@ -3430,28 +3502,31 @@ impl Methods {
|
|||
("as_ref", []) => useless_asref::check(cx, expr, "as_ref", recv),
|
||||
("assume_init", []) => uninit_assumed_init::check(cx, expr, recv),
|
||||
("cloned", []) => cloned_instead_of_copied::check(cx, expr, recv, span, self.msrv),
|
||||
("collect", []) => match method_call(recv) {
|
||||
Some((name @ ("cloned" | "copied"), recv2, [], _)) => {
|
||||
iter_cloned_collect::check(cx, name, expr, recv2);
|
||||
},
|
||||
Some(("map", m_recv, [m_arg], _)) => {
|
||||
map_collect_result_unit::check(cx, expr, m_recv, m_arg, recv);
|
||||
},
|
||||
Some(("take", take_self_arg, [take_arg], _)) => {
|
||||
if meets_msrv(self.msrv, msrvs::STR_REPEAT) {
|
||||
manual_str_repeat::check(cx, expr, recv, take_self_arg, take_arg);
|
||||
}
|
||||
},
|
||||
_ => {},
|
||||
("collect", []) if is_trait_method(cx, expr, sym::Iterator) => {
|
||||
needless_collect::check(cx, span, expr, recv, call_span);
|
||||
match method_call(recv) {
|
||||
Some((name @ ("cloned" | "copied"), recv2, [], _, _)) => {
|
||||
iter_cloned_collect::check(cx, name, expr, recv2);
|
||||
},
|
||||
Some(("map", m_recv, [m_arg], _, _)) => {
|
||||
map_collect_result_unit::check(cx, expr, m_recv, m_arg);
|
||||
},
|
||||
Some(("take", take_self_arg, [take_arg], _, _)) => {
|
||||
if meets_msrv(self.msrv, msrvs::STR_REPEAT) {
|
||||
manual_str_repeat::check(cx, expr, recv, take_self_arg, take_arg);
|
||||
}
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
},
|
||||
("count", []) if is_trait_method(cx, expr, sym::Iterator) => match method_call(recv) {
|
||||
Some(("cloned", recv2, [], _)) => iter_overeager_cloned::check(cx, expr, recv, recv2, true, false),
|
||||
Some((name2 @ ("into_iter" | "iter" | "iter_mut"), recv2, [], _)) => {
|
||||
Some(("cloned", recv2, [], _, _)) => iter_overeager_cloned::check(cx, expr, recv, recv2, true, false),
|
||||
Some((name2 @ ("into_iter" | "iter" | "iter_mut"), recv2, [], _, _)) => {
|
||||
iter_count::check(cx, expr, recv2, name2);
|
||||
},
|
||||
Some(("map", _, [arg], _)) => suspicious_map::check(cx, expr, recv, arg),
|
||||
Some(("filter", recv2, [arg], _)) => bytecount::check(cx, expr, recv2, arg),
|
||||
Some(("bytes", recv2, [], _)) => bytes_count_to_len::check(cx, expr, recv, recv2),
|
||||
Some(("map", _, [arg], _, _)) => suspicious_map::check(cx, expr, recv, arg),
|
||||
Some(("filter", recv2, [arg], _, _)) => bytecount::check(cx, expr, recv2, arg),
|
||||
Some(("bytes", recv2, [], _, _)) => bytes_count_to_len::check(cx, expr, recv, recv2),
|
||||
_ => {},
|
||||
},
|
||||
("drain", [arg]) => {
|
||||
|
|
@ -3463,8 +3538,8 @@ impl Methods {
|
|||
}
|
||||
},
|
||||
("expect", [_]) => match method_call(recv) {
|
||||
Some(("ok", recv, [], _)) => ok_expect::check(cx, expr, recv),
|
||||
Some(("err", recv, [], err_span)) => err_expect::check(cx, expr, recv, self.msrv, span, err_span),
|
||||
Some(("ok", recv, [], _, _)) => ok_expect::check(cx, expr, recv),
|
||||
Some(("err", recv, [], err_span, _)) => err_expect::check(cx, expr, recv, self.msrv, span, err_span),
|
||||
_ => expect_used::check(cx, expr, recv, false, self.allow_expect_in_tests),
|
||||
},
|
||||
("expect_err", [_]) => expect_used::check(cx, expr, recv, true, self.allow_expect_in_tests),
|
||||
|
|
@ -3484,13 +3559,13 @@ impl Methods {
|
|||
flat_map_option::check(cx, expr, arg, span);
|
||||
},
|
||||
("flatten", []) => match method_call(recv) {
|
||||
Some(("map", recv, [map_arg], map_span)) => map_flatten::check(cx, expr, recv, map_arg, map_span),
|
||||
Some(("cloned", recv2, [], _)) => iter_overeager_cloned::check(cx, expr, recv, recv2, false, true),
|
||||
Some(("map", recv, [map_arg], map_span, _)) => map_flatten::check(cx, expr, recv, map_arg, map_span),
|
||||
Some(("cloned", recv2, [], _, _)) => iter_overeager_cloned::check(cx, expr, recv, recv2, false, true),
|
||||
_ => {},
|
||||
},
|
||||
("fold", [init, acc]) => unnecessary_fold::check(cx, expr, init, acc, span),
|
||||
("for_each", [_]) => {
|
||||
if let Some(("inspect", _, [_], span2)) = method_call(recv) {
|
||||
if let Some(("inspect", _, [_], span2, _)) = method_call(recv) {
|
||||
inspect_for_each::check(cx, expr, span2);
|
||||
}
|
||||
},
|
||||
|
|
@ -3510,12 +3585,12 @@ impl Methods {
|
|||
iter_on_single_or_empty_collections::check(cx, expr, name, recv);
|
||||
},
|
||||
("join", [join_arg]) => {
|
||||
if let Some(("collect", _, _, span)) = method_call(recv) {
|
||||
if let Some(("collect", _, _, span, _)) = method_call(recv) {
|
||||
unnecessary_join::check(cx, expr, recv, join_arg, span);
|
||||
}
|
||||
},
|
||||
("last", []) | ("skip", [_]) => {
|
||||
if let Some((name2, recv2, args2, _span2)) = method_call(recv) {
|
||||
if let Some((name2, recv2, args2, _span2, _)) = method_call(recv) {
|
||||
if let ("cloned", []) = (name2, args2) {
|
||||
iter_overeager_cloned::check(cx, expr, recv, recv2, false, false);
|
||||
}
|
||||
|
|
@ -3527,13 +3602,13 @@ impl Methods {
|
|||
(name @ ("map" | "map_err"), [m_arg]) => {
|
||||
if name == "map" {
|
||||
map_clone::check(cx, expr, recv, m_arg, self.msrv);
|
||||
if let Some((map_name @ ("iter" | "into_iter"), recv2, _, _)) = method_call(recv) {
|
||||
if let Some((map_name @ ("iter" | "into_iter"), recv2, _, _, _)) = method_call(recv) {
|
||||
iter_kv_map::check(cx, map_name, expr, recv2, m_arg);
|
||||
}
|
||||
} else {
|
||||
map_err_ignore::check(cx, expr, m_arg);
|
||||
}
|
||||
if let Some((name, recv2, args, span2)) = method_call(recv) {
|
||||
if let Some((name, recv2, args, span2,_)) = method_call(recv) {
|
||||
match (name, args) {
|
||||
("as_mut", []) => option_as_ref_deref::check(cx, expr, recv2, m_arg, true, self.msrv),
|
||||
("as_ref", []) => option_as_ref_deref::check(cx, expr, recv2, m_arg, false, self.msrv),
|
||||
|
|
@ -3553,7 +3628,7 @@ impl Methods {
|
|||
manual_ok_or::check(cx, expr, recv, def, map);
|
||||
},
|
||||
("next", []) => {
|
||||
if let Some((name2, recv2, args2, _)) = method_call(recv) {
|
||||
if let Some((name2, recv2, args2, _, _)) = method_call(recv) {
|
||||
match (name2, args2) {
|
||||
("cloned", []) => iter_overeager_cloned::check(cx, expr, recv, recv2, false, false),
|
||||
("filter", [arg]) => filter_next::check(cx, expr, recv2, arg),
|
||||
|
|
@ -3566,10 +3641,10 @@ impl Methods {
|
|||
}
|
||||
},
|
||||
("nth", [n_arg]) => match method_call(recv) {
|
||||
Some(("bytes", recv2, [], _)) => bytes_nth::check(cx, expr, recv2, n_arg),
|
||||
Some(("cloned", recv2, [], _)) => iter_overeager_cloned::check(cx, expr, recv, recv2, false, false),
|
||||
Some(("iter", recv2, [], _)) => iter_nth::check(cx, expr, recv2, recv, n_arg, false),
|
||||
Some(("iter_mut", recv2, [], _)) => iter_nth::check(cx, expr, recv2, recv, n_arg, true),
|
||||
Some(("bytes", recv2, [], _, _)) => bytes_nth::check(cx, expr, recv2, n_arg),
|
||||
Some(("cloned", recv2, [], _, _)) => iter_overeager_cloned::check(cx, expr, recv, recv2, false, false),
|
||||
Some(("iter", recv2, [], _, _)) => iter_nth::check(cx, expr, recv2, recv, n_arg, false),
|
||||
Some(("iter_mut", recv2, [], _, _)) => iter_nth::check(cx, expr, recv2, recv, n_arg, true),
|
||||
_ => iter_nth_zero::check(cx, expr, recv, n_arg),
|
||||
},
|
||||
("ok_or_else", [arg]) => unnecessary_lazy_eval::check(cx, expr, recv, arg, "ok_or"),
|
||||
|
|
@ -3604,6 +3679,14 @@ impl Methods {
|
|||
("resize", [count_arg, default_arg]) => {
|
||||
vec_resize_to_zero::check(cx, expr, count_arg, default_arg, span);
|
||||
},
|
||||
("seek", [arg]) => {
|
||||
if meets_msrv(self.msrv, msrvs::SEEK_FROM_CURRENT) {
|
||||
seek_from_current::check(cx, expr, recv, arg);
|
||||
}
|
||||
if meets_msrv(self.msrv, msrvs::SEEK_REWIND) {
|
||||
seek_to_start_instead_of_rewind::check(cx, expr, recv, arg, span);
|
||||
}
|
||||
},
|
||||
("sort", []) => {
|
||||
stable_sort_primitive::check(cx, expr, recv);
|
||||
},
|
||||
|
|
@ -3626,7 +3709,7 @@ impl Methods {
|
|||
},
|
||||
("step_by", [arg]) => iterator_step_by_zero::check(cx, expr, arg),
|
||||
("take", [_arg]) => {
|
||||
if let Some((name2, recv2, args2, _span2)) = method_call(recv) {
|
||||
if let Some((name2, recv2, args2, _span2, _)) = method_call(recv) {
|
||||
if let ("cloned", []) = (name2, args2) {
|
||||
iter_overeager_cloned::check(cx, expr, recv, recv2, false, false);
|
||||
}
|
||||
|
|
@ -3649,13 +3732,13 @@ impl Methods {
|
|||
},
|
||||
("unwrap", []) => {
|
||||
match method_call(recv) {
|
||||
Some(("get", recv, [get_arg], _)) => {
|
||||
Some(("get", recv, [get_arg], _, _)) => {
|
||||
get_unwrap::check(cx, expr, recv, get_arg, false);
|
||||
},
|
||||
Some(("get_mut", recv, [get_arg], _)) => {
|
||||
Some(("get_mut", recv, [get_arg], _, _)) => {
|
||||
get_unwrap::check(cx, expr, recv, get_arg, true);
|
||||
},
|
||||
Some(("or", recv, [or_arg], or_span)) => {
|
||||
Some(("or", recv, [or_arg], or_span, _)) => {
|
||||
or_then_unwrap::check(cx, expr, recv, or_arg, or_span);
|
||||
},
|
||||
_ => {},
|
||||
|
|
@ -3664,19 +3747,19 @@ impl Methods {
|
|||
},
|
||||
("unwrap_err", []) => unwrap_used::check(cx, expr, recv, true, self.allow_unwrap_in_tests),
|
||||
("unwrap_or", [u_arg]) => match method_call(recv) {
|
||||
Some((arith @ ("checked_add" | "checked_sub" | "checked_mul"), lhs, [rhs], _)) => {
|
||||
Some((arith @ ("checked_add" | "checked_sub" | "checked_mul"), lhs, [rhs], _, _)) => {
|
||||
manual_saturating_arithmetic::check(cx, expr, lhs, rhs, u_arg, &arith["checked_".len()..]);
|
||||
},
|
||||
Some(("map", m_recv, [m_arg], span)) => {
|
||||
Some(("map", m_recv, [m_arg], span, _)) => {
|
||||
option_map_unwrap_or::check(cx, expr, m_recv, m_arg, recv, u_arg, span);
|
||||
},
|
||||
Some(("then_some", t_recv, [t_arg], _)) => {
|
||||
Some(("then_some", t_recv, [t_arg], _, _)) => {
|
||||
obfuscated_if_else::check(cx, expr, t_recv, t_arg, u_arg);
|
||||
},
|
||||
_ => {},
|
||||
},
|
||||
("unwrap_or_else", [u_arg]) => match method_call(recv) {
|
||||
Some(("map", recv, [map_arg], _))
|
||||
Some(("map", recv, [map_arg], _, _))
|
||||
if map_unwrap_or::check(cx, expr, recv, map_arg, u_arg, self.msrv) => {},
|
||||
_ => {
|
||||
unwrap_or_else_default::check(cx, expr, recv, u_arg);
|
||||
|
|
@ -3697,7 +3780,7 @@ impl Methods {
|
|||
}
|
||||
|
||||
fn check_is_some_is_none(cx: &LateContext<'_>, expr: &Expr<'_>, recv: &Expr<'_>, is_some: bool) {
|
||||
if let Some((name @ ("find" | "position" | "rposition"), f_recv, [arg], span)) = method_call(recv) {
|
||||
if let Some((name @ ("find" | "position" | "rposition"), f_recv, [arg], span, _)) = method_call(recv) {
|
||||
search_is_some::check(cx, expr, name, is_some, f_recv, arg, recv, span);
|
||||
}
|
||||
}
|
||||
|
|
@ -3906,14 +3989,6 @@ impl OutType {
|
|||
}
|
||||
}
|
||||
|
||||
fn is_bool(ty: &hir::Ty<'_>) -> bool {
|
||||
if let hir::TyKind::Path(QPath::Resolved(_, path)) = ty.kind {
|
||||
matches!(path.res, Res::PrimTy(PrimTy::Bool))
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
fn fn_header_equals(expected: hir::FnHeader, actual: hir::FnHeader) -> bool {
|
||||
expected.constness == actual.constness
|
||||
&& expected.unsafety == actual.unsafety
|
||||
|
|
|
|||
456
clippy_lints/src/methods/needless_collect.rs
Normal file
456
clippy_lints/src/methods/needless_collect.rs
Normal file
|
|
@ -0,0 +1,456 @@
|
|||
use super::NEEDLESS_COLLECT;
|
||||
use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_hir_and_then};
|
||||
use clippy_utils::higher;
|
||||
use clippy_utils::source::{snippet, snippet_with_applicability};
|
||||
use clippy_utils::sugg::Sugg;
|
||||
use clippy_utils::ty::{is_type_diagnostic_item, make_normalized_projection, make_projection};
|
||||
use clippy_utils::{
|
||||
can_move_expr_to_closure, get_enclosing_block, get_parent_node, is_trait_method, path_to_local, path_to_local_id,
|
||||
CaptureKind,
|
||||
};
|
||||
use rustc_data_structures::fx::FxHashMap;
|
||||
use rustc_errors::{Applicability, MultiSpan};
|
||||
use rustc_hir::intravisit::{walk_block, walk_expr, Visitor};
|
||||
use rustc_hir::{
|
||||
BindingAnnotation, Block, Expr, ExprKind, HirId, HirIdSet, Local, Mutability, Node, PatKind, Stmt, StmtKind,
|
||||
};
|
||||
use rustc_lint::LateContext;
|
||||
use rustc_middle::hir::nested_filter;
|
||||
use rustc_middle::ty::{self, AssocKind, EarlyBinder, GenericArg, GenericArgKind, Ty};
|
||||
use rustc_span::symbol::Ident;
|
||||
use rustc_span::{sym, Span, Symbol};
|
||||
|
||||
const NEEDLESS_COLLECT_MSG: &str = "avoid using `collect()` when not needed";
|
||||
|
||||
pub(super) fn check<'tcx>(
|
||||
cx: &LateContext<'tcx>,
|
||||
name_span: Span,
|
||||
collect_expr: &'tcx Expr<'_>,
|
||||
iter_expr: &'tcx Expr<'tcx>,
|
||||
call_span: Span,
|
||||
) {
|
||||
if let Some(parent) = get_parent_node(cx.tcx, collect_expr.hir_id) {
|
||||
match parent {
|
||||
Node::Expr(parent) => {
|
||||
if let ExprKind::MethodCall(name, _, args @ ([] | [_]), _) = parent.kind {
|
||||
let mut app = Applicability::MachineApplicable;
|
||||
let name = name.ident.as_str();
|
||||
let collect_ty = cx.typeck_results().expr_ty(collect_expr);
|
||||
|
||||
let sugg: String = match name {
|
||||
"len" => {
|
||||
if let Some(adt) = collect_ty.ty_adt_def()
|
||||
&& matches!(
|
||||
cx.tcx.get_diagnostic_name(adt.did()),
|
||||
Some(sym::Vec | sym::VecDeque | sym::LinkedList | sym::BinaryHeap)
|
||||
)
|
||||
{
|
||||
"count()".into()
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
},
|
||||
"is_empty"
|
||||
if is_is_empty_sig(cx, parent.hir_id)
|
||||
&& iterates_same_ty(cx, cx.typeck_results().expr_ty(iter_expr), collect_ty) =>
|
||||
{
|
||||
"next().is_none()".into()
|
||||
},
|
||||
"contains" => {
|
||||
if is_contains_sig(cx, parent.hir_id, iter_expr)
|
||||
&& let Some(arg) = args.first()
|
||||
{
|
||||
let (span, prefix) = if let ExprKind::AddrOf(_, _, arg) = arg.kind {
|
||||
(arg.span, "")
|
||||
} else {
|
||||
(arg.span, "*")
|
||||
};
|
||||
let snip = snippet_with_applicability(cx, span, "??", &mut app);
|
||||
format!("any(|x| x == {prefix}{snip})")
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
},
|
||||
_ => return,
|
||||
};
|
||||
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
NEEDLESS_COLLECT,
|
||||
call_span.with_hi(parent.span.hi()),
|
||||
NEEDLESS_COLLECT_MSG,
|
||||
"replace with",
|
||||
sugg,
|
||||
app,
|
||||
);
|
||||
}
|
||||
},
|
||||
Node::Local(l) => {
|
||||
if let PatKind::Binding(BindingAnnotation::NONE | BindingAnnotation::MUT, id, _, None)
|
||||
= l.pat.kind
|
||||
&& let ty = cx.typeck_results().expr_ty(collect_expr)
|
||||
&& [sym::Vec, sym::VecDeque, sym::BinaryHeap, sym::LinkedList].into_iter()
|
||||
.any(|item| is_type_diagnostic_item(cx, ty, item))
|
||||
&& let iter_ty = cx.typeck_results().expr_ty(iter_expr)
|
||||
&& let Some(block) = get_enclosing_block(cx, l.hir_id)
|
||||
&& let Some(iter_calls) = detect_iter_and_into_iters(block, id, cx, get_captured_ids(cx, iter_ty))
|
||||
&& let [iter_call] = &*iter_calls
|
||||
{
|
||||
let mut used_count_visitor = UsedCountVisitor {
|
||||
cx,
|
||||
id,
|
||||
count: 0,
|
||||
};
|
||||
walk_block(&mut used_count_visitor, block);
|
||||
if used_count_visitor.count > 1 {
|
||||
return;
|
||||
}
|
||||
|
||||
// Suggest replacing iter_call with iter_replacement, and removing stmt
|
||||
let mut span = MultiSpan::from_span(name_span);
|
||||
span.push_span_label(iter_call.span, "the iterator could be used here instead");
|
||||
span_lint_hir_and_then(
|
||||
cx,
|
||||
super::NEEDLESS_COLLECT,
|
||||
collect_expr.hir_id,
|
||||
span,
|
||||
NEEDLESS_COLLECT_MSG,
|
||||
|diag| {
|
||||
let iter_replacement = format!("{}{}", Sugg::hir(cx, iter_expr, ".."), iter_call.get_iter_method(cx));
|
||||
diag.multipart_suggestion(
|
||||
iter_call.get_suggestion_text(),
|
||||
vec![
|
||||
(l.span, String::new()),
|
||||
(iter_call.span, iter_replacement)
|
||||
],
|
||||
Applicability::MaybeIncorrect,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
},
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Checks if the given method call matches the expected signature of `([&[mut]] self) -> bool`
|
||||
fn is_is_empty_sig(cx: &LateContext<'_>, call_id: HirId) -> bool {
|
||||
cx.typeck_results().type_dependent_def_id(call_id).map_or(false, |id| {
|
||||
let sig = cx.tcx.fn_sig(id).skip_binder();
|
||||
sig.inputs().len() == 1 && sig.output().is_bool()
|
||||
})
|
||||
}
|
||||
|
||||
/// Checks if `<iter_ty as Iterator>::Item` is the same as `<collect_ty as IntoIter>::Item`
|
||||
fn iterates_same_ty<'tcx>(cx: &LateContext<'tcx>, iter_ty: Ty<'tcx>, collect_ty: Ty<'tcx>) -> bool {
|
||||
let item = Symbol::intern("Item");
|
||||
if let Some(iter_trait) = cx.tcx.get_diagnostic_item(sym::Iterator)
|
||||
&& let Some(into_iter_trait) = cx.tcx.get_diagnostic_item(sym::IntoIterator)
|
||||
&& let Some(iter_item_ty) = make_normalized_projection(cx.tcx, cx.param_env, iter_trait, item, [iter_ty])
|
||||
&& let Some(into_iter_item_proj) = make_projection(cx.tcx, into_iter_trait, item, [collect_ty])
|
||||
&& let Ok(into_iter_item_ty) = cx.tcx.try_normalize_erasing_regions(
|
||||
cx.param_env,
|
||||
cx.tcx.mk_projection(into_iter_item_proj.item_def_id, into_iter_item_proj.substs)
|
||||
)
|
||||
{
|
||||
iter_item_ty == into_iter_item_ty
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/// Checks if the given method call matches the expected signature of
|
||||
/// `([&[mut]] self, &<iter_ty as Iterator>::Item) -> bool`
|
||||
fn is_contains_sig(cx: &LateContext<'_>, call_id: HirId, iter_expr: &Expr<'_>) -> bool {
|
||||
let typeck = cx.typeck_results();
|
||||
if let Some(id) = typeck.type_dependent_def_id(call_id)
|
||||
&& let sig = cx.tcx.fn_sig(id)
|
||||
&& sig.skip_binder().output().is_bool()
|
||||
&& let [_, search_ty] = *sig.skip_binder().inputs()
|
||||
&& let ty::Ref(_, search_ty, Mutability::Not) = *cx.tcx.erase_late_bound_regions(sig.rebind(search_ty)).kind()
|
||||
&& let Some(iter_trait) = cx.tcx.get_diagnostic_item(sym::Iterator)
|
||||
&& let Some(iter_item) = cx.tcx
|
||||
.associated_items(iter_trait)
|
||||
.find_by_name_and_kind(cx.tcx, Ident::with_dummy_span(Symbol::intern("Item")), AssocKind::Type, iter_trait)
|
||||
&& let substs = cx.tcx.mk_substs([GenericArg::from(typeck.expr_ty_adjusted(iter_expr))].into_iter())
|
||||
&& let proj_ty = cx.tcx.mk_projection(iter_item.def_id, substs)
|
||||
&& let Ok(item_ty) = cx.tcx.try_normalize_erasing_regions(cx.param_env, proj_ty)
|
||||
{
|
||||
item_ty == EarlyBinder(search_ty).subst(cx.tcx, cx.typeck_results().node_substs(call_id))
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
struct IterFunction {
|
||||
func: IterFunctionKind,
|
||||
span: Span,
|
||||
}
|
||||
impl IterFunction {
|
||||
fn get_iter_method(&self, cx: &LateContext<'_>) -> String {
|
||||
match &self.func {
|
||||
IterFunctionKind::IntoIter => String::new(),
|
||||
IterFunctionKind::Len => String::from(".count()"),
|
||||
IterFunctionKind::IsEmpty => String::from(".next().is_none()"),
|
||||
IterFunctionKind::Contains(span) => {
|
||||
let s = snippet(cx, *span, "..");
|
||||
if let Some(stripped) = s.strip_prefix('&') {
|
||||
format!(".any(|x| x == {stripped})")
|
||||
} else {
|
||||
format!(".any(|x| x == *{s})")
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
fn get_suggestion_text(&self) -> &'static str {
|
||||
match &self.func {
|
||||
IterFunctionKind::IntoIter => {
|
||||
"use the original Iterator instead of collecting it and then producing a new one"
|
||||
},
|
||||
IterFunctionKind::Len => {
|
||||
"take the original Iterator's count instead of collecting it and finding the length"
|
||||
},
|
||||
IterFunctionKind::IsEmpty => {
|
||||
"check if the original Iterator has anything instead of collecting it and seeing if it's empty"
|
||||
},
|
||||
IterFunctionKind::Contains(_) => {
|
||||
"check if the original Iterator contains an element instead of collecting then checking"
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
enum IterFunctionKind {
|
||||
IntoIter,
|
||||
Len,
|
||||
IsEmpty,
|
||||
Contains(Span),
|
||||
}
|
||||
|
||||
struct IterFunctionVisitor<'a, 'tcx> {
|
||||
illegal_mutable_capture_ids: HirIdSet,
|
||||
current_mutably_captured_ids: HirIdSet,
|
||||
cx: &'a LateContext<'tcx>,
|
||||
uses: Vec<Option<IterFunction>>,
|
||||
hir_id_uses_map: FxHashMap<HirId, usize>,
|
||||
current_statement_hir_id: Option<HirId>,
|
||||
seen_other: bool,
|
||||
target: HirId,
|
||||
}
|
||||
impl<'tcx> Visitor<'tcx> for IterFunctionVisitor<'_, 'tcx> {
|
||||
fn visit_block(&mut self, block: &'tcx Block<'tcx>) {
|
||||
for (expr, hir_id) in block.stmts.iter().filter_map(get_expr_and_hir_id_from_stmt) {
|
||||
if check_loop_kind(expr).is_some() {
|
||||
continue;
|
||||
}
|
||||
self.visit_block_expr(expr, hir_id);
|
||||
}
|
||||
if let Some(expr) = block.expr {
|
||||
if let Some(loop_kind) = check_loop_kind(expr) {
|
||||
if let LoopKind::Conditional(block_expr) = loop_kind {
|
||||
self.visit_block_expr(block_expr, None);
|
||||
}
|
||||
} else {
|
||||
self.visit_block_expr(expr, None);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_expr(&mut self, expr: &'tcx Expr<'tcx>) {
|
||||
// Check function calls on our collection
|
||||
if let ExprKind::MethodCall(method_name, recv, [args @ ..], _) = &expr.kind {
|
||||
if method_name.ident.name == sym!(collect) && is_trait_method(self.cx, expr, sym::Iterator) {
|
||||
self.current_mutably_captured_ids = get_captured_ids(self.cx, self.cx.typeck_results().expr_ty(recv));
|
||||
self.visit_expr(recv);
|
||||
return;
|
||||
}
|
||||
|
||||
if path_to_local_id(recv, self.target) {
|
||||
if self
|
||||
.illegal_mutable_capture_ids
|
||||
.intersection(&self.current_mutably_captured_ids)
|
||||
.next()
|
||||
.is_none()
|
||||
{
|
||||
if let Some(hir_id) = self.current_statement_hir_id {
|
||||
self.hir_id_uses_map.insert(hir_id, self.uses.len());
|
||||
}
|
||||
match method_name.ident.name.as_str() {
|
||||
"into_iter" => self.uses.push(Some(IterFunction {
|
||||
func: IterFunctionKind::IntoIter,
|
||||
span: expr.span,
|
||||
})),
|
||||
"len" => self.uses.push(Some(IterFunction {
|
||||
func: IterFunctionKind::Len,
|
||||
span: expr.span,
|
||||
})),
|
||||
"is_empty" => self.uses.push(Some(IterFunction {
|
||||
func: IterFunctionKind::IsEmpty,
|
||||
span: expr.span,
|
||||
})),
|
||||
"contains" => self.uses.push(Some(IterFunction {
|
||||
func: IterFunctionKind::Contains(args[0].span),
|
||||
span: expr.span,
|
||||
})),
|
||||
_ => {
|
||||
self.seen_other = true;
|
||||
if let Some(hir_id) = self.current_statement_hir_id {
|
||||
self.hir_id_uses_map.remove(&hir_id);
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if let Some(hir_id) = path_to_local(recv) {
|
||||
if let Some(index) = self.hir_id_uses_map.remove(&hir_id) {
|
||||
if self
|
||||
.illegal_mutable_capture_ids
|
||||
.intersection(&self.current_mutably_captured_ids)
|
||||
.next()
|
||||
.is_none()
|
||||
{
|
||||
if let Some(hir_id) = self.current_statement_hir_id {
|
||||
self.hir_id_uses_map.insert(hir_id, index);
|
||||
}
|
||||
} else {
|
||||
self.uses[index] = None;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Check if the collection is used for anything else
|
||||
if path_to_local_id(expr, self.target) {
|
||||
self.seen_other = true;
|
||||
} else {
|
||||
walk_expr(self, expr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum LoopKind<'tcx> {
|
||||
Conditional(&'tcx Expr<'tcx>),
|
||||
Loop,
|
||||
}
|
||||
|
||||
fn check_loop_kind<'tcx>(expr: &Expr<'tcx>) -> Option<LoopKind<'tcx>> {
|
||||
if let Some(higher::WhileLet { let_expr, .. }) = higher::WhileLet::hir(expr) {
|
||||
return Some(LoopKind::Conditional(let_expr));
|
||||
}
|
||||
if let Some(higher::While { condition, .. }) = higher::While::hir(expr) {
|
||||
return Some(LoopKind::Conditional(condition));
|
||||
}
|
||||
if let Some(higher::ForLoop { arg, .. }) = higher::ForLoop::hir(expr) {
|
||||
return Some(LoopKind::Conditional(arg));
|
||||
}
|
||||
if let ExprKind::Loop { .. } = expr.kind {
|
||||
return Some(LoopKind::Loop);
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
impl<'tcx> IterFunctionVisitor<'_, 'tcx> {
|
||||
fn visit_block_expr(&mut self, expr: &'tcx Expr<'tcx>, hir_id: Option<HirId>) {
|
||||
self.current_statement_hir_id = hir_id;
|
||||
self.current_mutably_captured_ids = get_captured_ids(self.cx, self.cx.typeck_results().expr_ty(expr));
|
||||
self.visit_expr(expr);
|
||||
}
|
||||
}
|
||||
|
||||
fn get_expr_and_hir_id_from_stmt<'v>(stmt: &'v Stmt<'v>) -> Option<(&'v Expr<'v>, Option<HirId>)> {
|
||||
match stmt.kind {
|
||||
StmtKind::Expr(expr) | StmtKind::Semi(expr) => Some((expr, None)),
|
||||
StmtKind::Item(..) => None,
|
||||
StmtKind::Local(Local { init, pat, .. }) => {
|
||||
if let PatKind::Binding(_, hir_id, ..) = pat.kind {
|
||||
init.map(|init_expr| (init_expr, Some(hir_id)))
|
||||
} else {
|
||||
init.map(|init_expr| (init_expr, None))
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
struct UsedCountVisitor<'a, 'tcx> {
|
||||
cx: &'a LateContext<'tcx>,
|
||||
id: HirId,
|
||||
count: usize,
|
||||
}
|
||||
|
||||
impl<'a, 'tcx> Visitor<'tcx> for UsedCountVisitor<'a, 'tcx> {
|
||||
type NestedFilter = nested_filter::OnlyBodies;
|
||||
|
||||
fn visit_expr(&mut self, expr: &'tcx Expr<'_>) {
|
||||
if path_to_local_id(expr, self.id) {
|
||||
self.count += 1;
|
||||
} else {
|
||||
walk_expr(self, expr);
|
||||
}
|
||||
}
|
||||
|
||||
fn nested_visit_map(&mut self) -> Self::Map {
|
||||
self.cx.tcx.hir()
|
||||
}
|
||||
}
|
||||
|
||||
/// Detect the occurrences of calls to `iter` or `into_iter` for the
|
||||
/// given identifier
|
||||
fn detect_iter_and_into_iters<'tcx: 'a, 'a>(
|
||||
block: &'tcx Block<'tcx>,
|
||||
id: HirId,
|
||||
cx: &'a LateContext<'tcx>,
|
||||
captured_ids: HirIdSet,
|
||||
) -> Option<Vec<IterFunction>> {
|
||||
let mut visitor = IterFunctionVisitor {
|
||||
uses: Vec::new(),
|
||||
target: id,
|
||||
seen_other: false,
|
||||
cx,
|
||||
current_mutably_captured_ids: HirIdSet::default(),
|
||||
illegal_mutable_capture_ids: captured_ids,
|
||||
hir_id_uses_map: FxHashMap::default(),
|
||||
current_statement_hir_id: None,
|
||||
};
|
||||
visitor.visit_block(block);
|
||||
if visitor.seen_other {
|
||||
None
|
||||
} else {
|
||||
Some(visitor.uses.into_iter().flatten().collect())
|
||||
}
|
||||
}
|
||||
|
||||
fn get_captured_ids(cx: &LateContext<'_>, ty: Ty<'_>) -> HirIdSet {
|
||||
fn get_captured_ids_recursive(cx: &LateContext<'_>, ty: Ty<'_>, set: &mut HirIdSet) {
|
||||
match ty.kind() {
|
||||
ty::Adt(_, generics) => {
|
||||
for generic in *generics {
|
||||
if let GenericArgKind::Type(ty) = generic.unpack() {
|
||||
get_captured_ids_recursive(cx, ty, set);
|
||||
}
|
||||
}
|
||||
},
|
||||
ty::Closure(def_id, _) => {
|
||||
let closure_hir_node = cx.tcx.hir().get_if_local(*def_id).unwrap();
|
||||
if let Node::Expr(closure_expr) = closure_hir_node {
|
||||
can_move_expr_to_closure(cx, closure_expr)
|
||||
.unwrap()
|
||||
.into_iter()
|
||||
.for_each(|(hir_id, capture_kind)| {
|
||||
if matches!(capture_kind, CaptureKind::Ref(Mutability::Mut)) {
|
||||
set.insert(hir_id);
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
let mut set = HirIdSet::default();
|
||||
|
||||
get_captured_ids_recursive(cx, ty, &mut set);
|
||||
|
||||
set
|
||||
}
|
||||
|
|
@ -13,8 +13,8 @@ use rustc_span::sym;
|
|||
use super::OPTION_AS_REF_DEREF;
|
||||
|
||||
/// lint use of `_.as_ref().map(Deref::deref)` for `Option`s
|
||||
pub(super) fn check<'tcx>(
|
||||
cx: &LateContext<'tcx>,
|
||||
pub(super) fn check(
|
||||
cx: &LateContext<'_>,
|
||||
expr: &hir::Expr<'_>,
|
||||
as_ref_recv: &hir::Expr<'_>,
|
||||
map_arg: &hir::Expr<'_>,
|
||||
|
|
|
|||
|
|
@ -83,6 +83,8 @@ pub(super) fn check<'tcx>(
|
|||
method_span: Span,
|
||||
self_expr: &hir::Expr<'_>,
|
||||
arg: &'tcx hir::Expr<'_>,
|
||||
// `Some` if fn has second argument
|
||||
second_arg: Option<&hir::Expr<'_>>,
|
||||
span: Span,
|
||||
// None if lambda is required
|
||||
fun_span: Option<Span>,
|
||||
|
|
@ -109,30 +111,40 @@ pub(super) fn check<'tcx>(
|
|||
if poss.contains(&name);
|
||||
|
||||
then {
|
||||
let macro_expanded_snipped;
|
||||
let sugg: Cow<'_, str> = {
|
||||
let sugg = {
|
||||
let (snippet_span, use_lambda) = match (fn_has_arguments, fun_span) {
|
||||
(false, Some(fun_span)) => (fun_span, false),
|
||||
_ => (arg.span, true),
|
||||
};
|
||||
let snippet = {
|
||||
let not_macro_argument_snippet = snippet_with_macro_callsite(cx, snippet_span, "..");
|
||||
if not_macro_argument_snippet == "vec![]" {
|
||||
macro_expanded_snipped = snippet(cx, snippet_span, "..");
|
||||
|
||||
let format_span = |span: Span| {
|
||||
let not_macro_argument_snippet = snippet_with_macro_callsite(cx, span, "..");
|
||||
let snip = if not_macro_argument_snippet == "vec![]" {
|
||||
let macro_expanded_snipped = snippet(cx, snippet_span, "..");
|
||||
match macro_expanded_snipped.strip_prefix("$crate::vec::") {
|
||||
Some(stripped) => Cow::from(stripped),
|
||||
Some(stripped) => Cow::Owned(stripped.to_owned()),
|
||||
None => macro_expanded_snipped,
|
||||
}
|
||||
} else {
|
||||
not_macro_argument_snippet
|
||||
}
|
||||
};
|
||||
|
||||
snip.to_string()
|
||||
};
|
||||
|
||||
if use_lambda {
|
||||
let snip = format_span(snippet_span);
|
||||
let snip = if use_lambda {
|
||||
let l_arg = if fn_has_arguments { "_" } else { "" };
|
||||
format!("|{l_arg}| {snippet}").into()
|
||||
format!("|{l_arg}| {snip}")
|
||||
} else {
|
||||
snippet
|
||||
snip
|
||||
};
|
||||
|
||||
if let Some(f) = second_arg {
|
||||
let f = format_span(f.span);
|
||||
format!("{snip}, {f}")
|
||||
} else {
|
||||
snip
|
||||
}
|
||||
};
|
||||
let span_replace_word = method_span.with_hi(span.hi());
|
||||
|
|
@ -149,8 +161,8 @@ pub(super) fn check<'tcx>(
|
|||
}
|
||||
}
|
||||
|
||||
if let [arg] = args {
|
||||
let inner_arg = if let hir::ExprKind::Block(
|
||||
let extract_inner_arg = |arg: &'tcx hir::Expr<'_>| {
|
||||
if let hir::ExprKind::Block(
|
||||
hir::Block {
|
||||
stmts: [],
|
||||
expr: Some(expr),
|
||||
|
|
@ -162,19 +174,32 @@ pub(super) fn check<'tcx>(
|
|||
expr
|
||||
} else {
|
||||
arg
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
if let [arg] = args {
|
||||
let inner_arg = extract_inner_arg(arg);
|
||||
match inner_arg.kind {
|
||||
hir::ExprKind::Call(fun, or_args) => {
|
||||
let or_has_args = !or_args.is_empty();
|
||||
if !check_unwrap_or_default(cx, name, fun, arg, or_has_args, expr.span, method_span) {
|
||||
let fun_span = if or_has_args { None } else { Some(fun.span) };
|
||||
check_general_case(cx, name, method_span, receiver, arg, expr.span, fun_span);
|
||||
check_general_case(cx, name, method_span, receiver, arg, None, expr.span, fun_span);
|
||||
}
|
||||
},
|
||||
hir::ExprKind::Index(..) | hir::ExprKind::MethodCall(..) => {
|
||||
check_general_case(cx, name, method_span, receiver, arg, expr.span, None);
|
||||
check_general_case(cx, name, method_span, receiver, arg, None, expr.span, None);
|
||||
},
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
// `map_or` takes two arguments
|
||||
if let [arg, lambda] = args {
|
||||
let inner_arg = extract_inner_arg(arg);
|
||||
if let hir::ExprKind::Call(fun, or_args) = inner_arg.kind {
|
||||
let fun_span = if or_args.is_empty() { Some(fun.span) } else { None };
|
||||
check_general_case(cx, name, method_span, receiver, arg, Some(lambda), expr.span, fun_span);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
48
clippy_lints/src/methods/seek_from_current.rs
Normal file
48
clippy_lints/src/methods/seek_from_current.rs
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
use rustc_ast::ast::{LitIntType, LitKind};
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::{Expr, ExprKind};
|
||||
use rustc_lint::LateContext;
|
||||
|
||||
use clippy_utils::{
|
||||
diagnostics::span_lint_and_sugg, get_trait_def_id, match_def_path, paths, source::snippet_with_applicability,
|
||||
ty::implements_trait,
|
||||
};
|
||||
|
||||
use super::SEEK_FROM_CURRENT;
|
||||
|
||||
pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, recv: &'tcx Expr<'_>, arg: &'tcx Expr<'_>) {
|
||||
let ty = cx.typeck_results().expr_ty(recv);
|
||||
|
||||
if let Some(def_id) = get_trait_def_id(cx, &paths::STD_IO_SEEK) {
|
||||
if implements_trait(cx, ty, def_id, &[]) && arg_is_seek_from_current(cx, arg) {
|
||||
let mut applicability = Applicability::MachineApplicable;
|
||||
let snip = snippet_with_applicability(cx, recv.span, "..", &mut applicability);
|
||||
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
SEEK_FROM_CURRENT,
|
||||
expr.span,
|
||||
"using `SeekFrom::Current` to start from current position",
|
||||
"replace with",
|
||||
format!("{snip}.stream_position()"),
|
||||
applicability,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn arg_is_seek_from_current<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> bool {
|
||||
if let ExprKind::Call(f, args) = expr.kind &&
|
||||
let ExprKind::Path(ref path) = f.kind &&
|
||||
let Some(def_id) = cx.qpath_res(path, f.hir_id).opt_def_id() &&
|
||||
match_def_path(cx, def_id, &paths::STD_IO_SEEK_FROM_CURRENT) {
|
||||
// check if argument of `SeekFrom::Current` is `0`
|
||||
if args.len() == 1 &&
|
||||
let ExprKind::Lit(ref lit) = args[0].kind &&
|
||||
let LitKind::Int(0, LitIntType::Unsuffixed) = lit.node {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
45
clippy_lints/src/methods/seek_to_start_instead_of_rewind.rs
Normal file
45
clippy_lints/src/methods/seek_to_start_instead_of_rewind.rs
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
use clippy_utils::diagnostics::span_lint_and_then;
|
||||
use clippy_utils::ty::implements_trait;
|
||||
use clippy_utils::{get_trait_def_id, match_def_path, paths};
|
||||
use rustc_ast::ast::{LitIntType, LitKind};
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::{Expr, ExprKind};
|
||||
use rustc_lint::LateContext;
|
||||
use rustc_span::Span;
|
||||
|
||||
use super::SEEK_TO_START_INSTEAD_OF_REWIND;
|
||||
|
||||
pub(super) fn check<'tcx>(
|
||||
cx: &LateContext<'tcx>,
|
||||
expr: &'tcx Expr<'_>,
|
||||
recv: &'tcx Expr<'_>,
|
||||
arg: &'tcx Expr<'_>,
|
||||
name_span: Span,
|
||||
) {
|
||||
// Get receiver type
|
||||
let ty = cx.typeck_results().expr_ty(recv).peel_refs();
|
||||
|
||||
if let Some(seek_trait_id) = get_trait_def_id(cx, &paths::STD_IO_SEEK) &&
|
||||
implements_trait(cx, ty, seek_trait_id, &[]) &&
|
||||
let ExprKind::Call(func, args1) = arg.kind &&
|
||||
let ExprKind::Path(ref path) = func.kind &&
|
||||
let Some(def_id) = cx.qpath_res(path, func.hir_id).opt_def_id() &&
|
||||
match_def_path(cx, def_id, &paths::STD_IO_SEEKFROM_START) &&
|
||||
args1.len() == 1 &&
|
||||
let ExprKind::Lit(ref lit) = args1[0].kind &&
|
||||
let LitKind::Int(0, LitIntType::Unsuffixed) = lit.node
|
||||
{
|
||||
let method_call_span = expr.span.with_lo(name_span.lo());
|
||||
span_lint_and_then(
|
||||
cx,
|
||||
SEEK_TO_START_INSTEAD_OF_REWIND,
|
||||
method_call_span,
|
||||
"used `seek` to go to the start of the stream",
|
||||
|diag| {
|
||||
let app = Applicability::MachineApplicable;
|
||||
|
||||
diag.span_suggestion(method_call_span, "replace with", "rewind()", app);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -18,7 +18,11 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, recv: &hir::Expr
|
|||
let target = &arglists[0].0;
|
||||
let self_ty = cx.typeck_results().expr_ty(target).peel_refs();
|
||||
let ref_str = if *self_ty.kind() == ty::Str {
|
||||
""
|
||||
if matches!(target.kind, hir::ExprKind::Index(..)) {
|
||||
"&"
|
||||
} else {
|
||||
""
|
||||
}
|
||||
} else if is_type_lang_item(cx, self_ty, hir::LangItem::String) {
|
||||
"&"
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ use rustc_span::sym;
|
|||
|
||||
use super::SUSPICIOUS_MAP;
|
||||
|
||||
pub fn check<'tcx>(cx: &LateContext<'tcx>, expr: &hir::Expr<'_>, count_recv: &hir::Expr<'_>, map_arg: &hir::Expr<'_>) {
|
||||
pub fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, count_recv: &hir::Expr<'_>, map_arg: &hir::Expr<'_>) {
|
||||
if_chain! {
|
||||
if is_trait_method(cx, count_recv, sym::Iterator);
|
||||
let closure = expr_or_init(cx, map_arg);
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ pub(super) fn check<'tcx>(
|
|||
cx,
|
||||
UNNECESSARY_JOIN,
|
||||
span.with_hi(expr.span.hi()),
|
||||
r#"called `.collect<Vec<String>>().join("")` on an iterator"#,
|
||||
r#"called `.collect::<Vec<String>>().join("")` on an iterator"#,
|
||||
"try using",
|
||||
"collect::<String>()".to_owned(),
|
||||
applicability,
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
use clippy_utils::diagnostics::span_lint_and_help;
|
||||
use clippy_utils::ty::is_type_diagnostic_item;
|
||||
use clippy_utils::{is_in_test_function, is_lint_allowed};
|
||||
use clippy_utils::{is_in_cfg_test, is_lint_allowed};
|
||||
use rustc_hir as hir;
|
||||
use rustc_lint::LateContext;
|
||||
use rustc_span::sym;
|
||||
|
|
@ -18,16 +18,16 @@ pub(super) fn check(
|
|||
let obj_ty = cx.typeck_results().expr_ty(recv).peel_refs();
|
||||
|
||||
let mess = if is_type_diagnostic_item(cx, obj_ty, sym::Option) && !is_err {
|
||||
Some((UNWRAP_USED, "an Option", "None", ""))
|
||||
Some((UNWRAP_USED, "an `Option`", "None", ""))
|
||||
} else if is_type_diagnostic_item(cx, obj_ty, sym::Result) {
|
||||
Some((UNWRAP_USED, "a Result", if is_err { "Ok" } else { "Err" }, "an "))
|
||||
Some((UNWRAP_USED, "a `Result`", if is_err { "Ok" } else { "Err" }, "an "))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let method_suffix = if is_err { "_err" } else { "" };
|
||||
|
||||
if allow_unwrap_in_tests && is_in_test_function(cx.tcx, expr.hir_id) {
|
||||
if allow_unwrap_in_tests && is_in_cfg_test(cx.tcx, expr.hir_id) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -45,7 +45,7 @@ pub(super) fn check(
|
|||
cx,
|
||||
lint,
|
||||
expr.span,
|
||||
&format!("used `unwrap{method_suffix}()` on `{kind}` value"),
|
||||
&format!("used `unwrap{method_suffix}()` on {kind} value"),
|
||||
None,
|
||||
&help,
|
||||
);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue