mirror of
https://github.com/Dummi26/mers.git
synced 2025-03-10 14:13:52 +01:00
improve error messages
- fix bug where `thread` function wouldn't error when being called on a non-function - add compile error when a function in `try` is never used, because this is almost certainly a mistake and will cause mers to unexpectedly go down the "wrong" code path. - some small changes so mers_lib can be used in another project
This commit is contained in:
parent
4cf349a680
commit
49d13d92cc
@ -1,9 +1,10 @@
|
|||||||
use std::{
|
use std::{
|
||||||
fmt::{Debug, Display},
|
fmt::{Debug, Display},
|
||||||
sync::Arc,
|
rc::Rc,
|
||||||
|
sync::{atomic::AtomicUsize, Arc},
|
||||||
};
|
};
|
||||||
|
|
||||||
use colored::Colorize;
|
use colored::{ColoredString, Colorize};
|
||||||
use line_span::LineSpans;
|
use line_span::LineSpans;
|
||||||
|
|
||||||
#[cfg(feature = "parse")]
|
#[cfg(feature = "parse")]
|
||||||
@ -89,6 +90,7 @@ pub enum CheckErrorComponent {
|
|||||||
}
|
}
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct CheckErrorHRConfig {
|
pub struct CheckErrorHRConfig {
|
||||||
|
color_index: Rc<AtomicUsize>,
|
||||||
indent_start: String,
|
indent_start: String,
|
||||||
indent_default: String,
|
indent_default: String,
|
||||||
indent_end: String,
|
indent_end: String,
|
||||||
@ -113,6 +115,7 @@ impl Display for CheckErrorDisplay<'_> {
|
|||||||
self.e.human_readable(
|
self.e.human_readable(
|
||||||
f,
|
f,
|
||||||
&CheckErrorHRConfig {
|
&CheckErrorHRConfig {
|
||||||
|
color_index: Rc::new(AtomicUsize::new(0)),
|
||||||
indent_start: String::new(),
|
indent_start: String::new(),
|
||||||
indent_default: String::new(),
|
indent_default: String::new(),
|
||||||
indent_end: String::new(),
|
indent_end: String::new(),
|
||||||
@ -160,10 +163,10 @@ impl CheckError {
|
|||||||
let len = self.0.len();
|
let len = self.0.len();
|
||||||
for (i, component) in self.0.iter().enumerate() {
|
for (i, component) in self.0.iter().enumerate() {
|
||||||
macro_rules! indent {
|
macro_rules! indent {
|
||||||
() => {
|
($s:expr, $e:expr) => {
|
||||||
if i + 1 == len {
|
if $e && i + 1 == len {
|
||||||
&cfg.indent_end
|
&cfg.indent_end
|
||||||
} else if i == 0 {
|
} else if $s && i == 0 {
|
||||||
&cfg.indent_start
|
&cfg.indent_start
|
||||||
} else {
|
} else {
|
||||||
&cfg.indent_default
|
&cfg.indent_default
|
||||||
@ -171,20 +174,27 @@ impl CheckError {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
match component {
|
match component {
|
||||||
CheckErrorComponent::Message(msg) => writeln!(f, "{}{msg}", indent!())?,
|
CheckErrorComponent::Message(msg) => {
|
||||||
|
let lines = msg.lines().collect::<Vec<_>>();
|
||||||
|
let lc = lines.len();
|
||||||
|
for (i, line) in lines.into_iter().enumerate() {
|
||||||
|
writeln!(f, "{}{line}", indent!(i == 0, i + 1 == lc))?
|
||||||
|
}
|
||||||
|
}
|
||||||
CheckErrorComponent::Error(err) => {
|
CheckErrorComponent::Error(err) => {
|
||||||
|
let clr = Self::get_color(&cfg.color_index);
|
||||||
let mut cfg = cfg.clone();
|
let mut cfg = cfg.clone();
|
||||||
cfg.indent_start.push_str("│");
|
cfg.indent_start.push_str(&clr("│").to_string());
|
||||||
cfg.indent_default.push_str("│");
|
cfg.indent_default.push_str(&clr("│").to_string());
|
||||||
cfg.indent_end.push_str("└");
|
cfg.indent_end.push_str(&clr("└").to_string());
|
||||||
err.human_readable(f, &cfg)?;
|
err.human_readable(f, &cfg)?;
|
||||||
}
|
}
|
||||||
CheckErrorComponent::ErrorWithDifferentSource(err) => {
|
CheckErrorComponent::ErrorWithDifferentSource(err) => {
|
||||||
|
let clr = Self::get_color(&cfg.color_index);
|
||||||
let mut cfg = cfg.clone();
|
let mut cfg = cfg.clone();
|
||||||
cfg.indent_start.push_str(&"│".bright_yellow().to_string());
|
cfg.indent_start.push_str(&clr("┃").bold().to_string());
|
||||||
cfg.indent_default
|
cfg.indent_default.push_str(&clr("┃").bold().to_string());
|
||||||
.push_str(&"│".bright_yellow().to_string());
|
cfg.indent_end.push_str(&clr("┗").bold().to_string());
|
||||||
cfg.indent_end.push_str(&"└".bright_yellow().to_string());
|
|
||||||
err.human_readable(f, &cfg)?;
|
err.human_readable(f, &cfg)?;
|
||||||
}
|
}
|
||||||
CheckErrorComponent::Source(highlights) => {
|
CheckErrorComponent::Source(highlights) => {
|
||||||
@ -243,7 +253,7 @@ impl CheckError {
|
|||||||
writeln!(
|
writeln!(
|
||||||
f,
|
f,
|
||||||
"{}Line {first_line_nr} ({}..{}){}",
|
"{}Line {first_line_nr} ({}..{}){}",
|
||||||
indent!(),
|
indent!(true, false),
|
||||||
start_with_comments + 1 - first_line_start,
|
start_with_comments + 1 - first_line_start,
|
||||||
end_with_comments - last_line_start,
|
end_with_comments - last_line_start,
|
||||||
src_from,
|
src_from,
|
||||||
@ -252,7 +262,7 @@ impl CheckError {
|
|||||||
writeln!(
|
writeln!(
|
||||||
f,
|
f,
|
||||||
"{}Lines {first_line_nr}-{last_line_nr} ({}..{}){}",
|
"{}Lines {first_line_nr}-{last_line_nr} ({}..{}){}",
|
||||||
indent!(),
|
indent!(true, false),
|
||||||
start_with_comments + 1 - first_line_start,
|
start_with_comments + 1 - first_line_start,
|
||||||
end_with_comments - last_line_start,
|
end_with_comments - last_line_start,
|
||||||
src_from,
|
src_from,
|
||||||
@ -263,11 +273,13 @@ impl CheckError {
|
|||||||
} else {
|
} else {
|
||||||
src.src()[start..end].line_spans().collect::<Vec<_>>()
|
src.src()[start..end].line_spans().collect::<Vec<_>>()
|
||||||
};
|
};
|
||||||
for line in lines {
|
let lines_count = lines.len();
|
||||||
|
for (line_nr_rel, line) in lines.into_iter().enumerate() {
|
||||||
|
let last_line = line_nr_rel + 1 == lines_count;
|
||||||
let line_start = line.start();
|
let line_start = line.start();
|
||||||
let line_end = line.end();
|
let line_end = line.end();
|
||||||
let line = line.as_str();
|
let line = line.as_str();
|
||||||
writeln!(f, "{} {line}", indent!())?;
|
let mut line_printed = false;
|
||||||
let mut right = 0;
|
let mut right = 0;
|
||||||
for (pos, color) in highlights {
|
for (pos, color) in highlights {
|
||||||
if let Some(color) = color {
|
if let Some(color) = color {
|
||||||
@ -284,6 +296,11 @@ impl CheckError {
|
|||||||
let highlight_end = highlight_end - start;
|
let highlight_end = highlight_end - start;
|
||||||
if highlight_start < line_end && highlight_end > line_start
|
if highlight_start < line_end && highlight_end > line_start
|
||||||
{
|
{
|
||||||
|
if !line_printed {
|
||||||
|
// this isn't the last line (important for indent)
|
||||||
|
writeln!(f, "{} {line}", indent!(false, false))?;
|
||||||
|
line_printed = true;
|
||||||
|
}
|
||||||
// where the highlight starts in this line
|
// where the highlight starts in this line
|
||||||
let hl_start =
|
let hl_start =
|
||||||
highlight_start.saturating_sub(line_start);
|
highlight_start.saturating_sub(line_start);
|
||||||
@ -301,7 +318,7 @@ impl CheckError {
|
|||||||
let hl_len = hl_len.min(line.len() - right);
|
let hl_len = hl_len.min(line.len() - right);
|
||||||
right += hl_space + hl_len;
|
right += hl_space + hl_len;
|
||||||
if print_indent && right != 0 {
|
if print_indent && right != 0 {
|
||||||
write!(f, "{} ", indent!())?;
|
write!(f, "{} ", indent!(false, false))?;
|
||||||
}
|
}
|
||||||
write!(
|
write!(
|
||||||
f,
|
f,
|
||||||
@ -312,6 +329,10 @@ impl CheckError {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if !line_printed {
|
||||||
|
// may be last line (important for indent)
|
||||||
|
writeln!(f, "{} {line}", indent!(false, last_line))?;
|
||||||
|
}
|
||||||
if right != 0 {
|
if right != 0 {
|
||||||
writeln!(f)?;
|
writeln!(f)?;
|
||||||
}
|
}
|
||||||
@ -323,6 +344,19 @@ impl CheckError {
|
|||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
fn get_color(i: &AtomicUsize) -> impl Fn(&str) -> ColoredString {
|
||||||
|
let i = i.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
|
||||||
|
move |s| match i % 8 {
|
||||||
|
0 => s.bright_white(),
|
||||||
|
1 => s.bright_green(),
|
||||||
|
2 => s.bright_purple(),
|
||||||
|
3 => s.bright_cyan(),
|
||||||
|
4 => s.bright_red(),
|
||||||
|
5 => s.bright_yellow(),
|
||||||
|
6 => s.bright_magenta(),
|
||||||
|
_ => s.bright_blue(),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
impl From<String> for CheckError {
|
impl From<String> for CheckError {
|
||||||
fn from(value: String) -> Self {
|
fn from(value: String) -> Self {
|
||||||
|
@ -84,11 +84,19 @@ impl Config {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_var(mut self, name: String, val: Data) -> Self {
|
pub fn add_var(self, name: String, val: Data) -> Self {
|
||||||
let t = val.get().as_type();
|
let t = val.get().as_type();
|
||||||
|
self.add_var_arc(name, Arc::new(RwLock::new(val)), t)
|
||||||
|
}
|
||||||
|
pub fn add_var_arc(
|
||||||
|
mut self,
|
||||||
|
name: String,
|
||||||
|
val: Arc<RwLock<Data>>,
|
||||||
|
val_type: crate::data::Type,
|
||||||
|
) -> Self {
|
||||||
self.info_parsed.scopes[0].init_var(name, (0, self.globals));
|
self.info_parsed.scopes[0].init_var(name, (0, self.globals));
|
||||||
self.info_run.scopes[0].init_var(self.globals, Arc::new(RwLock::new(val)));
|
self.info_run.scopes[0].init_var(self.globals, val);
|
||||||
self.info_check.scopes[0].init_var(self.globals, t);
|
self.info_check.scopes[0].init_var(self.globals, val_type);
|
||||||
self.globals += 1;
|
self.globals += 1;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
@ -28,39 +28,56 @@ impl Config {
|
|||||||
out: Arc::new(|a, _i| {
|
out: Arc::new(|a, _i| {
|
||||||
let mut out = Type::empty();
|
let mut out = Type::empty();
|
||||||
for t in a.types.iter() {
|
for t in a.types.iter() {
|
||||||
if let Some(t) = t.as_any().downcast_ref::<data::tuple::TupleT>() {
|
if let Some(outer_tuple) = t.as_any().downcast_ref::<data::tuple::TupleT>() {
|
||||||
if t.0.len() != 2 {
|
if outer_tuple.0.len() != 2 {
|
||||||
return Err(format!("cannot use try with tuple argument where len != 2 (got len {})", t.0.len()).into());
|
return Err(format!("cannot use try with tuple argument where len != 2 (got len {})", outer_tuple.0.len()).into());
|
||||||
}
|
}
|
||||||
let arg_type = &t.0[0];
|
let arg_type = &outer_tuple.0[0];
|
||||||
let functions = &t.0[1];
|
let functions = &outer_tuple.0[1];
|
||||||
|
let mut used_functions_and_errors = vec![];
|
||||||
for arg_type in arg_type.subtypes_type().types.iter() {
|
for arg_type in arg_type.subtypes_type().types.iter() {
|
||||||
let arg_type = Type::newm(vec![arg_type.clone()]);
|
let arg_type = Type::newm(vec![arg_type.clone()]);
|
||||||
// possibilities for the tuple (f1, f2, f3, ..., fn)
|
// possibilities for the tuple (f1, f2, f3, ..., fn)
|
||||||
for ft in functions.types.iter() {
|
for (fti, ft) in functions.types.iter().enumerate() {
|
||||||
|
if used_functions_and_errors.len() <= fti {
|
||||||
|
used_functions_and_errors.push(vec![]);
|
||||||
|
}
|
||||||
let mut tuple_fallible = true;
|
let mut tuple_fallible = true;
|
||||||
let mut tuple_possible = false;
|
let mut tuple_possible = false;
|
||||||
if let Some(ft) = ft.as_any().downcast_ref::<data::tuple::TupleT>() {
|
if let Some(ft) = ft.as_any().downcast_ref::<data::tuple::TupleT>() {
|
||||||
// f1, f2, f3, ..., fn
|
// f1, f2, f3, ..., fn
|
||||||
let mut func_errors = vec![];
|
let mut func_errors = vec![];
|
||||||
for ft in ft.0.iter() {
|
let mut skip_checks = false;
|
||||||
|
for (fi, ft) in ft.0.iter().enumerate() {
|
||||||
|
if used_functions_and_errors[fti].len() <= fi {
|
||||||
|
used_functions_and_errors[fti].push(vec![]);
|
||||||
|
}
|
||||||
let mut func_fallible = false;
|
let mut func_fallible = false;
|
||||||
// possibilities for f_
|
// possibilities for f_
|
||||||
for ft in ft.types.iter() {
|
for (fvi, ft) in ft.types.iter().enumerate() {
|
||||||
if let Some(ft) = ft.as_any().downcast_ref::<data::function::FunctionT>() {
|
if let Some(ft) = ft.as_any().downcast_ref::<data::function::FunctionT>() {
|
||||||
func_errors.push(match ft.0(&arg_type) {
|
if used_functions_and_errors[fti][fi].len() <= fvi {
|
||||||
Err(e) => {
|
used_functions_and_errors[fti][fi].push(Some(vec![]));
|
||||||
func_fallible = true;
|
}
|
||||||
Some(e)
|
if !skip_checks {
|
||||||
}
|
func_errors.push((fvi, match ft.0(&arg_type) {
|
||||||
Ok(o) => {
|
Err(e) => {
|
||||||
tuple_possible = true;
|
func_fallible = true;
|
||||||
for t in o.types {
|
if let Some(errs) = &mut used_functions_and_errors[fti][fi][fvi] {
|
||||||
out.add(t);
|
errs.push(e.clone());
|
||||||
|
}
|
||||||
|
Some(e)
|
||||||
}
|
}
|
||||||
None
|
Ok(o) => {
|
||||||
},
|
used_functions_and_errors[fti][fi][fvi] = None;
|
||||||
});
|
tuple_possible = true;
|
||||||
|
for t in o.types {
|
||||||
|
out.add(t);
|
||||||
|
}
|
||||||
|
None
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
return Err(format!("try: arguments f1-fn must be functions").into());
|
return Err(format!("try: arguments f1-fn must be functions").into());
|
||||||
}
|
}
|
||||||
@ -69,7 +86,7 @@ impl Config {
|
|||||||
if !func_fallible {
|
if !func_fallible {
|
||||||
tuple_fallible = false;
|
tuple_fallible = false;
|
||||||
if tuple_possible {
|
if tuple_possible {
|
||||||
break;
|
skip_checks = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -77,8 +94,8 @@ impl Config {
|
|||||||
// if the argument is {arg_type}, there is no infallible function. add a fallback function to handle this case!
|
// if the argument is {arg_type}, there is no infallible function. add a fallback function to handle this case!
|
||||||
let mut e = CheckError::new()
|
let mut e = CheckError::new()
|
||||||
.msg(format!("if the argument is {arg_type}, there is no infallible function."))
|
.msg(format!("if the argument is {arg_type}, there is no infallible function."))
|
||||||
.msg(format!("Add a fallback function to handle this case!"));
|
.msg(format!("Add a function to handle this case!"));
|
||||||
for (i, err) in func_errors.into_iter().enumerate() {
|
for (i, err) in func_errors.into_iter() {
|
||||||
if let Some(err) = err {
|
if let Some(err) = err {
|
||||||
e = e
|
e = e
|
||||||
.msg(format!("Error for function #{}:", i + 1))
|
.msg(format!("Error for function #{}:", i + 1))
|
||||||
@ -92,6 +109,26 @@ impl Config {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
for (functions_posibility_index, functions_possibility) in used_functions_and_errors.into_iter().enumerate() {
|
||||||
|
for (func_index, func_possibilities) in functions_possibility.into_iter().enumerate() {
|
||||||
|
for (func_possibility_index, errors_if_unused) in func_possibilities.into_iter().enumerate() {
|
||||||
|
if let Some(errs) = errors_if_unused {
|
||||||
|
let mut e = CheckError::new().msg(format!("try: For the argument {t}:\nFunction #{}{} is never used.{}",
|
||||||
|
func_index + 1,
|
||||||
|
if functions_posibility_index != 0 || func_possibility_index != 0 {
|
||||||
|
format!(" (func-tuple possibility {}, function possibility {})", functions_posibility_index + 1, func_possibility_index + 1)
|
||||||
|
} else {
|
||||||
|
format!("")
|
||||||
|
},
|
||||||
|
if errs.is_empty() { "" } else { " Errors:" }));
|
||||||
|
for err in errs {
|
||||||
|
e = e.err(err);
|
||||||
|
}
|
||||||
|
return Err(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
return Err(format!("cannot use try with non-tuple argument").into());
|
return Err(format!("cannot use try with non-tuple argument").into());
|
||||||
}
|
}
|
||||||
|
@ -40,6 +40,8 @@ impl Config {
|
|||||||
Ok(t) => out.add(Arc::new(t)),
|
Ok(t) => out.add(Arc::new(t)),
|
||||||
Err(e) => return Err(CheckError::new().msg(format!("Can't call thread on a function which can't be called on an empty tuple: ")).err(e))
|
Err(e) => return Err(CheckError::new().msg(format!("Can't call thread on a function which can't be called on an empty tuple: ")).err(e))
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
return Err(format!("thread: argument wasn't a function").into());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(Type::new(ThreadT(out)))
|
Ok(Type::new(ThreadT(out)))
|
||||||
|
Loading…
Reference in New Issue
Block a user