//! 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, is_type_diagnostic_item, match_type, paths}; 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::ConstBlock(..) | 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<'tcx>(cx: &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::Index(obj, _) => { let ty = self.cx.typeck_results().expr_ty(obj); is_type_diagnostic_item(self.cx, ty, sym!(hashmap_type)) || match_type(self.cx, ty, &paths::BTREEMAP) }, 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 { 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) }