Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
125 changes: 102 additions & 23 deletions src/grain/vm/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,26 +10,25 @@
#[cfg(not(feature = "unchecked"))]
#[cfg(not(all(feature = "no_index", feature = "no_object")))]
use crate::eval::calc_data_sizes;
use crate::eval::{Caches, GlobalRuntimeState};
use crate::func::{get_builtin_binary_op_fn, get_builtin_op_assignment_fn};
use crate::packages::string_basic::print_with_func;
use crate::types::dynamic::DynamicWriteLock;
use crate::types::fn_ptr::FnPtrType;
use crate::types::StringsInterner;
// `Variant` is only re-exported from the crate root under `internals`, so it
// comes from where it is defined.
use crate::ast::Expr;
#[cfg(not(feature = "no_function"))]
use crate::types::dynamic::Variant;
#[cfg(not(feature = "no_index"))]
use crate::Array;
#[cfg(not(feature = "no_function"))]
use crate::CallFnOptions;
#[cfg(not(feature = "no_object"))]
use crate::Map;
#[cfg(not(feature = "no_function"))]
use crate::{types::dynamic::Variant, CallFnOptions};
use crate::{
eval::Caches, eval::GlobalRuntimeState, Dynamic, Engine, EvalAltResult, EvalContext, FnArgsVec,
Scope,
Dynamic, Engine, EvalAltResult, EvalContext, FnArgsVec, FnPtr, ImmutableString,
NativeCallContext, Position, Scope, FUNC_TO_STRING, INT,
};
use crate::{FnPtr, ImmutableString, NativeCallContext, Position, ThinVec, FUNC_TO_STRING, INT};

mod callback;

Expand Down Expand Up @@ -348,6 +347,7 @@
global: GlobalRuntimeState,
caches: Caches,
stack: Vec<Dynamic>,
strings_interner: StringsInterner,
/// One entry per `for` loop currently running.
///
/// Not on the operand stack, because an iterator is not a `Dynamic`. A
Expand Down Expand Up @@ -478,6 +478,7 @@
engine,
global,
caches: Caches::new(),
strings_interner: StringsInterner::new(256),
stack: Vec::new(),
iterators: Vec::new(),
handlers: Vec::new(),
Expand Down Expand Up @@ -518,6 +519,7 @@
engine: context.engine(),
global: context.global_runtime_state().clone(),
caches: Caches::new(),
strings_interner: StringsInterner::new(256),
stack: Vec::new(),
iterators: Vec::new(),
handlers: Vec::new(),
Expand Down Expand Up @@ -1618,6 +1620,83 @@
}
}

// Try to get a property through an indexer.
//
// This requires `no_index` and `no_object` to be off,
// otherwise it just passes the error through.
fn try_index_get(
&mut self,
target: &mut Dynamic,

Check warning on line 1629 in src/grain/vm/mod.rs

View workflow job for this annotation

GitHub Actions / Build (ubuntu-latest, --tests --features testing-environ,no_float,only_i32,no_object,no_index,no_...

unused variable: `target`

Check warning on line 1629 in src/grain/vm/mod.rs

View workflow job for this annotation

GitHub Actions / Build (ubuntu-latest, --features testing-environ,no_time,no_function,no_float,no_position,no_inde...

unused variable: `target`

Check warning on line 1629 in src/grain/vm/mod.rs

View workflow job for this annotation

GitHub Actions / Build (ubuntu-latest, --features testing-environ,no_object,serde,metadata,internals,debugging,gra...

unused variable: `target`

Check warning on line 1629 in src/grain/vm/mod.rs

View workflow job for this annotation

GitHub Actions / Build (ubuntu-latest, --features testing-environ,no_index,serde,metadata,internals,debugging,grai...

unused variable: `target`
key: &str,

Check warning on line 1630 in src/grain/vm/mod.rs

View workflow job for this annotation

GitHub Actions / Build (ubuntu-latest, --tests --features testing-environ,no_float,only_i32,no_object,no_index,no_...

unused variable: `key`

Check warning on line 1630 in src/grain/vm/mod.rs

View workflow job for this annotation

GitHub Actions / Build (ubuntu-latest, --features testing-environ,no_time,no_function,no_float,no_position,no_inde...

unused variable: `key`

Check warning on line 1630 in src/grain/vm/mod.rs

View workflow job for this annotation

GitHub Actions / Build (ubuntu-latest, --features testing-environ,no_object,serde,metadata,internals,debugging,gra...

unused variable: `key`

Check warning on line 1630 in src/grain/vm/mod.rs

View workflow job for this annotation

GitHub Actions / Build (ubuntu-latest, --features testing-environ,no_index,serde,metadata,internals,debugging,grai...

unused variable: `key`
err: Box<EvalAltResult>,
pos: Position,

Check warning on line 1632 in src/grain/vm/mod.rs

View workflow job for this annotation

GitHub Actions / Build (ubuntu-latest, --tests --features testing-environ,no_float,only_i32,no_object,no_index,no_...

unused variable: `pos`

Check warning on line 1632 in src/grain/vm/mod.rs

View workflow job for this annotation

GitHub Actions / Build (ubuntu-latest, --features testing-environ,no_time,no_function,no_float,no_position,no_inde...

unused variable: `pos`

Check warning on line 1632 in src/grain/vm/mod.rs

View workflow job for this annotation

GitHub Actions / Build (ubuntu-latest, --features testing-environ,no_object,serde,metadata,internals,debugging,gra...

unused variable: `pos`

Check warning on line 1632 in src/grain/vm/mod.rs

View workflow job for this annotation

GitHub Actions / Build (ubuntu-latest, --features testing-environ,no_index,serde,metadata,internals,debugging,grai...

unused variable: `pos`
) -> VmResult {
#[cfg(any(feature = "no_index", feature = "no_object"))]
return Err(err);

#[cfg(not(all(feature = "no_index", feature = "no_object")))]
match *err {

Check warning on line 1638 in src/grain/vm/mod.rs

View workflow job for this annotation

GitHub Actions / Build (ubuntu-latest, --features testing-environ,no_object,serde,metadata,internals,debugging,gra...

unreachable expression

Check warning on line 1638 in src/grain/vm/mod.rs

View workflow job for this annotation

GitHub Actions / Build (ubuntu-latest, --features testing-environ,no_index,serde,metadata,internals,debugging,grai...

unreachable expression
EvalAltResult::ErrorDotExpr(..) => {
let mut index = self.strings_interner.get(key).into();
self.engine
.call_indexer_get(&mut self.global, &mut self.caches, target, &mut index, pos)
.map_err(|err2| match *err2 {
EvalAltResult::ErrorIndexingType(..) => err,
_ => positioned(err2, pos),
})
}
_ => Err(err),
}
}

// Try to set a property through an index setter.
//
// This requires `no_index` and `no_object` to be off,
// otherwise it just passes the error through.
fn try_index_set(
&mut self,
target: &mut Dynamic,

Check warning on line 1658 in src/grain/vm/mod.rs

View workflow job for this annotation

GitHub Actions / Build (ubuntu-latest, --tests --features testing-environ,no_float,only_i32,no_object,no_index,no_...

unused variable: `target`

Check warning on line 1658 in src/grain/vm/mod.rs

View workflow job for this annotation

GitHub Actions / Build (ubuntu-latest, --features testing-environ,no_time,no_function,no_float,no_position,no_inde...

unused variable: `target`

Check warning on line 1658 in src/grain/vm/mod.rs

View workflow job for this annotation

GitHub Actions / Build (ubuntu-latest, --features testing-environ,no_object,serde,metadata,internals,debugging,gra...

unused variable: `target`

Check warning on line 1658 in src/grain/vm/mod.rs

View workflow job for this annotation

GitHub Actions / Build (ubuntu-latest, --features testing-environ,no_index,serde,metadata,internals,debugging,grai...

unused variable: `target`
key: &str,

Check warning on line 1659 in src/grain/vm/mod.rs

View workflow job for this annotation

GitHub Actions / Build (ubuntu-latest, --tests --features testing-environ,no_float,only_i32,no_object,no_index,no_...

unused variable: `key`

Check warning on line 1659 in src/grain/vm/mod.rs

View workflow job for this annotation

GitHub Actions / Build (ubuntu-latest, --features testing-environ,no_time,no_function,no_float,no_position,no_inde...

unused variable: `key`

Check warning on line 1659 in src/grain/vm/mod.rs

View workflow job for this annotation

GitHub Actions / Build (ubuntu-latest, --features testing-environ,no_object,serde,metadata,internals,debugging,gra...

unused variable: `key`

Check warning on line 1659 in src/grain/vm/mod.rs

View workflow job for this annotation

GitHub Actions / Build (ubuntu-latest, --features testing-environ,no_index,serde,metadata,internals,debugging,grai...

unused variable: `key`
value: &mut Dynamic,

Check warning on line 1660 in src/grain/vm/mod.rs

View workflow job for this annotation

GitHub Actions / Build (ubuntu-latest, --tests --features testing-environ,no_float,only_i32,no_object,no_index,no_...

unused variable: `value`

Check warning on line 1660 in src/grain/vm/mod.rs

View workflow job for this annotation

GitHub Actions / Build (ubuntu-latest, --features testing-environ,no_time,no_function,no_float,no_position,no_inde...

unused variable: `value`

Check warning on line 1660 in src/grain/vm/mod.rs

View workflow job for this annotation

GitHub Actions / Build (ubuntu-latest, --features testing-environ,no_object,serde,metadata,internals,debugging,gra...

unused variable: `value`

Check warning on line 1660 in src/grain/vm/mod.rs

View workflow job for this annotation

GitHub Actions / Build (ubuntu-latest, --features testing-environ,no_index,serde,metadata,internals,debugging,grai...

unused variable: `value`
fail_silently: bool,

Check warning on line 1661 in src/grain/vm/mod.rs

View workflow job for this annotation

GitHub Actions / Build (ubuntu-latest, --tests --features testing-environ,no_float,only_i32,no_object,no_index,no_...

unused variable: `fail_silently`

Check warning on line 1661 in src/grain/vm/mod.rs

View workflow job for this annotation

GitHub Actions / Build (ubuntu-latest, --features testing-environ,no_time,no_function,no_float,no_position,no_inde...

unused variable: `fail_silently`

Check warning on line 1661 in src/grain/vm/mod.rs

View workflow job for this annotation

GitHub Actions / Build (ubuntu-latest, --features testing-environ,no_object,serde,metadata,internals,debugging,gra...

unused variable: `fail_silently`

Check warning on line 1661 in src/grain/vm/mod.rs

View workflow job for this annotation

GitHub Actions / Build (ubuntu-latest, --features testing-environ,no_index,serde,metadata,internals,debugging,grai...

unused variable: `fail_silently`
err: Box<EvalAltResult>,
pos: Position,

Check warning on line 1663 in src/grain/vm/mod.rs

View workflow job for this annotation

GitHub Actions / Build (ubuntu-latest, --features testing-environ,no_index,serde,metadata,internals,debugging,grai...

unused variable: `pos`
) -> VmResult {
#[cfg(any(feature = "no_index", feature = "no_object"))]
return Err(err);

#[cfg(not(all(feature = "no_index", feature = "no_object")))]
match *err {

Check warning on line 1669 in src/grain/vm/mod.rs

View workflow job for this annotation

GitHub Actions / Build (ubuntu-latest, --features testing-environ,no_object,serde,metadata,internals,debugging,gra...

unreachable expression

Check warning on line 1669 in src/grain/vm/mod.rs

View workflow job for this annotation

GitHub Actions / Build (ubuntu-latest, --features testing-environ,no_index,serde,metadata,internals,debugging,grai...

unreachable expression
EvalAltResult::ErrorDotExpr(..) => {
let mut index = self.strings_interner.get(key).into();
match self
.engine
.call_indexer_set(
&mut self.global,
&mut self.caches,
target,
&mut index,
value,
true,
pos,
)
.map(|_| ())
{
Ok(()) => Ok(Dynamic::UNIT),
Err(err2) if matches!(*err2, EvalAltResult::ErrorIndexingType(..)) => {
if fail_silently {
Ok(Dynamic::UNIT)
} else {
Err(err)
}
}
Err(err2) => Err(positioned(err2, pos)),
}
}
_ => Err(err),
}
}

/// `.name`, which is a key on a map and a getter call on anything else.
///
/// The distinction is Rhai's and it is made at runtime, not at parse time
Expand All @@ -1643,14 +1722,12 @@
setter: u32,
) -> Result<(Dynamic, bool), Box<EvalAltResult>> {
let last = rest.is_empty();
// The key names a map entry; a host type is reached through the getter
// and setter names instead, which are looked up below.
#[cfg(not(feature = "no_object"))]

// The name is a map key for maps, and the same string is what a host
// type's fallback string indexer is addressed with.
let key = program
.name(name)
.ok_or_else(|| malformed(format!("no name {name}")))?;
#[cfg(feature = "no_object")]
let _ = name;

// A map is the one property holder that is not a host type, and
// `no_object` removes both it and the syntax that would reach one.
Expand Down Expand Up @@ -1712,32 +1789,34 @@
// `x.p += 1` has to read `p` back through the getter before it
// can add to it — the setter takes a finished value.
let mut new_val = if matches!(chain.tail, Tail::Assign { op: Some(_) }) {
let mut current = call(self, getter, &mut [target])?;
let mut current = call(self, getter, &mut [target])
.or_else(|err| self.try_index_get(target, key, err, step_pos))?;
self.store(program, chain_op(program, chain)?, &mut current, value, pos)?;
current
} else {
value
};
// A setter's return value is thrown away, as in Rhai.
let _ = call(self, setter, &mut [target, &mut new_val])?;
let _ = call(self, setter, &mut [target, &mut new_val]).or_else(|err| {
self.try_index_set(target, key, &mut new_val, false, err, step_pos)
})?;
return Ok((Dynamic::UNIT, true));
}
let out = call(self, getter, &mut [target])?;
let out = call(self, getter, &mut [target])
.or_else(|err| self.try_index_get(target, key, err, step_pos))?;
return Ok((out, false));
}

// A getter returns a value, so the rest of the chain works on a
// temporary. Rhai puts it back through the setter when the sub-chain
// was a method call, and skips the setter otherwise.
let mut temp = call(self, getter, &mut [target])?;
let mut temp = call(self, getter, &mut [target])
.or_else(|err| self.try_index_get(target, key, err, step_pos))?;
let (out, changed) =
self.walk_chain(program, chain, rest, &mut temp, operands, value, pos)?;
if changed {
let _ = call(self, setter, &mut [target, &mut temp]).or_else(|err| match *err {
// Fail silently if the property is read-only, as Rhai does (`eval/chaining.rs:1039`).
EvalAltResult::ErrorDotExpr(..) => Ok(Dynamic::UNIT),
_ => Err(err),
})?;
let _ = call(self, setter, &mut [target, &mut temp])
.or_else(|err| self.try_index_set(target, key, &mut temp, true, err, step_pos))?;
}
Ok((out, changed))
}
Expand Down Expand Up @@ -2430,7 +2509,7 @@
///
/// First check whether the call is a syntactic one (e.g. `is_def_fn`)
/// which are self-implemented or directly called into the
/// corresponding Rhai functinon.
/// corresponding Rhai function.
///
/// If the call is not to a syntactic one, it calls the function
/// normally, with arguments pushed onto the stack.
Expand Down Expand Up @@ -3971,7 +4050,7 @@
self.stack.push(
FnPtr {
name: name.into(),
curry: ThinVec::new(),
curry: Default::default(),
#[cfg(not(feature = "no_function"))]
env: None,
typ: FnPtrType::Normal,
Expand Down
1 change: 1 addition & 0 deletions tests/get_set.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ fn test_get_set() {

assert_eq!(engine.eval::<INT>(r"let a = new_ts(); a.abc").unwrap(), 4);
assert_eq!(engine.eval::<INT>(r"let a = new_ts(); a.abc = 42; a.abc").unwrap(), 42);
assert_eq!(engine.eval::<INT>(r"let a = new_ts(); a.abc += 10; a.abc").unwrap(), 14);
}

#[test]
Expand Down
25 changes: 25 additions & 0 deletions tests/grain/corpus/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,19 @@ pub fn engine() -> rhai::Engine {
Ok(())
},
);
#[cfg(not(all(feature = "no_index", feature = "no_object")))]
engine.register_indexer_get_set(
|w: &mut Widget, name: &str| -> Result<INT, Box<rhai::EvalAltResult>> {
let index = name.len() as INT - 1;
w.cells.get(index as usize).copied().ok_or_else(|| out_of_range(index, w.cells.len()))
},
|w: &mut Widget, name: &str, v: INT| -> Result<(), Box<rhai::EvalAltResult>> {
let index = name.len() as INT - 1;
let len = w.cells.len();
*w.cells.get_mut(index as usize).ok_or_else(|| out_of_range(index, len))? = v;
Ok(())
},
);

#[cfg(not(feature = "no_object"))]
{
Expand Down Expand Up @@ -247,7 +260,11 @@ pub fn applies_to_this_build(name: &str) -> bool {
| "host_index_set"
| "host_mutation_before_a_failure_survives_in_an_array"
| "host_index_temp_set"
| "host_string_index_property_get_fallback"
| "host_string_index_property_set_fallback"
| "host_string_index_property_op_assign_fallback"
| "host_temp_index_set"
| "host_temp_string_index_property_set_fallback"
| "index_assign_array"
| "index_assign_nested"
| "index_expression_reads_the_root"
Expand Down Expand Up @@ -328,6 +345,10 @@ pub fn applies_to_this_build(name: &str) -> bool {
| "fn_ptr_curried"
| "fn_ptr_from_dynamic_name"
| "fn_ptr_to_native"
| "host_string_index_property_get_fallback"
| "host_string_index_property_set_fallback"
| "host_string_index_property_op_assign_fallback"
| "host_temp_string_index_property_set_fallback"
| "index_assign_nested"
| "index_expression_reads_the_root"
| "interpolation_of_containers"
Expand Down Expand Up @@ -750,9 +771,13 @@ pub const CASES: &[Case] = &[
case("host_index_set", "let w = widget(1); w[1] = 99; w[1]"),
case("host_method_mutates", "let w = widget(4); w.bump(); w.level"),
case("host_method_pure", "let w = widget(4); w.doubled()"),
case("host_string_index_property_get_fallback", "let w = widget(1); w.a"),
case("host_string_index_property_set_fallback", "let w = widget(1); w.ab = 77; w.ab"),
case("host_string_index_property_op_assign_fallback", "let w = widget(1); w.a += 5; w.a"),
// Two levels, so the middle one is a temporary.
case("host_temp_set", "let h = holder(3); h.inner.level = 8; h.inner.level"),
case("host_temp_index_set", "let h = holder(3); h.inner[0] = 7; h.inner[0]"),
case("host_temp_string_index_property_set_fallback", "let h = holder(3); h.inner.ab = 7; h.inner.ab"),
// The mirror of it: an *index* step handing back the temporary, with the
// property below. The index has to survive the getter to address the setter
// with afterwards.
Expand Down
Loading