use super::*;
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct CoverageReport {
pub total: usize,
pub mir_covered: usize,
pub trap_stub: usize,
}
impl CoverageReport {
pub fn ratio(&self) -> f64 {
if self.total == 0 {
0.0
} else {
self.mir_covered as f64 / self.total as f64
}
}
}
pub fn coverage_report(program: &MirProgram) -> CoverageReport {
let mut report = CoverageReport::default();
for (_, mir_fn) in program.iter() {
report.total += 1;
if mir_expr_coverable(&mir_fn.body) {
report.mir_covered += 1;
} else {
report.trap_stub += 1;
}
}
report
}
pub(crate) fn mir_expr_coverable(expr: &Spanned<MirExpr>) -> bool {
match &expr.node {
MirExpr::Literal(_) | MirExpr::Local(_) | MirExpr::FnValue(_) => true,
MirExpr::BinOp(spanned_binop) => {
let bop = &spanned_binop.node;
let numeric = matches!(bop.lhs.ty(), Some(Type::Int) | Some(Type::Float));
let string_op = aver_type_str_of(&bop.lhs).trim() == "String"
&& matches!(
bop.op,
BinOp::Add
| BinOp::Eq
| BinOp::Neq
| BinOp::Lt
| BinOp::Gt
| BinOp::Lte
| BinOp::Gte
);
(numeric || string_op) && mir_expr_coverable(&bop.lhs) && mir_expr_coverable(&bop.rhs)
}
MirExpr::Neg(inner) | MirExpr::Return(inner) => mir_expr_coverable(inner),
MirExpr::Let(spanned_let) => {
let l = &spanned_let.node;
mir_expr_coverable(&l.value) && mir_expr_coverable(&l.body)
}
MirExpr::Call(spanned_call) => {
matches!(
spanned_call.node.callee,
MirCallee::Fn(_) | MirCallee::LocalSlot { .. }
) && spanned_call.node.args.iter().all(mir_expr_coverable)
}
MirExpr::TailCall(spanned_tc) => spanned_tc.node.args.iter().all(mir_expr_coverable),
MirExpr::Construct(spanned_ctor) => spanned_ctor.node.args.iter().all(mir_expr_coverable),
MirExpr::RecordCreate(spanned_rec) => spanned_rec
.node
.fields
.iter()
.all(|f| mir_expr_coverable(&f.value)),
MirExpr::RecordUpdate(spanned_upd) => {
mir_expr_coverable(&spanned_upd.node.base)
&& spanned_upd
.node
.updates
.iter()
.all(|f| mir_expr_coverable(&f.value))
}
MirExpr::Project(spanned_proj) => mir_expr_coverable(&spanned_proj.node.base),
MirExpr::Tuple(items) => items.iter().all(mir_expr_coverable),
MirExpr::MapLiteral(entries) => entries
.iter()
.all(|(k, v)| mir_expr_coverable(k) && mir_expr_coverable(v)),
MirExpr::List(items) => items.iter().all(mir_expr_coverable),
MirExpr::Try(inner) => mir_expr_coverable(inner),
MirExpr::IndependentProduct(spanned_ip) => {
spanned_ip.node.items.iter().all(mir_expr_coverable)
}
MirExpr::InterpolatedStr(parts) => parts.iter().all(|p| match p {
MirStrPart::Literal(_) => true,
MirStrPart::Expr(e) => mir_expr_coverable(e),
}),
MirExpr::Match(spanned_match) => {
let m = &spanned_match.node;
let unsupported_pat = m
.arms
.iter()
.any(|a| matches!(a.pattern, MirPattern::Tuple(_)));
let is_primitive = matches!(m.subject.ty(), Some(Type::Bool | Type::Int | Type::Str))
&& !m.arms.iter().any(|a| {
matches!(
a.pattern,
MirPattern::Bind(..)
| MirPattern::Ctor { .. }
| MirPattern::EmptyList
| MirPattern::Cons { .. }
)
});
let is_result_or_option = m.arms.iter().any(arm_is_mir_result_ctor)
|| m.arms.iter().any(arm_is_mir_option_ctor);
let is_list = m
.arms
.iter()
.any(|a| matches!(a.pattern, MirPattern::EmptyList | MirPattern::Cons { .. }));
let is_variant = m.arms.iter().any(|a| {
matches!(
a.pattern,
MirPattern::Ctor {
ctor: MirCtor::User(_),
..
}
)
});
!m.arms.is_empty()
&& !unsupported_pat
&& (is_primitive || is_result_or_option || is_list || is_variant)
&& mir_expr_coverable(&m.subject)
&& m.arms.iter().all(|a| mir_expr_coverable(&a.body))
}
_ => false,
}
}