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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 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#[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#[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}