| //! 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::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<'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) |
| } |