nuidl_lib/codegen/
diagnostic.rs

1use codespan::{FileId, Span};
2use std::fmt::Write;
3use std::io;
4use std::io::ErrorKind;
5use std::path::{Path, PathBuf};
6
7use crate::codegen::{Context, ImportCtx};
8use codespan_reporting::diagnostic as crd;
9use codespan_reporting::diagnostic::Label;
10use codespan_reporting::term::termcolor::{ColorChoice, StandardStream};
11use lalrpop_util::lexer::Token;
12use lalrpop_util::ParseError;
13
14#[derive(Debug, Copy, Clone, Eq, PartialEq)]
15pub struct GlobalSpan {
16    pub file: FileId,
17    pub span: Span,
18}
19
20impl GlobalSpan {
21    pub fn new(file: FileId, span: Span) -> Self {
22        GlobalSpan { file, span }
23    }
24}
25
26pub enum AnyDiagnostic {
27    // 01XX
28    CyclicImport(CyclicImport),
29    ImportFailed(ImportFailed),
30    // 02XX
31    InvalidToken(InvalidToken),
32    UnexpectedEof(UnexpectedEof),
33    UnexpectedToken(UnexpectedToken),
34    UserError(UserError),
35    // 03XX
36    UndeclaredType(UndeclaredType),
37    DuplicatePolyType(DuplicatePolyType),
38}
39
40fn token_to_span((left, _t, right): &(usize, Token, usize)) -> Span {
41    Span::new(*left as u32, *right as u32)
42}
43
44impl AnyDiagnostic {
45    pub fn from_parse_error(file: FileId, e: &ParseError<usize, Token, &'static str>) -> Self {
46        match e {
47            ParseError::InvalidToken { location } => {
48                AnyDiagnostic::InvalidToken(InvalidToken::new(file, *location))
49            }
50            ParseError::UnrecognizedEOF { location, expected } => {
51                AnyDiagnostic::UnexpectedEof(UnexpectedEof::new(file, *location, expected.clone()))
52            }
53            ParseError::UnrecognizedToken { token, expected } => {
54                AnyDiagnostic::UnexpectedToken(UnexpectedToken::new(
55                    GlobalSpan::new(file, token_to_span(token)),
56                    expected.clone(),
57                ))
58            }
59            ParseError::ExtraToken { token } => AnyDiagnostic::UnexpectedToken(
60                UnexpectedToken::new(GlobalSpan::new(file, token_to_span(token)), Vec::new()),
61            ),
62            ParseError::User { error } => {
63                AnyDiagnostic::UserError(UserError::new(file, error.to_string()))
64            }
65        }
66    }
67}
68
69impl Diagnostic for AnyDiagnostic {
70    fn to_codespan(&self, ctx: &Context) -> crd::Diagnostic<FileId> {
71        match self {
72            AnyDiagnostic::CyclicImport(d) => d.to_codespan(ctx),
73            AnyDiagnostic::ImportFailed(d) => d.to_codespan(ctx),
74            AnyDiagnostic::InvalidToken(d) => d.to_codespan(ctx),
75            AnyDiagnostic::UnexpectedEof(d) => d.to_codespan(ctx),
76            AnyDiagnostic::UnexpectedToken(d) => d.to_codespan(ctx),
77            AnyDiagnostic::UserError(d) => d.to_codespan(ctx),
78            AnyDiagnostic::UndeclaredType(d) => d.to_codespan(ctx),
79            AnyDiagnostic::DuplicatePolyType(d) => d.to_codespan(ctx),
80        }
81    }
82}
83
84pub trait Diagnostic {
85    fn to_codespan(&self, ctx: &Context) -> crd::Diagnostic<FileId>;
86}
87
88pub trait DiagnosticExt: Diagnostic {
89    fn report_and_exit(&self, ctx: &Context) -> ! {
90        let report = self.to_codespan(ctx);
91        let mut writer = StandardStream::stderr(ColorChoice::Auto);
92        let config = codespan_reporting::term::Config::default();
93        codespan_reporting::term::emit(&mut writer, &config, &ctx.files, &report).unwrap();
94        std::process::exit(1);
95    }
96}
97
98impl<T: Diagnostic> DiagnosticExt for T {}
99
100pub struct CyclicImport {
101    import: Label<FileId>,
102    first_caused_by: Option<Label<FileId>>,
103    stack: Vec<FileId>,
104}
105
106impl CyclicImport {
107    pub(crate) fn new(call_stack: &ImportCtx) -> Self {
108        let imp_loc = call_stack.location.unwrap();
109        let mut stack = Vec::new();
110        let mut first_caused_by = None;
111
112        for el in call_stack.iter() {
113            stack.push(el.imported);
114
115            if let Some(s) = el.location {
116                if s.file == call_stack.imported {
117                    first_caused_by =
118                        Some(Label::secondary(s.file, s.span).with_message("cycle entered here"));
119                    stack.push(s.file);
120                    break;
121                }
122            }
123        }
124
125        CyclicImport {
126            import: Label::primary(imp_loc.file, imp_loc.span)
127                .with_message("file eventually importing itself"),
128            first_caused_by,
129            stack,
130        }
131    }
132}
133
134impl Diagnostic for CyclicImport {
135    fn to_codespan(&self, ctx: &Context) -> crd::Diagnostic<FileId> {
136        let mut labels = vec![self.import.clone()];
137        labels.extend(self.first_caused_by.clone());
138
139        let mut iter = self.stack.iter().rev();
140        let first_file = *iter.next().unwrap();
141        let first_name = Path::new(ctx.files.name(first_file))
142            .file_name()
143            .unwrap()
144            .to_str()
145            .unwrap();
146        let second_file = *iter.next().unwrap();
147
148        let mut note = if first_file == second_file {
149            format!("cycle occurs because {} imports itself", first_name)
150        } else {
151            let second_name = Path::new(ctx.files.name(second_file))
152                .file_name()
153                .unwrap()
154                .to_str()
155                .unwrap();
156
157            format!(
158                "cycle occurs because {} imports {}",
159                first_name, second_name
160            )
161        };
162
163        while let Some(el) = iter.next() {
164            let file_name = Path::new(ctx.files.name(*el))
165                .file_name()
166                .unwrap()
167                .to_str()
168                .unwrap();
169            write!(note, ",\n  which imports {}", file_name).unwrap();
170        }
171
172        crd::Diagnostic::error()
173            .with_code("E0101")
174            .with_message("cyclic import")
175            .with_labels(labels)
176            .with_notes(vec![note])
177    }
178}
179
180pub struct ImportFailed {
181    path: PathBuf,
182    import: GlobalSpan,
183    errors: Vec<io::Error>,
184}
185
186impl ImportFailed {
187    pub fn new(path: PathBuf, import: GlobalSpan, errors: Vec<io::Error>) -> Self {
188        ImportFailed {
189            path,
190            import,
191            errors,
192        }
193    }
194}
195
196impl Diagnostic for ImportFailed {
197    fn to_codespan(&self, ctx: &Context) -> crd::Diagnostic<FileId> {
198        let mut notes = Vec::new();
199
200        if ctx.include_paths.is_empty() {
201            notes.push("no include paths listed".to_string());
202        }
203
204        for err in &self.errors {
205            if err.kind() != ErrorKind::NotFound {
206                notes.push(format!("I/O error: {}", err));
207            }
208        }
209
210        let import =
211            Label::primary(self.import.file, self.import.span).with_message("imported from here");
212
213        crd::Diagnostic::error()
214            .with_code("E0102")
215            .with_message(format!("failed to import '{}'", self.path.display()))
216            .with_labels(vec![import])
217            .with_notes(notes)
218    }
219}
220
221pub struct InvalidToken {
222    file: FileId,
223    location: usize,
224}
225
226impl InvalidToken {
227    pub fn new(file: FileId, location: usize) -> Self {
228        InvalidToken { file, location }
229    }
230}
231
232impl Diagnostic for InvalidToken {
233    fn to_codespan(&self, _ctx: &Context) -> crd::Diagnostic<FileId> {
234        let label = Label::primary(self.file, self.location..self.location + 1);
235
236        crd::Diagnostic::error()
237            .with_code("E0201")
238            .with_message("invalid token")
239            .with_labels(vec![label])
240    }
241}
242
243pub struct UnexpectedEof {
244    file: FileId,
245    location: usize,
246    expected: Vec<String>,
247}
248
249impl UnexpectedEof {
250    pub fn new(file: FileId, location: usize, expected: Vec<String>) -> Self {
251        UnexpectedEof {
252            file,
253            location,
254            expected,
255        }
256    }
257}
258
259impl Diagnostic for UnexpectedEof {
260    fn to_codespan(&self, _ctx: &Context) -> crd::Diagnostic<FileId> {
261        let label = Label::primary(self.file, self.location..self.location + 1);
262        let notes = make_token_list(&self.expected).into_iter().collect();
263
264        crd::Diagnostic::error()
265            .with_code("E0202")
266            .with_message("unexpected end of file")
267            .with_labels(vec![label])
268            .with_notes(notes)
269    }
270}
271
272pub struct UnexpectedToken {
273    token: GlobalSpan,
274    expected: Vec<String>,
275}
276
277impl UnexpectedToken {
278    pub fn new(token: GlobalSpan, expected: Vec<String>) -> Self {
279        UnexpectedToken { token, expected }
280    }
281}
282
283impl Diagnostic for UnexpectedToken {
284    fn to_codespan(&self, ctx: &Context) -> crd::Diagnostic<FileId> {
285        let text = ctx
286            .files
287            .source_slice(self.token.file, self.token.span)
288            .unwrap();
289        let label = Label::primary(self.token.file, self.token.span);
290        let notes = make_token_list(&self.expected).into_iter().collect();
291
292        crd::Diagnostic::error()
293            .with_code("E0203")
294            .with_message(format!("unexpected token '{}'", text))
295            .with_labels(vec![label])
296            .with_notes(notes)
297    }
298}
299
300pub struct UserError {
301    file: FileId,
302    text: String,
303}
304
305impl UserError {
306    pub fn new(file: FileId, text: String) -> Self {
307        UserError { file, text }
308    }
309}
310
311impl Diagnostic for UserError {
312    fn to_codespan(&self, _ctx: &Context) -> crd::Diagnostic<FileId> {
313        let label = Label::primary(self.file, 0..1);
314
315        crd::Diagnostic::error()
316            .with_code("E02XX")
317            .with_message(&self.text)
318            .with_labels(vec![label])
319            .with_notes(vec![
320                "no location info, the error may be anywhere in the file".to_string(),
321            ])
322    }
323}
324
325pub struct UndeclaredType {
326    token: GlobalSpan,
327}
328
329impl UndeclaredType {
330    pub fn new(token: GlobalSpan) -> Self {
331        UndeclaredType { token }
332    }
333}
334
335impl Diagnostic for UndeclaredType {
336    fn to_codespan(&self, ctx: &Context) -> crd::Diagnostic<FileId> {
337        let text = ctx
338            .files
339            .source_slice(self.token.file, self.token.span)
340            .unwrap();
341        let label = Label::primary(self.token.file, self.token.span);
342
343        crd::Diagnostic::error()
344            .with_code("E0301")
345            .with_message(format!("undeclared type '{}'", text))
346            .with_labels(vec![label])
347    }
348}
349
350pub struct DuplicatePolyType {
351    file: FileId,
352    poly_params: Vec<Span>,
353    target: Span,
354}
355
356impl DuplicatePolyType {
357    pub fn new(file: FileId, poly_params: Vec<Span>, target: Span) -> Self {
358        DuplicatePolyType {
359            file,
360            poly_params,
361            target,
362        }
363    }
364}
365
366impl Diagnostic for DuplicatePolyType {
367    fn to_codespan(&self, ctx: &Context) -> crd::Diagnostic<FileId> {
368        let mut labels = Vec::new();
369        labels.extend(
370            self.poly_params
371                .iter()
372                .map(|v| Label::primary(self.file, *v)),
373        );
374        let target_name = ctx.files.source_slice(self.file, self.target).unwrap();
375        labels.push(Label::secondary(self.file, self.target).with_message("the target parameter"));
376        let notes = vec!["a parameter referred to in a [poly()] attribute on another parameter receives the IID for that parameter; as such, the relationship needs to be unique".to_string()];
377
378        crd::Diagnostic::error()
379            .with_code("E0302")
380            .with_message(format!(
381                "parameter '{}' used as poly-type more than once",
382                target_name
383            ))
384            .with_labels(labels)
385            .with_notes(notes)
386    }
387}
388
389fn make_token_list(t: &[String]) -> Option<String> {
390    if !t.is_empty() {
391        let mut s = format!("expected ");
392
393        let mut iter = t.iter();
394        let mut cur = iter.next().unwrap();
395        let mut next = iter.next();
396        let mut i = 0;
397
398        loop {
399            match (i, next) {
400                (0, None) => {
401                    write!(s, "{}", cur).unwrap();
402                }
403                (0, Some(_)) => {
404                    write!(s, "one of {}", cur).unwrap();
405                }
406                (1, None) => {
407                    write!(s, " or {}", cur).unwrap();
408                }
409                (_, None) => {
410                    write!(s, ", or {}", cur).unwrap();
411                }
412                (_, Some(_)) => {
413                    write!(s, ", {}", cur).unwrap();
414                }
415            }
416
417            cur = match next {
418                None => break,
419                Some(s) => s,
420            };
421
422            next = iter.next();
423            i += 1;
424        }
425
426        Some(s)
427    } else {
428        None
429    }
430}