litcheck_filecheck/
errors.rs

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    /// Indicates an error while processing a potential match
88    #[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    /// Indicates a match for an excluded pattern.
101    #[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    /// Indicates a match for an expected pattern, but the match is on the
112    /// wrong line.
113    #[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    /// Indicates a discarded match for an expected pattern.
124    #[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    /// Indicates an error while processing a match after the match was found
137    /// for an expected or excluded pattern.
138    #[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    /// Indicates an error while processing a match after the match was found
151    /// for an expected or excluded pattern.
152    #[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    /// Indicates no match for an expected pattern, but this might follow good
167    /// matches when multiple matches are expected for the pattern, or it might
168    /// follow discarded matches for the pattern.
169    #[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    /// Indicates no match due to an expected or excluded pattern that has
180    /// proven to be invalid at match time.  The exact problems are usually
181    /// reported in subsequent diagnostics of the same match type but with
182    /// `Note` set.
183    #[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    /// Indicates a match attempt failed for unknown reasons
194    #[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    /// Indicates a fuzzy match that serves as a suggestion for the next
205    /// intended match for an expected pattern with too few or no good matches.
206    #[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    /// Indicates that matching all patterns in a set of patterns failed due
217    /// to at least one pattern not being matched.
218    ///
219    /// This occurs with CHECK-DAG/CHECK-NOT which are evaluated in groups
220    #[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    /// Returns true if this error was produced in the context of a possibly-valid match
229    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/// This is used to associated source spans from the match file
242/// with those from the input file.
243#[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/// This type wraps related diagnostics for use with [CheckFailedError]
328#[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}