diff options
| author | Jason Newcomb <jsnewcomb@pm.me> | 2021-03-25 09:25:04 -0400 |
|---|---|---|
| committer | Jason Newcomb <jsnewcomb@pm.me> | 2021-04-15 08:19:40 -0400 |
| commit | ce5e927713ddd32096f4e73f7a2c339cc8163d82 (patch) | |
| tree | 45caf3b8751de82133aa642e9b77715249a5ea48 /clippy_utils/src | |
| parent | b1c675f3fc682201cdb28719133285b878e2d157 (diff) | |
| download | rust-ce5e927713ddd32096f4e73f7a2c339cc8163d82.tar.gz rust-ce5e927713ddd32096f4e73f7a2c339cc8163d82.zip | |
Improve `map_entry` lint
Fix false positives where the map is used before inserting into the map. Fix false positives where two insertions happen. Suggest using `if let Entry::Vacant(e) = _.entry(_)` when `or_insert` might be a semantic change
Diffstat (limited to 'clippy_utils/src')
| -rw-r--r-- | clippy_utils/src/lib.rs | 125 | ||||
| -rw-r--r-- | clippy_utils/src/paths.rs | 4 | ||||
| -rw-r--r-- | clippy_utils/src/source.rs | 9 |
3 files changed, 121 insertions, 17 deletions
diff --git a/clippy_utils/src/lib.rs b/clippy_utils/src/lib.rs index 8e1a2105b96..0abee49f40c 100644 --- a/clippy_utils/src/lib.rs +++ b/clippy_utils/src/lib.rs @@ -63,9 +63,9 @@ use rustc_hir::def_id::{DefId, LOCAL_CRATE}; use rustc_hir::intravisit::{self, NestedVisitorMap, Visitor}; use rustc_hir::LangItem::{ResultErr, ResultOk}; use rustc_hir::{ - def, Arm, BindingAnnotation, Block, Body, Constness, Expr, ExprKind, FnDecl, GenericArgs, HirId, Impl, ImplItem, - ImplItemKind, Item, ItemKind, LangItem, MatchSource, Node, Param, Pat, PatKind, Path, PathSegment, QPath, - TraitItem, TraitItemKind, TraitRef, TyKind, + def, Arm, BindingAnnotation, Block, Body, Constness, Destination, Expr, ExprKind, FnDecl, GenericArgs, HirId, Impl, + ImplItem, ImplItemKind, Item, ItemKind, LangItem, MatchSource, Node, Param, Pat, PatKind, Path, PathSegment, QPath, + Stmt, StmtKind, TraitItem, TraitItemKind, TraitRef, TyKind, }; use rustc_lint::{LateContext, Level, Lint, LintContext}; use rustc_middle::hir::exports::Export; @@ -1245,6 +1245,82 @@ pub fn is_must_use_func_call(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { did.map_or(false, |did| must_use_attr(&cx.tcx.get_attrs(did)).is_some()) } +pub fn get_expr_use_node(tcx: TyCtxt<'tcx>, expr: &Expr<'_>) -> Option<Node<'tcx>> { + let map = tcx.hir(); + let mut child_id = expr.hir_id; + let mut iter = map.parent_iter(child_id); + loop { + match iter.next() { + None => break None, + Some((id, Node::Block(_))) => child_id = id, + Some((id, Node::Arm(arm))) if arm.body.hir_id == child_id => child_id = id, + Some((_, Node::Expr(expr))) => match expr.kind { + ExprKind::Break( + Destination { + target_id: Ok(dest), .. + }, + _, + ) => { + iter = map.parent_iter(dest); + child_id = dest; + }, + ExprKind::DropTemps(_) | ExprKind::Block(..) => child_id = expr.hir_id, + ExprKind::If(control_expr, ..) | ExprKind::Match(control_expr, ..) + if control_expr.hir_id != child_id => + { + child_id = expr.hir_id + }, + _ => break Some(Node::Expr(expr)), + }, + Some((_, node)) => break Some(node), + } + } +} + +pub fn is_expr_used(tcx: TyCtxt<'_>, expr: &Expr<'_>) -> bool { + !matches!( + get_expr_use_node(tcx, expr), + Some(Node::Stmt(Stmt { + kind: StmtKind::Expr(_) | StmtKind::Semi(_), + .. + })) + ) +} + +pub fn get_expr_use_or_unification_node(tcx: TyCtxt<'tcx>, expr: &Expr<'_>) -> Option<Node<'tcx>> { + let map = tcx.hir(); + let mut child_id = expr.hir_id; + let mut iter = map.parent_iter(child_id); + loop { + match iter.next() { + None => break None, + Some((id, Node::Block(_))) => child_id = id, + Some((id, Node::Arm(arm))) if arm.body.hir_id == child_id => child_id = id, + Some((_, Node::Expr(expr))) => match expr.kind { + ExprKind::Match(_, [arm], _) if arm.hir_id == child_id => child_id = expr.hir_id, + ExprKind::Block(..) | ExprKind::DropTemps(_) => child_id = expr.hir_id, + ExprKind::If(_, then_expr, None) if then_expr.hir_id == child_id => break None, + _ => break Some(Node::Expr(expr)), + }, + Some((_, node)) => break Some(node), + } + } +} + +pub fn is_expr_used_or_unified(tcx: TyCtxt<'_>, expr: &Expr<'_>) -> bool { + !matches!( + get_expr_use_or_unification_node(tcx, expr), + None | Some(Node::Stmt(Stmt { + kind: StmtKind::Expr(_) | StmtKind::Semi(_), + .. + })) + ) +} + +pub fn is_expr_final_block_expr(tcx: TyCtxt<'_>, expr: &Expr<'_>) -> bool { + matches!(get_parent_node(tcx, expr.hir_id), Some(Node::Block(..))) +} + pub fn is_no_std_crate(cx: &LateContext<'_>) -> bool { cx.tcx.hir().attrs(hir::CRATE_HIR_ID).iter().any(|attr| { if let ast::AttrKind::Normal(ref attr, _) = attr.kind { @@ -1414,28 +1490,43 @@ pub fn peel_hir_pat_refs(pat: &'a Pat<'a>) -> (&'a Pat<'a>, usize) { peel(pat, 0) } +/// Peels of expressions while the given closure returns `Some`. +pub fn peel_hir_expr_while<'tcx>( + mut expr: &'tcx Expr<'tcx>, + mut f: impl FnMut(&'tcx Expr<'tcx>) -> Option<&'tcx Expr<'tcx>>, +) -> &'tcx Expr<'tcx> { + while let Some(e) = f(expr) { + expr = e; + } + expr +} + /// Peels off up to the given number of references on the expression. Returns the underlying /// expression and the number of references removed. pub fn peel_n_hir_expr_refs(expr: &'a Expr<'a>, count: usize) -> (&'a Expr<'a>, usize) { - fn f(expr: &'a Expr<'a>, count: usize, target: usize) -> (&'a Expr<'a>, usize) { - match expr.kind { - ExprKind::AddrOf(_, _, expr) if count != target => f(expr, count + 1, target), - _ => (expr, count), - } - } - f(expr, 0, count) + let mut remaining = count; + let e = peel_hir_expr_while(expr, |e| match e.kind { + ExprKind::AddrOf(BorrowKind::Ref, _, e) if remaining != 0 => { + remaining -= 1; + Some(e) + }, + _ => None, + }); + (e, count - remaining) } /// Peels off all references on the expression. Returns the underlying expression and the number of /// references removed. pub fn peel_hir_expr_refs(expr: &'a Expr<'a>) -> (&'a Expr<'a>, usize) { - fn f(expr: &'a Expr<'a>, count: usize) -> (&'a Expr<'a>, usize) { - match expr.kind { - ExprKind::AddrOf(BorrowKind::Ref, _, expr) => f(expr, count + 1), - _ => (expr, count), - } - } - f(expr, 0) + let mut count = 0; + let e = peel_hir_expr_while(expr, |e| match e.kind { + ExprKind::AddrOf(BorrowKind::Ref, _, e) => { + count += 1; + Some(e) + }, + _ => None, + }); + (e, count) } #[macro_export] diff --git a/clippy_utils/src/paths.rs b/clippy_utils/src/paths.rs index ed8915f59e1..8066f6c223e 100644 --- a/clippy_utils/src/paths.rs +++ b/clippy_utils/src/paths.rs @@ -13,7 +13,9 @@ pub(super) const BEGIN_PANIC_FMT: [&str; 3] = ["std", "panicking", "begin_panic_ pub const BINARY_HEAP: [&str; 4] = ["alloc", "collections", "binary_heap", "BinaryHeap"]; pub const BORROW_TRAIT: [&str; 3] = ["core", "borrow", "Borrow"]; pub const BTREEMAP: [&str; 5] = ["alloc", "collections", "btree", "map", "BTreeMap"]; +pub const BTREEMAP_CONTAINS_KEY: [&str; 6] = ["alloc", "collections", "btree", "map", "BTreeMap", "contains_key"]; pub const BTREEMAP_ENTRY: [&str; 6] = ["alloc", "collections", "btree", "map", "entry", "Entry"]; +pub const BTREEMAP_INSERT: [&str; 6] = ["alloc", "collections", "btree", "map", "BTreeMap", "insert"]; pub const BTREESET: [&str; 5] = ["alloc", "collections", "btree", "set", "BTreeSet"]; pub const CLONE_TRAIT_METHOD: [&str; 4] = ["core", "clone", "Clone", "clone"]; pub const CMP_MAX: [&str; 3] = ["core", "cmp", "max"]; @@ -45,7 +47,9 @@ pub const FROM_ITERATOR: [&str; 5] = ["core", "iter", "traits", "collect", "From pub const FUTURE_FROM_GENERATOR: [&str; 3] = ["core", "future", "from_generator"]; pub const HASH: [&str; 3] = ["core", "hash", "Hash"]; pub const HASHMAP: [&str; 5] = ["std", "collections", "hash", "map", "HashMap"]; +pub const HASHMAP_CONTAINS_KEY: [&str; 6] = ["std", "collections", "hash", "map", "HashMap", "contains_key"]; pub const HASHMAP_ENTRY: [&str; 5] = ["std", "collections", "hash", "map", "Entry"]; +pub const HASHMAP_INSERT: [&str; 6] = ["std", "collections", "hash", "map", "HashMap", "insert"]; pub const HASHSET: [&str; 5] = ["std", "collections", "hash", "set", "HashSet"]; #[cfg(feature = "internal-lints")] pub const IDENT: [&str; 3] = ["rustc_span", "symbol", "Ident"]; diff --git a/clippy_utils/src/source.rs b/clippy_utils/src/source.rs index 2d794d48dc5..53180d1f9f5 100644 --- a/clippy_utils/src/source.rs +++ b/clippy_utils/src/source.rs @@ -66,6 +66,15 @@ pub fn indent_of<T: LintContext>(cx: &T, span: Span) -> Option<usize> { snippet_opt(cx, line_span(cx, span)).and_then(|snip| snip.find(|c: char| !c.is_whitespace())) } +/// Gets a snippet of the indentation of the line of a span +pub fn snippet_indent<T: LintContext>(cx: &T, span: Span) -> Option<String> { + snippet_opt(cx, line_span(cx, span)).map(|mut s| { + let len = s.len() - s.trim_start().len(); + s.truncate(len); + s + }) +} + // If the snippet is empty, it's an attribute that was inserted during macro // expansion and we want to ignore those, because they could come from external // sources that the user has no control over. |
