Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Invoke variable resolver for captured variables in closures #863

Merged
merged 1 commit into from
Apr 16, 2024
Merged
Show file tree
Hide file tree
Changes from all 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
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,15 @@
Rhai Release Notes
==================

Version 1.18.1
==============

Bug fixes
---------

* Variable resolver now correctly resolves variables that are captured in a closure.


Version 1.18.0
==============

Expand Down
24 changes: 24 additions & 0 deletions src/eval/stmt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
use crate::tokenizer::Token;
use crate::types::dynamic::{AccessMode, Union};
use crate::{Dynamic, Engine, RhaiResult, RhaiResultOf, Scope, VarDefInfo, ERR, INT};
use core::num::NonZeroUsize;

Check warning on line 11 in src/eval/stmt.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 import: `core::num::NonZeroUsize`

Check warning on line 11 in src/eval/stmt.rs

View workflow job for this annotation

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

unused import: `core::num::NonZeroUsize`

Check warning on line 11 in src/eval/stmt.rs

View workflow job for this annotation

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

unused import: `core::num::NonZeroUsize`

Check warning on line 11 in src/eval/stmt.rs

View workflow job for this annotation

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

unused import: `core::num::NonZeroUsize`
use std::hash::{Hash, Hasher};
#[cfg(feature = "no_std")]
use std::prelude::v1::*;
Expand Down Expand Up @@ -971,6 +972,29 @@
#[cfg(not(feature = "no_closure"))]
Stmt::Share(x) => {
for (var, index) in &**x {
// Check the variable resolver, if any
if let Some(ref resolve_var) = self.resolve_var {
let orig_scope_len = scope.len();

let context =
EvalContext::new(self, global, caches, scope, this_ptr.as_deref_mut());
let resolved_var =
resolve_var(&var.name, index.map_or(0, NonZeroUsize::get), context);

if orig_scope_len != scope.len() {
// The scope is changed, always search from now on
global.always_search_scope = true;
}

match resolved_var {
// If resolved to a variable, skip it (because we cannot make it shared)
Ok(Some(_)) => continue,
Ok(None) => (),
Err(err) => return Err(err.fill_position(var.pos)),
}
}

// Search the scope
let index = index
.map(|n| scope.len() - n.get())
.or_else(|| scope.search(&var.name))
Expand Down
18 changes: 17 additions & 1 deletion tests/var_scope.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use rhai::{Dynamic, Engine, EvalAltResult, Module, ParseErrorType, Position, Scope, INT};

Check warning on line 1 in tests/var_scope.rs

View workflow job for this annotation

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

unused import: `Dynamic`

#[test]
fn test_var_scope() {
Expand Down Expand Up @@ -324,7 +324,7 @@
}

#[test]
fn test_var_resolver() {
fn test_var_resolver1() {
let mut engine = Engine::new();

let mut scope = Scope::new();
Expand Down Expand Up @@ -392,6 +392,22 @@
EvalAltResult::ErrorVariableNotFound(n, ..) if n == "DO_NOT_USE"));
}

#[cfg(not(feature = "no_closure"))]
#[cfg(not(feature = "no_function"))]
#[cfg(not(feature = "no_object"))]
#[test]
fn test_var_resolver2() {
let mut engine = Engine::new();
let shared_state: INT = 42;

#[allow(deprecated)]
engine.on_var(move |name, _, _| if name == "state" { Ok(Some(Dynamic::from(shared_state))) } else { Ok(None) });

assert_eq!(engine.eval::<INT>("state").unwrap(), 42);
assert_eq!(engine.eval::<INT>("fn f() { state }; f()").unwrap(), 42);
assert_eq!(engine.eval::<INT>("let f = || state; f.call()").unwrap(), 42);
}

#[test]
fn test_var_def_filter() {
let mut engine = Engine::new();
Expand Down
Loading