Merge remote-tracking branch 'upstream/master' into rustup

This commit is contained in:
flip1995 2021-12-02 09:32:09 +00:00
commit abddd6c491
No known key found for this signature in database
GPG key ID: 1CA0DF2AF59D68A5
464 changed files with 7947 additions and 2839 deletions

View file

@ -1,7 +1,7 @@
use super::{contains_return, BIND_INSTEAD_OF_MAP};
use clippy_utils::diagnostics::{multispan_sugg_with_applicability, span_lint_and_sugg, span_lint_and_then};
use clippy_utils::source::{snippet, snippet_with_macro_callsite};
use clippy_utils::{in_macro, remove_blocks, visitors::find_all_ret_expressions};
use clippy_utils::{remove_blocks, visitors::find_all_ret_expressions};
use if_chain::if_chain;
use rustc_errors::Applicability;
use rustc_hir as hir;
@ -106,7 +106,7 @@ pub(crate) trait BindInsteadOfMap {
let mut suggs = Vec::new();
let can_sugg: bool = find_all_ret_expressions(cx, closure_expr, |ret_expr| {
if_chain! {
if !in_macro(ret_expr.span);
if !ret_expr.span.from_expansion();
if let hir::ExprKind::Call(func_path, [arg]) = ret_expr.kind;
if let hir::ExprKind::Path(QPath::Resolved(_, path)) = func_path.kind;
if Self::is_variant(cx, path.res);

View file

@ -69,7 +69,7 @@ fn extract_turbofish(cx: &LateContext<'_>, expr: &hir::Expr<'_>, ty: Ty<'tcx>) -
// i.e.: 2 wildcards in `std::collections::BTreeMap<&i32, &char>`
let ty_str = ty.to_string();
let start = ty_str.find('<').unwrap_or(0);
let end = ty_str.find('>').unwrap_or_else(|| ty_str.len());
let end = ty_str.find('>').unwrap_or(ty_str.len());
let nb_wildcard = ty_str[start..end].split(',').count();
let wildcards = format!("_{}", ", _".repeat(nb_wildcard - 1));
format!("{}<{}>", elements.join("::"), wildcards)

View file

@ -9,7 +9,7 @@ use rustc_span::sym;
use super::ITER_CLONED_COLLECT;
pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &hir::Expr<'_>, recv: &'tcx hir::Expr<'_>) {
pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, method_name: &str, expr: &hir::Expr<'_>, recv: &'tcx hir::Expr<'_>) {
if_chain! {
if is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(expr), sym::Vec);
if let Some(slice) = derefs_to_slice(cx, recv, cx.typeck_results().expr_ty(recv));
@ -20,8 +20,8 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &hir::Expr<'_>, recv: &'
cx,
ITER_CLONED_COLLECT,
to_replace,
"called `iter().cloned().collect()` on a slice to create a `Vec`. Calling `to_vec()` is both faster and \
more readable",
&format!("called `iter().{}().collect()` on a slice to create a `Vec`. Calling `to_vec()` is both faster and \
more readable", method_name),
"try",
".to_vec()".to_string(),
Applicability::MachineApplicable,

View file

@ -33,7 +33,6 @@ mod iter_nth_zero;
mod iter_skip_next;
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;
@ -50,6 +49,7 @@ 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;
@ -68,7 +68,7 @@ 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, 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;
@ -99,6 +99,7 @@ declare_clippy_lint! {
/// ```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"
@ -121,6 +122,7 @@ declare_clippy_lint! {
/// ```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"
@ -166,6 +168,7 @@ declare_clippy_lint! {
/// // 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()`"
@ -208,6 +211,7 @@ declare_clippy_lint! {
/// res?;
/// # Ok::<(), ()>(())
/// ```
#[clippy::version = "1.45.0"]
pub EXPECT_USED,
restriction,
"using `.expect()` on `Result` or `Option`, which might be better handled"
@ -237,6 +241,7 @@ declare_clippy_lint! {
/// }
/// }
/// ```
#[clippy::version = "pre 1.29.0"]
pub SHOULD_IMPLEMENT_TRAIT,
style,
"defining a method that should be implementing a std trait"
@ -284,6 +289,7 @@ declare_clippy_lint! {
/// }
/// }
/// ```
#[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"
@ -310,6 +316,7 @@ declare_clippy_lint! {
/// // 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"
@ -335,6 +342,7 @@ declare_clippy_lint! {
/// // 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()`"
@ -375,6 +383,7 @@ declare_clippy_lint! {
/// // 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)`"
@ -401,6 +410,7 @@ declare_clippy_lint! {
/// // 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)`"
@ -426,6 +436,7 @@ declare_clippy_lint! {
/// # 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()`"
@ -458,6 +469,7 @@ declare_clippy_lint! {
/// 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)`"
@ -481,6 +493,7 @@ declare_clippy_lint! {
/// # 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)`"
@ -504,6 +517,7 @@ declare_clippy_lint! {
/// # 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)`"
@ -527,6 +541,7 @@ declare_clippy_lint! {
/// // Good
/// vec.iter().flat_map(|x| x.iter());
/// ```
#[clippy::version = "1.31.0"]
pub MAP_FLATTEN,
pedantic,
"using combinations of `flatten` and `map` which can usually be written as a single method call"
@ -553,6 +568,7 @@ declare_clippy_lint! {
/// ```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(_)`"
@ -579,6 +595,7 @@ declare_clippy_lint! {
/// ```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(_)`"
@ -601,6 +618,7 @@ declare_clippy_lint! {
/// ```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"
@ -623,6 +641,7 @@ declare_clippy_lint! {
/// # 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"
@ -652,6 +671,7 @@ declare_clippy_lint! {
///
/// 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()`)"
@ -676,6 +696,7 @@ declare_clippy_lint! {
/// 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"
@ -710,6 +731,7 @@ declare_clippy_lint! {
/// # 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`"
@ -748,6 +770,7 @@ declare_clippy_lint! {
/// # 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"
@ -765,6 +788,7 @@ declare_clippy_lint! {
/// ```rust
/// 42u64.clone();
/// ```
#[clippy::version = "pre 1.29.0"]
pub CLONE_ON_COPY,
complexity,
"using `clone` on a `Copy` type"
@ -792,6 +816,7 @@ declare_clippy_lint! {
/// // Good
/// Rc::clone(&x);
/// ```
#[clippy::version = "pre 1.29.0"]
pub CLONE_ON_REF_PTR,
restriction,
"using 'clone' on a ref-counted pointer"
@ -814,6 +839,7 @@ declare_clippy_lint! {
/// 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`"
@ -837,6 +863,7 @@ declare_clippy_lint! {
/// // 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`"
@ -898,6 +925,7 @@ declare_clippy_lint! {
/// fn new() -> Self;
/// }
/// ```
#[clippy::version = "pre 1.29.0"]
pub NEW_RET_NO_SELF,
style,
"not returning type containing `Self` in a `new` method"
@ -922,6 +950,7 @@ declare_clippy_lint! {
///
/// // 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\")`"
@ -941,6 +970,7 @@ declare_clippy_lint! {
/// //..
/// }
/// ```
#[clippy::version = "pre 1.29.0"]
pub ITERATOR_STEP_BY_ZERO,
correctness,
"using `Iterator::step_by(0)`, which will panic at runtime"
@ -962,6 +992,7 @@ declare_clippy_lint! {
/// ```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"
@ -989,6 +1020,7 @@ declare_clippy_lint! {
/// # 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()`"
@ -1015,6 +1047,7 @@ declare_clippy_lint! {
/// 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"
@ -1039,6 +1072,7 @@ declare_clippy_lint! {
/// 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"
@ -1075,6 +1109,7 @@ declare_clippy_lint! {
/// 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"
@ -1098,6 +1133,7 @@ declare_clippy_lint! {
/// // 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"
@ -1127,6 +1163,7 @@ declare_clippy_lint! {
/// 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`"
@ -1150,6 +1187,7 @@ declare_clippy_lint! {
/// 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`"
@ -1174,6 +1212,7 @@ declare_clippy_lint! {
/// // 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"
@ -1199,6 +1238,7 @@ declare_clippy_lint! {
/// 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"
@ -1221,6 +1261,7 @@ declare_clippy_lint! {
/// ```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"
@ -1250,6 +1291,7 @@ declare_clippy_lint! {
/// // 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"
@ -1273,6 +1315,7 @@ declare_clippy_lint! {
/// // Good
/// let _ = (&vec![3, 4, 5]).iter();
/// ```
#[clippy::version = "1.32.0"]
pub INTO_ITER_ON_REF,
style,
"using `.into_iter()` on a reference"
@ -1292,6 +1335,7 @@ declare_clippy_lint! {
/// ```rust
/// let _ = (0..3).map(|x| x + 2).count();
/// ```
#[clippy::version = "1.39.0"]
pub SUSPICIOUS_MAP,
suspicious,
"suspicious usage of map"
@ -1326,6 +1370,7 @@ declare_clippy_lint! {
/// MaybeUninit::uninit().assume_init()
/// };
/// ```
#[clippy::version = "1.39.0"]
pub UNINIT_ASSUMED_INIT,
correctness,
"`MaybeUninit::uninit().assume_init()`"
@ -1354,6 +1399,7 @@ declare_clippy_lint! {
/// 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)`"
@ -1371,6 +1417,7 @@ declare_clippy_lint! {
/// ```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"
@ -1412,6 +1459,7 @@ declare_clippy_lint! {
/// # 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"
@ -1437,6 +1485,7 @@ declare_clippy_lint! {
/// 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()`"
@ -1463,6 +1512,7 @@ declare_clippy_lint! {
/// 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()`"
@ -1488,6 +1538,7 @@ declare_clippy_lint! {
/// 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"
@ -1526,6 +1577,7 @@ declare_clippy_lint! {
///
/// 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"
@ -1546,6 +1598,7 @@ declare_clippy_lint! {
/// ```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`"
@ -1578,6 +1631,7 @@ declare_clippy_lint! {
///
/// 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()`"
@ -1607,6 +1661,7 @@ declare_clippy_lint! {
/// assert!(x >= 0);
/// });
/// ```
#[clippy::version = "1.51.0"]
pub INSPECT_FOR_EACH,
complexity,
"using `.inspect().for_each()`, which can be replaced with `.for_each()`"
@ -1629,6 +1684,7 @@ declare_clippy_lint! {
/// # 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"
@ -1651,6 +1707,7 @@ declare_clippy_lint! {
/// 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)"
@ -1672,6 +1729,7 @@ declare_clippy_lint! {
/// // Good
/// let _ = "Hello".as_bytes().get(3);
/// ```
#[clippy::version = "1.52.0"]
pub BYTES_NTH,
style,
"replace `.bytes().nth()` with `.as_bytes().get()`"
@ -1697,6 +1755,7 @@ declare_clippy_lint! {
/// 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"
@ -1722,6 +1781,7 @@ declare_clippy_lint! {
/// let _ = some_vec.len();
/// let _ = &some_vec[..].len();
/// ```
#[clippy::version = "1.52.0"]
pub ITER_COUNT,
complexity,
"replace `.iter().count()` with `.len()`"
@ -1751,6 +1811,7 @@ declare_clippy_lint! {
/// // use x
/// }
/// ```
#[clippy::version = "1.54.0"]
pub SUSPICIOUS_SPLITN,
correctness,
"checks for `.splitn(0, ..)` and `.splitn(1, ..)`"
@ -1771,6 +1832,7 @@ declare_clippy_lint! {
/// // Good
/// let x: String = "x".repeat(10);
/// ```
#[clippy::version = "1.54.0"]
pub MANUAL_STR_REPEAT,
perf,
"manual implementation of `str::repeat`"
@ -1793,11 +1855,36 @@ declare_clippy_lint! {
/// 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`"
}
pub struct Methods {
avoid_breaking_exported_api: bool,
msrv: Option<RustcVersion>,
@ -1876,7 +1963,8 @@ impl_lint_pass!(Methods => [
SUSPICIOUS_SPLITN,
MANUAL_STR_REPEAT,
EXTEND_WITH_DRAIN,
MANUAL_SPLIT_ONCE
MANUAL_SPLIT_ONCE,
NEEDLESS_SPLITN
]);
/// Extracts a method call name, args, and `Span` of the method name.
@ -1900,7 +1988,7 @@ macro_rules! method_call {
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;
}
@ -2116,7 +2204,9 @@ fn check_methods<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, msrv: Optio
("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),
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);
},
@ -2208,7 +2298,10 @@ fn check_methods<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, msrv: Optio
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);
}
}
},

View file

@ -1,7 +1,7 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::is_lang_ctor;
use clippy_utils::source::snippet;
use clippy_utils::ty::is_type_diagnostic_item;
use clippy_utils::{is_lang_ctor, single_segment_path};
use rustc_errors::Applicability;
use rustc_hir as hir;
use rustc_hir::LangItem::{OptionNone, OptionSome};
@ -11,6 +11,28 @@ use rustc_span::symbol::sym;
use super::OPTION_MAP_OR_NONE;
use super::RESULT_MAP_OR_INTO_OPTION;
// The expression inside a closure may or may not have surrounding braces
// which causes problems when generating a suggestion.
fn reduce_unit_expression<'a>(
cx: &LateContext<'_>,
expr: &'a hir::Expr<'_>,
) -> Option<(&'a hir::Expr<'a>, &'a [hir::Expr<'a>])> {
match expr.kind {
hir::ExprKind::Call(func, arg_char) => Some((func, arg_char)),
hir::ExprKind::Block(block, _) => {
match (block.stmts, block.expr) {
(&[], Some(inner_expr)) => {
// If block only contains an expression,
// reduce `|x| { x + 1 }` to `|x| x + 1`
reduce_unit_expression(cx, inner_expr)
},
_ => None,
}
},
_ => None,
}
}
/// lint use of `_.map_or(None, _)` for `Option`s and `Result`s
pub(super) fn check<'tcx>(
cx: &LateContext<'tcx>,
@ -31,58 +53,75 @@ pub(super) fn check<'tcx>(
return;
}
let (lint_name, msg, instead, hint) = {
let default_arg_is_none = if let hir::ExprKind::Path(ref qpath) = def_arg.kind {
is_lang_ctor(cx, qpath, OptionNone)
} else {
return;
};
if !default_arg_is_none {
// nothing to lint!
return;
}
let f_arg_is_some = if let hir::ExprKind::Path(ref qpath) = map_arg.kind {
is_lang_ctor(cx, qpath, OptionSome)
} else {
false
};
if is_option {
let self_snippet = snippet(cx, recv.span, "..");
let func_snippet = snippet(cx, map_arg.span, "..");
let msg = "called `map_or(None, ..)` on an `Option` value. This can be done more directly by calling \
`and_then(..)` instead";
(
OPTION_MAP_OR_NONE,
msg,
"try using `and_then` instead",
format!("{0}.and_then({1})", self_snippet, func_snippet),
)
} else if f_arg_is_some {
let msg = "called `map_or(None, Some)` on a `Result` value. This can be done more directly by calling \
`ok()` instead";
let self_snippet = snippet(cx, recv.span, "..");
(
RESULT_MAP_OR_INTO_OPTION,
msg,
"try using `ok` instead",
format!("{0}.ok()", self_snippet),
)
} else {
// nothing to lint!
return;
}
let default_arg_is_none = if let hir::ExprKind::Path(ref qpath) = def_arg.kind {
is_lang_ctor(cx, qpath, OptionNone)
} else {
return;
};
span_lint_and_sugg(
cx,
lint_name,
expr.span,
msg,
instead,
hint,
Applicability::MachineApplicable,
);
if !default_arg_is_none {
// nothing to lint!
return;
}
let f_arg_is_some = if let hir::ExprKind::Path(ref qpath) = map_arg.kind {
is_lang_ctor(cx, qpath, OptionSome)
} else {
false
};
if is_option {
let self_snippet = snippet(cx, recv.span, "..");
if_chain! {
if let hir::ExprKind::Closure(_, _, id, span, _) = map_arg.kind;
let arg_snippet = snippet(cx, span, "..");
let body = cx.tcx.hir().body(id);
if let Some((func, arg_char)) = reduce_unit_expression(cx, &body.value);
if arg_char.len() == 1;
if let hir::ExprKind::Path(ref qpath) = func.kind;
if let Some(segment) = single_segment_path(qpath);
if segment.ident.name == sym::Some;
then {
let func_snippet = snippet(cx, arg_char[0].span, "..");
let msg = "called `map_or(None, ..)` on an `Option` value. This can be done more directly by calling \
`map(..)` instead";
return span_lint_and_sugg(
cx,
OPTION_MAP_OR_NONE,
expr.span,
msg,
"try using `map` instead",
format!("{0}.map({1} {2})", self_snippet, arg_snippet,func_snippet),
Applicability::MachineApplicable,
);
}
}
let func_snippet = snippet(cx, map_arg.span, "..");
let msg = "called `map_or(None, ..)` on an `Option` value. This can be done more directly by calling \
`and_then(..)` instead";
return span_lint_and_sugg(
cx,
OPTION_MAP_OR_NONE,
expr.span,
msg,
"try using `and_then` instead",
format!("{0}.and_then({1})", self_snippet, func_snippet),
Applicability::MachineApplicable,
);
} else if f_arg_is_some {
let msg = "called `map_or(None, Some)` on a `Result` value. This can be done more directly by calling \
`ok()` instead";
let self_snippet = snippet(cx, recv.span, "..");
return span_lint_and_sugg(
cx,
RESULT_MAP_OR_INTO_OPTION,
expr.span,
msg,
"try using `ok` instead",
format!("{0}.ok()", self_snippet),
Applicability::MachineApplicable,
);
}
}

View file

@ -1,16 +1,12 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::eager_or_lazy::is_lazyness_candidate;
use clippy_utils::is_trait_item;
use clippy_utils::eager_or_lazy::switch_to_lazy_eval;
use clippy_utils::source::{snippet, snippet_with_applicability, snippet_with_macro_callsite};
use clippy_utils::ty::implements_trait;
use clippy_utils::ty::{is_type_diagnostic_item, match_type};
use clippy_utils::{contains_return, last_path_segment, paths};
use clippy_utils::ty::{implements_trait, match_type};
use clippy_utils::{contains_return, is_trait_item, last_path_segment, paths};
use if_chain::if_chain;
use rustc_errors::Applicability;
use rustc_hir as hir;
use rustc_hir::{BlockCheckMode, UnsafeSource};
use rustc_lint::LateContext;
use rustc_middle::ty;
use rustc_span::source_map::Span;
use rustc_span::symbol::{kw, sym};
use std::borrow::Cow;
@ -96,25 +92,10 @@ pub(super) fn check<'tcx>(
(&paths::RESULT, true, &["or", "unwrap_or"], "else"),
];
if let hir::ExprKind::MethodCall(path, _, [self_arg, ..], _) = &arg.kind {
if path.ident.name == sym::len {
let ty = cx.typeck_results().expr_ty(self_arg).peel_refs();
match ty.kind() {
ty::Slice(_) | ty::Array(_, _) | ty::Str => return,
_ => (),
}
if is_type_diagnostic_item(cx, ty, sym::Vec) {
return;
}
}
}
if_chain! {
if KNOW_TYPES.iter().any(|k| k.2.contains(&name));
if is_lazyness_candidate(cx, arg);
if switch_to_lazy_eval(cx, arg);
if !contains_return(arg);
let self_ty = cx.typeck_results().expr_ty(self_expr);
@ -166,26 +147,30 @@ pub(super) fn check<'tcx>(
}
}
if args.len() == 2 {
match args[1].kind {
if let [self_arg, arg] = args {
let inner_arg = if let hir::ExprKind::Block(
hir::Block {
stmts: [],
expr: Some(expr),
..
},
_,
) = arg.kind
{
expr
} else {
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, &args[0], &args[1], or_has_args, expr.span) {
if !check_unwrap_or_default(cx, name, fun, self_arg, arg, or_has_args, expr.span) {
let fun_span = if or_has_args { None } else { Some(fun.span) };
check_general_case(cx, name, method_span, &args[0], &args[1], expr.span, fun_span);
check_general_case(cx, name, method_span, self_arg, arg, expr.span, fun_span);
}
},
hir::ExprKind::Index(..) | hir::ExprKind::MethodCall(..) => {
check_general_case(cx, name, method_span, &args[0], &args[1], expr.span, None);
},
hir::ExprKind::Block(block, _)
if block.rules == BlockCheckMode::UnsafeBlock(UnsafeSource::UserProvided) =>
{
if let Some(block_expr) = block.expr {
if let hir::ExprKind::MethodCall(..) = block_expr.kind {
check_general_case(cx, name, method_span, &args[0], &args[1], expr.span, None);
}
}
check_general_case(cx, name, method_span, self_arg, arg, expr.span, None);
},
_ => (),
}

View file

@ -11,7 +11,13 @@ use rustc_span::{symbol::sym, Span, SyntaxContext};
use super::MANUAL_SPLIT_ONCE;
pub(super) fn check(cx: &LateContext<'_>, method_name: &str, expr: &Expr<'_>, self_arg: &Expr<'_>, pat_arg: &Expr<'_>) {
pub(super) fn check_manual_split_once(
cx: &LateContext<'_>,
method_name: &str,
expr: &Expr<'_>,
self_arg: &Expr<'_>,
pat_arg: &Expr<'_>,
) {
if !cx.typeck_results().expr_ty_adjusted(self_arg).peel_refs().is_str() {
return;
}
@ -36,7 +42,7 @@ pub(super) fn check(cx: &LateContext<'_>, method_name: &str, expr: &Expr<'_>, se
format!("{}.{}({})", self_snip, method_name, pat_snip)
},
IterUsageKind::RNextTuple => format!("{}.{}({}).map(|(x, y)| (y, x))", self_snip, method_name, pat_snip),
IterUsageKind::Next => {
IterUsageKind::Next | IterUsageKind::Second => {
let self_deref = {
let adjust = cx.typeck_results().expr_adjustments(self_arg);
if adjust.is_empty() {
@ -51,26 +57,49 @@ pub(super) fn check(cx: &LateContext<'_>, method_name: &str, expr: &Expr<'_>, se
"*".repeat(adjust.len() - 2)
}
};
if usage.unwrap_kind.is_some() {
format!(
"{}.{}({}).map_or({}{}, |x| x.0)",
&self_snip, method_name, pat_snip, self_deref, &self_snip
)
if matches!(usage.kind, IterUsageKind::Next) {
match usage.unwrap_kind {
Some(UnwrapKind::Unwrap) => {
if reverse {
format!("{}.{}({}).unwrap().0", self_snip, method_name, pat_snip)
} else {
format!(
"{}.{}({}).map_or({}{}, |x| x.0)",
self_snip, method_name, pat_snip, self_deref, &self_snip
)
}
},
Some(UnwrapKind::QuestionMark) => {
format!(
"{}.{}({}).map_or({}{}, |x| x.0)",
self_snip, method_name, pat_snip, self_deref, &self_snip
)
},
None => {
format!(
"Some({}.{}({}).map_or({}{}, |x| x.0))",
&self_snip, method_name, pat_snip, self_deref, &self_snip
)
},
}
} else {
format!(
"Some({}.{}({}).map_or({}{}, |x| x.0))",
&self_snip, method_name, pat_snip, self_deref, &self_snip
)
match usage.unwrap_kind {
Some(UnwrapKind::Unwrap) => {
if reverse {
// In this case, no better suggestion is offered.
return;
}
format!("{}.{}({}).unwrap().1", self_snip, method_name, pat_snip)
},
Some(UnwrapKind::QuestionMark) => {
format!("{}.{}({})?.1", self_snip, method_name, pat_snip)
},
None => {
format!("{}.{}({}).map(|x| x.1)", self_snip, method_name, pat_snip)
},
}
}
},
IterUsageKind::Second => {
let access_str = match usage.unwrap_kind {
Some(UnwrapKind::Unwrap) => ".unwrap().1",
Some(UnwrapKind::QuestionMark) => "?.1",
None => ".map(|x| x.1)",
};
format!("{}.{}({}){}", self_snip, method_name, pat_snip, access_str)
},
};
span_lint_and_sugg(cx, MANUAL_SPLIT_ONCE, usage.span, msg, "try this", sugg, app);
@ -209,3 +238,86 @@ fn parse_iter_usage(
span,
})
}
use super::NEEDLESS_SPLITN;
pub(super) fn check_needless_splitn(
cx: &LateContext<'_>,
method_name: &str,
expr: &Expr<'_>,
self_arg: &Expr<'_>,
pat_arg: &Expr<'_>,
count: u128,
) {
if !cx.typeck_results().expr_ty_adjusted(self_arg).peel_refs().is_str() {
return;
}
let ctxt = expr.span.ctxt();
let mut app = Applicability::MachineApplicable;
let (reverse, message) = if method_name == "splitn" {
(false, "unnecessary use of `splitn`")
} else {
(true, "unnecessary use of `rsplitn`")
};
if_chain! {
if count >= 2;
if check_iter(cx, ctxt, cx.tcx.hir().parent_iter(expr.hir_id), count);
then {
span_lint_and_sugg(
cx,
NEEDLESS_SPLITN,
expr.span,
message,
"try this",
format!(
"{}.{}({})",
snippet_with_context(cx, self_arg.span, ctxt, "..", &mut app).0,
if reverse {"rsplit"} else {"split"},
snippet_with_context(cx, pat_arg.span, ctxt, "..", &mut app).0
),
app,
);
}
}
}
fn check_iter(
cx: &LateContext<'tcx>,
ctxt: SyntaxContext,
mut iter: impl Iterator<Item = (HirId, Node<'tcx>)>,
count: u128,
) -> bool {
match iter.next() {
Some((_, Node::Expr(e))) if e.span.ctxt() == ctxt => {
let (name, args) = if let ExprKind::MethodCall(name, _, [_, args @ ..], _) = e.kind {
(name, args)
} else {
return false;
};
if_chain! {
if let Some(did) = cx.typeck_results().type_dependent_def_id(e.hir_id);
if let Some(iter_id) = cx.tcx.get_diagnostic_item(sym::Iterator);
then {
match (&*name.ident.as_str(), args) {
("next", []) if cx.tcx.trait_of_item(did) == Some(iter_id) => {
return true;
},
("next_tuple", []) if count > 2 => {
return true;
},
("nth", [idx_expr]) if cx.tcx.trait_of_item(did) == Some(iter_id) => {
if let Some((Constant::Int(idx), _)) = constant(cx, cx.typeck_results(), idx_expr) {
if count > idx + 1 {
return true;
}
}
},
_ => return false,
}
}
}
},
_ => return false,
};
false
}

View file

@ -30,7 +30,7 @@ pub(super) fn check<'tcx>(
return;
}
if eager_or_lazy::is_eagerness_candidate(cx, body_expr) {
if eager_or_lazy::switch_to_eager_eval(cx, body_expr) {
let msg = if is_option {
"unnecessary closure used to substitute value for `Option::None`"
} else {