1use crate::common::*;
2use crate::test::TestInputType;
3
4#[derive(Diagnostic, Debug, thiserror::Error)]
5#[error("{test_from} failed")]
6#[diagnostic(help("see below for details"))]
7pub struct TestFailed {
8 pub test_from: TestInputType,
9 #[related]
10 pub errors: Vec<CheckFailedError>,
11}
12impl TestFailed {
13 pub fn new<'input, 'context: 'input>(
14 errors: Vec<CheckFailedError>,
15 context: &MatchContext<'input, 'context>,
16 ) -> Self {
17 Self {
18 test_from: TestInputType(context.match_file().uri().clone()),
19 errors,
20 }
21 }
22
23 pub fn errors(&self) -> &[CheckFailedError] {
24 self.errors.as_slice()
25 }
26}
27
28#[derive(Diagnostic, Debug, thiserror::Error)]
29pub enum InvalidCheckFileError {
30 #[error("check file did not contain any rules")]
31 #[diagnostic()]
32 Empty,
33 #[error("invalid CHECK-LABEL pattern")]
34 #[diagnostic()]
35 CheckLabelVariable {
36 #[label("in this pattern")]
37 line: SourceSpan,
38 #[label("variables/substitutions are not allowed on CHECK-LABEL lines")]
39 var: SourceSpan,
40 },
41 #[error("{kind} directives are not permitted to be the first directive in a file")]
42 #[diagnostic()]
43 InvalidFirstCheck {
44 #[label]
45 line: SourceSpan,
46 kind: Check,
47 },
48 #[error("invalid CHECK pattern")]
49 #[diagnostic()]
50 EmptyPattern(#[label("expected a non-empty pattern here")] SourceSpan),
51}
52
53#[derive(Debug, Diagnostic, thiserror::Error)]
54#[diagnostic()]
55#[error("invalid cast to numeric value: {kind:?}")]
56pub struct InvalidNumericCastError {
57 #[label("occurs due to cast implied by this pattern")]
58 pub span: Option<SourceSpan>,
59 pub kind: std::num::IntErrorKind,
60 #[label("specifically, the value captured by this pattern is not of the correct format")]
61 pub specific_span: Option<SourceSpan>,
62 #[source_code]
63 pub match_file: Arc<SourceFile>,
64}
65
66#[derive(Debug, Diagnostic, thiserror::Error)]
67#[error("reference to undefined variable '{name}'")]
68pub struct UndefinedVariableError {
69 #[label("occurs here")]
70 pub span: SourceSpan,
71 #[source_code]
72 pub match_file: Arc<SourceFile>,
73 pub name: Symbol,
74}
75
76#[derive(Diagnostic, Debug, thiserror::Error)]
77pub enum CheckFailedError {
78 #[error("the input file was rejected because it is empty, and --allow-empty was not set")]
79 #[diagnostic(
80 help = "if your input was the piped output of a command, it may have succeeded with no output when you expected it to fail"
81 )]
82 EmptyInput,
83 #[error("an error occurred while processing a potential match")]
85 #[diagnostic()]
86 MatchError {
87 #[label(primary, "when matching against this input")]
88 span: SourceSpan,
89 #[source_code]
90 input_file: Arc<SourceFile>,
91 #[related]
92 labels: Vec<RelatedLabel>,
93 #[help]
94 help: Option<String>,
95 },
96 #[error("match found, but was excluded")]
98 #[diagnostic()]
99 MatchFoundButExcluded {
100 #[label(primary, "match found here")]
101 span: SourceSpan,
102 #[source_code]
103 input_file: Arc<SourceFile>,
104 #[related]
105 labels: Vec<RelatedLabel>,
106 },
107 #[error("match found for expected pattern, but on the wrong line")]
110 #[diagnostic()]
111 MatchFoundButWrongLine {
112 #[label(primary, "match found here")]
113 span: SourceSpan,
114 #[source_code]
115 input_file: Arc<SourceFile>,
116 #[related]
117 pattern: Option<RelatedCheckError>,
118 },
119 #[error("match found, but was discarded")]
121 #[diagnostic()]
122 MatchFoundButDiscarded {
123 #[label(primary, "match found here")]
124 span: SourceSpan,
125 #[source_code]
126 input_file: Arc<SourceFile>,
127 #[related]
128 labels: Vec<RelatedLabel>,
129 #[help]
130 note: Option<String>,
131 },
132 #[error("match found, but there was an error processing it")]
135 #[diagnostic()]
136 MatchFoundErrorNote {
137 #[label(primary, "match found here")]
138 span: SourceSpan,
139 #[source_code]
140 input_file: Arc<SourceFile>,
141 #[related]
142 pattern: Option<RelatedCheckError>,
143 #[help]
144 help: Option<String>,
145 },
146 #[error("match found, but there was an error when evaluating a constraint")]
149 #[diagnostic()]
150 MatchFoundConstraintFailed {
151 #[label(primary, "match found here")]
152 span: SourceSpan,
153 #[source_code]
154 input_file: Arc<SourceFile>,
155 #[related]
156 pattern: Option<RelatedCheckError>,
157 #[related]
158 error: Option<RelatedError>,
159 #[help]
160 help: Option<String>,
161 },
162 #[error("no matches were found for expected pattern")]
166 #[diagnostic()]
167 MatchNoneButExpected {
168 #[label(primary, "pattern at this location was not matched")]
169 span: SourceSpan,
170 #[source_code]
171 match_file: Arc<SourceFile>,
172 #[help]
173 note: Option<String>,
174 },
175 #[error("unable to match invalid pattern")]
180 #[diagnostic()]
181 MatchNoneForInvalidPattern {
182 #[label(primary, "pattern at this location was invalid")]
183 span: SourceSpan,
184 #[source_code]
185 match_file: Arc<SourceFile>,
186 #[related]
187 error: Option<RelatedError>,
188 },
189 #[error("error occurred while matching pattern")]
191 #[diagnostic()]
192 MatchNoneErrorNote {
193 #[label(primary, "when matching this pattern")]
194 span: SourceSpan,
195 #[source_code]
196 match_file: Arc<SourceFile>,
197 #[related]
198 error: Option<RelatedError>,
199 },
200 #[error("an exact match was not found, but some similar matches were found, see notes")]
203 #[diagnostic()]
204 MatchFuzzy {
205 #[label(primary, "pattern at this location was invalid")]
206 span: SourceSpan,
207 #[source_code]
208 match_file: Arc<SourceFile>,
209 #[help]
210 notes: Option<String>,
211 },
212 #[error("one or more matches were not found for a set of expected patterns")]
217 #[diagnostic(help("see diagnostics for details about each failed pattern"))]
218 MatchAllFailed {
219 #[related]
220 failed: Vec<CheckFailedError>,
221 },
222 #[error("unable to match all instances of repeat pattern (matched {n} of {count} times)")]
223 #[diagnostic(help("see related errors below for additional details"))]
224 MatchRepeatedError {
225 #[label(primary, "when matching this pattern for the {}th time", n + 1)]
226 span: SourceSpan,
227 #[source_code]
228 match_file: Arc<SourceFile>,
229 n: usize,
230 count: usize,
231 #[label(collection)]
232 related: Vec<LabeledSpan>,
233 },
234 #[error("one or more matches were not found for a set of expected patterns")]
235 #[diagnostic(help("see related error for more information"))]
236 MatchGroupFailed {
237 #[label(primary, "this check failed")]
238 span: SourceSpan,
239 #[source_code]
240 match_file: Arc<SourceFile>,
241 #[related]
242 cause: Vec<CheckFailedError>,
243 #[label("these checks were skipped because they were dependent on the check that failed")]
244 skipped: Option<SourceSpan>,
245 },
246}
247impl CheckFailedError {
248 pub fn match_was_found(&self) -> bool {
250 matches!(
251 self,
252 Self::MatchFoundButExcluded { .. }
253 | Self::MatchFoundButWrongLine { .. }
254 | Self::MatchFoundButDiscarded { .. }
255 | Self::MatchFoundErrorNote { .. }
256 | Self::MatchFoundConstraintFailed { .. }
257 )
258 }
259
260 pub fn related_labels_for(&self, related_span: SourceSpan) -> Vec<LabeledSpan> {
261 use CheckFailedError::*;
262 let mut related = vec![];
263 let related_source_id = related_span.source_id();
264 match self {
265 EmptyInput => (),
266 err @ (MatchError { span, labels, .. }
267 | MatchFoundButExcluded { span, labels, .. }
268 | MatchFoundButDiscarded { span, labels, .. }) => {
269 if span.source_id() == related_source_id {
270 related.push(LabeledSpan::new_with_span(Some(err.to_string()), *span));
271 }
272 for label in labels {
273 if label.file.id() == related_source_id {
274 for label in label.labels.iter() {
275 related.push(LabeledSpan::new_with_span(
276 label.label().map(|s| s.to_string()),
277 label.span(),
278 ))
279 }
280 }
281 }
282 }
283 err @ (MatchFoundButWrongLine { span, pattern, .. }
284 | MatchFoundErrorNote { span, pattern, .. }) => {
285 if span.source_id() == related_source_id {
286 related.push(LabeledSpan::new_with_span(Some(err.to_string()), *span));
287 }
288 if let Some(pattern) = pattern.as_ref()
289 && pattern.span.source_id() == related_source_id
290 {
291 related.push(LabeledSpan::new_with_span(
292 Some("due to pattern at this location".to_string()),
293 pattern.span,
294 ));
295 }
296 }
297 err @ MatchFoundConstraintFailed {
298 span,
299 pattern,
300 error,
301 ..
302 } => {
303 if span.source_id() == related_source_id {
304 related.push(LabeledSpan::new_with_span(Some(err.to_string()), *span));
305 if let Some(error) = error.as_ref() {
306 related.push(LabeledSpan::new_with_span(Some(error.to_string()), *span));
307 }
308 }
309 if let Some(pattern) = pattern.as_ref()
310 && pattern.span.source_id() == related_source_id
311 {
312 related.push(LabeledSpan::new_with_span(
313 Some("due to pattern at this location".to_string()),
314 pattern.span,
315 ));
316 }
317 }
318 err @ MatchNoneButExpected { span, .. } => {
319 let message = err.to_string();
320 related.push(LabeledSpan::new_with_span(Some(message), *span));
321 }
322 err @ MatchNoneForInvalidPattern { span, error, .. } => {
323 if span.source_id() == related_source_id {
324 let message = err.to_string();
325 related.push(LabeledSpan::new_with_span(Some(message), *span));
326 if let Some(error) = error.as_ref() {
327 related.push(LabeledSpan::new_with_span(Some(error.to_string()), *span));
328 }
329 }
330 }
331 MatchNoneErrorNote { span, error, .. } => {
332 if span.source_id() == related_source_id
333 && let Some(error) = error.as_ref()
334 {
335 related.push(LabeledSpan::new_with_span(Some(error.to_string()), *span));
336 }
337 }
338 err @ MatchFuzzy { span, notes, .. } => {
339 if span.source_id() == related_source_id {
340 let message = err.to_string();
341 related.push(LabeledSpan::new_with_span(Some(message), *span));
342 if let Some(notes) = notes.clone() {
343 related.push(LabeledSpan::new_with_span(Some(notes), *span));
344 }
345 }
346 }
347 MatchAllFailed { .. } | MatchRepeatedError { .. } | MatchGroupFailed { .. } => (),
348 }
349
350 related
351 }
352}
353
354#[derive(Diagnostic, Debug, thiserror::Error)]
357#[error("check failed")]
358#[diagnostic()]
359pub struct RelatedCheckError {
360 #[label("due to pattern at this location")]
361 pub span: SourceSpan,
362 #[source_code]
363 pub match_file: Arc<SourceFile>,
364}
365
366#[derive(Debug, thiserror::Error)]
367#[error("see also")]
368pub struct RelatedLabel {
369 pub severity: litcheck::diagnostics::Severity,
370 pub labels: SmallVec<[Label; 1]>,
371 pub file: Arc<SourceFile>,
372}
373impl RelatedLabel {
374 pub fn error(label: Label, file: Arc<SourceFile>) -> Self {
375 Self {
376 severity: litcheck::diagnostics::Severity::Error,
377 labels: smallvec![label],
378 file,
379 }
380 }
381
382 pub fn warn(label: Label, file: Arc<SourceFile>) -> Self {
383 Self {
384 severity: litcheck::diagnostics::Severity::Warning,
385 labels: smallvec![label],
386 file,
387 }
388 }
389
390 pub fn note(label: Label, file: Arc<SourceFile>) -> Self {
391 Self {
392 severity: litcheck::diagnostics::Severity::Advice,
393 labels: smallvec![label],
394 file,
395 }
396 }
397
398 pub fn notes(label: impl IntoIterator<Item = Label>, file: Arc<SourceFile>) -> Self {
399 Self {
400 severity: litcheck::diagnostics::Severity::Advice,
401 labels: label.into_iter().collect(),
402 file,
403 }
404 }
405}
406impl Diagnostic for RelatedLabel {
407 fn code<'a>(&'a self) -> Option<Box<dyn fmt::Display + 'a>> {
408 None
409 }
410 fn severity(&self) -> Option<litcheck::diagnostics::Severity> {
411 Some(self.severity)
412 }
413 fn help<'a>(&'a self) -> Option<Box<dyn fmt::Display + 'a>> {
414 None
415 }
416 fn url<'a>(&'a self) -> Option<Box<dyn fmt::Display + 'a>> {
417 None
418 }
419 fn source_code(&self) -> Option<&dyn litcheck::diagnostics::SourceCode> {
420 Some(&self.file)
421 }
422 fn labels(&self) -> Option<Box<dyn Iterator<Item = litcheck::diagnostics::LabeledSpan> + '_>> {
423 if self.labels.is_empty() {
424 None
425 } else {
426 Some(Box::new(self.labels.iter().cloned().map(|l| l.into())))
427 }
428 }
429 fn related<'a>(&'a self) -> Option<Box<dyn Iterator<Item = &'a dyn Diagnostic> + 'a>> {
430 None
431 }
432 fn diagnostic_source(&self) -> Option<&dyn Diagnostic> {
433 None
434 }
435}
436
437#[derive(Debug)]
439pub struct RelatedError(Report);
440impl RelatedError {
441 pub fn into_report(self) -> Report {
442 self.0
443 }
444
445 #[inline(always)]
446 pub fn as_diagnostic(&self) -> &dyn Diagnostic {
447 self.0.as_ref()
448 }
449}
450impl Diagnostic for RelatedError {
451 fn code<'a>(&'a self) -> Option<Box<dyn fmt::Display + 'a>> {
452 self.as_diagnostic().code()
453 }
454 fn severity(&self) -> Option<litcheck::diagnostics::Severity> {
455 self.as_diagnostic().severity()
456 }
457 fn help<'a>(&'a self) -> Option<Box<dyn fmt::Display + 'a>> {
458 self.as_diagnostic().help()
459 }
460 fn url<'a>(&'a self) -> Option<Box<dyn fmt::Display + 'a>> {
461 self.as_diagnostic().url()
462 }
463 fn source_code(&self) -> Option<&dyn litcheck::diagnostics::SourceCode> {
464 self.as_diagnostic().source_code()
465 }
466 fn labels(&self) -> Option<Box<dyn Iterator<Item = litcheck::diagnostics::LabeledSpan> + '_>> {
467 self.as_diagnostic().labels()
468 }
469 fn related<'a>(&'a self) -> Option<Box<dyn Iterator<Item = &'a dyn Diagnostic> + 'a>> {
470 self.as_diagnostic().related()
471 }
472 fn diagnostic_source(&self) -> Option<&dyn Diagnostic> {
473 self.as_diagnostic().diagnostic_source()
474 }
475}
476impl fmt::Display for RelatedError {
477 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
478 fmt::Display::fmt(&self.0, f)
479 }
480}
481impl std::error::Error for RelatedError {
482 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
483 AsRef::<dyn std::error::Error>::as_ref(&self.0).source()
484 }
485}
486impl From<Report> for RelatedError {
487 fn from(report: Report) -> Self {
488 Self(report)
489 }
490}
491impl RelatedError {
492 pub const fn new(report: Report) -> Self {
493 Self(report)
494 }
495
496 pub fn wrap<E>(error: E) -> Self
497 where
498 E: Diagnostic + Send + Sync + 'static,
499 {
500 Self(Report::new_boxed(Box::new(error)))
501 }
502}