X-Git-Url: https://git.lizzy.rs/?a=blobdiff_plain;f=clippy_utils%2Fsrc%2Flib.rs;h=4e21436143503142ef43a798b79ccba5c8e2cf3e;hb=f690ef6f5c0d6af58e8e908b8e08c09c4921ac35;hp=00d2098a021b6adb36a957e9d0ca3fee2fb4580c;hpb=452181c69d2a106d9d5a1262b69e68a765c4b3e3;p=rust.git diff --git a/clippy_utils/src/lib.rs b/clippy_utils/src/lib.rs index 00d2098a021..4e214361435 100644 --- a/clippy_utils/src/lib.rs +++ b/clippy_utils/src/lib.rs @@ -1,6 +1,7 @@ #![feature(box_patterns)] #![feature(in_band_lifetimes)] #![feature(iter_zip)] +#![feature(let_else)] #![feature(rustc_private)] #![feature(control_flow_enum)] #![recursion_limit = "512"] @@ -37,7 +38,6 @@ #[allow(clippy::module_name_repetitions)] pub mod ast_utils; pub mod attrs; -pub mod camel_case; pub mod comparisons; pub mod consts; pub mod diagnostics; @@ -50,6 +50,7 @@ pub mod ptr; pub mod qualify_min_const_fn; pub mod source; +pub mod str_utils; pub mod sugg; pub mod ty; pub mod usage; @@ -68,12 +69,14 @@ use rustc_hir::def::{DefKind, Res}; use rustc_hir::def_id::DefId; use rustc_hir::hir_id::{HirIdMap, HirIdSet}; -use rustc_hir::intravisit::{self, walk_expr, ErasedMap, FnKind, NestedVisitorMap, Visitor}; +use rustc_hir::intravisit::{walk_expr, ErasedMap, FnKind, NestedVisitorMap, Visitor}; +use rustc_hir::itemlikevisit::ItemLikeVisitor; use rustc_hir::LangItem::{OptionNone, ResultErr, ResultOk}; use rustc_hir::{ - def, Arm, BindingAnnotation, Block, Body, Constness, Destination, Expr, ExprKind, FnDecl, GenericArgs, HirId, Impl, - ImplItem, ImplItemKind, IsAsync, Item, ItemKind, LangItem, Local, MatchSource, Mutability, Node, Param, Pat, - PatKind, Path, PathSegment, PrimTy, QPath, Stmt, StmtKind, TraitItem, TraitItemKind, TraitRef, TyKind, UnOp, + def, Arm, BindingAnnotation, Block, BlockCheckMode, Body, Constness, Destination, Expr, ExprKind, FnDecl, + ForeignItem, GenericArgs, HirId, Impl, ImplItem, ImplItemKind, IsAsync, Item, ItemKind, LangItem, Local, + MatchSource, Mutability, Node, Param, Pat, PatKind, Path, PathSegment, PrimTy, QPath, Stmt, StmtKind, TraitItem, + TraitItemKind, TraitRef, TyKind, UnOp, }; use rustc_lint::{LateContext, Level, Lint, LintContext}; use rustc_middle::hir::exports::Export; @@ -94,6 +97,7 @@ use crate::consts::{constant, Constant}; use crate::ty::{can_partially_move_ty, is_copy, is_recursively_primitive_type}; +use crate::visitors::expr_visitor_no_bodies; pub fn parse_msrv(msrv: &str, sess: Option<&Session>, span: Option) -> Option { if let Ok(version) = RustcVersion::parse(msrv) { @@ -248,12 +252,6 @@ pub fn is_lang_ctor(cx: &LateContext<'_>, qpath: &QPath<'_>, lang_item: LangItem false } -/// Returns `true` if this `span` was expanded by any macro. -#[must_use] -pub fn in_macro(span: Span) -> bool { - span.from_expansion() && !matches!(span.ctxt().outer_expn_data().kind, ExpnKind::Desugaring(..)) -} - pub fn is_unit_expr(expr: &Expr<'_>) -> bool { matches!( expr.kind, @@ -710,7 +708,7 @@ pub fn is_default_equivalent(cx: &LateContext<'_>, e: &Expr<'_>) -> bool { /// Checks if the top level expression can be moved into a closure as is. /// Currently checks for: /// * Break/Continue outside the given loop HIR ids. -/// * Yield/Return statments. +/// * Yield/Return statements. /// * Inline assembly. /// * Usages of a field of a local where the type of the local can be partially moved. /// @@ -842,10 +840,13 @@ fn pat_capture_kind(cx: &LateContext<'_>, pat: &Pat<'_>) -> CaptureKind { let mut capture_expr_ty = e; for (parent_id, parent) in cx.tcx.hir().parent_iter(e.hir_id) { - if let [Adjustment { - kind: Adjust::Deref(_) | Adjust::Borrow(AutoBorrow::Ref(..)), - target, - }, ref adjust @ ..] = *cx + if let [ + Adjustment { + kind: Adjust::Deref(_) | Adjust::Borrow(AutoBorrow::Ref(..)), + target, + }, + ref adjust @ .., + ] = *cx .typeck_results() .adjustments() .get(child_id) @@ -1108,63 +1109,30 @@ pub fn contains_name(name: Symbol, expr: &Expr<'_>) -> bool { /// Returns `true` if `expr` contains a return expression pub fn contains_return(expr: &hir::Expr<'_>) -> bool { - struct RetCallFinder { - found: bool, - } - - impl<'tcx> hir::intravisit::Visitor<'tcx> for RetCallFinder { - type Map = Map<'tcx>; - - fn visit_expr(&mut self, expr: &'tcx hir::Expr<'_>) { - if self.found { - return; - } + let mut found = false; + expr_visitor_no_bodies(|expr| { + if !found { if let hir::ExprKind::Ret(..) = &expr.kind { - self.found = true; - } else { - hir::intravisit::walk_expr(self, expr); + found = true; } } - - fn nested_visit_map(&mut self) -> hir::intravisit::NestedVisitorMap { - hir::intravisit::NestedVisitorMap::None - } - } - - let mut visitor = RetCallFinder { found: false }; - visitor.visit_expr(expr); - visitor.found -} - -struct FindMacroCalls<'a, 'b> { - names: &'a [&'b str], - result: Vec, -} - -impl<'a, 'b, 'tcx> Visitor<'tcx> for FindMacroCalls<'a, 'b> { - type Map = Map<'tcx>; - - fn visit_expr(&mut self, expr: &'tcx Expr<'_>) { - if self.names.iter().any(|fun| is_expn_of(expr.span, fun).is_some()) { - self.result.push(expr.span); - } - // and check sub-expressions - intravisit::walk_expr(self, expr); - } - - fn nested_visit_map(&mut self) -> NestedVisitorMap { - NestedVisitorMap::None - } + !found + }) + .visit_expr(expr); + found } /// Finds calls of the specified macros in a function body. pub fn find_macro_calls(names: &[&str], body: &Body<'_>) -> Vec { - let mut fmc = FindMacroCalls { - names, - result: Vec::new(), - }; - fmc.visit_expr(&body.value); - fmc.result + let mut result = Vec::new(); + expr_visitor_no_bodies(|expr| { + if names.iter().any(|fun| is_expn_of(expr.span, fun).is_some()) { + result.push(expr.span); + } + true + }) + .visit_expr(&body.value); + result } /// Extends the span to the beginning of the spans line, incl. whitespaces. @@ -1230,9 +1198,7 @@ pub fn get_enclosing_loop_or_closure(tcx: TyCtxt<'tcx>, expr: &Expr<'_>) -> Opti for (_, node) in tcx.hir().parent_iter(expr.hir_id) { match node { Node::Expr( - e - @ - Expr { + e @ Expr { kind: ExprKind::Loop(..) | ExprKind::Closure(..), .. }, @@ -1258,6 +1224,70 @@ pub fn get_parent_as_impl(tcx: TyCtxt<'_>, id: HirId) -> Option<&Impl<'_>> { } } +/// Removes blocks around an expression, only if the block contains just one expression +/// and no statements. Unsafe blocks are not removed. +/// +/// Examples: +/// * `{}` -> `{}` +/// * `{ x }` -> `x` +/// * `{{ x }}` -> `x` +/// * `{ x; }` -> `{ x; }` +/// * `{ x; y }` -> `{ x; y }` +/// * `{ unsafe { x } }` -> `unsafe { x }` +pub fn peel_blocks<'a>(mut expr: &'a Expr<'a>) -> &'a Expr<'a> { + while let ExprKind::Block( + Block { + stmts: [], + expr: Some(inner), + rules: BlockCheckMode::DefaultBlock, + .. + }, + _, + ) = expr.kind + { + expr = inner; + } + expr +} + +/// Removes blocks around an expression, only if the block contains just one expression +/// or just one expression statement with a semicolon. Unsafe blocks are not removed. +/// +/// Examples: +/// * `{}` -> `{}` +/// * `{ x }` -> `x` +/// * `{ x; }` -> `x` +/// * `{{ x; }}` -> `x` +/// * `{ x; y }` -> `{ x; y }` +/// * `{ unsafe { x } }` -> `unsafe { x }` +pub fn peel_blocks_with_stmt<'a>(mut expr: &'a Expr<'a>) -> &'a Expr<'a> { + while let ExprKind::Block( + Block { + stmts: [], + expr: Some(inner), + rules: BlockCheckMode::DefaultBlock, + .. + } + | Block { + stmts: + [ + Stmt { + kind: StmtKind::Expr(inner) | StmtKind::Semi(inner), + .. + }, + ], + expr: None, + rules: BlockCheckMode::DefaultBlock, + .. + }, + _, + ) = expr.kind + { + expr = inner; + } + expr +} + /// Checks if the given expression is the else clause of either an `if` or `if let` expression. pub fn is_else_clause(tcx: TyCtxt<'_>, expr: &Expr<'_>) -> bool { let mut iter = tcx.hir().parent_iter(expr.hir_id); @@ -1436,21 +1466,7 @@ pub fn recurse_or_patterns<'tcx, F: FnMut(&'tcx Pat<'tcx>)>(pat: &'tcx Pat<'tcx> /// Checks for the `#[automatically_derived]` attribute all `#[derive]`d /// implementations have. pub fn is_automatically_derived(attrs: &[ast::Attribute]) -> bool { - attrs.iter().any(|attr| attr.has_name(sym::automatically_derived)) -} - -/// Remove blocks around an expression. -/// -/// 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(block, ..) = expr.kind { - match (block.stmts.is_empty(), block.expr.as_ref()) { - (true, Some(e)) => expr = e, - _ => break, - } - } - expr + has_attr(attrs, sym::automatically_derived) } pub fn is_self(slf: &Param<'_>) -> bool { @@ -1470,16 +1486,6 @@ pub fn is_self_ty(slf: &hir::Ty<'_>) -> bool { false } -/// Checks if a given type looks safe to be uninitialized. -pub fn is_uninit_ty_valid(cx: &LateContext<'_>, ty: Ty<'_>) -> bool { - match ty.kind() { - rustc_ty::Array(component, _) => is_uninit_ty_valid(cx, component), - rustc_ty::Tuple(types) => types.types().all(|ty| is_uninit_ty_valid(cx, ty)), - rustc_ty::Adt(adt, _) => match_def_path(cx, adt.did, &paths::MEM_MAYBEUNINIT), - _ => false, - } -} - pub fn iter_input_pats<'tcx>(decl: &FnDecl<'_>, body: &'tcx Body<'_>) -> impl Iterator> { (0..decl.inputs.len()).map(move |i| &body.params[i]) } @@ -1568,20 +1574,29 @@ pub fn clip(tcx: TyCtxt<'_>, u: u128, ity: rustc_ty::UintTy) -> u128 { (u << amt) >> amt } -pub fn any_parent_is_automatically_derived(tcx: TyCtxt<'_>, node: HirId) -> bool { +pub fn has_attr(attrs: &[ast::Attribute], symbol: Symbol) -> bool { + attrs.iter().any(|attr| attr.has_name(symbol)) +} + +pub fn any_parent_has_attr(tcx: TyCtxt<'_>, node: HirId, symbol: Symbol) -> bool { let map = &tcx.hir(); let mut prev_enclosing_node = None; let mut enclosing_node = node; while Some(enclosing_node) != prev_enclosing_node { - if is_automatically_derived(map.attrs(enclosing_node)) { + if has_attr(map.attrs(enclosing_node), symbol) { return true; } prev_enclosing_node = Some(enclosing_node); enclosing_node = map.get_parent_item(enclosing_node); } + false } +pub fn any_parent_is_automatically_derived(tcx: TyCtxt<'_>, node: HirId) -> bool { + any_parent_has_attr(tcx, node, sym::automatically_derived) +} + /// Matches a function call with the given path and returns the arguments. /// /// Usage: @@ -1632,6 +1647,14 @@ pub fn match_def_path<'tcx>(cx: &LateContext<'tcx>, did: DefId, syms: &[&str]) - syms.iter().map(|x| Symbol::intern(x)).eq(path.iter().copied()) } +/// Checks if the given `DefId` matches the `libc` item. +pub fn match_libc_symbol(cx: &LateContext<'_>, did: DefId, name: &str) -> bool { + let path = cx.get_def_path(did); + // libc is meant to be used as a flat list of names, but they're all actually defined in different + // modules based on the target platform. Ignore everything but crate name and the item name. + path.first().map_or(false, |s| s.as_str() == "libc") && path.last().map_or(false, |s| s.as_str() == name) +} + pub fn match_panic_call(cx: &LateContext<'_>, expr: &'tcx Expr<'_>) -> Option<&'tcx Expr<'tcx>> { if let ExprKind::Call(func, [arg]) = expr.kind { expr_path_res(cx, func) @@ -1649,7 +1672,6 @@ pub fn match_panic_def_id(cx: &LateContext<'_>, did: DefId) -> bool { did, &[ &paths::BEGIN_PANIC, - &paths::BEGIN_PANIC_FMT, &paths::PANIC_ANY, &paths::PANICKING_PANIC, &paths::PANICKING_PANIC_FMT, @@ -1701,10 +1723,12 @@ pub fn is_async_fn(kind: FnKind<'_>) -> bool { pub fn get_async_fn_body(tcx: TyCtxt<'tcx>, body: &Body<'_>) -> Option<&'tcx Expr<'tcx>> { if let ExprKind::Call( _, - &[Expr { - kind: ExprKind::Closure(_, _, body, _, _), - .. - }], + &[ + Expr { + kind: ExprKind::Closure(_, _, body, _, _), + .. + }, + ], ) = body.value.kind { if let ExprKind::Block( @@ -1843,6 +1867,16 @@ 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 std_or_core(cx: &LateContext<'_>) -> Option<&'static str> { + if !is_no_std_crate(cx) { + Some("std") + } else if !is_no_core_crate(cx) { + Some("core") + } else { + None + } +} + 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 { @@ -1853,6 +1887,16 @@ pub fn is_no_std_crate(cx: &LateContext<'_>) -> bool { }) } +pub fn is_no_core_crate(cx: &LateContext<'_>) -> bool { + cx.tcx.hir().attrs(hir::CRATE_HIR_ID).iter().any(|attr| { + if let ast::AttrKind::Normal(ref attr, _) = attr.kind { + attr.path == sym::no_core + } else { + false + } + }) +} + /// Check if parent of a hir node is a trait implementation block. /// For example, `f` in /// ```rust,ignore @@ -2074,16 +2118,72 @@ pub fn is_hir_ty_cfg_dependant(cx: &LateContext<'_>, ty: &hir::Ty<'_>) -> bool { false } -/// Checks whether item either has `test` attribute applied, or -/// is a module with `test` in its name. -pub fn is_test_module_or_function(tcx: TyCtxt<'_>, item: &Item<'_>) -> bool { - if let Some(def_id) = tcx.hir().opt_local_def_id(item.hir_id()) { - if tcx.has_attr(def_id.to_def_id(), sym::test) { - return true; +struct VisitConstTestStruct<'tcx> { + tcx: TyCtxt<'tcx>, + names: Vec, + found: bool, +} +impl<'hir> ItemLikeVisitor<'hir> for VisitConstTestStruct<'hir> { + fn visit_item(&mut self, item: &Item<'_>) { + if let ItemKind::Const(ty, _body) = item.kind { + if let TyKind::Path(QPath::Resolved(_, path)) = ty.kind { + // We could also check for the type name `test::TestDescAndFn` + // and the `#[rustc_test_marker]` attribute? + if let Res::Def(DefKind::Struct, _) = path.res { + let has_test_marker = self + .tcx + .hir() + .attrs(item.hir_id()) + .iter() + .any(|a| a.has_name(sym::rustc_test_marker)); + if has_test_marker && self.names.contains(&item.ident.name) { + self.found = true; + } + } + } } } + fn visit_trait_item(&mut self, _: &TraitItem<'_>) {} + fn visit_impl_item(&mut self, _: &ImplItem<'_>) {} + fn visit_foreign_item(&mut self, _: &ForeignItem<'_>) {} +} + +/// Checks if the function containing the given `HirId` is a `#[test]` function +/// +/// Note: If you use this function, please add a `#[test]` case in `tests/ui_test`. +pub fn is_in_test_function(tcx: TyCtxt<'_>, id: hir::HirId) -> bool { + let names: Vec<_> = tcx + .hir() + .parent_iter(id) + // Since you can nest functions we need to collect all until we leave + // function scope + .filter_map(|(_id, node)| { + if let Node::Item(item) = node { + if let ItemKind::Fn(_, _, _) = item.kind { + return Some(item.ident.name); + } + } + None + }) + .collect(); + let parent_mod = tcx.parent_module(id); + let mut vis = VisitConstTestStruct { + tcx, + names, + found: false, + }; + tcx.hir().visit_item_likes_in_module(parent_mod, &mut vis); + vis.found +} - matches!(item.kind, ItemKind::Mod(..)) && item.ident.name.as_str().split('_').any(|a| a == "test" || a == "tests") +/// Checks whether item either has `test` attribute applied, or +/// is a module with `test` in its name. +/// +/// Note: If you use this function, please add a `#[test]` case in `tests/ui_test`. +pub fn is_test_module_or_function(tcx: TyCtxt<'_>, item: &Item<'_>) -> bool { + is_in_test_function(tcx, item.hir_id()) + || matches!(item.kind, ItemKind::Mod(..)) + && item.ident.name.as_str().split('_').any(|a| a == "test" || a == "tests") } macro_rules! op_utils {