rust/src/entry.rs

113 lines
3.6 KiB
Rust
Raw Normal View History

2015-12-21 17:35:56 -06:00
use rustc::lint::*;
use rustc_front::hir::*;
use syntax::codemap::Span;
2016-01-13 10:17:19 -06:00
use utils::{get_item_name, is_exp_equal, match_type, snippet, span_lint_and_then, walk_ptrs_ty};
use utils::{BTREEMAP_PATH, HASHMAP_PATH};
2015-12-21 17:35:56 -06:00
/// **What it does:** This lint checks for uses of `contains_key` + `insert` on `HashMap` or
/// `BTreeMap`.
2015-12-21 17:35:56 -06:00
///
/// **Why is this bad?** Using `entry` is more efficient.
2015-12-21 17:35:56 -06:00
///
2016-01-03 09:31:28 -06:00
/// **Known problems:** Some false negatives, eg.:
/// ```
/// let k = &key;
/// if !m.contains_key(k) { m.insert(k.clone(), v); }
/// ```
2015-12-21 17:35:56 -06:00
///
/// **Example:**
/// ```rust
/// if !m.contains_key(&k) { m.insert(k, v) }
/// ```
/// can be rewritten as:
/// ```rust
/// m.entry(k).or_insert(v);
/// ```
2015-12-21 17:35:56 -06:00
declare_lint! {
pub MAP_ENTRY,
2015-12-21 17:35:56 -06:00
Warn,
"use of `contains_key` followed by `insert` on a `HashMap` or `BTreeMap`"
2015-12-21 17:35:56 -06:00
}
#[derive(Copy,Clone)]
pub struct HashMapLint;
impl LintPass for HashMapLint {
fn get_lints(&self) -> LintArray {
lint_array!(MAP_ENTRY)
2015-12-21 17:35:56 -06:00
}
}
impl LateLintPass for HashMapLint {
fn check_expr(&mut self, cx: &LateContext, expr: &Expr) {
if_let_chain! {
[
let ExprIf(ref check, ref then, _) = expr.node,
let ExprUnary(UnOp::UnNot, ref check) = check.node,
let ExprMethodCall(ref name, _, ref params) = check.node,
params.len() >= 2,
name.node.as_str() == "contains_key"
], {
2016-01-03 09:31:28 -06:00
let key = match params[1].node {
ExprAddrOf(_, ref key) => key,
_ => return
};
2015-12-21 17:35:56 -06:00
let map = &params[0];
let obj_ty = walk_ptrs_ty(cx.tcx.expr_ty(map));
let kind = if match_type(cx, obj_ty, &BTREEMAP_PATH) {
"BTreeMap"
}
else if match_type(cx, obj_ty, &HASHMAP_PATH) {
"HashMap"
}
else {
return
};
let sole_expr = if then.expr.is_some() { 1 } else { 0 } + then.stmts.len() == 1;
2016-01-03 09:31:28 -06:00
if let Some(ref then) = then.expr {
check_for_insert(cx, expr.span, map, key, then, sole_expr, kind);
}
for stmt in &then.stmts {
if let StmtSemi(ref stmt, _) = stmt.node {
check_for_insert(cx, expr.span, map, key, stmt, sole_expr, kind);
2015-12-21 17:35:56 -06:00
}
}
}
}
}
}
fn check_for_insert(cx: &LateContext, span: Span, map: &Expr, key: &Expr, expr: &Expr, sole_expr: bool, kind: &str) {
2015-12-21 17:35:56 -06:00
if_let_chain! {
[
let ExprMethodCall(ref name, _, ref params) = expr.node,
2016-01-03 09:31:28 -06:00
params.len() == 3,
2015-12-21 17:35:56 -06:00
name.node.as_str() == "insert",
2016-01-03 09:31:28 -06:00
get_item_name(cx, map) == get_item_name(cx, &*params[0]),
is_exp_equal(cx, key, &params[1])
2015-12-21 17:35:56 -06:00
], {
let help = if sole_expr {
2016-01-13 10:17:19 -06:00
format!("{}.entry({}).or_insert({})",
2016-01-29 15:19:14 -06:00
snippet(cx, map.span, "map"),
snippet(cx, params[1].span, ".."),
snippet(cx, params[2].span, ".."))
}
else {
2016-01-13 10:17:19 -06:00
format!("{}.entry({})",
2016-01-29 15:19:14 -06:00
snippet(cx, map.span, "map"),
snippet(cx, params[1].span, ".."))
};
2016-01-13 10:17:19 -06:00
span_lint_and_then(cx, MAP_ENTRY, span,
&format!("usage of `contains_key` followed by `insert` on `{}`", kind), |db| {
2016-01-29 15:19:14 -06:00
db.span_suggestion(span, "Consider using", help);
2016-01-13 10:17:19 -06:00
});
2015-12-21 17:35:56 -06:00
}
}
}