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 CyclicImport(CyclicImport),
29 ImportFailed(ImportFailed),
30 InvalidToken(InvalidToken),
32 UnexpectedEof(UnexpectedEof),
33 UnexpectedToken(UnexpectedToken),
34 UserError(UserError),
35 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}