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) -> u64;
}
impl Complexity for Expr {
fn complexity(&self) -> u64 {
eval_expr(self, Nesting::zero()).0.into()
}
}
impl Complexity for ItemFn {
fn complexity(&self) -> u64 {
eval_block(&self.block, Nesting::zero()).0.into()
}
}
impl Complexity for ImplItemMethod {
fn complexity(&self) -> u64 {
eval_block(&self.block, Nesting::zero()).0.into()
}
}
#[derive(Debug, Copy, Clone)]
struct Index(u32);
impl Index {
#[inline]
fn zero() -> Self {
Self(0)
}
#[inline]
fn one() -> Self {
Self(1)
}
#[inline]
fn with_nesting(n: Nesting) -> Self {
Self(1 + n.0)
}
}
impl ops::Add for Index {
type Output = Self;
#[inline]
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, Copy, Clone)]
struct Nesting(u32);
impl Nesting {
#[inline]
fn zero() -> Self {
Self(0)
}
#[inline]
fn increase(self) -> Self {
Self(self.0 + 1)
}
}
fn eval_block(block: &Block, nesting: Nesting) -> Index {
block
.stmts
.iter()
.map(|e| eval_stmt(e, nesting))
.sum::<Index>()
}
fn eval_stmt(stmt: &Stmt, nesting: Nesting) -> Index {
match stmt {
Stmt::Local(Local {
init: Some((_, expr)),
..
}) => eval_expr(expr, nesting),
Stmt::Local(Local { init: None, .. }) => Index::zero(),
Stmt::Item(item) => eval_item(item, nesting),
Stmt::Expr(expr) | Stmt::Semi(expr, _) => eval_expr(expr, nesting),
}
}
fn eval_item(item: &Item, n: Nesting) -> Index {
match item {
Item::Const(ItemConst { expr, .. }) => eval_expr(expr, n),
Item::Static(ItemStatic { expr, .. }) => eval_expr(expr, n),
_ => Index::zero(),
}
}
fn eval_expr(expr: &Expr, nesting: Nesting) -> Index {
match expr {
Expr::Array(ExprArray { elems, .. }) | Expr::Tuple(ExprTuple { elems, .. }) => {
elems.iter().map(|e| eval_expr(e, nesting)).sum()
}
Expr::Assign(ExprAssign { left, right, .. })
| Expr::AssignOp(ExprAssignOp { left, right, .. })
| Expr::Binary(ExprBinary { left, right, .. })
| Expr::Index(ExprIndex {
expr: left,
index: right,
..
})
| Expr::Repeat(ExprRepeat {
expr: left,
len: right,
..
}) => eval_expr(left, nesting) + eval_expr(right, nesting),
Expr::Range(ExprRange { from, to, .. }) => {
from.as_ref()
.map(|e| eval_expr(e, nesting))
.unwrap_or_else(Index::zero)
+ to.as_ref()
.map(|e| eval_expr(e, nesting))
.unwrap_or_else(Index::zero)
}
Expr::Async(ExprAsync { block, .. })
| Expr::Block(ExprBlock { block, .. })
| Expr::Loop(ExprLoop { body: block, .. })
| Expr::TryBlock(ExprTryBlock { block, .. })
| Expr::Unsafe(ExprUnsafe { block, .. }) => eval_block(block, nesting.increase()),
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::Unary(ExprUnary { expr, .. })
| Expr::Yield(ExprYield {
expr: Some(expr), ..
}) => eval_expr(expr, nesting),
Expr::If(ExprIf {
cond,
then_branch,
else_branch,
..
}) => {
Index::with_nesting(nesting)
+ eval_expr(cond, nesting)
+ eval_block(then_branch, nesting.increase())
+ else_branch
.as_ref()
.map(|(_, expr)| Index::one() + eval_expr(&expr, nesting.increase()))
.unwrap_or_else(Index::zero)
}
Expr::Match(ExprMatch { expr, arms, .. }) => {
Index::with_nesting(nesting)
+ eval_expr(expr, nesting)
+ arms
.iter()
.map(|arm| {
eval_opt_t_expr(&arm.guard, nesting)
+ eval_expr(&arm.body, nesting.increase())
})
.sum::<Index>()
}
Expr::ForLoop(ExprForLoop { expr, body, .. }) => {
Index::with_nesting(nesting)
+ eval_expr(expr, nesting)
+ eval_block(body, nesting.increase())
}
Expr::While(ExprWhile { cond, body, .. }) => {
Index::with_nesting(nesting)
+ eval_expr(cond, nesting)
+ eval_block(body, nesting.increase())
}
Expr::Continue(_) | Expr::Break(_) => Index::one(),
Expr::Struct(ExprStruct { fields, rest, .. }) => {
fields
.iter()
.map(|v| eval_expr(&v.expr, nesting))
.sum::<Index>()
+ rest
.as_ref()
.map(|e| eval_expr(e, nesting))
.unwrap_or_else(Index::zero)
}
Expr::Call(ExprCall { func, args, .. }) => {
eval_expr(func, nesting) + args.iter().map(|a| eval_expr(a, nesting)).sum::<Index>()
}
Expr::MethodCall(ExprMethodCall { receiver, args, .. }) => {
eval_expr(receiver, nesting) + args.iter().map(|a| eval_expr(a, nesting)).sum::<Index>()
}
_ => Index::zero(),
}
}
fn eval_opt_t_expr<T>(opt_expr: &Option<(T, Box<Expr>)>, nesting: Nesting) -> Index {
opt_expr
.as_ref()
.map(|(_, expr)| eval_expr(expr, nesting))
.unwrap_or_else(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);
}
}