obelix 0.2.0

Obélix is a tool to reduce Rust source files to produce MVEs
Documentation
use quote::ToTokens;

mod fold;

#[derive(Debug, serde::Deserialize, PartialEq)]
struct Span {
    label: Option<String>,
}

#[derive(Debug, serde::Deserialize, PartialEq)]
struct Code {
    code: String,
}

#[derive(Debug, serde::Deserialize, PartialEq)]
#[serde(rename_all = "kebab-case")]
enum Level {
    /// A fatal error that prevents compilation.
    Error,
    /// A possible error or concern.
    Warning,
    /// Additional information or context about the diagnostic.
    Note,
    /// A suggestion on how to resolve the diagnostic.
    Help,
    /// A note attached to the message for further information.
    FailureNote,
    /// Indicates a bug within the compiler.
    #[serde(rename = "error: internal compiler error")]
    Ice,
}

#[derive(Debug, serde::Deserialize, PartialEq)]
struct Message {
    message: String,
    code: Option<Code>,
    level: Level,
    spans: Vec<Span>,
    children: Vec<Message>,
}

static MESSAGES_RE_RAW: &[&str] = &[
    "first, the lifetime cannot outlive the lifetime `\'.*` as defined on the function body at (?P<R>.*)...",
];

static MESSAGES_REGEX_SET: once_cell::sync::Lazy<regex::RegexSet> =
    once_cell::sync::Lazy::new(|| regex::RegexSet::new(MESSAGES_RE_RAW).unwrap());

static MESSAGES_REGEXES: once_cell::sync::Lazy<Vec<regex::Regex>> =
    once_cell::sync::Lazy::new(|| {
        MESSAGES_RE_RAW
            .iter()
            .map(|r| regex::Regex::new(r).unwrap())
            .collect()
    });

impl Message {
    fn cleanup(&mut self) {
        for i in MESSAGES_REGEX_SET.matches(&self.message) {
            let re = &MESSAGES_REGEXES[i];

            self.message = re
                .replace_all(&self.message, |_: &regex::Captures| String::new())
                .to_string();
        }

        for m in &mut self.children {
            m.cleanup();
        }
    }
}

fn compile(content: &syn::File) -> Vec<Message> {
    let mut child = std::process::Command::new("rustc")
        .args(&[
            "--error-format=json",
            "-",
            "--out-dir",
            "/tmp",
            "--emit",
            "metadata",
            "--crate-type",
            "lib",
        ])
        .stdin(std::process::Stdio::piped())
        .stderr(std::process::Stdio::piped())
        .spawn()
        .expect("Failed to spawn rustc process");

    {
        use std::io::Write;

        let stdin = child.stdin.as_mut().expect("Failed to open rustc stdin");
        stdin
            .write_all(content.to_token_stream().to_string().as_bytes())
            .expect("Failed to write to stdin");
    }

    let output = child
        .wait_with_output()
        .expect("Failed to read rustc output")
        .stderr;

    output
        .split(|&b| b == b'\n')
        // filter empty lines out
        .filter(|s| !s.is_empty())
        .map(|line| serde_json::from_slice::<Message>(line).expect("Could not parse message"))
        .map(|mut m| {
            m.cleanup();
            m
        })
        .collect()
}

pub fn simplify(content: &mut syn::File) {
    loop {
        let expected_errors = compile(content);
        let mut made_change = false;

        made_change = fold::fold::<fold::SimplifyFunctionBodyVisitor>(&expected_errors, content) && made_change;
        made_change = fold::fold::<fold::RemoveItemImplVisitor>(&expected_errors, content) && made_change;
        made_change = fold::fold::<fold::RemoveReturnValueVisitor>(&expected_errors, content) && made_change;
        made_change = fold::fold::<fold::RemoveStatementVisitor>(&expected_errors, content) && made_change;

        if !made_change {
            return;
        }
    }
}