use std::{iter, ops};
use syn::*;
mod private {
pub trait Sealed {}
impl Sealed for super::Expr {}
impl Sealed for super::ItemFn {}
impl Sealed for super::ImplItemMethod {}
}
pub trait Complexity: private::Sealed {
fn complexity(&self) -> u32;
}
impl Complexity for Expr {
fn complexity(&self) -> u32 {
eval_expr(self, State::default()).0
}
}
impl Complexity for ItemFn {
fn complexity(&self) -> u32 {
eval_block(&self.block, State::default()).0
}
}
impl Complexity for ImplItemMethod {
fn complexity(&self) -> u32 {
eval_block(&self.block, State::default()).0
}
}
#[derive(Debug, Clone, Copy)]
struct Index(u32);
impl Index {
fn zero() -> Self {
Self(0)
}
fn one() -> Self {
Self(1)
}
fn with_context(state: State) -> Self {
Self(state.nesting + 1)
}
}
impl ops::Add for Index {
type Output = Self;
fn add(self, other: Self) -> Self {
Self(self.0 + other.0)
}
}
impl iter::Sum<Index> for Index {
fn sum<I>(iter: I) -> Self
where
I: Iterator<Item = Self>,
{
iter.fold(Self::zero(), ops::Add::add)
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
enum LogBoolOp {
Not,
And,
Or,
}
impl LogBoolOp {
fn from_un_op(un_op: UnOp) -> Option<Self> {
match un_op {
UnOp::Not(_) => Some(Self::Not),
_ => None,
}
}
fn from_bin_op(bin_op: &BinOp) -> Option<Self> {
match bin_op {
BinOp::And(_) => Some(Self::And),
BinOp::Or(_) => Some(Self::Or),
_ => None,
}
}
fn eval_based_on_prev(self, prev: Option<Self>) -> Index {
match (prev, self) {
(Some(prev), current) => {
if prev == current {
Index::zero()
} else {
Index::one()
}
}
(None, _) => Index::one(),
}
}
}
#[derive(Debug, Default, Clone, Copy)]
struct State {
nesting: u32,
log_bool_op: Option<LogBoolOp>,
}
impl State {
fn increase_nesting(self) -> Self {
Self {
nesting: self.nesting + 1,
log_bool_op: self.log_bool_op,
}
}
}
fn eval_block(block: &Block, state: State) -> Index {
block
.stmts
.iter()
.map(|e| eval_stmt(e, state))
.sum::<Index>()
}
fn eval_stmt(stmt: &Stmt, state: State) -> Index {
match stmt {
Stmt::Local(Local {
init: Some((_, expr)),
..
}) => eval_expr(expr, state),
Stmt::Local(Local { init: None, .. }) => Index::zero(),
Stmt::Item(item) => eval_item(item, state),
Stmt::Expr(expr) | Stmt::Semi(expr, _) => eval_expr(expr, state),
}
}
fn eval_item(item: &Item, state: State) -> Index {
match item {
Item::Const(ItemConst { expr, .. }) => eval_expr(expr, state),
Item::Static(ItemStatic { expr, .. }) => eval_expr(expr, state),
_ => Index::zero(),
}
}
fn eval_expr_unary(expr_unary: &ExprUnary, mut state: State) -> Index {
let ExprUnary { op, expr, .. } = expr_unary;
if let Some(current) = LogBoolOp::from_un_op(*op) {
state.log_bool_op = Some(current);
}
eval_expr(expr, state)
}
fn eval_expr_binary(expr_binary: &ExprBinary, mut state: State) -> Index {
let ExprBinary {
left, op, right, ..
} = expr_binary;
let index = match LogBoolOp::from_bin_op(op) {
Some(current) => {
let index = current.eval_based_on_prev(state.log_bool_op);
state.log_bool_op = Some(current);
index
}
None => Index::zero(),
};
index + eval_expr(left, state) + eval_expr(right, state)
}
fn eval_expr_range(expr_range: &ExprRange, state: State) -> Index {
let ExprRange { from, to, .. } = expr_range;
from.as_ref()
.map(|e| eval_expr(e, state))
.unwrap_or_else(Index::zero)
+ to.as_ref()
.map(|e| eval_expr(e, state))
.unwrap_or_else(Index::zero)
}
fn eval_expr_if(expr_if: &ExprIf, state: State) -> Index {
let ExprIf {
cond,
then_branch,
else_branch,
..
} = expr_if;
Index::with_context(state)
+ eval_expr(cond, state)
+ eval_block(then_branch, state.increase_nesting())
+ else_branch
.as_ref()
.map(|(_, expr)| Index::one() + eval_expr(&expr, state.increase_nesting()))
.unwrap_or_else(Index::zero)
}
fn eval_expr_match(expr_match: &ExprMatch, state: State) -> Index {
let ExprMatch { expr, arms, .. } = expr_match;
Index::with_context(state)
+ eval_expr(expr, state)
+ arms
.iter()
.map(|arm| {
arm.guard
.as_ref()
.map(|(_, expr)| eval_expr(&expr, state))
.unwrap_or_else(Index::zero)
+ eval_expr(&arm.body, state.increase_nesting())
})
.sum::<Index>()
}
fn eval_expr_for_loop(expr_for_loop: &ExprForLoop, state: State) -> Index {
let ExprForLoop { expr, body, .. } = expr_for_loop;
Index::with_context(state) + eval_expr(expr, state) + eval_block(body, state.increase_nesting())
}
fn eval_expr_while(expr_while: &ExprWhile, state: State) -> Index {
let ExprWhile { cond, body, .. } = expr_while;
Index::with_context(state) + eval_expr(cond, state) + eval_block(body, state.increase_nesting())
}
fn eval_expr_struct(expr_struct: &ExprStruct, state: State) -> Index {
let ExprStruct { fields, rest, .. } = expr_struct;
fields
.iter()
.map(|v| eval_expr(&v.expr, state))
.sum::<Index>()
+ rest
.as_ref()
.map(|e| eval_expr(e, state))
.unwrap_or_else(Index::zero)
}
fn eval_expr_call(expr_call: &ExprCall, state: State) -> Index {
let ExprCall { func, args, .. } = expr_call;
eval_expr(func, state) + args.iter().map(|a| eval_expr(a, state)).sum::<Index>()
}
fn eval_expr_method_call(expr_method_call: &ExprMethodCall, state: State) -> Index {
let ExprMethodCall { receiver, args, .. } = expr_method_call;
eval_expr(receiver, state) + args.iter().map(|a| eval_expr(a, state)).sum::<Index>()
}
fn eval_expr(expr: &Expr, state: State) -> Index {
match expr {
Expr::Array(ExprArray { elems, .. }) | Expr::Tuple(ExprTuple { elems, .. }) => {
elems.iter().map(|e| eval_expr(e, state)).sum()
}
Expr::Unary(expr_unary) => eval_expr_unary(expr_unary, state),
Expr::Binary(expr_binary) => eval_expr_binary(expr_binary, state),
Expr::Assign(ExprAssign { left, right, .. })
| Expr::AssignOp(ExprAssignOp { left, right, .. })
| Expr::Index(ExprIndex {
expr: left,
index: right,
..
})
| Expr::Repeat(ExprRepeat {
expr: left,
len: right,
..
}) => eval_expr(left, state) + eval_expr(right, state),
Expr::Range(expr_range) => eval_expr_range(expr_range, state),
Expr::Async(ExprAsync { block, .. })
| Expr::Block(ExprBlock { block, .. })
| Expr::Loop(ExprLoop { body: block, .. })
| Expr::TryBlock(ExprTryBlock { block, .. })
| Expr::Unsafe(ExprUnsafe { block, .. }) => eval_block(block, state.increase_nesting()),
Expr::ForLoop(expr_for_loop) => eval_expr_for_loop(expr_for_loop, state),
Expr::While(expr_while) => eval_expr_while(expr_while, state),
Expr::Lit(_) | Expr::Path(_) => Index::zero(),
Expr::Await(ExprAwait { base: expr, .. })
| Expr::Box(ExprBox { expr, .. })
| Expr::Break(ExprBreak {
expr: Some(expr), ..
})
| Expr::Cast(ExprCast { expr, .. })
| Expr::Closure(ExprClosure { body: expr, .. })
| Expr::Field(ExprField { base: expr, .. })
| Expr::Group(ExprGroup { expr, .. })
| Expr::Let(ExprLet { expr, .. })
| Expr::Paren(ExprParen { expr, .. })
| Expr::Reference(ExprReference { expr, .. })
| Expr::Return(ExprReturn {
expr: Some(expr), ..
})
| Expr::Try(ExprTry { expr, .. })
| Expr::Type(ExprType { expr, .. })
| Expr::Yield(ExprYield {
expr: Some(expr), ..
}) => eval_expr(expr, state),
Expr::If(expr_if) => eval_expr_if(expr_if, state),
Expr::Match(expr_match) => eval_expr_match(expr_match, state),
Expr::Continue(_) | Expr::Break(_) => Index::one(),
Expr::Struct(expr_struct) => eval_expr_struct(expr_struct, state),
Expr::Call(expr_call) => eval_expr_call(expr_call, state),
Expr::MethodCall(expr_method_call) => eval_expr_method_call(expr_method_call, state),
Expr::Macro(_) => Index::zero(),
_ => Index::zero(),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn if_statement() {
let expr: Expr = parse_quote! {
if true { println!("test");
}
};
assert_eq!(expr.complexity(), 1);
}
#[test]
fn if_statement_nesting_increment() {
let expr: Expr = parse_quote! {
if true { if true { println!("test");
}
}
};
assert_eq!(expr.complexity(), 3);
}
#[test]
fn if_else_statement_no_nesting_increment() {
let expr: Expr = parse_quote! {
if true { if true { println!("test");
} else { println!("test");
}
}
};
assert_eq!(expr.complexity(), 4);
}
#[test]
fn for_loop() {
let expr: Expr = parse_quote! {
for element in iterable { if true { println!("test");
}
}
};
assert_eq!(expr.complexity(), 3);
}
#[test]
fn for_loop_nesting_increment() {
let expr: Expr = parse_quote! {
if true { for element in iterable { println!("test");
}
}
};
assert_eq!(expr.complexity(), 3);
}
#[test]
fn while_loop() {
let expr: Expr = parse_quote! {
while true { if true { println!("test");
}
}
};
assert_eq!(expr.complexity(), 3);
}
#[test]
fn while_loop_nesting_increment() {
let expr: Expr = parse_quote! {
if true { while true { println!("test");
}
}
};
assert_eq!(expr.complexity(), 3);
}
#[test]
fn match_statement_nesting_increment() {
let expr: Expr = parse_quote! {
if true { match true { true => println!("test"),
false => println!("test"),
}
}
};
assert_eq!(expr.complexity(), 3);
}
#[test]
fn logical_boolean_operators_same() {
let expr: Expr = parse_quote! { x && y };
assert_eq!(expr.complexity(), 1);
let expr: Expr = parse_quote! { x && y && z };
assert_eq!(expr.complexity(), 1);
let expr: Expr = parse_quote! { w && x && y && z };
assert_eq!(expr.complexity(), 1);
let expr: Expr = parse_quote! { x || y };
assert_eq!(expr.complexity(), 1);
let expr: Expr = parse_quote! { x || y || z };
assert_eq!(expr.complexity(), 1);
let expr: Expr = parse_quote! { w || x || y || z };
assert_eq!(expr.complexity(), 1);
}
#[test]
fn logical_boolean_operators_changing() {
let expr: Expr = parse_quote! { w && x || y || z };
assert_eq!(expr.complexity(), 2);
let expr: Expr = parse_quote! { w && x && y || z };
assert_eq!(expr.complexity(), 2);
let expr: Expr = parse_quote! { w && x || y && z };
assert_eq!(expr.complexity(), 3);
}
#[test]
fn logical_boolean_operators_not_operator() {
let expr: Expr = parse_quote! { !a && !b };
assert_eq!(expr.complexity(), 1);
let expr: Expr = parse_quote! { a && !(b && c) };
assert_eq!(expr.complexity(), 2);
let expr: Expr = parse_quote! { !(a || b) && !(c || d) };
assert_eq!(expr.complexity(), 3);
}
}