Skip to main content

forge/mutation/mutators/
unary_op_mutator.rs

1use eyre::Result;
2use solar::ast::{ExprKind, Span, UnOpKind};
3
4use super::{MutationContext, Mutator};
5use crate::mutation::mutant::{Mutant, MutationType, UnaryOpMutated};
6
7pub struct UnaryOpMutator;
8
9impl Mutator for UnaryOpMutator {
10    fn generate_mutants(&self, context: &MutationContext<'_>) -> Result<Vec<Mutant>> {
11        let operations = vec![
12            UnOpKind::PreInc, // number
13            UnOpKind::PreDec, // n
14            UnOpKind::Neg,    // n @todo filter this one only for int
15            UnOpKind::BitNot, // n
16        ];
17
18        let post_fixed_operations = vec![UnOpKind::PostInc, UnOpKind::PostDec];
19
20        let expr = context.expr.unwrap();
21
22        let (op, target_span) = match &expr.kind {
23            ExprKind::Unary(un_op, target) => (un_op.kind, target.span),
24            _ => unreachable!(),
25        };
26
27        let target_content = extract_span_text(context.source.unwrap_or(""), target_span);
28        if target_content.is_empty() {
29            return Ok(vec![]);
30        }
31
32        let original = context.original_text();
33        let source_line = context.source_line();
34        let line_number = context.line_number();
35        let column_number = context.column_number();
36
37        // Bool has only the Not operator as possible target -> we try removing it
38        if op == UnOpKind::Not {
39            return Ok(vec![Mutant {
40                span: expr.span,
41                mutation: MutationType::UnaryOperator(UnaryOpMutated::new(
42                    target_content,
43                    UnOpKind::Not,
44                )),
45                path: context.path.clone(),
46                original,
47                source_line,
48                line_number,
49                column_number,
50            }]);
51        }
52
53        let mut mutations: Vec<Mutant>;
54
55        mutations = operations
56            .into_iter()
57            .filter(|&kind| kind != op)
58            .map(|kind| {
59                let new_expression = format!("{}{}", kind.to_str(), target_content);
60
61                let mutated = UnaryOpMutated::new(new_expression, kind);
62
63                Mutant {
64                    span: expr.span,
65                    mutation: MutationType::UnaryOperator(mutated),
66                    path: context.path.clone(),
67                    original: original.clone(),
68                    source_line: source_line.clone(),
69                    line_number,
70                    column_number,
71                }
72            })
73            .collect();
74
75        mutations.extend(post_fixed_operations.into_iter().filter(|&kind| kind != op).map(
76            |kind| {
77                let new_expression = format!("{}{}", target_content, kind.to_str());
78
79                let mutated = UnaryOpMutated::new(new_expression, kind);
80
81                Mutant {
82                    span: expr.span,
83                    mutation: MutationType::UnaryOperator(mutated),
84                    path: context.path.clone(),
85                    original: original.clone(),
86                    source_line: source_line.clone(),
87                    line_number,
88                    column_number,
89                }
90            },
91        ));
92
93        Ok(mutations)
94    }
95
96    fn is_applicable(&self, ctxt: &MutationContext<'_>) -> bool {
97        if let Some(expr) = ctxt.expr
98            && let ExprKind::Unary(_, _) = &expr.kind
99        {
100            return true;
101        }
102
103        false
104    }
105}
106
107fn extract_span_text(source: &str, span: Span) -> String {
108    let lo = span.lo().0 as usize;
109    let hi = span.hi().0 as usize;
110    source.get(lo..hi).map(str::trim).unwrap_or_default().to_string()
111}