1use crate::common::*;
2use crate::test::TestInputType;
3
4#[derive(Diagnostic, Debug, thiserror::Error)]
5#[error("{test_from} failed")]
6#[diagnostic(help("see emitted diagnostics for details"))]
7pub struct TestFailed {
8 pub test_from: TestInputType,
9 #[label]
10 pub span: SourceSpan,
11 #[source_code]
12 pub input: ArcSource,
13 #[related]
14 pub errors: Vec<CheckFailedError>,
15}
16impl TestFailed {
17 pub fn new<'input, 'context: 'input>(
18 errors: Vec<CheckFailedError>,
19 context: &MatchContext<'input, 'context>,
20 ) -> Self {
21 let input_file = context.input_file();
22 Self {
23 test_from: TestInputType(context.match_file().name()),
24 span: input_file.span(),
25 input: input_file.clone(),
26 errors,
27 }
28 }
29
30 pub fn errors(&self) -> &[CheckFailedError] {
31 self.errors.as_slice()
32 }
33}
34
35#[derive(Diagnostic, Debug, thiserror::Error)]
36pub enum InvalidCheckFileError {
37 #[error("check file did not contain any rules")]
38 #[diagnostic()]
39 Empty,
40 #[error("invalid CHECK-LABEL pattern")]
41 #[diagnostic()]
42 CheckLabelVariable {
43 #[label("in this pattern")]
44 line: SourceSpan,
45 #[label("variables/substitutions are not allowed on CHECK-LABEL lines")]
46 var: SourceSpan,
47 },
48 #[error("{kind} directives are not permitted to be the first directive in a file")]
49 #[diagnostic()]
50 InvalidFirstCheck {
51 #[label]
52 line: SourceSpan,
53 kind: Check,
54 },
55 #[error("invalid CHECK pattern")]
56 #[diagnostic()]
57 EmptyPattern(#[label("expected a non-empty pattern here")] SourceSpan),
58}
59
60#[derive(Debug, Diagnostic, thiserror::Error)]
61#[diagnostic()]
62#[error("invalid cast to numeric value: {kind:?}")]
63pub struct InvalidNumericCastError {
64 #[label("occurs due to cast implied by this pattern")]
65 pub span: Option<SourceSpan>,
66 pub kind: std::num::IntErrorKind,
67 #[label("specifically, the value captured by this pattern is not of the correct format")]
68 pub specific_span: Option<SourceSpan>,
69 #[source_code]
70 pub match_file: ArcSource,
71}
72
73#[derive(Debug, Diagnostic, thiserror::Error)]
74#[error("reference to undefined variable '{name}'")]
75pub struct UndefinedVariableError {
76 #[label("occurs here")]
77 pub span: SourceSpan,
78 #[source_code]
79 pub match_file: ArcSource,
80 pub name: String,
81}
82
83#[derive(Diagnostic, Debug, thiserror::Error)]
84pub enum CheckFailedError {
85 #[error("the input file was rejected because it is empty, and --allow-empty was not set")]
86 EmptyInput,
87 #[error("an error occurred while processing a potential match")]
89 #[diagnostic()]
90 MatchError {
91 #[label("when matching against this input")]
92 span: SourceSpan,
93 #[source_code]
94 input_file: litcheck::diagnostics::ArcSource,
95 #[related]
96 labels: Vec<RelatedLabel>,
97 #[help]
98 help: Option<String>,
99 },
100 #[error("match found, but was excluded")]
102 #[diagnostic()]
103 MatchFoundButExcluded {
104 #[label("match found here")]
105 span: SourceSpan,
106 #[source_code]
107 input_file: litcheck::diagnostics::ArcSource,
108 #[related]
109 labels: Vec<RelatedLabel>,
110 },
111 #[error("match found for expected pattern, but on the wrong line")]
114 #[diagnostic()]
115 MatchFoundButWrongLine {
116 #[label("match found here")]
117 span: SourceSpan,
118 #[source_code]
119 input_file: litcheck::diagnostics::ArcSource,
120 #[related]
121 pattern: Option<RelatedCheckError>,
122 },
123 #[error("match found, but was discarded")]
125 #[diagnostic()]
126 MatchFoundButDiscarded {
127 #[label("match found here")]
128 span: SourceSpan,
129 #[source_code]
130 input_file: litcheck::diagnostics::ArcSource,
131 #[related]
132 labels: Vec<RelatedLabel>,
133 #[help]
134 note: Option<String>,
135 },
136 #[error("match found, but there was an error processing it")]
139 #[diagnostic()]
140 MatchFoundErrorNote {
141 #[label("match found here")]
142 span: SourceSpan,
143 #[source_code]
144 input_file: litcheck::diagnostics::ArcSource,
145 #[related]
146 pattern: Option<RelatedCheckError>,
147 #[help]
148 help: Option<String>,
149 },
150 #[error("match found, but there was an error when evaluating a constraint")]
153 #[diagnostic()]
154 MatchFoundConstraintFailed {
155 #[label("match found here")]
156 span: SourceSpan,
157 #[source_code]
158 input_file: litcheck::diagnostics::ArcSource,
159 #[related]
160 pattern: Option<RelatedCheckError>,
161 #[related]
162 error: Option<RelatedError>,
163 #[help]
164 help: Option<String>,
165 },
166 #[error("no matches were found for expected pattern")]
170 #[diagnostic()]
171 MatchNoneButExpected {
172 #[label("pattern at this location was not matched")]
173 span: SourceSpan,
174 #[source_code]
175 match_file: litcheck::diagnostics::ArcSource,
176 #[help]
177 note: Option<String>,
178 },
179 #[error("unable to match invalid pattern")]
184 #[diagnostic()]
185 MatchNoneForInvalidPattern {
186 #[label("pattern at this location was invalid")]
187 span: SourceSpan,
188 #[source_code]
189 match_file: litcheck::diagnostics::ArcSource,
190 #[related]
191 error: Option<RelatedError>,
192 },
193 #[error("error occurred while matching pattern")]
195 #[diagnostic()]
196 MatchNoneErrorNote {
197 #[label("when matching this pattern")]
198 span: SourceSpan,
199 #[source_code]
200 match_file: litcheck::diagnostics::ArcSource,
201 #[related]
202 error: Option<RelatedError>,
203 },
204 #[error("an exact match was not found, but some similar matches were found, see notes")]
207 #[diagnostic()]
208 MatchFuzzy {
209 #[label("pattern at this location was invalid")]
210 span: SourceSpan,
211 #[source_code]
212 match_file: litcheck::diagnostics::ArcSource,
213 #[help]
214 notes: Option<String>,
215 },
216 #[error("one or more matches were not found for a set of expected patterns")]
221 #[diagnostic(help("see diagnostics for details about each failed pattern"))]
222 MatchAllFailed {
223 #[related]
224 failed: Vec<CheckFailedError>,
225 },
226}
227impl CheckFailedError {
228 pub fn match_was_found(&self) -> bool {
230 matches!(
231 self,
232 Self::MatchFoundButExcluded { .. }
233 | Self::MatchFoundButWrongLine { .. }
234 | Self::MatchFoundButDiscarded { .. }
235 | Self::MatchFoundErrorNote { .. }
236 | Self::MatchFoundConstraintFailed { .. }
237 )
238 }
239}
240
241#[derive(Diagnostic, Debug, thiserror::Error)]
244#[error("check failed")]
245#[diagnostic()]
246pub struct RelatedCheckError {
247 #[label("due to pattern at this location")]
248 pub span: SourceSpan,
249 #[source_code]
250 pub match_file: litcheck::diagnostics::ArcSource,
251}
252
253#[derive(Debug, thiserror::Error)]
254#[error("see also")]
255pub struct RelatedLabel {
256 pub severity: litcheck::diagnostics::Severity,
257 pub labels: SmallVec<[Label; 1]>,
258 pub file: litcheck::diagnostics::ArcSource,
259}
260impl RelatedLabel {
261 pub fn error(label: Label, file: litcheck::diagnostics::ArcSource) -> Self {
262 Self {
263 severity: litcheck::diagnostics::Severity::Error,
264 labels: smallvec![label],
265 file,
266 }
267 }
268
269 pub fn warn(label: Label, file: litcheck::diagnostics::ArcSource) -> Self {
270 Self {
271 severity: litcheck::diagnostics::Severity::Warning,
272 labels: smallvec![label],
273 file,
274 }
275 }
276
277 pub fn note(label: Label, file: litcheck::diagnostics::ArcSource) -> Self {
278 Self {
279 severity: litcheck::diagnostics::Severity::Advice,
280 labels: smallvec![label],
281 file,
282 }
283 }
284
285 pub fn notes(
286 label: impl IntoIterator<Item = Label>,
287 file: litcheck::diagnostics::ArcSource,
288 ) -> Self {
289 Self {
290 severity: litcheck::diagnostics::Severity::Advice,
291 labels: label.into_iter().collect(),
292 file,
293 }
294 }
295}
296impl Diagnostic for RelatedLabel {
297 fn code<'a>(&'a self) -> Option<Box<dyn fmt::Display + 'a>> {
298 None
299 }
300 fn severity(&self) -> Option<litcheck::diagnostics::Severity> {
301 Some(self.severity)
302 }
303 fn help<'a>(&'a self) -> Option<Box<dyn fmt::Display + 'a>> {
304 None
305 }
306 fn url<'a>(&'a self) -> Option<Box<dyn fmt::Display + 'a>> {
307 None
308 }
309 fn source_code(&self) -> Option<&dyn litcheck::diagnostics::SourceCode> {
310 Some(&self.file)
311 }
312 fn labels(&self) -> Option<Box<dyn Iterator<Item = litcheck::diagnostics::LabeledSpan> + '_>> {
313 if self.labels.is_empty() {
314 None
315 } else {
316 Some(Box::new(self.labels.iter().cloned().map(|l| l.into())))
317 }
318 }
319 fn related<'a>(&'a self) -> Option<Box<dyn Iterator<Item = &'a dyn Diagnostic> + 'a>> {
320 None
321 }
322 fn diagnostic_source(&self) -> Option<&dyn Diagnostic> {
323 None
324 }
325}
326
327#[derive(Debug)]
329pub struct RelatedError(Report);
330impl RelatedError {
331 pub fn into_report(self) -> Report {
332 self.0
333 }
334
335 #[inline(always)]
336 pub fn as_diagnostic(&self) -> &dyn Diagnostic {
337 self.0.as_ref()
338 }
339}
340impl Diagnostic for RelatedError {
341 fn code<'a>(&'a self) -> Option<Box<dyn fmt::Display + 'a>> {
342 self.as_diagnostic().code()
343 }
344 fn severity(&self) -> Option<litcheck::diagnostics::Severity> {
345 self.as_diagnostic().severity()
346 }
347 fn help<'a>(&'a self) -> Option<Box<dyn fmt::Display + 'a>> {
348 self.as_diagnostic().help()
349 }
350 fn url<'a>(&'a self) -> Option<Box<dyn fmt::Display + 'a>> {
351 self.as_diagnostic().url()
352 }
353 fn source_code(&self) -> Option<&dyn litcheck::diagnostics::SourceCode> {
354 self.as_diagnostic().source_code()
355 }
356 fn labels(&self) -> Option<Box<dyn Iterator<Item = litcheck::diagnostics::LabeledSpan> + '_>> {
357 self.as_diagnostic().labels()
358 }
359 fn related<'a>(&'a self) -> Option<Box<dyn Iterator<Item = &'a dyn Diagnostic> + 'a>> {
360 self.as_diagnostic().related()
361 }
362 fn diagnostic_source(&self) -> Option<&dyn Diagnostic> {
363 self.as_diagnostic().diagnostic_source()
364 }
365}
366impl fmt::Display for RelatedError {
367 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
368 fmt::Display::fmt(&self.0, f)
369 }
370}
371impl std::error::Error for RelatedError {
372 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
373 AsRef::<dyn std::error::Error>::as_ref(&self.0).source()
374 }
375}
376impl From<Report> for RelatedError {
377 fn from(report: Report) -> Self {
378 Self(report)
379 }
380}
381impl RelatedError {
382 pub const fn new(report: Report) -> Self {
383 Self(report)
384 }
385
386 pub fn wrap<E>(error: E) -> Self
387 where
388 E: Diagnostic + Send + Sync + 'static,
389 {
390 Self(Report::new_boxed(Box::new(error)))
391 }
392}