Merge remote-tracking branch 'upstream/master' into rustup
This commit is contained in:
commit
3d60241841
215 changed files with 7469 additions and 1950 deletions
|
|
@ -1,7 +1,9 @@
|
|||
use clippy_utils::diagnostics::span_lint_and_sugg;
|
||||
use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then};
|
||||
use clippy_utils::macros::{is_panic, root_macro_call};
|
||||
use clippy_utils::source::{indent_of, reindent_multiline, snippet};
|
||||
use clippy_utils::ty::is_type_diagnostic_item;
|
||||
use clippy_utils::{is_trait_method, path_to_local_id, peel_blocks, SpanlessEq};
|
||||
use clippy_utils::{higher, is_trait_method, path_to_local_id, peel_blocks, SpanlessEq};
|
||||
use hir::{Body, HirId, MatchSource, Pat};
|
||||
use if_chain::if_chain;
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir as hir;
|
||||
|
|
@ -10,7 +12,7 @@ use rustc_hir::{Closure, Expr, ExprKind, PatKind, PathSegment, QPath, UnOp};
|
|||
use rustc_lint::LateContext;
|
||||
use rustc_middle::ty::adjustment::Adjust;
|
||||
use rustc_span::source_map::Span;
|
||||
use rustc_span::symbol::{sym, Symbol};
|
||||
use rustc_span::symbol::{sym, Ident, Symbol};
|
||||
use std::borrow::Cow;
|
||||
|
||||
use super::{MANUAL_FILTER_MAP, MANUAL_FIND_MAP, OPTION_FILTER_MAP};
|
||||
|
|
@ -48,6 +50,214 @@ fn is_option_filter_map(cx: &LateContext<'_>, filter_arg: &hir::Expr<'_>, map_ar
|
|||
is_method(cx, map_arg, sym::unwrap) && is_method(cx, filter_arg, sym!(is_some))
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
enum OffendingFilterExpr<'tcx> {
|
||||
/// `.filter(|opt| opt.is_some())`
|
||||
IsSome {
|
||||
/// The receiver expression
|
||||
receiver: &'tcx Expr<'tcx>,
|
||||
/// If `Some`, then this contains the span of an expression that possibly contains side
|
||||
/// effects: `.filter(|opt| side_effect(opt).is_some())`
|
||||
/// ^^^^^^^^^^^^^^^^
|
||||
///
|
||||
/// We will use this later for warning the user that the suggested fix may change
|
||||
/// the behavior.
|
||||
side_effect_expr_span: Option<Span>,
|
||||
},
|
||||
/// `.filter(|res| res.is_ok())`
|
||||
IsOk {
|
||||
/// The receiver expression
|
||||
receiver: &'tcx Expr<'tcx>,
|
||||
/// See `IsSome`
|
||||
side_effect_expr_span: Option<Span>,
|
||||
},
|
||||
/// `.filter(|enum| matches!(enum, Enum::A(_)))`
|
||||
Matches {
|
||||
/// The DefId of the variant being matched
|
||||
variant_def_id: hir::def_id::DefId,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum CalledMethod {
|
||||
OptionIsSome,
|
||||
ResultIsOk,
|
||||
}
|
||||
|
||||
/// The result of checking a `map` call, returned by `OffendingFilterExpr::check_map_call`
|
||||
#[derive(Debug)]
|
||||
enum CheckResult<'tcx> {
|
||||
Method {
|
||||
map_arg: &'tcx Expr<'tcx>,
|
||||
/// The method that was called inside of `filter`
|
||||
method: CalledMethod,
|
||||
/// See `OffendingFilterExpr::IsSome`
|
||||
side_effect_expr_span: Option<Span>,
|
||||
},
|
||||
PatternMatching {
|
||||
/// The span of the variant being matched
|
||||
/// if let Some(s) = enum
|
||||
/// ^^^^^^^
|
||||
variant_span: Span,
|
||||
/// if let Some(s) = enum
|
||||
/// ^
|
||||
variant_ident: Ident,
|
||||
},
|
||||
}
|
||||
|
||||
impl<'tcx> OffendingFilterExpr<'tcx> {
|
||||
pub fn check_map_call(
|
||||
&mut self,
|
||||
cx: &LateContext<'tcx>,
|
||||
map_body: &'tcx Body<'tcx>,
|
||||
map_param_id: HirId,
|
||||
filter_param_id: HirId,
|
||||
is_filter_param_ref: bool,
|
||||
) -> Option<CheckResult<'tcx>> {
|
||||
match *self {
|
||||
OffendingFilterExpr::IsSome {
|
||||
receiver,
|
||||
side_effect_expr_span,
|
||||
}
|
||||
| OffendingFilterExpr::IsOk {
|
||||
receiver,
|
||||
side_effect_expr_span,
|
||||
} => {
|
||||
// check if closure ends with expect() or unwrap()
|
||||
if let ExprKind::MethodCall(seg, map_arg, ..) = map_body.value.kind
|
||||
&& matches!(seg.ident.name, sym::expect | sym::unwrap | sym::unwrap_or)
|
||||
// .map(|y| f(y).copied().unwrap())
|
||||
// ~~~~
|
||||
&& let map_arg_peeled = match map_arg.kind {
|
||||
ExprKind::MethodCall(method, original_arg, [], _) if acceptable_methods(method) => {
|
||||
original_arg
|
||||
},
|
||||
_ => map_arg,
|
||||
}
|
||||
// .map(|y| y[.acceptable_method()].unwrap())
|
||||
&& let simple_equal = (path_to_local_id(receiver, filter_param_id)
|
||||
&& path_to_local_id(map_arg_peeled, map_param_id))
|
||||
&& let eq_fallback = (|a: &Expr<'_>, b: &Expr<'_>| {
|
||||
// in `filter(|x| ..)`, replace `*x` with `x`
|
||||
let a_path = if_chain! {
|
||||
if !is_filter_param_ref;
|
||||
if let ExprKind::Unary(UnOp::Deref, expr_path) = a.kind;
|
||||
then { expr_path } else { a }
|
||||
};
|
||||
// let the filter closure arg and the map closure arg be equal
|
||||
path_to_local_id(a_path, filter_param_id)
|
||||
&& path_to_local_id(b, map_param_id)
|
||||
&& cx.typeck_results().expr_ty_adjusted(a) == cx.typeck_results().expr_ty_adjusted(b)
|
||||
})
|
||||
&& (simple_equal
|
||||
|| SpanlessEq::new(cx).expr_fallback(eq_fallback).eq_expr(receiver, map_arg_peeled))
|
||||
{
|
||||
Some(CheckResult::Method {
|
||||
map_arg,
|
||||
side_effect_expr_span,
|
||||
method: match self {
|
||||
OffendingFilterExpr::IsSome { .. } => CalledMethod::OptionIsSome,
|
||||
OffendingFilterExpr::IsOk { .. } => CalledMethod::ResultIsOk,
|
||||
OffendingFilterExpr::Matches { .. } => unreachable!("only IsSome and IsOk can get here"),
|
||||
}
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
},
|
||||
OffendingFilterExpr::Matches { variant_def_id } => {
|
||||
let expr_uses_local = |pat: &Pat<'_>, expr: &Expr<'_>| {
|
||||
if let PatKind::TupleStruct(QPath::Resolved(_, path), [subpat], _) = pat.kind
|
||||
&& let PatKind::Binding(_, local_id, ident, _) = subpat.kind
|
||||
&& path_to_local_id(expr.peel_blocks(), local_id)
|
||||
&& let Some(local_variant_def_id) = path.res.opt_def_id()
|
||||
&& local_variant_def_id == variant_def_id
|
||||
{
|
||||
Some((ident, pat.span))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
};
|
||||
|
||||
// look for:
|
||||
// `if let Variant (v) = enum { v } else { unreachable!() }`
|
||||
// ^^^^^^^ ^ ^^^^ ^^^^^^^^^^^^^^^^^^
|
||||
// variant_span variant_ident scrutinee else_ (blocks peeled later)
|
||||
// OR
|
||||
// `match enum { Variant (v) => v, _ => unreachable!() }`
|
||||
// ^^^^ ^^^^^^^ ^ ^^^^^^^^^^^^^^
|
||||
// scrutinee variant_span variant_ident else_
|
||||
let (scrutinee, else_, variant_ident, variant_span) =
|
||||
match higher::IfLetOrMatch::parse(cx, map_body.value) {
|
||||
// For `if let` we want to check that the variant matching arm references the local created by its pattern
|
||||
Some(higher::IfLetOrMatch::IfLet(sc, pat, then, Some(else_)))
|
||||
if let Some((ident, span)) = expr_uses_local(pat, then) =>
|
||||
{
|
||||
(sc, else_, ident, span)
|
||||
},
|
||||
// For `match` we want to check that the "else" arm is the wildcard (`_`) pattern
|
||||
// and that the variant matching arm references the local created by its pattern
|
||||
Some(higher::IfLetOrMatch::Match(sc, [arm, wild_arm], MatchSource::Normal))
|
||||
if let PatKind::Wild = wild_arm.pat.kind
|
||||
&& let Some((ident, span)) = expr_uses_local(arm.pat, arm.body.peel_blocks()) =>
|
||||
{
|
||||
(sc, wild_arm.body, ident, span)
|
||||
},
|
||||
_ => return None,
|
||||
};
|
||||
|
||||
if path_to_local_id(scrutinee, map_param_id)
|
||||
// else branch should be a `panic!` or `unreachable!` macro call
|
||||
&& let Some(mac) = root_macro_call(else_.peel_blocks().span)
|
||||
&& (is_panic(cx, mac.def_id) || cx.tcx.opt_item_name(mac.def_id) == Some(sym::unreachable))
|
||||
{
|
||||
Some(CheckResult::PatternMatching { variant_span, variant_ident })
|
||||
} else {
|
||||
None
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn hir(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>, filter_param_id: HirId) -> Option<Self> {
|
||||
if let ExprKind::MethodCall(path, receiver, [], _) = expr.kind
|
||||
&& let Some(recv_ty) = cx.typeck_results().expr_ty(receiver).peel_refs().ty_adt_def()
|
||||
{
|
||||
// we still want to lint if the expression possibly contains side effects,
|
||||
// *but* it can't be machine-applicable then, because that can change the behavior of the program:
|
||||
// .filter(|x| effect(x).is_some()).map(|x| effect(x).unwrap())
|
||||
// vs.
|
||||
// .filter_map(|x| effect(x))
|
||||
//
|
||||
// the latter only calls `effect` once
|
||||
let side_effect_expr_span = receiver.can_have_side_effects().then_some(receiver.span);
|
||||
|
||||
if cx.tcx.is_diagnostic_item(sym::Option, recv_ty.did())
|
||||
&& path.ident.name == sym!(is_some)
|
||||
{
|
||||
Some(Self::IsSome { receiver, side_effect_expr_span })
|
||||
} else if cx.tcx.is_diagnostic_item(sym::Result, recv_ty.did())
|
||||
&& path.ident.name == sym!(is_ok)
|
||||
{
|
||||
Some(Self::IsOk { receiver, side_effect_expr_span })
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else if let Some(macro_call) = root_macro_call(expr.span)
|
||||
&& cx.tcx.get_diagnostic_name(macro_call.def_id) == Some(sym::matches_macro)
|
||||
// we know for a fact that the wildcard pattern is the second arm
|
||||
&& let ExprKind::Match(scrutinee, [arm, _], _) = expr.kind
|
||||
&& path_to_local_id(scrutinee, filter_param_id)
|
||||
&& let PatKind::TupleStruct(QPath::Resolved(_, path), ..) = arm.pat.kind
|
||||
&& let Some(variant_def_id) = path.res.opt_def_id()
|
||||
{
|
||||
Some(OffendingFilterExpr::Matches { variant_def_id })
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// is `filter(|x| x.is_some()).map(|x| x.unwrap())`
|
||||
fn is_filter_some_map_unwrap(
|
||||
cx: &LateContext<'_>,
|
||||
|
|
@ -102,55 +312,18 @@ pub(super) fn check(
|
|||
} else {
|
||||
(filter_param.pat, false)
|
||||
};
|
||||
// closure ends with is_some() or is_ok()
|
||||
if let PatKind::Binding(_, filter_param_id, _, None) = filter_pat.kind;
|
||||
if let ExprKind::MethodCall(path, filter_arg, [], _) = filter_body.value.kind;
|
||||
if let Some(opt_ty) = cx.typeck_results().expr_ty(filter_arg).peel_refs().ty_adt_def();
|
||||
if let Some(is_result) = if cx.tcx.is_diagnostic_item(sym::Option, opt_ty.did()) {
|
||||
Some(false)
|
||||
} else if cx.tcx.is_diagnostic_item(sym::Result, opt_ty.did()) {
|
||||
Some(true)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
if path.ident.name.as_str() == if is_result { "is_ok" } else { "is_some" };
|
||||
|
||||
// ...map(|x| ...unwrap())
|
||||
if let PatKind::Binding(_, filter_param_id, _, None) = filter_pat.kind;
|
||||
if let Some(mut offending_expr) = OffendingFilterExpr::hir(cx, filter_body.value, filter_param_id);
|
||||
|
||||
if let ExprKind::Closure(&Closure { body: map_body_id, .. }) = map_arg.kind;
|
||||
let map_body = cx.tcx.hir().body(map_body_id);
|
||||
if let [map_param] = map_body.params;
|
||||
if let PatKind::Binding(_, map_param_id, map_param_ident, None) = map_param.pat.kind;
|
||||
// closure ends with expect() or unwrap()
|
||||
if let ExprKind::MethodCall(seg, map_arg, ..) = map_body.value.kind;
|
||||
if matches!(seg.ident.name, sym::expect | sym::unwrap | sym::unwrap_or);
|
||||
|
||||
// .filter(..).map(|y| f(y).copied().unwrap())
|
||||
// ~~~~
|
||||
let map_arg_peeled = match map_arg.kind {
|
||||
ExprKind::MethodCall(method, original_arg, [], _) if acceptable_methods(method) => {
|
||||
original_arg
|
||||
},
|
||||
_ => map_arg,
|
||||
};
|
||||
if let Some(check_result) =
|
||||
offending_expr.check_map_call(cx, map_body, map_param_id, filter_param_id, is_filter_param_ref);
|
||||
|
||||
// .filter(|x| x.is_some()).map(|y| y[.acceptable_method()].unwrap())
|
||||
let simple_equal = path_to_local_id(filter_arg, filter_param_id)
|
||||
&& path_to_local_id(map_arg_peeled, map_param_id);
|
||||
|
||||
let eq_fallback = |a: &Expr<'_>, b: &Expr<'_>| {
|
||||
// in `filter(|x| ..)`, replace `*x` with `x`
|
||||
let a_path = if_chain! {
|
||||
if !is_filter_param_ref;
|
||||
if let ExprKind::Unary(UnOp::Deref, expr_path) = a.kind;
|
||||
then { expr_path } else { a }
|
||||
};
|
||||
// let the filter closure arg and the map closure arg be equal
|
||||
path_to_local_id(a_path, filter_param_id)
|
||||
&& path_to_local_id(b, map_param_id)
|
||||
&& cx.typeck_results().expr_ty_adjusted(a) == cx.typeck_results().expr_ty_adjusted(b)
|
||||
};
|
||||
|
||||
if simple_equal || SpanlessEq::new(cx).expr_fallback(eq_fallback).eq_expr(filter_arg, map_arg_peeled);
|
||||
then {
|
||||
let span = filter_span.with_hi(expr.span.hi());
|
||||
let (filter_name, lint) = if is_find {
|
||||
|
|
@ -159,22 +332,53 @@ pub(super) fn check(
|
|||
("filter", MANUAL_FILTER_MAP)
|
||||
};
|
||||
let msg = format!("`{filter_name}(..).map(..)` can be simplified as `{filter_name}_map(..)`");
|
||||
let (to_opt, deref) = if is_result {
|
||||
(".ok()", String::new())
|
||||
} else {
|
||||
let derefs = cx.typeck_results()
|
||||
.expr_adjustments(map_arg)
|
||||
.iter()
|
||||
.filter(|adj| matches!(adj.kind, Adjust::Deref(_)))
|
||||
.count();
|
||||
|
||||
("", "*".repeat(derefs))
|
||||
let (sugg, note_and_span, applicability) = match check_result {
|
||||
CheckResult::Method { map_arg, method, side_effect_expr_span } => {
|
||||
let (to_opt, deref) = match method {
|
||||
CalledMethod::ResultIsOk => (".ok()", String::new()),
|
||||
CalledMethod::OptionIsSome => {
|
||||
let derefs = cx.typeck_results()
|
||||
.expr_adjustments(map_arg)
|
||||
.iter()
|
||||
.filter(|adj| matches!(adj.kind, Adjust::Deref(_)))
|
||||
.count();
|
||||
|
||||
("", "*".repeat(derefs))
|
||||
}
|
||||
};
|
||||
|
||||
let sugg = format!(
|
||||
"{filter_name}_map(|{map_param_ident}| {deref}{}{to_opt})",
|
||||
snippet(cx, map_arg.span, ".."),
|
||||
);
|
||||
let (note_and_span, applicability) = if let Some(span) = side_effect_expr_span {
|
||||
let note = "the suggestion might change the behavior of the program when merging `filter` and `map`, \
|
||||
because this expression potentially contains side effects and will only execute once";
|
||||
|
||||
(Some((note, span)), Applicability::MaybeIncorrect)
|
||||
} else {
|
||||
(None, Applicability::MachineApplicable)
|
||||
};
|
||||
|
||||
(sugg, note_and_span, applicability)
|
||||
}
|
||||
CheckResult::PatternMatching { variant_span, variant_ident } => {
|
||||
let pat = snippet(cx, variant_span, "<pattern>");
|
||||
|
||||
(format!("{filter_name}_map(|{map_param_ident}| match {map_param_ident} {{ \
|
||||
{pat} => Some({variant_ident}), \
|
||||
_ => None \
|
||||
}})"), None, Applicability::MachineApplicable)
|
||||
}
|
||||
};
|
||||
let sugg = format!(
|
||||
"{filter_name}_map(|{map_param_ident}| {deref}{}{to_opt})",
|
||||
snippet(cx, map_arg.span, ".."),
|
||||
);
|
||||
span_lint_and_sugg(cx, lint, span, &msg, "try", sugg, Applicability::MachineApplicable);
|
||||
span_lint_and_then(cx, lint, span, &msg, |diag| {
|
||||
diag.span_suggestion(span, "try", sugg, applicability);
|
||||
|
||||
if let Some((note, span)) = note_and_span {
|
||||
diag.span_note(span, note);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
45
clippy_lints/src/methods/filter_map_bool_then.rs
Normal file
45
clippy_lints/src/methods/filter_map_bool_then.rs
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
use clippy_utils::diagnostics::span_lint_and_sugg;
|
||||
use clippy_utils::paths::BOOL_THEN;
|
||||
use clippy_utils::source::snippet_opt;
|
||||
use clippy_utils::ty::is_copy;
|
||||
use clippy_utils::{is_from_proc_macro, is_trait_method, match_def_path, peel_blocks};
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::{Expr, ExprKind};
|
||||
use rustc_lint::{LateContext, LintContext};
|
||||
use rustc_middle::lint::in_external_macro;
|
||||
use rustc_span::{sym, Span};
|
||||
|
||||
use super::FILTER_MAP_BOOL_THEN;
|
||||
|
||||
pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>, arg: &Expr<'_>, call_span: Span) {
|
||||
if !in_external_macro(cx.sess(), expr.span)
|
||||
&& is_trait_method(cx, expr, sym::Iterator)
|
||||
&& let ExprKind::Closure(closure) = arg.kind
|
||||
&& let body = cx.tcx.hir().body(closure.body)
|
||||
&& let value = peel_blocks(body.value)
|
||||
// Indexing should be fine as `filter_map` always has 1 input, we unfortunately need both
|
||||
// `inputs` and `params` here as we need both the type and the span
|
||||
&& let param_ty = closure.fn_decl.inputs[0]
|
||||
&& let param = body.params[0]
|
||||
&& is_copy(cx, cx.typeck_results().node_type(param_ty.hir_id).peel_refs())
|
||||
&& let ExprKind::MethodCall(_, recv, [then_arg], _) = value.kind
|
||||
&& let ExprKind::Closure(then_closure) = then_arg.kind
|
||||
&& let then_body = peel_blocks(cx.tcx.hir().body(then_closure.body).value)
|
||||
&& let Some(def_id) = cx.typeck_results().type_dependent_def_id(value.hir_id)
|
||||
&& match_def_path(cx, def_id, &BOOL_THEN)
|
||||
&& !is_from_proc_macro(cx, expr)
|
||||
&& let Some(param_snippet) = snippet_opt(cx, param.span)
|
||||
&& let Some(filter) = snippet_opt(cx, recv.span)
|
||||
&& let Some(map) = snippet_opt(cx, then_body.span)
|
||||
{
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
FILTER_MAP_BOOL_THEN,
|
||||
call_span,
|
||||
"usage of `bool::then` in `filter_map`",
|
||||
"use `filter` then `map` instead",
|
||||
format!("filter(|&{param_snippet}| {filter}).map(|{param_snippet}| {map})"),
|
||||
Applicability::MachineApplicable,
|
||||
);
|
||||
}
|
||||
}
|
||||
33
clippy_lints/src/methods/format_collect.rs
Normal file
33
clippy_lints/src/methods/format_collect.rs
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
use super::FORMAT_COLLECT;
|
||||
use clippy_utils::diagnostics::span_lint_and_then;
|
||||
use clippy_utils::macros::{is_format_macro, root_macro_call_first_node};
|
||||
use clippy_utils::ty::is_type_lang_item;
|
||||
use rustc_hir::{Expr, ExprKind, LangItem};
|
||||
use rustc_lint::LateContext;
|
||||
use rustc_span::Span;
|
||||
|
||||
/// Same as `peel_blocks` but only actually considers blocks that are not from an expansion.
|
||||
/// This is needed because always calling `peel_blocks` would otherwise remove parts of the
|
||||
/// `format!` macro, which would cause `root_macro_call_first_node` to return `None`.
|
||||
fn peel_non_expn_blocks<'tcx>(expr: &'tcx Expr<'tcx>) -> Option<&'tcx Expr<'tcx>> {
|
||||
match expr.kind {
|
||||
ExprKind::Block(block, _) if !expr.span.from_expansion() => peel_non_expn_blocks(block.expr?),
|
||||
_ => Some(expr),
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, map_arg: &Expr<'_>, map_span: Span) {
|
||||
if is_type_lang_item(cx, cx.typeck_results().expr_ty(expr), LangItem::String)
|
||||
&& let ExprKind::Closure(closure) = map_arg.kind
|
||||
&& let body = cx.tcx.hir().body(closure.body)
|
||||
&& let Some(value) = peel_non_expn_blocks(body.value)
|
||||
&& let Some(mac) = root_macro_call_first_node(cx, value)
|
||||
&& is_format_macro(cx, mac.def_id)
|
||||
{
|
||||
span_lint_and_then(cx, FORMAT_COLLECT, expr.span, "use of `format!` to build up a string from an iterator", |diag| {
|
||||
diag.span_help(map_span, "call `fold` instead")
|
||||
.span_help(value.span.source_callsite(), "... and use the `write!` macro here")
|
||||
.note("this can be written more efficiently by appending to a `String` directly");
|
||||
});
|
||||
}
|
||||
}
|
||||
34
clippy_lints/src/methods/iter_skip_zero.rs
Normal file
34
clippy_lints/src/methods/iter_skip_zero.rs
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
use clippy_utils::consts::{constant, Constant};
|
||||
use clippy_utils::diagnostics::span_lint_and_then;
|
||||
use clippy_utils::{is_from_proc_macro, is_trait_method};
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::Expr;
|
||||
use rustc_lint::LateContext;
|
||||
use rustc_span::sym;
|
||||
|
||||
use super::ITER_SKIP_ZERO;
|
||||
|
||||
pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>, arg_expr: &Expr<'_>) {
|
||||
if !expr.span.from_expansion()
|
||||
&& is_trait_method(cx, expr, sym::Iterator)
|
||||
&& let Some(arg) = constant(cx, cx.typeck_results(), arg_expr).and_then(|constant| {
|
||||
if let Constant::Int(arg) = constant {
|
||||
Some(arg)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
&& arg == 0
|
||||
&& !is_from_proc_macro(cx, expr)
|
||||
{
|
||||
span_lint_and_then(cx, ITER_SKIP_ZERO, arg_expr.span, "usage of `.skip(0)`", |diag| {
|
||||
diag.span_suggestion(
|
||||
arg_expr.span,
|
||||
"if you meant to skip the first element, use",
|
||||
"1",
|
||||
Applicability::MaybeIncorrect,
|
||||
)
|
||||
.note("this call to `skip` does nothing and is useless; remove it");
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -21,11 +21,13 @@ mod expect_used;
|
|||
mod extend_with_drain;
|
||||
mod filetype_is_file;
|
||||
mod filter_map;
|
||||
mod filter_map_bool_then;
|
||||
mod filter_map_identity;
|
||||
mod filter_map_next;
|
||||
mod filter_next;
|
||||
mod flat_map_identity;
|
||||
mod flat_map_option;
|
||||
mod format_collect;
|
||||
mod from_iter_instead_of_collect;
|
||||
mod get_first;
|
||||
mod get_last_with_len;
|
||||
|
|
@ -44,6 +46,7 @@ mod iter_nth_zero;
|
|||
mod iter_on_single_or_empty_collections;
|
||||
mod iter_overeager_cloned;
|
||||
mod iter_skip_next;
|
||||
mod iter_skip_zero;
|
||||
mod iter_with_drain;
|
||||
mod iterator_step_by_zero;
|
||||
mod manual_next_back;
|
||||
|
|
@ -73,6 +76,7 @@ mod or_then_unwrap;
|
|||
mod path_buf_push_overwrite;
|
||||
mod range_zip_with_len;
|
||||
mod read_line_without_trim;
|
||||
mod readonly_write_lock;
|
||||
mod repeat_once;
|
||||
mod search_is_some;
|
||||
mod seek_from_current;
|
||||
|
|
@ -85,6 +89,7 @@ mod skip_while_next;
|
|||
mod stable_sort_primitive;
|
||||
mod str_splitn;
|
||||
mod string_extend_chars;
|
||||
mod string_lit_chars_any;
|
||||
mod suspicious_command_arg_space;
|
||||
mod suspicious_map;
|
||||
mod suspicious_splitn;
|
||||
|
|
@ -100,7 +105,6 @@ mod unnecessary_lazy_eval;
|
|||
mod unnecessary_literal_unwrap;
|
||||
mod unnecessary_sort_by;
|
||||
mod unnecessary_to_owned;
|
||||
mod unwrap_or_else_default;
|
||||
mod unwrap_used;
|
||||
mod useless_asref;
|
||||
mod utils;
|
||||
|
|
@ -114,7 +118,7 @@ use clippy_utils::consts::{constant, Constant};
|
|||
use clippy_utils::diagnostics::{span_lint, span_lint_and_help};
|
||||
use clippy_utils::msrvs::{self, Msrv};
|
||||
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, return_ty};
|
||||
use clippy_utils::{contains_return, is_bool, is_trait_method, iter_input_pats, peel_blocks, return_ty};
|
||||
use if_chain::if_chain;
|
||||
use rustc_hir as hir;
|
||||
use rustc_hir::{Expr, ExprKind, Node, Stmt, StmtKind, TraitItem, TraitItemKind};
|
||||
|
|
@ -473,29 +477,40 @@ declare_clippy_lint! {
|
|||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// Checks for usage of `_.unwrap_or_else(Default::default)` on `Option` and
|
||||
/// `Result` values.
|
||||
/// Checks for usages of the following functions with an argument that constructs a default value
|
||||
/// (e.g., `Default::default` or `String::new`):
|
||||
/// - `unwrap_or`
|
||||
/// - `unwrap_or_else`
|
||||
/// - `or_insert`
|
||||
/// - `or_insert_with`
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
/// Readability, these can be written as `_.unwrap_or_default`, which is
|
||||
/// simpler and more concise.
|
||||
/// Readability. Using `unwrap_or_default` in place of `unwrap_or`/`unwrap_or_else`, or `or_default`
|
||||
/// in place of `or_insert`/`or_insert_with`, is simpler and more concise.
|
||||
///
|
||||
/// ### Known problems
|
||||
/// In some cases, the argument of `unwrap_or`, etc. is needed for type inference. The lint uses a
|
||||
/// heuristic to try to identify such cases. However, the heuristic can produce false negatives.
|
||||
///
|
||||
/// ### Examples
|
||||
/// ```rust
|
||||
/// # let x = Some(1);
|
||||
/// x.unwrap_or_else(Default::default);
|
||||
/// x.unwrap_or_else(u32::default);
|
||||
/// # let mut map = std::collections::HashMap::<u64, String>::new();
|
||||
/// x.unwrap_or(Default::default());
|
||||
/// map.entry(42).or_insert_with(String::new);
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```rust
|
||||
/// # let x = Some(1);
|
||||
/// # let mut map = std::collections::HashMap::<u64, String>::new();
|
||||
/// x.unwrap_or_default();
|
||||
/// map.entry(42).or_default();
|
||||
/// ```
|
||||
#[clippy::version = "1.56.0"]
|
||||
pub UNWRAP_OR_ELSE_DEFAULT,
|
||||
pub UNWRAP_OR_DEFAULT,
|
||||
style,
|
||||
"using `.unwrap_or_else(Default::default)`, which is more succinctly expressed as `.unwrap_or_default()`"
|
||||
"using `.unwrap_or`, etc. with an argument that constructs a default value"
|
||||
}
|
||||
|
||||
declare_clippy_lint! {
|
||||
|
|
@ -3378,6 +3393,152 @@ declare_clippy_lint! {
|
|||
"calling `Stdin::read_line`, then trying to parse it without first trimming"
|
||||
}
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// Checks for `<string_lit>.chars().any(|i| i == c)`.
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
/// It's significantly slower than using a pattern instead, like
|
||||
/// `matches!(c, '\\' | '.' | '+')`.
|
||||
///
|
||||
/// Despite this being faster, this is not `perf` as this is pretty common, and is a rather nice
|
||||
/// way to check if a `char` is any in a set. In any case, this `restriction` lint is available
|
||||
/// for situations where that additional performance is absolutely necessary.
|
||||
///
|
||||
/// ### Example
|
||||
/// ```rust
|
||||
/// # let c = 'c';
|
||||
/// "\\.+*?()|[]{}^$#&-~".chars().any(|x| x == c);
|
||||
/// ```
|
||||
/// Use instead:
|
||||
/// ```rust
|
||||
/// # let c = 'c';
|
||||
/// matches!(c, '\\' | '.' | '+' | '*' | '(' | ')' | '|' | '[' | ']' | '{' | '}' | '^' | '$' | '#' | '&' | '-' | '~');
|
||||
/// ```
|
||||
#[clippy::version = "1.72.0"]
|
||||
pub STRING_LIT_CHARS_ANY,
|
||||
restriction,
|
||||
"checks for `<string_lit>.chars().any(|i| i == c)`"
|
||||
}
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// Checks for usage of `.map(|_| format!(..)).collect::<String>()`.
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
/// This allocates a new string for every element in the iterator.
|
||||
/// This can be done more efficiently by creating the `String` once and appending to it in `Iterator::fold`,
|
||||
/// using either the `write!` macro which supports exactly the same syntax as the `format!` macro,
|
||||
/// or concatenating with `+` in case the iterator yields `&str`/`String`.
|
||||
///
|
||||
/// Note also that `write!`-ing into a `String` can never fail, despite the return type of `write!` being `std::fmt::Result`,
|
||||
/// so it can be safely ignored or unwrapped.
|
||||
///
|
||||
/// ### Example
|
||||
/// ```rust
|
||||
/// fn hex_encode(bytes: &[u8]) -> String {
|
||||
/// bytes.iter().map(|b| format!("{b:02X}")).collect()
|
||||
/// }
|
||||
/// ```
|
||||
/// Use instead:
|
||||
/// ```rust
|
||||
/// use std::fmt::Write;
|
||||
/// fn hex_encode(bytes: &[u8]) -> String {
|
||||
/// bytes.iter().fold(String::new(), |mut output, b| {
|
||||
/// let _ = write!(output, "{b:02X}");
|
||||
/// output
|
||||
/// })
|
||||
/// }
|
||||
/// ```
|
||||
#[clippy::version = "1.72.0"]
|
||||
pub FORMAT_COLLECT,
|
||||
perf,
|
||||
"`format!`ing every element in a collection, then collecting the strings into a new `String`"
|
||||
}
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// Checks for usage of `.skip(0)` on iterators.
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
/// This was likely intended to be `.skip(1)` to skip the first element, as `.skip(0)` does
|
||||
/// nothing. If not, the call should be removed.
|
||||
///
|
||||
/// ### Example
|
||||
/// ```rust
|
||||
/// let v = vec![1, 2, 3];
|
||||
/// let x = v.iter().skip(0).collect::<Vec<_>>();
|
||||
/// let y = v.iter().collect::<Vec<_>>();
|
||||
/// assert_eq!(x, y);
|
||||
/// ```
|
||||
#[clippy::version = "1.72.0"]
|
||||
pub ITER_SKIP_ZERO,
|
||||
correctness,
|
||||
"disallows `.skip(0)`"
|
||||
}
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// Checks for usage of `bool::then` in `Iterator::filter_map`.
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
/// This can be written with `filter` then `map` instead, which would reduce nesting and
|
||||
/// separates the filtering from the transformation phase. This comes with no cost to
|
||||
/// performance and is just cleaner.
|
||||
///
|
||||
/// ### Limitations
|
||||
/// Does not lint `bool::then_some`, as it eagerly evaluates its arguments rather than lazily.
|
||||
/// This can create differing behavior, so better safe than sorry.
|
||||
///
|
||||
/// ### Example
|
||||
/// ```rust
|
||||
/// # fn really_expensive_fn(i: i32) -> i32 { i }
|
||||
/// # let v = vec![];
|
||||
/// _ = v.into_iter().filter_map(|i| (i % 2 == 0).then(|| really_expensive_fn(i)));
|
||||
/// ```
|
||||
/// Use instead:
|
||||
/// ```rust
|
||||
/// # fn really_expensive_fn(i: i32) -> i32 { i }
|
||||
/// # let v = vec![];
|
||||
/// _ = v.into_iter().filter(|i| i % 2 == 0).map(|i| really_expensive_fn(i));
|
||||
/// ```
|
||||
#[clippy::version = "1.72.0"]
|
||||
pub FILTER_MAP_BOOL_THEN,
|
||||
style,
|
||||
"checks for usage of `bool::then` in `Iterator::filter_map`"
|
||||
}
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// Looks for calls to `RwLock::write` where the lock is only used for reading.
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
/// The write portion of `RwLock` is exclusive, meaning that no other thread
|
||||
/// can access the lock while this writer is active.
|
||||
///
|
||||
/// ### Example
|
||||
/// ```rust
|
||||
/// use std::sync::RwLock;
|
||||
/// fn assert_is_zero(lock: &RwLock<i32>) {
|
||||
/// let num = lock.write().unwrap();
|
||||
/// assert_eq!(*num, 0);
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```rust
|
||||
/// use std::sync::RwLock;
|
||||
/// fn assert_is_zero(lock: &RwLock<i32>) {
|
||||
/// let num = lock.read().unwrap();
|
||||
/// assert_eq!(*num, 0);
|
||||
/// }
|
||||
/// ```
|
||||
#[clippy::version = "1.73.0"]
|
||||
pub READONLY_WRITE_LOCK,
|
||||
nursery,
|
||||
"acquiring a write lock when a read lock would work"
|
||||
}
|
||||
|
||||
pub struct Methods {
|
||||
avoid_breaking_exported_api: bool,
|
||||
msrv: Msrv,
|
||||
|
|
@ -3408,7 +3569,7 @@ impl_lint_pass!(Methods => [
|
|||
SHOULD_IMPLEMENT_TRAIT,
|
||||
WRONG_SELF_CONVENTION,
|
||||
OK_EXPECT,
|
||||
UNWRAP_OR_ELSE_DEFAULT,
|
||||
UNWRAP_OR_DEFAULT,
|
||||
MAP_UNWRAP_OR,
|
||||
RESULT_MAP_OR_INTO_OPTION,
|
||||
OPTION_MAP_OR_NONE,
|
||||
|
|
@ -3512,6 +3673,11 @@ impl_lint_pass!(Methods => [
|
|||
UNNECESSARY_LITERAL_UNWRAP,
|
||||
DRAIN_COLLECT,
|
||||
MANUAL_TRY_FOLD,
|
||||
FORMAT_COLLECT,
|
||||
STRING_LIT_CHARS_ANY,
|
||||
ITER_SKIP_ZERO,
|
||||
FILTER_MAP_BOOL_THEN,
|
||||
READONLY_WRITE_LOCK
|
||||
]);
|
||||
|
||||
/// Extracts a method call name, args, and `Span` of the method name.
|
||||
|
|
@ -3666,8 +3832,7 @@ impl<'tcx> LateLintPass<'tcx> for Methods {
|
|||
then {
|
||||
let first_arg_span = first_arg_ty.span;
|
||||
let first_arg_ty = hir_ty_to_ty(cx.tcx, first_arg_ty);
|
||||
let self_ty = TraitRef::identity(cx.tcx, item.owner_id.to_def_id())
|
||||
.self_ty();
|
||||
let self_ty = TraitRef::identity(cx.tcx, item.owner_id.to_def_id()).self_ty();
|
||||
wrong_self_convention::check(
|
||||
cx,
|
||||
item.ident.name.as_str(),
|
||||
|
|
@ -3684,8 +3849,7 @@ impl<'tcx> LateLintPass<'tcx> for Methods {
|
|||
if item.ident.name == sym::new;
|
||||
if let TraitItemKind::Fn(_, _) = item.kind;
|
||||
let ret_ty = return_ty(cx, item.owner_id);
|
||||
let self_ty = TraitRef::identity(cx.tcx, item.owner_id.to_def_id())
|
||||
.self_ty();
|
||||
let self_ty = TraitRef::identity(cx.tcx, item.owner_id.to_def_id()).self_ty();
|
||||
if !ret_ty.contains(self_ty);
|
||||
|
||||
then {
|
||||
|
|
@ -3733,8 +3897,9 @@ impl Methods {
|
|||
Some((name @ ("cloned" | "copied"), recv2, [], _, _)) => {
|
||||
iter_cloned_collect::check(cx, name, expr, recv2);
|
||||
},
|
||||
Some(("map", m_recv, [m_arg], _, _)) => {
|
||||
Some(("map", m_recv, [m_arg], m_ident_span, _)) => {
|
||||
map_collect_result_unit::check(cx, expr, m_recv, m_arg);
|
||||
format_collect::check(cx, expr, m_arg, m_ident_span);
|
||||
},
|
||||
Some(("take", take_self_arg, [take_arg], _, _)) => {
|
||||
if self.msrv.meets(msrvs::STR_REPEAT) {
|
||||
|
|
@ -3790,6 +3955,7 @@ impl Methods {
|
|||
},
|
||||
("filter_map", [arg]) => {
|
||||
unnecessary_filter_map::check(cx, expr, arg, name);
|
||||
filter_map_bool_then::check(cx, expr, arg, call_span);
|
||||
filter_map_identity::check(cx, expr, arg, span);
|
||||
},
|
||||
("find_map", [arg]) => {
|
||||
|
|
@ -3833,7 +3999,16 @@ impl Methods {
|
|||
unnecessary_join::check(cx, expr, recv, join_arg, span);
|
||||
}
|
||||
},
|
||||
("last", []) | ("skip", [_]) => {
|
||||
("skip", [arg]) => {
|
||||
iter_skip_zero::check(cx, expr, arg);
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
("last", []) => {
|
||||
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);
|
||||
|
|
@ -3885,6 +4060,13 @@ impl Methods {
|
|||
}
|
||||
}
|
||||
},
|
||||
("any", [arg]) if let ExprKind::Closure(arg) = arg.kind
|
||||
&& let body = cx.tcx.hir().body(arg.body)
|
||||
&& let [param] = body.params
|
||||
&& let Some(("chars", recv, _, _, _)) = method_call(recv) =>
|
||||
{
|
||||
string_lit_chars_any::check(cx, expr, recv, param, peel_blocks(body.value), &self.msrv);
|
||||
}
|
||||
("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),
|
||||
|
|
@ -4027,7 +4209,6 @@ impl Methods {
|
|||
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);
|
||||
unnecessary_lazy_eval::check(cx, expr, recv, u_arg, "unwrap_or");
|
||||
},
|
||||
}
|
||||
|
|
@ -4040,6 +4221,9 @@ impl Methods {
|
|||
range_zip_with_len::check(cx, expr, iter_recv, arg);
|
||||
}
|
||||
},
|
||||
("write", []) => {
|
||||
readonly_write_lock::check(cx, expr, recv);
|
||||
}
|
||||
_ => {},
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,16 +1,17 @@
|
|||
use clippy_utils::diagnostics::span_lint_and_sugg;
|
||||
use clippy_utils::eager_or_lazy::switch_to_lazy_eval;
|
||||
use clippy_utils::source::snippet_with_context;
|
||||
use clippy_utils::ty::{implements_trait, is_type_diagnostic_item};
|
||||
use clippy_utils::{contains_return, is_trait_item, last_path_segment};
|
||||
use clippy_utils::ty::{expr_type_is_certain, implements_trait, is_type_diagnostic_item};
|
||||
use clippy_utils::{contains_return, is_default_equivalent, is_default_equivalent_call, last_path_segment};
|
||||
use if_chain::if_chain;
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir as hir;
|
||||
use rustc_lint::LateContext;
|
||||
use rustc_middle::ty;
|
||||
use rustc_span::source_map::Span;
|
||||
use rustc_span::symbol::{kw, sym, Symbol};
|
||||
use rustc_span::symbol::{self, sym, Symbol};
|
||||
use {rustc_ast as ast, rustc_hir as hir};
|
||||
|
||||
use super::OR_FUN_CALL;
|
||||
use super::{OR_FUN_CALL, UNWRAP_OR_DEFAULT};
|
||||
|
||||
/// Checks for the `OR_FUN_CALL` lint.
|
||||
#[allow(clippy::too_many_lines)]
|
||||
|
|
@ -24,53 +25,72 @@ pub(super) fn check<'tcx>(
|
|||
) {
|
||||
/// Checks for `unwrap_or(T::new())`, `unwrap_or(T::default())`,
|
||||
/// `or_insert(T::new())` or `or_insert(T::default())`.
|
||||
/// Similarly checks for `unwrap_or_else(T::new)`, `unwrap_or_else(T::default)`,
|
||||
/// `or_insert_with(T::new)` or `or_insert_with(T::default)`.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn check_unwrap_or_default(
|
||||
cx: &LateContext<'_>,
|
||||
name: &str,
|
||||
receiver: &hir::Expr<'_>,
|
||||
fun: &hir::Expr<'_>,
|
||||
arg: &hir::Expr<'_>,
|
||||
or_has_args: bool,
|
||||
call_expr: Option<&hir::Expr<'_>>,
|
||||
span: Span,
|
||||
method_span: Span,
|
||||
) -> bool {
|
||||
let is_default_default = || is_trait_item(cx, fun, sym::Default);
|
||||
if !expr_type_is_certain(cx, receiver) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let implements_default = |arg, default_trait_id| {
|
||||
let arg_ty = cx.typeck_results().expr_ty(arg);
|
||||
implements_trait(cx, arg_ty, default_trait_id, &[])
|
||||
};
|
||||
|
||||
if_chain! {
|
||||
if !or_has_args;
|
||||
if let Some(sugg) = match name {
|
||||
"unwrap_or" => Some("unwrap_or_default"),
|
||||
"or_insert" => Some("or_default"),
|
||||
_ => None,
|
||||
};
|
||||
if let hir::ExprKind::Path(ref qpath) = fun.kind;
|
||||
if let Some(default_trait_id) = cx.tcx.get_diagnostic_item(sym::Default);
|
||||
let path = last_path_segment(qpath).ident.name;
|
||||
// needs to target Default::default in particular or be *::new and have a Default impl
|
||||
// available
|
||||
if (matches!(path, kw::Default) && is_default_default())
|
||||
|| (matches!(path, sym::new) && implements_default(arg, default_trait_id));
|
||||
|
||||
then {
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
OR_FUN_CALL,
|
||||
method_span.with_hi(span.hi()),
|
||||
&format!("use of `{name}` followed by a call to `{path}`"),
|
||||
"try",
|
||||
format!("{sugg}()"),
|
||||
Applicability::MachineApplicable,
|
||||
);
|
||||
|
||||
true
|
||||
let is_new = |fun: &hir::Expr<'_>| {
|
||||
if let hir::ExprKind::Path(ref qpath) = fun.kind {
|
||||
let path = last_path_segment(qpath).ident.name;
|
||||
matches!(path, sym::new)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
};
|
||||
|
||||
let output_type_implements_default = |fun| {
|
||||
let fun_ty = cx.typeck_results().expr_ty(fun);
|
||||
if let ty::FnDef(def_id, args) = fun_ty.kind() {
|
||||
let output_ty = cx.tcx.fn_sig(def_id).instantiate(cx.tcx, args).skip_binder().output();
|
||||
cx.tcx
|
||||
.get_diagnostic_item(sym::Default)
|
||||
.map_or(false, |default_trait_id| {
|
||||
implements_trait(cx, output_ty, default_trait_id, args)
|
||||
})
|
||||
} else {
|
||||
false
|
||||
}
|
||||
};
|
||||
|
||||
let sugg = match (name, call_expr.is_some()) {
|
||||
("unwrap_or", true) | ("unwrap_or_else", false) => "unwrap_or_default",
|
||||
("or_insert", true) | ("or_insert_with", false) => "or_default",
|
||||
_ => return false,
|
||||
};
|
||||
|
||||
// needs to target Default::default in particular or be *::new and have a Default impl
|
||||
// available
|
||||
if (is_new(fun) && output_type_implements_default(fun))
|
||||
|| match call_expr {
|
||||
Some(call_expr) => is_default_equivalent(cx, call_expr),
|
||||
None => is_default_equivalent_call(cx, fun) || closure_body_returns_empty_to_string(cx, fun),
|
||||
}
|
||||
{
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
UNWRAP_OR_DEFAULT,
|
||||
method_span.with_hi(span.hi()),
|
||||
&format!("use of `{name}` to construct default value"),
|
||||
"try",
|
||||
format!("{sugg}()"),
|
||||
Applicability::MachineApplicable,
|
||||
);
|
||||
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -168,11 +188,16 @@ pub(super) fn check<'tcx>(
|
|||
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) {
|
||||
if or_has_args
|
||||
|| !check_unwrap_or_default(cx, name, receiver, fun, Some(inner_arg), 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, None, expr.span, fun_span);
|
||||
}
|
||||
},
|
||||
hir::ExprKind::Path(..) | hir::ExprKind::Closure(..) => {
|
||||
check_unwrap_or_default(cx, name, receiver, inner_arg, None, expr.span, method_span);
|
||||
},
|
||||
hir::ExprKind::Index(..) | hir::ExprKind::MethodCall(..) => {
|
||||
check_general_case(cx, name, method_span, receiver, arg, None, expr.span, None);
|
||||
},
|
||||
|
|
@ -189,3 +214,22 @@ pub(super) fn check<'tcx>(
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn closure_body_returns_empty_to_string(cx: &LateContext<'_>, e: &hir::Expr<'_>) -> bool {
|
||||
if let hir::ExprKind::Closure(&hir::Closure { body, .. }) = e.kind {
|
||||
let body = cx.tcx.hir().body(body);
|
||||
|
||||
if body.params.is_empty()
|
||||
&& let hir::Expr{ kind, .. } = &body.value
|
||||
&& let hir::ExprKind::MethodCall(hir::PathSegment {ident, ..}, self_arg, _, _) = kind
|
||||
&& ident.name == sym::to_string
|
||||
&& let hir::Expr{ kind, .. } = self_arg
|
||||
&& let hir::ExprKind::Lit(lit) = kind
|
||||
&& let ast::LitKind::Str(symbol::kw::Empty, _) = lit.node
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
|
|
|||
|
|
@ -35,8 +35,8 @@ pub fn check(cx: &LateContext<'_>, call: &Expr<'_>, recv: &Expr<'_>, arg: &Expr<
|
|||
&& segment.ident.name == sym!(parse)
|
||||
&& let parse_result_ty = cx.typeck_results().expr_ty(parent)
|
||||
&& is_type_diagnostic_item(cx, parse_result_ty, sym::Result)
|
||||
&& let ty::Adt(_, substs) = parse_result_ty.kind()
|
||||
&& let Some(ok_ty) = substs[0].as_type()
|
||||
&& let ty::Adt(_, args) = parse_result_ty.kind()
|
||||
&& let Some(ok_ty) = args[0].as_type()
|
||||
&& parse_fails_on_trailing_newline(ok_ty)
|
||||
{
|
||||
let local_snippet = snippet(cx, expr.span, "<expr>");
|
||||
|
|
|
|||
52
clippy_lints/src/methods/readonly_write_lock.rs
Normal file
52
clippy_lints/src/methods/readonly_write_lock.rs
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
use super::READONLY_WRITE_LOCK;
|
||||
use clippy_utils::diagnostics::span_lint_and_sugg;
|
||||
use clippy_utils::mir::{enclosing_mir, visit_local_usage};
|
||||
use clippy_utils::source::snippet;
|
||||
use clippy_utils::ty::is_type_diagnostic_item;
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::{Expr, ExprKind, Node};
|
||||
use rustc_lint::LateContext;
|
||||
use rustc_middle::mir::{Location, START_BLOCK};
|
||||
use rustc_span::sym;
|
||||
|
||||
fn is_unwrap_call(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
|
||||
if let ExprKind::MethodCall(path, receiver, ..) = expr.kind
|
||||
&& path.ident.name == sym::unwrap
|
||||
{
|
||||
is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(receiver).peel_refs(), sym::Result)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, receiver: &Expr<'_>) {
|
||||
if is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(receiver).peel_refs(), sym::RwLock)
|
||||
&& let Node::Expr(unwrap_call_expr) = cx.tcx.hir().get_parent(expr.hir_id)
|
||||
&& is_unwrap_call(cx, unwrap_call_expr)
|
||||
&& let parent = cx.tcx.hir().get_parent(unwrap_call_expr.hir_id)
|
||||
&& let Node::Local(local) = parent
|
||||
&& let Some(mir) = enclosing_mir(cx.tcx, expr.hir_id)
|
||||
&& let Some((local, _)) = mir.local_decls.iter_enumerated().find(|(_, decl)| {
|
||||
local.span.contains(decl.source_info.span)
|
||||
})
|
||||
&& let Some(usages) = visit_local_usage(&[local], mir, Location {
|
||||
block: START_BLOCK,
|
||||
statement_index: 0,
|
||||
})
|
||||
&& let [usage] = usages.as_slice()
|
||||
{
|
||||
let writer_never_mutated = usage.local_consume_or_mutate_locs.is_empty();
|
||||
|
||||
if writer_never_mutated {
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
READONLY_WRITE_LOCK,
|
||||
expr.span,
|
||||
"this write lock is used only for reading",
|
||||
"consider using a read lock instead",
|
||||
format!("{}.read()", snippet(cx, receiver.span, "<receiver>")),
|
||||
Applicability::MaybeIncorrect // write lock might be intentional for enforcing exclusiveness
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
58
clippy_lints/src/methods/string_lit_chars_any.rs
Normal file
58
clippy_lints/src/methods/string_lit_chars_any.rs
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
use clippy_utils::diagnostics::span_lint_and_then;
|
||||
use clippy_utils::msrvs::{Msrv, MATCHES_MACRO};
|
||||
use clippy_utils::source::snippet_opt;
|
||||
use clippy_utils::{is_from_proc_macro, is_trait_method, path_to_local};
|
||||
use itertools::Itertools;
|
||||
use rustc_ast::LitKind;
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::{BinOpKind, Expr, ExprKind, Param, PatKind};
|
||||
use rustc_lint::LateContext;
|
||||
use rustc_span::sym;
|
||||
|
||||
use super::STRING_LIT_CHARS_ANY;
|
||||
|
||||
pub(super) fn check<'tcx>(
|
||||
cx: &LateContext<'tcx>,
|
||||
expr: &'tcx Expr<'tcx>,
|
||||
recv: &Expr<'_>,
|
||||
param: &'tcx Param<'tcx>,
|
||||
body: &Expr<'_>,
|
||||
msrv: &Msrv,
|
||||
) {
|
||||
if msrv.meets(MATCHES_MACRO)
|
||||
&& is_trait_method(cx, expr, sym::Iterator)
|
||||
&& let PatKind::Binding(_, arg, _, _) = param.pat.kind
|
||||
&& let ExprKind::Lit(lit_kind) = recv.kind
|
||||
&& let LitKind::Str(val, _) = lit_kind.node
|
||||
&& let ExprKind::Binary(kind, lhs, rhs) = body.kind
|
||||
&& let BinOpKind::Eq = kind.node
|
||||
&& let Some(lhs_path) = path_to_local(lhs)
|
||||
&& let Some(rhs_path) = path_to_local(rhs)
|
||||
&& let scrutinee = match (lhs_path == arg, rhs_path == arg) {
|
||||
(true, false) => rhs,
|
||||
(false, true) => lhs,
|
||||
_ => return,
|
||||
}
|
||||
&& !is_from_proc_macro(cx, expr)
|
||||
&& let Some(scrutinee_snip) = snippet_opt(cx, scrutinee.span)
|
||||
{
|
||||
// Normalize the char using `map` so `join` doesn't use `Display`, if we don't then
|
||||
// something like `r"\"` will become `'\'`, which is of course invalid
|
||||
let pat_snip = val.as_str().chars().map(|c| format!("{c:?}")).join(" | ");
|
||||
|
||||
span_lint_and_then(
|
||||
cx,
|
||||
STRING_LIT_CHARS_ANY,
|
||||
expr.span,
|
||||
"usage of `.chars().any(...)` to check if a char matches any from a string literal",
|
||||
|diag| {
|
||||
diag.span_suggestion_verbose(
|
||||
expr.span,
|
||||
"use `matches!(...)` instead",
|
||||
format!("matches!({scrutinee_snip}, {pat_snip})"),
|
||||
Applicability::MachineApplicable,
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -24,9 +24,9 @@ pub(super) fn check(cx: &LateContext<'_>, receiver: &Expr<'_>, call_span: Span)
|
|||
|
||||
if let Some(Adjustment { target: recv_ty, .. }) = recv_adjusts.last()
|
||||
&& let ty::Ref(_, ty, _) = recv_ty.kind()
|
||||
&& let ty::Adt(adt, substs) = ty.kind()
|
||||
&& let ty::Adt(adt, args) = ty.kind()
|
||||
&& adt.is_box()
|
||||
&& is_dyn_any(cx, substs.type_at(0))
|
||||
&& is_dyn_any(cx, args.type_at(0))
|
||||
{
|
||||
span_lint_and_then(
|
||||
cx,
|
||||
|
|
|
|||
|
|
@ -3,6 +3,8 @@ use clippy_utils::{is_res_lang_ctor, last_path_segment, path_res, MaybePath};
|
|||
use rustc_errors::Applicability;
|
||||
use rustc_hir as hir;
|
||||
use rustc_lint::LateContext;
|
||||
use rustc_middle::ty;
|
||||
use rustc_middle::ty::print::with_forced_trimmed_paths;
|
||||
|
||||
use super::UNNECESSARY_LITERAL_UNWRAP;
|
||||
|
||||
|
|
@ -22,6 +24,7 @@ fn get_ty_from_args<'a>(args: Option<&'a [hir::GenericArg<'a>]>, index: usize) -
|
|||
}
|
||||
}
|
||||
|
||||
#[expect(clippy::too_many_lines)]
|
||||
pub(super) fn check(
|
||||
cx: &LateContext<'_>,
|
||||
expr: &hir::Expr<'_>,
|
||||
|
|
@ -84,6 +87,34 @@ pub(super) fn check(
|
|||
}
|
||||
Some(suggs)
|
||||
},
|
||||
("None", "unwrap_or_default", _) => {
|
||||
let ty = cx.typeck_results().expr_ty(expr);
|
||||
let default_ty_string = if let ty::Adt(def, ..) = ty.kind() {
|
||||
with_forced_trimmed_paths!(format!("{}", cx.tcx.def_path_str(def.did())))
|
||||
} else {
|
||||
"Default".to_string()
|
||||
};
|
||||
Some(vec![(expr.span, format!("{default_ty_string}::default()"))])
|
||||
},
|
||||
("None", "unwrap_or", _) => Some(vec![
|
||||
(expr.span.with_hi(args[0].span.lo()), String::new()),
|
||||
(expr.span.with_lo(args[0].span.hi()), String::new()),
|
||||
]),
|
||||
("None", "unwrap_or_else", _) => match args[0].kind {
|
||||
hir::ExprKind::Closure(hir::Closure {
|
||||
fn_decl:
|
||||
hir::FnDecl {
|
||||
output: hir::FnRetTy::DefaultReturn(span) | hir::FnRetTy::Return(hir::Ty { span, .. }),
|
||||
..
|
||||
},
|
||||
..
|
||||
}) => Some(vec![
|
||||
(expr.span.with_hi(span.hi()), String::new()),
|
||||
(expr.span.with_lo(args[0].span.hi()), String::new()),
|
||||
]),
|
||||
_ => None,
|
||||
},
|
||||
_ if call_args.is_empty() => None,
|
||||
(_, _, Some(_)) => None,
|
||||
("Ok", "unwrap_err", None) | ("Err", "unwrap", None) => Some(vec![
|
||||
(
|
||||
|
|
|
|||
|
|
@ -6,7 +6,8 @@ use if_chain::if_chain;
|
|||
use rustc_errors::Applicability;
|
||||
use rustc_hir::{Closure, Expr, ExprKind, Mutability, Param, Pat, PatKind, Path, PathSegment, QPath};
|
||||
use rustc_lint::LateContext;
|
||||
use rustc_middle::ty::{self, GenericArgKind};
|
||||
use rustc_middle::ty;
|
||||
use rustc_middle::ty::GenericArgKind;
|
||||
use rustc_span::sym;
|
||||
use rustc_span::symbol::Ident;
|
||||
use std::iter;
|
||||
|
|
|
|||
|
|
@ -1,66 +0,0 @@
|
|||
//! Lint for `some_result_or_option.unwrap_or_else(Default::default)`
|
||||
|
||||
use super::UNWRAP_OR_ELSE_DEFAULT;
|
||||
use clippy_utils::diagnostics::span_lint_and_sugg;
|
||||
use clippy_utils::is_default_equivalent_call;
|
||||
use clippy_utils::source::snippet_with_applicability;
|
||||
use clippy_utils::ty::is_type_diagnostic_item;
|
||||
use rustc_ast::ast::LitKind;
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir as hir;
|
||||
use rustc_lint::LateContext;
|
||||
use rustc_span::{sym, symbol};
|
||||
|
||||
pub(super) fn check<'tcx>(
|
||||
cx: &LateContext<'tcx>,
|
||||
expr: &'tcx hir::Expr<'_>,
|
||||
recv: &'tcx hir::Expr<'_>,
|
||||
u_arg: &'tcx hir::Expr<'_>,
|
||||
) {
|
||||
// something.unwrap_or_else(Default::default)
|
||||
// ^^^^^^^^^- recv ^^^^^^^^^^^^^^^^- u_arg
|
||||
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^- expr
|
||||
let recv_ty = cx.typeck_results().expr_ty(recv);
|
||||
let is_option = is_type_diagnostic_item(cx, recv_ty, sym::Option);
|
||||
let is_result = is_type_diagnostic_item(cx, recv_ty, sym::Result);
|
||||
|
||||
if_chain! {
|
||||
if is_option || is_result;
|
||||
if closure_body_returns_empty_to_string(cx, u_arg) || is_default_equivalent_call(cx, u_arg);
|
||||
then {
|
||||
let mut applicability = Applicability::MachineApplicable;
|
||||
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
UNWRAP_OR_ELSE_DEFAULT,
|
||||
expr.span,
|
||||
"use of `.unwrap_or_else(..)` to construct default value",
|
||||
"try",
|
||||
format!(
|
||||
"{}.unwrap_or_default()",
|
||||
snippet_with_applicability(cx, recv.span, "..", &mut applicability)
|
||||
),
|
||||
applicability,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn closure_body_returns_empty_to_string(cx: &LateContext<'_>, e: &hir::Expr<'_>) -> bool {
|
||||
if let hir::ExprKind::Closure(&hir::Closure { body, .. }) = e.kind {
|
||||
let body = cx.tcx.hir().body(body);
|
||||
|
||||
if body.params.is_empty()
|
||||
&& let hir::Expr{ kind, .. } = &body.value
|
||||
&& let hir::ExprKind::MethodCall(hir::PathSegment {ident, ..}, self_arg, _, _) = kind
|
||||
&& ident == &symbol::Ident::from_str("to_string")
|
||||
&& let hir::Expr{ kind, .. } = self_arg
|
||||
&& let hir::ExprKind::Lit(lit) = kind
|
||||
&& let LitKind::Str(symbol::kw::Empty, _) = lit.node
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue