erg_common/
error.rs

1//! provides common components for error handling.
2//!
3//! エラー処理に関する汎用的なコンポーネントを提供する
4use 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/// This includes not only Error but also Warning, Exception
23/// Numbering of this is not specifically related to ErrFmt.errno().
24#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
25#[repr(u8)]
26pub enum ErrorKind {
27    /* compile errors */
28    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    /* compile warnings */
53    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    /* runtime errors */
65    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    /* runtime warnings */
105    BytesWarning = 180,
106    ResourceWarning = 181,
107    RuntimeWarning = 182,
108    UnicodeWarning = 183,
109    UserWarning = 184,
110    /* exceptions */
111    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///
229/// Points the location (of an error) in a code.
230/// The beginning and end of each row and column where the error occurred.
231/// Basically, the beginning and end of each row and column where the error occurred is kept.
232///
233#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
234pub enum Location {
235    ///
236    /// Location used for basic errors
237    /// ```erg
238    /// # erg
239    /// a = 1
240    /// a = 2 # Error, `a` is assigned twice
241    /// // Value assigned to the structure
242    /// Location::Range {
243    ///    ln_begin: 2,
244    ///    col_begin: 0,
245    ///    ln_end: 2,
246    ///    col_end: 1,
247    /// }
248    /// ```
249    ///
250    Range {
251        /// 1-origin
252        ln_begin: u32,
253        /// 0-origin
254        col_begin: u32,
255        ln_end: u32,
256        col_end: u32,
257    },
258    /// Used for loss of location information when desugared.
259    /// If there are guaranteed to be multiple rows
260    LineRange(u32, u32),
261    /// Used when Location information is lost when desugared
262    /// If it is guaranteed to be a single line
263    Line(u32),
264    /// Used by default in case of loss of Location information
265    #[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        // ln_begin:col_begin-ln_end:col_end
337        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            // assert_eq!(self.line_begin, other.line_begin);
365            // assert_eq!(self.line_end, other.line_end);
366            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    /// 1-origin
485    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    /// 0-origin
504    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    /// ```
528    /// # use erg_common::error::Location;
529    /// let loc = Location::range(1, 3, 1, 7);
530    /// assert_eq!(loc.ln_begin(), Some(1));
531    /// assert!(loc.contains(Location::range(1, 4, 1, 5)));
532    /// let loc = Location::range(1, 3, 3, 2);
533    /// assert!(loc.contains(Location::range(1, 4, 1, 5)));
534    /// assert!(!loc.contains(Location::range(1, 4, 3, 5)));
535    /// assert!(loc.contains(Location::range(1, 4, 2, 5)));
536    /// assert!(!loc.contains(Location::range(1, 2, 2, 5)));
537    /// ```
538    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    // for formatting points
581    chars: &Characters,
582    // kinds of error for specify the color
583    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(&not_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    ///
652    /// Used when the msg or hint is empty.
653    /// `msg` is type of `Vec<String>` instead of `Option<String>` because it can be used when there are multiple `msg`s as well as multiple lines.
654    /// # Example
655    /// ```
656    /// # use erg_common::error::{Location, SubMessage};
657    /// # use erg_common::style::{Color, StyledString};
658    /// let loc = Location::Line(1);
659    /// let msg = SubMessage::ambiguous_new(loc, vec![], None); // this code same as only_loc()
660    ///
661    /// let hint = Some("hint message here".to_string());
662    /// let msg = SubMessage::ambiguous_new(loc, vec![], hint);
663    /// /* example
664    ///    -------
665    ///          `- hint message here
666    /// */
667    ///
668    /// let hint = Some("hint here".to_string());
669    /// let first = StyledString::new("1th message", Some(Color::Red), None);
670    /// let second = StyledString::new("2th message", Some(Color::White), None);
671    /// let nth = StyledString::new("nth message", Some(Color::Green), None);
672    /// let msg = SubMessage::ambiguous_new(
673    ///     loc,
674    ///     vec![
675    ///         first.to_string(),
676    ///         second.to_string(),
677    ///         // ...,
678    ///         nth.to_string(),
679    ///     ],
680    ///     hint);
681    /// /* example
682    ///    -------
683    ///          :- 1th message
684    ///          :- 2th message
685    ///                :
686    ///          :- nth message
687    ///          `- hint here
688    /// */
689    ///
690    /// ```
691    ///
692    pub fn ambiguous_new(loc: Location, msg: Vec<String>, hint: Option<String>) -> Self {
693        Self { loc, msg, hint }
694    }
695
696    ///
697    /// Used when only Location is fixed.
698    /// In this case, error position is just modified
699    /// # Example
700    /// ```
701    /// # use erg_common::error::{Location, SubMessage};
702    /// let loc = Location::Line(1);
703    /// let sub_msg = SubMessage::only_loc(loc);
704    /// ```
705    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    // Line breaks are not included except for line breaks that signify the end of a sentence.
726    // In other words, do not include blank lines for formatting purposes.
727    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/// In Erg, common parts used by error.
831/// Must be wrap when to use.
832#[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
988/// format:
989/// ```txt
990/// Error[#{.errno}]: File {file}, line {.loc (as line)}, in {.caused_by}
991///
992/// {.loc (as line)}| {src}
993/// {offset}        : {pointer}
994/// {offset}        :         {sub_msgs}
995/// {offset}        :         {.hint}
996///
997/// {.kind}: {.desc}
998///
999/// ```
1000///
1001/// example:
1002/// ```txt
1003/// Error[#2223]: File <stdin>, line 1, in <module>
1004///
1005/// 1 │ 100 = i
1006///   · ---
1007///   ·   │─ sub_msg1: first sub message here
1008///   ·   │─ sub_msg2: second sub message here
1009///   ·   ╰─ hint: hint message here
1010///
1011/// SyntaxError: cannot assign to 100
1012///
1013/// ```
1014pub trait ErrorDisplay {
1015    fn core(&self) -> &ErrorCore;
1016    fn input(&self) -> &Input;
1017    /// The block name the error caused.
1018    /// This will be None if the error occurred before semantic analysis.
1019    fn caused_by(&self) -> &str;
1020    /// the previous error that caused this error.
1021    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    /// for fmt::Display
1059    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}