1use std::fmt;
2use std::fs::File;
3use std::io::BufReader;
4use std::io::prelude::*;
5use std::sync::OnceLock;
6
7use camino::Utf8Path;
8use regex::Regex;
9use tracing::*;
10
11#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
12pub enum ErrorKind {
13 Help,
14 Error,
15 Note,
16 Suggestion,
17 Warning,
18 Raw,
19}
20
21impl ErrorKind {
22 pub fn from_compiler_str(s: &str) -> ErrorKind {
23 match s {
24 "help" => ErrorKind::Help,
25 "error" | "error: internal compiler error" => ErrorKind::Error,
26 "note" | "failure-note" => ErrorKind::Note,
27 "warning" => ErrorKind::Warning,
28 _ => panic!("unexpected compiler diagnostic kind `{s}`"),
29 }
30 }
31
32 pub fn from_user_str(s: &str) -> ErrorKind {
35 match s {
36 "HELP" | "help" => ErrorKind::Help,
37 "ERROR" | "error" => ErrorKind::Error,
38 "NOTE" | "note" | "MONO_ITEM" => ErrorKind::Note,
41 "SUGGESTION" => ErrorKind::Suggestion,
42 "WARN" | "WARNING" | "warn" | "warning" => ErrorKind::Warning,
43 "RAW" => ErrorKind::Raw,
44 _ => panic!(
45 "unexpected diagnostic kind `{s}`, expected \
46 `ERROR`, `WARN`, `NOTE`, `HELP` or `SUGGESTION`"
47 ),
48 }
49 }
50}
51
52impl fmt::Display for ErrorKind {
53 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
54 match *self {
55 ErrorKind::Help => write!(f, "HELP"),
56 ErrorKind::Error => write!(f, "ERROR"),
57 ErrorKind::Note => write!(f, "NOTE"),
58 ErrorKind::Suggestion => write!(f, "SUGGESTION"),
59 ErrorKind::Warning => write!(f, "WARN"),
60 ErrorKind::Raw => write!(f, "RAW"),
61 }
62 }
63}
64
65#[derive(Debug)]
66pub struct Error {
67 pub line_num: Option<usize>,
68 pub kind: ErrorKind,
70 pub msg: String,
71 pub require_annotation: bool,
75}
76
77impl Error {
78 pub fn render_for_expected(&self) -> String {
79 use colored::Colorize;
80 format!("{: <10}line {: >3}: {}", self.kind, self.line_num_str(), self.msg.cyan())
81 }
82
83 pub fn line_num_str(&self) -> String {
84 self.line_num.map_or("?".to_string(), |line_num| line_num.to_string())
85 }
86}
87
88pub fn load_errors(testfile: &Utf8Path, revision: Option<&str>) -> Vec<Error> {
99 let rdr = BufReader::new(File::open(testfile.as_std_path()).unwrap());
100
101 let mut last_nonfollow_error = None;
110
111 rdr.lines()
112 .enumerate()
113 .filter(|(_, line)| line.is_ok())
115 .filter_map(|(line_num, line)| {
116 parse_expected(last_nonfollow_error, line_num + 1, &line.unwrap(), revision).map(
117 |(follow_prev, error)| {
118 if !follow_prev {
119 last_nonfollow_error = error.line_num;
120 }
121 error
122 },
123 )
124 })
125 .collect()
126}
127
128fn parse_expected(
129 last_nonfollow_error: Option<usize>,
130 line_num: usize,
131 line: &str,
132 test_revision: Option<&str>,
133) -> Option<(bool, Error)> {
134 static RE: OnceLock<Regex> = OnceLock::new();
145
146 let captures = RE
147 .get_or_init(|| {
148 Regex::new(r"//(?:\[(?P<revs>[\w\-,]+)])?~(?P<adjust>\?|\||[v\^]*)").unwrap()
149 })
150 .captures(line)?;
151
152 match (test_revision, captures.name("revs")) {
153 (Some(test_revision), Some(revision_filters)) => {
155 if !revision_filters.as_str().split(',').any(|r| r == test_revision) {
156 return None;
157 }
158 }
159
160 (None, Some(_)) => panic!("Only tests with revisions should use `//[X]~`"),
161
162 (Some(_), None) | (None, None) => {}
164 }
165
166 let tag = captures.get(0).unwrap();
168 let rest = line[tag.end()..].trim_start();
169 let (kind_str, _) =
170 rest.split_once(|c: char| c != '_' && !c.is_ascii_alphabetic()).unwrap_or((rest, ""));
171 let kind = ErrorKind::from_user_str(kind_str);
172 let untrimmed_msg = &rest[kind_str.len()..];
173 let msg = untrimmed_msg.strip_prefix(':').unwrap_or(untrimmed_msg).trim().to_owned();
174
175 let line_num_adjust = &captures["adjust"];
176 let (follow_prev, line_num) = if line_num_adjust == "|" {
177 (true, Some(last_nonfollow_error.expect("encountered //~| without preceding //~^ line")))
178 } else if line_num_adjust == "?" {
179 (false, None)
180 } else if line_num_adjust.starts_with('v') {
181 (false, Some(line_num + line_num_adjust.len()))
182 } else {
183 (false, Some(line_num - line_num_adjust.len()))
184 };
185
186 debug!(
187 "line={:?} tag={:?} follow_prev={:?} kind={:?} msg={:?}",
188 line_num,
189 tag.as_str(),
190 follow_prev,
191 kind,
192 msg
193 );
194 Some((follow_prev, Error { line_num, kind, msg, require_annotation: true }))
195}
196
197#[cfg(test)]
198mod tests;