diff options
Diffstat (limited to 'clippy_lints/src/utils')
| -rw-r--r-- | clippy_lints/src/utils/eager_or_lazy.rs | 128 | ||||
| -rw-r--r-- | clippy_lints/src/utils/mod.rs | 1 | ||||
| -rw-r--r-- | clippy_lints/src/utils/sugg.rs | 2 | ||||
| -rw-r--r-- | clippy_lints/src/utils/usage.rs | 66 |
4 files changed, 196 insertions, 1 deletions
diff --git a/clippy_lints/src/utils/eager_or_lazy.rs b/clippy_lints/src/utils/eager_or_lazy.rs new file mode 100644 index 00000000000..6938d9971d9 --- /dev/null +++ b/clippy_lints/src/utils/eager_or_lazy.rs @@ -0,0 +1,128 @@ +//! Utilities for evaluating whether eagerly evaluated expressions can be made lazy and vice versa. +//! +//! Things to consider: +//! - has the expression side-effects? +//! - is the expression computationally expensive? +//! +//! See lints: +//! - unnecessary-lazy-evaluations +//! - or-fun-call +//! - option-if-let-else + +use crate::utils::is_ctor_or_promotable_const_function; +use rustc_hir::def::{DefKind, Res}; + +use rustc_hir::intravisit; +use rustc_hir::intravisit::{NestedVisitorMap, Visitor}; + +use rustc_hir::{Block, Expr, ExprKind, Path, QPath}; +use rustc_lint::LateContext; +use rustc_middle::hir::map::Map; + +/// Is the expr pure (is it free from side-effects)? +/// This function is named so to stress that it isn't exhaustive and returns FNs. +fn identify_some_pure_patterns(expr: &Expr<'_>) -> bool { + match expr.kind { + ExprKind::Lit(..) | ExprKind::Path(..) | ExprKind::Field(..) => true, + ExprKind::AddrOf(_, _, addr_of_expr) => identify_some_pure_patterns(addr_of_expr), + ExprKind::Tup(tup_exprs) => tup_exprs.iter().all(|expr| identify_some_pure_patterns(expr)), + ExprKind::Struct(_, fields, expr) => { + fields.iter().all(|f| identify_some_pure_patterns(f.expr)) + && expr.map_or(true, |e| identify_some_pure_patterns(e)) + }, + ExprKind::Call( + &Expr { + kind: + ExprKind::Path(QPath::Resolved( + _, + Path { + res: Res::Def(DefKind::Ctor(..) | DefKind::Variant, ..), + .. + }, + )), + .. + }, + args, + ) => args.iter().all(|expr| identify_some_pure_patterns(expr)), + ExprKind::Block( + &Block { + stmts, + expr: Some(expr), + .. + }, + _, + ) => stmts.is_empty() && identify_some_pure_patterns(expr), + ExprKind::Box(..) + | ExprKind::Array(..) + | ExprKind::Call(..) + | ExprKind::MethodCall(..) + | ExprKind::Binary(..) + | ExprKind::Unary(..) + | ExprKind::Cast(..) + | ExprKind::Type(..) + | ExprKind::DropTemps(..) + | ExprKind::Loop(..) + | ExprKind::Match(..) + | ExprKind::Closure(..) + | ExprKind::Block(..) + | ExprKind::Assign(..) + | ExprKind::AssignOp(..) + | ExprKind::Index(..) + | ExprKind::Break(..) + | ExprKind::Continue(..) + | ExprKind::Ret(..) + | ExprKind::InlineAsm(..) + | ExprKind::LlvmInlineAsm(..) + | ExprKind::Repeat(..) + | ExprKind::Yield(..) + | ExprKind::Err => false, + } +} + +/// Identify some potentially computationally expensive patterns. +/// This function is named so to stress that its implementation is non-exhaustive. +/// It returns FNs and FPs. +fn identify_some_potentially_expensive_patterns<'a, 'tcx>(cx: &'a LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> bool { + // Searches an expression for method calls or function calls that aren't ctors + struct FunCallFinder<'a, 'tcx> { + cx: &'a LateContext<'tcx>, + found: bool, + } + + impl<'a, 'tcx> intravisit::Visitor<'tcx> for FunCallFinder<'a, 'tcx> { + type Map = Map<'tcx>; + + fn visit_expr(&mut self, expr: &'tcx Expr<'tcx>) { + let call_found = match &expr.kind { + // ignore enum and struct constructors + ExprKind::Call(..) => !is_ctor_or_promotable_const_function(self.cx, expr), + ExprKind::MethodCall(..) => true, + _ => false, + }; + + if call_found { + self.found |= true; + } + + if !self.found { + intravisit::walk_expr(self, expr); + } + } + + fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> { + NestedVisitorMap::None + } + } + + let mut finder = FunCallFinder { cx, found: false }; + finder.visit_expr(expr); + finder.found +} + +pub fn is_eagerness_candidate<'a, 'tcx>(cx: &'a LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> bool { + !identify_some_potentially_expensive_patterns(cx, expr) && identify_some_pure_patterns(expr) +} + +pub fn is_lazyness_candidate<'a, 'tcx>(cx: &'a LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> bool { + identify_some_potentially_expensive_patterns(cx, expr) +} diff --git a/clippy_lints/src/utils/mod.rs b/clippy_lints/src/utils/mod.rs index 8db7e693e62..95eedf88178 100644 --- a/clippy_lints/src/utils/mod.rs +++ b/clippy_lints/src/utils/mod.rs @@ -10,6 +10,7 @@ pub mod comparisons; pub mod conf; pub mod constants; mod diagnostics; +pub mod eager_or_lazy; pub mod higher; mod hir_utils; pub mod inspector; diff --git a/clippy_lints/src/utils/sugg.rs b/clippy_lints/src/utils/sugg.rs index 811fde388d1..ec8b7e59b59 100644 --- a/clippy_lints/src/utils/sugg.rs +++ b/clippy_lints/src/utils/sugg.rs @@ -49,7 +49,7 @@ impl<'a> Sugg<'a> { /// Convenience function around `hir_opt` for suggestions with a default /// text. pub fn hir(cx: &LateContext<'_>, expr: &hir::Expr<'_>, default: &'a str) -> Self { - Self::hir_opt(cx, expr).unwrap_or_else(|| Sugg::NonParen(Cow::Borrowed(default))) + Self::hir_opt(cx, expr).unwrap_or(Sugg::NonParen(Cow::Borrowed(default))) } /// Same as `hir`, but it adapts the applicability level by following rules: diff --git a/clippy_lints/src/utils/usage.rs b/clippy_lints/src/utils/usage.rs index 4a64b935ac9..ea1dc3be29b 100644 --- a/clippy_lints/src/utils/usage.rs +++ b/clippy_lints/src/utils/usage.rs @@ -1,6 +1,8 @@ use crate::utils::match_var; use rustc_data_structures::fx::FxHashSet; +use rustc_hir as hir; use rustc_hir::def::Res; +use rustc_hir::intravisit; use rustc_hir::intravisit::{walk_expr, NestedVisitorMap, Visitor}; use rustc_hir::{Expr, HirId, Path}; use rustc_infer::infer::TyCtxtInferExt; @@ -108,3 +110,67 @@ pub fn is_unused<'tcx>(ident: &'tcx Ident, body: &'tcx Expr<'_>) -> bool { walk_expr(&mut visitor, body); !visitor.used } + +pub struct ParamBindingIdCollector { + binding_hir_ids: Vec<hir::HirId>, +} +impl<'tcx> ParamBindingIdCollector { + fn collect_binding_hir_ids(body: &'tcx hir::Body<'tcx>) -> Vec<hir::HirId> { + let mut finder = ParamBindingIdCollector { + binding_hir_ids: Vec::new(), + }; + finder.visit_body(body); + finder.binding_hir_ids + } +} +impl<'tcx> intravisit::Visitor<'tcx> for ParamBindingIdCollector { + type Map = Map<'tcx>; + + fn visit_param(&mut self, param: &'tcx hir::Param<'tcx>) { + if let hir::PatKind::Binding(_, hir_id, ..) = param.pat.kind { + self.binding_hir_ids.push(hir_id); + } + } + + fn nested_visit_map(&mut self) -> intravisit::NestedVisitorMap<Self::Map> { + intravisit::NestedVisitorMap::None + } +} + +pub struct BindingUsageFinder<'a, 'tcx> { + cx: &'a LateContext<'tcx>, + binding_ids: Vec<hir::HirId>, + usage_found: bool, +} +impl<'a, 'tcx> BindingUsageFinder<'a, 'tcx> { + pub fn are_params_used(cx: &'a LateContext<'tcx>, body: &'tcx hir::Body<'tcx>) -> bool { + let mut finder = BindingUsageFinder { + cx, + binding_ids: ParamBindingIdCollector::collect_binding_hir_ids(body), + usage_found: false, + }; + finder.visit_body(body); + finder.usage_found + } +} +impl<'a, 'tcx> intravisit::Visitor<'tcx> for BindingUsageFinder<'a, 'tcx> { + type Map = Map<'tcx>; + + fn visit_expr(&mut self, expr: &'tcx hir::Expr<'tcx>) { + if !self.usage_found { + intravisit::walk_expr(self, expr); + } + } + + fn visit_path(&mut self, path: &'tcx hir::Path<'tcx>, _: hir::HirId) { + if let hir::def::Res::Local(id) = path.res { + if self.binding_ids.contains(&id) { + self.usage_found = true; + } + } + } + + fn nested_visit_map(&mut self) -> intravisit::NestedVisitorMap<Self::Map> { + intravisit::NestedVisitorMap::OnlyBodies(self.cx.tcx.hir()) + } +} |
