mod iter_next_slice;
mod iter_nth;
mod iter_nth_zero;
+mod iter_overeager_cloned;
mod iter_skip_next;
+mod iter_with_drain;
mod iterator_step_by_zero;
mod manual_saturating_arithmetic;
-mod manual_split_once;
mod manual_str_repeat;
mod map_collect_result_unit;
mod map_flatten;
mod option_map_or_none;
mod option_map_unwrap_or;
mod or_fun_call;
+mod or_then_unwrap;
mod search_is_some;
mod single_char_add_str;
mod single_char_insert_string;
mod single_char_pattern;
mod single_char_push_string;
mod skip_while_next;
+mod str_splitn;
mod string_extend_chars;
mod suspicious_map;
mod suspicious_splitn;
mod uninit_assumed_init;
mod unnecessary_filter_map;
mod unnecessary_fold;
+mod unnecessary_iter_cloned;
+mod unnecessary_join;
mod unnecessary_lazy_eval;
+mod unnecessary_to_owned;
mod unwrap_or_else_default;
mod unwrap_used;
mod useless_asref;
use clippy_utils::consts::{constant, Constant};
use clippy_utils::diagnostics::{span_lint, span_lint_and_help};
use clippy_utils::ty::{contains_adt_constructor, contains_ty, implements_trait, is_copy, is_type_diagnostic_item};
-use clippy_utils::{contains_return, get_trait_def_id, in_macro, iter_input_pats, meets_msrv, msrvs, paths, return_ty};
+use clippy_utils::{contains_return, get_trait_def_id, iter_input_pats, meets_msrv, msrvs, paths, 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_lint::{LateContext, LateLintPass, LintContext};
use rustc_middle::lint::in_external_macro;
-use rustc_middle::ty::{self, TraitRef, Ty, TyS};
+use rustc_middle::ty::{self, TraitRef, Ty};
use rustc_semver::RustcVersion;
use rustc_session::{declare_tool_lint, impl_lint_pass};
-use rustc_span::symbol::SymbolStr;
use rustc_span::{sym, Span};
use rustc_typeck::hir_ty_to_ty;
/// ```rust
/// [1, 2, 3].iter().copied();
/// ```
+ #[clippy::version = "1.53.0"]
pub CLONED_INSTEAD_OF_COPIED,
pedantic,
"used `cloned` where `copied` could be used instead"
}
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for usage of `_.cloned().<func>()` where call to `.cloned()` can be postponed.
+ ///
+ /// ### Why is this bad?
+ /// It's often inefficient to clone all elements of an iterator, when eventually, only some
+ /// of them will be consumed.
+ ///
+ /// ### Examples
+ /// ```rust
+ /// # let vec = vec!["string".to_string()];
+ ///
+ /// // Bad
+ /// vec.iter().cloned().take(10);
+ ///
+ /// // Good
+ /// vec.iter().take(10).cloned();
+ ///
+ /// // Bad
+ /// vec.iter().cloned().last();
+ ///
+ /// // Good
+ /// vec.iter().last().cloned();
+ ///
+ /// ```
+ /// ### Known Problems
+ /// This `lint` removes the side of effect of cloning items in the iterator.
+ /// A code that relies on that side-effect could fail.
+ ///
+ #[clippy::version = "1.59.0"]
+ pub ITER_OVEREAGER_CLONED,
+ perf,
+ "using `cloned()` early with `Iterator::iter()` can lead to some performance inefficiencies"
+}
+
declare_clippy_lint! {
/// ### What it does
/// Checks for usages of `Iterator::flat_map()` where `filter_map()` could be
/// ```rust
/// let nums: Vec<i32> = ["1", "2", "whee!"].iter().filter_map(|x| x.parse().ok()).collect();
/// ```
+ #[clippy::version = "1.53.0"]
pub FLAT_MAP_OPTION,
pedantic,
"used `flat_map` where `filter_map` could be used instead"
/// // Good
/// res.expect("more helpful message");
/// ```
+ #[clippy::version = "1.45.0"]
pub UNWRAP_USED,
restriction,
"using `.unwrap()` on `Result` or `Option`, which should at least get a better message using `expect()`"
/// res?;
/// # Ok::<(), ()>(())
/// ```
+ #[clippy::version = "1.45.0"]
pub EXPECT_USED,
restriction,
"using `.expect()` on `Result` or `Option`, which might be better handled"
/// }
/// }
/// ```
+ #[clippy::version = "pre 1.29.0"]
pub SHOULD_IMPLEMENT_TRAIT,
style,
"defining a method that should be implementing a std trait"
/// }
/// }
/// ```
+ #[clippy::version = "pre 1.29.0"]
pub WRONG_SELF_CONVENTION,
style,
"defining a method named with an established prefix (like \"into_\") that takes `self` with the wrong convention"
/// // Good
/// x.expect("why did I do this again?");
/// ```
+ #[clippy::version = "pre 1.29.0"]
pub OK_EXPECT,
style,
"using `ok().expect()`, which gives worse error messages than calling `expect` directly on the Result"
/// // Good
/// x.unwrap_or_default();
/// ```
+ #[clippy::version = "1.56.0"]
pub UNWRAP_OR_ELSE_DEFAULT,
style,
"using `.unwrap_or_else(Default::default)`, which is more succinctly expressed as `.unwrap_or_default()`"
/// // Good
/// x.map_or_else(some_function, |a| a + 1);
/// ```
+ #[clippy::version = "1.45.0"]
pub MAP_UNWRAP_OR,
pedantic,
"using `.map(f).unwrap_or(a)` or `.map(f).unwrap_or_else(func)`, which are more succinctly expressed as `map_or(a, f)` or `map_or_else(a, f)`"
/// // Good
/// opt.and_then(|a| Some(a + 1));
/// ```
+ #[clippy::version = "pre 1.29.0"]
pub OPTION_MAP_OR_NONE,
style,
"using `Option.map_or(None, f)`, which is more succinctly expressed as `and_then(f)`"
/// # let r: Result<u32, &str> = Ok(1);
/// assert_eq!(Some(1), r.ok());
/// ```
+ #[clippy::version = "1.44.0"]
pub RESULT_MAP_OR_INTO_OPTION,
style,
"using `Result.map_or(None, Some)`, which is more succinctly expressed as `ok()`"
/// let _ = res().map(|s| if s.len() == 42 { 10 } else { 20 });
/// let _ = res().map_err(|s| if s.len() == 42 { 10 } else { 20 });
/// ```
+ #[clippy::version = "1.45.0"]
pub BIND_INSTEAD_OF_MAP,
complexity,
"using `Option.and_then(|x| Some(y))`, which is more succinctly expressed as `map(|x| y)`"
/// # let vec = vec![1];
/// vec.iter().find(|x| **x == 0);
/// ```
+ #[clippy::version = "pre 1.29.0"]
pub FILTER_NEXT,
complexity,
"using `filter(p).next()`, which is more succinctly expressed as `.find(p)`"
/// # let vec = vec![1];
/// vec.iter().find(|x| **x != 0);
/// ```
+ #[clippy::version = "1.42.0"]
pub SKIP_WHILE_NEXT,
complexity,
"using `skip_while(p).next()`, which is more succinctly expressed as `.find(!p)`"
///
/// ### Why is this bad?
/// Readability, this can be written more concisely as
- /// `_.flat_map(_)`
+ /// `_.flat_map(_)` for `Iterator` or `_.and_then(_)` for `Option`
///
/// ### Example
/// ```rust
/// let vec = vec![vec![1]];
+ /// let opt = Some(5);
///
/// // Bad
/// vec.iter().map(|x| x.iter()).flatten();
+ /// opt.map(|x| Some(x * 2)).flatten();
///
/// // Good
/// vec.iter().flat_map(|x| x.iter());
+ /// opt.and_then(|x| Some(x * 2));
/// ```
+ #[clippy::version = "1.31.0"]
pub MAP_FLATTEN,
- pedantic,
+ complexity,
"using combinations of `flatten` and `map` which can usually be written as a single method call"
}
/// ```rust
/// (0_i32..10).filter_map(|n| n.checked_add(1));
/// ```
+ #[clippy::version = "1.51.0"]
pub MANUAL_FILTER_MAP,
complexity,
"using `_.filter(_).map(_)` in a way that can be written more simply as `filter_map(_)`"
/// ```rust
/// (0_i32..10).find_map(|n| n.checked_add(1));
/// ```
+ #[clippy::version = "1.51.0"]
pub MANUAL_FIND_MAP,
complexity,
"using `_.find(_).map(_)` in a way that can be written more simply as `find_map(_)`"
/// ```rust
/// (0..3).find_map(|x| if x == 2 { Some(x) } else { None });
/// ```
+ #[clippy::version = "1.36.0"]
pub FILTER_MAP_NEXT,
pedantic,
"using combination of `filter_map` and `next` which can usually be written as a single method call"
/// # let iter = vec![vec![0]].into_iter();
/// iter.flatten();
/// ```
+ #[clippy::version = "1.39.0"]
pub FLAT_MAP_IDENTITY,
complexity,
"call to `flat_map` where `flatten` is sufficient"
///
/// let _ = !"hello world".contains("world");
/// ```
+ #[clippy::version = "pre 1.29.0"]
pub SEARCH_IS_SOME,
complexity,
"using an iterator or string search followed by `is_some()` or `is_none()`, which is more succinctly expressed as a call to `any()` or `contains()` (with negation in case of `is_none()`)"
/// let name = "foo";
/// if name.starts_with('_') {};
/// ```
+ #[clippy::version = "pre 1.29.0"]
pub CHARS_NEXT_CMP,
style,
"using `.chars().next()` to check if a string starts with a char"
/// # let foo = Some(String::new());
/// foo.unwrap_or_default();
/// ```
+ #[clippy::version = "pre 1.29.0"]
pub OR_FUN_CALL,
perf,
"using any `*or` method with a function call, which suggests `*or_else`"
}
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for `.or(…).unwrap()` calls to Options and Results.
+ ///
+ /// ### Why is this bad?
+ /// You should use `.unwrap_or(…)` instead for clarity.
+ ///
+ /// ### Example
+ /// ```rust
+ /// # let fallback = "fallback";
+ /// // Result
+ /// # type Error = &'static str;
+ /// # let result: Result<&str, Error> = Err("error");
+ /// let value = result.or::<Error>(Ok(fallback)).unwrap();
+ ///
+ /// // Option
+ /// # let option: Option<&str> = None;
+ /// let value = option.or(Some(fallback)).unwrap();
+ /// ```
+ /// Use instead:
+ /// ```rust
+ /// # let fallback = "fallback";
+ /// // Result
+ /// # let result: Result<&str, &str> = Err("error");
+ /// let value = result.unwrap_or(fallback);
+ ///
+ /// // Option
+ /// # let option: Option<&str> = None;
+ /// let value = option.unwrap_or(fallback);
+ /// ```
+ #[clippy::version = "1.61.0"]
+ pub OR_THEN_UNWRAP,
+ complexity,
+ "checks for `.or(…).unwrap()` calls to Options and Results."
+}
+
declare_clippy_lint! {
/// ### What it does
/// Checks for calls to `.expect(&format!(...))`, `.expect(foo(..))`,
/// # let err_msg = "I'm a teapot";
/// foo.unwrap_or_else(|| panic!("Err {}: {}", err_code, err_msg));
/// ```
+ #[clippy::version = "pre 1.29.0"]
pub EXPECT_FUN_CALL,
perf,
"using any `expect` method with a function call"
/// ```rust
/// 42u64.clone();
/// ```
+ #[clippy::version = "pre 1.29.0"]
pub CLONE_ON_COPY,
complexity,
"using `clone` on a `Copy` type"
/// // Good
/// Rc::clone(&x);
/// ```
+ #[clippy::version = "pre 1.29.0"]
pub CLONE_ON_REF_PTR,
restriction,
"using 'clone' on a ref-counted pointer"
/// println!("{:p} {:p}", *y, z); // prints out the same pointer
/// }
/// ```
+ #[clippy::version = "pre 1.29.0"]
pub CLONE_DOUBLE_REF,
correctness,
"using `clone` on `&&T`"
/// // OK, the specialized impl is used
/// ["foo", "bar"].iter().map(|&s| s.to_string());
/// ```
+ #[clippy::version = "1.40.0"]
pub INEFFICIENT_TO_STRING,
pedantic,
"using `to_string` on `&&T` where `T: ToString`"
/// fn new() -> Self;
/// }
/// ```
+ #[clippy::version = "pre 1.29.0"]
pub NEW_RET_NO_SELF,
style,
"not returning type containing `Self` in a `new` method"
///
/// // Good
/// _.split('x');
+ #[clippy::version = "pre 1.29.0"]
pub SINGLE_CHAR_PATTERN,
perf,
"using a single-character str where a char could be used, e.g., `_.split(\"x\")`"
/// //..
/// }
/// ```
+ #[clippy::version = "pre 1.29.0"]
pub ITERATOR_STEP_BY_ZERO,
correctness,
"using `Iterator::step_by(0)`, which will panic at runtime"
/// ```rust
/// let _ = std::iter::empty::<Option<i32>>().flatten();
/// ```
+ #[clippy::version = "1.53.0"]
pub OPTION_FILTER_MAP,
complexity,
"filtering `Option` for `Some` then force-unwrapping, which can be one type-safe operation"
/// # s.insert(1);
/// let x = s.iter().next();
/// ```
+ #[clippy::version = "1.42.0"]
pub ITER_NTH_ZERO,
style,
"replace `iter.nth(0)` with `iter.next()`"
/// let bad_vec = some_vec.get(3);
/// let bad_slice = &some_vec[..].get(3);
/// ```
+ #[clippy::version = "pre 1.29.0"]
pub ITER_NTH,
perf,
"using `.iter().nth()` on a standard library type with O(1) element access"
/// let bad_vec = some_vec.iter().nth(3);
/// let bad_slice = &some_vec[..].iter().nth(3);
/// ```
+ #[clippy::version = "pre 1.29.0"]
pub ITER_SKIP_NEXT,
style,
"using `.skip(x).next()` on an iterator"
}
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for use of `.drain(..)` on `Vec` and `VecDeque` for iteration.
+ ///
+ /// ### Why is this bad?
+ /// `.into_iter()` is simpler with better performance.
+ ///
+ /// ### Example
+ /// ```rust
+ /// # use std::collections::HashSet;
+ /// let mut foo = vec![0, 1, 2, 3];
+ /// let bar: HashSet<usize> = foo.drain(..).collect();
+ /// ```
+ /// Use instead:
+ /// ```rust
+ /// # use std::collections::HashSet;
+ /// let foo = vec![0, 1, 2, 3];
+ /// let bar: HashSet<usize> = foo.into_iter().collect();
+ /// ```
+ #[clippy::version = "1.61.0"]
+ pub ITER_WITH_DRAIN,
+ nursery,
+ "replace `.drain(..)` with `.into_iter()`"
+}
+
declare_clippy_lint! {
/// ### What it does
/// Checks for use of `.get().unwrap()` (or
/// let last = some_vec[3];
/// some_vec[0] = 1;
/// ```
+ #[clippy::version = "pre 1.29.0"]
pub GET_UNWRAP,
restriction,
"using `.get().unwrap()` or `.get_mut().unwrap()` when using `[]` would work instead"
/// // Good
/// a.append(&mut b);
/// ```
+ #[clippy::version = "1.55.0"]
pub EXTEND_WITH_DRAIN,
perf,
"using vec.append(&mut vec) to move the full range of a vecor to another"
/// s.push_str(abc);
/// s.push_str(&def);
/// ```
+ #[clippy::version = "pre 1.29.0"]
pub STRING_EXTEND_CHARS,
style,
"using `x.extend(s.chars())` where s is a `&str` or `String`"
/// let s = [1, 2, 3, 4, 5];
/// let s2: Vec<isize> = s.to_vec();
/// ```
+ #[clippy::version = "pre 1.29.0"]
pub ITER_CLONED_COLLECT,
style,
"using `.cloned().collect()` on slice to create a `Vec`"
/// // Good
/// name.ends_with('_') || name.ends_with('-');
/// ```
+ #[clippy::version = "pre 1.29.0"]
pub CHARS_LAST_CMP,
style,
"using `.chars().last()` or `.chars().next_back()` to check if a string ends with a char"
/// let x: &[i32] = &[1, 2, 3, 4, 5];
/// do_stuff(x);
/// ```
+ #[clippy::version = "pre 1.29.0"]
pub USELESS_ASREF,
complexity,
"using `as_ref` where the types before and after the call are the same"
/// ```rust
/// let _ = (0..3).any(|x| x > 2);
/// ```
+ #[clippy::version = "pre 1.29.0"]
pub UNNECESSARY_FOLD,
style,
"using `fold` when a more succinct alternative exists"
declare_clippy_lint! {
/// ### What it does
- /// Checks for `filter_map` calls which could be replaced by `filter` or `map`.
+ /// Checks for `filter_map` calls that could be replaced by `filter` or `map`.
/// More specifically it checks if the closure provided is only performing one of the
/// filter or map operations and suggests the appropriate option.
///
/// // As there is no conditional check on the argument this could be written as:
/// let _ = (0..4).map(|x| x + 1);
/// ```
+ #[clippy::version = "1.31.0"]
pub UNNECESSARY_FILTER_MAP,
complexity,
"using `filter_map` when a more succinct alternative exists"
}
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for `find_map` calls that could be replaced by `find` or `map`. More
+ /// specifically it checks if the closure provided is only performing one of the
+ /// find or map operations and suggests the appropriate option.
+ ///
+ /// ### Why is this bad?
+ /// Complexity. The intent is also clearer if only a single
+ /// operation is being performed.
+ ///
+ /// ### Example
+ /// ```rust
+ /// let _ = (0..3).find_map(|x| if x > 2 { Some(x) } else { None });
+ ///
+ /// // As there is no transformation of the argument this could be written as:
+ /// let _ = (0..3).find(|&x| x > 2);
+ /// ```
+ ///
+ /// ```rust
+ /// let _ = (0..4).find_map(|x| Some(x + 1));
+ ///
+ /// // As there is no conditional check on the argument this could be written as:
+ /// let _ = (0..4).map(|x| x + 1).next();
+ /// ```
+ #[clippy::version = "1.61.0"]
+ pub UNNECESSARY_FIND_MAP,
+ complexity,
+ "using `find_map` when a more succinct alternative exists"
+}
+
declare_clippy_lint! {
/// ### What it does
/// Checks for `into_iter` calls on references which should be replaced by `iter`
/// // Good
/// let _ = (&vec![3, 4, 5]).iter();
/// ```
+ #[clippy::version = "1.32.0"]
pub INTO_ITER_ON_REF,
style,
"using `.into_iter()` on a reference"
/// ```rust
/// let _ = (0..3).map(|x| x + 2).count();
/// ```
+ #[clippy::version = "1.39.0"]
pub SUSPICIOUS_MAP,
suspicious,
"suspicious usage of map"
/// MaybeUninit::uninit().assume_init()
/// };
/// ```
+ #[clippy::version = "1.39.0"]
pub UNINIT_ASSUMED_INIT,
correctness,
"`MaybeUninit::uninit().assume_init()`"
/// let add = x.saturating_add(y);
/// let sub = x.saturating_sub(y);
/// ```
+ #[clippy::version = "1.39.0"]
pub MANUAL_SATURATING_ARITHMETIC,
style,
"`.chcked_add/sub(x).unwrap_or(MAX/MIN)`"
/// ```rust
/// unsafe { (&() as *const ()).offset(1) };
/// ```
+ #[clippy::version = "1.41.0"]
pub ZST_OFFSET,
correctness,
"Check for offset calculations on raw pointers to zero-sized types"
/// # Ok::<_, std::io::Error>(())
/// # };
/// ```
+ #[clippy::version = "1.42.0"]
pub FILETYPE_IS_FILE,
restriction,
"`FileType::is_file` is not recommended to test for readable file type"
/// opt.as_deref()
/// # ;
/// ```
+ #[clippy::version = "1.42.0"]
pub OPTION_AS_REF_DEREF,
complexity,
"using `as_ref().map(Deref::deref)`, which is more succinctly expressed as `as_deref()`"
/// a.get(2);
/// b.get(0);
/// ```
+ #[clippy::version = "1.46.0"]
pub ITER_NEXT_SLICE,
style,
"using `.iter().next()` on a sliced array, which can be shortened to just `.get()`"
/// string.insert(0, 'R');
/// string.push('R');
/// ```
+ #[clippy::version = "1.49.0"]
pub SINGLE_CHAR_ADD_STR,
style,
"`push_str()` or `insert_str()` used with a single-character string literal as parameter"
///
/// opt.unwrap_or(42);
/// ```
+ #[clippy::version = "1.48.0"]
pub UNNECESSARY_LAZY_EVALUATIONS,
style,
"using unnecessary lazy evaluation, which can be replaced with simpler eager evaluation"
/// ```rust
/// (0..3).try_for_each(|t| Err(t));
/// ```
+ #[clippy::version = "1.49.0"]
pub MAP_COLLECT_RESULT_UNIT,
style,
"using `.map(_).collect::<Result<(),_>()`, which can be replaced with `try_for_each`"
///
/// assert_eq!(v, vec![5, 5, 5, 5, 5]);
/// ```
+ #[clippy::version = "1.49.0"]
pub FROM_ITER_INSTEAD_OF_COLLECT,
pedantic,
"use `.collect()` instead of `::from_iter()`"
/// assert!(x >= 0);
/// });
/// ```
+ #[clippy::version = "1.51.0"]
pub INSPECT_FOR_EACH,
complexity,
"using `.inspect().for_each()`, which can be replaced with `.for_each()`"
/// # let iter = vec![Some(1)].into_iter();
/// iter.flatten();
/// ```
+ #[clippy::version = "1.52.0"]
pub FILTER_MAP_IDENTITY,
complexity,
"call to `filter_map` where `flatten` is sufficient"
/// let x = [1, 2, 3];
/// let y: Vec<_> = x.iter().map(|x| 2*x).collect();
/// ```
+ #[clippy::version = "1.52.0"]
pub MAP_IDENTITY,
complexity,
"using iterator.map(|x| x)"
/// // Good
/// let _ = "Hello".as_bytes().get(3);
/// ```
+ #[clippy::version = "1.52.0"]
pub BYTES_NTH,
style,
"replace `.bytes().nth()` with `.as_bytes().get()`"
/// let b = a.clone();
/// let c = a.clone();
/// ```
+ #[clippy::version = "1.52.0"]
pub IMPLICIT_CLONE,
pedantic,
"implicitly cloning a value by invoking a function on its dereferenced type"
/// let _ = some_vec.len();
/// let _ = &some_vec[..].len();
/// ```
+ #[clippy::version = "1.52.0"]
pub ITER_COUNT,
complexity,
"replace `.iter().count()` with `.len()`"
/// // use x
/// }
/// ```
+ #[clippy::version = "1.54.0"]
pub SUSPICIOUS_SPLITN,
correctness,
"checks for `.splitn(0, ..)` and `.splitn(1, ..)`"
/// // Good
/// let x: String = "x".repeat(10);
/// ```
+ #[clippy::version = "1.54.0"]
pub MANUAL_STR_REPEAT,
perf,
"manual implementation of `str::repeat`"
/// let (key, value) = _.split_once('=')?;
/// let value = _.split_once('=')?.1;
/// ```
+ #[clippy::version = "1.57.0"]
pub MANUAL_SPLIT_ONCE,
complexity,
"replace `.splitn(2, pat)` with `.split_once(pat)`"
}
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for usages of `str::splitn` (or `str::rsplitn`) where using `str::split` would be the same.
+ /// ### Why is this bad?
+ /// The function `split` is simpler and there is no performance difference in these cases, considering
+ /// that both functions return a lazy iterator.
+ /// ### Example
+ /// ```rust
+ /// // Bad
+ /// let str = "key=value=add";
+ /// let _ = str.splitn(3, '=').next().unwrap();
+ /// ```
+ /// Use instead:
+ /// ```rust
+ /// // Good
+ /// let str = "key=value=add";
+ /// let _ = str.split('=').next().unwrap();
+ /// ```
+ #[clippy::version = "1.58.0"]
+ pub NEEDLESS_SPLITN,
+ complexity,
+ "usages of `str::splitn` that can be replaced with `str::split`"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for unnecessary calls to [`ToOwned::to_owned`](https://doc.rust-lang.org/std/borrow/trait.ToOwned.html#tymethod.to_owned)
+ /// and other `to_owned`-like functions.
+ ///
+ /// ### Why is this bad?
+ /// The unnecessary calls result in useless allocations.
+ ///
+ /// ### Known problems
+ /// `unnecessary_to_owned` can falsely trigger if `IntoIterator::into_iter` is applied to an
+ /// owned copy of a resource and the resource is later used mutably. See
+ /// [#8148](https://github.com/rust-lang/rust-clippy/issues/8148).
+ ///
+ /// ### Example
+ /// ```rust
+ /// let path = std::path::Path::new("x");
+ /// foo(&path.to_string_lossy().to_string());
+ /// fn foo(s: &str) {}
+ /// ```
+ /// Use instead:
+ /// ```rust
+ /// let path = std::path::Path::new("x");
+ /// foo(&path.to_string_lossy());
+ /// fn foo(s: &str) {}
+ /// ```
+ #[clippy::version = "1.58.0"]
+ pub UNNECESSARY_TO_OWNED,
+ perf,
+ "unnecessary calls to `to_owned`-like functions"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for use of `.collect::<Vec<String>>().join("")` on iterators.
+ ///
+ /// ### Why is this bad?
+ /// `.collect::<String>()` is more concise and usually more performant
+ ///
+ /// ### Example
+ /// ```rust
+ /// let vector = vec!["hello", "world"];
+ /// let output = vector.iter().map(|item| item.to_uppercase()).collect::<Vec<String>>().join("");
+ /// println!("{}", output);
+ /// ```
+ /// The correct use would be:
+ /// ```rust
+ /// let vector = vec!["hello", "world"];
+ /// let output = vector.iter().map(|item| item.to_uppercase()).collect::<String>();
+ /// println!("{}", output);
+ /// ```
+ /// ### Known problems
+ /// While `.collect::<String>()` is more performant in most cases, there are cases where
+ /// using `.collect::<String>()` over `.collect::<Vec<String>>().join("")`
+ /// will prevent loop unrolling and will result in a negative performance impact.
+ #[clippy::version = "1.61.0"]
+ pub UNNECESSARY_JOIN,
+ pedantic,
+ "using `.collect::<Vec<String>>().join(\"\")` on an iterator"
+}
+
pub struct Methods {
avoid_breaking_exported_api: bool,
msrv: Option<RustcVersion>,
OPTION_MAP_OR_NONE,
BIND_INSTEAD_OF_MAP,
OR_FUN_CALL,
+ OR_THEN_UNWRAP,
EXPECT_FUN_CALL,
CHARS_NEXT_CMP,
CHARS_LAST_CMP,
CLONE_ON_COPY,
CLONE_ON_REF_PTR,
CLONE_DOUBLE_REF,
+ ITER_OVEREAGER_CLONED,
CLONED_INSTEAD_OF_COPIED,
FLAT_MAP_OPTION,
INEFFICIENT_TO_STRING,
GET_UNWRAP,
STRING_EXTEND_CHARS,
ITER_CLONED_COLLECT,
+ ITER_WITH_DRAIN,
USELESS_ASREF,
UNNECESSARY_FOLD,
UNNECESSARY_FILTER_MAP,
+ UNNECESSARY_FIND_MAP,
INTO_ITER_ON_REF,
SUSPICIOUS_MAP,
UNINIT_ASSUMED_INIT,
SUSPICIOUS_SPLITN,
MANUAL_STR_REPEAT,
EXTEND_WITH_DRAIN,
- MANUAL_SPLIT_ONCE
+ MANUAL_SPLIT_ONCE,
+ NEEDLESS_SPLITN,
+ UNNECESSARY_TO_OWNED,
+ UNNECESSARY_JOIN,
]);
/// Extracts a method call name, args, and `Span` of the method name.
-fn method_call<'tcx>(recv: &'tcx hir::Expr<'tcx>) -> Option<(SymbolStr, &'tcx [hir::Expr<'tcx>], Span)> {
- if let ExprKind::MethodCall(path, span, args, _) = recv.kind {
+fn method_call<'tcx>(recv: &'tcx hir::Expr<'tcx>) -> Option<(&'tcx str, &'tcx [hir::Expr<'tcx>], Span)> {
+ if let ExprKind::MethodCall(path, args, _) = recv.kind {
if !args.iter().any(|e| e.span.from_expansion()) {
- return Some((path.ident.name.as_str(), args, span));
+ let name = path.ident.name.as_str();
+ return Some((name, args, path.ident.span));
}
}
None
}
-/// Same as `method_call` but the `SymbolStr` is dereferenced into a temporary `&str`
-macro_rules! method_call {
- ($expr:expr) => {
- method_call($expr)
- .as_ref()
- .map(|&(ref name, args, span)| (&**name, args, span))
- };
-}
-
impl<'tcx> LateLintPass<'tcx> for Methods {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>) {
- if in_macro(expr.span) {
+ if expr.span.from_expansion() {
return;
}
hir::ExprKind::Call(func, args) => {
from_iter_instead_of_collect::check(cx, expr, args, func);
},
- hir::ExprKind::MethodCall(method_call, ref method_span, args, _) => {
- or_fun_call::check(cx, expr, *method_span, &method_call.ident.as_str(), args);
- expect_fun_call::check(cx, expr, *method_span, &method_call.ident.as_str(), args);
+ hir::ExprKind::MethodCall(method_call, args, _) => {
+ let method_span = method_call.ident.span;
+ or_fun_call::check(cx, expr, method_span, method_call.ident.as_str(), args);
+ expect_fun_call::check(cx, expr, method_span, method_call.ident.as_str(), args);
clone_on_copy::check(cx, expr, method_call.ident.name, args);
clone_on_ref_ptr::check(cx, expr, method_call.ident.name, args);
inefficient_to_string::check(cx, expr, method_call.ident.name, args);
single_char_add_str::check(cx, expr, args);
- into_iter_on_ref::check(cx, expr, *method_span, method_call.ident.name, args);
+ into_iter_on_ref::check(cx, expr, method_span, method_call.ident.name, args);
single_char_pattern::check(cx, expr, method_call.ident.name, args);
+ unnecessary_to_owned::check(cx, expr, method_call.ident.name, args);
},
hir::ExprKind::Binary(op, lhs, rhs) if op.node == hir::BinOpKind::Eq || op.node == hir::BinOpKind::Ne => {
let mut info = BinaryExprInfo {
let method_sig = cx.tcx.fn_sig(impl_item.def_id);
let method_sig = cx.tcx.erase_late_bound_regions(method_sig);
- let first_arg_ty = &method_sig.inputs().iter().next();
+ let first_arg_ty = method_sig.inputs().iter().next();
// check conventions w.r.t. conversion method names and predicates
if let Some(first_arg_ty) = first_arg_ty;
if name == method_config.method_name &&
sig.decl.inputs.len() == method_config.param_count &&
method_config.output_type.matches(&sig.decl.output) &&
- method_config.self_kind.matches(cx, self_ty, first_arg_ty) &&
+ method_config.self_kind.matches(cx, self_ty, *first_arg_ty) &&
fn_header_equals(method_config.fn_header, sig.header) &&
method_config.lifetime_param_cond(impl_item)
{
{
wrong_self_convention::check(
cx,
- &name,
+ name,
self_ty,
- first_arg_ty,
+ *first_arg_ty,
first_arg.pat.span,
implements_trait,
false
// 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(cx.tcx, ret_ty, self_adt) {
+ if contains_adt_constructor(ret_ty, self_adt) {
return;
}
- } else if contains_ty(cx.tcx, ret_ty, self_ty) {
+ } else if contains_ty(ret_ty, self_ty) {
return;
}
// 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 {
+ ty::Term::Ty(ty) => ty,
+ ty::Term::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(cx.tcx, projection_predicate.ty, self_adt) {
+ if contains_adt_constructor(assoc_ty, self_adt) {
return;
}
- } else if contains_ty(cx.tcx, projection_predicate.ty, self_ty) {
+ } else if contains_ty(assoc_ty, self_ty) {
return;
}
}
}
}
- if name == "new" && !TyS::same_type(ret_ty, self_ty) {
+ if name == "new" && ret_ty != self_ty {
span_lint(
cx,
NEW_RET_NO_SELF,
let self_ty = TraitRef::identity(cx.tcx, item.def_id.to_def_id()).self_ty().skip_binder();
wrong_self_convention::check(
cx,
- &item.ident.name.as_str(),
+ item.ident.name.as_str(),
self_ty,
first_arg_ty,
first_arg_span,
if let TraitItemKind::Fn(_, _) = item.kind;
let ret_ty = return_ty(cx, item.hir_id());
let self_ty = TraitRef::identity(cx.tcx, item.def_id.to_def_id()).self_ty().skip_binder();
- if !contains_ty(cx.tcx, ret_ty, self_ty);
+ if !contains_ty(ret_ty, self_ty);
then {
span_lint(
#[allow(clippy::too_many_lines)]
fn check_methods<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, msrv: Option<&RustcVersion>) {
- if let Some((name, [recv, args @ ..], span)) = method_call!(expr) {
+ if let Some((name, [recv, args @ ..], span)) = method_call(expr) {
match (name, args) {
("add" | "offset" | "sub" | "wrapping_offset" | "wrapping_add" | "wrapping_sub", [_arg]) => {
zst_offset::check(cx, expr, recv);
("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, msrv),
- ("collect", []) => match method_call!(recv) {
- Some(("cloned", [recv2], _)) => iter_cloned_collect::check(cx, expr, recv2),
+ ("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);
},
},
_ => {},
},
- ("count", []) => match method_call!(recv) {
- Some((name @ ("into_iter" | "iter" | "iter_mut"), [recv2], _)) => {
- iter_count::check(cx, expr, recv2, name);
+ (name @ "count", args @ []) => match method_call(recv) {
+ Some(("cloned", [recv2], _)) => iter_overeager_cloned::check(cx, expr, recv2, name, args),
+ 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),
_ => {},
},
- ("expect", [_]) => match method_call!(recv) {
+ ("drain", [arg]) => {
+ iter_with_drain::check(cx, expr, recv, span, arg);
+ },
+ ("expect", [_]) => match method_call(recv) {
Some(("ok", [recv], _)) => ok_expect::check(cx, expr, recv),
_ => expect_used::check(cx, expr, recv),
},
extend_with_drain::check(cx, expr, recv, arg);
},
("filter_map", [arg]) => {
- unnecessary_filter_map::check(cx, expr, arg);
+ unnecessary_filter_map::check(cx, expr, arg, name);
filter_map_identity::check(cx, expr, arg, span);
},
+ ("find_map", [arg]) => {
+ unnecessary_filter_map::check(cx, expr, arg, name);
+ },
("flat_map", [arg]) => {
flat_map_identity::check(cx, expr, arg, span);
flat_map_option::check(cx, expr, arg, span);
},
- ("flatten", []) => {
- if let Some(("map", [recv, map_arg], _)) = method_call!(recv) {
- map_flatten::check(cx, expr, recv, map_arg);
- }
+ (name @ "flatten", args @ []) => 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, recv2, name, args),
+ _ => {},
},
("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);
}
},
("is_file", []) => filetype_is_file::check(cx, expr, recv),
("is_none", []) => check_is_some_is_none(cx, expr, recv, false),
("is_some", []) => check_is_some_is_none(cx, expr, recv, true),
+ ("join", [join_arg]) => {
+ if let Some(("collect", _, span)) = method_call(recv) {
+ unnecessary_join::check(cx, expr, recv, join_arg, span);
+ }
+ },
+ ("last", args @ []) | ("skip", args @ [_]) => {
+ if let Some((name2, [recv2, args2 @ ..], _span2)) = method_call(recv) {
+ if let ("cloned", []) = (name2, args2) {
+ iter_overeager_cloned::check(cx, expr, recv2, name, args);
+ }
+ }
+ },
("map", [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, msrv),
("as_ref", []) => option_as_ref_deref::check(cx, expr, recv2, m_arg, false, msrv),
map_identity::check(cx, expr, recv, m_arg, span);
},
("map_or", [def, map]) => option_map_or_none::check(cx, expr, recv, def, map),
- ("next", []) => {
- if let Some((name, [recv, args @ ..], _)) = method_call!(recv) {
- match (name, args) {
- ("filter", [arg]) => filter_next::check(cx, expr, recv, arg),
- ("filter_map", [arg]) => filter_map_next::check(cx, expr, recv, arg, msrv),
- ("iter", []) => iter_next_slice::check(cx, expr, recv),
- ("skip", [arg]) => iter_skip_next::check(cx, expr, recv, arg),
+ (name @ "next", args @ []) => {
+ if let Some((name2, [recv2, args2 @ ..], _)) = method_call(recv) {
+ match (name2, args2) {
+ ("cloned", []) => iter_overeager_cloned::check(cx, expr, recv2, name, args),
+ ("filter", [arg]) => filter_next::check(cx, expr, recv2, arg),
+ ("filter_map", [arg]) => filter_map_next::check(cx, expr, recv2, arg, msrv),
+ ("iter", []) => iter_next_slice::check(cx, expr, recv2),
+ ("skip", [arg]) => iter_skip_next::check(cx, expr, recv2, arg),
("skip_while", [_]) => skip_while_next::check(cx, expr),
_ => {},
}
}
},
- ("nth", [n_arg]) => match method_call!(recv) {
+ ("nth", args @ [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, recv2, name, args),
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),
if let Some((Constant::Int(count), _)) = constant(cx, cx.typeck_results(), count_arg) {
suspicious_splitn::check(cx, name, expr, recv, count);
if count == 2 && meets_msrv(msrv, &msrvs::STR_SPLIT_ONCE) {
- manual_split_once::check(cx, name, expr, recv, pat_arg);
+ str_splitn::check_manual_split_once(cx, name, expr, recv, pat_arg);
+ }
+ if count >= 2 {
+ str_splitn::check_needless_splitn(cx, name, expr, recv, pat_arg, count);
}
}
},
}
},
("step_by", [arg]) => iterator_step_by_zero::check(cx, expr, arg),
+ ("take", args @ [_arg]) => {
+ if let Some((name2, [recv2, args2 @ ..], _span2)) = method_call(recv) {
+ if let ("cloned", []) = (name2, args2) {
+ iter_overeager_cloned::check(cx, expr, recv2, name, args);
+ }
+ }
+ },
("to_os_string" | "to_owned" | "to_path_buf" | "to_vec", []) => {
- implicit_clone::check(cx, name, expr, recv, span);
+ implicit_clone::check(cx, name, expr, recv);
},
- ("unwrap", []) => match method_call!(recv) {
- Some(("get", [recv, get_arg], _)) => get_unwrap::check(cx, expr, recv, get_arg, false),
- Some(("get_mut", [recv, get_arg], _)) => get_unwrap::check(cx, expr, recv, get_arg, true),
- _ => unwrap_used::check(cx, expr, recv),
+ ("unwrap", []) => {
+ match method_call(recv) {
+ Some(("get", [recv, get_arg], _)) => {
+ get_unwrap::check(cx, expr, recv, get_arg, false);
+ },
+ Some(("get_mut", [recv, get_arg], _)) => {
+ get_unwrap::check(cx, expr, recv, get_arg, true);
+ },
+ Some(("or", [recv, or_arg], or_span)) => {
+ or_then_unwrap::check(cx, expr, recv, or_arg, or_span);
+ },
+ _ => {},
+ }
+ unwrap_used::check(cx, expr, recv);
},
- ("unwrap_or", [u_arg]) => match method_call!(recv) {
+ ("unwrap_or", [u_arg]) => match method_call(recv) {
Some((arith @ ("checked_add" | "checked_sub" | "checked_mul"), [lhs, rhs], _)) => {
manual_saturating_arithmetic::check(cx, expr, lhs, rhs, u_arg, &arith["checked_".len()..]);
},
},
_ => {},
},
- ("unwrap_or_else", [u_arg]) => match method_call!(recv) {
+ ("unwrap_or_else", [u_arg]) => match method_call(recv) {
Some(("map", [recv, map_arg], _)) if map_unwrap_or::check(cx, expr, recv, map_arg, u_arg, msrv) => {},
_ => {
unwrap_or_else_default::check(cx, expr, recv, u_arg);
}
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);
}
}
implements_trait(cx, ty, trait_def_id, &[parent_ty.into()])
}
+ fn matches_none<'a>(cx: &LateContext<'a>, parent_ty: Ty<'a>, ty: Ty<'a>) -> bool {
+ !matches_value(cx, parent_ty, ty)
+ && !matches_ref(cx, hir::Mutability::Not, parent_ty, ty)
+ && !matches_ref(cx, hir::Mutability::Mut, parent_ty, ty)
+ }
+
match self {
Self::Value => matches_value(cx, parent_ty, ty),
Self::Ref => matches_ref(cx, hir::Mutability::Not, parent_ty, ty) || ty == parent_ty && is_copy(cx, ty),
Self::RefMut => matches_ref(cx, hir::Mutability::Mut, parent_ty, ty),
- Self::No => ty != parent_ty,
+ Self::No => matches_none(cx, parent_ty, ty),
}
}