1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
use crate::common::*;
use crate::test::TestInputType;

#[derive(Diagnostic, Debug, thiserror::Error)]
#[error("{test_from} failed")]
#[diagnostic(help("see emitted diagnostics for details"))]
pub struct TestFailed {
    pub test_from: TestInputType,
    #[label]
    pub span: SourceSpan,
    #[source_code]
    pub input: ArcSource,
    #[related]
    pub errors: Vec<CheckFailedError>,
}
impl TestFailed {
    pub fn new<'input, 'context: 'input>(
        errors: Vec<CheckFailedError>,
        context: &MatchContext<'input, 'context>,
    ) -> Self {
        let input_file = context.input_file();
        Self {
            test_from: TestInputType(context.match_file().name()),
            span: input_file.span(),
            input: input_file.clone(),
            errors,
        }
    }

    pub fn errors(&self) -> &[CheckFailedError] {
        self.errors.as_slice()
    }
}

#[derive(Diagnostic, Debug, thiserror::Error)]
pub enum InvalidCheckFileError {
    #[error("check file did not contain any rules")]
    #[diagnostic()]
    Empty,
    #[error("invalid CHECK-LABEL pattern")]
    #[diagnostic()]
    CheckLabelVariable {
        #[label("in this pattern")]
        line: SourceSpan,
        #[label("variables/substitutions are not allowed on CHECK-LABEL lines")]
        var: SourceSpan,
    },
    #[error("{kind} directives are not permitted to be the first directive in a file")]
    #[diagnostic()]
    InvalidFirstCheck {
        #[label]
        line: SourceSpan,
        kind: Check,
    },
    #[error("invalid CHECK pattern")]
    #[diagnostic()]
    EmptyPattern(#[label("expected a non-empty pattern here")] SourceSpan),
}

#[derive(Debug, Diagnostic, thiserror::Error)]
#[diagnostic()]
#[error("invalid cast to numeric value: {kind:?}")]
pub struct InvalidNumericCastError {
    #[label("occurs due to cast implied by this pattern")]
    pub span: Option<SourceSpan>,
    pub kind: std::num::IntErrorKind,
    #[label("specifically, the value captured by this pattern is not of the correct format")]
    pub specific_span: Option<SourceSpan>,
    #[source_code]
    pub match_file: ArcSource,
}

#[derive(Debug, Diagnostic, thiserror::Error)]
#[error("reference to undefined variable '{name}'")]
pub struct UndefinedVariableError {
    #[label("occurs here")]
    pub span: SourceSpan,
    #[source_code]
    pub match_file: ArcSource,
    pub name: String,
}

#[derive(Diagnostic, Debug, thiserror::Error)]
pub enum CheckFailedError {
    #[error("the input file was rejected because it is empty, and --allow-empty was not set")]
    EmptyInput,
    /// Indicates an error while processing a potential match
    #[error("an error occurred while processing a potential match")]
    #[diagnostic()]
    MatchError {
        #[label("when matching against this input")]
        span: SourceSpan,
        #[source_code]
        input_file: litcheck::diagnostics::ArcSource,
        #[related]
        labels: Vec<RelatedLabel>,
        #[help]
        help: Option<String>,
    },
    /// Indicates a match for an excluded pattern.
    #[error("match found, but was excluded")]
    #[diagnostic()]
    MatchFoundButExcluded {
        #[label("match found here")]
        span: SourceSpan,
        #[source_code]
        input_file: litcheck::diagnostics::ArcSource,
        #[related]
        labels: Vec<RelatedLabel>,
    },
    /// Indicates a match for an expected pattern, but the match is on the
    /// wrong line.
    #[error("match found for expected pattern, but on the wrong line")]
    #[diagnostic()]
    MatchFoundButWrongLine {
        #[label("match found here")]
        span: SourceSpan,
        #[source_code]
        input_file: litcheck::diagnostics::ArcSource,
        #[related]
        pattern: Option<RelatedCheckError>,
    },
    /// Indicates a discarded match for an expected pattern.
    #[error("match found, but was discarded")]
    #[diagnostic()]
    MatchFoundButDiscarded {
        #[label("match found here")]
        span: SourceSpan,
        #[source_code]
        input_file: litcheck::diagnostics::ArcSource,
        #[related]
        labels: Vec<RelatedLabel>,
        #[help]
        note: Option<String>,
    },
    /// Indicates an error while processing a match after the match was found
    /// for an expected or excluded pattern.
    #[error("match found, but there was an error processing it")]
    #[diagnostic()]
    MatchFoundErrorNote {
        #[label("match found here")]
        span: SourceSpan,
        #[source_code]
        input_file: litcheck::diagnostics::ArcSource,
        #[related]
        pattern: Option<RelatedCheckError>,
        #[help]
        help: Option<String>,
    },
    /// Indicates an error while processing a match after the match was found
    /// for an expected or excluded pattern.
    #[error("match found, but there was an error when evaluating a constraint")]
    #[diagnostic()]
    MatchFoundConstraintFailed {
        #[label("match found here")]
        span: SourceSpan,
        #[source_code]
        input_file: litcheck::diagnostics::ArcSource,
        #[related]
        pattern: Option<RelatedCheckError>,
        #[related]
        error: Option<RelatedError>,
        #[help]
        help: Option<String>,
    },
    /// Indicates no match for an expected pattern, but this might follow good
    /// matches when multiple matches are expected for the pattern, or it might
    /// follow discarded matches for the pattern.
    #[error("no matches were found for expected pattern")]
    #[diagnostic()]
    MatchNoneButExpected {
        #[label("pattern at this location was not matched")]
        span: SourceSpan,
        #[source_code]
        match_file: litcheck::diagnostics::ArcSource,
        #[help]
        note: Option<String>,
    },
    /// Indicates no match due to an expected or excluded pattern that has
    /// proven to be invalid at match time.  The exact problems are usually
    /// reported in subsequent diagnostics of the same match type but with
    /// `Note` set.
    #[error("unable to match invalid pattern")]
    #[diagnostic()]
    MatchNoneForInvalidPattern {
        #[label("pattern at this location was invalid")]
        span: SourceSpan,
        #[source_code]
        match_file: litcheck::diagnostics::ArcSource,
        #[related]
        error: Option<RelatedError>,
    },
    /// Indicates a match attempt failed for unknown reasons
    #[error("error occurred while matching pattern")]
    #[diagnostic()]
    MatchNoneErrorNote {
        #[label("when matching this pattern")]
        span: SourceSpan,
        #[source_code]
        match_file: litcheck::diagnostics::ArcSource,
        #[related]
        error: Option<RelatedError>,
    },
    /// Indicates a fuzzy match that serves as a suggestion for the next
    /// intended match for an expected pattern with too few or no good matches.
    #[error("an exact match was not found, but some similar matches were found, see notes")]
    #[diagnostic()]
    MatchFuzzy {
        #[label("pattern at this location was invalid")]
        span: SourceSpan,
        #[source_code]
        match_file: litcheck::diagnostics::ArcSource,
        #[help]
        notes: Option<String>,
    },
    /// Indicates that matching all patterns in a set of patterns failed due
    /// to at least one pattern not being matched.
    ///
    /// This occurs with CHECK-DAG/CHECK-NOT which are evaluated in groups
    #[error("one or more matches were not found for a set of expected patterns")]
    #[diagnostic(help("see diagnostics for details about each failed pattern"))]
    MatchAllFailed {
        #[related]
        failed: Vec<CheckFailedError>,
    },
}
impl CheckFailedError {
    /// Returns true if this error was produced in the context of a possibly-valid match
    pub fn match_was_found(&self) -> bool {
        matches!(
            self,
            Self::MatchFoundButExcluded { .. }
                | Self::MatchFoundButWrongLine { .. }
                | Self::MatchFoundButDiscarded { .. }
                | Self::MatchFoundErrorNote { .. }
                | Self::MatchFoundConstraintFailed { .. }
        )
    }
}

/// This is used to associated source spans from the match file
/// with those from the input file.
#[derive(Diagnostic, Debug, thiserror::Error)]
#[error("check failed")]
#[diagnostic()]
pub struct RelatedCheckError {
    #[label("due to pattern at this location")]
    pub span: SourceSpan,
    #[source_code]
    pub match_file: litcheck::diagnostics::ArcSource,
}

#[derive(Debug, thiserror::Error)]
#[error("see also")]
pub struct RelatedLabel {
    pub severity: litcheck::diagnostics::Severity,
    pub labels: SmallVec<[Label; 1]>,
    pub file: litcheck::diagnostics::ArcSource,
}
impl RelatedLabel {
    pub fn error(label: Label, file: litcheck::diagnostics::ArcSource) -> Self {
        Self {
            severity: litcheck::diagnostics::Severity::Error,
            labels: smallvec![label],
            file,
        }
    }

    pub fn warn(label: Label, file: litcheck::diagnostics::ArcSource) -> Self {
        Self {
            severity: litcheck::diagnostics::Severity::Warning,
            labels: smallvec![label],
            file,
        }
    }

    pub fn note(label: Label, file: litcheck::diagnostics::ArcSource) -> Self {
        Self {
            severity: litcheck::diagnostics::Severity::Advice,
            labels: smallvec![label],
            file,
        }
    }

    pub fn notes(
        label: impl IntoIterator<Item = Label>,
        file: litcheck::diagnostics::ArcSource,
    ) -> Self {
        Self {
            severity: litcheck::diagnostics::Severity::Advice,
            labels: label.into_iter().collect(),
            file,
        }
    }
}
impl Diagnostic for RelatedLabel {
    fn code<'a>(&'a self) -> Option<Box<dyn fmt::Display + 'a>> {
        None
    }
    fn severity(&self) -> Option<litcheck::diagnostics::Severity> {
        Some(self.severity)
    }
    fn help<'a>(&'a self) -> Option<Box<dyn fmt::Display + 'a>> {
        None
    }
    fn url<'a>(&'a self) -> Option<Box<dyn fmt::Display + 'a>> {
        None
    }
    fn source_code(&self) -> Option<&dyn litcheck::diagnostics::SourceCode> {
        Some(&self.file)
    }
    fn labels(&self) -> Option<Box<dyn Iterator<Item = litcheck::diagnostics::LabeledSpan> + '_>> {
        if self.labels.is_empty() {
            None
        } else {
            Some(Box::new(self.labels.iter().cloned().map(|l| l.into())))
        }
    }
    fn related<'a>(&'a self) -> Option<Box<dyn Iterator<Item = &'a dyn Diagnostic> + 'a>> {
        None
    }
    fn diagnostic_source(&self) -> Option<&dyn Diagnostic> {
        None
    }
}

/// This type wraps related diagnostics for use with [CheckFailedError]
#[derive(Debug)]
pub struct RelatedError(Report);
impl RelatedError {
    pub fn into_report(self) -> Report {
        self.0
    }

    #[inline(always)]
    pub fn as_diagnostic(&self) -> &dyn Diagnostic {
        self.0.as_ref()
    }
}
impl Diagnostic for RelatedError {
    fn code<'a>(&'a self) -> Option<Box<dyn fmt::Display + 'a>> {
        self.as_diagnostic().code()
    }
    fn severity(&self) -> Option<litcheck::diagnostics::Severity> {
        self.as_diagnostic().severity()
    }
    fn help<'a>(&'a self) -> Option<Box<dyn fmt::Display + 'a>> {
        self.as_diagnostic().help()
    }
    fn url<'a>(&'a self) -> Option<Box<dyn fmt::Display + 'a>> {
        self.as_diagnostic().url()
    }
    fn source_code(&self) -> Option<&dyn litcheck::diagnostics::SourceCode> {
        self.as_diagnostic().source_code()
    }
    fn labels(&self) -> Option<Box<dyn Iterator<Item = litcheck::diagnostics::LabeledSpan> + '_>> {
        self.as_diagnostic().labels()
    }
    fn related<'a>(&'a self) -> Option<Box<dyn Iterator<Item = &'a dyn Diagnostic> + 'a>> {
        self.as_diagnostic().related()
    }
    fn diagnostic_source(&self) -> Option<&dyn Diagnostic> {
        self.as_diagnostic().diagnostic_source()
    }
}
impl fmt::Display for RelatedError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        fmt::Display::fmt(&self.0, f)
    }
}
impl std::error::Error for RelatedError {
    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
        AsRef::<dyn std::error::Error>::as_ref(&self.0).source()
    }
}
impl From<Report> for RelatedError {
    fn from(report: Report) -> Self {
        Self(report)
    }
}
impl RelatedError {
    pub const fn new(report: Report) -> Self {
        Self(report)
    }

    pub fn wrap<E>(error: E) -> Self
    where
        E: Diagnostic + Send + Sync + 'static,
    {
        Self(Report::new_boxed(Box::new(error)))
    }
}