Factor out match_var

This commit is contained in:
Cameron Steffen 2021-06-09 09:17:34 -05:00
parent dd0246813f
commit b792bb301c
5 changed files with 92 additions and 144 deletions

View File

@ -1,15 +1,15 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::source::snippet_with_applicability;
use clippy_utils::ty::match_type;
use clippy_utils::{contains_name, get_pat_name, paths, single_segment_path};
use clippy_utils::visitors::LocalUsedVisitor;
use clippy_utils::{path_to_local_id, paths, peel_ref_operators, remove_blocks, strip_pat_refs};
use if_chain::if_chain;
use rustc_errors::Applicability;
use rustc_hir::{BinOpKind, BorrowKind, Expr, ExprKind, UnOp};
use rustc_hir::{BinOpKind, Expr, ExprKind, PatKind};
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::ty::{self, UintTy};
use rustc_session::{declare_lint_pass, declare_tool_lint};
use rustc_span::sym;
use rustc_span::Symbol;
declare_clippy_lint! {
/// **What it does:** Checks for naive byte counts
@ -38,42 +38,43 @@ declare_lint_pass!(ByteCount => [NAIVE_BYTECOUNT]);
impl<'tcx> LateLintPass<'tcx> for ByteCount {
fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) {
if_chain! {
if let ExprKind::MethodCall(count, _, count_args, _) = expr.kind;
if let ExprKind::MethodCall(count, _, [count_recv], _) = expr.kind;
if count.ident.name == sym!(count);
if count_args.len() == 1;
if let ExprKind::MethodCall(filter, _, filter_args, _) = count_args[0].kind;
if let ExprKind::MethodCall(filter, _, [filter_recv, filter_arg], _) = count_recv.kind;
if filter.ident.name == sym!(filter);
if filter_args.len() == 2;
if let ExprKind::Closure(_, _, body_id, _, _) = filter_args[1].kind;
if let ExprKind::Closure(_, _, body_id, _, _) = filter_arg.kind;
let body = cx.tcx.hir().body(body_id);
if body.params.len() == 1;
if let Some(argname) = get_pat_name(body.params[0].pat);
if let [param] = body.params;
if let PatKind::Binding(_, arg_id, _, _) = strip_pat_refs(param.pat).kind;
if let ExprKind::Binary(ref op, l, r) = body.value.kind;
if op.node == BinOpKind::Eq;
if match_type(cx,
cx.typeck_results().expr_ty(&filter_args[0]).peel_refs(),
cx.typeck_results().expr_ty(filter_recv).peel_refs(),
&paths::SLICE_ITER);
let operand_is_arg = |expr| {
let expr = peel_ref_operators(cx, remove_blocks(expr));
path_to_local_id(expr, arg_id)
};
let needle = if operand_is_arg(l) {
r
} else if operand_is_arg(r) {
l
} else {
return;
};
if ty::Uint(UintTy::U8) == *cx.typeck_results().expr_ty(needle).peel_refs().kind();
if !LocalUsedVisitor::new(cx, arg_id).check_expr(needle);
then {
let needle = match get_path_name(l) {
Some(name) if check_arg(name, argname, r) => r,
_ => match get_path_name(r) {
Some(name) if check_arg(name, argname, l) => l,
_ => { return; }
}
};
if ty::Uint(UintTy::U8) != *cx.typeck_results().expr_ty(needle).peel_refs().kind() {
return;
}
let haystack = if let ExprKind::MethodCall(path, _, args, _) =
filter_args[0].kind {
filter_recv.kind {
let p = path.ident.name;
if (p == sym::iter || p == sym!(iter_mut)) && args.len() == 1 {
&args[0]
} else {
&filter_args[0]
&filter_recv
}
} else {
&filter_args[0]
&filter_recv
};
let mut applicability = Applicability::MaybeIncorrect;
span_lint_and_sugg(
@ -91,24 +92,3 @@ impl<'tcx> LateLintPass<'tcx> for ByteCount {
};
}
}
fn check_arg(name: Symbol, arg: Symbol, needle: &Expr<'_>) -> bool {
name == arg && !contains_name(name, needle)
}
fn get_path_name(expr: &Expr<'_>) -> Option<Symbol> {
match expr.kind {
ExprKind::Box(e) | ExprKind::AddrOf(BorrowKind::Ref, _, e) | ExprKind::Unary(UnOp::Deref, e) => {
get_path_name(e)
},
ExprKind::Block(b, _) => {
if b.stmts.is_empty() {
b.expr.as_ref().and_then(|p| get_path_name(p))
} else {
None
}
},
ExprKind::Path(ref qpath) => single_segment_path(qpath).map(|ps| ps.ident.name),
_ => None,
}
}

View File

@ -1,11 +1,10 @@
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::visitors::LocalUsedVisitor;
use clippy_utils::{is_lang_ctor, path_to_local, SpanlessEq};
use clippy_utils::{is_lang_ctor, path_to_local, peel_ref_operators, SpanlessEq};
use if_chain::if_chain;
use rustc_hir::LangItem::OptionNone;
use rustc_hir::{Arm, Expr, ExprKind, Guard, HirId, Pat, PatKind, StmtKind, UnOp};
use rustc_hir::{Arm, Expr, ExprKind, Guard, HirId, Pat, PatKind, StmtKind};
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::ty::TypeckResults;
use rustc_session::{declare_lint_pass, declare_tool_lint};
use rustc_span::{MultiSpan, Span};
@ -73,7 +72,7 @@ fn check_arm<'tcx>(arm: &Arm<'tcx>, wild_outer_arm: &Arm<'tcx>, cx: &LateContext
if arms_inner.iter().all(|arm| arm.guard.is_none());
// match expression must be a local binding
// match <local> { .. }
if let Some(binding_id) = path_to_local(strip_ref_operators(expr_in, cx.typeck_results()));
if let Some(binding_id) = path_to_local(peel_ref_operators(cx, expr_in));
// one of the branches must be "wild-like"
if let Some(wild_inner_arm_idx) = arms_inner.iter().rposition(|arm_inner| arm_is_wild_like(cx, arm_inner));
let (wild_inner_arm, non_wild_inner_arm) =
@ -163,16 +162,3 @@ fn pat_contains_or(pat: &Pat<'_>) -> bool {
});
result
}
/// Removes `AddrOf` operators (`&`) or deref operators (`*`), but only if a reference type is
/// dereferenced. An overloaded deref such as `Vec` to slice would not be removed.
fn strip_ref_operators<'hir>(mut expr: &'hir Expr<'hir>, typeck_results: &TypeckResults<'_>) -> &'hir Expr<'hir> {
loop {
match expr.kind {
ExprKind::AddrOf(_, _, e) => expr = e,
ExprKind::Unary(UnOp::Deref, e) if typeck_results.expr_ty(e).is_ref() => expr = e,
_ => break,
}
}
expr
}

View File

@ -3,19 +3,17 @@ use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::source::{snippet_with_applicability, snippet_with_context};
use clippy_utils::ty::{is_type_diagnostic_item, peel_mid_ty_refs_is_mutable};
use clippy_utils::{
can_move_expr_to_closure, in_constant, is_allowed, is_else_clause, is_lang_ctor, match_var, peel_hir_expr_refs,
can_move_expr_to_closure, in_constant, is_allowed, is_else_clause, is_lang_ctor, path_to_local_id,
peel_hir_expr_refs,
};
use rustc_ast::util::parser::PREC_POSTFIX;
use rustc_errors::Applicability;
use rustc_hir::LangItem::{OptionNone, OptionSome};
use rustc_hir::{Arm, BindingAnnotation, Block, Expr, ExprKind, MatchSource, Mutability, Pat, PatKind};
use rustc_hir::{Arm, BindingAnnotation, Block, Expr, ExprKind, HirId, MatchSource, Mutability, Pat, PatKind};
use rustc_lint::{LateContext, LateLintPass, LintContext};
use rustc_middle::lint::in_external_macro;
use rustc_session::{declare_lint_pass, declare_tool_lint};
use rustc_span::{
symbol::{sym, Ident},
SyntaxContext,
};
use rustc_span::{sym, SyntaxContext};
declare_clippy_lint! {
/// **What it does:** Checks for usages of `match` which could be implemented using `map`
@ -141,13 +139,13 @@ impl LateLintPass<'_> for ManualMap {
scrutinee_str.into()
};
let body_str = if let PatKind::Binding(annotation, _, some_binding, None) = some_pat.kind {
match can_pass_as_func(cx, some_binding, some_expr) {
let body_str = if let PatKind::Binding(annotation, id, some_binding, None) = some_pat.kind {
match can_pass_as_func(cx, id, some_expr) {
Some(func) if func.span.ctxt() == some_expr.span.ctxt() => {
snippet_with_applicability(cx, func.span, "..", &mut app).into_owned()
},
_ => {
if match_var(some_expr, some_binding.name)
if path_to_local_id(some_expr, id)
&& !is_allowed(cx, MATCH_AS_REF, expr.hir_id)
&& binding_ref.is_some()
{
@ -199,10 +197,10 @@ impl LateLintPass<'_> for ManualMap {
// Checks whether the expression could be passed as a function, or whether a closure is needed.
// Returns the function to be passed to `map` if it exists.
fn can_pass_as_func(cx: &LateContext<'tcx>, binding: Ident, expr: &'tcx Expr<'_>) -> Option<&'tcx Expr<'tcx>> {
fn can_pass_as_func(cx: &LateContext<'tcx>, binding: HirId, expr: &'tcx Expr<'_>) -> Option<&'tcx Expr<'tcx>> {
match expr.kind {
ExprKind::Call(func, [arg])
if match_var(arg, binding.name) && cx.typeck_results().expr_adjustments(arg).is_empty() =>
if path_to_local_id(arg, binding) && cx.typeck_results().expr_adjustments(arg).is_empty() =>
{
Some(func)
},

View File

@ -72,7 +72,7 @@ use rustc_hir::LangItem::{ResultErr, ResultOk};
use rustc_hir::{
def, Arm, BindingAnnotation, Block, Body, Constness, Destination, Expr, ExprKind, FnDecl, GenericArgs, HirId, Impl,
ImplItem, ImplItemKind, IsAsync, Item, ItemKind, LangItem, Local, MatchSource, Node, Param, Pat, PatKind, Path,
PathSegment, QPath, Stmt, StmtKind, TraitItem, TraitItemKind, TraitRef, TyKind,
PathSegment, QPath, Stmt, StmtKind, TraitItem, TraitItemKind, TraitRef, TyKind, UnOp,
};
use rustc_lint::{LateContext, Level, Lint, LintContext};
use rustc_middle::hir::exports::Export;
@ -326,16 +326,6 @@ pub fn is_trait_method(cx: &LateContext<'_>, expr: &Expr<'_>, diag_item: Symbol)
.map_or(false, |did| is_diag_trait_item(cx, did, diag_item))
}
/// Checks if an expression references a variable of the given name.
pub fn match_var(expr: &Expr<'_>, var: Symbol) -> bool {
if let ExprKind::Path(QPath::Resolved(None, path)) = expr.kind {
if let [p] = path.segments {
return p.ident.name == var;
}
}
false
}
pub fn last_path_segment<'tcx>(path: &QPath<'tcx>) -> &'tcx PathSegment<'tcx> {
match *path {
QPath::Resolved(_, path) => path.segments.last().expect("A path must have at least one segment"),
@ -707,16 +697,6 @@ pub fn get_item_name(cx: &LateContext<'_>, expr: &Expr<'_>) -> Option<Symbol> {
}
}
/// Gets the name of a `Pat`, if any.
pub fn get_pat_name(pat: &Pat<'_>) -> Option<Symbol> {
match pat.kind {
PatKind::Binding(.., ref spname, _) => Some(spname.name),
PatKind::Path(ref qpath) => single_segment_path(qpath).map(|ps| ps.ident.name),
PatKind::Box(p) | PatKind::Ref(p, _) => get_pat_name(&*p),
_ => None,
}
}
pub struct ContainsName {
pub name: Symbol,
pub result: bool,
@ -1404,47 +1384,42 @@ pub fn is_must_use_func_call(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
/// Checks if an expression represents the identity function
/// Only examines closures and `std::convert::identity`
pub fn is_expr_identity_function(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
/// Returns true if the expression is a binding to the given pattern
fn is_expr_pat_binding(cx: &LateContext<'_>, expr: &Expr<'_>, pat: &Pat<'_>) -> bool {
if let PatKind::Binding(_, _, ident, _) = pat.kind {
if match_var(expr, ident.name) {
return !(cx.typeck_results().hir_owner == expr.hir_id.owner && is_adjusted(cx, expr));
}
}
false
}
/// Checks if a function's body represents the identity function. Looks for bodies of the form:
/// * `|x| x`
/// * `|x| return x`
/// * `|x| { return x }`
/// * `|x| { return x; }`
fn is_body_identity_function(cx: &LateContext<'_>, func: &Body<'_>) -> bool {
let body = remove_blocks(&func.value);
let value_pat = if let [value_param] = func.params {
value_param.pat
} else {
return false;
let id = if_chain! {
if let [param] = func.params;
if let PatKind::Binding(_, id, _, _) = param.pat.kind;
then {
id
} else {
return false;
}
};
match body.kind {
ExprKind::Path(QPath::Resolved(None, _)) => is_expr_pat_binding(cx, body, value_pat),
ExprKind::Ret(Some(ret_val)) => is_expr_pat_binding(cx, ret_val, value_pat),
ExprKind::Block(block, _) => {
if_chain! {
if let &[block_stmt] = &block.stmts;
if let StmtKind::Semi(expr) | StmtKind::Expr(expr) = block_stmt.kind;
if let ExprKind::Ret(Some(ret_val)) = expr.kind;
then {
is_expr_pat_binding(cx, ret_val, value_pat)
} else {
false
let mut expr = &func.value;
loop {
match expr.kind {
#[rustfmt::skip]
ExprKind::Block(&Block { stmts: [], expr: Some(e), .. }, _, )
| ExprKind::Ret(Some(e)) => expr = e,
#[rustfmt::skip]
ExprKind::Block(&Block { stmts: [stmt], expr: None, .. }, _) => {
if_chain! {
if let StmtKind::Semi(e) | StmtKind::Expr(e) = stmt.kind;
if let ExprKind::Ret(Some(ret_val)) = e.kind;
then {
expr = ret_val;
} else {
return false;
}
}
}
},
_ => false,
},
_ => return path_to_local_id(expr, id) && cx.typeck_results().expr_adjustments(expr).is_empty(),
}
}
}
@ -1710,6 +1685,19 @@ pub fn peel_hir_expr_refs(expr: &'a Expr<'a>) -> (&'a Expr<'a>, usize) {
(e, count)
}
/// Removes `AddrOf` operators (`&`) or deref operators (`*`), but only if a reference type is
/// dereferenced. An overloaded deref such as `Vec` to slice would not be removed.
pub fn peel_ref_operators<'hir>(cx: &LateContext<'_>, mut expr: &'hir Expr<'hir>) -> &'hir Expr<'hir> {
loop {
match expr.kind {
ExprKind::AddrOf(_, _, e) => expr = e,
ExprKind::Unary(UnOp::Deref, e) if cx.typeck_results().expr_ty(e).is_ref() => expr = e,
_ => break,
}
}
expr
}
#[macro_export]
macro_rules! unwrap_cargo_metadata {
($cx: ident, $lint: ident, $deps: expr) => {{

View File

@ -1,10 +1,10 @@
use crate::source::snippet;
use crate::{get_pat_name, match_var};
use crate::{path_to_local_id, strip_pat_refs};
use rustc_hir::intravisit::{walk_expr, NestedVisitorMap, Visitor};
use rustc_hir::{Body, BodyId, Expr, ExprKind, Param};
use rustc_hir::{Body, BodyId, Expr, ExprKind, HirId, PatKind};
use rustc_lint::LateContext;
use rustc_middle::hir::map::Map;
use rustc_span::{Span, Symbol};
use rustc_span::Span;
use std::borrow::Cow;
pub fn get_spans(
@ -14,10 +14,11 @@ pub fn get_spans(
replacements: &[(&'static str, &'static str)],
) -> Option<Vec<(Span, Cow<'static, str>)>> {
if let Some(body) = opt_body_id.map(|id| cx.tcx.hir().body(id)) {
get_binding_name(&body.params[idx]).map_or_else(
|| Some(vec![]),
|name| extract_clone_suggestions(cx, name, replacements, body),
)
if let PatKind::Binding(_, binding_id, _, _) = strip_pat_refs(body.params[idx].pat).kind {
extract_clone_suggestions(cx, binding_id, replacements, body)
} else {
Some(vec![])
}
} else {
Some(vec![])
}
@ -25,13 +26,13 @@ pub fn get_spans(
fn extract_clone_suggestions<'tcx>(
cx: &LateContext<'tcx>,
name: Symbol,
id: HirId,
replace: &[(&'static str, &'static str)],
body: &'tcx Body<'_>,
) -> Option<Vec<(Span, Cow<'static, str>)>> {
let mut visitor = PtrCloneVisitor {
cx,
name,
id,
replace,
spans: vec![],
abort: false,
@ -42,7 +43,7 @@ fn extract_clone_suggestions<'tcx>(
struct PtrCloneVisitor<'a, 'tcx> {
cx: &'a LateContext<'tcx>,
name: Symbol,
id: HirId,
replace: &'a [(&'static str, &'static str)],
spans: Vec<(Span, Cow<'static, str>)>,
abort: bool,
@ -55,16 +56,15 @@ impl<'a, 'tcx> Visitor<'tcx> for PtrCloneVisitor<'a, 'tcx> {
if self.abort {
return;
}
if let ExprKind::MethodCall(seg, _, args, _) = expr.kind {
if args.len() == 1 && match_var(&args[0], self.name) {
if let ExprKind::MethodCall(seg, _, [recv], _) = expr.kind {
if path_to_local_id(recv, self.id) {
if seg.ident.name.as_str() == "capacity" {
self.abort = true;
return;
}
for &(fn_name, suffix) in self.replace {
if seg.ident.name.as_str() == fn_name {
self.spans
.push((expr.span, snippet(self.cx, args[0].span, "_") + suffix));
self.spans.push((expr.span, snippet(self.cx, recv.span, "_") + suffix));
return;
}
}
@ -77,7 +77,3 @@ impl<'a, 'tcx> Visitor<'tcx> for PtrCloneVisitor<'a, 'tcx> {
NestedVisitorMap::None
}
}
fn get_binding_name(arg: &Param<'_>) -> Option<Symbol> {
get_pat_name(arg.pat)
}