1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
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;
        }
    }
}