//! provides common components for error handling. //! //! エラー処理に関する汎用的なコンポーネントを提供する use std::cmp::{self, Ordering}; use std::fmt; use std::io::{stderr, BufWriter, Write as _}; use crate::io::{Input, InputKind}; use crate::style::Attribute; use crate::style::Characters; use crate::style::Color; use crate::style::StyledStr; use crate::style::StyledStrings; use crate::style::Theme; use crate::style::THEME; use crate::traits::{Locational, Stream}; use crate::{impl_display_from_debug, switch_lang}; #[cfg(feature = "pylib")] use pyo3::prelude::*; /// This includes not only Error but also Warning, Exception /// Numbering of this is not specifically related to ErrFmt.errno(). #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] #[repr(u8)] pub enum ErrorKind { /* compile errors */ AssignError = 0, AttributeError = 1, BytecodeError = 2, CompilerSystemError = 3, EnvironmentError = 4, FeatureError = 5, ImportError = 6, IndentationError = 7, NameError = 8, NotImplementedError = 9, PatternError = 10, SyntaxError = 11, TabError = 12, TypeError = 13, UnboundLocalError = 14, PurityError = 15, HasEffect = 16, MoveError = 17, NotConstExpr = 18, InheritanceError = 19, VisibilityError = 20, MethodError = 21, DummyError = 22, ExpectNextLine = 23, /* compile warnings */ AttributeWarning = 60, CastWarning = 61, DeprecationWarning = 62, FutureWarning = 63, ImportWarning = 64, PendingDeprecationWarning = 65, SyntaxWarning = 66, TypeWarning = 67, NameWarning = 68, UnusedWarning = 69, Warning = 70, /* runtime errors */ ArithmeticError = 100, AssertionError = 101, BlockingIOError = 102, BrokenPipeError = 103, BufferError = 104, ChildProcessError = 105, ConnectionAbortedError = 106, ConnectionError = 107, ConnectionRefusedError = 108, ConnectionResetError = 109, EOFError = 110, FileExistsError = 111, FileNotFoundError = 112, IndexError = 113, InterruptedError = 114, IoError = 115, IsADirectoryError = 116, KeyError = 117, LookupError = 118, MemoryError = 119, ModuleNotFoundError = 120, NotADirectoryError = 121, OSError = 122, OverflowError = 123, PermissionError = 124, ProcessLookupError = 125, RecursionError = 126, ReferenceError = 127, RuntimeAttributeError = 128, RuntimeError = 129, RuntimeTypeError = 130, RuntimeUnicodeError = 131, TimeoutError = 132, UnicodeError = 133, UserError = 134, ValueError = 135, VMSystemError = 136, WindowsError = 137, ZeroDivisionError = 138, /* runtime warnings */ BytesWarning = 180, ResourceWarning = 181, RuntimeWarning = 182, UnicodeWarning = 183, UserWarning = 184, /* exceptions */ BaseException = 200, Exception = 201, GeneratorExit = 202, KeyboardInterrupt = 203, StopAsyncIteration = 204, StopIteration = 205, SystemExit = 206, UserException = 207, } use ErrorKind::*; impl_display_from_debug!(ErrorKind); impl ErrorKind { pub fn is_warning(&self) -> bool { (60..=100).contains(&(*self as u8)) || (180..=200).contains(&(*self as u8)) } pub fn is_error(&self) -> bool { (0..=59).contains(&(*self as u8)) || (100..=179).contains(&(*self as u8)) } pub fn is_exception(&self) -> bool { (200..=255).contains(&(*self as u8)) } } impl From<&str> for ErrorKind { fn from(s: &str) -> ErrorKind { match s { "AssignError" => Self::AssignError, "AttributeError" => Self::AttributeError, "BytecodeError" => Self::BytecodeError, "CompilerSystemError" => Self::CompilerSystemError, "EnvironmentError" => Self::EnvironmentError, "FeatureError" => Self::FeatureError, "ImportError" => Self::ImportError, "IndentationError" => Self::IndentationError, "NameError" => Self::NameError, "NotImplementedError" => Self::NotImplementedError, "PatternError" => Self::PatternError, "SyntaxError" => Self::SyntaxError, "TabError" => Self::TabError, "TypeError" => Self::TypeError, "UnboundLocalError" => Self::UnboundLocalError, "HasEffect" => Self::HasEffect, "PurityError" => Self::PurityError, "MoveError" => Self::MoveError, "AttributeWarning" => Self::AttributeWarning, "CastWarning" => Self::CastWarning, "DeprecationWarning" => Self::DeprecationWarning, "FutureWarning" => Self::FutureWarning, "ImportWarning" => Self::ImportWarning, "PendingDeprecationWarning" => Self::PendingDeprecationWarning, "SyntaxWarning" => Self::SyntaxWarning, "TypeWarning" => Self::TypeWarning, "NameWarning" => Self::NameWarning, "UnusedWarning" => Self::UnusedWarning, "Warning" => Self::Warning, "ArithmeticError" => Self::ArithmeticError, "AssertionError" => Self::AssertionError, "BlockingIOError" => Self::BlockingIOError, "BrokenPipeError" => Self::BrokenPipeError, "BufferError" => Self::BufferError, "ChildProcessError" => Self::ChildProcessError, "ConnectionAbortedError" => Self::ConnectionAbortedError, "ConnectionError" => Self::ConnectionError, "ConnectionRefusedError" => Self::ConnectionRefusedError, "ConnectionResetError" => Self::ConnectionResetError, "EOFError" => Self::EOFError, "FileExistsError" => Self::FileExistsError, "FileNotFoundError" => Self::FileNotFoundError, "IndexError" => Self::IndexError, "InterruptedError" => Self::InterruptedError, "IoError" => Self::IoError, "IsADirectoryError" => Self::IsADirectoryError, "KeyError" => Self::KeyError, "LookupError" => Self::LookupError, "MemoryError" => Self::MemoryError, "ModuleNotFoundError" => Self::ModuleNotFoundError, "NotADirectoryError" => Self::NotADirectoryError, "OSError" => Self::OSError, "OverflowError" => Self::OverflowError, "PermissionError" => Self::PermissionError, "ProcessLookupError" => Self::ProcessLookupError, "RecursionError" => Self::RecursionError, "ReferenceError" => Self::ReferenceError, "RuntimeAttributeError" => Self::RuntimeAttributeError, "RuntimeError" => Self::RuntimeError, "RuntimeTypeError" => Self::RuntimeTypeError, "RuntimeUnicodeError" => Self::RuntimeUnicodeError, "TimeoutError" => Self::TimeoutError, "UnicodeError" => Self::UnicodeError, "UserError" => Self::UserError, "ValueError" => Self::ValueError, "VMSystemError" => Self::VMSystemError, "WindowsError" => Self::WindowsError, "ZeroDivisionError" => Self::ZeroDivisionError, "BytesWarning" => Self::BytesWarning, "ResourceWarning" => Self::ResourceWarning, "RuntimeWarning" => Self::RuntimeWarning, "UnicodeWarning" => Self::UnicodeWarning, "UserWarning" => Self::UserWarning, "BaseException" => Self::BaseException, "Exception" => Self::Exception, "GeneratorExit" => Self::GeneratorExit, "KeyboardInterrupt" => Self::KeyboardInterrupt, "StopAsyncIteration" => Self::StopAsyncIteration, "StopIteration" => Self::StopIteration, "SystemExit" => Self::SystemExit, "UserException" => Self::UserException, _ => Self::UserError, } } } /// /// Points the location (of an error) in a code. /// The beginning and end of each row and column where the error occurred. /// Basically, the beginning and end of each row and column where the error occurred is kept. /// #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)] pub enum Location { /// /// Location used for basic errors /// ```erg /// # erg /// a = 1 /// a = 2 # Error, `a` is assigned twice /// // Value assigned to the structure /// Location::Range { /// ln_begin: 2, /// col_begin: 0, /// ln_end: 2, /// col_end: 1, /// } /// ``` /// Range { /// 1-origin ln_begin: u32, /// 0-origin col_begin: u32, ln_end: u32, col_end: u32, }, /// Used for loss of location information when desugared. /// If there are guaranteed to be multiple rows LineRange(u32, u32), /// Used when Location information is lost when desugared /// If it is guaranteed to be a single line Line(u32), /// Used by default in case of loss of Location information #[default] Unknown, } impl fmt::Display for Location { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::Range { ln_begin, col_begin, ln_end, col_end, } => write!(f, "{ln_begin}:{col_begin}-{ln_end}:{col_end}"), Self::LineRange(ln_begin, ln_end) => write!(f, "{ln_begin}:?-{ln_end}:?"), Self::Line(ln) => write!(f, "{ln}:??-{ln}:??"), Self::Unknown => write!(f, "?"), } } } #[cfg(feature = "pylib")] impl FromPyObject<'_> for Location { fn extract_bound(ob: &Bound<'_, PyAny>) -> PyResult { if let Ok(s) = ob.extract::() { Ok(s.parse::().unwrap()) } else if let Ok(s) = ob.extract::() { Ok(Location::Line(s)) } else if let Ok((l, r)) = ob.extract::<(u32, u32)>() { Ok(Location::LineRange(l, r)) } else if let Ok((lb, cb, le, ce)) = ob.extract::<(u32, u32, u32, u32)>() { Ok(Location::Range { ln_begin: lb, col_begin: cb, ln_end: le, col_end: ce, }) } else { Err(PyErr::new::(format!( "expected Into, but got {:?}", ob.get_type().name()? ))) } } } #[cfg(feature = "pylib")] impl IntoPy for Location { fn into_py(self, py: Python<'_>) -> PyObject { match self { Self::Line(l) => (l, py.None(), l, py.None()).into_py(py), Self::LineRange(lb, le) => (lb, py.None(), le, py.None()).into_py(py), Self::Range { ln_begin, col_begin, ln_end, col_end, } => (ln_begin, col_begin, ln_end, col_end).into_py(py), Self::Unknown => (py.None(), py.None(), py.None(), py.None()).into_py(py), } } } impl std::str::FromStr for Location { type Err = (); fn from_str(s: &str) -> Result { if s == "?" { return Ok(Self::Unknown); } // ln_begin:col_begin-ln_end:col_end let mut comps = s.split('-'); let mut comp1 = comps.next().ok_or(())?.split(':'); let mut comp2 = comps.next().ok_or(())?.split(':'); let ln_begin = comp1.next().unwrap().parse::().map_err(|_| ())?; let col_begin = comp1.next().unwrap().parse::(); let ln_end = comp2.next().unwrap().parse::().map_err(|_| ())?; let col_end = comp2.next().unwrap().parse::(); match (col_begin, col_end) { (Ok(col_begin), Ok(col_end)) => Ok(Self::Range { ln_begin, col_begin, ln_end, col_end, }), _ if ln_begin == ln_end => Ok(Self::Line(ln_begin)), _ => Ok(Self::LineRange(ln_begin, ln_end)), } } } impl Ord for Location { fn cmp(&self, other: &Location) -> Ordering { if self.ln_end() < other.ln_begin() { Ordering::Less } else if other.ln_end() < self.ln_begin() { Ordering::Greater } else if self.ln_begin() == self.ln_end() && other.ln_begin() == other.ln_end() { // assert_eq!(self.line_begin, other.line_begin); // assert_eq!(self.line_end, other.line_end); if self.col_end() <= other.col_begin() { Ordering::Less } else if other.col_end() <= self.col_begin() { Ordering::Greater } else { Ordering::Equal } } else { Ordering::Equal } } } impl PartialOrd for Location { #[allow(clippy::non_canonical_partial_ord_impl)] fn partial_cmp(&self, other: &Location) -> Option { if self.is_unknown() || other.is_unknown() { None } else { Some(self.cmp(other)) } } } impl Locational for Location { fn loc(&self) -> Self { *self } } impl Location { pub fn concat(l: &L, r: &R) -> Self { let l_loc = l.loc(); let r_loc = r.loc(); match ( l_loc.ln_begin(), l_loc.col_begin(), r_loc.ln_end(), r_loc.col_end(), ) { (Some(lb), Some(cb), Some(le), Some(ce)) => Self::range(lb, cb, le, ce), (Some(lb), _, Some(le), _) => Self::LineRange(lb, le), (Some(l), _, _, _) | (_, _, Some(l), _) => Self::Line(l), _ => Self::Unknown, } } pub fn left_main_concat(l: &L, r: &R) -> Self { let l_loc = l.loc(); let r_loc = r.loc(); match ( l_loc.ln_begin(), l_loc.col_begin(), r_loc.ln_end(), r_loc.col_end(), ) { (Some(lb), Some(cb), Some(le), Some(ce)) => Self::range(lb, cb, le, ce), (Some(_), _, None, None) => l_loc, (Some(lb), _, Some(le), _) => Self::LineRange(lb, le), (Some(l), _, _, _) | (_, _, Some(l), _) => Self::Line(l), _ => Self::Unknown, } } pub fn slow_stream(ls: &[L]) -> Self { if ls.is_empty() { return Self::Unknown; } let Some(first_known) = ls.iter().find(|l| !l.loc().is_unknown()) else { return Self::Unknown; }; let Some(last_known) = ls.iter().rfind(|l| !l.loc().is_unknown()) else { return Self::Unknown; }; Self::concat(first_known, last_known) } pub fn stream(ls: &[L]) -> Self { if ls.is_empty() { return Self::Unknown; } let first_known = ls.first().unwrap(); let last_known = ls.last().unwrap(); Self::concat(first_known, last_known) } pub const fn range(ln_begin: u32, col_begin: u32, ln_end: u32, col_end: u32) -> Self { Self::Range { ln_begin, col_begin, ln_end, col_end, } } pub const fn is_unknown(&self) -> bool { matches!(self, Self::Unknown) } pub const fn is_real(&self) -> bool { match self { Self::Line(l) => *l != 0, Self::LineRange(lb, le) => *lb != 0 && *le != 0, Self::Range { ln_begin, ln_end, .. } => *ln_begin != 0 && *ln_end != 0, Self::Unknown => false, } } pub const fn unknown_or(&self, other: Self) -> Self { if self.is_unknown() { other } else { *self } } /// 1-origin pub const fn ln_begin(&self) -> Option { match self { Self::Range { ln_begin, .. } | Self::LineRange(ln_begin, _) | Self::Line(ln_begin) => { Some(*ln_begin) } Self::Unknown => None, } } pub const fn ln_end(&self) -> Option { match self { Self::Range { ln_end, .. } | Self::LineRange(_, ln_end) | Self::Line(ln_end) => { Some(*ln_end) } Self::Unknown => None, } } /// 0-origin pub const fn col_begin(&self) -> Option { match self { Self::Range { col_begin, .. } => Some(*col_begin), _ => None, } } pub const fn col_end(&self) -> Option { match self { Self::Range { col_end, .. } => Some(*col_end), _ => None, } } pub const fn length(&self) -> Option { match self { Self::Range { col_begin, col_end, .. } => Some(*col_end - *col_begin), _ => None, } } /// ``` /// # use erg_common::error::Location; /// let loc = Location::range(1, 3, 1, 7); /// assert_eq!(loc.ln_begin(), Some(1)); /// assert!(loc.contains(Location::range(1, 4, 1, 5))); /// let loc = Location::range(1, 3, 3, 2); /// assert!(loc.contains(Location::range(1, 4, 1, 5))); /// assert!(!loc.contains(Location::range(1, 4, 3, 5))); /// assert!(loc.contains(Location::range(1, 4, 2, 5))); /// assert!(!loc.contains(Location::range(1, 2, 2, 5))); /// ``` pub fn contains(&self, other: Self) -> bool { match (*self, other) { ( Self::Range { ln_begin: lb1, col_begin: cb1, ln_end: le1, col_end: ce1, }, Self::Range { ln_begin: lb2, col_begin: cb2, ln_end: le2, col_end: ce2, }, ) => { let same_start_line = lb1 == lb2; let same_end_line = le1 == le2; if same_start_line && same_end_line { cb1 <= cb2 && ce1 >= ce2 } else if same_start_line { cb1 <= cb2 && le1 >= le2 } else if same_end_line { lb1 <= lb2 && ce1 >= ce2 } else { lb1 <= lb2 && le1 >= le2 } } _ => false, } } } #[allow(clippy::too_many_arguments)] fn format_context( e: &E, ln_begin: usize, ln_end: usize, col_begin: usize, col_end: usize, err_color: Color, gutter_color: Color, // for formatting points chars: &Characters, // kinds of error for specify the color mark: char, sub_msg: &[String], hint: Option<&String>, ) -> String { let mark = mark.to_string(); let codes = e.input().reread_lines(ln_begin, ln_end); let mut context = StyledStrings::default(); let final_step = ln_end - ln_begin; let max_digit = ln_end.to_string().len(); let (vbreak, vbar) = chars.gutters(); let offset = format!("{} {} ", &" ".repeat(max_digit), vbreak); for (i, lineno) in (ln_begin..=ln_end).enumerate() { context.push_str_with_color(format!("{lineno:, pub hint: Option, } impl SubMessage { /// /// Used when the msg or hint is empty. /// `msg` is type of `Vec` instead of `Option` because it can be used when there are multiple `msg`s as well as multiple lines. /// # Example /// ``` /// # use erg_common::error::{Location, SubMessage}; /// # use erg_common::style::{Color, StyledString}; /// let loc = Location::Line(1); /// let msg = SubMessage::ambiguous_new(loc, vec![], None); // this code same as only_loc() /// /// let hint = Some("hint message here".to_string()); /// let msg = SubMessage::ambiguous_new(loc, vec![], hint); /// /* example /// ------- /// `- hint message here /// */ /// /// let hint = Some("hint here".to_string()); /// let first = StyledString::new("1th message", Some(Color::Red), None); /// let second = StyledString::new("2th message", Some(Color::White), None); /// let nth = StyledString::new("nth message", Some(Color::Green), None); /// let msg = SubMessage::ambiguous_new( /// loc, /// vec![ /// first.to_string(), /// second.to_string(), /// // ..., /// nth.to_string(), /// ], /// hint); /// /* example /// ------- /// :- 1th message /// :- 2th message /// : /// :- nth message /// `- hint here /// */ /// /// ``` /// pub fn ambiguous_new(loc: Location, msg: Vec, hint: Option) -> Self { Self { loc, msg, hint } } /// /// Used when only Location is fixed. /// In this case, error position is just modified /// # Example /// ``` /// # use erg_common::error::{Location, SubMessage}; /// let loc = Location::Line(1); /// let sub_msg = SubMessage::only_loc(loc); /// ``` pub fn only_loc(loc: Location) -> Self { Self { loc, msg: Vec::new(), hint: None, } } pub fn set_hint>(&mut self, hint: S) { self.hint = Some(hint.into()); } pub fn get_hint(&self) -> Option<&str> { self.hint.as_deref() } pub fn get_msg(&self) -> &[String] { self.msg.as_ref() } // Line breaks are not included except for line breaks that signify the end of a sentence. // In other words, do not include blank lines for formatting purposes. fn format_code_and_pointer( &self, e: &E, err_color: Color, gutter_color: Color, mark: char, chars: &Characters, ) -> String { match self.loc.unknown_or(e.core().loc) { Location::Range { ln_begin, col_begin, ln_end, col_end, } => format_context( e, ln_begin as usize, ln_end as usize, col_begin as usize, col_end as usize, err_color, gutter_color, chars, mark, &self.msg, self.hint.as_ref(), ), Location::LineRange(ln_begin, ln_end) => { let (vbreak, vbar) = chars.gutters(); let mut cxt = StyledStrings::default(); let codes = e.input().reread_lines(ln_begin as usize, ln_end as usize); let mark = mark.to_string(); for (i, lineno) in (ln_begin..=ln_end).enumerate() { cxt.push_str_with_color(format!("{lineno} {vbar} "), gutter_color); cxt.push_str(codes.get(i).unwrap_or(&String::new())); cxt.push_str("\n"); cxt.push_str_with_color( format!("{} {}", &" ".repeat(lineno.to_string().len()), vbreak), gutter_color, ); cxt.push_str(&" ".repeat(lineno.to_string().len())); cxt.push_str_with_color( mark.repeat(cmp::max(1, codes.get(i).map_or(1, |code| code.len()))), err_color, ); cxt.push_str("\n"); } cxt.push_str("\n"); for msg in self.msg.iter() { cxt.push_str(msg); cxt.push_str("\n"); } if let Some(hint) = self.hint.as_ref() { cxt.push_str(hint); cxt.push_str("\n"); } cxt.to_string() } Location::Line(lineno) => { let input = e.input(); let (_, vbar) = chars.gutters(); let codes = input.reread_lines(lineno as usize, lineno as usize); let default = "???".to_string(); let code = codes.first().unwrap_or(&default); let mut cxt = StyledStrings::default(); cxt.push_str_with_color(format!(" {lineno} {vbar} "), gutter_color); cxt.push_str(code); cxt.push_str("\n"); for msg in self.msg.iter() { cxt.push_str(msg); cxt.push_str("\n"); } if let Some(hint) = self.hint.as_ref() { cxt.push_str(hint); cxt.push_str("\n"); } cxt.push_str("\n"); cxt.to_string() } Location::Unknown => match &e.input().kind { InputKind::File { .. } => "\n".to_string(), _other => { let (_, vbar) = chars.gutters(); let mut cxt = StyledStrings::default(); cxt.push_str_with_color(format!(" ? {vbar} "), gutter_color); cxt.push_str(&e.input().reread()); cxt.push_str("\n"); for msg in self.msg.iter() { cxt.push_str(msg); cxt.push_str("\n"); } if let Some(hint) = self.hint.as_ref() { cxt.push_str(hint); cxt.push_str("\n"); } cxt.push_str("\n"); cxt.to_string() } }, } } } /// In Erg, common parts used by error. /// Must be wrap when to use. #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct ErrorCore { pub sub_messages: Vec, pub main_message: String, pub errno: usize, pub kind: ErrorKind, pub loc: Location, theme: Theme, } impl fmt::Display for ErrorCore { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{self:?}") } } impl std::error::Error for ErrorCore {} impl ErrorCore { pub fn new>( sub_messages: Vec, main_message: S, errno: usize, kind: ErrorKind, loc: Location, ) -> Self { Self { sub_messages, main_message: main_message.into(), errno, kind, loc, theme: THEME, } } pub fn dummy(errno: usize) -> Self { Self::new( vec![SubMessage::only_loc(Location::Unknown)], "", errno, DummyError, Location::Unknown, ) } pub fn unreachable(fn_name: &str, line: u32) -> Self { Self::bug(line as usize, Location::Line(line), fn_name, line) } pub fn bug(errno: usize, loc: Location, fn_name: &str, line: u32) -> Self { const URL: StyledStr = StyledStr::new( "https://github.com/erg-lang/erg", Some(Color::White), Some(Attribute::Underline), ); let main_msg = switch_lang!( "japanese" => format!("\ これはErgのバグです、開発者に報告して下さい({URL}) 発生箇所: {fn_name}:{line}"), "simplified_chinese" => format!("\ 这是Erg的bug,请报告给{URL} 原因来自: {fn_name}:{line}"), "traditional_chinese" => format!("\ 這是Erg的bug,請報告給{URL} 原因來自: {fn_name}:{line}"), "english" => format!("\ This is a bug of Erg, please report it to {URL} Caused from: {fn_name}:{line}"), ); let main_msg = StyledStr::new(&main_msg, Some(Color::Red), Some(Attribute::Bold)).to_string(); Self::new( vec![SubMessage::only_loc(loc)], main_msg, errno, CompilerSystemError, loc, ) } pub fn get_loc_with_fallback(&self) -> Location { if self.loc == Location::Unknown { for sub in &self.sub_messages { if sub.loc != Location::Unknown { return sub.loc; } } Location::Unknown } else { self.loc } } pub fn get_hint(&self) -> Option<&str> { for sub in self.sub_messages.iter() { if let Some(hint) = &sub.hint { return Some(hint); } } None } pub fn hints(&self) -> Vec<&str> { self.sub_messages .iter() .filter_map(|sub| sub.hint.as_deref()) .collect() } pub fn mut_hints(&mut self) -> Vec<&mut String> { self.sub_messages .iter_mut() .filter_map(|sub| sub.hint.as_mut()) .collect() } pub fn fmt_header(&self, color: Color, caused_by: &str, input: &str) -> String { let loc = match self.loc { Location::Range { ln_begin, ln_end, .. } if ln_begin == ln_end => format!(", line {ln_begin}"), Location::Range { ln_begin, ln_end, .. } | Location::LineRange(ln_begin, ln_end) => format!(", line {ln_begin}..{ln_end}"), Location::Line(lineno) => format!(", line {lineno}"), Location::Unknown => "".to_string(), }; let kind = if self.kind.is_error() { "Error" } else if self.kind.is_warning() { "Warning" } else { "Exception" }; let kind = self.theme.characters.error_kind_format(kind, self.errno); format!( "{kind}: File {input}{loc}, {caused_by}", kind = StyledStr::new(&kind, Some(color), Some(Attribute::Bold)) ) } fn specified_theme(&self) -> (Color, char) { let (color, mark) = if self.kind.is_error() { self.theme.error() } else if self.kind.is_warning() { self.theme.warning() } else { self.theme.exception() }; (color, mark) } } /// format: /// ```txt /// Error[#{.errno}]: File {file}, line {.loc (as line)}, in {.caused_by} /// /// {.loc (as line)}| {src} /// {offset} : {pointer} /// {offset} : {sub_msgs} /// {offset} : {.hint} /// /// {.kind}: {.desc} /// /// ``` /// /// example: /// ```txt /// Error[#2223]: File , line 1, in /// /// 1 │ 100 = i /// · --- /// · │─ sub_msg1: first sub message here /// · │─ sub_msg2: second sub message here /// · ╰─ hint: hint message here /// /// SyntaxError: cannot assign to 100 /// /// ``` pub trait ErrorDisplay { fn core(&self) -> &ErrorCore; fn input(&self) -> &Input; /// The block name the error caused. /// This will be None if the error occurred before semantic analysis. fn caused_by(&self) -> &str; /// the previous error that caused this error. fn ref_inner(&self) -> Option<&Self>; fn write_to_stderr(&self) { let mut stderr = stderr(); self.write_to(&mut stderr) } fn write_to(&self, w: &mut W) { let mut writer = BufWriter::new(w); writer.write_all(self.show().as_bytes()).unwrap(); writer.flush().unwrap(); if let Some(inner) = self.ref_inner() { inner.write_to_stderr() } } fn show(&self) -> String { let core = self.core(); let (color, mark) = core.specified_theme(); let (gutter_color, chars) = core.theme.characters(); let mut msg = String::new(); msg += &core.fmt_header(color, self.caused_by(), self.input().kind.as_str()); msg += "\n\n"; for sub_msg in &core.sub_messages { msg += &sub_msg.format_code_and_pointer(self, color, gutter_color, mark, chars); } if core.sub_messages.is_empty() { let sub_msg = SubMessage::ambiguous_new(self.core().loc, vec![], None); msg += &sub_msg.format_code_and_pointer(self, color, gutter_color, mark, chars); } msg += &core.kind.to_string(); msg += ": "; msg += &core.main_message; msg += "\n\n"; msg } /// for fmt::Display fn format(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let core = self.core(); let (color, mark) = core.specified_theme(); let (gutter_color, chars) = core.theme.characters(); write!( f, "{}\n\n", core.fmt_header(color, self.caused_by(), self.input().kind.as_str()) )?; for sub_msg in &core.sub_messages { write!( f, "{}", &sub_msg.format_code_and_pointer(self, color, gutter_color, mark, chars) )?; } write!(f, "{}\n\n", core.main_message)?; if let Some(inner) = self.ref_inner() { inner.format(f) } else { Ok(()) } } } #[macro_export] macro_rules! impl_display_and_error { ($Strc: ident) => { impl std::fmt::Display for $Strc { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { $crate::error::ErrorDisplay::format(self, f) } } impl std::error::Error for $Strc {} }; } pub trait MultiErrorDisplay: Stream { fn write_all_stderr(&self) { for err in self.iter() { err.write_to_stderr(); } } fn write_all_to(&self, w: &mut impl std::io::Write) { for err in self.iter() { err.write_to(w); } } fn fmt_all(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { for err in self.iter() { err.format(f)?; } write!(f, "") } }