use crate::utils::sugg::Sugg;
use crate::utils::usage::{is_unused, mutated_variables};
use crate::utils::{
- get_enclosing_block, get_parent_expr, get_trait_def_id, has_iter_method, higher,
- implements_trait, is_integer_const, is_no_std_crate, is_refutable, is_type_diagnostic_item,
- last_path_segment, match_trait_method, match_type, match_var, multispan_sugg, qpath_res,
- snippet, snippet_opt, snippet_with_applicability, snippet_with_macro_callsite, span_lint,
- span_lint_and_help, span_lint_and_sugg, span_lint_and_then, sugg, SpanlessEq,
+ get_enclosing_block, get_parent_expr, get_trait_def_id, has_iter_method, higher, implements_trait,
+ is_integer_const, is_no_std_crate, is_refutable, is_type_diagnostic_item, last_path_segment, match_trait_method,
+ match_type, match_var, multispan_sugg, qpath_res, snippet, snippet_opt, snippet_with_applicability,
+ snippet_with_macro_callsite, span_lint, span_lint_and_help, span_lint_and_sugg, span_lint_and_then, sugg,
+ SpanlessEq,
};
use if_chain::if_chain;
use rustc_ast::ast;
start: Some(start),
end: Some(end),
limits,
- }) = higher::range(cx, arg)
+ }) = higher::range(arg)
{
// the var must be a single name
if let PatKind::Binding(_, canonical_id, _, _) = pat.kind {
}
}
-// Scans for the usage of the for loop pattern
-struct ForPatternVisitor<'a, 'tcx> {
- found_pattern: bool,
- // Pattern that we are searching for
- for_pattern: &'a Pat<'tcx>,
- cx: &'a LateContext<'tcx>,
-}
-
-impl<'a, 'tcx> Visitor<'tcx> for ForPatternVisitor<'a, 'tcx> {
- type Map = Map<'tcx>;
-
- fn visit_expr(&mut self, expr: &'tcx Expr<'_>) {
- // Recursively explore an expression until a ExprKind::Path is found
- match &expr.kind {
- ExprKind::Array(expr_list) | ExprKind::MethodCall(_, _, expr_list, _) | ExprKind::Tup(expr_list) => {
- for expr in *expr_list {
- self.visit_expr(expr)
- }
- },
- ExprKind::Binary(_, lhs_expr, rhs_expr) => {
- self.visit_expr(lhs_expr);
- self.visit_expr(rhs_expr);
- },
- ExprKind::Box(expr)
- | ExprKind::Unary(_, expr)
- | ExprKind::Cast(expr, _)
- | ExprKind::Type(expr, _)
- | ExprKind::AddrOf(_, _, expr)
- | ExprKind::Field(expr, _)
- | ExprKind::Struct(_, _, Some(expr)) => self.visit_expr(expr),
- _ => {
- // Exploration cannot continue ... calculate the hir_id of the current
- // expr assuming it is a Path
- if let Some(hir_id) = var_def_id(self.cx, &expr) {
- // Pattern is found
- if hir_id == self.for_pattern.hir_id {
- self.found_pattern = true;
- }
- // If the for loop pattern is a tuple, determine whether the current
- // expr is inside that tuple pattern
- if let PatKind::Tuple(pat_list, _) = &self.for_pattern.kind {
- let hir_id_list: Vec<HirId> = pat_list.iter().map(|p| p.hir_id).collect();
- if hir_id_list.contains(&hir_id) {
- self.found_pattern = true;
- }
- }
- }
- },
- }
- }
-
- // This is triggered by walk_expr() for the case of vec.push(pat)
- fn visit_qpath(&mut self, qpath: &'tcx QPath<'_>, _: HirId, _: Span) {
- if_chain! {
- if let QPath::Resolved(_, path) = qpath;
- if let Res::Local(hir_id) = &path.res;
- then {
- if *hir_id == self.for_pattern.hir_id{
- self.found_pattern = true;
- }
-
- if let PatKind::Tuple(pat_list, _) = &self.for_pattern.kind {
- let hir_id_list: Vec<HirId> = pat_list.iter().map(|p| p.hir_id).collect();
- if hir_id_list.contains(&hir_id) {
- self.found_pattern = true;
- }
- }
- }
- }
- }
-
- fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
- NestedVisitorMap::None
- }
-}
-
// Scans the body of the for loop and determines whether lint should be given
struct SameItemPushVisitor<'a, 'tcx> {
should_lint: bool,
body: &'tcx Expr<'_>,
_: &'tcx Expr<'_>,
) {
+ fn emit_lint(cx: &LateContext<'_>, vec: &Expr<'_>, pushed_item: &Expr<'_>) {
+ let vec_str = snippet_with_macro_callsite(cx, vec.span, "");
+ let item_str = snippet_with_macro_callsite(cx, pushed_item.span, "");
+
+ span_lint_and_help(
+ cx,
+ SAME_ITEM_PUSH,
+ vec.span,
+ "it looks like the same item is being pushed into this Vec",
+ None,
+ &format!(
+ "try using vec![{};SIZE] or {}.resize(NEW_SIZE, {})",
+ item_str, vec_str, item_str
+ ),
+ )
+ }
+
+ if !matches!(pat.kind, PatKind::Wild) {
+ return;
+ }
+
// Determine whether it is safe to lint the body
let mut same_item_push_visitor = SameItemPushVisitor {
should_lint: true,
walk_expr(&mut same_item_push_visitor, body);
if same_item_push_visitor.should_lint {
if let Some((vec, pushed_item)) = same_item_push_visitor.vec_push {
- // Make sure that the push does not involve possibly mutating values
- if mutated_variables(pushed_item, cx).map_or(false, |mutvars| mutvars.is_empty()) {
- // Walk through the expression being pushed and make sure that it
- // does not contain the for loop pattern
- let mut for_pat_visitor = ForPatternVisitor {
- found_pattern: false,
- for_pattern: pat,
- cx,
- };
- walk_expr(&mut for_pat_visitor, pushed_item);
-
- if !for_pat_visitor.found_pattern {
- let vec_str = snippet_with_macro_callsite(cx, vec.span, "");
- let item_str = snippet_with_macro_callsite(cx, pushed_item.span, "");
-
- span_lint_and_help(
- cx,
- SAME_ITEM_PUSH,
- vec.span,
- "it looks like the same item is being pushed into this Vec",
- None,
- &format!(
- "try using vec![{};SIZE] or {}.resize(NEW_SIZE, {})",
- item_str, vec_str, item_str
- ),
- )
+ let vec_ty = cx.typeck_results().expr_ty(vec);
+ let ty = vec_ty.walk().nth(1).unwrap().expect_ty();
+ if cx
+ .tcx
+ .lang_items()
+ .clone_trait()
+ .map_or(false, |id| implements_trait(cx, ty, id, &[]))
+ {
+ // Make sure that the push does not involve possibly mutating values
+ match pushed_item.kind {
+ ExprKind::Path(ref qpath) => {
+ match qpath_res(cx, qpath, pushed_item.hir_id) {
+ // immutable bindings that are initialized with literal or constant
+ Res::Local(hir_id) => {
+ if_chain! {
+ let node = cx.tcx.hir().get(hir_id);
+ if let Node::Binding(pat) = node;
+ if let PatKind::Binding(bind_ann, ..) = pat.kind;
+ if !matches!(bind_ann, BindingAnnotation::RefMut | BindingAnnotation::Mutable);
+ let parent_node = cx.tcx.hir().get_parent_node(hir_id);
+ if let Some(Node::Local(parent_let_expr)) = cx.tcx.hir().find(parent_node);
+ if let Some(init) = parent_let_expr.init;
+ then {
+ match init.kind {
+ // immutable bindings that are initialized with literal
+ ExprKind::Lit(..) => emit_lint(cx, vec, pushed_item),
+ // immutable bindings that are initialized with constant
+ ExprKind::Path(ref path) => {
+ if let Res::Def(DefKind::Const, ..) = qpath_res(cx, path, init.hir_id) {
+ emit_lint(cx, vec, pushed_item);
+ }
+ }
+ _ => {},
+ }
+ }
+ }
+ },
+ // constant
+ Res::Def(DefKind::Const, ..) => emit_lint(cx, vec, pushed_item),
+ _ => {},
+ }
+ },
+ ExprKind::Lit(..) => emit_lint(cx, vec, pushed_item),
+ _ => {},
}
}
}
start: Some(start),
ref end,
limits,
- }) = higher::range(cx, arg)
+ }) = higher::range(arg)
{
// the var must be a single name
if let PatKind::Binding(_, canonical_id, ident, _) = pat.kind {
start: Some(start),
end: Some(end),
..
- }) = higher::range(cx, arg)
+ }) = higher::range(arg)
{
let mut_ids = vec![check_for_mutability(cx, start), check_for_mutability(cx, end)];
if mut_ids[0].is_some() || mut_ids[1].is_some() {
match_type(cx, ty, &paths::BTREEMAP) ||
is_type_diagnostic_item(cx, ty, sym!(hashmap_type)) {
if method.ident.name == sym!(len) {
- let span = shorten_span(expr, sym!(collect));
+ let span = shorten_needless_collect_span(expr);
span_lint_and_sugg(
cx,
NEEDLESS_COLLECT,
);
}
if method.ident.name == sym!(is_empty) {
- let span = shorten_span(expr, sym!(iter));
+ let span = shorten_needless_collect_span(expr);
span_lint_and_sugg(
cx,
NEEDLESS_COLLECT,
span,
NEEDLESS_COLLECT_MSG,
"replace with",
- "get(0).is_none()".to_string(),
+ "next().is_none()".to_string(),
Applicability::MachineApplicable,
);
}
if method.ident.name == sym!(contains) {
let contains_arg = snippet(cx, args[1].span, "??");
- let span = shorten_span(expr, sym!(collect));
+ let span = shorten_needless_collect_span(expr);
span_lint_and_then(
cx,
NEEDLESS_COLLECT,
}
}
-fn shorten_span(expr: &Expr<'_>, target_fn_name: Symbol) -> Span {
- let mut current_expr = expr;
- while let ExprKind::MethodCall(ref path, ref span, ref args, _) = current_expr.kind {
- if path.ident.name == target_fn_name {
+fn shorten_needless_collect_span(expr: &Expr<'_>) -> Span {
+ if_chain! {
+ if let ExprKind::MethodCall(.., args, _) = &expr.kind;
+ if let ExprKind::MethodCall(_, span, ..) = &args[0].kind;
+ then {
return expr.span.with_lo(span.lo());
}
- current_expr = &args[0];
}
- unreachable!()
+ unreachable!();
}