Merge remote-tracking branch 'upstream/master' into rustup
This commit is contained in:
commit
2c0cea7cbc
170 changed files with 5217 additions and 855 deletions
|
|
@ -26,13 +26,14 @@ fn is_method(cx: &LateContext<'_>, expr: &hir::Expr<'_>, method_name: Symbol) ->
|
|||
hir::ExprKind::Closure(&hir::Closure { body, .. }) => {
|
||||
let body = cx.tcx.hir().body(body);
|
||||
let closure_expr = peel_blocks(body.value);
|
||||
let arg_id = body.params[0].pat.hir_id;
|
||||
match closure_expr.kind {
|
||||
hir::ExprKind::MethodCall(hir::PathSegment { ident, .. }, receiver, ..) => {
|
||||
if ident.name == method_name
|
||||
&& let hir::ExprKind::Path(path) = &receiver.kind
|
||||
&& let Res::Local(ref local) = cx.qpath_res(path, receiver.hir_id)
|
||||
&& !body.params.is_empty()
|
||||
{
|
||||
let arg_id = body.params[0].pat.hir_id;
|
||||
return arg_id == *local;
|
||||
}
|
||||
false
|
||||
|
|
|
|||
|
|
@ -1,80 +1,181 @@
|
|||
use clippy_utils::ty::get_iterator_item_ty;
|
||||
use hir::ExprKind;
|
||||
use rustc_lint::{LateContext, LintContext};
|
||||
|
||||
use super::{ITER_FILTER_IS_OK, ITER_FILTER_IS_SOME};
|
||||
|
||||
use clippy_utils::diagnostics::span_lint_and_sugg;
|
||||
use clippy_utils::source::{indent_of, reindent_multiline};
|
||||
use clippy_utils::{is_trait_method, peel_blocks, span_contains_comment};
|
||||
use clippy_utils::{get_parent_expr, is_trait_method, peel_blocks, span_contains_comment};
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir as hir;
|
||||
use rustc_hir::def::Res;
|
||||
use rustc_hir::QPath;
|
||||
use rustc_span::symbol::{sym, Symbol};
|
||||
use rustc_span::symbol::{sym, Ident, Symbol};
|
||||
use rustc_span::Span;
|
||||
use std::borrow::Cow;
|
||||
|
||||
fn is_method(cx: &LateContext<'_>, expr: &hir::Expr<'_>, method_name: Symbol) -> bool {
|
||||
match &expr.kind {
|
||||
hir::ExprKind::Path(QPath::TypeRelative(_, mname)) => mname.ident.name == method_name,
|
||||
hir::ExprKind::Path(QPath::Resolved(_, segments)) => {
|
||||
segments.segments.last().unwrap().ident.name == method_name
|
||||
///
|
||||
/// Returns true if the expression is a method call to `method_name`
|
||||
/// e.g. `a.method_name()` or `Option::method_name`.
|
||||
///
|
||||
/// The type-checker verifies for us that the method accepts the right kind of items
|
||||
/// (e.g. `Option::is_some` accepts `Option<_>`), so we don't need to check that.
|
||||
///
|
||||
/// How to capture each case:
|
||||
///
|
||||
/// `.filter(|a| { std::option::Option::is_some(a) })`
|
||||
/// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ <- this is a closure, getting unwrapped and
|
||||
/// recursively checked.
|
||||
/// `std::option::Option::is_some(a)`
|
||||
/// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ <- this is a call. It unwraps to a path with
|
||||
/// `QPath::TypeRelative`. Since this is a type relative path, we need to check the method name, the
|
||||
/// type, and that the parameter of the closure is passed in the call. This part is the dual of
|
||||
/// `receiver.method_name()` below.
|
||||
///
|
||||
/// `filter(std::option::Option::is_some);`
|
||||
/// ^^^^^^^^^^^^^^^^^^^^^^^^^^^ <- this is a type relative path, like above, we check the
|
||||
/// type and the method name.
|
||||
///
|
||||
/// `filter(|a| a.is_some());`
|
||||
/// ^^^^^^^^^^^^^^^ <- this is a method call inside a closure,
|
||||
/// we check that the parameter of the closure is the receiver of the method call and don't allow
|
||||
/// any other parameters.
|
||||
fn is_method(
|
||||
cx: &LateContext<'_>,
|
||||
expr: &hir::Expr<'_>,
|
||||
type_symbol: Symbol,
|
||||
method_name: Symbol,
|
||||
params: &[&hir::Pat<'_>],
|
||||
) -> bool {
|
||||
fn pat_is_recv(ident: Ident, param: &hir::Pat<'_>) -> bool {
|
||||
match param.kind {
|
||||
hir::PatKind::Binding(_, _, other, _) => ident == other,
|
||||
hir::PatKind::Ref(pat, _) => pat_is_recv(ident, pat),
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
match expr.kind {
|
||||
hir::ExprKind::MethodCall(hir::PathSegment { ident, .. }, recv, ..) => {
|
||||
// compare the identifier of the receiver to the parameter
|
||||
// we are in a filter => closure has a single parameter and a single, non-block
|
||||
// expression, this means that the parameter shadows all outside variables with
|
||||
// the same name => avoid FPs. If the parameter is not the receiver, then this hits
|
||||
// outside variables => avoid FP
|
||||
if ident.name == method_name
|
||||
&& let ExprKind::Path(QPath::Resolved(None, path)) = recv.kind
|
||||
&& let &[seg] = path.segments
|
||||
&& params.iter().any(|p| pat_is_recv(seg.ident, p))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
false
|
||||
},
|
||||
// This is used to check for complete paths via `|a| std::option::Option::is_some(a)`
|
||||
// this then unwraps to a path with `QPath::TypeRelative`
|
||||
// we pass the params as they've been passed to the current call through the closure
|
||||
hir::ExprKind::Call(expr, [param]) => {
|
||||
// this will hit the `QPath::TypeRelative` case and check that the method name is correct
|
||||
if is_method(cx, expr, type_symbol, method_name, params)
|
||||
// we then check that this is indeed passing the parameter of the closure
|
||||
&& let ExprKind::Path(QPath::Resolved(None, path)) = param.kind
|
||||
&& let &[seg] = path.segments
|
||||
&& params.iter().any(|p| pat_is_recv(seg.ident, p))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
false
|
||||
},
|
||||
hir::ExprKind::Path(QPath::TypeRelative(ty, mname)) => {
|
||||
let ty = cx.typeck_results().node_type(ty.hir_id);
|
||||
if let Some(did) = cx.tcx.get_diagnostic_item(type_symbol)
|
||||
&& ty.ty_adt_def() == cx.tcx.type_of(did).skip_binder().ty_adt_def()
|
||||
{
|
||||
return mname.ident.name == method_name;
|
||||
}
|
||||
false
|
||||
},
|
||||
hir::ExprKind::MethodCall(segment, _, _, _) => segment.ident.name == method_name,
|
||||
hir::ExprKind::Closure(&hir::Closure { body, .. }) => {
|
||||
let body = cx.tcx.hir().body(body);
|
||||
let closure_expr = peel_blocks(body.value);
|
||||
let arg_id = body.params[0].pat.hir_id;
|
||||
match closure_expr.kind {
|
||||
hir::ExprKind::MethodCall(hir::PathSegment { ident, .. }, receiver, ..) => {
|
||||
if ident.name == method_name
|
||||
&& let hir::ExprKind::Path(path) = &receiver.kind
|
||||
&& let Res::Local(ref local) = cx.qpath_res(path, receiver.hir_id)
|
||||
{
|
||||
return arg_id == *local;
|
||||
}
|
||||
false
|
||||
},
|
||||
_ => false,
|
||||
}
|
||||
let params = body.params.iter().map(|param| param.pat).collect::<Vec<_>>();
|
||||
is_method(cx, closure_expr, type_symbol, method_name, params.as_slice())
|
||||
},
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
fn parent_is_map(cx: &LateContext<'_>, expr: &hir::Expr<'_>) -> bool {
|
||||
if let hir::Node::Expr(parent_expr) = cx.tcx.hir().get_parent(expr.hir_id) {
|
||||
is_method(cx, parent_expr, rustc_span::sym::map)
|
||||
} else {
|
||||
false
|
||||
if let Some(expr) = get_parent_expr(cx, expr)
|
||||
&& is_trait_method(cx, expr, sym::Iterator)
|
||||
&& let hir::ExprKind::MethodCall(path, _, _, _) = expr.kind
|
||||
&& path.ident.name == rustc_span::sym::map
|
||||
{
|
||||
return true;
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, filter_arg: &hir::Expr<'_>, filter_span: Span) {
|
||||
let is_iterator = is_trait_method(cx, expr, sym::Iterator);
|
||||
let parent_is_not_map = !parent_is_map(cx, expr);
|
||||
enum FilterType {
|
||||
IsSome,
|
||||
IsOk,
|
||||
}
|
||||
|
||||
if is_iterator
|
||||
&& parent_is_not_map
|
||||
&& is_method(cx, filter_arg, sym!(is_some))
|
||||
&& !span_contains_comment(cx.sess().source_map(), filter_span.with_hi(expr.span.hi()))
|
||||
/// Returns the `FilterType` of the expression if it is a filter over an Iter<Option> or
|
||||
/// Iter<Result> with the parent expression not being a map, and not having a comment in the span of
|
||||
/// the filter. If it is not a filter over an Iter<Option> or Iter<Result> then it returns None
|
||||
///
|
||||
/// How this is done:
|
||||
/// 1. we know that this is invoked in a method call with `filter` as the method name via `mod.rs`
|
||||
/// 2. we check that we are in a trait method. Therefore we are in an
|
||||
/// `(x as Iterator).filter({filter_arg})` method call.
|
||||
/// 3. we check that the parent expression is not a map. This is because we don't want to lint
|
||||
/// twice, and we already have a specialized lint for that.
|
||||
/// 4. we check that the span of the filter does not contain a comment.
|
||||
/// 5. we get the type of the `Item` in the `Iterator`, and compare against the type of Option and
|
||||
/// Result.
|
||||
/// 6. we finally check the contents of the filter argument to see if it is a call to `is_some` or
|
||||
/// `is_ok`.
|
||||
/// 7. if all of the above are true, then we return the `FilterType`
|
||||
fn expression_type(
|
||||
cx: &LateContext<'_>,
|
||||
expr: &hir::Expr<'_>,
|
||||
filter_arg: &hir::Expr<'_>,
|
||||
filter_span: Span,
|
||||
) -> Option<FilterType> {
|
||||
if !is_trait_method(cx, expr, sym::Iterator)
|
||||
|| parent_is_map(cx, expr)
|
||||
|| span_contains_comment(cx.sess().source_map(), filter_span.with_hi(expr.span.hi()))
|
||||
{
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
ITER_FILTER_IS_SOME,
|
||||
filter_span.with_hi(expr.span.hi()),
|
||||
"`filter` for `is_some` on iterator over `Option`",
|
||||
"consider using `flatten` instead",
|
||||
reindent_multiline(Cow::Borrowed("flatten()"), true, indent_of(cx, filter_span)).into_owned(),
|
||||
Applicability::HasPlaceholders,
|
||||
);
|
||||
return None;
|
||||
}
|
||||
if is_iterator
|
||||
&& parent_is_not_map
|
||||
&& is_method(cx, filter_arg, sym!(is_ok))
|
||||
&& !span_contains_comment(cx.sess().source_map(), filter_span.with_hi(expr.span.hi()))
|
||||
if let hir::ExprKind::MethodCall(_, receiver, _, _) = expr.kind
|
||||
&& let receiver_ty = cx.typeck_results().expr_ty(receiver)
|
||||
&& let Some(iter_item_ty) = get_iterator_item_ty(cx, receiver_ty)
|
||||
{
|
||||
span_lint_and_sugg(
|
||||
if let Some(opt_defid) = cx.tcx.get_diagnostic_item(sym::Option)
|
||||
&& let opt_ty = cx.tcx.type_of(opt_defid).skip_binder()
|
||||
&& iter_item_ty.ty_adt_def() == opt_ty.ty_adt_def()
|
||||
&& is_method(cx, filter_arg, sym::Option, sym!(is_some), &[])
|
||||
{
|
||||
return Some(FilterType::IsSome);
|
||||
}
|
||||
|
||||
if let Some(opt_defid) = cx.tcx.get_diagnostic_item(sym::Result)
|
||||
&& let opt_ty = cx.tcx.type_of(opt_defid).skip_binder()
|
||||
&& iter_item_ty.ty_adt_def() == opt_ty.ty_adt_def()
|
||||
&& is_method(cx, filter_arg, sym::Result, sym!(is_ok), &[])
|
||||
{
|
||||
return Some(FilterType::IsOk);
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, filter_arg: &hir::Expr<'_>, filter_span: Span) {
|
||||
// we are in a filter inside an iterator
|
||||
match expression_type(cx, expr, filter_arg, filter_span) {
|
||||
None => (),
|
||||
Some(FilterType::IsOk) => span_lint_and_sugg(
|
||||
cx,
|
||||
ITER_FILTER_IS_OK,
|
||||
filter_span.with_hi(expr.span.hi()),
|
||||
|
|
@ -82,6 +183,15 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, filter_arg: &hir
|
|||
"consider using `flatten` instead",
|
||||
reindent_multiline(Cow::Borrowed("flatten()"), true, indent_of(cx, filter_span)).into_owned(),
|
||||
Applicability::HasPlaceholders,
|
||||
);
|
||||
),
|
||||
Some(FilterType::IsSome) => span_lint_and_sugg(
|
||||
cx,
|
||||
ITER_FILTER_IS_SOME,
|
||||
filter_span.with_hi(expr.span.hi()),
|
||||
"`filter` for `is_some` on iterator over `Option`",
|
||||
"consider using `flatten` instead",
|
||||
reindent_multiline(Cow::Borrowed("flatten()"), true, indent_of(cx, filter_span)).into_owned(),
|
||||
Applicability::HasPlaceholders,
|
||||
),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
59
clippy_lints/src/methods/manual_is_variant_and.rs
Normal file
59
clippy_lints/src/methods/manual_is_variant_and.rs
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
use clippy_config::msrvs::{Msrv, OPTION_RESULT_IS_VARIANT_AND};
|
||||
use clippy_utils::diagnostics::span_lint_and_sugg;
|
||||
use clippy_utils::source::snippet;
|
||||
use clippy_utils::ty::is_type_diagnostic_item;
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_lint::LateContext;
|
||||
use rustc_span::{sym, Span};
|
||||
|
||||
use super::MANUAL_IS_VARIANT_AND;
|
||||
|
||||
pub(super) fn check<'tcx>(
|
||||
cx: &LateContext<'_>,
|
||||
expr: &'tcx rustc_hir::Expr<'_>,
|
||||
map_recv: &'tcx rustc_hir::Expr<'_>,
|
||||
map_arg: &'tcx rustc_hir::Expr<'_>,
|
||||
map_span: Span,
|
||||
msrv: &Msrv,
|
||||
) {
|
||||
// Don't lint if:
|
||||
|
||||
// 1. the `expr` is generated by a macro
|
||||
if expr.span.from_expansion() {
|
||||
return;
|
||||
}
|
||||
|
||||
// 2. the caller of `map()` is neither `Option` nor `Result`
|
||||
let is_option = is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(map_recv), sym::Option);
|
||||
let is_result = is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(map_recv), sym::Result);
|
||||
if !is_option && !is_result {
|
||||
return;
|
||||
}
|
||||
|
||||
// 3. the caller of `unwrap_or_default` is neither `Option<bool>` nor `Result<bool, _>`
|
||||
if !cx.typeck_results().expr_ty(expr).is_bool() {
|
||||
return;
|
||||
}
|
||||
|
||||
// 4. msrv doesn't meet `OPTION_RESULT_IS_VARIANT_AND`
|
||||
if !msrv.meets(OPTION_RESULT_IS_VARIANT_AND) {
|
||||
return;
|
||||
}
|
||||
|
||||
let lint_msg = if is_option {
|
||||
"called `map(<f>).unwrap_or_default()` on an `Option` value"
|
||||
} else {
|
||||
"called `map(<f>).unwrap_or_default()` on a `Result` value"
|
||||
};
|
||||
let suggestion = if is_option { "is_some_and" } else { "is_ok_and" };
|
||||
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
MANUAL_IS_VARIANT_AND,
|
||||
expr.span.with_lo(map_span.lo()),
|
||||
lint_msg,
|
||||
"use",
|
||||
format!("{}({})", suggestion, snippet(cx, map_arg.span, "..")),
|
||||
Applicability::MachineApplicable,
|
||||
);
|
||||
}
|
||||
|
|
@ -2,9 +2,10 @@ use clippy_config::msrvs::{self, Msrv};
|
|||
use clippy_utils::diagnostics::span_lint_and_sugg;
|
||||
use clippy_utils::source::snippet_with_applicability;
|
||||
use clippy_utils::ty::{is_copy, is_type_diagnostic_item};
|
||||
use clippy_utils::{is_diag_trait_item, peel_blocks};
|
||||
use clippy_utils::{is_diag_trait_item, match_def_path, paths, peel_blocks};
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir as hir;
|
||||
use rustc_hir::def_id::DefId;
|
||||
use rustc_lint::LateContext;
|
||||
use rustc_middle::mir::Mutability;
|
||||
use rustc_middle::ty;
|
||||
|
|
@ -14,60 +15,110 @@ use rustc_span::{sym, Span};
|
|||
|
||||
use super::MAP_CLONE;
|
||||
|
||||
// If this `map` is called on an `Option` or a `Result` and the previous call is `as_ref`, we don't
|
||||
// run this lint because it would overlap with `useless_asref` which provides a better suggestion
|
||||
// in this case.
|
||||
fn should_run_lint(cx: &LateContext<'_>, e: &hir::Expr<'_>, method_id: DefId) -> bool {
|
||||
if is_diag_trait_item(cx, method_id, sym::Iterator) {
|
||||
return true;
|
||||
}
|
||||
// We check if it's an `Option` or a `Result`.
|
||||
if let Some(id) = cx.tcx.impl_of_method(method_id) {
|
||||
let identity = cx.tcx.type_of(id).instantiate_identity();
|
||||
if !is_type_diagnostic_item(cx, identity, sym::Option) && !is_type_diagnostic_item(cx, identity, sym::Result) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
// We check if the previous method call is `as_ref`.
|
||||
if let hir::ExprKind::MethodCall(path1, receiver, _, _) = &e.kind
|
||||
&& let hir::ExprKind::MethodCall(path2, _, _, _) = &receiver.kind
|
||||
{
|
||||
return path2.ident.name != sym::as_ref || path1.ident.name != sym::map;
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
pub(super) fn check(cx: &LateContext<'_>, e: &hir::Expr<'_>, recv: &hir::Expr<'_>, arg: &hir::Expr<'_>, msrv: &Msrv) {
|
||||
if let Some(method_id) = cx.typeck_results().type_dependent_def_id(e.hir_id)
|
||||
&& (cx.tcx.impl_of_method(method_id).map_or(false, |id| {
|
||||
is_type_diagnostic_item(cx, cx.tcx.type_of(id).instantiate_identity(), sym::Option)
|
||||
}) || is_diag_trait_item(cx, method_id, sym::Iterator))
|
||||
&& let hir::ExprKind::Closure(&hir::Closure { body, .. }) = arg.kind
|
||||
&& should_run_lint(cx, e, method_id)
|
||||
{
|
||||
let closure_body = cx.tcx.hir().body(body);
|
||||
let closure_expr = peel_blocks(closure_body.value);
|
||||
match closure_body.params[0].pat.kind {
|
||||
hir::PatKind::Ref(inner, hir::Mutability::Not) => {
|
||||
if let hir::PatKind::Binding(hir::BindingAnnotation::NONE, .., name, None) = inner.kind {
|
||||
if ident_eq(name, closure_expr) {
|
||||
lint_explicit_closure(cx, e.span, recv.span, true, msrv);
|
||||
}
|
||||
}
|
||||
},
|
||||
hir::PatKind::Binding(hir::BindingAnnotation::NONE, .., name, None) => {
|
||||
match closure_expr.kind {
|
||||
hir::ExprKind::Unary(hir::UnOp::Deref, inner) => {
|
||||
if ident_eq(name, inner) {
|
||||
if let ty::Ref(.., Mutability::Not) = cx.typeck_results().expr_ty(inner).kind() {
|
||||
match arg.kind {
|
||||
hir::ExprKind::Closure(&hir::Closure { body, .. }) => {
|
||||
let closure_body = cx.tcx.hir().body(body);
|
||||
let closure_expr = peel_blocks(closure_body.value);
|
||||
match closure_body.params[0].pat.kind {
|
||||
hir::PatKind::Ref(inner, hir::Mutability::Not) => {
|
||||
if let hir::PatKind::Binding(hir::BindingAnnotation::NONE, .., name, None) = inner.kind {
|
||||
if ident_eq(name, closure_expr) {
|
||||
lint_explicit_closure(cx, e.span, recv.span, true, msrv);
|
||||
}
|
||||
}
|
||||
},
|
||||
hir::ExprKind::MethodCall(method, obj, [], _) => {
|
||||
if ident_eq(name, obj) && method.ident.name == sym::clone
|
||||
&& let Some(fn_id) = cx.typeck_results().type_dependent_def_id(closure_expr.hir_id)
|
||||
&& let Some(trait_id) = cx.tcx.trait_of_item(fn_id)
|
||||
&& cx.tcx.lang_items().clone_trait().map_or(false, |id| id == trait_id)
|
||||
// no autoderefs
|
||||
&& !cx.typeck_results().expr_adjustments(obj).iter()
|
||||
.any(|a| matches!(a.kind, Adjust::Deref(Some(..))))
|
||||
{
|
||||
let obj_ty = cx.typeck_results().expr_ty(obj);
|
||||
if let ty::Ref(_, ty, mutability) = obj_ty.kind() {
|
||||
if matches!(mutability, Mutability::Not) {
|
||||
let copy = is_copy(cx, *ty);
|
||||
lint_explicit_closure(cx, e.span, recv.span, copy, msrv);
|
||||
hir::PatKind::Binding(hir::BindingAnnotation::NONE, .., name, None) => {
|
||||
match closure_expr.kind {
|
||||
hir::ExprKind::Unary(hir::UnOp::Deref, inner) => {
|
||||
if ident_eq(name, inner) {
|
||||
if let ty::Ref(.., Mutability::Not) = cx.typeck_results().expr_ty(inner).kind() {
|
||||
lint_explicit_closure(cx, e.span, recv.span, true, msrv);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
lint_needless_cloning(cx, e.span, recv.span);
|
||||
}
|
||||
},
|
||||
hir::ExprKind::MethodCall(method, obj, [], _) => {
|
||||
if ident_eq(name, obj) && method.ident.name == sym::clone
|
||||
&& let Some(fn_id) = cx.typeck_results().type_dependent_def_id(closure_expr.hir_id)
|
||||
&& let Some(trait_id) = cx.tcx.trait_of_item(fn_id)
|
||||
&& cx.tcx.lang_items().clone_trait().map_or(false, |id| id == trait_id)
|
||||
// no autoderefs
|
||||
&& !cx.typeck_results().expr_adjustments(obj).iter()
|
||||
.any(|a| matches!(a.kind, Adjust::Deref(Some(..))))
|
||||
{
|
||||
let obj_ty = cx.typeck_results().expr_ty(obj);
|
||||
if let ty::Ref(_, ty, mutability) = obj_ty.kind() {
|
||||
if matches!(mutability, Mutability::Not) {
|
||||
let copy = is_copy(cx, *ty);
|
||||
lint_explicit_closure(cx, e.span, recv.span, copy, msrv);
|
||||
}
|
||||
} else {
|
||||
lint_needless_cloning(cx, e.span, recv.span);
|
||||
}
|
||||
}
|
||||
},
|
||||
hir::ExprKind::Call(call, [_]) => {
|
||||
if let hir::ExprKind::Path(qpath) = call.kind {
|
||||
handle_path(cx, call, &qpath, e, recv);
|
||||
}
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
},
|
||||
hir::ExprKind::Path(qpath) => handle_path(cx, arg, &qpath, e, recv),
|
||||
_ => {},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_path(
|
||||
cx: &LateContext<'_>,
|
||||
arg: &hir::Expr<'_>,
|
||||
qpath: &hir::QPath<'_>,
|
||||
e: &hir::Expr<'_>,
|
||||
recv: &hir::Expr<'_>,
|
||||
) {
|
||||
if let Some(path_def_id) = cx.qpath_res(qpath, arg.hir_id).opt_def_id()
|
||||
&& match_def_path(cx, path_def_id, &paths::CLONE_TRAIT_METHOD)
|
||||
{
|
||||
// FIXME: It would be better to infer the type to check if it's copyable or not
|
||||
// to suggest to use `.copied()` instead of `.cloned()` where applicable.
|
||||
lint_path(cx, e.span, recv.span);
|
||||
}
|
||||
}
|
||||
|
||||
fn ident_eq(name: Ident, path: &hir::Expr<'_>) -> bool {
|
||||
if let hir::ExprKind::Path(hir::QPath::Resolved(None, path)) = path.kind {
|
||||
path.segments.len() == 1 && path.segments[0].ident == name
|
||||
|
|
@ -88,6 +139,23 @@ fn lint_needless_cloning(cx: &LateContext<'_>, root: Span, receiver: Span) {
|
|||
);
|
||||
}
|
||||
|
||||
fn lint_path(cx: &LateContext<'_>, replace: Span, root: Span) {
|
||||
let mut applicability = Applicability::MachineApplicable;
|
||||
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
MAP_CLONE,
|
||||
replace,
|
||||
"you are explicitly cloning with `.map()`",
|
||||
"consider calling the dedicated `cloned` method",
|
||||
format!(
|
||||
"{}.cloned()",
|
||||
snippet_with_applicability(cx, root, "..", &mut applicability),
|
||||
),
|
||||
applicability,
|
||||
);
|
||||
}
|
||||
|
||||
fn lint_explicit_closure(cx: &LateContext<'_>, replace: Span, root: Span, is_copy: bool, msrv: &Msrv) {
|
||||
let mut applicability = Applicability::MachineApplicable;
|
||||
|
||||
|
|
|
|||
|
|
@ -51,6 +51,7 @@ mod iter_skip_zero;
|
|||
mod iter_with_drain;
|
||||
mod iterator_step_by_zero;
|
||||
mod join_absolute_paths;
|
||||
mod manual_is_variant_and;
|
||||
mod manual_next_back;
|
||||
mod manual_ok_or;
|
||||
mod manual_saturating_arithmetic;
|
||||
|
|
@ -70,6 +71,7 @@ mod no_effect_replace;
|
|||
mod obfuscated_if_else;
|
||||
mod ok_expect;
|
||||
mod open_options;
|
||||
mod option_as_ref_cloned;
|
||||
mod option_as_ref_deref;
|
||||
mod option_map_or_err_ok;
|
||||
mod option_map_or_none;
|
||||
|
|
@ -93,6 +95,7 @@ mod single_char_pattern;
|
|||
mod single_char_push_string;
|
||||
mod skip_while_next;
|
||||
mod stable_sort_primitive;
|
||||
mod str_split;
|
||||
mod str_splitn;
|
||||
mod string_extend_chars;
|
||||
mod string_lit_chars_any;
|
||||
|
|
@ -2589,11 +2592,11 @@ declare_clippy_lint! {
|
|||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// Checks for usage of `x.get(0)` instead of
|
||||
/// `x.first()`.
|
||||
/// `x.first()` or `x.front()`.
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
/// Using `x.first()` is easier to read and has the same
|
||||
/// result.
|
||||
/// Using `x.first()` for `Vec`s and slices or `x.front()`
|
||||
/// for `VecDeque`s is easier to read and has the same result.
|
||||
///
|
||||
/// ### Example
|
||||
/// ```no_run
|
||||
|
|
@ -2609,7 +2612,7 @@ declare_clippy_lint! {
|
|||
#[clippy::version = "1.63.0"]
|
||||
pub GET_FIRST,
|
||||
style,
|
||||
"Using `x.get(0)` when `x.first()` is simpler"
|
||||
"Using `x.get(0)` when `x.first()` or `x.front()` is simpler"
|
||||
}
|
||||
|
||||
declare_clippy_lint! {
|
||||
|
|
@ -3784,7 +3787,7 @@ declare_clippy_lint! {
|
|||
///
|
||||
/// ### Why is this bad?
|
||||
/// This pattern is often followed by manual unwrapping of the `Option`. The simplification
|
||||
/// results in more readable and succint code without the need for manual unwrapping.
|
||||
/// results in more readable and succinct code without the need for manual unwrapping.
|
||||
///
|
||||
/// ### Example
|
||||
/// ```no_run
|
||||
|
|
@ -3810,7 +3813,7 @@ declare_clippy_lint! {
|
|||
///
|
||||
/// ### Why is this bad?
|
||||
/// This pattern is often followed by manual unwrapping of `Result`. The simplification
|
||||
/// results in more readable and succint code without the need for manual unwrapping.
|
||||
/// results in more readable and succinct code without the need for manual unwrapping.
|
||||
///
|
||||
/// ### Example
|
||||
/// ```no_run
|
||||
|
|
@ -3829,6 +3832,87 @@ declare_clippy_lint! {
|
|||
"filtering an iterator over `Result`s for `Ok` can be achieved with `flatten`"
|
||||
}
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// Checks for usage of `option.map(f).unwrap_or_default()` and `result.map(f).unwrap_or_default()` where f is a function or closure that returns the `bool` type.
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
/// Readability. These can be written more concisely as `option.is_some_and(f)` and `result.is_ok_and(f)`.
|
||||
///
|
||||
/// ### Example
|
||||
/// ```no_run
|
||||
/// # let option = Some(1);
|
||||
/// # let result: Result<usize, ()> = Ok(1);
|
||||
/// option.map(|a| a > 10).unwrap_or_default();
|
||||
/// result.map(|a| a > 10).unwrap_or_default();
|
||||
/// ```
|
||||
/// Use instead:
|
||||
/// ```no_run
|
||||
/// # let option = Some(1);
|
||||
/// # let result: Result<usize, ()> = Ok(1);
|
||||
/// option.is_some_and(|a| a > 10);
|
||||
/// result.is_ok_and(|a| a > 10);
|
||||
/// ```
|
||||
#[clippy::version = "1.76.0"]
|
||||
pub MANUAL_IS_VARIANT_AND,
|
||||
pedantic,
|
||||
"using `.map(f).unwrap_or_default()`, which is more succinctly expressed as `is_some_and(f)` or `is_ok_and(f)`"
|
||||
}
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
///
|
||||
/// Checks for usages of `str.trim().split("\n")` and `str.trim().split("\r\n")`.
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
///
|
||||
/// Hard-coding the line endings makes the code less compatible. `str.lines` should be used instead.
|
||||
///
|
||||
/// ### Example
|
||||
/// ```no_run
|
||||
/// "some\ntext\nwith\nnewlines\n".trim().split('\n');
|
||||
/// ```
|
||||
/// Use instead:
|
||||
/// ```no_run
|
||||
/// "some\ntext\nwith\nnewlines\n".lines();
|
||||
/// ```
|
||||
///
|
||||
/// ### Known Problems
|
||||
///
|
||||
/// This lint cannot detect if the split is intentionally restricted to a single type of newline (`"\n"` or
|
||||
/// `"\r\n"`), for example during the parsing of a specific file format in which precisely one newline type is
|
||||
/// valid.
|
||||
/// ```
|
||||
#[clippy::version = "1.76.0"]
|
||||
pub STR_SPLIT_AT_NEWLINE,
|
||||
pedantic,
|
||||
"splitting a trimmed string at hard-coded newlines"
|
||||
}
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// Checks for usage of `.as_ref().cloned()` and `.as_mut().cloned()` on `Option`s
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
/// This can be written more concisely by cloning the `Option` directly.
|
||||
///
|
||||
/// ### Example
|
||||
/// ```no_run
|
||||
/// fn foo(bar: &Option<Vec<u8>>) -> Option<Vec<u8>> {
|
||||
/// bar.as_ref().cloned()
|
||||
/// }
|
||||
/// ```
|
||||
/// Use instead:
|
||||
/// ```no_run
|
||||
/// fn foo(bar: &Option<Vec<u8>>) -> Option<Vec<u8>> {
|
||||
/// bar.clone()
|
||||
/// }
|
||||
/// ```
|
||||
#[clippy::version = "1.77.0"]
|
||||
pub OPTION_AS_REF_CLONED,
|
||||
pedantic,
|
||||
"cloning an `Option` via `as_ref().cloned()`"
|
||||
}
|
||||
|
||||
pub struct Methods {
|
||||
avoid_breaking_exported_api: bool,
|
||||
msrv: Msrv,
|
||||
|
|
@ -3983,6 +4067,9 @@ impl_lint_pass!(Methods => [
|
|||
RESULT_FILTER_MAP,
|
||||
ITER_FILTER_IS_SOME,
|
||||
ITER_FILTER_IS_OK,
|
||||
MANUAL_IS_VARIANT_AND,
|
||||
STR_SPLIT_AT_NEWLINE,
|
||||
OPTION_AS_REF_CLONED,
|
||||
]);
|
||||
|
||||
/// Extracts a method call name, args, and `Span` of the method name.
|
||||
|
|
@ -4230,7 +4317,10 @@ impl Methods {
|
|||
("as_mut", []) => useless_asref::check(cx, expr, "as_mut", 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, &self.msrv),
|
||||
("cloned", []) => {
|
||||
cloned_instead_of_copied::check(cx, expr, recv, span, &self.msrv);
|
||||
option_as_ref_cloned::check(cx, recv, span);
|
||||
},
|
||||
("collect", []) if is_trait_method(cx, expr, sym::Iterator) => {
|
||||
needless_collect::check(cx, span, expr, recv, call_span);
|
||||
match method_call(recv) {
|
||||
|
|
@ -4569,6 +4659,9 @@ impl Methods {
|
|||
("sort_unstable_by", [arg]) => {
|
||||
unnecessary_sort_by::check(cx, expr, recv, arg, true);
|
||||
},
|
||||
("split", [arg]) => {
|
||||
str_split::check(cx, expr, recv, arg);
|
||||
},
|
||||
("splitn" | "rsplitn", [count_arg, pat_arg]) => {
|
||||
if let Some(Constant::Int(count)) = constant(cx, cx.typeck_results(), count_arg) {
|
||||
suspicious_splitn::check(cx, name, expr, recv, count);
|
||||
|
|
@ -4664,7 +4757,13 @@ impl Methods {
|
|||
}
|
||||
unnecessary_literal_unwrap::check(cx, expr, recv, name, args);
|
||||
},
|
||||
("unwrap_or_default" | "unwrap_unchecked" | "unwrap_err_unchecked", []) => {
|
||||
("unwrap_or_default", []) => {
|
||||
if let Some(("map", m_recv, [arg], span, _)) = method_call(recv) {
|
||||
manual_is_variant_and::check(cx, expr, m_recv, arg, span, &self.msrv);
|
||||
}
|
||||
unnecessary_literal_unwrap::check(cx, expr, recv, name, args);
|
||||
},
|
||||
("unwrap_unchecked" | "unwrap_err_unchecked", []) => {
|
||||
unnecessary_literal_unwrap::check(cx, expr, recv, name, args);
|
||||
},
|
||||
("unwrap_or_else", [u_arg]) => {
|
||||
|
|
|
|||
24
clippy_lints/src/methods/option_as_ref_cloned.rs
Normal file
24
clippy_lints/src/methods/option_as_ref_cloned.rs
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
use clippy_utils::diagnostics::span_lint_and_sugg;
|
||||
use clippy_utils::ty::is_type_diagnostic_item;
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::Expr;
|
||||
use rustc_lint::LateContext;
|
||||
use rustc_span::{sym, Span};
|
||||
|
||||
use super::{method_call, OPTION_AS_REF_CLONED};
|
||||
|
||||
pub(super) fn check(cx: &LateContext<'_>, cloned_recv: &Expr<'_>, cloned_ident_span: Span) {
|
||||
if let Some((method @ ("as_ref" | "as_mut"), as_ref_recv, [], as_ref_ident_span, _)) = method_call(cloned_recv)
|
||||
&& is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(as_ref_recv).peel_refs(), sym::Option)
|
||||
{
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
OPTION_AS_REF_CLONED,
|
||||
as_ref_ident_span.to(cloned_ident_span),
|
||||
&format!("cloning an `Option<_>` using `.{method}().cloned()`"),
|
||||
"this can be written more concisely by cloning the `Option<_>` directly",
|
||||
"clone".into(),
|
||||
Applicability::MachineApplicable,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -72,7 +72,7 @@ pub(super) fn check<'tcx>(
|
|||
}
|
||||
|
||||
// is_some_and is stabilised && `unwrap_or` argument is false; suggest `is_some_and` instead
|
||||
let suggest_is_some_and = msrv.meets(msrvs::OPTION_IS_SOME_AND)
|
||||
let suggest_is_some_and = msrv.meets(msrvs::OPTION_RESULT_IS_VARIANT_AND)
|
||||
&& matches!(&unwrap_arg.kind, ExprKind::Lit(lit)
|
||||
if matches!(lit.node, rustc_ast::LitKind::Bool(false)));
|
||||
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ pub(super) fn check(
|
|||
&& path.chars().all(char::is_alphanumeric)
|
||||
{
|
||||
let mut sugg = snippet(cx, recv.span, "..").into_owned();
|
||||
if msrv.meets(msrvs::OPTION_IS_SOME_AND) {
|
||||
if msrv.meets(msrvs::OPTION_RESULT_IS_VARIANT_AND) {
|
||||
let _ = write!(sugg, r#".extension().is_some_and(|ext| ext == "{path}")"#);
|
||||
} else {
|
||||
let _ = write!(sugg, r#".extension().map_or(false, |ext| ext == "{path}")"#);
|
||||
|
|
|
|||
|
|
@ -13,7 +13,11 @@ pub(super) fn check(
|
|||
as_str_span: Span,
|
||||
other_method_span: Span,
|
||||
) {
|
||||
if cx.typeck_results().expr_ty(recv).ty_adt_def().is_some_and(|adt| Some(adt.did()) == cx.tcx.lang_items().string())
|
||||
if cx
|
||||
.typeck_results()
|
||||
.expr_ty(recv)
|
||||
.ty_adt_def()
|
||||
.is_some_and(|adt| Some(adt.did()) == cx.tcx.lang_items().string())
|
||||
{
|
||||
let mut applicability = Applicability::MachineApplicable;
|
||||
span_lint_and_sugg(
|
||||
|
|
|
|||
38
clippy_lints/src/methods/str_split.rs
Normal file
38
clippy_lints/src/methods/str_split.rs
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
use clippy_utils::diagnostics::span_lint_and_sugg;
|
||||
use clippy_utils::source::snippet_with_context;
|
||||
use clippy_utils::visitors::is_const_evaluatable;
|
||||
use rustc_ast::ast::LitKind;
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::{Expr, ExprKind};
|
||||
use rustc_lint::LateContext;
|
||||
|
||||
use super::STR_SPLIT_AT_NEWLINE;
|
||||
|
||||
pub(super) fn check<'a>(cx: &LateContext<'a>, expr: &'_ Expr<'_>, split_recv: &'a Expr<'_>, split_arg: &'_ Expr<'_>) {
|
||||
// We're looking for `A.trim().split(B)`, where the adjusted type of `A` is `&str` (e.g. an
|
||||
// expression returning `String`), and `B` is a `Pattern` that hard-codes a newline (either `"\n"`
|
||||
// or `"\r\n"`). There are a lot of ways to specify a pattern, and this lint only checks the most
|
||||
// basic ones: a `'\n'`, `"\n"`, and `"\r\n"`.
|
||||
if let ExprKind::MethodCall(trim_method_name, trim_recv, [], _) = split_recv.kind
|
||||
&& trim_method_name.ident.as_str() == "trim"
|
||||
&& cx.typeck_results().expr_ty_adjusted(trim_recv).peel_refs().is_str()
|
||||
&& !is_const_evaluatable(cx, trim_recv)
|
||||
&& let ExprKind::Lit(split_lit) = split_arg.kind
|
||||
&& (matches!(split_lit.node, LitKind::Char('\n'))
|
||||
|| matches!(split_lit.node, LitKind::Str(sym, _) if (sym.as_str() == "\n" || sym.as_str() == "\r\n")))
|
||||
{
|
||||
let mut app = Applicability::MaybeIncorrect;
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
STR_SPLIT_AT_NEWLINE,
|
||||
expr.span,
|
||||
"using `str.trim().split()` with hard-coded newlines",
|
||||
"use `str.lines()` instead",
|
||||
format!(
|
||||
"{}.lines()",
|
||||
snippet_with_context(cx, trim_recv.span, expr.span.ctxt(), "..", &mut app).0
|
||||
),
|
||||
app,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -5,7 +5,8 @@ use clippy_utils::ty::implements_trait;
|
|||
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;
|
||||
|
|
|
|||
|
|
@ -3,13 +3,13 @@ use super::unnecessary_iter_cloned::{self, is_into_iter};
|
|||
use clippy_config::msrvs::{self, Msrv};
|
||||
use clippy_utils::diagnostics::span_lint_and_sugg;
|
||||
use clippy_utils::source::snippet_opt;
|
||||
use clippy_utils::ty::{get_iterator_item_ty, implements_trait, is_copy, peel_mid_ty_refs};
|
||||
use clippy_utils::ty::{get_iterator_item_ty, implements_trait, is_copy, is_type_lang_item, peel_mid_ty_refs};
|
||||
use clippy_utils::visitors::find_all_ret_expressions;
|
||||
use clippy_utils::{fn_def_id, get_parent_expr, is_diag_item_method, is_diag_trait_item, return_ty};
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::def::{DefKind, Res};
|
||||
use rustc_hir::def_id::DefId;
|
||||
use rustc_hir::{BorrowKind, Expr, ExprKind, ItemKind, Node};
|
||||
use rustc_hir::{BorrowKind, Expr, ExprKind, ItemKind, LangItem, Node};
|
||||
use rustc_hir_typeck::{FnCtxt, Inherited};
|
||||
use rustc_infer::infer::TyCtxtInferExt;
|
||||
use rustc_lint::LateContext;
|
||||
|
|
@ -246,6 +246,19 @@ fn check_split_call_arg(cx: &LateContext<'_>, expr: &Expr<'_>, method_name: Symb
|
|||
&& let Some(receiver_snippet) = snippet_opt(cx, receiver.span)
|
||||
&& let Some(arg_snippet) = snippet_opt(cx, argument_expr.span)
|
||||
{
|
||||
// We may end-up here because of an expression like `x.to_string().split(…)` where the type of `x`
|
||||
// implements `AsRef<str>` but does not implement `Deref<Target = str>`. In this case, we have to
|
||||
// add `.as_ref()` to the suggestion.
|
||||
let as_ref = if is_type_lang_item(cx, cx.typeck_results().expr_ty(expr), LangItem::String)
|
||||
&& let Some(deref_trait_id) = cx.tcx.get_diagnostic_item(sym::Deref)
|
||||
&& cx.get_associated_type(cx.typeck_results().expr_ty(receiver), deref_trait_id, "Target")
|
||||
!= Some(cx.tcx.types.str_)
|
||||
{
|
||||
".as_ref()"
|
||||
} else {
|
||||
""
|
||||
};
|
||||
|
||||
// The next suggestion may be incorrect because the removal of the `to_owned`-like
|
||||
// function could cause the iterator to hold a reference to a resource that is used
|
||||
// mutably. See https://github.com/rust-lang/rust-clippy/issues/8148.
|
||||
|
|
@ -255,7 +268,7 @@ fn check_split_call_arg(cx: &LateContext<'_>, expr: &Expr<'_>, method_name: Symb
|
|||
parent.span,
|
||||
&format!("unnecessary use of `{method_name}`"),
|
||||
"use",
|
||||
format!("{receiver_snippet}.split({arg_snippet})"),
|
||||
format!("{receiver_snippet}{as_ref}.split({arg_snippet})"),
|
||||
Applicability::MaybeIncorrect,
|
||||
);
|
||||
return true;
|
||||
|
|
@ -445,7 +458,10 @@ fn can_change_type<'a>(cx: &LateContext<'a>, mut expr: &'a Expr<'a>, mut ty: Ty<
|
|||
{
|
||||
let bound_fn_sig = cx.tcx.fn_sig(callee_def_id);
|
||||
let fn_sig = bound_fn_sig.skip_binder();
|
||||
if let Some(arg_index) = recv.into_iter().chain(call_args).position(|arg| arg.hir_id == expr.hir_id)
|
||||
if let Some(arg_index) = recv
|
||||
.into_iter()
|
||||
.chain(call_args)
|
||||
.position(|arg| arg.hir_id == expr.hir_id)
|
||||
&& let param_ty = fn_sig.input(arg_index).skip_binder()
|
||||
&& let ty::Param(ParamTy { index: param_index , ..}) = *param_ty.kind()
|
||||
// https://github.com/rust-lang/rust-clippy/issues/9504 and https://github.com/rust-lang/rust-clippy/issues/10021
|
||||
|
|
|
|||
|
|
@ -1,19 +1,52 @@
|
|||
use clippy_utils::diagnostics::span_lint_and_sugg;
|
||||
use clippy_utils::source::snippet_with_applicability;
|
||||
use clippy_utils::ty::walk_ptrs_ty_depth;
|
||||
use clippy_utils::{get_parent_expr, is_trait_method};
|
||||
use clippy_utils::{get_parent_expr, is_diag_trait_item, match_def_path, paths, peel_blocks};
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir as hir;
|
||||
use rustc_lint::LateContext;
|
||||
use rustc_span::sym;
|
||||
use rustc_middle::ty::adjustment::Adjust;
|
||||
use rustc_middle::ty::{Ty, TyCtxt, TypeSuperVisitable, TypeVisitable, TypeVisitor};
|
||||
use rustc_span::{sym, Span};
|
||||
|
||||
use core::ops::ControlFlow;
|
||||
|
||||
use super::USELESS_ASREF;
|
||||
|
||||
/// Returns the first type inside the `Option`/`Result` type passed as argument.
|
||||
fn get_enum_ty(enum_ty: Ty<'_>) -> Option<Ty<'_>> {
|
||||
struct ContainsTyVisitor {
|
||||
level: usize,
|
||||
}
|
||||
|
||||
impl<'tcx> TypeVisitor<TyCtxt<'tcx>> for ContainsTyVisitor {
|
||||
type BreakTy = Ty<'tcx>;
|
||||
|
||||
fn visit_ty(&mut self, t: Ty<'tcx>) -> ControlFlow<Self::BreakTy> {
|
||||
self.level += 1;
|
||||
if self.level == 1 {
|
||||
t.super_visit_with(self)
|
||||
} else {
|
||||
ControlFlow::Break(t)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
match enum_ty.visit_with(&mut ContainsTyVisitor { level: 0 }) {
|
||||
ControlFlow::Break(ty) => Some(ty),
|
||||
ControlFlow::Continue(()) => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Checks for the `USELESS_ASREF` lint.
|
||||
pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, call_name: &str, recvr: &hir::Expr<'_>) {
|
||||
// when we get here, we've already checked that the call name is "as_ref" or "as_mut"
|
||||
// check if the call is to the actual `AsRef` or `AsMut` trait
|
||||
if is_trait_method(cx, expr, sym::AsRef) || is_trait_method(cx, expr, sym::AsMut) {
|
||||
let Some(def_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id) else {
|
||||
return;
|
||||
};
|
||||
|
||||
if is_diag_trait_item(cx, def_id, sym::AsRef) || is_diag_trait_item(cx, def_id, sym::AsMut) {
|
||||
// check if the type after `as_ref` or `as_mut` is the same as before
|
||||
let rcv_ty = cx.typeck_results().expr_ty(recvr);
|
||||
let res_ty = cx.typeck_results().expr_ty(expr);
|
||||
|
|
@ -39,5 +72,89 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, call_name: &str,
|
|||
applicability,
|
||||
);
|
||||
}
|
||||
} else if match_def_path(cx, def_id, &["core", "option", "Option", call_name])
|
||||
|| match_def_path(cx, def_id, &["core", "result", "Result", call_name])
|
||||
{
|
||||
let rcv_ty = cx.typeck_results().expr_ty(recvr).peel_refs();
|
||||
let res_ty = cx.typeck_results().expr_ty(expr).peel_refs();
|
||||
|
||||
if let Some(rcv_ty) = get_enum_ty(rcv_ty)
|
||||
&& let Some(res_ty) = get_enum_ty(res_ty)
|
||||
// If the only thing the `as_mut`/`as_ref` call is doing is adding references and not
|
||||
// changing the type, then we can move forward.
|
||||
&& rcv_ty.peel_refs() == res_ty.peel_refs()
|
||||
&& let Some(parent) = get_parent_expr(cx, expr)
|
||||
&& let hir::ExprKind::MethodCall(segment, _, args, _) = parent.kind
|
||||
&& segment.ident.span != expr.span
|
||||
// We check that the called method name is `map`.
|
||||
&& segment.ident.name == sym::map
|
||||
// And that it only has one argument.
|
||||
&& let [arg] = args
|
||||
&& is_calling_clone(cx, arg)
|
||||
{
|
||||
lint_as_ref_clone(cx, expr.span.with_hi(parent.span.hi()), recvr, call_name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn check_qpath(cx: &LateContext<'_>, qpath: hir::QPath<'_>, hir_id: hir::HirId) -> bool {
|
||||
// We check it's calling the `clone` method of the `Clone` trait.
|
||||
if let Some(path_def_id) = cx.qpath_res(&qpath, hir_id).opt_def_id() {
|
||||
match_def_path(cx, path_def_id, &paths::CLONE_TRAIT_METHOD)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
fn is_calling_clone(cx: &LateContext<'_>, arg: &hir::Expr<'_>) -> bool {
|
||||
match arg.kind {
|
||||
hir::ExprKind::Closure(&hir::Closure { body, .. }) => {
|
||||
// If it's a closure, we need to check what is called.
|
||||
let closure_body = cx.tcx.hir().body(body);
|
||||
let closure_expr = peel_blocks(closure_body.value);
|
||||
match closure_expr.kind {
|
||||
hir::ExprKind::MethodCall(method, obj, [], _) => {
|
||||
if method.ident.name == sym::clone
|
||||
&& let Some(fn_id) = cx.typeck_results().type_dependent_def_id(closure_expr.hir_id)
|
||||
&& let Some(trait_id) = cx.tcx.trait_of_item(fn_id)
|
||||
// We check it's the `Clone` trait.
|
||||
&& cx.tcx.lang_items().clone_trait().map_or(false, |id| id == trait_id)
|
||||
// no autoderefs
|
||||
&& !cx.typeck_results().expr_adjustments(obj).iter()
|
||||
.any(|a| matches!(a.kind, Adjust::Deref(Some(..))))
|
||||
{
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
},
|
||||
hir::ExprKind::Call(call, [_]) => {
|
||||
if let hir::ExprKind::Path(qpath) = call.kind {
|
||||
check_qpath(cx, qpath, call.hir_id)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
},
|
||||
_ => false,
|
||||
}
|
||||
},
|
||||
hir::ExprKind::Path(qpath) => check_qpath(cx, qpath, arg.hir_id),
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
fn lint_as_ref_clone(cx: &LateContext<'_>, span: Span, recvr: &hir::Expr<'_>, call_name: &str) {
|
||||
let mut applicability = Applicability::MachineApplicable;
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
USELESS_ASREF,
|
||||
span,
|
||||
&format!("this call to `{call_name}.map(...)` does nothing"),
|
||||
"try",
|
||||
format!(
|
||||
"{}.clone()",
|
||||
snippet_with_applicability(cx, recvr.span, "..", &mut applicability)
|
||||
),
|
||||
applicability,
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -74,6 +74,7 @@ pub(super) fn get_hint_if_single_char_arg(
|
|||
match ch {
|
||||
"'" => "\\'",
|
||||
r"\" => "\\\\",
|
||||
"\\\"" => "\"", // no need to escape `"` in `'"'`
|
||||
_ => ch,
|
||||
}
|
||||
);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue