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 Unknown,
21}
22
23impl ErrorKind {
24 pub fn from_compiler_str(s: &str) -> ErrorKind {
25 match s {
26 "help" => ErrorKind::Help,
27 "error" | "error: internal compiler error" => ErrorKind::Error,
28 "note" | "failure-note" => ErrorKind::Note,
29 "warning" => ErrorKind::Warning,
30 _ => panic!("unexpected compiler diagnostic kind `{s}`"),
31 }
32 }
33
34 fn from_user_str(s: &str) -> Option<ErrorKind> {
37 Some(match s {
38 "HELP" | "help" => ErrorKind::Help,
39 "ERROR" | "error" => ErrorKind::Error,
40 "NOTE" | "note" => ErrorKind::Note,
41 "SUGGESTION" => ErrorKind::Suggestion,
42 "WARN" | "WARNING" | "warn" | "warning" => ErrorKind::Warning,
43 "RAW" => ErrorKind::Raw,
44 _ => return None,
45 })
46 }
47
48 pub fn expect_from_user_str(s: &str) -> ErrorKind {
49 ErrorKind::from_user_str(s).unwrap_or_else(|| {
50 panic!(
51 "unexpected diagnostic kind `{s}`, expected \
52 `ERROR`, `WARN`, `NOTE`, `HELP`, `SUGGESTION` or `RAW`"
53 )
54 })
55 }
56}
57
58impl fmt::Display for ErrorKind {
59 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
60 match *self {
61 ErrorKind::Help => write!(f, "HELP"),
62 ErrorKind::Error => write!(f, "ERROR"),
63 ErrorKind::Note => write!(f, "NOTE"),
64 ErrorKind::Suggestion => write!(f, "SUGGESTION"),
65 ErrorKind::Warning => write!(f, "WARN"),
66 ErrorKind::Raw => write!(f, "RAW"),
67 ErrorKind::Unknown => write!(f, "UNKNOWN"),
68 }
69 }
70}
71
72#[derive(Debug)]
73pub struct Error {
74 pub line_num: Option<usize>,
75 pub column_num: Option<usize>,
76 pub kind: ErrorKind,
78 pub msg: String,
79 pub require_annotation: bool,
83}
84
85pub fn load_errors(testfile: &Utf8Path, revision: Option<&str>) -> Vec<Error> {
96 let rdr = BufReader::new(File::open(testfile.as_std_path()).unwrap());
97
98 let mut last_nonfollow_error = None;
107
108 rdr.lines()
109 .enumerate()
110 .filter(|(_, line)| line.is_ok())
112 .filter_map(|(line_num, line)| {
113 parse_expected(last_nonfollow_error, line_num + 1, &line.unwrap(), revision).map(
114 |(follow_prev, error)| {
115 if !follow_prev {
116 last_nonfollow_error = error.line_num;
117 }
118 error
119 },
120 )
121 })
122 .collect()
123}
124
125fn parse_expected(
126 last_nonfollow_error: Option<usize>,
127 line_num: usize,
128 line: &str,
129 test_revision: Option<&str>,
130) -> Option<(bool, Error)> {
131 static RE: OnceLock<Regex> = OnceLock::new();
142
143 let captures = RE
144 .get_or_init(|| {
145 Regex::new(r"//(?:\[(?P<revs>[\w\-,]+)])?~(?P<adjust>\?|\||[v\^]*)").unwrap()
146 })
147 .captures(line)?;
148
149 match (test_revision, captures.name("revs")) {
150 (Some(test_revision), Some(revision_filters)) => {
152 if !revision_filters.as_str().split(',').any(|r| r == test_revision) {
153 return None;
154 }
155 }
156
157 (None, Some(_)) => panic!("Only tests with revisions should use `//[X]~`"),
158
159 (Some(_), None) | (None, None) => {}
161 }
162
163 let tag = captures.get(0).unwrap();
165 let rest = line[tag.end()..].trim_start();
166 let (kind_str, _) =
167 rest.split_once(|c: char| c != '_' && !c.is_ascii_alphabetic()).unwrap_or((rest, ""));
168 let (kind, untrimmed_msg) = match ErrorKind::from_user_str(kind_str) {
169 Some(kind) => (kind, &rest[kind_str.len()..]),
170 None => (ErrorKind::Unknown, rest),
171 };
172 let msg = untrimmed_msg.strip_prefix(':').unwrap_or(untrimmed_msg).trim().to_owned();
173
174 let line_num_adjust = &captures["adjust"];
175 let (follow_prev, line_num) = if line_num_adjust == "|" {
176 (true, Some(last_nonfollow_error.expect("encountered //~| without preceding //~^ line")))
177 } else if line_num_adjust == "?" {
178 (false, None)
179 } else if line_num_adjust.starts_with('v') {
180 (false, Some(line_num + line_num_adjust.len()))
181 } else {
182 (false, Some(line_num - line_num_adjust.len()))
183 };
184 let column_num = Some(tag.start() + 1);
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, column_num, kind, msg, require_annotation: true }))
195}
196
197#[cfg(test)]
198mod tests;