X-Git-Url: https://git.lizzy.rs/?a=blobdiff_plain;f=clippy_utils%2Fsrc%2Flib.rs;h=769836aaf18ed6c54c3773c5c4e052e8528f96c5;hb=6d50cff45afeeca85a14f1284badf9fc4b5b7668;hp=4a523520a4350fda3411b6ea807950ab07904e75;hpb=f6c5d8d599070bacad800ac7014fa3a6f140eadc;p=rust.git diff --git a/clippy_utils/src/lib.rs b/clippy_utils/src/lib.rs index 4a523520a43..769836aaf18 100644 --- a/clippy_utils/src/lib.rs +++ b/clippy_utils/src/lib.rs @@ -3,7 +3,14 @@ #![feature(iter_zip)] #![feature(rustc_private)] #![recursion_limit = "512"] +#![cfg_attr(feature = "deny-warnings", deny(warnings))] #![allow(clippy::missing_errors_doc, clippy::missing_panics_doc, clippy::must_use_candidate)] +// warn on the same lints as `clippy_lints` +#![warn(trivial_casts, trivial_numeric_casts)] +// warn on lints, that are included in `rust-lang/rust`s bootstrap +#![warn(rust_2018_idioms, unused_lifetimes)] +// warn on rustc internal lints +#![warn(rustc::internal)] // FIXME: switch to something more ergonomic here, once available. // (Currently there is no way to opt into sysroot crates without `extern crate`.) @@ -13,7 +20,6 @@ extern crate rustc_data_structures; extern crate rustc_errors; extern crate rustc_hir; -extern crate rustc_hir_pretty; extern crate rustc_infer; extern crate rustc_lexer; extern crate rustc_lint; @@ -38,6 +44,7 @@ pub mod eager_or_lazy; pub mod higher; mod hir_utils; +pub mod msrvs; pub mod numeric_literal; pub mod paths; pub mod ptr; @@ -56,16 +63,16 @@ use if_chain::if_chain; use rustc_ast::ast::{self, Attribute, BorrowKind, LitKind}; -use rustc_data_structures::fx::FxHashMap; +use rustc_data_structures::unhash::UnhashMap; use rustc_hir as hir; use rustc_hir::def::{DefKind, Res}; -use rustc_hir::def_id::{DefId, LOCAL_CRATE}; -use rustc_hir::intravisit::{self, NestedVisitorMap, Visitor}; +use rustc_hir::def_id::DefId; +use rustc_hir::intravisit::{self, walk_expr, ErasedMap, FnKind, 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, IsAsync, Item, ItemKind, LangItem, Local, 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; @@ -82,7 +89,7 @@ use rustc_target::abi::Integer; use crate::consts::{constant, Constant}; -use crate::ty::is_recursively_primitive_type; +use crate::ty::{can_partially_move_ty, is_recursively_primitive_type}; pub fn parse_msrv(msrv: &str, sess: Option<&Session>, span: Option) -> Option { if let Ok(version) = RustcVersion::parse(msrv) { @@ -321,7 +328,7 @@ pub fn is_trait_method(cx: &LateContext<'_>, expr: &Expr<'_>, diag_item: Symbol) /// 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, ref path)) = expr.kind { + if let ExprKind::Path(QPath::Resolved(None, path)) = expr.kind { if let [p] = path.segments { return p.ident.name == var; } @@ -331,8 +338,8 @@ pub fn match_var(expr: &Expr<'_>, var: Symbol) -> bool { pub fn last_path_segment<'tcx>(path: &QPath<'tcx>) -> &'tcx PathSegment<'tcx> { match *path { - QPath::Resolved(_, ref path) => path.segments.last().expect("A path must have at least one segment"), - QPath::TypeRelative(_, ref seg) => seg, + QPath::Resolved(_, path) => path.segments.last().expect("A path must have at least one segment"), + QPath::TypeRelative(_, seg) => seg, QPath::LangItem(..) => panic!("last_path_segment: lang item has no path segments"), } } @@ -360,8 +367,8 @@ pub fn get_qpath_generic_tys(path: &QPath<'tcx>) -> impl Iterator(path: &QPath<'tcx>) -> Option<&'tcx PathSegment<'tcx>> { match *path { - QPath::Resolved(_, ref path) => path.segments.get(0), - QPath::TypeRelative(_, ref seg) => Some(seg), + QPath::Resolved(_, path) => path.segments.get(0), + QPath::TypeRelative(_, seg) => Some(seg), QPath::LangItem(..) => None, } } @@ -381,8 +388,8 @@ pub fn single_segment_path<'tcx>(path: &QPath<'tcx>) -> Option<&'tcx PathSegment /// ``` pub fn match_qpath(path: &QPath<'_>, segments: &[&str]) -> bool { match *path { - QPath::Resolved(_, ref path) => match_path(path, segments), - QPath::TypeRelative(ref ty, ref segment) => match ty.kind { + QPath::Resolved(_, path) => match_path(path, segments), + QPath::TypeRelative(ty, segment) => match ty.kind { TyKind::Path(ref inner_path) => { if let [prefix @ .., end] = segments { if match_qpath(inner_path, prefix) { @@ -450,7 +457,7 @@ pub fn match_path(path: &Path<'_>, segments: &[&str]) -> bool { /// If the expression is a path to a local, returns the canonical `HirId` of the local. pub fn path_to_local(expr: &Expr<'_>) -> Option { - if let ExprKind::Path(QPath::Resolved(None, ref path)) = expr.kind { + if let ExprKind::Path(QPath::Resolved(None, path)) = expr.kind { if let Res::Local(id) = path.res { return Some(id); } @@ -548,6 +555,73 @@ pub fn trait_ref_of_method<'tcx>(cx: &LateContext<'tcx>, hir_id: HirId) -> Optio None } +/// Checks if the top level expression can be moved into a closure as is. +pub fn can_move_expr_to_closure_no_visit(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, jump_targets: &[HirId]) -> bool { + match expr.kind { + ExprKind::Break(Destination { target_id: Ok(id), .. }, _) + | ExprKind::Continue(Destination { target_id: Ok(id), .. }) + if jump_targets.contains(&id) => + { + true + }, + ExprKind::Break(..) + | ExprKind::Continue(_) + | ExprKind::Ret(_) + | ExprKind::Yield(..) + | ExprKind::InlineAsm(_) + | ExprKind::LlvmInlineAsm(_) => false, + // Accessing a field of a local value can only be done if the type isn't + // partially moved. + ExprKind::Field(base_expr, _) + if matches!( + base_expr.kind, + ExprKind::Path(QPath::Resolved(_, Path { res: Res::Local(_), .. })) + ) && can_partially_move_ty(cx, cx.typeck_results().expr_ty(base_expr)) => + { + // TODO: check if the local has been partially moved. Assume it has for now. + false + } + _ => true, + } +} + +/// Checks if the expression can be moved into a closure as is. +pub fn can_move_expr_to_closure(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> bool { + struct V<'cx, 'tcx> { + cx: &'cx LateContext<'tcx>, + loops: Vec, + allow_closure: bool, + } + impl Visitor<'tcx> for V<'_, 'tcx> { + type Map = ErasedMap<'tcx>; + fn nested_visit_map(&mut self) -> NestedVisitorMap { + NestedVisitorMap::None + } + + fn visit_expr(&mut self, e: &'tcx Expr<'_>) { + if !self.allow_closure { + return; + } + if let ExprKind::Loop(b, ..) = e.kind { + self.loops.push(e.hir_id); + self.visit_block(b); + self.loops.pop(); + } else { + self.allow_closure &= can_move_expr_to_closure_no_visit(self.cx, e, &self.loops); + walk_expr(self, e); + } + } + } + + let mut v = V { + cx, + allow_closure: true, + loops: Vec::new(), + }; + v.visit_expr(expr); + v.allow_closure +} + /// Returns the method names and argument list of nested method call expressions that make up /// `expr`. method/span lists are sorted with the most recent call first. pub fn method_calls<'tcx>( @@ -587,13 +661,13 @@ pub fn method_chain_args<'a>(expr: &'a Expr<'_>, methods: &[&str]) -> Option first - if let ExprKind::MethodCall(ref path, _, ref args, _) = current.kind { + if let ExprKind::MethodCall(path, _, args, _) = current.kind { if path.ident.name.as_str() == *method_name { if args.iter().any(|e| e.span.from_expansion()) { return None; } - matched.push(&**args); // build up `matched` backwards - current = &args[0] // go to parent expression + matched.push(args); // build up `matched` backwards + current = &args[0]; // go to parent expression } else { return None; } @@ -609,8 +683,8 @@ pub fn method_chain_args<'a>(expr: &'a Expr<'_>, methods: &[&str]) -> Option, def_id: DefId) -> bool { cx.tcx - .entry_fn(LOCAL_CRATE) - .map_or(false, |(entry_fn_def_id, _)| def_id == entry_fn_def_id.to_def_id()) + .entry_fn(()) + .map_or(false, |(entry_fn_def_id, _)| def_id == entry_fn_def_id) } /// Returns `true` if the expression is in the program's `#[panic_handler]`. @@ -638,7 +712,7 @@ pub fn get_pat_name(pat: &Pat<'_>) -> Option { 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(ref p) | PatKind::Ref(ref p, _) => get_pat_name(&*p), + PatKind::Box(p) | PatKind::Ref(p, _) => get_pat_name(&*p), _ => None, } } @@ -753,7 +827,13 @@ pub fn get_parent_node(tcx: TyCtxt<'_>, id: HirId) -> Option> { /// Gets the parent expression, if any –- this is useful to constrain a lint. pub fn get_parent_expr<'tcx>(cx: &LateContext<'tcx>, e: &Expr<'_>) -> Option<&'tcx Expr<'tcx>> { - match get_parent_node(cx.tcx, e.hir_id) { + get_parent_expr_for_hir(cx, e.hir_id) +} + +/// This retrieves the parent for the given `HirId` if it's an expression. This is useful for +/// constraint lints +pub fn get_parent_expr_for_hir<'tcx>(cx: &LateContext<'tcx>, hir_id: hir::HirId) -> Option<&'tcx Expr<'tcx>> { + match get_parent_node(cx.tcx, hir_id) { Some(Node::Expr(parent)) => Some(parent), _ => None, } @@ -774,13 +854,31 @@ pub fn get_enclosing_block<'tcx>(cx: &LateContext<'tcx>, hir_id: HirId) -> Optio kind: ImplItemKind::Fn(_, eid), .. }) => match cx.tcx.hir().body(eid).value.kind { - ExprKind::Block(ref block, _) => Some(block), + ExprKind::Block(block, _) => Some(block), _ => None, }, _ => None, }) } +/// Gets the loop enclosing the given expression, if any. +pub fn get_enclosing_loop(tcx: TyCtxt<'tcx>, expr: &Expr<'_>) -> Option<&'tcx Expr<'tcx>> { + let map = tcx.hir(); + for (_, node) in map.parent_iter(expr.hir_id) { + match node { + Node::Expr( + e @ Expr { + kind: ExprKind::Loop(..), + .. + }, + ) => return Some(e), + Node::Expr(_) | Node::Stmt(_) | Node::Block(_) | Node::Local(_) | Node::Arm(_) => (), + _ => break, + } + } + None +} + /// Gets the parent node if it's an impl block. pub fn get_parent_as_impl(tcx: TyCtxt<'_>, id: HirId) -> Option<&Impl<'_>> { let map = tcx.hir(); @@ -873,7 +971,12 @@ pub fn is_expn_of(mut span: Span, name: &str) -> Option { let data = span.ctxt().outer_expn_data(); let new_span = data.call_site; - if let ExpnKind::Macro(MacroKind::Bang, mac_name) = data.kind { + if let ExpnKind::Macro { + kind: MacroKind::Bang, + name: mac_name, + proc_macro: _, + } = data.kind + { if mac_name.as_str() == name { return Some(new_span); } @@ -901,7 +1004,12 @@ pub fn is_direct_expn_of(span: Span, name: &str) -> Option { let data = span.ctxt().outer_expn_data(); let new_span = data.call_site; - if let ExpnKind::Macro(MacroKind::Bang, mac_name) = data.kind { + if let ExpnKind::Macro { + kind: MacroKind::Bang, + name: mac_name, + proc_macro: _, + } = data.kind + { if mac_name.as_str() == name { return Some(new_span); } @@ -920,7 +1028,7 @@ pub fn return_ty<'tcx>(cx: &LateContext<'tcx>, fn_item: hir::HirId) -> Ty<'tcx> /// Checks if an expression is constructing a tuple-like enum variant or struct pub fn is_ctor_or_promotable_const_function(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { - if let ExprKind::Call(ref fun, _) = expr.kind { + if let ExprKind::Call(fun, _) = expr.kind { if let ExprKind::Path(ref qp) = fun.kind { let res = cx.qpath_res(qp, fun.hir_id); return match res { @@ -950,21 +1058,21 @@ fn are_refutable<'a, I: Iterator>>(cx: &LateContext<'_>, mut match pat.kind { PatKind::Wild => false, PatKind::Binding(_, _, _, pat) => pat.map_or(false, |pat| is_refutable(cx, pat)), - PatKind::Box(ref pat) | PatKind::Ref(ref pat, _) => is_refutable(cx, pat), + PatKind::Box(pat) | PatKind::Ref(pat, _) => is_refutable(cx, pat), PatKind::Lit(..) | PatKind::Range(..) => true, PatKind::Path(ref qpath) => is_enum_variant(cx, qpath, pat.hir_id), - PatKind::Or(ref pats) => { + PatKind::Or(pats) => { // TODO: should be the honest check, that pats is exhaustive set are_refutable(cx, pats.iter().map(|pat| &**pat)) }, - PatKind::Tuple(ref pats, _) => are_refutable(cx, pats.iter().map(|pat| &**pat)), - PatKind::Struct(ref qpath, ref fields, _) => { + PatKind::Tuple(pats, _) => are_refutable(cx, pats.iter().map(|pat| &**pat)), + PatKind::Struct(ref qpath, fields, _) => { is_enum_variant(cx, qpath, pat.hir_id) || are_refutable(cx, fields.iter().map(|field| &*field.pat)) }, - PatKind::TupleStruct(ref qpath, ref pats, _) => { + PatKind::TupleStruct(ref qpath, pats, _) => { is_enum_variant(cx, qpath, pat.hir_id) || are_refutable(cx, pats.iter().map(|pat| &**pat)) }, - PatKind::Slice(ref head, ref middle, ref tail) => { + PatKind::Slice(head, ref middle, tail) => { match &cx.typeck_results().node_type(pat.hir_id).kind() { rustc_ty::Slice(..) => { // [..] is the only irrefutable slice pattern. @@ -986,9 +1094,9 @@ fn are_refutable<'a, I: Iterator>>(cx: &LateContext<'_>, mut /// the function once on the given pattern. pub fn recurse_or_patterns<'tcx, F: FnMut(&'tcx Pat<'tcx>)>(pat: &'tcx Pat<'tcx>, mut f: F) { if let PatKind::Or(pats) = pat.kind { - pats.iter().cloned().for_each(f) + pats.iter().copied().for_each(f); } else { - f(pat) + f(pat); } } @@ -1003,7 +1111,7 @@ pub fn is_automatically_derived(attrs: &[ast::Attribute]) -> bool { /// Ie. `x`, `{ x }` and `{{{{ x }}}}` all give `x`. `{ x; y }` and `{}` return /// themselves. pub fn remove_blocks<'tcx>(mut expr: &'tcx Expr<'tcx>) -> &'tcx Expr<'tcx> { - while let ExprKind::Block(ref block, ..) = expr.kind { + while let ExprKind::Block(block, ..) = expr.kind { match (block.stmts.is_empty(), block.expr.as_ref()) { (true, Some(e)) => expr = e, _ => break, @@ -1022,7 +1130,7 @@ pub fn is_self(slf: &Param<'_>) -> bool { pub fn is_self_ty(slf: &hir::Ty<'_>) -> bool { if_chain! { - if let TyKind::Path(QPath::Resolved(None, ref path)) = slf.kind; + if let TyKind::Path(QPath::Resolved(None, path)) = slf.kind; if let Res::SelfTy(..) = path.res; then { return true @@ -1040,7 +1148,7 @@ pub fn iter_input_pats<'tcx>(decl: &FnDecl<'_>, body: &'tcx Body<'_>) -> impl It pub fn is_try<'tcx>(cx: &LateContext<'_>, expr: &'tcx Expr<'tcx>) -> Option<&'tcx Expr<'tcx>> { fn is_ok(cx: &LateContext<'_>, arm: &Arm<'_>) -> bool { if_chain! { - if let PatKind::TupleStruct(ref path, ref pat, None) = arm.pat.kind; + if let PatKind::TupleStruct(ref path, pat, None) = arm.pat.kind; if is_lang_ctor(cx, path, ResultOk); if let PatKind::Binding(_, hir_id, _, None) = pat[0].kind; if path_to_local_id(arm.body, hir_id); @@ -1059,7 +1167,7 @@ fn is_err(cx: &LateContext<'_>, arm: &Arm<'_>) -> bool { } } - if let ExprKind::Match(_, ref arms, ref source) = expr.kind { + if let ExprKind::Match(_, arms, ref source) = expr.kind { // desugared from a `?` operator if let MatchSource::TryDesugar = *source { return Some(expr); @@ -1146,12 +1254,12 @@ pub fn match_function_call<'tcx>( path: &[&str], ) -> Option<&'tcx [Expr<'tcx>]> { if_chain! { - if let ExprKind::Call(ref fun, ref args) = expr.kind; + if let ExprKind::Call(fun, args) = expr.kind; if let ExprKind::Path(ref qpath) = fun.kind; if let Some(fun_def_id) = cx.qpath_res(qpath, fun.hir_id).opt_def_id(); if match_def_path(cx, fun_def_id, path); then { - return Some(&args) + return Some(args) } }; None @@ -1163,14 +1271,14 @@ pub fn match_any_def_paths(cx: &LateContext<'_>, did: DefId, paths: &[&[&str]]) let search_path = cx.get_def_path(did); paths .iter() - .position(|p| p.iter().map(|x| Symbol::intern(x)).eq(search_path.iter().cloned())) + .position(|p| p.iter().map(|x| Symbol::intern(x)).eq(search_path.iter().copied())) } /// Checks if the given `DefId` matches the path. pub fn match_def_path<'tcx>(cx: &LateContext<'tcx>, did: DefId, syms: &[&str]) -> bool { // We should probably move to Symbols in Clippy as well rather than interning every time. let path = cx.get_def_path(did); - syms.iter().map(|x| Symbol::intern(x)).eq(path.iter().cloned()) + syms.iter().map(|x| Symbol::intern(x)).eq(path.iter().copied()) } pub fn match_panic_call(cx: &LateContext<'_>, expr: &'tcx Expr<'_>) -> Option<&'tcx Expr<'tcx>> { @@ -1208,15 +1316,15 @@ pub fn if_sequence<'tcx>(mut expr: &'tcx Expr<'tcx>) -> (Vec<&'tcx Expr<'tcx>>, let mut conds = Vec::new(); let mut blocks: Vec<&Block<'_>> = Vec::new(); - while let ExprKind::If(ref cond, ref then_expr, ref else_expr) = expr.kind { - conds.push(&**cond); - if let ExprKind::Block(ref block, _) = then_expr.kind { + while let ExprKind::If(cond, then_expr, ref else_expr) = expr.kind { + conds.push(cond); + if let ExprKind::Block(block, _) = then_expr.kind { blocks.push(block); } else { panic!("ExprKind::If node is not an ExprKind::Block"); } - if let Some(ref else_expr) = *else_expr { + if let Some(else_expr) = *else_expr { expr = else_expr; } else { break; @@ -1225,27 +1333,46 @@ pub fn if_sequence<'tcx>(mut expr: &'tcx Expr<'tcx>) -> (Vec<&'tcx Expr<'tcx>>, // final `else {..}` if !blocks.is_empty() { - if let ExprKind::Block(ref block, _) = expr.kind { - blocks.push(&**block); + if let ExprKind::Block(block, _) = expr.kind { + blocks.push(block); } } (conds, blocks) } -/// This function returns true if the given expression is the `else` or `if else` part of an if -/// statement -pub fn parent_node_is_if_expr(expr: &Expr<'_>, cx: &LateContext<'_>) -> bool { - let map = cx.tcx.hir(); - let parent_id = map.get_parent_node(expr.hir_id); - let parent_node = map.get(parent_id); - matches!( - parent_node, - Node::Expr(Expr { - kind: ExprKind::If(_, _, _), +/// Checks if the given function kind is an async function. +pub fn is_async_fn(kind: FnKind<'_>) -> bool { + matches!(kind, FnKind::ItemFn(_, _, header, _) if header.asyncness == IsAsync::Async) +} + +/// Peels away all the compiler generated code surrounding the body of an async function, +pub fn get_async_fn_body(tcx: TyCtxt<'tcx>, body: &Body<'_>) -> Option<&'tcx Expr<'tcx>> { + if let ExprKind::Call( + _, + &[Expr { + kind: ExprKind::Closure(_, _, body, _, _), .. - }) - ) + }], + ) = body.value.kind + { + if let ExprKind::Block( + Block { + stmts: [], + expr: + Some(Expr { + kind: ExprKind::DropTemps(expr), + .. + }), + .. + }, + _, + ) = tcx.hir().body(body).value.kind + { + return Some(expr); + } + }; + None } // Finds the `#[must_use]` attribute, if any @@ -1256,7 +1383,7 @@ pub fn must_use_attr(attrs: &[Attribute]) -> Option<&Attribute> { // check if expr is calling method or function with #[must_use] attribute pub fn is_must_use_func_call(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { let did = match expr.kind { - ExprKind::Call(ref path, _) => if_chain! { + ExprKind::Call(path, _) => if_chain! { if let ExprKind::Path(ref qpath) = path.kind; if let def::Res::Def(_, did) = cx.qpath_res(qpath, path.hir_id); then { @@ -1269,7 +1396,52 @@ pub fn is_must_use_func_call(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { _ => None, }; - did.map_or(false, |did| must_use_attr(&cx.tcx.get_attrs(did)).is_some()) + did.map_or(false, |did| must_use_attr(cx.tcx.get_attrs(did)).is_some()) +} + +/// Gets the node where an expression is either used, or it's type is unified with another branch. +pub fn get_expr_use_or_unification_node(tcx: TyCtxt<'tcx>, expr: &Expr<'_>) -> Option> { + 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), + } + } +} + +/// Checks if the result of an expression is used, or it's type is unified with another branch. +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(_) + | StmtKind::Local(Local { + pat: Pat { + kind: PatKind::Wild, + .. + }, + .. + }), + .. + })) + ) +} + +/// Checks if the expression is the final expression returned from a block. +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 { @@ -1400,14 +1572,16 @@ pub fn search_same(exprs: &[T], hash: Hash, eq: Eq) -> Vec<(&T, &T) Hash: Fn(&T) -> u64, Eq: Fn(&T, &T) -> bool, { - if exprs.len() == 2 && eq(&exprs[0], &exprs[1]) { - return vec![(&exprs[0], &exprs[1])]; + match exprs { + [a, b] if eq(a, b) => return vec![(a, b)], + _ if exprs.len() <= 2 => return vec![], + _ => {}, } let mut match_expr_list: Vec<(&T, &T)> = Vec::new(); - let mut map: FxHashMap<_, Vec<&_>> = - FxHashMap::with_capacity_and_hasher(exprs.len(), BuildHasherDefault::default()); + let mut map: UnhashMap> = + UnhashMap::with_capacity_and_hasher(exprs.len(), BuildHasherDefault::default()); for expr in exprs { match map.entry(hash(expr)) { @@ -1441,28 +1615,43 @@ fn peel(pat: &'a Pat<'a>, count: usize) -> (&'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]