litcheck_filecheck/
errors.rs

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