2020-09-24 12:49:22 +00:00
|
|
|
//! 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
|
|
|
|
|
2020-10-29 20:30:20 +00:00
|
|
|
use crate::utils::{is_ctor_or_promotable_const_function, is_type_diagnostic_item, match_type, paths};
|
2020-09-24 12:49:22 +00:00
|
|
|
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 {
|
2020-10-06 21:51:31 +00:00
|
|
|
ExprKind::Lit(..) | ExprKind::ConstBlock(..) | ExprKind::Path(..) | ExprKind::Field(..) => true,
|
2020-09-24 12:49:22 +00:00
|
|
|
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.
|
2020-10-06 09:49:08 +00:00
|
|
|
fn identify_some_potentially_expensive_patterns<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> bool {
|
2020-09-24 12:49:22 +00:00
|
|
|
// 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),
|
2020-10-29 20:30:20 +00:00
|
|
|
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)
|
|
|
|
},
|
2020-09-24 12:49:22 +00:00
|
|
|
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)
|
|
|
|
}
|