1use std::cmp::{self, Ordering};
5use std::fmt;
6use std::io::{stderr, BufWriter, Write as _};
7
8use crate::io::{Input, InputKind};
9use crate::style::Attribute;
10use crate::style::Characters;
11use crate::style::Color;
12use crate::style::StyledStr;
13use crate::style::StyledStrings;
14use crate::style::Theme;
15use crate::style::THEME;
16use crate::traits::{Locational, Stream};
17use crate::{impl_display_from_debug, switch_lang};
18
19#[cfg(feature = "pylib")]
20use pyo3::prelude::*;
21
22#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
25#[repr(u8)]
26pub enum ErrorKind {
27 AssignError = 0,
29 AttributeError = 1,
30 BytecodeError = 2,
31 CompilerSystemError = 3,
32 EnvironmentError = 4,
33 FeatureError = 5,
34 ImportError = 6,
35 IndentationError = 7,
36 NameError = 8,
37 NotImplementedError = 9,
38 PatternError = 10,
39 SyntaxError = 11,
40 TabError = 12,
41 TypeError = 13,
42 UnboundLocalError = 14,
43 PurityError = 15,
44 HasEffect = 16,
45 MoveError = 17,
46 NotConstExpr = 18,
47 InheritanceError = 19,
48 VisibilityError = 20,
49 MethodError = 21,
50 DummyError = 22,
51 ExpectNextLine = 23,
52 AttributeWarning = 60,
54 CastWarning = 61,
55 DeprecationWarning = 62,
56 FutureWarning = 63,
57 ImportWarning = 64,
58 PendingDeprecationWarning = 65,
59 SyntaxWarning = 66,
60 TypeWarning = 67,
61 NameWarning = 68,
62 UnusedWarning = 69,
63 Warning = 70,
64 ArithmeticError = 100,
66 AssertionError = 101,
67 BlockingIOError = 102,
68 BrokenPipeError = 103,
69 BufferError = 104,
70 ChildProcessError = 105,
71 ConnectionAbortedError = 106,
72 ConnectionError = 107,
73 ConnectionRefusedError = 108,
74 ConnectionResetError = 109,
75 EOFError = 110,
76 FileExistsError = 111,
77 FileNotFoundError = 112,
78 IndexError = 113,
79 InterruptedError = 114,
80 IoError = 115,
81 IsADirectoryError = 116,
82 KeyError = 117,
83 LookupError = 118,
84 MemoryError = 119,
85 ModuleNotFoundError = 120,
86 NotADirectoryError = 121,
87 OSError = 122,
88 OverflowError = 123,
89 PermissionError = 124,
90 ProcessLookupError = 125,
91 RecursionError = 126,
92 ReferenceError = 127,
93 RuntimeAttributeError = 128,
94 RuntimeError = 129,
95 RuntimeTypeError = 130,
96 RuntimeUnicodeError = 131,
97 TimeoutError = 132,
98 UnicodeError = 133,
99 UserError = 134,
100 ValueError = 135,
101 VMSystemError = 136,
102 WindowsError = 137,
103 ZeroDivisionError = 138,
104 BytesWarning = 180,
106 ResourceWarning = 181,
107 RuntimeWarning = 182,
108 UnicodeWarning = 183,
109 UserWarning = 184,
110 BaseException = 200,
112 Exception = 201,
113 GeneratorExit = 202,
114 KeyboardInterrupt = 203,
115 StopAsyncIteration = 204,
116 StopIteration = 205,
117 SystemExit = 206,
118 UserException = 207,
119}
120
121use ErrorKind::*;
122
123impl_display_from_debug!(ErrorKind);
124
125impl ErrorKind {
126 pub fn is_warning(&self) -> bool {
127 (60..=100).contains(&(*self as u8)) || (180..=200).contains(&(*self as u8))
128 }
129
130 pub fn is_error(&self) -> bool {
131 (0..=59).contains(&(*self as u8)) || (100..=179).contains(&(*self as u8))
132 }
133
134 pub fn is_exception(&self) -> bool {
135 (200..=255).contains(&(*self as u8))
136 }
137}
138
139impl From<&str> for ErrorKind {
140 fn from(s: &str) -> ErrorKind {
141 match s {
142 "AssignError" => Self::AssignError,
143 "AttributeError" => Self::AttributeError,
144 "BytecodeError" => Self::BytecodeError,
145 "CompilerSystemError" => Self::CompilerSystemError,
146 "EnvironmentError" => Self::EnvironmentError,
147 "FeatureError" => Self::FeatureError,
148 "ImportError" => Self::ImportError,
149 "IndentationError" => Self::IndentationError,
150 "NameError" => Self::NameError,
151 "NotImplementedError" => Self::NotImplementedError,
152 "PatternError" => Self::PatternError,
153 "SyntaxError" => Self::SyntaxError,
154 "TabError" => Self::TabError,
155 "TypeError" => Self::TypeError,
156 "UnboundLocalError" => Self::UnboundLocalError,
157 "HasEffect" => Self::HasEffect,
158 "PurityError" => Self::PurityError,
159 "MoveError" => Self::MoveError,
160 "AttributeWarning" => Self::AttributeWarning,
161 "CastWarning" => Self::CastWarning,
162 "DeprecationWarning" => Self::DeprecationWarning,
163 "FutureWarning" => Self::FutureWarning,
164 "ImportWarning" => Self::ImportWarning,
165 "PendingDeprecationWarning" => Self::PendingDeprecationWarning,
166 "SyntaxWarning" => Self::SyntaxWarning,
167 "TypeWarning" => Self::TypeWarning,
168 "NameWarning" => Self::NameWarning,
169 "UnusedWarning" => Self::UnusedWarning,
170 "Warning" => Self::Warning,
171 "ArithmeticError" => Self::ArithmeticError,
172 "AssertionError" => Self::AssertionError,
173 "BlockingIOError" => Self::BlockingIOError,
174 "BrokenPipeError" => Self::BrokenPipeError,
175 "BufferError" => Self::BufferError,
176 "ChildProcessError" => Self::ChildProcessError,
177 "ConnectionAbortedError" => Self::ConnectionAbortedError,
178 "ConnectionError" => Self::ConnectionError,
179 "ConnectionRefusedError" => Self::ConnectionRefusedError,
180 "ConnectionResetError" => Self::ConnectionResetError,
181 "EOFError" => Self::EOFError,
182 "FileExistsError" => Self::FileExistsError,
183 "FileNotFoundError" => Self::FileNotFoundError,
184 "IndexError" => Self::IndexError,
185 "InterruptedError" => Self::InterruptedError,
186 "IoError" => Self::IoError,
187 "IsADirectoryError" => Self::IsADirectoryError,
188 "KeyError" => Self::KeyError,
189 "LookupError" => Self::LookupError,
190 "MemoryError" => Self::MemoryError,
191 "ModuleNotFoundError" => Self::ModuleNotFoundError,
192 "NotADirectoryError" => Self::NotADirectoryError,
193 "OSError" => Self::OSError,
194 "OverflowError" => Self::OverflowError,
195 "PermissionError" => Self::PermissionError,
196 "ProcessLookupError" => Self::ProcessLookupError,
197 "RecursionError" => Self::RecursionError,
198 "ReferenceError" => Self::ReferenceError,
199 "RuntimeAttributeError" => Self::RuntimeAttributeError,
200 "RuntimeError" => Self::RuntimeError,
201 "RuntimeTypeError" => Self::RuntimeTypeError,
202 "RuntimeUnicodeError" => Self::RuntimeUnicodeError,
203 "TimeoutError" => Self::TimeoutError,
204 "UnicodeError" => Self::UnicodeError,
205 "UserError" => Self::UserError,
206 "ValueError" => Self::ValueError,
207 "VMSystemError" => Self::VMSystemError,
208 "WindowsError" => Self::WindowsError,
209 "ZeroDivisionError" => Self::ZeroDivisionError,
210 "BytesWarning" => Self::BytesWarning,
211 "ResourceWarning" => Self::ResourceWarning,
212 "RuntimeWarning" => Self::RuntimeWarning,
213 "UnicodeWarning" => Self::UnicodeWarning,
214 "UserWarning" => Self::UserWarning,
215 "BaseException" => Self::BaseException,
216 "Exception" => Self::Exception,
217 "GeneratorExit" => Self::GeneratorExit,
218 "KeyboardInterrupt" => Self::KeyboardInterrupt,
219 "StopAsyncIteration" => Self::StopAsyncIteration,
220 "StopIteration" => Self::StopIteration,
221 "SystemExit" => Self::SystemExit,
222 "UserException" => Self::UserException,
223 _ => Self::UserError,
224 }
225 }
226}
227
228#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
234pub enum Location {
235 Range {
251 ln_begin: u32,
253 col_begin: u32,
255 ln_end: u32,
256 col_end: u32,
257 },
258 LineRange(u32, u32),
261 Line(u32),
264 #[default]
266 Unknown,
267}
268
269impl fmt::Display for Location {
270 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
271 match self {
272 Self::Range {
273 ln_begin,
274 col_begin,
275 ln_end,
276 col_end,
277 } => write!(f, "{ln_begin}:{col_begin}-{ln_end}:{col_end}"),
278 Self::LineRange(ln_begin, ln_end) => write!(f, "{ln_begin}:?-{ln_end}:?"),
279 Self::Line(ln) => write!(f, "{ln}:??-{ln}:??"),
280 Self::Unknown => write!(f, "?"),
281 }
282 }
283}
284
285#[cfg(feature = "pylib")]
286impl FromPyObject<'_> for Location {
287 fn extract_bound(ob: &Bound<'_, PyAny>) -> PyResult<Self> {
288 if let Ok(s) = ob.extract::<String>() {
289 Ok(s.parse::<Location>().unwrap())
290 } else if let Ok(s) = ob.extract::<u32>() {
291 Ok(Location::Line(s))
292 } else if let Ok((l, r)) = ob.extract::<(u32, u32)>() {
293 Ok(Location::LineRange(l, r))
294 } else if let Ok((lb, cb, le, ce)) = ob.extract::<(u32, u32, u32, u32)>() {
295 Ok(Location::Range {
296 ln_begin: lb,
297 col_begin: cb,
298 ln_end: le,
299 col_end: ce,
300 })
301 } else {
302 Err(PyErr::new::<pyo3::exceptions::PyTypeError, _>(format!(
303 "expected Into<Location>, but got {:?}",
304 ob.get_type().name()?
305 )))
306 }
307 }
308}
309
310#[cfg(feature = "pylib")]
311impl<'py> IntoPyObject<'py> for Location {
312 type Target = pyo3::types::PyTuple;
313 type Output = Bound<'py, Self::Target>;
314 type Error = pyo3::PyErr;
315 fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
316 match self {
317 Self::Line(l) => (l, py.None(), l, py.None()).into_pyobject(py),
318 Self::LineRange(lb, le) => (lb, py.None(), le, py.None()).into_pyobject(py),
319 Self::Range {
320 ln_begin,
321 col_begin,
322 ln_end,
323 col_end,
324 } => (ln_begin, col_begin, ln_end, col_end).into_pyobject(py),
325 Self::Unknown => (py.None(), py.None(), py.None(), py.None()).into_pyobject(py),
326 }
327 }
328}
329
330impl std::str::FromStr for Location {
331 type Err = ();
332 fn from_str(s: &str) -> Result<Self, Self::Err> {
333 if s == "?" {
334 return Ok(Self::Unknown);
335 }
336 let mut comps = s.split('-');
338 let mut comp1 = comps.next().ok_or(())?.split(':');
339 let mut comp2 = comps.next().ok_or(())?.split(':');
340 let ln_begin = comp1.next().unwrap().parse::<u32>().map_err(|_| ())?;
341 let col_begin = comp1.next().unwrap().parse::<u32>();
342 let ln_end = comp2.next().unwrap().parse::<u32>().map_err(|_| ())?;
343 let col_end = comp2.next().unwrap().parse::<u32>();
344 match (col_begin, col_end) {
345 (Ok(col_begin), Ok(col_end)) => Ok(Self::Range {
346 ln_begin,
347 col_begin,
348 ln_end,
349 col_end,
350 }),
351 _ if ln_begin == ln_end => Ok(Self::Line(ln_begin)),
352 _ => Ok(Self::LineRange(ln_begin, ln_end)),
353 }
354 }
355}
356
357impl Ord for Location {
358 fn cmp(&self, other: &Location) -> Ordering {
359 if self.ln_end() < other.ln_begin() {
360 Ordering::Less
361 } else if other.ln_end() < self.ln_begin() {
362 Ordering::Greater
363 } else if self.ln_begin() == self.ln_end() && other.ln_begin() == other.ln_end() {
364 if self.col_end() <= other.col_begin() {
367 Ordering::Less
368 } else if other.col_end() <= self.col_begin() {
369 Ordering::Greater
370 } else {
371 Ordering::Equal
372 }
373 } else {
374 Ordering::Equal
375 }
376 }
377}
378
379impl PartialOrd for Location {
380 #[allow(clippy::non_canonical_partial_ord_impl)]
381 fn partial_cmp(&self, other: &Location) -> Option<Ordering> {
382 if self.is_unknown() || other.is_unknown() {
383 None
384 } else {
385 Some(self.cmp(other))
386 }
387 }
388}
389
390impl Locational for Location {
391 fn loc(&self) -> Self {
392 *self
393 }
394}
395
396impl Location {
397 pub fn concat<L: Locational, R: Locational>(l: &L, r: &R) -> Self {
398 let l_loc = l.loc();
399 let r_loc = r.loc();
400 match (
401 l_loc.ln_begin(),
402 l_loc.col_begin(),
403 r_loc.ln_end(),
404 r_loc.col_end(),
405 ) {
406 (Some(lb), Some(cb), Some(le), Some(ce)) => Self::range(lb, cb, le, ce),
407 (Some(lb), _, Some(le), _) => Self::LineRange(lb, le),
408 (Some(l), _, _, _) | (_, _, Some(l), _) => Self::Line(l),
409 _ => Self::Unknown,
410 }
411 }
412
413 pub fn left_main_concat<L: Locational, R: Locational>(l: &L, r: &R) -> Self {
414 let l_loc = l.loc();
415 let r_loc = r.loc();
416 match (
417 l_loc.ln_begin(),
418 l_loc.col_begin(),
419 r_loc.ln_end(),
420 r_loc.col_end(),
421 ) {
422 (Some(lb), Some(cb), Some(le), Some(ce)) => Self::range(lb, cb, le, ce),
423 (Some(_), _, None, None) => l_loc,
424 (Some(lb), _, Some(le), _) => Self::LineRange(lb, le),
425 (Some(l), _, _, _) | (_, _, Some(l), _) => Self::Line(l),
426 _ => Self::Unknown,
427 }
428 }
429
430 pub fn slow_stream<L: Locational>(ls: &[L]) -> Self {
431 if ls.is_empty() {
432 return Self::Unknown;
433 }
434 let Some(first_known) = ls.iter().find(|l| !l.loc().is_unknown()) else {
435 return Self::Unknown;
436 };
437 let Some(last_known) = ls.iter().rfind(|l| !l.loc().is_unknown()) else {
438 return Self::Unknown;
439 };
440 Self::concat(first_known, last_known)
441 }
442
443 pub fn stream<L: Locational>(ls: &[L]) -> Self {
444 if ls.is_empty() {
445 return Self::Unknown;
446 }
447 let first_known = ls.first().unwrap();
448 let last_known = ls.last().unwrap();
449 Self::concat(first_known, last_known)
450 }
451
452 pub const fn range(ln_begin: u32, col_begin: u32, ln_end: u32, col_end: u32) -> Self {
453 Self::Range {
454 ln_begin,
455 col_begin,
456 ln_end,
457 col_end,
458 }
459 }
460
461 pub const fn is_unknown(&self) -> bool {
462 matches!(self, Self::Unknown)
463 }
464
465 pub const fn is_real(&self) -> bool {
466 match self {
467 Self::Line(l) => *l != 0,
468 Self::LineRange(lb, le) => *lb != 0 && *le != 0,
469 Self::Range {
470 ln_begin, ln_end, ..
471 } => *ln_begin != 0 && *ln_end != 0,
472 Self::Unknown => false,
473 }
474 }
475
476 pub const fn unknown_or(&self, other: Self) -> Self {
477 if self.is_unknown() {
478 other
479 } else {
480 *self
481 }
482 }
483
484 pub const fn ln_begin(&self) -> Option<u32> {
486 match self {
487 Self::Range { ln_begin, .. } | Self::LineRange(ln_begin, _) | Self::Line(ln_begin) => {
488 Some(*ln_begin)
489 }
490 Self::Unknown => None,
491 }
492 }
493
494 pub const fn ln_end(&self) -> Option<u32> {
495 match self {
496 Self::Range { ln_end, .. } | Self::LineRange(_, ln_end) | Self::Line(ln_end) => {
497 Some(*ln_end)
498 }
499 Self::Unknown => None,
500 }
501 }
502
503 pub const fn col_begin(&self) -> Option<u32> {
505 match self {
506 Self::Range { col_begin, .. } => Some(*col_begin),
507 _ => None,
508 }
509 }
510
511 pub const fn col_end(&self) -> Option<u32> {
512 match self {
513 Self::Range { col_end, .. } => Some(*col_end),
514 _ => None,
515 }
516 }
517
518 pub const fn length(&self) -> Option<u32> {
519 match self {
520 Self::Range {
521 col_begin, col_end, ..
522 } => Some(*col_end - *col_begin),
523 _ => None,
524 }
525 }
526
527 pub fn contains(&self, other: Self) -> bool {
539 match (*self, other) {
540 (
541 Self::Range {
542 ln_begin: lb1,
543 col_begin: cb1,
544 ln_end: le1,
545 col_end: ce1,
546 },
547 Self::Range {
548 ln_begin: lb2,
549 col_begin: cb2,
550 ln_end: le2,
551 col_end: ce2,
552 },
553 ) => {
554 let same_start_line = lb1 == lb2;
555 let same_end_line = le1 == le2;
556 if same_start_line && same_end_line {
557 cb1 <= cb2 && ce1 >= ce2
558 } else if same_start_line {
559 cb1 <= cb2 && le1 >= le2
560 } else if same_end_line {
561 lb1 <= lb2 && ce1 >= ce2
562 } else {
563 lb1 <= lb2 && le1 >= le2
564 }
565 }
566 _ => false,
567 }
568 }
569}
570
571#[allow(clippy::too_many_arguments)]
572fn format_context<E: ErrorDisplay + ?Sized>(
573 e: &E,
574 ln_begin: usize,
575 ln_end: usize,
576 col_begin: usize,
577 col_end: usize,
578 err_color: Color,
579 gutter_color: Color,
580 chars: &Characters,
582 mark: char,
584 sub_msg: &[String],
585 hint: Option<&String>,
586) -> String {
587 let mark = mark.to_string();
588 let codes = e.input().reread_lines(ln_begin, ln_end);
589 let mut context = StyledStrings::default();
590 let final_step = ln_end - ln_begin;
591 let max_digit = ln_end.to_string().len();
592 let (vbreak, vbar) = chars.gutters();
593 let offset = format!("{} {} ", &" ".repeat(max_digit), vbreak);
594 for (i, lineno) in (ln_begin..=ln_end).enumerate() {
595 context.push_str_with_color(format!("{lineno:<max_digit$} {vbar} "), gutter_color);
596 let not_found = "???".to_string();
597 let code = codes.get(i).unwrap_or(¬_found);
598 context.push_str(code);
599 context.push_str("\n");
600 context.push_str_with_color(&offset, gutter_color);
601 if i == 0 && i == final_step {
602 context.push_str(&" ".repeat(col_begin));
603 context.push_str_with_color(
604 mark.repeat(cmp::max(1, col_end.saturating_sub(col_begin))),
605 err_color,
606 );
607 } else if i == 0 {
608 context.push_str(&" ".repeat(col_begin));
609 context.push_str_with_color(
610 mark.repeat(cmp::max(1, code.len().saturating_sub(col_begin))),
611 err_color,
612 );
613 } else if i == final_step {
614 context.push_str_with_color(mark.repeat(col_end), err_color);
615 } else {
616 context.push_str_with_color(mark.repeat(cmp::max(1, code.len())), err_color);
617 }
618 context.push_str("\n");
619 }
620
621 let msg_num = sub_msg.len().saturating_sub(1);
622 for (i, msg) in sub_msg.iter().enumerate() {
623 context.push_str_with_color(&offset, gutter_color);
624 context.push_str(&" ".repeat(col_end.saturating_sub(1)));
625 if i == msg_num && hint.is_none() {
626 context.push_str_with_color(chars.left_bottom_line(), err_color);
627 } else {
628 context.push_str_with_color(chars.left_cross(), err_color);
629 }
630 context.push_str(msg);
631 context.push_str("\n")
632 }
633 if let Some(hint) = hint {
634 context.push_str_with_color(&offset, gutter_color);
635 context.push_str(&" ".repeat(col_end.saturating_sub(1)));
636 context.push_str_with_color(chars.left_bottom_line(), err_color);
637 context.push_str(hint);
638 context.push_str("\n")
639 }
640 context.to_string() + "\n"
641}
642
643#[derive(Debug, Clone, PartialEq, Eq, Hash)]
644pub struct SubMessage {
645 pub loc: Location,
646 pub msg: Vec<String>,
647 pub hint: Option<String>,
648}
649
650impl SubMessage {
651 pub fn ambiguous_new(loc: Location, msg: Vec<String>, hint: Option<String>) -> Self {
693 Self { loc, msg, hint }
694 }
695
696 pub fn only_loc(loc: Location) -> Self {
706 Self {
707 loc,
708 msg: Vec::new(),
709 hint: None,
710 }
711 }
712
713 pub fn set_hint<S: Into<String>>(&mut self, hint: S) {
714 self.hint = Some(hint.into());
715 }
716
717 pub fn get_hint(&self) -> Option<&str> {
718 self.hint.as_deref()
719 }
720
721 pub fn get_msg(&self) -> &[String] {
722 self.msg.as_ref()
723 }
724
725 fn format_code_and_pointer<E: ErrorDisplay + ?Sized>(
728 &self,
729 e: &E,
730 err_color: Color,
731 gutter_color: Color,
732 mark: char,
733 chars: &Characters,
734 ) -> String {
735 match self.loc.unknown_or(e.core().loc) {
736 Location::Range {
737 ln_begin,
738 col_begin,
739 ln_end,
740 col_end,
741 } => format_context(
742 e,
743 ln_begin as usize,
744 ln_end as usize,
745 col_begin as usize,
746 col_end as usize,
747 err_color,
748 gutter_color,
749 chars,
750 mark,
751 &self.msg,
752 self.hint.as_ref(),
753 ),
754 Location::LineRange(ln_begin, ln_end) => {
755 let (vbreak, vbar) = chars.gutters();
756 let mut cxt = StyledStrings::default();
757 let codes = e.input().reread_lines(ln_begin as usize, ln_end as usize);
758 let mark = mark.to_string();
759 for (i, lineno) in (ln_begin..=ln_end).enumerate() {
760 cxt.push_str_with_color(format!("{lineno} {vbar} "), gutter_color);
761 cxt.push_str(codes.get(i).unwrap_or(&String::new()));
762 cxt.push_str("\n");
763 cxt.push_str_with_color(
764 format!("{} {}", &" ".repeat(lineno.to_string().len()), vbreak),
765 gutter_color,
766 );
767 cxt.push_str(&" ".repeat(lineno.to_string().len()));
768 cxt.push_str_with_color(
769 mark.repeat(cmp::max(1, codes.get(i).map_or(1, |code| code.len()))),
770 err_color,
771 );
772 cxt.push_str("\n");
773 }
774 cxt.push_str("\n");
775 for msg in self.msg.iter() {
776 cxt.push_str(msg);
777 cxt.push_str("\n");
778 }
779 if let Some(hint) = self.hint.as_ref() {
780 cxt.push_str(hint);
781 cxt.push_str("\n");
782 }
783 cxt.to_string()
784 }
785 Location::Line(lineno) => {
786 let input = e.input();
787 let (_, vbar) = chars.gutters();
788 let codes = input.reread_lines(lineno as usize, lineno as usize);
789 let default = "???".to_string();
790 let code = codes.first().unwrap_or(&default);
791 let mut cxt = StyledStrings::default();
792 cxt.push_str_with_color(format!(" {lineno} {vbar} "), gutter_color);
793 cxt.push_str(code);
794 cxt.push_str("\n");
795 for msg in self.msg.iter() {
796 cxt.push_str(msg);
797 cxt.push_str("\n");
798 }
799 if let Some(hint) = self.hint.as_ref() {
800 cxt.push_str(hint);
801 cxt.push_str("\n");
802 }
803 cxt.push_str("\n");
804 cxt.to_string()
805 }
806 Location::Unknown => match &e.input().kind {
807 InputKind::File { .. } => "\n".to_string(),
808 _other => {
809 let (_, vbar) = chars.gutters();
810 let mut cxt = StyledStrings::default();
811 cxt.push_str_with_color(format!(" ? {vbar} "), gutter_color);
812 cxt.push_str(&e.input().reread());
813 cxt.push_str("\n");
814 for msg in self.msg.iter() {
815 cxt.push_str(msg);
816 cxt.push_str("\n");
817 }
818 if let Some(hint) = self.hint.as_ref() {
819 cxt.push_str(hint);
820 cxt.push_str("\n");
821 }
822 cxt.push_str("\n");
823 cxt.to_string()
824 }
825 },
826 }
827 }
828}
829
830#[derive(Debug, Clone, PartialEq, Eq, Hash)]
833pub struct ErrorCore {
834 pub sub_messages: Vec<SubMessage>,
835 pub main_message: String,
836 pub errno: usize,
837 pub kind: ErrorKind,
838 pub loc: Location,
839 theme: Theme,
840}
841
842impl fmt::Display for ErrorCore {
843 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
844 write!(f, "{self:?}")
845 }
846}
847
848impl std::error::Error for ErrorCore {}
849
850impl ErrorCore {
851 pub fn new<S: Into<String>>(
852 sub_messages: Vec<SubMessage>,
853 main_message: S,
854 errno: usize,
855 kind: ErrorKind,
856 loc: Location,
857 ) -> Self {
858 Self {
859 sub_messages,
860 main_message: main_message.into(),
861 errno,
862 kind,
863 loc,
864 theme: THEME,
865 }
866 }
867
868 pub fn dummy(errno: usize) -> Self {
869 Self::new(
870 vec![SubMessage::only_loc(Location::Unknown)],
871 "<dummy>",
872 errno,
873 DummyError,
874 Location::Unknown,
875 )
876 }
877
878 pub fn unreachable(fn_name: &str, line: u32) -> Self {
879 Self::bug(line as usize, Location::Line(line), fn_name, line)
880 }
881
882 pub fn bug(errno: usize, loc: Location, fn_name: &str, line: u32) -> Self {
883 const URL: StyledStr = StyledStr::new(
884 "https://github.com/erg-lang/erg",
885 Some(Color::White),
886 Some(Attribute::Underline),
887 );
888
889 let main_msg = switch_lang!(
890 "japanese" => format!("\
891 これはErgのバグです、開発者に報告して下さい({URL})
892 発生箇所: {fn_name}:{line}"),
893 "simplified_chinese" => format!("\
894 这是Erg的bug,请报告给{URL}
895 原因来自: {fn_name}:{line}"),
896 "traditional_chinese" => format!("\
897 這是Erg的bug,請報告給{URL}
898 原因來自: {fn_name}:{line}"),
899 "english" => format!("\
900 This is a bug of Erg, please report it to {URL}
901 Caused from: {fn_name}:{line}"),
902 );
903 let main_msg =
904 StyledStr::new(&main_msg, Some(Color::Red), Some(Attribute::Bold)).to_string();
905 Self::new(
906 vec![SubMessage::only_loc(loc)],
907 main_msg,
908 errno,
909 CompilerSystemError,
910 loc,
911 )
912 }
913
914 pub fn get_loc_with_fallback(&self) -> Location {
915 if self.loc == Location::Unknown {
916 for sub in &self.sub_messages {
917 if sub.loc != Location::Unknown {
918 return sub.loc;
919 }
920 }
921 Location::Unknown
922 } else {
923 self.loc
924 }
925 }
926
927 pub fn get_hint(&self) -> Option<&str> {
928 for sub in self.sub_messages.iter() {
929 if let Some(hint) = &sub.hint {
930 return Some(hint);
931 }
932 }
933 None
934 }
935
936 pub fn hints(&self) -> Vec<&str> {
937 self.sub_messages
938 .iter()
939 .filter_map(|sub| sub.hint.as_deref())
940 .collect()
941 }
942
943 pub fn mut_hints(&mut self) -> Vec<&mut String> {
944 self.sub_messages
945 .iter_mut()
946 .filter_map(|sub| sub.hint.as_mut())
947 .collect()
948 }
949
950 pub fn fmt_header(&self, color: Color, caused_by: &str, input: &str) -> String {
951 let loc = match self.loc {
952 Location::Range {
953 ln_begin, ln_end, ..
954 } if ln_begin == ln_end => format!(", line {ln_begin}"),
955 Location::Range {
956 ln_begin, ln_end, ..
957 }
958 | Location::LineRange(ln_begin, ln_end) => format!(", line {ln_begin}..{ln_end}"),
959 Location::Line(lineno) => format!(", line {lineno}"),
960 Location::Unknown => "".to_string(),
961 };
962 let kind = if self.kind.is_error() {
963 "Error"
964 } else if self.kind.is_warning() {
965 "Warning"
966 } else {
967 "Exception"
968 };
969 let kind = self.theme.characters.error_kind_format(kind, self.errno);
970 format!(
971 "{kind}: File {input}{loc}, {caused_by}",
972 kind = StyledStr::new(&kind, Some(color), Some(Attribute::Bold))
973 )
974 }
975
976 fn specified_theme(&self) -> (Color, char) {
977 let (color, mark) = if self.kind.is_error() {
978 self.theme.error()
979 } else if self.kind.is_warning() {
980 self.theme.warning()
981 } else {
982 self.theme.exception()
983 };
984 (color, mark)
985 }
986}
987
988pub trait ErrorDisplay {
1015 fn core(&self) -> &ErrorCore;
1016 fn input(&self) -> &Input;
1017 fn caused_by(&self) -> &str;
1020 fn ref_inner(&self) -> Option<&Self>;
1022
1023 fn write_to_stderr(&self) {
1024 let mut stderr = stderr();
1025 self.write_to(&mut stderr)
1026 }
1027
1028 fn write_to<W: std::io::Write>(&self, w: &mut W) {
1029 let mut writer = BufWriter::new(w);
1030 writer.write_all(self.show().as_bytes()).unwrap();
1031 writer.flush().unwrap();
1032 if let Some(inner) = self.ref_inner() {
1033 inner.write_to_stderr()
1034 }
1035 }
1036
1037 fn show(&self) -> String {
1038 let core = self.core();
1039 let (color, mark) = core.specified_theme();
1040 let (gutter_color, chars) = core.theme.characters();
1041 let mut msg = String::new();
1042 msg += &core.fmt_header(color, self.caused_by(), self.input().kind.as_str());
1043 msg += "\n\n";
1044 for sub_msg in &core.sub_messages {
1045 msg += &sub_msg.format_code_and_pointer(self, color, gutter_color, mark, chars);
1046 }
1047 if core.sub_messages.is_empty() {
1048 let sub_msg = SubMessage::ambiguous_new(self.core().loc, vec![], None);
1049 msg += &sub_msg.format_code_and_pointer(self, color, gutter_color, mark, chars);
1050 }
1051 msg += &core.kind.to_string();
1052 msg += ": ";
1053 msg += &core.main_message;
1054 msg += "\n\n";
1055 msg
1056 }
1057
1058 fn format(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1060 let core = self.core();
1061 let (color, mark) = core.specified_theme();
1062 let (gutter_color, chars) = core.theme.characters();
1063 write!(
1064 f,
1065 "{}\n\n",
1066 core.fmt_header(color, self.caused_by(), self.input().kind.as_str())
1067 )?;
1068 for sub_msg in &core.sub_messages {
1069 write!(
1070 f,
1071 "{}",
1072 &sub_msg.format_code_and_pointer(self, color, gutter_color, mark, chars)
1073 )?;
1074 }
1075 write!(f, "{}\n\n", core.main_message)?;
1076 if let Some(inner) = self.ref_inner() {
1077 inner.format(f)
1078 } else {
1079 Ok(())
1080 }
1081 }
1082}
1083
1084#[macro_export]
1085macro_rules! impl_display_and_error {
1086 ($Strc: ident) => {
1087 impl std::fmt::Display for $Strc {
1088 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1089 $crate::error::ErrorDisplay::format(self, f)
1090 }
1091 }
1092
1093 impl std::error::Error for $Strc {}
1094 };
1095}
1096
1097pub trait MultiErrorDisplay<Item: ErrorDisplay>: Stream<Item> {
1098 fn write_all_stderr(&self) {
1099 for err in self.iter() {
1100 err.write_to_stderr();
1101 }
1102 }
1103
1104 fn write_all_to(&self, w: &mut impl std::io::Write) {
1105 for err in self.iter() {
1106 err.write_to(w);
1107 }
1108 }
1109
1110 fn fmt_all(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1111 for err in self.iter() {
1112 err.format(f)?;
1113 }
1114 write!(f, "")
1115 }
1116}