diff options
| author | bors <bors@rust-lang.org> | 2021-09-08 23:52:31 +0000 |
|---|---|---|
| committer | bors <bors@rust-lang.org> | 2021-09-08 23:52:31 +0000 |
| commit | 626649ff1f33e89e471fff9e90bbb0a6d30141f9 (patch) | |
| tree | 4d2ce60078481404426f21d6a833f4bcdb0884b9 /src/tools/clippy/clippy_utils | |
| parent | 97032a6dfacdd3548e4bff98c90a6b3875a14077 (diff) | |
| parent | fe247b4df7fcf22c99fad1f6f6af28bd1c088c73 (diff) | |
| download | rust-626649ff1f33e89e471fff9e90bbb0a6d30141f9.tar.gz rust-626649ff1f33e89e471fff9e90bbb0a6d30141f9.zip | |
Auto merge of #88615 - flip1995:clippyup, r=Manishearth
Update Clippy r? `@Manishearth`
Diffstat (limited to 'src/tools/clippy/clippy_utils')
| -rw-r--r-- | src/tools/clippy/clippy_utils/Cargo.toml | 6 | ||||
| -rw-r--r-- | src/tools/clippy/clippy_utils/src/diagnostics.rs | 2 | ||||
| -rw-r--r-- | src/tools/clippy/clippy_utils/src/higher.rs | 165 | ||||
| -rw-r--r-- | src/tools/clippy/clippy_utils/src/hir_utils.rs | 6 | ||||
| -rw-r--r-- | src/tools/clippy/clippy_utils/src/lib.rs | 357 | ||||
| -rw-r--r-- | src/tools/clippy/clippy_utils/src/msrvs.rs | 3 | ||||
| -rw-r--r-- | src/tools/clippy/clippy_utils/src/paths.rs | 1 | ||||
| -rw-r--r-- | src/tools/clippy/clippy_utils/src/qualify_min_const_fn.rs | 4 | ||||
| -rw-r--r-- | src/tools/clippy/clippy_utils/src/sugg.rs | 2 | ||||
| -rw-r--r-- | src/tools/clippy/clippy_utils/src/ty.rs | 9 | ||||
| -rw-r--r-- | src/tools/clippy/clippy_utils/src/usage.rs | 11 | ||||
| -rw-r--r-- | src/tools/clippy/clippy_utils/src/visitors.rs | 117 |
12 files changed, 519 insertions, 164 deletions
diff --git a/src/tools/clippy/clippy_utils/Cargo.toml b/src/tools/clippy/clippy_utils/Cargo.toml index c65b2958ec5..4c038a99795 100644 --- a/src/tools/clippy/clippy_utils/Cargo.toml +++ b/src/tools/clippy/clippy_utils/Cargo.toml @@ -1,15 +1,11 @@ [package] name = "clippy_utils" -version = "0.1.56" +version = "0.1.57" edition = "2018" publish = false [dependencies] if_chain = "1.0.0" -itertools = "0.9" -regex-syntax = "0.6" -serde = { version = "1.0", features = ["derive"] } -unicode-normalization = "0.1" rustc-semver="1.1.0" [features] diff --git a/src/tools/clippy/clippy_utils/src/diagnostics.rs b/src/tools/clippy/clippy_utils/src/diagnostics.rs index 71cfa196fc3..9302e5c21fa 100644 --- a/src/tools/clippy/clippy_utils/src/diagnostics.rs +++ b/src/tools/clippy/clippy_utils/src/diagnostics.rs @@ -21,7 +21,7 @@ fn docs_link(diag: &mut DiagnosticBuilder<'_>, lint: &'static Lint) { "for further information visit https://rust-lang.github.io/rust-clippy/{}/index.html#{}", &option_env!("RUST_RELEASE_NUM").map_or("master".to_string(), |n| { // extract just major + minor version and ignore patch versions - format!("rust-{}", n.rsplitn(2, '.').nth(1).unwrap()) + format!("rust-{}", n.rsplit_once('.').unwrap().1) }), lint )); diff --git a/src/tools/clippy/clippy_utils/src/higher.rs b/src/tools/clippy/clippy_utils/src/higher.rs index c06b894a738..05a4a014319 100644 --- a/src/tools/clippy/clippy_utils/src/higher.rs +++ b/src/tools/clippy/clippy_utils/src/higher.rs @@ -6,33 +6,38 @@ use crate::{is_expn_of, match_def_path, paths}; use if_chain::if_chain; use rustc_ast::ast::{self, LitKind}; use rustc_hir as hir; -use rustc_hir::{Block, BorrowKind, Expr, ExprKind, LoopSource, Node, Pat, StmtKind, UnOp}; +use rustc_hir::{Arm, Block, BorrowKind, Expr, ExprKind, LoopSource, MatchSource, Node, Pat, StmtKind, UnOp}; use rustc_lint::LateContext; use rustc_span::{sym, ExpnKind, Span, Symbol}; /// The essential nodes of a desugared for loop as well as the entire span: /// `for pat in arg { body }` becomes `(pat, arg, body)`. Return `(pat, arg, body, span)`. pub struct ForLoop<'tcx> { + /// `for` loop item pub pat: &'tcx hir::Pat<'tcx>, + /// `IntoIterator` argument pub arg: &'tcx hir::Expr<'tcx>, + /// `for` loop body pub body: &'tcx hir::Expr<'tcx>, + /// entire `for` loop span pub span: Span, } impl<'tcx> ForLoop<'tcx> { #[inline] + /// Parses a desugared `for` loop pub fn hir(expr: &Expr<'tcx>) -> Option<Self> { if_chain! { - if let hir::ExprKind::Match(ref iterexpr, ref arms, hir::MatchSource::ForLoopDesugar) = expr.kind; + if let hir::ExprKind::Match(iterexpr, arms, hir::MatchSource::ForLoopDesugar) = expr.kind; if let Some(first_arm) = arms.get(0); - if let hir::ExprKind::Call(_, ref iterargs) = iterexpr.kind; + if let hir::ExprKind::Call(_, iterargs) = iterexpr.kind; if let Some(first_arg) = iterargs.get(0); if iterargs.len() == 1 && arms.len() == 1 && first_arm.guard.is_none(); - if let hir::ExprKind::Loop(ref block, ..) = first_arm.body.kind; + if let hir::ExprKind::Loop(block, ..) = first_arm.body.kind; if block.expr.is_none(); if let [ _, _, ref let_stmt, ref body ] = *block.stmts; - if let hir::StmtKind::Local(ref local) = let_stmt.kind; - if let hir::StmtKind::Expr(ref body_expr) = body.kind; + if let hir::StmtKind::Local(local) = let_stmt.kind; + if let hir::StmtKind::Expr(body_expr) = body.kind; then { return Some(Self { pat: &*local.pat, @@ -46,14 +51,19 @@ impl<'tcx> ForLoop<'tcx> { } } +/// An `if` expression without `DropTemps` pub struct If<'hir> { + /// `if` condition pub cond: &'hir Expr<'hir>, - pub r#else: Option<&'hir Expr<'hir>>, + /// `if` then expression pub then: &'hir Expr<'hir>, + /// `else` expression + pub r#else: Option<&'hir Expr<'hir>>, } impl<'hir> If<'hir> { #[inline] + /// Parses an `if` expression pub const fn hir(expr: &Expr<'hir>) -> Option<Self> { if let ExprKind::If( Expr { @@ -64,21 +74,27 @@ impl<'hir> If<'hir> { r#else, ) = expr.kind { - Some(Self { cond, r#else, then }) + Some(Self { cond, then, r#else }) } else { None } } } +/// An `if let` expression pub struct IfLet<'hir> { + /// `if let` pattern pub let_pat: &'hir Pat<'hir>, + /// `if let` scrutinee pub let_expr: &'hir Expr<'hir>, + /// `if let` then expression pub if_then: &'hir Expr<'hir>, + /// `if let` else expression pub if_else: Option<&'hir Expr<'hir>>, } impl<'hir> IfLet<'hir> { + /// Parses an `if let` expression pub fn hir(cx: &LateContext<'_>, expr: &Expr<'hir>) -> Option<Self> { if let ExprKind::If( Expr { @@ -92,7 +108,14 @@ impl<'hir> IfLet<'hir> { let hir = cx.tcx.hir(); let mut iter = hir.parent_iter(expr.hir_id); if let Some((_, Node::Block(Block { stmts: [], .. }))) = iter.next() { - if let Some((_, Node::Expr(Expr { kind: ExprKind::Loop(_, _, LoopSource::While, _), .. }))) = iter.next() { + if let Some(( + _, + Node::Expr(Expr { + kind: ExprKind::Loop(_, _, LoopSource::While, _), + .. + }), + )) = iter.next() + { // while loop desugar return None; } @@ -108,14 +131,49 @@ impl<'hir> IfLet<'hir> { } } +/// An `if let` or `match` expression. Useful for lints that trigger on one or the other. +pub enum IfLetOrMatch<'hir> { + /// Any `match` expression + Match(&'hir Expr<'hir>, &'hir [Arm<'hir>], MatchSource), + /// scrutinee, pattern, then block, else block + IfLet( + &'hir Expr<'hir>, + &'hir Pat<'hir>, + &'hir Expr<'hir>, + Option<&'hir Expr<'hir>>, + ), +} + +impl<'hir> IfLetOrMatch<'hir> { + /// Parses an `if let` or `match` expression + pub fn parse(cx: &LateContext<'_>, expr: &Expr<'hir>) -> Option<Self> { + match expr.kind { + ExprKind::Match(expr, arms, source) => Some(Self::Match(expr, arms, source)), + _ => IfLet::hir(cx, expr).map( + |IfLet { + let_expr, + let_pat, + if_then, + if_else, + }| { Self::IfLet(let_expr, let_pat, if_then, if_else) }, + ), + } + } +} + +/// An `if` or `if let` expression pub struct IfOrIfLet<'hir> { + /// `if` condition that is maybe a `let` expression pub cond: &'hir Expr<'hir>, - pub r#else: Option<&'hir Expr<'hir>>, + /// `if` then expression pub then: &'hir Expr<'hir>, + /// `else` expression + pub r#else: Option<&'hir Expr<'hir>>, } impl<'hir> IfOrIfLet<'hir> { #[inline] + /// Parses an `if` or `if let` expression pub const fn hir(expr: &Expr<'hir>) -> Option<Self> { if let ExprKind::If(cond, then, r#else) = expr.kind { if let ExprKind::DropTemps(new_cond) = cond.kind { @@ -126,7 +184,7 @@ impl<'hir> IfOrIfLet<'hir> { }); } if let ExprKind::Let(..) = cond.kind { - return Some(Self { cond, r#else, then }); + return Some(Self { cond, then, r#else }); } } None @@ -155,7 +213,7 @@ impl<'a> Range<'a> { } match expr.kind { - hir::ExprKind::Call(ref path, ref args) + hir::ExprKind::Call(path, args) if matches!( path.kind, hir::ExprKind::Path(hir::QPath::LangItem(hir::LangItem::RangeInclusiveNew, _)) @@ -167,7 +225,7 @@ impl<'a> Range<'a> { limits: ast::RangeLimits::Closed, }) }, - hir::ExprKind::Struct(ref path, ref fields, None) => match path { + hir::ExprKind::Struct(path, fields, None) => match &path { hir::QPath::LangItem(hir::LangItem::RangeFull, _) => Some(Range { start: None, end: None, @@ -213,7 +271,7 @@ impl<'a> VecArgs<'a> { /// from `vec!`. pub fn hir(cx: &LateContext<'_>, expr: &'a hir::Expr<'_>) -> Option<VecArgs<'a>> { if_chain! { - if let hir::ExprKind::Call(ref fun, ref args) = expr.kind; + if let hir::ExprKind::Call(fun, args) = expr.kind; if let hir::ExprKind::Path(ref qpath) = fun.kind; if is_expn_of(fun.span, "vec").is_some(); if let Some(fun_def_id) = cx.qpath_res(qpath, fun.hir_id).opt_def_id(); @@ -225,10 +283,10 @@ impl<'a> VecArgs<'a> { else if match_def_path(cx, fun_def_id, &paths::SLICE_INTO_VEC) && args.len() == 1 { // `vec![a, b, c]` case if_chain! { - if let hir::ExprKind::Box(ref boxed) = args[0].kind; - if let hir::ExprKind::Array(ref args) = boxed.kind; + if let hir::ExprKind::Box(boxed) = args[0].kind; + if let hir::ExprKind::Array(args) = boxed.kind; then { - return Some(VecArgs::Vec(&*args)); + return Some(VecArgs::Vec(args)); } } @@ -247,14 +305,17 @@ impl<'a> VecArgs<'a> { } } +/// A desugared `while` loop pub struct While<'hir> { - pub if_cond: &'hir Expr<'hir>, - pub if_then: &'hir Expr<'hir>, - pub if_else: Option<&'hir Expr<'hir>>, + /// `while` loop condition + pub condition: &'hir Expr<'hir>, + /// `while` loop body + pub body: &'hir Expr<'hir>, } impl<'hir> While<'hir> { #[inline] + /// Parses a desugared `while` loop pub const fn hir(expr: &Expr<'hir>) -> Option<Self> { if let ExprKind::Loop( Block { @@ -263,11 +324,11 @@ impl<'hir> While<'hir> { kind: ExprKind::If( Expr { - kind: ExprKind::DropTemps(if_cond), + kind: ExprKind::DropTemps(condition), .. }, - if_then, - if_else_ref, + body, + _, ), .. }), @@ -278,59 +339,53 @@ impl<'hir> While<'hir> { _, ) = expr.kind { - let if_else = *if_else_ref; - return Some(Self { - if_cond, - if_then, - if_else, - }); + return Some(Self { condition, body }); } None } } +/// A desugared `while let` loop pub struct WhileLet<'hir> { - pub if_expr: &'hir Expr<'hir>, + /// `while let` loop item pattern pub let_pat: &'hir Pat<'hir>, + /// `while let` loop scrutinee pub let_expr: &'hir Expr<'hir>, + /// `while let` loop body pub if_then: &'hir Expr<'hir>, - pub if_else: Option<&'hir Expr<'hir>>, } impl<'hir> WhileLet<'hir> { #[inline] + /// Parses a desugared `while let` loop pub const fn hir(expr: &Expr<'hir>) -> Option<Self> { if let ExprKind::Loop( Block { - expr: Some(if_expr), .. + expr: + Some(Expr { + kind: + ExprKind::If( + Expr { + kind: ExprKind::Let(let_pat, let_expr, _), + .. + }, + if_then, + _, + ), + .. + }), + .. }, _, LoopSource::While, _, ) = expr.kind { - if let Expr { - kind: - ExprKind::If( - Expr { - kind: ExprKind::Let(let_pat, let_expr, _), - .. - }, - if_then, - if_else_ref, - ), - .. - } = if_expr - { - let if_else = *if_else_ref; - return Some(Self { - if_expr, - let_pat, - let_expr, - if_then, - if_else, - }); - } + return Some(Self { + let_pat, + let_expr, + if_then, + }); } None } @@ -532,7 +587,7 @@ pub fn is_from_for_desugar(local: &hir::Local<'_>) -> bool { // } // ``` if_chain! { - if let Some(ref expr) = local.init; + if let Some(expr) = local.init; if let hir::ExprKind::Match(_, _, hir::MatchSource::ForLoopDesugar) = expr.kind; then { return true; diff --git a/src/tools/clippy/clippy_utils/src/hir_utils.rs b/src/tools/clippy/clippy_utils/src/hir_utils.rs index a44f2df2fd6..6e9a1de21ee 100644 --- a/src/tools/clippy/clippy_utils/src/hir_utils.rs +++ b/src/tools/clippy/clippy_utils/src/hir_utils.rs @@ -232,9 +232,7 @@ impl HirEqInterExpr<'_, '_, '_> { (&ExprKind::If(lc, lt, ref le), &ExprKind::If(rc, rt, ref re)) => { self.eq_expr(lc, rc) && self.eq_expr(&**lt, &**rt) && both(le, re, |l, r| self.eq_expr(l, r)) }, - (&ExprKind::Let(ref lp, ref le, _), &ExprKind::Let(ref rp, ref re, _)) => { - self.eq_pat(lp, rp) && self.eq_expr(le, re) - }, + (&ExprKind::Let(lp, le, _), &ExprKind::Let(rp, re, _)) => self.eq_pat(lp, rp) && self.eq_expr(le, re), (&ExprKind::Lit(ref l), &ExprKind::Lit(ref r)) => l.node == r.node, (&ExprKind::Loop(lb, ref ll, ref lls, _), &ExprKind::Loop(rb, ref rl, ref rls, _)) => { lls == rls && self.eq_block(lb, rb) && both(ll, rl, |l, r| l.ident.name == r.ident.name) @@ -668,7 +666,7 @@ impl<'a, 'tcx> SpanlessHash<'a, 'tcx> { } } }, - ExprKind::Let(ref pat, ref expr, _) => { + ExprKind::Let(pat, expr, _) => { self.hash_expr(expr); self.hash_pat(pat); }, diff --git a/src/tools/clippy/clippy_utils/src/lib.rs b/src/tools/clippy/clippy_utils/src/lib.rs index 0906f958cfb..da259511fe0 100644 --- a/src/tools/clippy/clippy_utils/src/lib.rs +++ b/src/tools/clippy/clippy_utils/src/lib.rs @@ -2,6 +2,7 @@ #![feature(in_band_lifetimes)] #![feature(iter_zip)] #![feature(rustc_private)] +#![feature(control_flow_enum)] #![recursion_limit = "512"] #![cfg_attr(feature = "deny-warnings", deny(warnings))] #![allow(clippy::missing_errors_doc, clippy::missing_panics_doc, clippy::must_use_candidate)] @@ -62,23 +63,27 @@ use std::collections::hash_map::Entry; use std::hash::BuildHasherDefault; use if_chain::if_chain; -use rustc_ast::ast::{self, Attribute, BorrowKind, LitKind}; +use rustc_ast::ast::{self, Attribute, LitKind}; use rustc_data_structures::unhash::UnhashMap; use rustc_hir as hir; 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::LangItem::{ResultErr, ResultOk}; +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, Node, Param, Pat, PatKind, Path, - PathSegment, PrimTy, QPath, Stmt, StmtKind, TraitItem, TraitItemKind, TraitRef, TyKind, UnOp, + 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; use rustc_middle::hir::map::Map; +use rustc_middle::hir::place::PlaceBase; use rustc_middle::ty as rustc_ty; -use rustc_middle::ty::{layout::IntegerExt, DefIdTree, Ty, TyCtxt, TypeFoldable}; +use rustc_middle::ty::adjustment::{Adjust, Adjustment, AutoBorrow}; +use rustc_middle::ty::binding::BindingMode; +use rustc_middle::ty::{layout::IntegerExt, BorrowKind, DefIdTree, Ty, TyCtxt, TypeAndMut, TypeFoldable, UpvarCapture}; use rustc_semver::RustcVersion; use rustc_session::Session; use rustc_span::hygiene::{ExpnKind, MacroKind}; @@ -89,7 +94,7 @@ use rustc_span::{Span, DUMMY_SP}; use rustc_target::abi::Integer; use crate::consts::{constant, Constant}; -use crate::ty::{can_partially_move_ty, is_recursively_primitive_type}; +use crate::ty::{can_partially_move_ty, is_copy, is_recursively_primitive_type}; pub fn parse_msrv(msrv: &str, sess: Option<&Session>, span: Option<Span>) -> Option<RustcVersion> { if let Ok(version) = RustcVersion::parse(msrv) { @@ -255,7 +260,17 @@ pub fn in_macro(span: Span) -> bool { } pub fn is_unit_expr(expr: &Expr<'_>) -> bool { - matches!(expr.kind, ExprKind::Block(Block { stmts: [], expr: None, .. }, _) | ExprKind::Tup([])) + matches!( + expr.kind, + ExprKind::Block( + Block { + stmts: [], + expr: None, + .. + }, + _ + ) | ExprKind::Tup([]) + ) } /// Checks if given pattern is a wildcard (`_`) @@ -629,12 +644,106 @@ pub fn can_mut_borrow_both(cx: &LateContext<'_>, e1: &Expr<'_>, e2: &Expr<'_>) - false } +/// Returns true if the `def_id` associated with the `path` is recognized as a "default-equivalent" +/// constructor from the std library +fn is_default_equivalent_ctor(cx: &LateContext<'_>, def_id: DefId, path: &QPath<'_>) -> bool { + let std_types_symbols = &[ + sym::string_type, + sym::vec_type, + sym::vecdeque_type, + sym::LinkedList, + sym::hashmap_type, + sym::BTreeMap, + sym::hashset_type, + sym::BTreeSet, + sym::BinaryHeap, + ]; + + if let QPath::TypeRelative(_, method) = path { + if method.ident.name == sym::new { + if let Some(impl_did) = cx.tcx.impl_of_method(def_id) { + if let Some(adt) = cx.tcx.type_of(impl_did).ty_adt_def() { + return std_types_symbols + .iter() + .any(|&symbol| cx.tcx.is_diagnostic_item(symbol, adt.did)); + } + } + } + } + false +} + +/// Returns true if the expr is equal to `Default::default()` of it's type when evaluated. +/// It doesn't cover all cases, for example indirect function calls (some of std +/// functions are supported) but it is the best we have. +pub fn is_default_equivalent(cx: &LateContext<'_>, e: &Expr<'_>) -> bool { + match &e.kind { + ExprKind::Lit(lit) => match lit.node { + LitKind::Bool(false) | LitKind::Int(0, _) => true, + LitKind::Str(s, _) => s.is_empty(), + _ => false, + }, + ExprKind::Tup(items) | ExprKind::Array(items) => items.iter().all(|x| is_default_equivalent(cx, x)), + ExprKind::Repeat(x, _) => is_default_equivalent(cx, x), + ExprKind::Call(repl_func, _) => if_chain! { + if let ExprKind::Path(ref repl_func_qpath) = repl_func.kind; + if let Some(repl_def_id) = cx.qpath_res(repl_func_qpath, repl_func.hir_id).opt_def_id(); + if is_diag_trait_item(cx, repl_def_id, sym::Default) + || is_default_equivalent_ctor(cx, repl_def_id, repl_func_qpath); + then { + true + } + else { + false + } + }, + ExprKind::Path(qpath) => is_lang_ctor(cx, qpath, OptionNone), + ExprKind::AddrOf(rustc_hir::BorrowKind::Ref, _, expr) => matches!(expr.kind, ExprKind::Array([])), + _ => false, + } +} + /// 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 { +/// Currently checks for: +/// * Break/Continue outside the given loop HIR ids. +/// * Yield/Return statments. +/// * Inline assembly. +/// * Usages of a field of a local where the type of the local can be partially moved. +/// +/// For example, given the following function: +/// +/// ``` +/// fn f<'a>(iter: &mut impl Iterator<Item = (usize, &'a mut String)>) { +/// for item in iter { +/// let s = item.1; +/// if item.0 > 10 { +/// continue; +/// } else { +/// s.clear(); +/// } +/// } +/// } +/// ``` +/// +/// When called on the expression `item.0` this will return false unless the local `item` is in the +/// `ignore_locals` set. The type `(usize, &mut String)` can have the second element moved, so it +/// isn't always safe to move into a closure when only a single field is needed. +/// +/// When called on the `continue` expression this will return false unless the outer loop expression +/// is in the `loop_ids` set. +/// +/// Note that this check is not recursive, so passing the `if` expression will always return true +/// even though sub-expressions might return false. +pub fn can_move_expr_to_closure_no_visit( + cx: &LateContext<'tcx>, + expr: &'tcx Expr<'_>, + loop_ids: &[HirId], + ignore_locals: &HirIdSet, +) -> bool { match expr.kind { ExprKind::Break(Destination { target_id: Ok(id), .. }, _) | ExprKind::Continue(Destination { target_id: Ok(id), .. }) - if jump_targets.contains(&id) => + if loop_ids.contains(&id) => { true }, @@ -646,25 +755,170 @@ pub fn can_move_expr_to_closure_no_visit(cx: &LateContext<'tcx>, expr: &'tcx Exp | 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)) => - { + ExprKind::Field( + &Expr { + hir_id, + kind: + ExprKind::Path(QPath::Resolved( + _, + Path { + res: Res::Local(local_id), + .. + }, + )), + .. + }, + _, + ) if !ignore_locals.contains(local_id) && can_partially_move_ty(cx, cx.typeck_results().node_type(hir_id)) => { // 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 { +/// How a local is captured by a closure +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum CaptureKind { + Value, + Ref(Mutability), +} +impl CaptureKind { + pub fn is_imm_ref(self) -> bool { + self == Self::Ref(Mutability::Not) + } +} +impl std::ops::BitOr for CaptureKind { + type Output = Self; + fn bitor(self, rhs: Self) -> Self::Output { + match (self, rhs) { + (CaptureKind::Value, _) | (_, CaptureKind::Value) => CaptureKind::Value, + (CaptureKind::Ref(Mutability::Mut), CaptureKind::Ref(_)) + | (CaptureKind::Ref(_), CaptureKind::Ref(Mutability::Mut)) => CaptureKind::Ref(Mutability::Mut), + (CaptureKind::Ref(Mutability::Not), CaptureKind::Ref(Mutability::Not)) => CaptureKind::Ref(Mutability::Not), + } + } +} +impl std::ops::BitOrAssign for CaptureKind { + fn bitor_assign(&mut self, rhs: Self) { + *self = *self | rhs; + } +} + +/// Given an expression referencing a local, determines how it would be captured in a closure. +/// Note as this will walk up to parent expressions until the capture can be determined it should +/// only be used while making a closure somewhere a value is consumed. e.g. a block, match arm, or +/// function argument (other than a receiver). +pub fn capture_local_usage(cx: &LateContext<'tcx>, e: &Expr<'_>) -> CaptureKind { + fn pat_capture_kind(cx: &LateContext<'_>, pat: &Pat<'_>) -> CaptureKind { + let mut capture = CaptureKind::Ref(Mutability::Not); + pat.each_binding_or_first(&mut |_, id, span, _| match cx + .typeck_results() + .extract_binding_mode(cx.sess(), id, span) + .unwrap() + { + BindingMode::BindByValue(_) if !is_copy(cx, cx.typeck_results().node_type(id)) => { + capture = CaptureKind::Value; + }, + BindingMode::BindByReference(Mutability::Mut) if capture != CaptureKind::Value => { + capture = CaptureKind::Ref(Mutability::Mut); + }, + _ => (), + }); + capture + } + + debug_assert!(matches!( + e.kind, + ExprKind::Path(QPath::Resolved(None, Path { res: Res::Local(_), .. })) + )); + + let map = cx.tcx.hir(); + let mut child_id = e.hir_id; + let mut capture = CaptureKind::Value; + let mut capture_expr_ty = e; + + for (parent_id, parent) in map.parent_iter(e.hir_id) { + if let [Adjustment { + kind: Adjust::Deref(_) | Adjust::Borrow(AutoBorrow::Ref(..)), + target, + }, ref adjust @ ..] = *cx + .typeck_results() + .adjustments() + .get(child_id) + .map_or(&[][..], |x| &**x) + { + if let rustc_ty::RawPtr(TypeAndMut { mutbl: mutability, .. }) | rustc_ty::Ref(_, _, mutability) = + *adjust.last().map_or(target, |a| a.target).kind() + { + return CaptureKind::Ref(mutability); + } + } + + match parent { + Node::Expr(e) => match e.kind { + ExprKind::AddrOf(_, mutability, _) => return CaptureKind::Ref(mutability), + ExprKind::Index(..) | ExprKind::Unary(UnOp::Deref, _) => capture = CaptureKind::Ref(Mutability::Not), + ExprKind::Assign(lhs, ..) | ExprKind::Assign(_, lhs, _) if lhs.hir_id == child_id => { + return CaptureKind::Ref(Mutability::Mut); + }, + ExprKind::Field(..) => { + if capture == CaptureKind::Value { + capture_expr_ty = e; + } + }, + ExprKind::Let(pat, ..) => { + let mutability = match pat_capture_kind(cx, pat) { + CaptureKind::Value => Mutability::Not, + CaptureKind::Ref(m) => m, + }; + return CaptureKind::Ref(mutability); + }, + ExprKind::Match(_, arms, _) => { + let mut mutability = Mutability::Not; + for capture in arms.iter().map(|arm| pat_capture_kind(cx, arm.pat)) { + match capture { + CaptureKind::Value => break, + CaptureKind::Ref(Mutability::Mut) => mutability = Mutability::Mut, + CaptureKind::Ref(Mutability::Not) => (), + } + } + return CaptureKind::Ref(mutability); + }, + _ => break, + }, + Node::Local(l) => match pat_capture_kind(cx, l.pat) { + CaptureKind::Value => break, + capture @ CaptureKind::Ref(_) => return capture, + }, + _ => break, + } + + child_id = parent_id; + } + + if capture == CaptureKind::Value && is_copy(cx, cx.typeck_results().expr_ty(capture_expr_ty)) { + // Copy types are never automatically captured by value. + CaptureKind::Ref(Mutability::Not) + } else { + capture + } +} + +/// Checks if the expression can be moved into a closure as is. This will return a list of captures +/// if so, otherwise, `None`. +pub fn can_move_expr_to_closure(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> Option<HirIdMap<CaptureKind>> { struct V<'cx, 'tcx> { cx: &'cx LateContext<'tcx>, + // Stack of potential break targets contained in the expression. loops: Vec<HirId>, + /// Local variables created in the expression. These don't need to be captured. + locals: HirIdSet, + /// Whether this expression can be turned into a closure. allow_closure: bool, + /// Locals which need to be captured, and whether they need to be by value, reference, or + /// mutable reference. + captures: HirIdMap<CaptureKind>, } impl Visitor<'tcx> for V<'_, 'tcx> { type Map = ErasedMap<'tcx>; @@ -676,24 +930,67 @@ pub fn can_move_expr_to_closure(cx: &LateContext<'tcx>, expr: &'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); + + match e.kind { + ExprKind::Path(QPath::Resolved(None, &Path { res: Res::Local(l), .. })) => { + if !self.locals.contains(&l) { + let cap = capture_local_usage(self.cx, e); + self.captures.entry(l).and_modify(|e| *e |= cap).or_insert(cap); + } + }, + ExprKind::Closure(..) => { + let closure_id = self.cx.tcx.hir().local_def_id(e.hir_id).to_def_id(); + for capture in self.cx.typeck_results().closure_min_captures_flattened(closure_id) { + let local_id = match capture.place.base { + PlaceBase::Local(id) => id, + PlaceBase::Upvar(var) => var.var_path.hir_id, + _ => continue, + }; + if !self.locals.contains(&local_id) { + let capture = match capture.info.capture_kind { + UpvarCapture::ByValue(_) => CaptureKind::Value, + UpvarCapture::ByRef(borrow) => match borrow.kind { + BorrowKind::ImmBorrow => CaptureKind::Ref(Mutability::Not), + BorrowKind::UniqueImmBorrow | BorrowKind::MutBorrow => { + CaptureKind::Ref(Mutability::Mut) + }, + }, + }; + self.captures + .entry(local_id) + .and_modify(|e| *e |= capture) + .or_insert(capture); + } + } + }, + ExprKind::Loop(b, ..) => { + self.loops.push(e.hir_id); + self.visit_block(b); + self.loops.pop(); + }, + _ => { + self.allow_closure &= can_move_expr_to_closure_no_visit(self.cx, e, &self.loops, &self.locals); + walk_expr(self, e); + }, } } + + fn visit_pat(&mut self, p: &'tcx Pat<'tcx>) { + p.each_binding_or_first(&mut |_, id, _, _| { + self.locals.insert(id); + }); + } } let mut v = V { cx, allow_closure: true, loops: Vec::new(), + locals: HirIdSet::default(), + captures: HirIdMap::default(), }; v.visit_expr(expr); - v.allow_closure + v.allow_closure.then(|| v.captures) } /// Returns the method names and argument list of nested method call expressions that make up @@ -1365,13 +1662,13 @@ pub fn if_sequence<'tcx>(mut expr: &'tcx Expr<'tcx>) -> (Vec<&'tcx Expr<'tcx>>, while let Some(higher::IfOrIfLet { cond, then, r#else }) = higher::IfOrIfLet::hir(expr) { conds.push(&*cond); - if let ExprKind::Block(ref block, _) = then.kind { + if let ExprKind::Block(block, _) = then.kind { blocks.push(block); } else { panic!("ExprKind::If node is not an ExprKind::Block"); } - if let Some(ref else_expr) = r#else { + if let Some(else_expr) = r#else { expr = else_expr; } else { break; @@ -1708,7 +2005,7 @@ pub fn peel_hir_expr_while<'tcx>( pub fn peel_n_hir_expr_refs(expr: &'a Expr<'a>, count: usize) -> (&'a Expr<'a>, usize) { let mut remaining = count; let e = peel_hir_expr_while(expr, |e| match e.kind { - ExprKind::AddrOf(BorrowKind::Ref, _, e) if remaining != 0 => { + ExprKind::AddrOf(ast::BorrowKind::Ref, _, e) if remaining != 0 => { remaining -= 1; Some(e) }, @@ -1722,7 +2019,7 @@ pub fn peel_n_hir_expr_refs(expr: &'a Expr<'a>, count: usize) -> (&'a Expr<'a>, pub fn peel_hir_expr_refs(expr: &'a Expr<'a>) -> (&'a Expr<'a>, usize) { let mut count = 0; let e = peel_hir_expr_while(expr, |e| match e.kind { - ExprKind::AddrOf(BorrowKind::Ref, _, e) => { + ExprKind::AddrOf(ast::BorrowKind::Ref, _, e) => { count += 1; Some(e) }, diff --git a/src/tools/clippy/clippy_utils/src/msrvs.rs b/src/tools/clippy/clippy_utils/src/msrvs.rs index 4a9c4fd0276..fa57dfbb57e 100644 --- a/src/tools/clippy/clippy_utils/src/msrvs.rs +++ b/src/tools/clippy/clippy_utils/src/msrvs.rs @@ -13,9 +13,12 @@ macro_rules! msrv_aliases { // names may refer to stabilized feature flags or library items msrv_aliases! { 1,53,0 { OR_PATTERNS } + 1,52,0 { STR_SPLIT_ONCE } 1,50,0 { BOOL_THEN } + 1,47,0 { TAU } 1,46,0 { CONST_IF_MATCH } 1,45,0 { STR_STRIP_PREFIX } + 1,43,0 { LOG2_10, LOG10_2 } 1,42,0 { MATCHES_MACRO } 1,41,0 { RE_REBALANCING_COHERENCE, RESULT_MAP_OR_ELSE } 1,40,0 { MEM_TAKE, NON_EXHAUSTIVE, OPTION_AS_DEREF } diff --git a/src/tools/clippy/clippy_utils/src/paths.rs b/src/tools/clippy/clippy_utils/src/paths.rs index b0c3fe1e5a7..d7e46c2d3eb 100644 --- a/src/tools/clippy/clippy_utils/src/paths.rs +++ b/src/tools/clippy/clippy_utils/src/paths.rs @@ -68,6 +68,7 @@ pub const IO_WRITE: [&str; 3] = ["std", "io", "Write"]; pub const IPADDR_V4: [&str; 5] = ["std", "net", "ip", "IpAddr", "V4"]; pub const IPADDR_V6: [&str; 5] = ["std", "net", "ip", "IpAddr", "V6"]; pub const ITER_REPEAT: [&str; 5] = ["core", "iter", "sources", "repeat", "repeat"]; +pub const ITERTOOLS_NEXT_TUPLE: [&str; 3] = ["itertools", "Itertools", "next_tuple"]; #[cfg(feature = "internal-lints")] pub const KW_MODULE: [&str; 3] = ["rustc_span", "symbol", "kw"]; #[cfg(feature = "internal-lints")] diff --git a/src/tools/clippy/clippy_utils/src/qualify_min_const_fn.rs b/src/tools/clippy/clippy_utils/src/qualify_min_const_fn.rs index 4fb9e6b07e7..8f14b590d27 100644 --- a/src/tools/clippy/clippy_utils/src/qualify_min_const_fn.rs +++ b/src/tools/clippy/clippy_utils/src/qualify_min_const_fn.rs @@ -36,9 +36,7 @@ pub fn is_min_const_fn(tcx: TyCtxt<'tcx>, body: &'a Body<'tcx>, msrv: Option<&Ru ty::PredicateKind::ObjectSafe(_) => panic!("object safe predicate on function: {:#?}", predicate), ty::PredicateKind::ClosureKind(..) => panic!("closure kind predicate on function: {:#?}", predicate), ty::PredicateKind::Subtype(_) => panic!("subtype predicate on function: {:#?}", predicate), - ty::PredicateKind::Coerce(_) => { - panic!("coerce predicate on function: {:#?}", predicate) - }, + ty::PredicateKind::Coerce(_) => panic!("coerce predicate on function: {:#?}", predicate), ty::PredicateKind::Trait(pred) => { if Some(pred.def_id()) == tcx.lang_items().sized_trait() { continue; diff --git a/src/tools/clippy/clippy_utils/src/sugg.rs b/src/tools/clippy/clippy_utils/src/sugg.rs index 65d93e8f86e..ab05a0b4238 100644 --- a/src/tools/clippy/clippy_utils/src/sugg.rs +++ b/src/tools/clippy/clippy_utils/src/sugg.rs @@ -329,7 +329,7 @@ fn has_enclosing_paren(sugg: impl AsRef<str>) -> bool { } } -// Copied from the rust standart library, and then edited +/// Copied from the rust standard library, and then edited macro_rules! forward_binop_impls_to_ref { (impl $imp:ident, $method:ident for $t:ty, type Output = $o:ty) => { impl $imp<$t> for &$t { diff --git a/src/tools/clippy/clippy_utils/src/ty.rs b/src/tools/clippy/clippy_utils/src/ty.rs index 3cd8ed5aa2c..d6f9ebe89bc 100644 --- a/src/tools/clippy/clippy_utils/src/ty.rs +++ b/src/tools/clippy/clippy_utils/src/ty.rs @@ -10,7 +10,7 @@ use rustc_hir::{TyKind, Unsafety}; use rustc_infer::infer::TyCtxtInferExt; use rustc_lint::LateContext; use rustc_middle::ty::subst::{GenericArg, GenericArgKind}; -use rustc_middle::ty::{self, TyCtxt, AdtDef, IntTy, Ty, TypeFoldable, UintTy}; +use rustc_middle::ty::{self, AdtDef, IntTy, Ty, TyCtxt, TypeFoldable, UintTy}; use rustc_span::sym; use rustc_span::symbol::{Ident, Symbol}; use rustc_span::DUMMY_SP; @@ -224,6 +224,13 @@ fn is_normalizable_helper<'tcx>( result } +/// Returns true iff the given type is a non aggregate primitive (a bool or char, any integer or +/// floating-point number type). For checking aggregation of primitive types (e.g. tuples and slices +/// of primitive type) see `is_recursively_primitive_type` +pub fn is_non_aggregate_primitive_type(ty: Ty<'_>) -> bool { + matches!(ty.kind(), ty::Bool | ty::Char | ty::Int(_) | ty::Uint(_) | ty::Float(_)) +} + /// Returns true iff the given type is a primitive (a bool or char, any integer or floating-point /// number type, a str, or an array, slice, or tuple of those types). pub fn is_recursively_primitive_type(ty: Ty<'_>) -> bool { diff --git a/src/tools/clippy/clippy_utils/src/usage.rs b/src/tools/clippy/clippy_utils/src/usage.rs index ac885e99944..098ec175fe2 100644 --- a/src/tools/clippy/clippy_utils/src/usage.rs +++ b/src/tools/clippy/clippy_utils/src/usage.rs @@ -1,10 +1,9 @@ use crate as utils; use rustc_hir as hir; -use rustc_hir::def::Res; use rustc_hir::intravisit; use rustc_hir::intravisit::{NestedVisitorMap, Visitor}; use rustc_hir::HirIdSet; -use rustc_hir::{Expr, ExprKind, HirId, Path}; +use rustc_hir::{Expr, ExprKind, HirId}; use rustc_infer::infer::TyCtxtInferExt; use rustc_lint::LateContext; use rustc_middle::hir::map::Map; @@ -35,12 +34,8 @@ pub fn mutated_variables<'tcx>(expr: &'tcx Expr<'_>, cx: &LateContext<'tcx>) -> Some(delegate.used_mutably) } -pub fn is_potentially_mutated<'tcx>(variable: &'tcx Path<'_>, expr: &'tcx Expr<'_>, cx: &LateContext<'tcx>) -> bool { - if let Res::Local(id) = variable.res { - mutated_variables(expr, cx).map_or(true, |mutated| mutated.contains(&id)) - } else { - true - } +pub fn is_potentially_mutated<'tcx>(variable: HirId, expr: &'tcx Expr<'_>, cx: &LateContext<'tcx>) -> bool { + mutated_variables(expr, cx).map_or(true, |mutated| mutated.contains(&variable)) } struct MutVarsDelegate { diff --git a/src/tools/clippy/clippy_utils/src/visitors.rs b/src/tools/clippy/clippy_utils/src/visitors.rs index ce00106dd4d..503effbdad5 100644 --- a/src/tools/clippy/clippy_utils/src/visitors.rs +++ b/src/tools/clippy/clippy_utils/src/visitors.rs @@ -4,6 +4,7 @@ use rustc_hir::intravisit::{self, walk_expr, ErasedMap, NestedVisitorMap, Visito use rustc_hir::{def::Res, Arm, Block, Body, BodyId, Destination, Expr, ExprKind, HirId, Stmt}; use rustc_lint::LateContext; use rustc_middle::hir::map::Map; +use std::ops::ControlFlow; /// returns `true` if expr contains match expr desugared from try fn contains_try(expr: &hir::Expr<'_>) -> bool { @@ -133,62 +134,6 @@ where } } -pub struct LocalUsedVisitor<'hir> { - hir: Map<'hir>, - pub local_hir_id: HirId, - pub used: bool, -} - -impl<'hir> LocalUsedVisitor<'hir> { - pub fn new(cx: &LateContext<'hir>, local_hir_id: HirId) -> Self { - Self { - hir: cx.tcx.hir(), - local_hir_id, - used: false, - } - } - - fn check<T>(&mut self, t: T, visit: fn(&mut Self, T)) -> bool { - visit(self, t); - std::mem::replace(&mut self.used, false) - } - - pub fn check_arm(&mut self, arm: &'hir Arm<'_>) -> bool { - self.check(arm, Self::visit_arm) - } - - pub fn check_body(&mut self, body: &'hir Body<'_>) -> bool { - self.check(body, Self::visit_body) - } - - pub fn check_expr(&mut self, expr: &'hir Expr<'_>) -> bool { - self.check(expr, Self::visit_expr) - } - - pub fn check_stmt(&mut self, stmt: &'hir Stmt<'_>) -> bool { - self.check(stmt, Self::visit_stmt) - } -} - -impl<'v> Visitor<'v> for LocalUsedVisitor<'v> { - type Map = Map<'v>; - - fn visit_expr(&mut self, expr: &'v Expr<'v>) { - if self.used { - return; - } - if path_to_local_id(expr, self.local_hir_id) { - self.used = true; - } else { - walk_expr(self, expr); - } - } - - fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> { - NestedVisitorMap::OnlyBodies(self.hir) - } -} - /// A type which can be visited. pub trait Visitable<'tcx> { /// Calls the corresponding `visit_*` function on the visitor. @@ -203,7 +148,22 @@ macro_rules! visitable_ref { } }; } +visitable_ref!(Arm, visit_arm); visitable_ref!(Block, visit_block); +visitable_ref!(Body, visit_body); +visitable_ref!(Expr, visit_expr); +visitable_ref!(Stmt, visit_stmt); + +// impl<'tcx, I: IntoIterator> Visitable<'tcx> for I +// where +// I::Item: Visitable<'tcx>, +// { +// fn visit<V: Visitor<'tcx>>(self, visitor: &mut V) { +// for x in self { +// x.visit(visitor); +// } +// } +// } /// Calls the given function for each break expression. pub fn visit_break_exprs<'tcx>( @@ -260,3 +220,48 @@ pub fn is_res_used(cx: &LateContext<'_>, res: Res, body: BodyId) -> bool { v.visit_expr(&cx.tcx.hir().body(body).value); v.found } + +/// Calls the given function for each usage of the given local. +pub fn for_each_local_usage<'tcx, B>( + cx: &LateContext<'tcx>, + visitable: impl Visitable<'tcx>, + id: HirId, + f: impl FnMut(&'tcx Expr<'tcx>) -> ControlFlow<B>, +) -> ControlFlow<B> { + struct V<'tcx, B, F> { + map: Map<'tcx>, + id: HirId, + f: F, + res: ControlFlow<B>, + } + impl<'tcx, B, F: FnMut(&'tcx Expr<'tcx>) -> ControlFlow<B>> Visitor<'tcx> for V<'tcx, B, F> { + type Map = Map<'tcx>; + fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> { + NestedVisitorMap::OnlyBodies(self.map) + } + + fn visit_expr(&mut self, e: &'tcx Expr<'_>) { + if self.res.is_continue() { + if path_to_local_id(e, self.id) { + self.res = (self.f)(e); + } else { + walk_expr(self, e); + } + } + } + } + + let mut v = V { + map: cx.tcx.hir(), + id, + f, + res: ControlFlow::CONTINUE, + }; + visitable.visit(&mut v); + v.res +} + +/// Checks if the given local is used. +pub fn is_local_used(cx: &LateContext<'tcx>, visitable: impl Visitable<'tcx>, id: HirId) -> bool { + for_each_local_usage(cx, visitable, id, |_| ControlFlow::BREAK).is_break() +} |
