vyre-conform 0.1.0

Conformance suite for vyre backends — proves byte-identical output to CPU reference
Documentation
//! Control-flow mutations.

use crate::adversarial::mutations::catalog::Branch;
use syn::spanned::Spanned;
use syn::visit::Visit;

/// Delete a branch by replacing the `if` condition with `false` or removing `else`.
#[inline]
pub fn apply_branch_delete(source: &str, branch: Branch) -> String {
    match branch {
        Branch::If => crate::adversarial::mutations::catalog::lexical::replace_code(
            source,
            "if ",
            "if false ",
            1,
        ),
        Branch::Else => {
            let syntax = match syn::parse_file(source) {
                Ok(s) => s,
                Err(_) => return source.to_string(),
            };
            let mut visitor = ElseDeleteVisitor { target: None };
            visitor.visit_file(&syntax);
            if let Some((start_lc, end_lc)) = visitor.target {
                let start =
                    crate::adversarial::mutations::catalog::lexical::line_column_to_byte_offset(
                        source,
                        start_lc.line,
                        start_lc.column,
                    );
                let end =
                    crate::adversarial::mutations::catalog::lexical::line_column_to_byte_offset(
                        source,
                        end_lc.line,
                        end_lc.column,
                    );
                let mut result = String::with_capacity(source.len() - (end - start));
                result.push_str(&source[..start]);
                result.push_str(&source[end..]);
                result
            } else {
                source.to_string()
            }
        }
    }
}

struct ElseDeleteVisitor {
    target: Option<(proc_macro2::LineColumn, proc_macro2::LineColumn)>,
}

impl<'ast> Visit<'ast> for ElseDeleteVisitor {
    fn visit_expr_if(&mut self, i: &'ast syn::ExprIf) {
        if self.target.is_some() {
            return;
        }
        if let Some((else_token, else_branch)) = &i.else_branch {
            self.target = Some((else_token.span.start(), else_branch.span().end()));
        }
        syn::visit::visit_expr_if(self, i);
    }
}

/// Invert the first executable `if` condition by wrapping it in `!(...)`.
#[inline]
pub fn apply_condition_invert(source: &str) -> String {
    let syntax = match syn::parse_file(source) {
        Ok(s) => s,
        Err(_) => return source.to_string(),
    };
    let mut visitor = ConditionInvertVisitor { target: None };
    visitor.visit_file(&syntax);
    if let Some((start_lc, end_lc)) = visitor.target {
        let start = crate::adversarial::mutations::catalog::lexical::line_column_to_byte_offset(
            source,
            start_lc.line,
            start_lc.column,
        );
        let end = crate::adversarial::mutations::catalog::lexical::line_column_to_byte_offset(
            source,
            end_lc.line,
            end_lc.column,
        );
        let mut result = String::with_capacity(source.len() + 3);
        result.push_str(&source[..start]);
        result.push_str("!(");
        result.push_str(&source[start..end]);
        result.push(')');
        result.push_str(&source[end..]);
        result
    } else {
        source.to_string()
    }
}

struct ConditionInvertVisitor {
    target: Option<(proc_macro2::LineColumn, proc_macro2::LineColumn)>,
}

impl<'ast> Visit<'ast> for ConditionInvertVisitor {
    fn visit_expr_if(&mut self, i: &'ast syn::ExprIf) {
        if self.target.is_some() {
            return;
        }
        // Skip `if let` — inverting a let-expression is not valid Rust.
        if !matches!(i.cond.as_ref(), syn::Expr::Let(_)) {
            self.target = Some((i.cond.span().start(), i.cond.span().end()));
        }
        syn::visit::visit_expr_if(self, i);
    }
}

/// Shift the end bound of the first `for` loop range by `by`.
#[inline]
pub fn apply_loop_end_shift(source: &str, by: i64) -> String {
    let syntax = match syn::parse_file(source) {
        Ok(s) => s,
        Err(_) => return source.to_string(),
    };
    let mut visitor = LoopEndShiftVisitor { target: None };
    visitor.visit_file(&syntax);
    if let Some((start_lc, end_lc)) = visitor.target {
        let start = crate::adversarial::mutations::catalog::lexical::line_column_to_byte_offset(
            source,
            start_lc.line,
            start_lc.column,
        );
        let end = crate::adversarial::mutations::catalog::lexical::line_column_to_byte_offset(
            source,
            end_lc.line,
            end_lc.column,
        );
        let rhs = &source[start..end];
        let replacement = if by >= 0 {
            format!("({}).saturating_add({})", rhs, by)
        } else {
            format!("({}).saturating_sub({})", rhs, by.unsigned_abs())
        };
        let mut result = String::with_capacity(source.len() - (end - start) + replacement.len());
        result.push_str(&source[..start]);
        result.push_str(&replacement);
        result.push_str(&source[end..]);
        result
    } else {
        source.to_string()
    }
}

struct LoopEndShiftVisitor {
    target: Option<(proc_macro2::LineColumn, proc_macro2::LineColumn)>,
}

impl<'ast> Visit<'ast> for LoopEndShiftVisitor {
    fn visit_expr_range(&mut self, i: &'ast syn::ExprRange) {
        if self.target.is_some() {
            return;
        }
        if let Some(end) = &i.end {
            self.target = Some((end.span().start(), end.span().end()));
        }
        syn::visit::visit_expr_range(self, i);
    }
}

/// Shift the start bound of the first `for` loop range from `0` to `by`.
#[inline]
pub fn apply_loop_start_shift(source: &str, by: i32) -> String {
    if let Some(for_pos) =
        crate::adversarial::mutations::catalog::lexical::find_code(source, "for ")
    {
        let rest = &source[for_pos + 4..];
        if let Some(range_pos) = rest.find("..") {
            let prefix_end = for_pos + 4 + range_pos;
            let prefix = &source[..prefix_end];
            if let Some(start_pos) = prefix.rfind('0') {
                let left_ok = start_pos
                    .checked_sub(1)
                    .and_then(|j| prefix.get(j..=j))
                    .map(|s| {
                        let c = s.chars().next().unwrap();
                        c.is_whitespace() || c == '=' || c == '(' || c == '[' || c == ','
                    })
                    .unwrap_or(true);
                if left_ok {
                    return format!("{}{}{}", &source[..start_pos], by, &source[start_pos + 1..]);
                }
            }
        }
    }
    source.to_string()
}

/// Replace the first executable `break` with `continue`.
#[inline]
pub fn apply_break_to_continue(source: &str) -> String {
    crate::adversarial::mutations::catalog::lexical::replace_code_word(
        source, "break", "continue", 1,
    )
}

/// Remove the first executable `continue;`.
#[inline]
pub fn apply_continue_remove(source: &str) -> String {
    crate::adversarial::mutations::catalog::lexical::replace_code(source, "continue;", "", 1)
}

/// Skip the first executable `if` branch by forcing its condition false.
#[inline]
pub fn apply_flow_skip(source: &str) -> String {
    crate::adversarial::mutations::catalog::lexical::replace_code(source, "if ", "if false && ", 1)
}