Merge pull request #232 from erg-lang/split-err-msg

Split error messages
This commit is contained in:
Shunsuke Shibayama 2022-11-23 15:55:19 +09:00 committed by GitHub
commit 2caa6b6ec9
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
15 changed files with 1652 additions and 1006 deletions

View file

@ -10,9 +10,9 @@ use crate::style::Attribute;
use crate::style::Characters; use crate::style::Characters;
use crate::style::Color; use crate::style::Color;
use crate::style::StyledStr; use crate::style::StyledStr;
use crate::style::StyledString;
use crate::style::StyledStrings; use crate::style::StyledStrings;
use crate::style::Theme; use crate::style::Theme;
use crate::style::THEME;
use crate::traits::{Locational, Stream}; use crate::traits::{Locational, Stream};
use crate::{impl_display_from_debug, switch_lang}; use crate::{impl_display_from_debug, switch_lang};
@ -228,37 +228,6 @@ impl From<&str> for ErrorKind {
/// ///
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub enum Location { pub enum Location {
///
/// Error used when the error is caused by a discrepancy with a code on another line
///
/// # Example
///
/// Ownership error
///
/// ```erg
/// a: Nat = 1
/// a.consume_ownership() // move occurs
///
/// function(a) // borrowed after moved
/// ```
///
/// `a` moves ownership in a method(or function) that are defined and consume it.
///
/// ```erg
/// Location::RangePair {
/// ln_first: (2, 2),
/// col_first: (0, 1),
/// ln_second: (4, 4),
/// col_second: (9, 10),
/// }
/// ```
///
RangePair {
ln_first: (usize, usize),
col_first: (usize, usize),
ln_second: (usize, usize),
col_second: (usize, usize),
},
/// ///
/// Location used for basic errors /// Location used for basic errors
/// ```erg /// ```erg
@ -310,128 +279,39 @@ impl Location {
} }
} }
pub fn pair(lhs: Self, rhs: Self) -> Self {
Self::RangePair {
ln_first: (lhs.ln_begin().unwrap(), lhs.ln_end().unwrap()),
col_first: (lhs.col_begin().unwrap(), lhs.col_end().unwrap()),
ln_second: (rhs.ln_begin().unwrap(), rhs.ln_end().unwrap()),
col_second: (rhs.col_begin().unwrap(), rhs.col_end().unwrap()),
}
}
pub const fn ln_begin(&self) -> Option<usize> { pub const fn ln_begin(&self) -> Option<usize> {
match self { match self {
Self::RangePair { Self::Range { ln_begin, .. } | Self::LineRange(ln_begin, _) | Self::Line(ln_begin) => {
ln_first: (ln_begin, _), Some(*ln_begin)
..
} }
| Self::Range { ln_begin, .. }
| Self::LineRange(ln_begin, _)
| Self::Line(ln_begin) => Some(*ln_begin),
Self::Unknown => None, Self::Unknown => None,
} }
} }
pub const fn ln_end(&self) -> Option<usize> { pub const fn ln_end(&self) -> Option<usize> {
match self { match self {
Self::RangePair { Self::Range { ln_end, .. } | Self::LineRange(ln_end, _) | Self::Line(ln_end) => {
ln_second: (_, ln_end), Some(*ln_end)
..
} }
| Self::Range { ln_end, .. }
| Self::LineRange(ln_end, _)
| Self::Line(ln_end) => Some(*ln_end),
Self::Unknown => None, Self::Unknown => None,
} }
} }
pub const fn col_begin(&self) -> Option<usize> { pub const fn col_begin(&self) -> Option<usize> {
match self { match self {
Self::RangePair { Self::Range { col_begin, .. } => Some(*col_begin),
col_first: (col_begin, _),
..
}
| Self::Range { col_begin, .. } => Some(*col_begin),
_ => None, _ => None,
} }
} }
pub const fn col_end(&self) -> Option<usize> { pub const fn col_end(&self) -> Option<usize> {
match self { match self {
Self::RangePair { Self::Range { col_end, .. } => Some(*col_end),
col_second: (_, col_end),
..
}
| Self::Range { col_end, .. } => Some(*col_end),
_ => None, _ => None,
} }
} }
} }
/// In Erg, common parts used by error.
/// Must be wrap when to use.
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct ErrorCore {
pub errno: usize,
pub kind: ErrorKind,
pub loc: Location,
pub desc: String,
pub hint: Option<String>,
}
impl ErrorCore {
pub fn new<S: Into<String>>(
errno: usize,
kind: ErrorKind,
loc: Location,
desc: S,
hint: Option<String>,
) -> Self {
Self {
errno,
kind,
loc,
desc: desc.into(),
hint,
}
}
pub fn dummy(errno: usize) -> Self {
Self::new(
errno,
DummyError,
Location::Line(errno as usize),
"<dummy>",
None,
)
}
pub fn unreachable(fn_name: &str, line: u32) -> Self {
Self::bug(line as usize, Location::Line(line as usize), 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),
);
Self::new(
errno,
CompilerSystemError,
loc,
switch_lang!(
"japanese" => format!("これはErgのバグです、開発者に報告して下さい({URL})\n{fn_name}:{line}より発生"),
"simplified_chinese" => format!("这是Erg的bug请报告给{URL}\n原因来自: {fn_name}:{line}"),
"traditional_chinese" => format!("这是Erg的bug请报告给{URL}\n原因来自: {fn_name}:{line}"),
"english" => format!("this is a bug of Erg, please report it to {URL}\ncaused from: {fn_name}:{line}"),
),
None,
)
}
}
#[allow(clippy::too_many_arguments)] #[allow(clippy::too_many_arguments)]
fn format_context<E: ErrorDisplay + ?Sized>( fn format_context<E: ErrorDisplay + ?Sized>(
e: &E, e: &E,
@ -445,6 +325,8 @@ fn format_context<E: ErrorDisplay + ?Sized>(
chars: &Characters, chars: &Characters,
// kinds of error for specify the color // kinds of error for specify the color
mark: char, mark: char,
sub_msg: &[String],
hint: Option<&String>,
) -> String { ) -> String {
let mark = mark.to_string(); let mark = mark.to_string();
let codes = if e.input().is_repl() { let codes = if e.input().is_repl() {
@ -484,20 +366,326 @@ fn format_context<E: ErrorDisplay + ?Sized>(
} }
context.push_str("\n"); context.push_str("\n");
} }
context.push_str_with_color(&offset, gutter_color);
context.push_str(&" ".repeat(col_end - 1)); let msg_num = sub_msg.len().saturating_sub(1);
context.push_str_with_color(&chars.left_bottom_line(), err_color); for (i, msg) in sub_msg.iter().enumerate() {
context.to_string() context.push_str_with_color(&offset, gutter_color);
context.push_str(&" ".repeat(col_end - 1));
if i == msg_num && hint.is_none() {
context.push_str_with_color(&chars.left_bottom_line(), err_color);
} else {
context.push_str_with_color(&chars.left_cross(), err_color);
}
context.push_str(msg);
context.push_str("\n")
}
if let Some(hint) = hint {
context.push_str_with_color(&offset, gutter_color);
context.push_str(&" ".repeat(col_end - 1));
context.push_str_with_color(&chars.left_bottom_line(), err_color);
context.push_str(hint);
context.push_str("\n")
}
context.to_string() + "\n"
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct SubMessage {
pub loc: Location,
msg: Vec<String>,
hint: Option<String>,
}
impl SubMessage {
///
/// Used when the msg or hint si empty.
/// `msg` is Vec\<String\> instead of Option\<String\> because it can be used when there are multiple `msg`s as well as multiple lines.
/// # Example
/// ```
/// 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", Color::Red, None);
/// let second = StyledString::new("2th message", Color::White, None);
/// :
/// let nth = StyledString::new("nth message", 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<String>, hint: Option<String>) -> Self {
Self { loc, msg, hint }
}
///
/// Used when only Location is fixed.
/// In this case, error position is just modified
/// # Example
/// ```
/// 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<S: Into<String>>(&mut self, hint: S) {
self.hint = Some(hint.into());
}
pub fn get_hint(self) -> Option<String> {
self.hint
}
// 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<E: ErrorDisplay + ?Sized>(
&self,
e: &E,
err_color: Color,
gutter_color: Color,
mark: char,
chars: &Characters,
) -> String {
match self.loc {
Location::Range {
ln_begin,
col_begin,
ln_end,
col_end,
} => format_context(
e,
ln_begin,
ln_end,
col_begin,
col_end,
err_color,
gutter_color,
chars,
mark,
&self.msg,
self.hint.as_ref(),
),
Location::LineRange(ln_begin, ln_end) => {
let input = e.input();
let (vbreak, vbar) = chars.gutters();
let mut cxt = StyledStrings::default();
let codes = if input.is_repl() {
vec![input.reread()]
} else {
input.reread_lines(ln_begin, ln_end)
};
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[i]);
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[i].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 code = if input.is_repl() {
input.reread()
} else {
input.reread_lines(lineno, lineno).remove(0)
};
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() {
Input::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(&other.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<SubMessage>,
pub main_message: String,
pub errno: usize,
pub kind: ErrorKind,
pub loc: Location,
theme: Theme,
}
impl ErrorCore {
pub fn new<S: Into<String>>(
sub_messages: Vec<SubMessage>,
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::Line(errno as usize))],
"<dummy>",
errno,
DummyError,
Location::Line(errno as usize),
)
}
pub fn unreachable(fn_name: &str, line: u32) -> Self {
Self::bug(line as usize, Location::Line(line as usize), 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 m_msg = switch_lang!(
"japanese" => format!("これはErgのバグです、開発者に報告して下さい({URL})\n{fn_name}:{line}より発生"),
"simplified_chinese" => format!("这是Erg的bug请报告给{URL}\n原因来自: {fn_name}:{line}"),
"traditional_chinese" => format!("这是Erg的bug请报告给{URL}\n原因来自: {fn_name}:{line}"),
"english" => format!("this is a bug of Erg, please report it to {URL}\ncaused from: {fn_name}:{line}"),
);
Self::new(
vec![SubMessage::only_loc(loc)],
&m_msg,
errno,
CompilerSystemError,
loc,
)
}
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: /// format:
/// ```txt /// ```txt
/// Error[#{.errno}]: File {file}, line {.loc (as line)}, in {.caused_by} /// Error[#{.errno}]: File {file}, line {.loc (as line)}, in {.caused_by}
/// {.loc (as line)}| {src}
/// {pointer}
/// {.kind}: {.desc}
/// ///
/// {.hint} /// {.loc (as line)}| {src}
/// {offset} : {pointer}
/// {offset} : {sub_msgs}
/// {offset} : {.hint}
///
/// {.kind}: {.desc}
/// ///
/// ``` /// ```
/// ///
@ -505,21 +693,20 @@ fn format_context<E: ErrorDisplay + ?Sized>(
/// ```txt /// ```txt
/// Error[#2223]: File <stdin>, line 1, in <module> /// Error[#2223]: File <stdin>, line 1, in <module>
/// ///
/// 1 | 100 = i /// 1 │ 100 = i
/// --- /// · ---
/// ╰─ SyntaxError: cannot assign to 100 /// · │─ sub_msg1: first sub message here
/// · │─ sub_msg2: second sub message here
/// · ╰─ hint: hint message here
/// ///
/// hint: hint message here /// SyntaxError: cannot assign to 100
/// ///
/// ``` /// ```
pub trait ErrorDisplay { pub trait ErrorDisplay {
fn core(&self) -> &ErrorCore; fn core(&self) -> &ErrorCore;
fn input(&self) -> &Input; fn input(&self) -> &Input;
/// Colors and indication char for each type(error, warning, exception)
fn theme(&self) -> &Theme;
/// The block name the error caused. /// The block name the error caused.
/// This will be None if the error occurred before semantic analysis. /// This will be None if the error occurred before semantic analysis.
/// As for the internal error, do not put the fn name here.
fn caused_by(&self) -> &str; fn caused_by(&self) -> &str;
/// the previous error that caused this error. /// the previous error that caused this error.
fn ref_inner(&self) -> Option<&Self>; fn ref_inner(&self) -> Option<&Self>;
@ -539,248 +726,44 @@ pub trait ErrorDisplay {
} }
fn show(&self) -> String { fn show(&self) -> String {
let theme = self.theme(); let core = self.core();
let ((color, mark), kind) = if self.core().kind.is_error() { let (color, mark) = core.specified_theme();
(theme.error(), "Error") let (gutter_color, chars) = core.theme.characters();
} else if self.core().kind.is_warning() { let mut msg = String::new();
(theme.warning(), "Warning") msg += &core.fmt_header(color, self.caused_by(), self.input().enclosed_name());
} else { msg += "\n\n";
(theme.exception(), "Exception") for sub_msg in &core.sub_messages {
}; msg += &sub_msg.format_code_and_pointer(self, color, gutter_color, mark, chars);
let (gutter_color, chars) = theme.characters();
let kind = StyledString::new(
&chars.error_kind_format(kind, self.core().errno),
Some(color),
Some(Attribute::Bold),
);
// When hint is None, hint desc is "" and empty line is displayed, but hint is Some(...), hint desc is "..." and filled by text
if let Some(hint) = self.core().hint.as_ref() {
let (hint_color, _) = theme.hint();
let mut hints = StyledStrings::default();
hints.push_str_with_color_and_attribute("hint: ", hint_color, Attribute::Bold);
hints.push_str(hint);
format!(
"\
{}
{}{}: {}
{}
",
self.format_header(kind),
self.format_code_and_pointer(color, gutter_color, mark, chars),
self.core().kind,
self.core().desc,
hints,
)
} else {
format!(
"\
{}
{}{}: {}
",
self.format_header(kind),
self.format_code_and_pointer(color, gutter_color, mark, chars),
self.core().kind,
self.core().desc,
)
} }
msg += &core.main_message;
msg += "\n\n";
msg
} }
/// for fmt::Display /// for fmt::Display
fn format(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn format(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let theme = self.theme(); let core = self.core();
let ((color, mark), kind) = if self.core().kind.is_error() { let (color, mark) = core.specified_theme();
(theme.error(), "Error") let (gutter_color, chars) = core.theme.characters();
} else if self.core().kind.is_warning() { write!(
(theme.warning(), "Warning") f,
} else { "{}\n\n",
(theme.exception(), "Exception") core.fmt_header(color, self.caused_by(), self.input().enclosed_name())
}; )?;
let (gutter_color, chars) = theme.characters(); for sub_msg in &core.sub_messages {
let kind = StyledString::new( write!(
&chars.error_kind_format(kind, self.core().errno),
Some(color),
Some(Attribute::Bold),
);
// When hint is None, hint desc is "" and empty line is displayed, but hint is Some(...), hint desc is "..." and filled by text
if let Some(hint) = self.core().hint.as_ref() {
let (hint_color, _) = theme.hint();
let mut hints = StyledStrings::default();
hints.push_str_with_color_and_attribute("hint: ", hint_color, Attribute::Bold);
hints.push_str(hint);
writeln!(
f, f,
"\ "{}",
{} &sub_msg.format_code_and_pointer(self, color, gutter_color, mark, chars)
{}{}: {}
{}
",
self.format_header(kind),
self.format_code_and_pointer(color, gutter_color, mark, chars),
self.core().kind,
self.core().desc,
hints,
)?;
} else {
writeln!(
f,
"\
{}
{}{}: {}
",
self.format_header(kind),
self.format_code_and_pointer(color, gutter_color, mark, chars),
self.core().kind,
self.core().desc,
)?; )?;
} }
write!(f, "{}\n\n", core.main_message)?;
if let Some(inner) = self.ref_inner() { if let Some(inner) = self.ref_inner() {
inner.format(f) inner.format(f)
} else { } else {
Ok(()) Ok(())
} }
} }
fn format_header(&self, kind: StyledString) -> String {
let loc = match self.core().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::RangePair {
ln_first: (l1, l2),
ln_second: (l3, l4),
..
} => format!(", line {l1}..{l2}, {l3}..{l4}"),
Location::Line(lineno) => format!(", line {lineno}"),
Location::Unknown => "".to_string(),
};
let caused_by = if self.caused_by() != "" {
format!(", in {}", self.caused_by())
} else {
"".to_string()
};
format!(
"{kind}: File {input}{loc}{caused_by}\n",
input = self.input().enclosed_name(),
)
}
fn format_code_and_pointer(
&self,
err_color: Color,
gutter_color: Color,
mark: char,
chars: &Characters,
) -> String {
match self.core().loc {
// TODO: Current implementation does not allow for multiple descriptions of errors to be given at each location
// In the future, this will be implemented in a different structure that can handle multiple lines and files
Location::RangePair {
ln_first,
col_first,
ln_second,
col_second,
} => {
format_context(
self,
ln_first.0,
ln_first.1,
col_first.0,
col_first.1,
err_color,
gutter_color,
chars,
mark,
) +
"\n" // TODO: dealing with error chains
+ &format_context(
self,
ln_second.0,
ln_second.1,
col_second.0,
col_second.1,
err_color,
gutter_color,
chars,
mark,
)
}
Location::Range {
ln_begin,
col_begin,
ln_end,
col_end,
} => format_context(
self,
ln_begin,
ln_end,
col_begin,
col_end,
err_color,
gutter_color,
chars,
mark,
),
Location::LineRange(ln_begin, ln_end) => {
let (_, vbar) = chars.gutters();
let mut cxt = StyledStrings::default();
let codes = if self.input().is_repl() {
vec![self.input().reread()]
} else {
self.input().reread_lines(ln_begin, ln_end)
};
let mark = mark.to_string();
for (i, lineno) in (ln_begin..=ln_end).enumerate() {
cxt.push_str_with_color(&format!("{lineno} {}", vbar), err_color);
cxt.push_str(&codes[i]);
cxt.push_str("\n");
cxt.push_str(&" ".repeat(lineno.to_string().len() + 3)); // +3 means ` | `
cxt.push_str_with_color(
&mark.repeat(cmp::max(1, codes[i].len())),
gutter_color,
);
cxt.push_str("\n");
}
cxt.to_string()
}
Location::Line(lineno) => {
let (_, vbar) = chars.gutters();
let code = if self.input().is_repl() {
self.input().reread()
} else {
self.input().reread_lines(lineno, lineno).remove(0)
};
let mut cxt = StyledStrings::default();
cxt.push_str_with_color(&format!(" {lineno} {} ", vbar), gutter_color);
cxt.push_str(&code);
cxt.push_str("\n");
cxt.to_string()
}
Location::Unknown => match self.input() {
Input::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(&other.reread());
cxt.to_string()
}
},
}
}
} }
#[macro_export] #[macro_export]

View file

@ -1,4 +1,4 @@
use std::borrow::Borrow; use std::borrow::{Borrow, Cow};
use std::fmt; use std::fmt;
use std::hash::{Hash, Hasher}; use std::hash::{Hash, Hasher};
use std::ops::{Add, Deref}; use std::ops::{Add, Deref};
@ -61,6 +61,15 @@ impl From<Str> for String {
} }
} }
impl<'a> From<Str> for Cow<'a, str> {
fn from(s: Str) -> Self {
match s {
Str::Static(s) => Cow::Owned(s.to_owned()),
_ => unreachable!(),
}
}
}
// &'static str -> &strになってしまわないように // &'static str -> &strになってしまわないように
// あえて`impl<S: Into<Str>> From<S> for Str { ... }`はしない // あえて`impl<S: Into<Str>> From<S> for Str { ... }`はしない
impl From<&'static str> for Str { impl From<&'static str> for Str {

View file

@ -1,3 +1,5 @@
use std::borrow::Cow;
pub const ATTR_RESET: &str = "\x1b[0m"; pub const ATTR_RESET: &str = "\x1b[0m";
pub const BOLD: &str = "\x1b[1m"; pub const BOLD: &str = "\x1b[1m";
pub const UNDERLINE: &str = "\x1b[4m"; pub const UNDERLINE: &str = "\x1b[4m";
@ -15,15 +17,15 @@ pub const RED: &str = "\x1b[91m";
pub const WHITE: &str = "\x1b[97m"; pub const WHITE: &str = "\x1b[97m";
pub const YELLOW: &str = "\x1b[93m"; pub const YELLOW: &str = "\x1b[93m";
// custom colors when use `pretty` // custom colors when use `pretty`
pub const CUSTOM_RED: &str = "\x1b[38;2;185;64;71m"; pub const CUSTOM_RED: &str = "\x1b[38;2;255;76;76m";
pub const CUSTOM_BLUE: &str = "\x1b[38;2;230;234;227m"; pub const CUSTOM_BLUE: &str = "\x1b[38;2;76;76;255m";
pub const CUSTOM_GRAY: &str = "\x1b[38;2;244;0;25m"; pub const CUSTOM_GRAY: &str = "\x1b[38;2;231;231;235m";
pub const CUSTOM_CYAN: &str = "\x1b[38;2;160;216;239m"; pub const CUSTOM_CYAN: &str = "\x1b[38;2;76;255;255m";
pub const CUSTOM_MAGENTA: &str = "\x1b[38;2;103;65;150m"; pub const CUSTOM_MAGENTA: &str = "\x1b[38;2;165;76;255m";
pub const CUSTOM_GREEN: &str = "\x1b[38;2;170;209;71m"; pub const CUSTOM_GREEN: &str = "\x1b[38;2;76;255;76m";
pub const CUSTOM_YELLOW: &str = "\x1b[38;2;230;180;34m"; pub const CUSTOM_YELLOW: &str = "\x1b[38;2;255;255;76m";
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd)] #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Hash)]
pub enum Color { pub enum Color {
Reset, Reset,
Black, Black,
@ -68,7 +70,7 @@ impl Color {
} }
} }
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd)] #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Hash)]
pub enum Attribute { pub enum Attribute {
Reset, Reset,
Underline, Underline,
@ -87,13 +89,14 @@ impl Attribute {
} }
} }
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct ThemeColors { pub struct ThemeColors {
pub error: Color, pub error: Color,
pub warning: Color, pub warning: Color,
pub exception: Color, pub exception: Color,
pub gutter: Color, pub gutter: Color,
pub hint: Color, pub hint: Color,
pub accent: Color,
} }
#[cfg(not(feature = "pretty"))] #[cfg(not(feature = "pretty"))]
@ -103,6 +106,7 @@ pub const COLORS: ThemeColors = ThemeColors {
exception: Color::Magenta, exception: Color::Magenta,
gutter: Color::Cyan, gutter: Color::Cyan,
hint: Color::Green, hint: Color::Green,
accent: Color::White,
}; };
#[cfg(feature = "pretty")] #[cfg(feature = "pretty")]
@ -112,9 +116,10 @@ pub const COLORS: ThemeColors = ThemeColors {
exception: Color::CustomMagenta, exception: Color::CustomMagenta,
gutter: Color::CustomCyan, gutter: Color::CustomCyan,
hint: Color::CustomGreen, hint: Color::CustomGreen,
accent: Color::CustomGray,
}; };
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct Characters { pub struct Characters {
hat: char, // error hat: char, // error
wave: char, // exception wave: char, // exception
@ -141,10 +146,10 @@ impl Characters {
(self.vbreak, self.vbar) (self.vbreak, self.vbar)
} }
// " `- " // "`- "
#[cfg(not(feature = "unicode"))] #[cfg(not(feature = "unicode"))]
pub fn left_bottom_line(&self) -> String { pub fn left_bottom_line(&self) -> String {
format!(" {}{} ", self.lbot, self.line) format!("{}{} ", self.lbot, self.line)
} }
// `╰─ ` // `╰─ `
@ -153,6 +158,18 @@ impl Characters {
format!("{}{} ", self.lbot, self.line) format!("{}{} ", self.lbot, self.line)
} }
// "|- "
#[cfg(not(feature = "unicode"))]
pub fn left_cross(&self) -> String {
format!("{}{} ", self.vbar, self.line)
}
// "│─ "
#[cfg(feature = "unicode")]
pub fn left_cross(&self) -> String {
format!("{}{} ", self.vbar, self.line)
}
// kind[padded error number] // kind[padded error number]
#[cfg(not(feature = "pretty"))] #[cfg(not(feature = "pretty"))]
pub fn error_kind_format(&self, kind: &str, err_num: usize) -> String { pub fn error_kind_format(&self, kind: &str, err_num: usize) -> String {
@ -177,7 +194,7 @@ impl Characters {
} }
} }
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct Theme { pub struct Theme {
pub colors: ThemeColors, pub colors: ThemeColors,
pub characters: Characters, pub characters: Characters,
@ -254,11 +271,7 @@ pub struct StyledStr<'a> {
} }
impl<'a> StyledStr<'a> { impl<'a> StyledStr<'a> {
pub const fn new<'b: 'a>( pub const fn new(text: &'a str, color: Option<Color>, attribute: Option<Attribute>) -> Self {
text: &'b str,
color: Option<Color>,
attribute: Option<Attribute>,
) -> Self {
Self { Self {
text, text,
color, color,
@ -300,9 +313,22 @@ pub struct StyledString {
} }
impl StyledString { impl StyledString {
pub fn new(s: &str, color: Option<Color>, attribute: Option<Attribute>) -> Self { ///
/// # Example
/// ```
/// let s = String::from("Hello, world");
/// StyledString::new(s, None, None);
/// let s = "Hello, world";
/// StyledString::new(s, None, None);
/// ```
pub fn new<'a, S: Into<Cow<'a, str>>>(
s: S,
color: Option<Color>,
attribute: Option<Attribute>,
) -> Self {
let text: Cow<'a, str> = s.into();
Self { Self {
text: String::from(s), text: text.into_owned(),
color, color,
attribute, attribute,
} }
@ -320,7 +346,11 @@ impl StyledString {
/// println!("{text}"); // Two lines of text underlined are displayed /// println!("{text}"); // Two lines of text underlined are displayed
/// ``` /// ```
pub fn push_str(&mut self, s: &str) { pub fn push_str(&mut self, s: &str) {
self.text.push_str(s); self.text.push_str(s)
}
pub fn is_empty(&self) -> bool {
self.text.is_empty()
} }
} }
@ -405,9 +435,10 @@ impl StyledStrings {
/// texts.push_str_with_color("\n If you want to add break lines, you should add `\n`.", Color::Magenta); /// texts.push_str_with_color("\n If you want to add break lines, you should add `\n`.", Color::Magenta);
/// println!("{}", texts); /// println!("{}", texts);
/// ``` /// ```
pub fn push_str_with_color(&mut self, s: &str, color: Color) { pub fn push_str_with_color<'a, S: Into<Cow<'a, str>>>(&mut self, s: S, color: Color) {
if self.is_same_color(color) { if self.is_same_color(color) {
self.texts.last_mut().unwrap().text.push_str(s); let text = s.into();
self.texts.last_mut().unwrap().text.push_str(&text);
} else { } else {
self.texts.push(StyledString::new(s, Some(color), None)); self.texts.push(StyledString::new(s, Some(color), None));
} }
@ -426,23 +457,37 @@ impl StyledStrings {
/// // texts.push_str_with_color_and_attribute("Must be specify the color and attribute", None, Attribute::Underline); /// // texts.push_str_with_color_and_attribute("Must be specify the color and attribute", None, Attribute::Underline);
/// println!("{}", texts); /// println!("{}", texts);
/// ``` /// ```
pub fn push_str_with_color_and_attribute(&mut self, s: &str, color: Color, attr: Attribute) { pub fn push_str_with_color_and_attribute<'a, S: Into<Cow<'a, str>>>(
&mut self,
s: S,
color: Color,
attr: Attribute,
) {
if self.is_same_color(color) && self.is_same_attribute(attr) { if self.is_same_color(color) && self.is_same_attribute(attr) {
self.texts.last_mut().unwrap().text.push_str(s); let text = s.into();
self.texts.last_mut().unwrap().text.push_str(&text);
} else { } else {
self.texts self.texts
.push(StyledString::new(s, Some(color), Some(attr))); .push(StyledString::new(s, Some(color), Some(attr)));
} }
} }
pub fn is_same_color(&self, color: Color) -> bool { ///
/// Determine if all strings in Vec are empty
/// Returns False if any string is present.
///
pub fn is_empty(&self) -> bool {
self.texts.iter().all(|s| s.is_empty())
}
fn is_same_color(&self, color: Color) -> bool {
if let Some(text) = self.texts.last() { if let Some(text) = self.texts.last() {
return text.color == Some(color); return text.color == Some(color);
} }
false false
} }
pub fn is_same_attribute(&self, attr: Attribute) -> bool { fn is_same_attribute(&self, attr: Attribute) -> bool {
if let Some(text) = self.texts.last() { if let Some(text) = self.texts.last() {
if let Some(text_attr) = text.attribute { if let Some(text_attr) = text.attribute {
return text_attr == attr; return text_attr == attr;
@ -461,6 +506,12 @@ impl std::fmt::Display for StyledStrings {
} }
} }
impl From<StyledStrings> for String {
fn from(s: StyledStrings) -> Self {
s.to_string()
}
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
@ -513,10 +564,15 @@ mod tests {
Attribute::Bold, Attribute::Bold,
); );
texts.push_str_with_color_and_attribute( texts.push_str_with_color_and_attribute(
"White and underlined text", "Blue and underlined text\n",
Color::White, Color::Blue,
Attribute::Underline, Attribute::Underline,
); );
texts.push_str_with_color_and_attribute(
"Red and reversed text",
Color::Red,
Attribute::Reversed,
);
println!("{}", texts); println!("{}", texts);
} }
} }

View file

@ -477,12 +477,7 @@ pub trait Locational {
fn ln_begin(&self) -> Option<usize> { fn ln_begin(&self) -> Option<usize> {
match self.loc() { match self.loc() {
Location::RangePair { Location::Range { ln_begin, .. } | Location::LineRange(ln_begin, _) => Some(ln_begin),
ln_first: (ln_begin, _),
..
}
| Location::Range { ln_begin, .. }
| Location::LineRange(ln_begin, _) => Some(ln_begin),
Location::Line(lineno) => Some(lineno), Location::Line(lineno) => Some(lineno),
Location::Unknown => None, Location::Unknown => None,
} }
@ -490,12 +485,7 @@ pub trait Locational {
fn ln_end(&self) -> Option<usize> { fn ln_end(&self) -> Option<usize> {
match self.loc() { match self.loc() {
Location::RangePair { Location::Range { ln_end, .. } | Location::LineRange(_, ln_end) => Some(ln_end),
ln_second: (_, ln_end),
..
}
| Location::Range { ln_end, .. }
| Location::LineRange(_, ln_end) => Some(ln_end),
Location::Line(lineno) => Some(lineno), Location::Line(lineno) => Some(lineno),
Location::Unknown => None, Location::Unknown => None,
} }

View file

@ -241,7 +241,8 @@ impl Context {
match subr { match subr {
ConstSubr::User(_user) => todo!(), ConstSubr::User(_user) => todo!(),
ConstSubr::Builtin(builtin) => builtin.call(args, self).map_err(|mut e| { ConstSubr::Builtin(builtin) => builtin.call(args, self).map_err(|mut e| {
e.0.loc = loc; // TODO: Is it possible to get 0?
e.0.sub_messages.get_mut(0).unwrap().loc = loc;
EvalErrors::from(EvalError::new( EvalErrors::from(EvalError::new(
*e.0, *e.0,
self.cfg.input.clone(), self.cfg.input.clone(),

View file

@ -6,29 +6,39 @@ use crate::context::Context;
use crate::ty::constructors::{and, mono}; use crate::ty::constructors::{and, mono};
use crate::ty::value::{EvalValueResult, GenTypeObj, TypeObj, ValueObj}; use crate::ty::value::{EvalValueResult, GenTypeObj, TypeObj, ValueObj};
use crate::ty::ValueArgs; use crate::ty::ValueArgs;
use erg_common::error::{ErrorCore, ErrorKind, Location}; use erg_common::error::{ErrorCore, ErrorKind, Location, SubMessage};
use erg_common::style::{RED, RESET, YELLOW}; use erg_common::style::{Color, StyledStr, StyledString, THEME};
const ERR: Color = THEME.colors.error;
const WARN: Color = THEME.colors.warning;
const SUP_ERR: StyledStr = StyledStr::new("Super", Some(ERR), None);
const SUP_WARN: StyledStr = StyledStr::new("Super", Some(WARN), None);
const CLASS_ERR: StyledStr = StyledStr::new("Class", Some(ERR), None);
const REQ_ERR: StyledStr = StyledStr::new("Requirement", Some(ERR), None);
const REQ_WARN: StyledStr = StyledStr::new("Requirement", Some(WARN), None);
/// Requirement: Type, Impl := Type -> ClassType /// Requirement: Type, Impl := Type -> ClassType
pub fn class_func(mut args: ValueArgs, ctx: &Context) -> EvalValueResult<ValueObj> { pub fn class_func(mut args: ValueArgs, ctx: &Context) -> EvalValueResult<ValueObj> {
let require = args.remove_left_or_key("Requirement").ok_or_else(|| { let require = args.remove_left_or_key("Requirement").ok_or_else(|| {
ErrorCore::new( ErrorCore::new(
vec![SubMessage::only_loc(Location::Unknown)],
format!("{REQ_ERR} is not passed"),
line!() as usize, line!() as usize,
ErrorKind::TypeError, ErrorKind::TypeError,
Location::Unknown, Location::Unknown,
format!("{RED}Requirement{RESET} is not passed"),
None,
) )
})?; })?;
let Some(require) = require.as_type() else { let Some(require) = require.as_type() else {
let require = StyledString::new(&format!("{}", require), Some(ERR), None);
return Err(ErrorCore::new( return Err(ErrorCore::new(
vec![SubMessage::only_loc(Location::Unknown)],
format!(
"non-type object {require} is passed to {REQ_WARN}",
),
line!() as usize, line!() as usize,
ErrorKind::TypeError, ErrorKind::TypeError,
Location::Unknown, Location::Unknown,
format!(
"non-type object {RED}{require}{RESET} is passed to {YELLOW}Requirement{RESET}",
),
None,
).into()); ).into());
}; };
let impls = args.remove_left_or_key("Impl"); let impls = args.remove_left_or_key("Impl");
@ -40,23 +50,25 @@ pub fn class_func(mut args: ValueArgs, ctx: &Context) -> EvalValueResult<ValueOb
/// Super: ClassType, Impl := Type, Additional := Type -> ClassType /// Super: ClassType, Impl := Type, Additional := Type -> ClassType
pub fn inherit_func(mut args: ValueArgs, ctx: &Context) -> EvalValueResult<ValueObj> { pub fn inherit_func(mut args: ValueArgs, ctx: &Context) -> EvalValueResult<ValueObj> {
let sup = args.remove_left_or_key("Super").ok_or_else(|| { let sup = args.remove_left_or_key("Super").ok_or_else(|| {
let sup = StyledStr::new("Super", Some(ERR), None);
ErrorCore::new( ErrorCore::new(
vec![SubMessage::only_loc(Location::Unknown)],
format!("{sup} is not passed"),
line!() as usize, line!() as usize,
ErrorKind::KeyError, ErrorKind::KeyError,
Location::Unknown, Location::Unknown,
format!("{RED}Super{RESET} is not passed"),
None,
) )
})?; })?;
let Some(sup) = sup.as_type() else { let Some(sup) = sup.as_type() else {
let sup_ty = StyledString::new(&format!("{}", sup), Some(ERR), None);
return Err(ErrorCore::new( return Err(ErrorCore::new(
vec![SubMessage::only_loc(Location::Unknown)],
format!(
"non-class object {sup_ty} is passed to {SUP_WARN}",
),
line!() as usize, line!() as usize,
ErrorKind::TypeError, ErrorKind::TypeError,
Location::Unknown, Location::Unknown,
format!(
"non-class object {RED}{sup}{RESET} is passed to {YELLOW}Super{RESET}",
),
None,
).into()); ).into());
}; };
let impls = args.remove_left_or_key("Impl"); let impls = args.remove_left_or_key("Impl");
@ -74,11 +86,11 @@ pub fn inherit_func(mut args: ValueArgs, ctx: &Context) -> EvalValueResult<Value
pub fn inheritable_func(mut args: ValueArgs, _ctx: &Context) -> EvalValueResult<ValueObj> { pub fn inheritable_func(mut args: ValueArgs, _ctx: &Context) -> EvalValueResult<ValueObj> {
let class = args.remove_left_or_key("Class").ok_or_else(|| { let class = args.remove_left_or_key("Class").ok_or_else(|| {
ErrorCore::new( ErrorCore::new(
vec![SubMessage::only_loc(Location::Unknown)],
format!("{CLASS_ERR} is not passed"),
line!() as usize, line!() as usize,
ErrorKind::KeyError, ErrorKind::KeyError,
Location::Unknown, Location::Unknown,
format!("{RED}Class{RESET} is not passed"),
None,
) )
})?; })?;
match class { match class {
@ -106,22 +118,23 @@ pub fn inheritable_func(mut args: ValueArgs, _ctx: &Context) -> EvalValueResult<
pub fn trait_func(mut args: ValueArgs, ctx: &Context) -> EvalValueResult<ValueObj> { pub fn trait_func(mut args: ValueArgs, ctx: &Context) -> EvalValueResult<ValueObj> {
let require = args.remove_left_or_key("Requirement").ok_or_else(|| { let require = args.remove_left_or_key("Requirement").ok_or_else(|| {
ErrorCore::new( ErrorCore::new(
vec![SubMessage::only_loc(Location::Unknown)],
format!("{REQ_ERR} is not passed"),
line!() as usize, line!() as usize,
ErrorKind::KeyError, ErrorKind::KeyError,
Location::Unknown, Location::Unknown,
format!("{RED}Requirement{RESET} is not passed"),
None,
) )
})?; })?;
let Some(require) = require.as_type() else { let Some(require) = require.as_type() else {
let require = StyledString::new(&format!("{}", require), Some(ERR), None);
return Err(ErrorCore::new( return Err(ErrorCore::new(
vec![SubMessage::only_loc(Location::Unknown)],
format!(
"non-type object {require} is passed to {REQ_WARN}",
),
line!() as usize, line!() as usize,
ErrorKind::TypeError, ErrorKind::TypeError,
Location::Unknown, Location::Unknown,
format!(
"non-type object {RED}{require}{RESET} is passed to {YELLOW}Requirement{RESET}",
),
None,
).into()); ).into());
}; };
let impls = args.remove_left_or_key("Impl"); let impls = args.remove_left_or_key("Impl");
@ -134,22 +147,23 @@ pub fn trait_func(mut args: ValueArgs, ctx: &Context) -> EvalValueResult<ValueOb
pub fn subsume_func(mut args: ValueArgs, ctx: &Context) -> EvalValueResult<ValueObj> { pub fn subsume_func(mut args: ValueArgs, ctx: &Context) -> EvalValueResult<ValueObj> {
let sup = args.remove_left_or_key("Super").ok_or_else(|| { let sup = args.remove_left_or_key("Super").ok_or_else(|| {
ErrorCore::new( ErrorCore::new(
vec![SubMessage::only_loc(Location::Unknown)],
format!("{SUP_ERR} is not passed"),
line!() as usize, line!() as usize,
ErrorKind::KeyError, ErrorKind::KeyError,
Location::Unknown, Location::Unknown,
format!("{RED}Super{RESET} is not passed"),
None,
) )
})?; })?;
let Some(sup) = sup.as_type() else { let Some(sup) = sup.as_type() else {
let sup = StyledString::new(&format!("{}", sup), Some(ERR), None);
return Err(ErrorCore::new( return Err(ErrorCore::new(
vec![SubMessage::only_loc(Location::Unknown)],
format!(
"non-trait object {sup} is passed to {SUP_WARN}",
),
line!() as usize, line!() as usize,
ErrorKind::TypeError, ErrorKind::TypeError,
Location::Unknown, Location::Unknown,
format!(
"non-trait object {RED}{sup}{RESET} is passed to {YELLOW}Super{RESET}",
),
None,
).into()); ).into());
}; };
let impls = args.remove_left_or_key("Impl"); let impls = args.remove_left_or_key("Impl");
@ -171,16 +185,16 @@ pub fn __array_getitem__(mut args: ValueArgs, ctx: &Context) -> EvalValueResult<
Ok(v.clone()) Ok(v.clone())
} else { } else {
Err(ErrorCore::new( Err(ErrorCore::new(
line!() as usize, vec![SubMessage::only_loc(Location::Unknown)],
ErrorKind::IndexError,
Location::Unknown,
format!( format!(
"[{}] has {} elements, but accessed {}th element", "[{}] has {} elements, but accessed {}th element",
erg_common::fmt_vec(&slf), erg_common::fmt_vec(&slf),
slf.len(), slf.len(),
index index
), ),
None, line!() as usize,
ErrorKind::IndexError,
Location::Unknown,
) )
.into()) .into())
} }
@ -210,11 +224,11 @@ pub fn __dict_getitem__(mut args: ValueArgs, ctx: &Context) -> EvalValueResult<V
Ok(v.clone()) Ok(v.clone())
} else { } else {
Err(ErrorCore::new( Err(ErrorCore::new(
vec![SubMessage::only_loc(Location::Unknown)],
format!("{slf} has no key {index}"),
line!() as usize, line!() as usize,
ErrorKind::IndexError, ErrorKind::IndexError,
Location::Unknown, Location::Unknown,
format!("{slf} has no key {index}"),
None,
) )
.into()) .into())
} }
@ -235,11 +249,11 @@ pub fn __range_getitem__(mut args: ValueArgs, _ctx: &Context) -> EvalValueResult
Ok(ValueObj::Nat(start + index)) Ok(ValueObj::Nat(start + index))
} else { } else {
Err(ErrorCore::new( Err(ErrorCore::new(
vec![SubMessage::only_loc(Location::Unknown)],
format!("Index out of range: {}", index),
line!() as usize, line!() as usize,
ErrorKind::IndexError, ErrorKind::IndexError,
Location::Unknown, Location::Unknown,
format!("Index out of range: {index}"),
None,
) )
.into()) .into())
} }

View file

@ -4,7 +4,7 @@ use std::path::{Path, PathBuf};
use erg_common::config::Input; use erg_common::config::Input;
use erg_common::env::erg_pystd_path; use erg_common::env::erg_pystd_path;
use erg_common::error::{ErrorCore, ErrorKind, Location}; use erg_common::error::{ErrorCore, ErrorKind, Location, SubMessage};
use erg_common::levenshtein::get_similar_name; use erg_common::levenshtein::get_similar_name;
use erg_common::set::Set; use erg_common::set::Set;
use erg_common::traits::{Locational, Stream}; use erg_common::traits::{Locational, Stream};
@ -756,13 +756,21 @@ impl Context {
TyCheckErrors::new( TyCheckErrors::new(
errs.into_iter() errs.into_iter()
.map(|e| { .map(|e| {
// HACK: dname.loc()はダミーLocationしか返さないので、エラーならop.loc()で上書きする let mut sub_msges = Vec::new();
for sub_msg in e.core.sub_messages {
sub_msges.push(SubMessage::ambiguous_new(
// HACK: dname.loc()はダミーLocationしか返さないので、エラーならop.loc()で上書きする
bin.loc(),
vec![],
sub_msg.get_hint(),
));
}
let core = ErrorCore::new( let core = ErrorCore::new(
sub_msges,
e.core.main_message,
e.core.errno, e.core.errno,
e.core.kind, e.core.kind,
bin.loc(), e.core.loc,
e.core.desc,
e.core.hint,
); );
TyCheckError::new(core, self.cfg.input.clone(), e.caused_by) TyCheckError::new(core, self.cfg.input.clone(), e.caused_by)
}) })
@ -797,12 +805,20 @@ impl Context {
TyCheckErrors::new( TyCheckErrors::new(
errs.into_iter() errs.into_iter()
.map(|e| { .map(|e| {
let mut sub_msges = Vec::new();
for sub_msg in e.core.sub_messages {
sub_msges.push(SubMessage::ambiguous_new(
unary.loc(),
vec![],
sub_msg.get_hint(),
));
}
let core = ErrorCore::new( let core = ErrorCore::new(
sub_msges,
e.core.main_message,
e.core.errno, e.core.errno,
e.core.kind, e.core.kind,
unary.loc(), e.core.loc,
e.core.desc,
e.core.hint,
); );
TyCheckError::new(core, self.cfg.input.clone(), e.caused_by) TyCheckError::new(core, self.cfg.input.clone(), e.caused_by)
}) })
@ -1052,7 +1068,8 @@ impl Context {
TyCheckError::type_mismatch_error( TyCheckError::type_mismatch_error(
self.cfg.input.clone(), self.cfg.input.clone(),
line!() as usize, line!() as usize,
e.core.loc, // TODO: Is it possible to get 0?
e.core.sub_messages.get(0).unwrap().loc,
e.caused_by, e.caused_by,
&name[..], &name[..],
Some(nth), Some(nth),
@ -1107,7 +1124,8 @@ impl Context {
TyCheckError::type_mismatch_error( TyCheckError::type_mismatch_error(
self.cfg.input.clone(), self.cfg.input.clone(),
line!() as usize, line!() as usize,
e.core.loc, // TODO: Is it possible to get 0?
e.core.sub_messages.get(0).unwrap().loc,
e.caused_by, e.caused_by,
&name[..], &name[..],
Some(nth), Some(nth),
@ -1164,7 +1182,8 @@ impl Context {
TyCheckError::type_mismatch_error( TyCheckError::type_mismatch_error(
self.cfg.input.clone(), self.cfg.input.clone(),
line!() as usize, line!() as usize,
e.core.loc, // TODO: Is it possible to get 0?
e.core.sub_messages.get(0).unwrap().loc,
e.caused_by, e.caused_by,
&name[..], &name[..],
Some(nth), Some(nth),

View file

@ -416,7 +416,8 @@ impl Context {
TyCheckError::return_type_error( TyCheckError::return_type_error(
self.cfg.input.clone(), self.cfg.input.clone(),
line!() as usize, line!() as usize,
e.core.loc, // TODO: is it possible to get 0?
e.core.sub_messages.get(0).unwrap().loc,
e.caused_by, e.caused_by,
readable_name(name.inspect()), readable_name(name.inspect()),
spec_ret_t, spec_ret_t,

File diff suppressed because it is too large Load diff

View file

@ -191,7 +191,7 @@ impl ASTLowerer {
"traditional_chinese" => "如果您不想使用該值請使用discard函數", "traditional_chinese" => "如果您不想使用該值請使用discard函數",
"english" => "if you don't use the value, use discard function", "english" => "if you don't use the value, use discard function",
) )
.into(), .to_owned(),
), ),
)) ))
} else { } else {
@ -266,7 +266,8 @@ impl ASTLowerer {
"simplified_chinese" => "数组元素必须全部是相同类型", "simplified_chinese" => "数组元素必须全部是相同类型",
"traditional_chinese" => "數組元素必須全部是相同類型", "traditional_chinese" => "數組元素必須全部是相同類型",
"english" => "all elements of an array must be of the same type", "english" => "all elements of an array must be of the same type",
), )
.to_owned(),
Some( Some(
switch_lang!( switch_lang!(
"japanese" => "Int or Strなど明示的に型を指定してください", "japanese" => "Int or Strなど明示的に型を指定してください",
@ -274,7 +275,7 @@ impl ASTLowerer {
"traditional_chinese" => "請明確指定類型,例如: Int or Str", "traditional_chinese" => "請明確指定類型,例如: Int or Str",
"english" => "please specify the type explicitly, e.g. Int or Str", "english" => "please specify the type explicitly, e.g. Int or Str",
) )
.into(), .to_owned(),
), ),
))); )));
} }
@ -412,7 +413,8 @@ impl ASTLowerer {
"simplified_chinese" => "集合元素必须全部是相同类型", "simplified_chinese" => "集合元素必须全部是相同类型",
"traditional_chinese" => "集合元素必須全部是相同類型", "traditional_chinese" => "集合元素必須全部是相同類型",
"english" => "all elements of a set must be of the same type", "english" => "all elements of a set must be of the same type",
), )
.to_owned(),
Some( Some(
switch_lang!( switch_lang!(
"japanese" => "Int or Strなど明示的に型を指定してください", "japanese" => "Int or Strなど明示的に型を指定してください",
@ -420,7 +422,7 @@ impl ASTLowerer {
"traditional_chinese" => "明確指定類型,例如: Int or Str", "traditional_chinese" => "明確指定類型,例如: Int or Str",
"english" => "please specify the type explicitly, e.g. Int or Str", "english" => "please specify the type explicitly, e.g. Int or Str",
) )
.into(), .to_owned(),
), ),
))); )));
} }
@ -538,7 +540,8 @@ impl ASTLowerer {
"simplified_chinese" => "Dict的值必须是同一类型", "simplified_chinese" => "Dict的值必须是同一类型",
"traditional_chinese" => "Dict的值必須是同一類型", "traditional_chinese" => "Dict的值必須是同一類型",
"english" => "Values of Dict must be the same type", "english" => "Values of Dict must be the same type",
), )
.to_owned(),
Some( Some(
switch_lang!( switch_lang!(
"japanese" => "Int or Strなど明示的に型を指定してください", "japanese" => "Int or Strなど明示的に型を指定してください",
@ -546,7 +549,7 @@ impl ASTLowerer {
"traditional_chinese" => "明確指定類型,例如: Int or Str", "traditional_chinese" => "明確指定類型,例如: Int or Str",
"english" => "please specify the type explicitly, e.g. Int or Str", "english" => "please specify the type explicitly, e.g. Int or Str",
) )
.into(), .to_owned(),
), ),
))); )));
} }
@ -692,7 +695,7 @@ impl ASTLowerer {
line!() as usize, line!() as usize,
call.args.loc(), call.args.loc(),
self.ctx.caused_by(), self.ctx.caused_by(),
"invalid assert casting type", "invalid assert casting type".to_owned(),
None, None,
))); )));
} }
@ -751,7 +754,7 @@ impl ASTLowerer {
line!() as usize, line!() as usize,
other.loc(), other.loc(),
self.ctx.caused_by(), self.ctx.caused_by(),
"", "".to_owned(),
None, None,
))) )))
} }

View file

@ -5,7 +5,7 @@ use std::string::FromUtf8Error;
use erg_common::cache::CacheSet; use erg_common::cache::CacheSet;
use erg_common::config::{ErgConfig, Input}; use erg_common::config::{ErgConfig, Input};
use erg_common::dict::Dict; use erg_common::dict::Dict;
use erg_common::error::{ErrorCore, ErrorKind, Location}; use erg_common::error::{ErrorCore, ErrorKind, Location, SubMessage};
use erg_common::python_util::PythonVersion; use erg_common::python_util::PythonVersion;
use erg_common::serialize::DataTypePrefix; use erg_common::serialize::DataTypePrefix;
use erg_common::{fn_name, switch_lang}; use erg_common::{fn_name, switch_lang};
@ -39,11 +39,11 @@ impl From<FromUtf8Error> for DeserializeError {
impl From<DeserializeError> for ErrorCore { impl From<DeserializeError> for ErrorCore {
fn from(err: DeserializeError) -> Self { fn from(err: DeserializeError) -> Self {
ErrorCore::new( ErrorCore::new(
vec![SubMessage::only_loc(Location::Unknown)],
err.desc,
err.errno, err.errno,
ErrorKind::ImportError, ErrorKind::ImportError,
Location::Unknown, Location::Unknown,
err.desc,
Option::<String>::None,
) )
} }
} }

View file

@ -4,8 +4,10 @@
use std::fmt; use std::fmt;
use erg_common::config::Input; use erg_common::config::Input;
use erg_common::error::{ErrorCore, ErrorDisplay, ErrorKind::*, Location, MultiErrorDisplay}; use erg_common::error::{
use erg_common::style::{Attribute, Color, StyledStr, StyledString, Theme}; ErrorCore, ErrorDisplay, ErrorKind::*, Location, MultiErrorDisplay, SubMessage,
};
use erg_common::style::{Attribute, Color, StyledStr, StyledString, THEME};
use erg_common::traits::Stream; use erg_common::traits::Stream;
use erg_common::{impl_display_and_error, impl_stream_for_wrapper, switch_lang}; use erg_common::{impl_display_and_error, impl_stream_for_wrapper, switch_lang};
@ -29,62 +31,68 @@ pub struct LexErrors(Vec<LexError>);
impl_stream_for_wrapper!(LexErrors, LexError); impl_stream_for_wrapper!(LexErrors, LexError);
const ERR: Color = THEME.colors.error;
const HINT: Color = THEME.colors.hint;
const ACCENT: Color = THEME.colors.accent;
impl LexError { impl LexError {
pub fn new(core: ErrorCore) -> Self { pub fn new(core: ErrorCore) -> Self {
Self(Box::new(core)) Self(Box::new(core))
} }
pub fn set_hint<S: Into<String>>(&mut self, hint: S) { pub fn set_hint<S: Into<String>>(&mut self, hint: S) {
self.0.hint = Some(hint.into()); if let Some(sub_msg) = self.0.sub_messages.get_mut(0) {
sub_msg.set_hint(hint)
}
} }
pub fn compiler_bug(errno: usize, loc: Location, fn_name: &str, line: u32) -> Self { pub fn compiler_bug(errno: usize, loc: Location, fn_name: &str, line: u32) -> Self {
const URL: StyledStr = StyledStr::new( const URL: StyledStr = StyledStr::new(
"https://github.com/erg-lang/erg", "https://github.com/erg-lang/erg",
Some(Color::White), Some(ACCENT),
Some(Attribute::Underline), Some(Attribute::Underline),
); );
Self::new(ErrorCore::new( Self::new(ErrorCore::new(
errno, vec![SubMessage::only_loc(loc)],
CompilerSystemError,
loc,
switch_lang!( switch_lang!(
"japanese" => format!("これはErg compilerのバグです、開発者に報告して下さい ({URL})\n{fn_name}:{line}より発生"), "japanese" => format!("これはErg compilerのバグです、開発者に報告して下さい ({URL})\n{fn_name}:{line}より発生"),
"simplified_chinese" => format!("这是Erg编译器的一个错误请报告给{URL}\n原因来自: {fn_name}:{line}"), "simplified_chinese" => format!("这是Erg编译器的一个错误请报告给{URL}\n原因来自: {fn_name}:{line}"),
"traditional_chinese" => format!("這是Erg編譯器的一個錯誤請報告給{URL}\n原因來自: {fn_name}:{line}"), "traditional_chinese" => format!("這是Erg編譯器的一個錯誤請報告給{URL}\n原因來自: {fn_name}:{line}"),
"english" => format!("this is a bug of the Erg compiler, please report it to {URL}\ncaused from: {fn_name}:{line}"), "english" => format!("this is a bug of the Erg compiler, please report it to {URL}\ncaused from: {fn_name}:{line}"),
), ),
None, errno,
CompilerSystemError,
loc,
)) ))
} }
pub fn feature_error(errno: usize, loc: Location, name: &str) -> Self { pub fn feature_error(errno: usize, loc: Location, name: &str) -> Self {
Self::new(ErrorCore::new( Self::new(ErrorCore::new(
errno, vec![SubMessage::only_loc(loc)],
FeatureError,
loc,
switch_lang!( switch_lang!(
"japanese" => format!("この機能({name})はまだ正式に提供されていません"), "japanese" => format!("この機能({name})はまだ正式に提供されていません"),
"simplified_chinese" => format!("此功能({name})尚未实现"), "simplified_chinese" => format!("此功能({name})尚未实现"),
"traditional_chinese" => format!("此功能({name})尚未實現"), "traditional_chinese" => format!("此功能({name})尚未實現"),
"english" => format!("this feature({name}) is not implemented yet"), "english" => format!("this feature({name}) is not implemented yet"),
), ),
None, errno,
FeatureError,
loc,
)) ))
} }
pub fn simple_syntax_error(errno: usize, loc: Location) -> Self { pub fn simple_syntax_error(errno: usize, loc: Location) -> Self {
Self::new(ErrorCore::new( Self::new(ErrorCore::new(
errno, vec![SubMessage::only_loc(loc)],
SyntaxError,
loc,
switch_lang!( switch_lang!(
"japanese" => "不正な構文です", "japanese" => "不正な構文です",
"simplified_chinese" => "无效的语法", "simplified_chinese" => "无效的语法",
"traditional_chinese" => "無效的語法", "traditional_chinese" => "無效的語法",
"english" => "invalid syntax", "english" => "invalid syntax",
), ),
None, errno,
SyntaxError,
loc,
)) ))
} }
@ -94,7 +102,13 @@ impl LexError {
desc: S, desc: S,
hint: Option<String>, hint: Option<String>,
) -> Self { ) -> Self {
Self::new(ErrorCore::new(errno, SyntaxError, loc, desc, hint)) Self::new(ErrorCore::new(
vec![SubMessage::ambiguous_new(loc, vec![], hint)],
desc,
errno,
SyntaxError,
loc,
))
} }
pub fn syntax_warning<S: Into<String>>( pub fn syntax_warning<S: Into<String>>(
@ -103,7 +117,13 @@ impl LexError {
desc: S, desc: S,
hint: Option<String>, hint: Option<String>,
) -> Self { ) -> Self {
Self::new(ErrorCore::new(errno, SyntaxWarning, loc, desc, hint)) Self::new(ErrorCore::new(
vec![SubMessage::ambiguous_new(loc, vec![], hint)],
desc,
errno,
SyntaxWarning,
loc,
))
} }
pub fn no_var_error( pub fn no_var_error(
@ -113,6 +133,7 @@ impl LexError {
similar_name: Option<String>, similar_name: Option<String>,
) -> Self { ) -> Self {
let hint = similar_name.map(|n| { let hint = similar_name.map(|n| {
let n = StyledString::new(&n, Some(HINT), Some(Attribute::Bold));
switch_lang!( switch_lang!(
"japanese" => format!("似た名前の変数があります: {n}"), "japanese" => format!("似た名前の変数があります: {n}"),
"simplified_chinese" => format!("存在相同名称变量: {n}"), "simplified_chinese" => format!("存在相同名称变量: {n}"),
@ -120,18 +141,18 @@ impl LexError {
"english" => format!("exists a similar name variable: {n}"), "english" => format!("exists a similar name variable: {n}"),
) )
}); });
let name = StyledString::new(name, Some(Color::Red), Some(Attribute::Underline)); let name = StyledString::new(name, Some(ERR), Some(Attribute::Underline));
Self::new(ErrorCore::new( Self::new(ErrorCore::new(
errno, vec![SubMessage::ambiguous_new(loc, vec![], hint)],
NameError,
loc,
switch_lang!( switch_lang!(
"japanese" => format!("{name}という変数は定義されていません"), "japanese" => format!("{name}という変数は定義されていません"),
"simplified_chinese" => format!("{name}未定义"), "simplified_chinese" => format!("{name}未定义"),
"traditional_chinese" => format!("{name}未定義"), "traditional_chinese" => format!("{name}未定義"),
"english" => format!("{name} is not defined"), "english" => format!("{name} is not defined"),
), ),
hint, errno,
NameError,
loc,
)) ))
} }
} }
@ -164,7 +185,6 @@ pub type DesugaringResult<T> = Result<T, DesugaringError>;
pub struct ParserRunnerError { pub struct ParserRunnerError {
pub core: ErrorCore, pub core: ErrorCore,
pub input: Input, pub input: Input,
pub theme: Theme,
} }
impl_display_and_error!(ParserRunnerError); impl_display_and_error!(ParserRunnerError);
@ -176,9 +196,6 @@ impl ErrorDisplay for ParserRunnerError {
fn input(&self) -> &Input { fn input(&self) -> &Input {
&self.input &self.input
} }
fn theme(&self) -> &Theme {
&self.theme
}
fn caused_by(&self) -> &str { fn caused_by(&self) -> &str {
"" ""
} }
@ -188,8 +205,8 @@ impl ErrorDisplay for ParserRunnerError {
} }
impl ParserRunnerError { impl ParserRunnerError {
pub const fn new(core: ErrorCore, input: Input, theme: Theme) -> Self { pub const fn new(core: ErrorCore, input: Input) -> Self {
Self { core, input, theme } Self { core, input }
} }
} }
@ -209,10 +226,10 @@ impl fmt::Display for ParserRunnerErrors {
} }
impl ParserRunnerErrors { impl ParserRunnerErrors {
pub fn convert(input: &Input, errs: ParseErrors, theme: Theme) -> Self { pub fn convert(input: &Input, errs: ParseErrors) -> Self {
Self( Self(
errs.into_iter() errs.into_iter()
.map(|err| ParserRunnerError::new(*err.0, input.clone(), theme)) .map(|err| ParserRunnerError::new(*err.0, input.clone()))
.collect(), .collect(),
) )
} }

View file

@ -4,7 +4,6 @@ use std::cmp::Ordering;
use erg_common::cache::CacheSet; use erg_common::cache::CacheSet;
use erg_common::config::ErgConfig; use erg_common::config::ErgConfig;
use erg_common::config::Input; use erg_common::config::Input;
use erg_common::style::THEME;
use erg_common::traits::{Locational, Runnable, Stream}; use erg_common::traits::{Locational, Runnable, Stream};
use erg_common::{debug_power_assert, fn_name_full, normalize_newline, switch_lang}; use erg_common::{debug_power_assert, fn_name_full, normalize_newline, switch_lang};
@ -43,7 +42,7 @@ impl Runnable for LexerRunner {
let lexer = Lexer::from_str(self.input().read()); let lexer = Lexer::from_str(self.input().read());
let ts = lexer let ts = lexer
.lex() .lex()
.map_err(|errs| LexerRunnerErrors::convert(self.input(), errs, THEME))?; .map_err(|errs| LexerRunnerErrors::convert(self.input(), errs))?;
println!("{ts}"); println!("{ts}");
Ok(0) Ok(0)
} }
@ -53,13 +52,13 @@ impl Runnable for LexerRunner {
if cfg!(feature = "debug") { if cfg!(feature = "debug") {
let ts = lexer let ts = lexer
.lex() .lex()
.map_err(|errs| LexerRunnerErrors::convert(self.input(), errs, THEME))?; .map_err(|errs| LexerRunnerErrors::convert(self.input(), errs))?;
println!("{ts}"); println!("{ts}");
Ok(ts.to_string()) Ok(ts.to_string())
} else { } else {
Ok(lexer Ok(lexer
.lex() .lex()
.map_err(|errs| LexerRunnerErrors::convert(self.input(), errs, THEME))? .map_err(|errs| LexerRunnerErrors::convert(self.input(), errs))?
.to_string()) .to_string())
} }
} }

View file

@ -11,7 +11,6 @@ use erg_common::error::Location;
use erg_common::option_enum_unwrap; use erg_common::option_enum_unwrap;
use erg_common::set::Set as HashSet; use erg_common::set::Set as HashSet;
use erg_common::str::Str; use erg_common::str::Str;
use erg_common::style::THEME;
use erg_common::traits::Runnable; use erg_common::traits::Runnable;
use erg_common::traits::{Locational, Stream}; use erg_common::traits::{Locational, Stream};
use erg_common::{ use erg_common::{
@ -207,16 +206,16 @@ impl ParserRunner {
pub fn parse_token_stream(&mut self, ts: TokenStream) -> Result<Module, ParserRunnerErrors> { pub fn parse_token_stream(&mut self, ts: TokenStream) -> Result<Module, ParserRunnerErrors> {
Parser::new(ts) Parser::new(ts)
.parse() .parse()
.map_err(|errs| ParserRunnerErrors::convert(self.input(), errs, THEME)) .map_err(|errs| ParserRunnerErrors::convert(self.input(), errs))
} }
pub fn parse(&mut self, src: String) -> Result<Module, ParserRunnerErrors> { pub fn parse(&mut self, src: String) -> Result<Module, ParserRunnerErrors> {
let ts = Lexer::new(Input::Str(src)) let ts = Lexer::new(Input::Str(src))
.lex() .lex()
.map_err(|errs| ParserRunnerErrors::convert(self.input(), errs, THEME))?; .map_err(|errs| ParserRunnerErrors::convert(self.input(), errs))?;
Parser::new(ts) Parser::new(ts)
.parse() .parse()
.map_err(|errs| ParserRunnerErrors::convert(self.input(), errs, THEME)) .map_err(|errs| ParserRunnerErrors::convert(self.input(), errs))
} }
} }

View file

@ -1,6 +1,5 @@
use erg_common::config::{ErgConfig, Input}; use erg_common::config::{ErgConfig, Input};
use erg_common::error::MultiErrorDisplay; use erg_common::error::MultiErrorDisplay;
use erg_common::style::THEME;
use erg_common::traits::Runnable; use erg_common::traits::Runnable;
use erg_parser::error::ParserRunnerErrors; use erg_parser::error::ParserRunnerErrors;
@ -64,7 +63,7 @@ fn parse_test_from_code(file_path: &'static str) -> Result<(), ParserRunnerError
match parser.parse_token_stream( match parser.parse_token_stream(
lexer lexer
.lex() .lex()
.map_err(|errs| ParserRunnerErrors::convert(&input, errs, THEME))?, .map_err(|errs| ParserRunnerErrors::convert(&input, errs))?,
) { ) {
Ok(module) => { Ok(module) => {
println!("{module}"); println!("{module}");