Auto merge of #7333 - camsteffen:match-var, r=llogiq

Factor out match_var and get_pat_name utils

...because checking vars by name is bad, because of shadowing.

changelog: none
This commit is contained in:
bors 2021-06-10 05:20:56 +00:00
commit f7d09b45e9
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::diagnostics::span_lint_and_sugg;
use clippy_utils::source::snippet_with_applicability; use clippy_utils::source::snippet_with_applicability;
use clippy_utils::ty::match_type; 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 if_chain::if_chain;
use rustc_errors::Applicability; 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_lint::{LateContext, LateLintPass};
use rustc_middle::ty::{self, UintTy}; use rustc_middle::ty::{self, UintTy};
use rustc_session::{declare_lint_pass, declare_tool_lint}; use rustc_session::{declare_lint_pass, declare_tool_lint};
use rustc_span::sym; use rustc_span::sym;
use rustc_span::Symbol;
declare_clippy_lint! { declare_clippy_lint! {
/// **What it does:** Checks for naive byte counts /// **What it does:** Checks for naive byte counts
@ -38,42 +38,43 @@ declare_lint_pass!(ByteCount => [NAIVE_BYTECOUNT]);
impl<'tcx> LateLintPass<'tcx> for ByteCount { impl<'tcx> LateLintPass<'tcx> for ByteCount {
fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) { fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) {
if_chain! { 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.ident.name == sym!(count);
if count_args.len() == 1; if let ExprKind::MethodCall(filter, _, [filter_recv, filter_arg], _) = count_recv.kind;
if let ExprKind::MethodCall(filter, _, filter_args, _) = count_args[0].kind;
if filter.ident.name == sym!(filter); if filter.ident.name == sym!(filter);
if filter_args.len() == 2; if let ExprKind::Closure(_, _, body_id, _, _) = filter_arg.kind;
if let ExprKind::Closure(_, _, body_id, _, _) = filter_args[1].kind;
let body = cx.tcx.hir().body(body_id); let body = cx.tcx.hir().body(body_id);
if body.params.len() == 1; if let [param] = body.params;
if let Some(argname) = get_pat_name(body.params[0].pat); if let PatKind::Binding(_, arg_id, _, _) = strip_pat_refs(param.pat).kind;
if let ExprKind::Binary(ref op, l, r) = body.value.kind; if let ExprKind::Binary(ref op, l, r) = body.value.kind;
if op.node == BinOpKind::Eq; if op.node == BinOpKind::Eq;
if match_type(cx, 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); &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 { 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, _) = let haystack = if let ExprKind::MethodCall(path, _, args, _) =
filter_args[0].kind { filter_recv.kind {
let p = path.ident.name; let p = path.ident.name;
if (p == sym::iter || p == sym!(iter_mut)) && args.len() == 1 { if (p == sym::iter || p == sym!(iter_mut)) && args.len() == 1 {
&args[0] &args[0]
} else { } else {
&filter_args[0] &filter_recv
} }
} else { } else {
&filter_args[0] &filter_recv
}; };
let mut applicability = Applicability::MaybeIncorrect; let mut applicability = Applicability::MaybeIncorrect;
span_lint_and_sugg( 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::diagnostics::span_lint_and_then;
use clippy_utils::visitors::LocalUsedVisitor; 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 if_chain::if_chain;
use rustc_hir::LangItem::OptionNone; 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_lint::{LateContext, LateLintPass};
use rustc_middle::ty::TypeckResults;
use rustc_session::{declare_lint_pass, declare_tool_lint}; use rustc_session::{declare_lint_pass, declare_tool_lint};
use rustc_span::{MultiSpan, Span}; 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()); if arms_inner.iter().all(|arm| arm.guard.is_none());
// match expression must be a local binding // match expression must be a local binding
// match <local> { .. } // 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" // 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)); 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) = let (wild_inner_arm, non_wild_inner_arm) =
@ -163,16 +162,3 @@ fn pat_contains_or(pat: &Pat<'_>) -> bool {
}); });
result 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::source::{snippet_with_applicability, snippet_with_context};
use clippy_utils::ty::{is_type_diagnostic_item, peel_mid_ty_refs_is_mutable}; use clippy_utils::ty::{is_type_diagnostic_item, peel_mid_ty_refs_is_mutable};
use clippy_utils::{ 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_ast::util::parser::PREC_POSTFIX;
use rustc_errors::Applicability; use rustc_errors::Applicability;
use rustc_hir::LangItem::{OptionNone, OptionSome}; 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_lint::{LateContext, LateLintPass, LintContext};
use rustc_middle::lint::in_external_macro; use rustc_middle::lint::in_external_macro;
use rustc_session::{declare_lint_pass, declare_tool_lint}; use rustc_session::{declare_lint_pass, declare_tool_lint};
use rustc_span::{ use rustc_span::{sym, SyntaxContext};
symbol::{sym, Ident},
SyntaxContext,
};
declare_clippy_lint! { declare_clippy_lint! {
/// **What it does:** Checks for usages of `match` which could be implemented using `map` /// **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() scrutinee_str.into()
}; };
let body_str = if let PatKind::Binding(annotation, _, some_binding, None) = some_pat.kind { let body_str = if let PatKind::Binding(annotation, id, some_binding, None) = some_pat.kind {
match can_pass_as_func(cx, some_binding, some_expr) { match can_pass_as_func(cx, id, some_expr) {
Some(func) if func.span.ctxt() == some_expr.span.ctxt() => { Some(func) if func.span.ctxt() == some_expr.span.ctxt() => {
snippet_with_applicability(cx, func.span, "..", &mut app).into_owned() 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) && !is_allowed(cx, MATCH_AS_REF, expr.hir_id)
&& binding_ref.is_some() && 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. // 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. // 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 { match expr.kind {
ExprKind::Call(func, [arg]) 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) Some(func)
}, },

View File

@ -72,7 +72,7 @@ use rustc_hir::LangItem::{ResultErr, ResultOk};
use rustc_hir::{ use rustc_hir::{
def, Arm, BindingAnnotation, Block, Body, Constness, Destination, Expr, ExprKind, FnDecl, GenericArgs, HirId, Impl, 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, 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_lint::{LateContext, Level, Lint, LintContext};
use rustc_middle::hir::exports::Export; 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)) .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> { pub fn last_path_segment<'tcx>(path: &QPath<'tcx>) -> &'tcx PathSegment<'tcx> {
match *path { match *path {
QPath::Resolved(_, path) => path.segments.last().expect("A path must have at least one segment"), 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 struct ContainsName {
pub name: Symbol, pub name: Symbol,
pub result: bool, 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 /// Checks if an expression represents the identity function
/// Only examines closures and `std::convert::identity` /// Only examines closures and `std::convert::identity`
pub fn is_expr_identity_function(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { 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: /// Checks if a function's body represents the identity function. Looks for bodies of the form:
/// * `|x| x` /// * `|x| x`
/// * `|x| return x` /// * `|x| return x`
/// * `|x| { return x }` /// * `|x| { return x }`
/// * `|x| { return x; }` /// * `|x| { return x; }`
fn is_body_identity_function(cx: &LateContext<'_>, func: &Body<'_>) -> bool { fn is_body_identity_function(cx: &LateContext<'_>, func: &Body<'_>) -> bool {
let body = remove_blocks(&func.value); let id = if_chain! {
if let [param] = func.params;
let value_pat = if let [value_param] = func.params { if let PatKind::Binding(_, id, _, _) = param.pat.kind;
value_param.pat then {
} else { id
return false; } else {
return false;
}
}; };
match body.kind { let mut expr = &func.value;
ExprKind::Path(QPath::Resolved(None, _)) => is_expr_pat_binding(cx, body, value_pat), loop {
ExprKind::Ret(Some(ret_val)) => is_expr_pat_binding(cx, ret_val, value_pat), match expr.kind {
ExprKind::Block(block, _) => { #[rustfmt::skip]
if_chain! { ExprKind::Block(&Block { stmts: [], expr: Some(e), .. }, _, )
if let &[block_stmt] = &block.stmts; | ExprKind::Ret(Some(e)) => expr = e,
if let StmtKind::Semi(expr) | StmtKind::Expr(expr) = block_stmt.kind; #[rustfmt::skip]
if let ExprKind::Ret(Some(ret_val)) = expr.kind; ExprKind::Block(&Block { stmts: [stmt], expr: None, .. }, _) => {
then { if_chain! {
is_expr_pat_binding(cx, ret_val, value_pat) if let StmtKind::Semi(e) | StmtKind::Expr(e) = stmt.kind;
} else { if let ExprKind::Ret(Some(ret_val)) = e.kind;
false then {
expr = ret_val;
} else {
return false;
}
} }
} },
}, _ => return path_to_local_id(expr, id) && cx.typeck_results().expr_adjustments(expr).is_empty(),
_ => false, }
} }
} }
@ -1710,6 +1685,19 @@ pub fn peel_hir_expr_refs(expr: &'a Expr<'a>) -> (&'a Expr<'a>, usize) {
(e, count) (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_export]
macro_rules! unwrap_cargo_metadata { macro_rules! unwrap_cargo_metadata {
($cx: ident, $lint: ident, $deps: expr) => {{ ($cx: ident, $lint: ident, $deps: expr) => {{

View File

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