Merge pull request #552 from schungx/master
Fix indexers with compound assignments.
This commit is contained in:
commit
a259dc1e0d
10
CHANGELOG.md
10
CHANGELOG.md
@ -1,6 +1,15 @@
|
||||
Rhai Release Notes
|
||||
==================
|
||||
|
||||
Version 1.7.0
|
||||
=============
|
||||
|
||||
Bug fixes
|
||||
---------
|
||||
|
||||
* Compound assignments now work properly with indexers.
|
||||
|
||||
|
||||
Version 1.6.1
|
||||
=============
|
||||
|
||||
@ -9,6 +18,7 @@ Bug fixes
|
||||
|
||||
* Functions with `Dynamic` parameters now work in qualified calls from `import`ed modules.
|
||||
* `rhai-repl` now compiles with the new patch version of `rustyline`.
|
||||
* `rhai_codegen` dependency is now explicitly `1.4` or higher.
|
||||
|
||||
Script-breaking changes
|
||||
-----------------------
|
||||
|
@ -3,7 +3,7 @@ members = [".", "codegen"]
|
||||
|
||||
[package]
|
||||
name = "rhai"
|
||||
version = "1.6.1"
|
||||
version = "1.7.0"
|
||||
rust-version = "1.57"
|
||||
edition = "2018"
|
||||
authors = ["Jonathan Turner", "Lukáš Hozda", "Stephen Chung", "jhwgh1968"]
|
||||
|
@ -7,6 +7,10 @@ use crate::{
|
||||
#[cfg(feature = "no_std")]
|
||||
use std::prelude::v1::*;
|
||||
|
||||
#[cfg(not(feature = "no_std"))]
|
||||
#[cfg(not(target_family = "wasm"))]
|
||||
use std::path::PathBuf;
|
||||
|
||||
impl Engine {
|
||||
/// Evaluate a file, but throw away the result and only return error (if any).
|
||||
/// Useful for when you don't need the result, but still need to keep track of possible errors.
|
||||
@ -17,12 +21,12 @@ impl Engine {
|
||||
///
|
||||
/// This method is deprecated. Use [`run_file`][Engine::run_file] instead.
|
||||
///
|
||||
/// This method will be removed in the next major version.
|
||||
/// This method will be removed in the next majocd cr version.
|
||||
#[deprecated(since = "1.1.0", note = "use `run_file` instead")]
|
||||
#[cfg(not(feature = "no_std"))]
|
||||
#[cfg(not(target_family = "wasm"))]
|
||||
#[inline(always)]
|
||||
pub fn consume_file(&self, path: std::path::PathBuf) -> RhaiResultOf<()> {
|
||||
pub fn consume_file(&self, path: PathBuf) -> RhaiResultOf<()> {
|
||||
self.run_file(path)
|
||||
}
|
||||
|
||||
@ -40,11 +44,7 @@ impl Engine {
|
||||
#[cfg(not(feature = "no_std"))]
|
||||
#[cfg(not(target_family = "wasm"))]
|
||||
#[inline(always)]
|
||||
pub fn consume_file_with_scope(
|
||||
&self,
|
||||
scope: &mut Scope,
|
||||
path: std::path::PathBuf,
|
||||
) -> RhaiResultOf<()> {
|
||||
pub fn consume_file_with_scope(&self, scope: &mut Scope, path: PathBuf) -> RhaiResultOf<()> {
|
||||
self.run_file_with_scope(scope, path)
|
||||
}
|
||||
|
||||
|
@ -6,13 +6,12 @@ use crate::types::dynamic::Variant;
|
||||
use crate::{Engine, RhaiResultOf, Scope, AST, ERR};
|
||||
#[cfg(feature = "no_std")]
|
||||
use std::prelude::v1::*;
|
||||
use std::{fs::File, io::Read, path::PathBuf};
|
||||
|
||||
impl Engine {
|
||||
/// Read the contents of a file into a string.
|
||||
fn read_file(path: std::path::PathBuf) -> RhaiResultOf<String> {
|
||||
use std::io::Read;
|
||||
|
||||
let mut f = std::fs::File::open(path.clone()).map_err(|err| {
|
||||
fn read_file(path: PathBuf) -> RhaiResultOf<String> {
|
||||
let mut f = File::open(path.clone()).map_err(|err| {
|
||||
ERR::ErrorSystem(
|
||||
format!("Cannot open script file '{}'", path.to_string_lossy()),
|
||||
err.into(),
|
||||
@ -62,7 +61,7 @@ impl Engine {
|
||||
/// # }
|
||||
/// ```
|
||||
#[inline(always)]
|
||||
pub fn compile_file(&self, path: std::path::PathBuf) -> RhaiResultOf<AST> {
|
||||
pub fn compile_file(&self, path: PathBuf) -> RhaiResultOf<AST> {
|
||||
self.compile_file_with_scope(&Scope::new(), path)
|
||||
}
|
||||
/// Compile a script file into an [`AST`] using own scope, which can be used later for evaluation.
|
||||
@ -100,11 +99,7 @@ impl Engine {
|
||||
/// # }
|
||||
/// ```
|
||||
#[inline]
|
||||
pub fn compile_file_with_scope(
|
||||
&self,
|
||||
scope: &Scope,
|
||||
path: std::path::PathBuf,
|
||||
) -> RhaiResultOf<AST> {
|
||||
pub fn compile_file_with_scope(&self, scope: &Scope, path: PathBuf) -> RhaiResultOf<AST> {
|
||||
Self::read_file(path).and_then(|contents| Ok(self.compile_with_scope(scope, &contents)?))
|
||||
}
|
||||
/// Evaluate a script file.
|
||||
@ -125,7 +120,7 @@ impl Engine {
|
||||
/// # }
|
||||
/// ```
|
||||
#[inline]
|
||||
pub fn eval_file<T: Variant + Clone>(&self, path: std::path::PathBuf) -> RhaiResultOf<T> {
|
||||
pub fn eval_file<T: Variant + Clone>(&self, path: PathBuf) -> RhaiResultOf<T> {
|
||||
Self::read_file(path).and_then(|contents| self.eval::<T>(&contents))
|
||||
}
|
||||
/// Evaluate a script file with own scope.
|
||||
@ -160,7 +155,7 @@ impl Engine {
|
||||
pub fn eval_file_with_scope<T: Variant + Clone>(
|
||||
&self,
|
||||
scope: &mut Scope,
|
||||
path: std::path::PathBuf,
|
||||
path: PathBuf,
|
||||
) -> RhaiResultOf<T> {
|
||||
Self::read_file(path).and_then(|contents| self.eval_with_scope(scope, &contents))
|
||||
}
|
||||
@ -168,7 +163,7 @@ impl Engine {
|
||||
///
|
||||
/// Not available under `no_std` or `WASM`.
|
||||
#[inline]
|
||||
pub fn run_file(&self, path: std::path::PathBuf) -> RhaiResultOf<()> {
|
||||
pub fn run_file(&self, path: PathBuf) -> RhaiResultOf<()> {
|
||||
Self::read_file(path).and_then(|contents| self.run(&contents))
|
||||
}
|
||||
/// Evaluate a file with own scope, returning any error (if any).
|
||||
@ -182,11 +177,7 @@ impl Engine {
|
||||
///
|
||||
/// This allows functions to be optimized based on dynamic global constants.
|
||||
#[inline]
|
||||
pub fn run_file_with_scope(
|
||||
&self,
|
||||
scope: &mut Scope,
|
||||
path: std::path::PathBuf,
|
||||
) -> RhaiResultOf<()> {
|
||||
pub fn run_file_with_scope(&self, scope: &mut Scope, path: PathBuf) -> RhaiResultOf<()> {
|
||||
Self::read_file(path).and_then(|contents| self.run_with_scope(scope, &contents))
|
||||
}
|
||||
}
|
||||
|
@ -27,7 +27,7 @@ pub struct Limits {
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
pub max_function_expr_depth: Option<NonZeroUsize>,
|
||||
/// Maximum number of operations allowed to run.
|
||||
pub max_operations: Option<std::num::NonZeroU64>,
|
||||
pub max_operations: Option<NonZeroU64>,
|
||||
/// Maximum number of [modules][crate::Module] allowed to load.
|
||||
///
|
||||
/// Set to zero to effectively disable loading any [module][crate::Module].
|
||||
|
@ -8,6 +8,7 @@ use std::{
|
||||
fmt,
|
||||
hash::Hash,
|
||||
ops::{Add, AddAssign},
|
||||
ptr,
|
||||
};
|
||||
|
||||
/// Compiled AST (abstract syntax tree) of a Rhai script.
|
||||
@ -870,8 +871,8 @@ impl PartialEq for ASTNode<'_> {
|
||||
#[inline(always)]
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
match (self, other) {
|
||||
(Self::Stmt(x), Self::Stmt(y)) => std::ptr::eq(*x, *y),
|
||||
(Self::Expr(x), Self::Expr(y)) => std::ptr::eq(*x, *y),
|
||||
(Self::Stmt(x), Self::Stmt(y)) => ptr::eq(*x, *y),
|
||||
(Self::Expr(x), Self::Expr(y)) => ptr::eq(*x, *y),
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
@ -17,7 +17,11 @@ use std::{
|
||||
};
|
||||
|
||||
#[cfg(not(feature = "no_float"))]
|
||||
use std::str::FromStr;
|
||||
use std::{
|
||||
hash::Hasher,
|
||||
ops::{Deref, DerefMut},
|
||||
str::FromStr,
|
||||
};
|
||||
|
||||
#[cfg(not(feature = "no_float"))]
|
||||
use num_traits::float::FloatCore as Float;
|
||||
@ -230,7 +234,7 @@ pub struct FloatWrapper<F>(F);
|
||||
#[cfg(not(feature = "no_float"))]
|
||||
impl Hash for FloatWrapper<crate::FLOAT> {
|
||||
#[inline(always)]
|
||||
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||
self.0.to_ne_bytes().hash(state);
|
||||
}
|
||||
}
|
||||
@ -252,7 +256,7 @@ impl<F: Float> AsMut<F> for FloatWrapper<F> {
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "no_float"))]
|
||||
impl<F: Float> std::ops::Deref for FloatWrapper<F> {
|
||||
impl<F: Float> Deref for FloatWrapper<F> {
|
||||
type Target = F;
|
||||
|
||||
#[inline(always)]
|
||||
@ -262,7 +266,7 @@ impl<F: Float> std::ops::Deref for FloatWrapper<F> {
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "no_float"))]
|
||||
impl<F: Float> std::ops::DerefMut for FloatWrapper<F> {
|
||||
impl<F: Float> DerefMut for FloatWrapper<F> {
|
||||
#[inline(always)]
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.0
|
||||
|
@ -201,12 +201,12 @@ impl Engine {
|
||||
self.run_debugger(scope, global, state, lib, this_ptr, _parent, level)?;
|
||||
|
||||
let ((new_val, new_pos), (op_info, op_pos)) = new_val.expect("`Some`");
|
||||
let mut idx_val_for_setter = idx_val.clone();
|
||||
let mut idx_val2 = idx_val.clone();
|
||||
|
||||
let try_setter = match self.get_indexed_mut(
|
||||
global, state, lib, target, idx_val, pos, true, false, level,
|
||||
) {
|
||||
// Indexed value is a reference - update directly
|
||||
// Indexed value is not a temp value - update directly
|
||||
Ok(ref mut obj_ptr) => {
|
||||
self.eval_op_assignment(
|
||||
global, state, lib, op_info, op_pos, obj_ptr, root, new_val,
|
||||
@ -217,7 +217,7 @@ impl Engine {
|
||||
self.check_data_size(obj_ptr, new_pos)?;
|
||||
None
|
||||
}
|
||||
// Can't index - try to call an index setter
|
||||
// Indexed value cannot be referenced - use indexer
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
Err(err) if matches!(*err, ERR::ErrorIndexingType(..)) => Some(new_val),
|
||||
// Any other error
|
||||
@ -225,8 +225,30 @@ impl Engine {
|
||||
};
|
||||
|
||||
if let Some(mut new_val) = try_setter {
|
||||
let idx = &mut idx_val2;
|
||||
|
||||
// Is this an op-assignment?
|
||||
if op_info.is_some() {
|
||||
let idx = &mut idx.clone();
|
||||
// Call the index getter to get the current value
|
||||
if let Ok(val) =
|
||||
self.call_indexer_get(global, state, lib, target, idx, level)
|
||||
{
|
||||
let mut res = val.into();
|
||||
// Run the op-assignment
|
||||
self.eval_op_assignment(
|
||||
global, state, lib, op_info, op_pos, &mut res, root,
|
||||
new_val, level,
|
||||
)
|
||||
.map_err(|err| err.fill_position(new_pos))?;
|
||||
// Replace new value
|
||||
new_val = res.take_or_clone();
|
||||
#[cfg(not(feature = "unchecked"))]
|
||||
self.check_data_size(&new_val, new_pos)?;
|
||||
}
|
||||
}
|
||||
|
||||
// Try to call index setter
|
||||
let idx = &mut idx_val_for_setter;
|
||||
let new_val = &mut new_val;
|
||||
self.call_indexer_set(
|
||||
global, state, lib, target, idx, new_val, is_ref_mut, level,
|
||||
@ -333,6 +355,7 @@ impl Engine {
|
||||
self.call_indexer_get(
|
||||
global, state, lib, target, &mut prop, level,
|
||||
)
|
||||
.map(|r| (r, false))
|
||||
.map_err(|e| {
|
||||
match *e {
|
||||
ERR::ErrorIndexingType(..) => err,
|
||||
@ -398,6 +421,7 @@ impl Engine {
|
||||
self.call_indexer_get(
|
||||
global, state, lib, target, &mut prop, level,
|
||||
)
|
||||
.map(|r| (r, false))
|
||||
.map_err(|e| match *e {
|
||||
ERR::ErrorIndexingType(..) => err,
|
||||
_ => e,
|
||||
@ -494,6 +518,7 @@ impl Engine {
|
||||
self.call_indexer_get(
|
||||
global, state, lib, target, &mut prop, level,
|
||||
)
|
||||
.map(|r| (r, false))
|
||||
.map_err(
|
||||
|e| match *e {
|
||||
ERR::ErrorIndexingType(..) => err,
|
||||
@ -803,7 +828,7 @@ impl Engine {
|
||||
target: &mut Dynamic,
|
||||
idx: &mut Dynamic,
|
||||
level: usize,
|
||||
) -> RhaiResultOf<(Dynamic, bool)> {
|
||||
) -> RhaiResultOf<Dynamic> {
|
||||
let args = &mut [target, idx];
|
||||
let hash_get = crate::ast::FnCallHashes::from_native(global.hash_idx_get());
|
||||
let fn_name = crate::engine::FN_IDX_GET;
|
||||
@ -812,6 +837,7 @@ impl Engine {
|
||||
self.exec_fn_call(
|
||||
None, global, state, lib, fn_name, hash_get, args, true, true, pos, level,
|
||||
)
|
||||
.map(|(r, ..)| r)
|
||||
}
|
||||
|
||||
/// Call a set indexer.
|
||||
@ -1039,7 +1065,7 @@ impl Engine {
|
||||
|
||||
_ if use_indexers => self
|
||||
.call_indexer_get(global, state, lib, target, &mut idx, level)
|
||||
.map(|(v, ..)| v.into()),
|
||||
.map(Into::into),
|
||||
|
||||
_ => Err(ERR::ErrorIndexingType(
|
||||
format!(
|
||||
|
@ -6,7 +6,7 @@ use crate::ast::{ASTNode, Expr, Stmt};
|
||||
use crate::{Dynamic, Engine, EvalAltResult, Identifier, Module, Position, RhaiResultOf, Scope};
|
||||
#[cfg(feature = "no_std")]
|
||||
use std::prelude::v1::*;
|
||||
use std::{fmt, mem};
|
||||
use std::{fmt, iter::repeat, mem};
|
||||
|
||||
/// Callback function to initialize the debugger.
|
||||
#[cfg(not(feature = "sync"))]
|
||||
@ -160,10 +160,7 @@ impl fmt::Display for BreakPoint {
|
||||
f,
|
||||
"{} ({})",
|
||||
fn_name,
|
||||
std::iter::repeat("_")
|
||||
.take(*args)
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ")
|
||||
repeat("_").take(*args).collect::<Vec<_>>().join(", ")
|
||||
)?;
|
||||
if !*enabled {
|
||||
f.write_str(" (disabled)")?;
|
||||
|
@ -2,10 +2,9 @@
|
||||
|
||||
use crate::func::call::FnResolutionCache;
|
||||
use crate::StaticVec;
|
||||
use std::collections::BTreeMap;
|
||||
use std::marker::PhantomData;
|
||||
#[cfg(feature = "no_std")]
|
||||
use std::prelude::v1::*;
|
||||
use std::{collections::BTreeMap, marker::PhantomData};
|
||||
|
||||
/// _(internals)_ A type that holds all the current states of the [`Engine`][crate::Engine].
|
||||
/// Exported under the `internals` feature only.
|
||||
|
@ -48,7 +48,7 @@ pub fn by_value<T: Variant + Clone>(data: &mut Dynamic) -> T {
|
||||
// # Safety
|
||||
//
|
||||
// We already checked that `T` is `&str`, so it is safe to cast here.
|
||||
return unsafe { std::mem::transmute_copy::<_, T>(&ref_str) };
|
||||
return unsafe { mem::transmute_copy::<_, T>(&ref_str) };
|
||||
}
|
||||
if TypeId::of::<T>() == TypeId::of::<String>() {
|
||||
// If T is `String`, data must be `ImmutableString`, so map directly to it
|
||||
|
@ -1,7 +1,7 @@
|
||||
use crate::{Engine, Module, ModuleResolver, Position, RhaiResultOf, Shared, ERR};
|
||||
use std::ops::AddAssign;
|
||||
#[cfg(feature = "no_std")]
|
||||
use std::prelude::v1::*;
|
||||
use std::{ops::AddAssign, vec::IntoIter};
|
||||
|
||||
/// [Module] resolution service that holds a collection of module resolvers,
|
||||
/// to be searched in sequential order.
|
||||
@ -108,7 +108,7 @@ impl ModuleResolversCollection {
|
||||
|
||||
impl IntoIterator for ModuleResolversCollection {
|
||||
type Item = Box<dyn ModuleResolver>;
|
||||
type IntoIter = std::vec::IntoIter<Box<dyn ModuleResolver>>;
|
||||
type IntoIter = IntoIter<Box<dyn ModuleResolver>>;
|
||||
|
||||
#[inline(always)]
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
|
@ -3,7 +3,7 @@ use crate::{
|
||||
};
|
||||
#[cfg(feature = "no_std")]
|
||||
use std::prelude::v1::*;
|
||||
use std::{collections::BTreeMap, ops::AddAssign};
|
||||
use std::{collections::btree_map::IntoIter, collections::BTreeMap, ops::AddAssign};
|
||||
|
||||
/// A static [module][Module] resolution service that serves [modules][Module] added into it.
|
||||
///
|
||||
@ -120,7 +120,7 @@ impl StaticModuleResolver {
|
||||
|
||||
impl IntoIterator for StaticModuleResolver {
|
||||
type Item = (Identifier, Shared<Module>);
|
||||
type IntoIter = std::collections::btree_map::IntoIter<SmartString, Shared<Module>>;
|
||||
type IntoIter = IntoIter<SmartString, Shared<Module>>;
|
||||
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
self.0.into_iter()
|
||||
|
@ -334,6 +334,30 @@ fn test_get_set_indexer() -> Result<(), Box<EvalAltResult>> {
|
||||
42
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
engine.eval::<INT>(
|
||||
r#"
|
||||
let my_map = new_map();
|
||||
my_map["eggs"] = 41;
|
||||
my_map["eggs"] = my_map["eggs"] + 1;
|
||||
my_map["eggs"]
|
||||
"#,
|
||||
)?,
|
||||
42
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
engine.eval::<INT>(
|
||||
r#"
|
||||
let my_map = new_map();
|
||||
my_map["eggs"] = 41;
|
||||
my_map["eggs"] += 1;
|
||||
my_map["eggs"]
|
||||
"#,
|
||||
)?,
|
||||
42
|
||||
);
|
||||
|
||||
assert!(engine
|
||||
.eval::<INT>(
|
||||
r#"
|
||||
|
Loading…
Reference in New Issue
Block a user