FEATURE: Allow BuildErrors to wrap other errors.

This commit is contained in:
Jeremy Wall 2019-02-20 20:28:05 -06:00
parent d2bd18b647
commit 014710a4ba
5 changed files with 95 additions and 85 deletions

View File

@ -143,13 +143,7 @@ fn test_type_checks() {
} }
#[test] #[test]
#[should_panic(expected = "UserDefined")] #[should_panic(expected = "UserDefined: I am a failure!")]
fn test_declarative_failures_are_user_defined() {
assert_build("fail \"I am a failure!\";");
}
#[test]
#[should_panic(expected = "Caused By:\n\tI am a failure!")]
fn test_declarative_failures_are_caused_by_msg() { fn test_declarative_failures_are_caused_by_msg() {
assert_build("fail \"I am a failure!\";"); assert_build("fail \"I am a failure!\";");
} }

View File

@ -54,7 +54,7 @@ impl<V: Into<String> + Clone> FormatRenderer for SimpleFormatter<V> {
for c in self.tmpl.chars() { for c in self.tmpl.chars() {
if c == '@' && !should_escape { if c == '@' && !should_escape {
if count == self.args.len() { if count == self.args.len() {
return Err(Box::new(error::BuildError::new_with_pos( return Err(Box::new(error::BuildError::with_pos(
"Too few arguments to string \ "Too few arguments to string \
formatter.", formatter.",
error::ErrorType::FormatError, error::ErrorType::FormatError,
@ -72,7 +72,7 @@ impl<V: Into<String> + Clone> FormatRenderer for SimpleFormatter<V> {
} }
} }
if self.args.len() != count { if self.args.len() != count {
return Err(Box::new(error::BuildError::new_with_pos( return Err(Box::new(error::BuildError::with_pos(
"Too many arguments to string \ "Too many arguments to string \
formatter.", formatter.",
error::ErrorType::FormatError, error::ErrorType::FormatError,
@ -111,7 +111,7 @@ impl<'a> ExpressionFormatter<'a> {
if c == '{' { if c == '{' {
brace_count += 1; brace_count += 1;
} else { } else {
return Err(Box::new(error::BuildError::new_with_pos( return Err(Box::new(error::BuildError::with_pos(
format!( format!(
"Invalid syntax for format string expected '{{' but got {}", "Invalid syntax for format string expected '{{' but got {}",
c c
@ -122,7 +122,7 @@ impl<'a> ExpressionFormatter<'a> {
} }
} }
None => { None => {
return Err(Box::new(error::BuildError::new_with_pos( return Err(Box::new(error::BuildError::with_pos(
"Invalid syntax for format string expected '{' but string ended", "Invalid syntax for format string expected '{' but string ended",
error::ErrorType::FormatError, error::ErrorType::FormatError,
pos.clone(), pos.clone(),
@ -148,7 +148,7 @@ impl<'a> ExpressionFormatter<'a> {
} }
// empty expressions are an error // empty expressions are an error
if expr_string.is_empty() { if expr_string.is_empty() {
return Err(Box::new(error::BuildError::new_with_pos( return Err(Box::new(error::BuildError::with_pos(
"Got an empty expression in format string", "Got an empty expression in format string",
error::ErrorType::FormatError, error::ErrorType::FormatError,
pos.clone(), pos.clone(),
@ -163,7 +163,7 @@ impl<'a> ExpressionFormatter<'a> {
expr_string.push(c); expr_string.push(c);
} }
} }
return Err(Box::new(error::BuildError::new_with_pos( return Err(Box::new(error::BuildError::with_pos(
"Expected '}' but got end of string", "Expected '}' but got end of string",
error::ErrorType::FormatError, error::ErrorType::FormatError,
pos.clone(), pos.clone(),

View File

@ -61,7 +61,7 @@ impl FuncDef {
// Error conditions. If the args don't match the length and types of the argdefs then this is // Error conditions. If the args don't match the length and types of the argdefs then this is
// func call error. // func call error.
if args.len() > self.argdefs.len() { if args.len() > self.argdefs.len() {
return Err(Box::new(error::BuildError::new_with_pos( return Err(Box::new(error::BuildError::with_pos(
"Func called with too many args", "Func called with too many args",
error::ErrorType::BadArgLen, error::ErrorType::BadArgLen,
self.pos.clone(), self.pos.clone(),
@ -129,7 +129,7 @@ macro_rules! eval_binary_expr {
return Ok(Rc::new($result)); return Ok(Rc::new($result));
} }
val => { val => {
return Err(Box::new(error::BuildError::new_with_pos( return Err(Box::new(error::BuildError::with_pos(
format!("Expected {} but got ({})", $msg, val), format!("Expected {} but got ({})", $msg, val),
error::ErrorType::TypeFail, error::ErrorType::TypeFail,
$pos.clone(), $pos.clone(),
@ -277,13 +277,15 @@ impl<'a> FileBuilder<'a> {
&Value::Int(ref i) => Ok(Rc::new(Val::Int(i.val))), &Value::Int(ref i) => Ok(Rc::new(Val::Int(i.val))),
&Value::Float(ref f) => Ok(Rc::new(Val::Float(f.val))), &Value::Float(ref f) => Ok(Rc::new(Val::Float(f.val))),
&Value::Str(ref s) => Ok(Rc::new(Val::Str(s.val.to_string()))), &Value::Str(ref s) => Ok(Rc::new(Val::Str(s.val.to_string()))),
&Value::Symbol(ref s) => scope.lookup_sym(&(s.into()), true).ok_or(Box::new( &Value::Symbol(ref s) => {
error::BuildError::new_with_pos( scope
format!("Unable to find binding {}", s.val,), .lookup_sym(&(s.into()), true)
error::ErrorType::NoSuchSymbol, .ok_or(Box::new(error::BuildError::with_pos(
v.pos().clone(), format!("Unable to find binding {}", s.val,),
), error::ErrorType::NoSuchSymbol,
)), v.pos().clone(),
)))
}
&Value::List(ref def) => self.eval_list(def, scope), &Value::List(ref def) => self.eval_list(def, scope),
&Value::Tuple(ref tuple) => self.eval_tuple(&tuple.val, scope), &Value::Tuple(ref tuple) => self.eval_tuple(&tuple.val, scope),
} }
@ -327,7 +329,7 @@ impl<'a> FileBuilder<'a> {
Some(val) => Ok(val), Some(val) => Ok(val),
} }
} }
Err(err) => Err(Box::new(error::BuildError::new_with_pos( Err(err) => Err(Box::new(error::BuildError::with_pos(
format!("{}", err,), format!("{}", err,),
error::ErrorType::ParseError, error::ErrorType::ParseError,
(&input).into(), (&input).into(),
@ -408,7 +410,7 @@ impl<'a> FileBuilder<'a> {
mut_assets_cache.stash(path, result.clone())?; mut_assets_cache.stash(path, result.clone())?;
return Ok(result); return Ok(result);
} else { } else {
return Err(Box::new(error::BuildError::new_with_pos( return Err(Box::new(error::BuildError::with_pos(
format!("No such import {} in the std library.", def.path.fragment), format!("No such import {} in the std library.", def.path.fragment),
error::ErrorType::Unsupported, error::ErrorType::Unsupported,
def.pos.clone(), def.pos.clone(),
@ -418,7 +420,7 @@ impl<'a> FileBuilder<'a> {
// Try a relative path first. // Try a relative path first.
let normalized = self.find_file(&def.path.fragment, true)?; let normalized = self.find_file(&def.path.fragment, true)?;
if self.detect_import_cycle(normalized.to_string_lossy().as_ref()) { if self.detect_import_cycle(normalized.to_string_lossy().as_ref()) {
return Err(Box::new(error::BuildError::new_with_pos( return Err(Box::new(error::BuildError::with_pos(
format!( format!(
"Import Cycle Detected!!!! {} is already in import stack: {:?}", "Import Cycle Detected!!!! {} is already in import stack: {:?}",
normalized.to_string_lossy(), normalized.to_string_lossy(),
@ -450,7 +452,7 @@ impl<'a> FileBuilder<'a> {
let val = self.eval_expr(&def.value, &child_scope)?; let val = self.eval_expr(&def.value, &child_scope)?;
let name = &def.name; let name = &def.name;
if Self::check_reserved_word(&name.fragment) { if Self::check_reserved_word(&name.fragment) {
return Err(Box::new(error::BuildError::new_with_pos( return Err(Box::new(error::BuildError::with_pos(
format!("Let {} binding collides with reserved word", name.fragment), format!("Let {} binding collides with reserved word", name.fragment),
error::ErrorType::ReservedWordError, error::ErrorType::ReservedWordError,
name.pos.clone(), name.pos.clone(),
@ -458,7 +460,7 @@ impl<'a> FileBuilder<'a> {
} }
match self.scope.build_output.entry(name.into()) { match self.scope.build_output.entry(name.into()) {
Entry::Occupied(e) => { Entry::Occupied(e) => {
return Err(Box::new(error::BuildError::new_with_pos( return Err(Box::new(error::BuildError::with_pos(
format!( format!(
"Binding \ "Binding \
for {:?} already \ for {:?} already \
@ -490,7 +492,7 @@ impl<'a> FileBuilder<'a> {
self.out_lock = Some((typ.fragment.to_string(), val.clone())); self.out_lock = Some((typ.fragment.to_string(), val.clone()));
Ok(val) Ok(val)
} else { } else {
Err(Box::new(error::BuildError::new_with_pos( Err(Box::new(error::BuildError::with_pos(
format!("You can only have one output per file."), format!("You can only have one output per file."),
error::ErrorType::Unsupported, error::ErrorType::Unsupported,
pos.clone(), pos.clone(),
@ -519,7 +521,7 @@ impl<'a> FileBuilder<'a> {
return Ok(Rc::new(Val::Str([s.to_string(), ss.clone()].concat()))); return Ok(Rc::new(Val::Str([s.to_string(), ss.clone()].concat())));
} }
val => { val => {
return Err(Box::new(error::BuildError::new_with_pos( return Err(Box::new(error::BuildError::with_pos(
format!( format!(
"Expected \ "Expected \
String \ String \
@ -540,7 +542,7 @@ impl<'a> FileBuilder<'a> {
return Ok(Rc::new(Val::List(new_vec))); return Ok(Rc::new(Val::List(new_vec)));
} }
val => { val => {
return Err(Box::new(error::BuildError::new_with_pos( return Err(Box::new(error::BuildError::with_pos(
format!( format!(
"Expected \ "Expected \
List \ List \
@ -554,7 +556,7 @@ impl<'a> FileBuilder<'a> {
} }
}, },
ref expr => { ref expr => {
return Err(Box::new(error::BuildError::new_with_pos( return Err(Box::new(error::BuildError::with_pos(
format!("{} does not support the '+' operation", expr.type_name()), format!("{} does not support the '+' operation", expr.type_name()),
error::ErrorType::Unsupported, error::ErrorType::Unsupported,
lpos.clone(), lpos.clone(),
@ -578,7 +580,7 @@ impl<'a> FileBuilder<'a> {
eval_binary_expr!(&Val::Float(ff), rpos, right, Val::Float(f - ff), "Float") eval_binary_expr!(&Val::Float(ff), rpos, right, Val::Float(f - ff), "Float")
} }
ref expr => { ref expr => {
return Err(Box::new(error::BuildError::new_with_pos( return Err(Box::new(error::BuildError::with_pos(
format!("{} does not support the '-' operation", expr.type_name()), format!("{} does not support the '-' operation", expr.type_name()),
error::ErrorType::Unsupported, error::ErrorType::Unsupported,
lpos.clone(), lpos.clone(),
@ -602,7 +604,7 @@ impl<'a> FileBuilder<'a> {
eval_binary_expr!(&Val::Float(ff), rpos, right, Val::Float(f * ff), "Float") eval_binary_expr!(&Val::Float(ff), rpos, right, Val::Float(f * ff), "Float")
} }
ref expr => { ref expr => {
return Err(Box::new(error::BuildError::new_with_pos( return Err(Box::new(error::BuildError::with_pos(
format!("{} does not support the '*' operation", expr.type_name()), format!("{} does not support the '*' operation", expr.type_name()),
error::ErrorType::Unsupported, error::ErrorType::Unsupported,
lpos.clone(), lpos.clone(),
@ -626,7 +628,7 @@ impl<'a> FileBuilder<'a> {
eval_binary_expr!(&Val::Float(ff), rpos, right, Val::Float(f % ff), "Float") eval_binary_expr!(&Val::Float(ff), rpos, right, Val::Float(f % ff), "Float")
} }
ref expr => { ref expr => {
return Err(Box::new(error::BuildError::new_with_pos( return Err(Box::new(error::BuildError::with_pos(
format!( format!(
"{} does not support the 'modulus' operation", "{} does not support the 'modulus' operation",
expr.type_name() expr.type_name()
@ -653,7 +655,7 @@ impl<'a> FileBuilder<'a> {
eval_binary_expr!(&Val::Float(ff), rpos, right, Val::Float(f / ff), "Float") eval_binary_expr!(&Val::Float(ff), rpos, right, Val::Float(f / ff), "Float")
} }
ref expr => { ref expr => {
return Err(Box::new(error::BuildError::new_with_pos( return Err(Box::new(error::BuildError::with_pos(
format!("{} does not support the '*' operation", expr.type_name()), format!("{} does not support the '*' operation", expr.type_name()),
error::ErrorType::Unsupported, error::ErrorType::Unsupported,
lpos.clone(), lpos.clone(),
@ -670,7 +672,7 @@ impl<'a> FileBuilder<'a> {
) -> Result<Rc<Val>, Box<dyn Error>> { ) -> Result<Rc<Val>, Box<dyn Error>> {
match left.equal(right.as_ref()) { match left.equal(right.as_ref()) {
Ok(b) => Ok(Rc::new(Val::Boolean(b))), Ok(b) => Ok(Rc::new(Val::Boolean(b))),
Err(e) => Err(Box::new(error::BuildError::new_with_pos( Err(e) => Err(Box::new(error::BuildError::with_pos(
e.msg, e.msg,
e.err_type, e.err_type,
pos.clone(), pos.clone(),
@ -686,7 +688,7 @@ impl<'a> FileBuilder<'a> {
) -> Result<Rc<Val>, Box<dyn Error>> { ) -> Result<Rc<Val>, Box<dyn Error>> {
match left.equal(right.as_ref()) { match left.equal(right.as_ref()) {
Ok(b) => Ok(Rc::new(Val::Boolean(!b))), Ok(b) => Ok(Rc::new(Val::Boolean(!b))),
Err(e) => Err(Box::new(error::BuildError::new_with_pos( Err(e) => Err(Box::new(error::BuildError::with_pos(
e.msg, e.msg,
e.err_type, e.err_type,
pos.clone(), pos.clone(),
@ -711,7 +713,7 @@ impl<'a> FileBuilder<'a> {
return Ok(Rc::new(Val::Boolean(l > r))); return Ok(Rc::new(Val::Boolean(l > r)));
} }
} }
Err(Box::new(error::BuildError::new_with_pos( Err(Box::new(error::BuildError::with_pos(
format!("Expected {} but got ({})", left.type_name(), right,), format!("Expected {} but got ({})", left.type_name(), right,),
error::ErrorType::TypeFail, error::ErrorType::TypeFail,
pos.clone(), pos.clone(),
@ -735,7 +737,7 @@ impl<'a> FileBuilder<'a> {
return Ok(Rc::new(Val::Boolean(l < r))); return Ok(Rc::new(Val::Boolean(l < r)));
} }
} }
Err(Box::new(error::BuildError::new_with_pos( Err(Box::new(error::BuildError::with_pos(
format!("Expected {} but got ({})", left.type_name(), right,), format!("Expected {} but got ({})", left.type_name(), right,),
error::ErrorType::TypeFail, error::ErrorType::TypeFail,
pos.clone(), pos.clone(),
@ -758,7 +760,7 @@ impl<'a> FileBuilder<'a> {
return Ok(Rc::new(Val::Boolean(l <= r))); return Ok(Rc::new(Val::Boolean(l <= r)));
} }
} }
Err(Box::new(error::BuildError::new_with_pos( Err(Box::new(error::BuildError::with_pos(
format!("Expected {} but got ({})", left.type_name(), right), format!("Expected {} but got ({})", left.type_name(), right),
error::ErrorType::TypeFail, error::ErrorType::TypeFail,
pos.clone(), pos.clone(),
@ -781,7 +783,7 @@ impl<'a> FileBuilder<'a> {
return Ok(Rc::new(Val::Boolean(l >= r))); return Ok(Rc::new(Val::Boolean(l >= r)));
} }
} }
Err(Box::new(error::BuildError::new_with_pos( Err(Box::new(error::BuildError::with_pos(
format!("Expected {} but got ({})", left.type_name(), right,), format!("Expected {} but got ({})", left.type_name(), right,),
error::ErrorType::TypeFail, error::ErrorType::TypeFail,
pos.clone(), pos.clone(),
@ -797,7 +799,7 @@ impl<'a> FileBuilder<'a> {
Expression::Simple(Value::Symbol(ref s)) => { Expression::Simple(Value::Symbol(ref s)) => {
scope scope
.lookup_sym(s, true) .lookup_sym(s, true)
.ok_or(Box::new(error::BuildError::new_with_pos( .ok_or(Box::new(error::BuildError::with_pos(
format!("Unable to find binding {}", s.val,), format!("Unable to find binding {}", s.val,),
error::ErrorType::NoSuchSymbol, error::ErrorType::NoSuchSymbol,
pos, pos,
@ -806,7 +808,7 @@ impl<'a> FileBuilder<'a> {
Expression::Simple(Value::Str(ref s)) => { Expression::Simple(Value::Str(ref s)) => {
scope scope
.lookup_sym(s, false) .lookup_sym(s, false)
.ok_or(Box::new(error::BuildError::new_with_pos( .ok_or(Box::new(error::BuildError::with_pos(
format!("Unable to find binding {}", s.val,), format!("Unable to find binding {}", s.val,),
error::ErrorType::NoSuchSymbol, error::ErrorType::NoSuchSymbol,
pos, pos,
@ -821,12 +823,12 @@ impl<'a> FileBuilder<'a> {
Val::Int(i) => scope.lookup_idx(right.pos(), &Val::Int(*i)), Val::Int(i) => scope.lookup_idx(right.pos(), &Val::Int(*i)),
Val::Str(ref s) => scope Val::Str(ref s) => scope
.lookup_sym(&PositionedItem::new(s.clone(), pos.clone()), false) .lookup_sym(&PositionedItem::new(s.clone(), pos.clone()), false)
.ok_or(Box::new(error::BuildError::new_with_pos( .ok_or(Box::new(error::BuildError::with_pos(
format!("Unable to find binding {}", s,), format!("Unable to find binding {}", s,),
error::ErrorType::NoSuchSymbol, error::ErrorType::NoSuchSymbol,
pos, pos,
))), ))),
_ => Err(Box::new(error::BuildError::new_with_pos( _ => Err(Box::new(error::BuildError::with_pos(
format!("Invalid selector lookup {}", val.type_name(),), format!("Invalid selector lookup {}", val.type_name(),),
error::ErrorType::NoSuchSymbol, error::ErrorType::NoSuchSymbol,
pos, pos,
@ -867,7 +869,7 @@ impl<'a> FileBuilder<'a> {
return Ok(right); return Ok(right);
} }
} }
return Err(Box::new(error::BuildError::new_with_pos( return Err(Box::new(error::BuildError::with_pos(
format!( format!(
"Expected boolean value for operator but got ({})", "Expected boolean value for operator but got ({})",
left.type_name() left.type_name()
@ -876,7 +878,7 @@ impl<'a> FileBuilder<'a> {
right_pos.clone(), right_pos.clone(),
))); )));
} else { } else {
return Err(Box::new(error::BuildError::new_with_pos( return Err(Box::new(error::BuildError::with_pos(
format!( format!(
"Expected boolean value for operator but got ({})", "Expected boolean value for operator but got ({})",
left.type_name() left.type_name()
@ -899,7 +901,7 @@ impl<'a> FileBuilder<'a> {
let right = self.eval_expr(right, scope)?; let right = self.eval_expr(right, scope)?;
// presence checks are only valid for tuples and lists. // presence checks are only valid for tuples and lists.
if !(right.is_tuple() || right.is_list()) { if !(right.is_tuple() || right.is_list()) {
return Err(Box::new(error::BuildError::new_with_pos( return Err(Box::new(error::BuildError::with_pos(
format!( format!(
"Invalid righthand type for in operator {}", "Invalid righthand type for in operator {}",
right.type_name() right.type_name()
@ -943,7 +945,7 @@ impl<'a> FileBuilder<'a> {
let re = if let Val::Str(ref s) = right.as_ref() { let re = if let Val::Str(ref s) = right.as_ref() {
regex::Regex::new(s.as_ref())? regex::Regex::new(s.as_ref())?
} else { } else {
return Err(Box::new(error::BuildError::new_with_pos( return Err(Box::new(error::BuildError::with_pos(
format!("Expected string for regex but got ({})", right.type_name()), format!("Expected string for regex but got ({})", right.type_name()),
error::ErrorType::TypeFail, error::ErrorType::TypeFail,
right_pos.clone(), right_pos.clone(),
@ -952,7 +954,7 @@ impl<'a> FileBuilder<'a> {
let tgt = if let Val::Str(ref s) = left.as_ref() { let tgt = if let Val::Str(ref s) = left.as_ref() {
s.as_ref() s.as_ref()
} else { } else {
return Err(Box::new(error::BuildError::new_with_pos( return Err(Box::new(error::BuildError::with_pos(
format!("Expected string but got ({})", left.type_name()), format!("Expected string but got ({})", left.type_name()),
error::ErrorType::TypeFail, error::ErrorType::TypeFail,
left_pos.clone(), left_pos.clone(),
@ -1074,7 +1076,7 @@ impl<'a> FileBuilder<'a> {
{ {
v.insert((src_val.0, expr_result)); v.insert((src_val.0, expr_result));
} else { } else {
return Err(Box::new(error::BuildError::new_with_pos( return Err(Box::new(error::BuildError::with_pos(
format!( format!(
"Expected type {} for field {} but got ({})", "Expected type {} for field {} but got ({})",
src_val.1.type_name(), src_val.1.type_name(),
@ -1132,7 +1134,7 @@ impl<'a> FileBuilder<'a> {
PositionedItem::new_with_pos(String::from("mod"), Position::new(0, 0, 0)); PositionedItem::new_with_pos(String::from("mod"), Position::new(0, 0, 0));
match b.scope.build_output.entry(mod_key) { match b.scope.build_output.entry(mod_key) {
Entry::Occupied(e) => { Entry::Occupied(e) => {
return Err(Box::new(error::BuildError::new_with_pos( return Err(Box::new(error::BuildError::with_pos(
format!( format!(
"Binding \ "Binding \
for {:?} already \ for {:?} already \
@ -1153,7 +1155,7 @@ impl<'a> FileBuilder<'a> {
// tuple using them. // tuple using them.
return Ok(b.get_outputs_as_val()); return Ok(b.get_outputs_as_val());
} else { } else {
return Err(Box::new(error::BuildError::new_with_pos( return Err(Box::new(error::BuildError::with_pos(
format!( format!(
"Weird value stored in our module parameters slot {:?}", "Weird value stored in our module parameters slot {:?}",
mod_def.arg_tuple mod_def.arg_tuple
@ -1163,7 +1165,7 @@ impl<'a> FileBuilder<'a> {
))); )));
} }
} }
Err(Box::new(error::BuildError::new_with_pos( Err(Box::new(error::BuildError::with_pos(
format!("Expected Tuple or Module but got ({})", v), format!("Expected Tuple or Module but got ({})", v),
error::ErrorType::TypeFail, error::ErrorType::TypeFail,
def.selector.pos().clone(), def.selector.pos().clone(),
@ -1207,14 +1209,14 @@ impl<'a> FileBuilder<'a> {
} }
return match def.eval(self, argvals) { return match def.eval(self, argvals) {
Ok(v) => Ok(v), Ok(v) => Ok(v),
Err(e) => Err(Box::new(error::BuildError::new_with_pos( Err(e) => Err(Box::new(error::BuildError::with_pos(
format!("Func evaluation failed\nCaused by:\n\t{}", e), format!("Func evaluation failed\nCaused by:\n\t{}", e),
error::ErrorType::TypeFail, error::ErrorType::TypeFail,
call_pos, call_pos,
))), ))),
}; };
} }
Err(Box::new(error::BuildError::new_with_pos( Err(Box::new(error::BuildError::with_pos(
// We should pretty print the selectors here. // We should pretty print the selectors here.
format!("{} is not a Function", v), format!("{} is not a Function", v),
error::ErrorType::TypeFail, error::ErrorType::TypeFail,
@ -1280,7 +1282,7 @@ impl<'a> FileBuilder<'a> {
// Otherwise return the default. // Otherwise return the default.
return self.eval_expr(def_expr, scope); return self.eval_expr(def_expr, scope);
} else { } else {
return Err(Box::new(error::BuildError::new_with_pos( return Err(Box::new(error::BuildError::with_pos(
format!( format!(
"Expected String but got \ "Expected String but got \
{} in Select expression", {} in Select expression",
@ -1340,7 +1342,7 @@ impl<'a> FileBuilder<'a> {
let new_name = if let &Val::Str(ref s) = fs[0].as_ref() { let new_name = if let &Val::Str(ref s) = fs[0].as_ref() {
s.clone() s.clone()
} else { } else {
return Err(Box::new(error::BuildError::new_with_pos( return Err(Box::new(error::BuildError::with_pos(
format!( format!(
"map on tuple expects the first item out list to be a string but got size {}", "map on tuple expects the first item out list to be a string but got size {}",
fs[0].type_name() fs[0].type_name()
@ -1351,7 +1353,7 @@ impl<'a> FileBuilder<'a> {
}; };
out.push((new_name, fs[1].clone())); out.push((new_name, fs[1].clone()));
} else { } else {
return Err(Box::new(error::BuildError::new_with_pos( return Err(Box::new(error::BuildError::with_pos(
format!( format!(
"map on a tuple field expects a list of size 2 as output but got size {}", "map on a tuple field expects a list of size 2 as output but got size {}",
fs.len() fs.len()
@ -1361,7 +1363,7 @@ impl<'a> FileBuilder<'a> {
))); )));
} }
} else { } else {
return Err(Box::new(error::BuildError::new_with_pos( return Err(Box::new(error::BuildError::with_pos(
format!( format!(
"map on a tuple field expects a list as output but got ({})", "map on a tuple field expects a list as output but got ({})",
result.type_name() result.type_name()
@ -1393,7 +1395,7 @@ impl<'a> FileBuilder<'a> {
let funcdef = match maybe_mac.as_ref() { let funcdef = match maybe_mac.as_ref() {
&Val::Func(ref funcdef) => funcdef, &Val::Func(ref funcdef) => funcdef,
_ => { _ => {
return Err(Box::new(error::BuildError::new_with_pos( return Err(Box::new(error::BuildError::with_pos(
format!("Expected func but got {:?}", def.func), format!("Expected func but got {:?}", def.func),
error::ErrorType::TypeFail, error::ErrorType::TypeFail,
def.pos.clone(), def.pos.clone(),
@ -1423,7 +1425,7 @@ impl<'a> FileBuilder<'a> {
} }
} }
other => { other => {
return Err(Box::new(error::BuildError::new_with_pos( return Err(Box::new(error::BuildError::with_pos(
format!( format!(
"Expected List Str, or Tuple as target but got {:?}", "Expected List Str, or Tuple as target but got {:?}",
other.type_name() other.type_name()
@ -1458,7 +1460,7 @@ impl<'a> FileBuilder<'a> {
// noop // noop
} }
_ => { _ => {
return Err(Box::new(error::BuildError::new_with_pos( return Err(Box::new(error::BuildError::with_pos(
format!( format!(
"Expected boolean or NULL for filter return but got ({})", "Expected boolean or NULL for filter return but got ({})",
out.type_name() out.type_name()
@ -1474,7 +1476,7 @@ impl<'a> FileBuilder<'a> {
result.push_str(&s); result.push_str(&s);
} }
_ => { _ => {
return Err(Box::new(error::BuildError::new_with_pos( return Err(Box::new(error::BuildError::with_pos(
format!("Expected string map return but got ({})", out.type_name()), format!("Expected string map return but got ({})", out.type_name()),
error::ErrorType::TypeFail, error::ErrorType::TypeFail,
def.pos.clone(), def.pos.clone(),
@ -1497,7 +1499,7 @@ impl<'a> FileBuilder<'a> {
let macdef = match maybe_mac.as_ref() { let macdef = match maybe_mac.as_ref() {
&Val::Func(ref macdef) => macdef, &Val::Func(ref macdef) => macdef,
_ => { _ => {
return Err(Box::new(error::BuildError::new_with_pos( return Err(Box::new(error::BuildError::with_pos(
format!("Expected func but got {:?}", def.func), format!("Expected func but got {:?}", def.func),
error::ErrorType::TypeFail, error::ErrorType::TypeFail,
def.pos.clone(), def.pos.clone(),
@ -1509,7 +1511,7 @@ impl<'a> FileBuilder<'a> {
&Val::Tuple(ref fs) => self.eval_functional_tuple_processing(fs, macdef, typ), &Val::Tuple(ref fs) => self.eval_functional_tuple_processing(fs, macdef, typ),
// TODO(jwall): Strings? // TODO(jwall): Strings?
&Val::Str(ref s) => self.eval_functional_string_processing(s, macdef, typ), &Val::Str(ref s) => self.eval_functional_string_processing(s, macdef, typ),
other => Err(Box::new(error::BuildError::new_with_pos( other => Err(Box::new(error::BuildError::with_pos(
format!( format!(
"Expected List or Tuple as target but got {:?}", "Expected List or Tuple as target but got {:?}",
other.type_name() other.type_name()
@ -1619,7 +1621,7 @@ impl<'a> FileBuilder<'a> {
let normalized = match self.find_file(path, false) { let normalized = match self.find_file(path, false) {
Ok(p) => p, Ok(p) => p,
Err(e) => { Err(e) => {
return Err(Box::new(error::BuildError::new_with_pos( return Err(Box::new(error::BuildError::with_pos(
format!("Error finding file {} {}", path, e), format!("Error finding file {} {}", path, e),
error::ErrorType::TypeFail, error::ErrorType::TypeFail,
pos.clone(), pos.clone(),
@ -1629,7 +1631,7 @@ impl<'a> FileBuilder<'a> {
let mut f = match File::open(&normalized) { let mut f = match File::open(&normalized) {
Ok(f) => f, Ok(f) => f,
Err(e) => { Err(e) => {
return Err(Box::new(error::BuildError::new_with_pos( return Err(Box::new(error::BuildError::with_pos(
format!("Error opening file {} {}", normalized.to_string_lossy(), e), format!("Error opening file {} {}", normalized.to_string_lossy(), e),
error::ErrorType::TypeFail, error::ErrorType::TypeFail,
pos.clone(), pos.clone(),
@ -1656,11 +1658,12 @@ impl<'a> FileBuilder<'a> {
eprintln!("including an empty file. Use NULL as the result"); eprintln!("including an empty file. Use NULL as the result");
Rc::new(Val::Empty) Rc::new(Val::Empty)
} else { } else {
// FIXME(jwall): This should be wrapped in a BuildError
importer.import(file_contents.as_bytes())? importer.import(file_contents.as_bytes())?
}; };
Ok(val) Ok(val)
} }
None => Err(Box::new(error::BuildError::new_with_pos( None => Err(Box::new(error::BuildError::with_pos(
format!("Unknown include conversion type {}", def.typ.fragment), format!("Unknown include conversion type {}", def.typ.fragment),
error::ErrorType::Unsupported, error::ErrorType::Unsupported,
def.typ.pos.clone(), def.typ.pos.clone(),
@ -1686,7 +1689,7 @@ impl<'a> FileBuilder<'a> {
let start = match start.as_ref() { let start = match start.as_ref() {
&Val::Int(i) => i, &Val::Int(i) => i,
_ => { _ => {
return Err(Box::new(error::BuildError::new_with_pos( return Err(Box::new(error::BuildError::with_pos(
format!( format!(
"Expected an integer for range start but got ({})", "Expected an integer for range start but got ({})",
start.type_name() start.type_name()
@ -1703,7 +1706,7 @@ impl<'a> FileBuilder<'a> {
match step.as_ref() { match step.as_ref() {
&Val::Int(i) => i, &Val::Int(i) => i,
_ => { _ => {
return Err(Box::new(error::BuildError::new_with_pos( return Err(Box::new(error::BuildError::with_pos(
format!( format!(
"Expected an integer for range step but got ({})", "Expected an integer for range step but got ({})",
step.type_name() step.type_name()
@ -1722,7 +1725,7 @@ impl<'a> FileBuilder<'a> {
let end = match end.as_ref() { let end = match end.as_ref() {
&Val::Int(i) => i, &Val::Int(i) => i,
_ => { _ => {
return Err(Box::new(error::BuildError::new_with_pos( return Err(Box::new(error::BuildError::with_pos(
format!( format!(
"Expected an integer for range start but got ({})", "Expected an integer for range start but got ({})",
end.type_name() end.type_name()
@ -1749,7 +1752,7 @@ impl<'a> FileBuilder<'a> {
let typ = match tval.as_ref() { let typ = match tval.as_ref() {
Val::Str(ref s) => s.clone(), Val::Str(ref s) => s.clone(),
_ => { _ => {
return Err(Box::new(error::BuildError::new_with_pos( return Err(Box::new(error::BuildError::with_pos(
format!("Expected string expression but got ({})", tval), format!("Expected string expression but got ({})", tval),
error::ErrorType::TypeFail, error::ErrorType::TypeFail,
def.right.pos().clone(), def.right.pos().clone(),
@ -1768,7 +1771,7 @@ impl<'a> FileBuilder<'a> {
"func" => val.is_func(), "func" => val.is_func(),
"module" => val.is_module(), "module" => val.is_module(),
other => { other => {
return Err(Box::new(error::BuildError::new_with_pos( return Err(Box::new(error::BuildError::with_pos(
format!("Expected valid type name but got ({})", other), format!("Expected valid type name but got ({})", other),
error::ErrorType::TypeFail, error::ErrorType::TypeFail,
def.right.pos().clone(), def.right.pos().clone(),
@ -1804,13 +1807,13 @@ impl<'a> FileBuilder<'a> {
&Expression::Fail(ref def) => { &Expression::Fail(ref def) => {
let err = self.eval_expr(&def.message, scope)?; let err = self.eval_expr(&def.message, scope)?;
return if let Val::Str(ref s) = err.as_ref() { return if let Val::Str(ref s) = err.as_ref() {
Err(Box::new(error::BuildError::new_with_pos( Err(Box::new(error::BuildError::with_pos(
s.clone(), s.clone(),
error::ErrorType::UserDefined, error::ErrorType::UserDefined,
def.pos.clone(), def.pos.clone(),
))) )))
} else { } else {
Err(Box::new(error::BuildError::new_with_pos( Err(Box::new(error::BuildError::with_pos(
format!( format!(
"Expected string for message but got ({})", "Expected string for message but got ({})",
def.message.as_ref() def.message.as_ref()
@ -1825,7 +1828,7 @@ impl<'a> FileBuilder<'a> {
return if let Val::Boolean(b) = val.as_ref() { return if let Val::Boolean(b) = val.as_ref() {
Ok(Rc::new(Val::Boolean(!b))) Ok(Rc::new(Val::Boolean(!b)))
} else { } else {
Err(Box::new(error::BuildError::new_with_pos( Err(Box::new(error::BuildError::with_pos(
format!( format!(
"Expected boolean for expression but got ({})", "Expected boolean for expression but got ({})",
def.expr.as_ref() def.expr.as_ref()

View File

@ -115,7 +115,7 @@ impl Scope {
return Self::lookup_in_list(pos, idx, fs); return Self::lookup_in_list(pos, idx, fs);
} }
} }
Err(Box::new(error::BuildError::new_with_pos( Err(Box::new(error::BuildError::with_pos(
"Not a list in index lookup.", "Not a list in index lookup.",
error::ErrorType::TypeFail, error::ErrorType::TypeFail,
pos.clone(), pos.clone(),
@ -189,7 +189,7 @@ impl Scope {
if let Some(vv) = find_in_fieldlist(&field, fs) { if let Some(vv) = find_in_fieldlist(&field, fs) {
Ok(vv) Ok(vv)
} else { } else {
Err(Box::new(error::BuildError::new_with_pos( Err(Box::new(error::BuildError::with_pos(
format!("Unable to {} match element in tuple.", field,), format!("Unable to {} match element in tuple.", field,),
error::ErrorType::NoSuchSymbol, error::ErrorType::NoSuchSymbol,
pos.clone(), pos.clone(),
@ -206,7 +206,7 @@ impl Scope {
&Val::Int(i) => i as usize, &Val::Int(i) => i as usize,
&Val::Str(ref s) => s.parse::<usize>()?, &Val::Str(ref s) => s.parse::<usize>()?,
_ => { _ => {
return Err(Box::new(error::BuildError::new_with_pos( return Err(Box::new(error::BuildError::with_pos(
format!("Invalid idx type {} for list lookup", field), format!("Invalid idx type {} for list lookup", field),
error::ErrorType::TypeFail, error::ErrorType::TypeFail,
pos.clone(), pos.clone(),
@ -216,7 +216,7 @@ impl Scope {
if idx < elems.len() { if idx < elems.len() {
Ok(elems[idx].clone()) Ok(elems[idx].clone())
} else { } else {
Err(Box::new(error::BuildError::new_with_pos( Err(Box::new(error::BuildError::with_pos(
format!("idx {} out of bounds in list", idx), format!("idx {} out of bounds in list", idx),
error::ErrorType::NoSuchSymbol, error::ErrorType::NoSuchSymbol,
pos.clone(), pos.clone(),

View File

@ -61,15 +61,19 @@ pub struct BuildError {
pub err_type: ErrorType, pub err_type: ErrorType,
pub pos: Option<Position>, pub pos: Option<Position>,
pub msg: String, pub msg: String,
pub cause: Option<Box<dyn error::Error>>,
// This field is only present to prevent people from constructing these
// outside of the module they are defined in.
_pkgonly: (), _pkgonly: (),
} }
impl BuildError { impl BuildError {
pub fn new_with_pos<S: Into<String>>(msg: S, t: ErrorType, pos: Position) -> Self { pub fn with_pos<S: Into<String>>(msg: S, t: ErrorType, pos: Position) -> Self {
BuildError { BuildError {
err_type: t, err_type: t,
pos: Some(pos), pos: Some(pos),
msg: msg.into(), msg: msg.into(),
cause: None,
_pkgonly: (), _pkgonly: (),
} }
} }
@ -79,10 +83,16 @@ impl BuildError {
err_type: t, err_type: t,
pos: None, pos: None,
msg: msg.into(), msg: msg.into(),
cause: None,
_pkgonly: (), _pkgonly: (),
} }
} }
pub fn wrap_cause(mut self, cause: Box<dyn error::Error>) -> Self {
self.cause = Some(cause);
self
}
fn render(&self, w: &mut fmt::Formatter) -> fmt::Result { fn render(&self, w: &mut fmt::Formatter) -> fmt::Result {
if let Some(ref pos) = self.pos { if let Some(ref pos) = self.pos {
let file = match pos.file { let file = match pos.file {
@ -91,11 +101,14 @@ impl BuildError {
}; };
write!( write!(
w, w,
"{} at {} line: {}, column: {}\nCaused By:\n\t{} ", "{}: {} at {} line: {}, column: {}",
self.err_type, file, pos.line, pos.column, self.msg self.err_type, self.msg, file, pos.line, pos.column
)?; )?;
} else { } else {
write!(w, "{} \nCaused By:\n\t{} ", self.err_type, self.msg)?; write!(w, "{}: {}", self.err_type, self.msg)?;
}
if let Some(ref cause) = self.cause {
write!(w, "\nCaused By:\n\t{}", cause)?;
} }
Ok(()) Ok(())
} }