Propagate source info.

This commit is contained in:
Stephen Chung 2020-12-30 21:12:51 +08:00
parent 386e34aacd
commit ba7f8c6391
9 changed files with 180 additions and 112 deletions

View File

@ -1,6 +1,20 @@
Rhai Release Notes Rhai Release Notes
================== ==================
Version 0.19.10
===============
Breaking changes
----------------
* The error variant `EvalAltResult::ErrorInFunctionCall` has a new parameter holding the _source_ of the function.
Enhancements
------------
* Source information is provided when there is an error within a call to a function defined in another module.
Version 0.19.9 Version 0.19.9
============== ==============

View File

@ -89,7 +89,7 @@ fn main() {
} }
} }
let module = match engine let mut module = match engine
.compile(&contents) .compile(&contents)
.map_err(|err| err.into()) .map_err(|err| err.into())
.and_then(|ast| Module::eval_ast_as_new(Default::default(), &ast, &engine)) .and_then(|ast| Module::eval_ast_as_new(Default::default(), &ast, &engine))
@ -106,6 +106,8 @@ fn main() {
Ok(m) => m, Ok(m) => m,
}; };
module.set_id(Some(&filename));
engine.register_global_module(module.into()); engine.register_global_module(module.into());
has_init_scripts = true; has_init_scripts = true;

View File

@ -508,7 +508,11 @@ pub struct State {
/// Number of modules loaded. /// Number of modules loaded.
pub modules: usize, pub modules: usize,
/// Cached lookup values for function hashes. /// Cached lookup values for function hashes.
pub functions_cache: HashMap<NonZeroU64, Option<CallableFunction>, StraightHasherBuilder>, pub functions_cache: HashMap<
NonZeroU64,
Option<(CallableFunction, Option<ImmutableString>)>,
StraightHasherBuilder,
>,
} }
impl State { impl State {

View File

@ -175,11 +175,8 @@ impl Engine {
) -> Result<(Dynamic, bool), Box<EvalAltResult>> { ) -> Result<(Dynamic, bool), Box<EvalAltResult>> {
self.inc_operations(state, pos)?; self.inc_operations(state, pos)?;
let func = state.functions_cache.get(&hash_fn).cloned(); // Check if function access already in the cache
if !state.functions_cache.contains_key(&hash_fn) {
let func = if let Some(ref f) = func {
f.as_ref()
} else {
// Search for the native function // Search for the native function
// First search registered functions (can override packages) // First search registered functions (can override packages)
// Then search packages // Then search packages
@ -189,18 +186,24 @@ impl Engine {
let f = self let f = self
.global_namespace .global_namespace
.get_fn(hash_fn, pub_only) .get_fn(hash_fn, pub_only)
.cloned()
.map(|f| (f, None))
.or_else(|| { .or_else(|| {
self.global_modules self.global_modules.iter().find_map(|m| {
.iter() m.get_fn(hash_fn, false)
.find_map(|m| m.get_fn(hash_fn, false)) .cloned()
.map(|f| (f, m.id_raw().clone()))
})
}) })
.or_else(|| mods.get_fn(hash_fn)); .or_else(|| mods.get_fn(hash_fn).cloned().map(|f| (f, None)));
state.functions_cache.insert(hash_fn, f.cloned()); // Store into cache
f state.functions_cache.insert(hash_fn, f);
}; }
if let Some(func) = func { let func = state.functions_cache.get(&hash_fn).unwrap();
if let Some((func, source)) = func {
assert!(func.is_native()); assert!(func.is_native());
// Calling pure function but the first argument is a reference? // Calling pure function but the first argument is a reference?
@ -208,11 +211,16 @@ impl Engine {
backup.change_first_arg_to_copy(is_ref && func.is_pure(), args); backup.change_first_arg_to_copy(is_ref && func.is_pure(), args);
// Run external function // Run external function
let source = if source.is_none() {
&state.source
} else {
source
};
let result = if func.is_plugin_fn() { let result = if func.is_plugin_fn() {
func.get_plugin_fn() func.get_plugin_fn()
.call((self, &state.source, mods, lib).into(), args) .call((self, source, mods, lib).into(), args)
} else { } else {
func.get_native_fn()((self, &state.source, mods, lib).into(), args) func.get_native_fn()((self, source, mods, lib).into(), args)
}; };
// Restore the original reference // Restore the original reference
@ -413,9 +421,26 @@ impl Engine {
.or_else(|err| match *err { .or_else(|err| match *err {
// Convert return statement to return value // Convert return statement to return value
EvalAltResult::Return(x, _) => Ok(x), EvalAltResult::Return(x, _) => Ok(x),
EvalAltResult::ErrorInFunctionCall(name, err, _) => { EvalAltResult::ErrorInFunctionCall(name, src, err, _) => {
EvalAltResult::ErrorInFunctionCall( EvalAltResult::ErrorInFunctionCall(
format!("{} > {}", fn_def.name, name), format!(
"{}{} < {}",
name,
if src.is_empty() {
"".to_string()
} else {
format!(" @ '{}'", src)
},
fn_def.name
),
fn_def
.lib
.as_ref()
.map(|m| m.id())
.flatten()
.or_else(|| state.source.as_ref().map(|s| s.as_str()))
.unwrap_or("")
.to_string(),
err, err,
pos, pos,
) )
@ -424,7 +449,20 @@ impl Engine {
// System errors are passed straight-through // System errors are passed straight-through
err if err.is_system_exception() => Err(Box::new(err)), err if err.is_system_exception() => Err(Box::new(err)),
// Other errors are wrapped in `ErrorInFunctionCall` // Other errors are wrapped in `ErrorInFunctionCall`
_ => EvalAltResult::ErrorInFunctionCall(fn_def.name.to_string(), err, pos).into(), _ => EvalAltResult::ErrorInFunctionCall(
fn_def.name.to_string(),
fn_def
.lib
.as_ref()
.map(|m| m.id())
.flatten()
.or_else(|| state.source.as_ref().map(|s| s.as_str()))
.unwrap_or("")
.to_string(),
err,
pos,
)
.into(),
}); });
// Remove all local variables // Remove all local variables
@ -553,96 +591,81 @@ impl Engine {
}) })
//.or_else(|| self.global_namespace.get_fn(hash_script, pub_only)) //.or_else(|| self.global_namespace.get_fn(hash_script, pub_only))
.or_else(|| { .or_else(|| {
self.global_modules self.global_modules.iter().find_map(|m| {
.iter() m.get_fn(hash_script, false)
.find_map(|m| m.get_fn(hash_script, false)) .map(|f| (f, m.id_raw().clone()))
.map(|f| (f, None)) })
}) })
//.or_else(|| mods.iter().find_map(|(_, m)| m.get_qualified_fn(hash_script).map(|f| (f, m.id_raw().clone())))) //.or_else(|| mods.iter().find_map(|(_, m)| m.get_qualified_fn(hash_script).map(|f| (f, m.id_raw().clone()))))
.unwrap(); .unwrap();
if func.is_script() { assert!(func.is_script());
let func = func.get_fn_def();
let scope: &mut Scope = &mut Default::default(); let func = func.get_fn_def();
// Move captured variables into scope let scope: &mut Scope = &mut Default::default();
#[cfg(not(feature = "no_closure"))]
if let Some(captured) = _capture_scope { // Move captured variables into scope
if !func.externals.is_empty() { #[cfg(not(feature = "no_closure"))]
captured if let Some(captured) = _capture_scope {
.into_iter() if !func.externals.is_empty() {
.filter(|(name, _, _)| func.externals.iter().any(|ex| ex == name)) captured
.for_each(|(name, value, _)| { .into_iter()
// Consume the scope values. .filter(|(name, _, _)| func.externals.iter().any(|ex| ex == name))
scope.push_dynamic(name, value); .for_each(|(name, value, _)| {
}); // Consume the scope values.
} scope.push_dynamic(name, value);
});
} }
}
let result = if _is_method { let result = if _is_method {
// Method call of script function - map first argument to `this` // Method call of script function - map first argument to `this`
let (first, rest) = args.split_first_mut().unwrap(); let (first, rest) = args.split_first_mut().unwrap();
mem::swap(&mut state.source, &mut source); mem::swap(&mut state.source, &mut source);
let level = _level + 1; let level = _level + 1;
let result = self.call_script_fn( let result = self.call_script_fn(
scope, scope,
mods,
state,
lib,
&mut Some(*first),
func,
rest,
pos,
level,
);
// Restore the original source
state.source = source;
result?
} else {
// Normal call of script function
// The first argument is a reference?
let mut backup: ArgBackup = Default::default();
backup.change_first_arg_to_copy(is_ref, args);
mem::swap(&mut state.source, &mut source);
let level = _level + 1;
let result = self.call_script_fn(
scope, mods, state, lib, &mut None, func, args, pos, level,
);
// Restore the original source
state.source = source;
// Restore the original reference
backup.restore_first_arg(args);
result?
};
Ok((result, false))
} else {
// If it is a native function, redirect it
self.call_native_fn(
mods, mods,
state, state,
lib, lib,
fn_name, &mut Some(*first),
hash_script, func,
args, rest,
is_ref,
pub_only,
pos, pos,
def_val, level,
) );
}
// Restore the original source
state.source = source;
result?
} else {
// Normal call of script function
// The first argument is a reference?
let mut backup: ArgBackup = Default::default();
backup.change_first_arg_to_copy(is_ref, args);
mem::swap(&mut state.source, &mut source);
let level = _level + 1;
let result = self
.call_script_fn(scope, mods, state, lib, &mut None, func, args, pos, level);
// Restore the original source
state.source = source;
// Restore the original reference
backup.restore_first_arg(args);
result?
};
Ok((result, false))
} }
// Normal native function call // Normal native function call

View File

@ -214,6 +214,7 @@ mod array_functions {
.map_err(|err| { .map_err(|err| {
Box::new(EvalAltResult::ErrorInFunctionCall( Box::new(EvalAltResult::ErrorInFunctionCall(
"map".to_string(), "map".to_string(),
ctx.source().unwrap_or("").to_string(),
err, err,
Position::NONE, Position::NONE,
)) ))
@ -245,6 +246,7 @@ mod array_functions {
.map_err(|err| { .map_err(|err| {
Box::new(EvalAltResult::ErrorInFunctionCall( Box::new(EvalAltResult::ErrorInFunctionCall(
"filter".to_string(), "filter".to_string(),
ctx.source().unwrap_or("").to_string(),
err, err,
Position::NONE, Position::NONE,
)) ))
@ -278,6 +280,7 @@ mod array_functions {
.map_err(|err| { .map_err(|err| {
Box::new(EvalAltResult::ErrorInFunctionCall( Box::new(EvalAltResult::ErrorInFunctionCall(
"index_of".to_string(), "index_of".to_string(),
ctx.source().unwrap_or("").to_string(),
err, err,
Position::NONE, Position::NONE,
)) ))
@ -311,6 +314,7 @@ mod array_functions {
.map_err(|err| { .map_err(|err| {
Box::new(EvalAltResult::ErrorInFunctionCall( Box::new(EvalAltResult::ErrorInFunctionCall(
"some".to_string(), "some".to_string(),
ctx.source().unwrap_or("").to_string(),
err, err,
Position::NONE, Position::NONE,
)) ))
@ -344,6 +348,7 @@ mod array_functions {
.map_err(|err| { .map_err(|err| {
Box::new(EvalAltResult::ErrorInFunctionCall( Box::new(EvalAltResult::ErrorInFunctionCall(
"all".to_string(), "all".to_string(),
ctx.source().unwrap_or("").to_string(),
err, err,
Position::NONE, Position::NONE,
)) ))
@ -379,6 +384,7 @@ mod array_functions {
.map_err(|err| { .map_err(|err| {
Box::new(EvalAltResult::ErrorInFunctionCall( Box::new(EvalAltResult::ErrorInFunctionCall(
"reduce".to_string(), "reduce".to_string(),
ctx.source().unwrap_or("").to_string(),
err, err,
Position::NONE, Position::NONE,
)) ))
@ -397,6 +403,7 @@ mod array_functions {
let mut result = initial.call_dynamic(ctx, None, []).map_err(|err| { let mut result = initial.call_dynamic(ctx, None, []).map_err(|err| {
Box::new(EvalAltResult::ErrorInFunctionCall( Box::new(EvalAltResult::ErrorInFunctionCall(
"reduce".to_string(), "reduce".to_string(),
ctx.source().unwrap_or("").to_string(),
err, err,
Position::NONE, Position::NONE,
)) ))
@ -416,6 +423,7 @@ mod array_functions {
.map_err(|err| { .map_err(|err| {
Box::new(EvalAltResult::ErrorInFunctionCall( Box::new(EvalAltResult::ErrorInFunctionCall(
"reduce".to_string(), "reduce".to_string(),
ctx.source().unwrap_or("").to_string(),
err, err,
Position::NONE, Position::NONE,
)) ))
@ -446,6 +454,7 @@ mod array_functions {
.map_err(|err| { .map_err(|err| {
Box::new(EvalAltResult::ErrorInFunctionCall( Box::new(EvalAltResult::ErrorInFunctionCall(
"reduce_rev".to_string(), "reduce_rev".to_string(),
ctx.source().unwrap_or("").to_string(),
err, err,
Position::NONE, Position::NONE,
)) ))
@ -464,6 +473,7 @@ mod array_functions {
let mut result = initial.call_dynamic(ctx, None, []).map_err(|err| { let mut result = initial.call_dynamic(ctx, None, []).map_err(|err| {
Box::new(EvalAltResult::ErrorInFunctionCall( Box::new(EvalAltResult::ErrorInFunctionCall(
"reduce_rev".to_string(), "reduce_rev".to_string(),
ctx.source().unwrap_or("").to_string(),
err, err,
Position::NONE, Position::NONE,
)) ))
@ -483,6 +493,7 @@ mod array_functions {
.map_err(|err| { .map_err(|err| {
Box::new(EvalAltResult::ErrorInFunctionCall( Box::new(EvalAltResult::ErrorInFunctionCall(
"reduce_rev".to_string(), "reduce_rev".to_string(),
ctx.source().unwrap_or("").to_string(),
err, err,
Position::NONE, Position::NONE,
)) ))
@ -553,6 +564,7 @@ mod array_functions {
.map_err(|err| { .map_err(|err| {
Box::new(EvalAltResult::ErrorInFunctionCall( Box::new(EvalAltResult::ErrorInFunctionCall(
"drain".to_string(), "drain".to_string(),
ctx.source().unwrap_or("").to_string(),
err, err,
Position::NONE, Position::NONE,
)) ))
@ -612,6 +624,7 @@ mod array_functions {
.map_err(|err| { .map_err(|err| {
Box::new(EvalAltResult::ErrorInFunctionCall( Box::new(EvalAltResult::ErrorInFunctionCall(
"retain".to_string(), "retain".to_string(),
ctx.source().unwrap_or("").to_string(),
err, err,
Position::NONE, Position::NONE,
)) ))

View File

@ -34,8 +34,8 @@ pub enum EvalAltResult {
/// Call to an unknown function. Wrapped value is the function signature. /// Call to an unknown function. Wrapped value is the function signature.
ErrorFunctionNotFound(String, Position), ErrorFunctionNotFound(String, Position),
/// An error has occurred inside a called function. /// An error has occurred inside a called function.
/// Wrapped values are the function name and the interior error. /// Wrapped values are the function name, function source, and the interior error.
ErrorInFunctionCall(String, Box<EvalAltResult>, Position), ErrorInFunctionCall(String, String, Box<EvalAltResult>, Position),
/// Usage of an unknown [module][crate::Module]. Wrapped value is the [module][crate::Module] name. /// Usage of an unknown [module][crate::Module]. Wrapped value is the [module][crate::Module] name.
ErrorModuleNotFound(String, Position), ErrorModuleNotFound(String, Position),
/// An error has occurred while loading a [module][crate::Module]. /// An error has occurred while loading a [module][crate::Module].
@ -98,7 +98,7 @@ impl EvalAltResult {
#[allow(deprecated)] #[allow(deprecated)]
Self::ErrorSystem(_, s) => s.description(), Self::ErrorSystem(_, s) => s.description(),
Self::ErrorParsing(p, _) => p.desc(), Self::ErrorParsing(p, _) => p.desc(),
Self::ErrorInFunctionCall(_, _, _) => "Error in called function", Self::ErrorInFunctionCall(_,_, _, _) => "Error in called function",
Self::ErrorInModule(_, _, _) => "Error in module", Self::ErrorInModule(_, _, _) => "Error in module",
Self::ErrorFunctionNotFound(_, _) => "Function not found", Self::ErrorFunctionNotFound(_, _) => "Function not found",
Self::ErrorUnboundThis(_) => "'this' is not bound", Self::ErrorUnboundThis(_) => "'this' is not bound",
@ -152,12 +152,19 @@ impl fmt::Display for EvalAltResult {
Self::ErrorParsing(p, _) => write!(f, "Syntax error: {}", p)?, Self::ErrorParsing(p, _) => write!(f, "Syntax error: {}", p)?,
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
Self::ErrorInFunctionCall(s, err, _) if crate::engine::is_anonymous_fn(s) => { Self::ErrorInFunctionCall(s, src, err, _) if crate::engine::is_anonymous_fn(s) => {
write!(f, "Error in call to closure: {}", err)? write!(f, "{}, in call to closure", err)?;
if !src.is_empty() {
write!(f, " @ '{}'", src)?;
}
} }
Self::ErrorInFunctionCall(s, err, _) => { Self::ErrorInFunctionCall(s, src, err, _) => {
write!(f, "Error in call to function '{}': {}", s, err)? write!(f, "{}, in call to function {}", err, s)?;
if !src.is_empty() {
write!(f, " @ '{}'", src)?;
}
} }
Self::ErrorInModule(s, err, _) if s.is_empty() => { Self::ErrorInModule(s, err, _) if s.is_empty() => {
write!(f, "Error in module: {}", err)? write!(f, "Error in module: {}", err)?
} }
@ -165,8 +172,9 @@ impl fmt::Display for EvalAltResult {
Self::ErrorFunctionNotFound(s, _) Self::ErrorFunctionNotFound(s, _)
| Self::ErrorVariableNotFound(s, _) | Self::ErrorVariableNotFound(s, _)
| Self::ErrorDataRace(s, _) | Self::ErrorDataRace(s, _) => write!(f, "{}: {}", desc, s)?,
| Self::ErrorModuleNotFound(s, _) => write!(f, "{}: '{}'", desc, s)?,
Self::ErrorModuleNotFound(s, _) => write!(f, "{}: '{}'", desc, s)?,
Self::ErrorDotExpr(s, _) if !s.is_empty() => write!(f, "{}", s)?, Self::ErrorDotExpr(s, _) if !s.is_empty() => write!(f, "{}", s)?,
@ -187,9 +195,7 @@ impl fmt::Display for EvalAltResult {
Self::ErrorRuntime(d, _) if d.is::<()>() => f.write_str(desc)?, Self::ErrorRuntime(d, _) if d.is::<()>() => f.write_str(desc)?,
Self::ErrorRuntime(d, _) => write!(f, "{}: {}", desc, d)?, Self::ErrorRuntime(d, _) => write!(f, "{}: {}", desc, d)?,
Self::ErrorAssignmentToConstant(s, _) => { Self::ErrorAssignmentToConstant(s, _) => write!(f, "Cannot assign to constant {}", s)?,
write!(f, "Cannot assign to constant '{}'", s)?
}
Self::ErrorMismatchOutputType(s, r, _) => { Self::ErrorMismatchOutputType(s, r, _) => {
write!(f, "Output type is incorrect: {} (expecting {})", r, s)? write!(f, "Output type is incorrect: {} (expecting {})", r, s)?
} }
@ -276,7 +282,7 @@ impl EvalAltResult {
Self::ErrorParsing(_, _) => false, Self::ErrorParsing(_, _) => false,
Self::ErrorFunctionNotFound(_, _) Self::ErrorFunctionNotFound(_, _)
| Self::ErrorInFunctionCall(_, _, _) | Self::ErrorInFunctionCall(_, _, _, _)
| Self::ErrorInModule(_, _, _) | Self::ErrorInModule(_, _, _)
| Self::ErrorUnboundThis(_) | Self::ErrorUnboundThis(_)
| Self::ErrorMismatchDataType(_, _, _) | Self::ErrorMismatchDataType(_, _, _)
@ -334,7 +340,7 @@ impl EvalAltResult {
Self::ErrorParsing(_, pos) Self::ErrorParsing(_, pos)
| Self::ErrorFunctionNotFound(_, pos) | Self::ErrorFunctionNotFound(_, pos)
| Self::ErrorInFunctionCall(_, _, pos) | Self::ErrorInFunctionCall(_, _, _, pos)
| Self::ErrorInModule(_, _, pos) | Self::ErrorInModule(_, _, pos)
| Self::ErrorUnboundThis(pos) | Self::ErrorUnboundThis(pos)
| Self::ErrorMismatchDataType(_, _, pos) | Self::ErrorMismatchDataType(_, _, pos)
@ -367,7 +373,7 @@ impl EvalAltResult {
Self::ErrorParsing(_, pos) Self::ErrorParsing(_, pos)
| Self::ErrorFunctionNotFound(_, pos) | Self::ErrorFunctionNotFound(_, pos)
| Self::ErrorInFunctionCall(_, _, pos) | Self::ErrorInFunctionCall(_, _, _, pos)
| Self::ErrorInModule(_, _, pos) | Self::ErrorInModule(_, _, pos)
| Self::ErrorUnboundThis(pos) | Self::ErrorUnboundThis(pos)
| Self::ErrorMismatchDataType(_, _, pos) | Self::ErrorMismatchDataType(_, _, pos)

View File

@ -212,6 +212,12 @@ impl From<&str> for ImmutableString {
Self(value.to_string().into()) Self(value.to_string().into())
} }
} }
impl From<&String> for ImmutableString {
#[inline(always)]
fn from(value: &String) -> Self {
Self(value.to_string().into())
}
}
impl From<String> for ImmutableString { impl From<String> for ImmutableString {
#[inline(always)] #[inline(always)]
fn from(value: String) -> Self { fn from(value: String) -> Self {

View File

@ -73,7 +73,7 @@ fn test_fn_ptr() -> Result<(), Box<EvalAltResult>> {
"# "#
) )
.expect_err("should error"), .expect_err("should error"),
EvalAltResult::ErrorInFunctionCall(fn_name, err, _) EvalAltResult::ErrorInFunctionCall(fn_name, _, err, _)
if fn_name == "foo" && matches!(*err, EvalAltResult::ErrorUnboundThis(_)) if fn_name == "foo" && matches!(*err, EvalAltResult::ErrorUnboundThis(_))
)); ));

View File

@ -227,7 +227,7 @@ fn test_module_resolver() -> Result<(), Box<EvalAltResult>> {
"# "#
) )
.expect_err("should error"), .expect_err("should error"),
EvalAltResult::ErrorInFunctionCall(fn_name, _, _) if fn_name == "foo" EvalAltResult::ErrorInFunctionCall(fn_name, _, _, _) if fn_name == "foo"
)); ));
engine.set_max_modules(1000); engine.set_max_modules(1000);