Merge pull request #604 from schungx/master
Add function pointer short-hand.
This commit is contained in:
commit
eaef24b679
@ -30,6 +30,10 @@ New features
|
|||||||
|
|
||||||
* An API is added to automatically generate definition files from a fully-configured `Engine`, for use with the Rhai Language Server.
|
* An API is added to automatically generate definition files from a fully-configured `Engine`, for use with the Rhai Language Server.
|
||||||
|
|
||||||
|
### Short-hand to function pointers
|
||||||
|
|
||||||
|
* Using a script-defined function's name (in place of a variable) implicitly creates a function pointer to the function.
|
||||||
|
|
||||||
Enhancements
|
Enhancements
|
||||||
------------
|
------------
|
||||||
|
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "rhai_codegen"
|
name = "rhai_codegen"
|
||||||
version = "1.4.1"
|
version = "1.4.2"
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
|
resolver = "2"
|
||||||
authors = ["jhwgh1968", "Stephen Chung"]
|
authors = ["jhwgh1968", "Stephen Chung"]
|
||||||
description = "Procedural macros support package for Rhai, a scripting language and engine for Rust"
|
description = "Procedural macros support package for Rhai, a scripting language and engine for Rust"
|
||||||
homepage = "https://rhai.rs/book/plugins/index.html"
|
homepage = "https://rhai.rs/book/plugins/index.html"
|
||||||
@ -15,11 +16,11 @@ proc-macro = true
|
|||||||
default = []
|
default = []
|
||||||
metadata = []
|
metadata = []
|
||||||
|
|
||||||
[dev-dependencies]
|
|
||||||
rhai = { path = "..", version = "1.6", features = ["metadata"] }
|
|
||||||
trybuild = "1"
|
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
proc-macro2 = "1"
|
proc-macro2 = "1"
|
||||||
syn = { version = "1.0", features = ["full", "parsing", "printing", "proc-macro", "extra-traits"] }
|
syn = { version = "1.0", features = ["full", "parsing", "printing", "proc-macro", "extra-traits"] }
|
||||||
quote = "1"
|
quote = "1"
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
rhai = { path = "..", version = "1.6", features = ["metadata"] }
|
||||||
|
trybuild = "1"
|
||||||
|
@ -5208,6 +5208,16 @@ fn tan(x: float) -> float;
|
|||||||
fn tanh(x: float) -> float;
|
fn tanh(x: float) -> float;
|
||||||
|
|
||||||
/// Create a timestamp containing the current system time.
|
/// Create a timestamp containing the current system time.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```rhai
|
||||||
|
/// let now = timestamp();
|
||||||
|
///
|
||||||
|
/// sleep(10.0); // sleep for 10 seconds
|
||||||
|
///
|
||||||
|
/// print(now.elapsed); // prints 10.???
|
||||||
|
/// ```
|
||||||
fn timestamp() -> Instant;
|
fn timestamp() -> Instant;
|
||||||
|
|
||||||
/// Convert the BLOB into an array of integers.
|
/// Convert the BLOB into an array of integers.
|
||||||
|
@ -1,170 +0,0 @@
|
|||||||
/// This definition file extends the scope of all scripts.
|
|
||||||
///
|
|
||||||
/// The items defined here simply exist and are available.
|
|
||||||
/// everywhere.
|
|
||||||
///
|
|
||||||
/// These definitions should be used for built-in functions and
|
|
||||||
/// local domain-specific environment-provided values.
|
|
||||||
module static;
|
|
||||||
|
|
||||||
/// Display any data to the standard output.
|
|
||||||
///
|
|
||||||
/// # Example
|
|
||||||
///
|
|
||||||
/// ```rhai
|
|
||||||
/// let answer = 42;
|
|
||||||
///
|
|
||||||
/// print(`The Answer is ${answer}`);
|
|
||||||
/// ```
|
|
||||||
fn print(data: ?);
|
|
||||||
|
|
||||||
/// Display any data to the standard output in debug format.
|
|
||||||
///
|
|
||||||
/// # Example
|
|
||||||
///
|
|
||||||
/// ```rhai
|
|
||||||
/// let answer = 42;
|
|
||||||
///
|
|
||||||
/// debug(answer);
|
|
||||||
/// ```
|
|
||||||
fn debug(data: ?);
|
|
||||||
|
|
||||||
/// Get the type of a value.
|
|
||||||
///
|
|
||||||
/// # Example
|
|
||||||
///
|
|
||||||
/// ```rhai
|
|
||||||
/// let x = "hello, world!";
|
|
||||||
///
|
|
||||||
/// print(x.type_of()); // prints "string"
|
|
||||||
/// ```
|
|
||||||
fn type_of(data: ?) -> String;
|
|
||||||
|
|
||||||
/// Create a function pointer to a named function.
|
|
||||||
///
|
|
||||||
/// If the specified name is not a valid function name, an error is raised.
|
|
||||||
///
|
|
||||||
/// # Example
|
|
||||||
///
|
|
||||||
/// ```rhai
|
|
||||||
/// let f = Fn("foo"); // function pointer to 'foo'
|
|
||||||
///
|
|
||||||
/// f.call(42); // call: foo(42)
|
|
||||||
/// ```
|
|
||||||
fn Fn(fn_name: String) -> FnPtr;
|
|
||||||
|
|
||||||
/// Call a function pointed to by a function pointer,
|
|
||||||
/// passing following arguments to the function call.
|
|
||||||
///
|
|
||||||
/// If an appropriate function is not found, an error is raised.
|
|
||||||
///
|
|
||||||
/// # Example
|
|
||||||
///
|
|
||||||
/// ```rhai
|
|
||||||
/// let f = Fn("foo"); // function pointer to 'foo'
|
|
||||||
///
|
|
||||||
/// f.call(1, 2, 3); // call: foo(1, 2, 3)
|
|
||||||
/// ```
|
|
||||||
fn call(fn_ptr: FnPtr, ...args: ?) -> ?;
|
|
||||||
|
|
||||||
/// Call a function pointed to by a function pointer, binding the `this` pointer
|
|
||||||
/// to the object of the method call, and passing on following arguments to the function call.
|
|
||||||
///
|
|
||||||
/// If an appropriate function is not found, an error is raised.
|
|
||||||
///
|
|
||||||
/// # Example
|
|
||||||
///
|
|
||||||
/// ```rhai
|
|
||||||
/// fn add(x) {
|
|
||||||
/// this + x
|
|
||||||
/// }
|
|
||||||
///
|
|
||||||
/// let f = Fn("add"); // function pointer to 'add'
|
|
||||||
///
|
|
||||||
/// let x = 41;
|
|
||||||
///
|
|
||||||
/// let r = x.call(f, 1); // call: add(1) with 'this' = 'x'
|
|
||||||
///
|
|
||||||
/// print(r); // prints 42
|
|
||||||
/// ```
|
|
||||||
fn call(obj: ?, fn_ptr: FnPtr, ...args: ?) -> ?;
|
|
||||||
|
|
||||||
/// Curry a number of arguments into a function pointer and return it as a new function pointer.
|
|
||||||
///
|
|
||||||
/// # Example
|
|
||||||
///
|
|
||||||
/// ```rhai
|
|
||||||
/// fn foo(x, y, z) {
|
|
||||||
/// x + y + z
|
|
||||||
/// }
|
|
||||||
///
|
|
||||||
/// let f = Fn("foo");
|
|
||||||
///
|
|
||||||
/// let g = f.curry(1, 2); // curried arguments: 1, 2
|
|
||||||
///
|
|
||||||
/// g.call(3); // call: foo(1, 2, 3)
|
|
||||||
/// ```
|
|
||||||
fn curry(fn_ptr: FnPtr, ...args: ?) -> FnPtr;
|
|
||||||
|
|
||||||
/// Return `true` if a script-defined function exists with a specified name and
|
|
||||||
/// number of parameters.
|
|
||||||
///
|
|
||||||
/// # Example
|
|
||||||
///
|
|
||||||
/// ```rhai
|
|
||||||
/// fn foo(x) { }
|
|
||||||
///
|
|
||||||
/// print(is_def_fn("foo", 1)); // prints true
|
|
||||||
/// print(is_def_fn("foo", 2)); // prints false
|
|
||||||
/// print(is_def_fn("foo", 0)); // prints false
|
|
||||||
/// print(is_def_fn("bar", 1)); // prints false
|
|
||||||
/// ```
|
|
||||||
fn is_def_fn(fn_name: String, num_params: i64) -> bool;
|
|
||||||
|
|
||||||
/// Return `true` if a variable matching a specified name is defined.
|
|
||||||
///
|
|
||||||
/// # Example
|
|
||||||
///
|
|
||||||
/// ```rhai
|
|
||||||
/// let x = 42;
|
|
||||||
///
|
|
||||||
/// print(is_def_var("x")); // prints true
|
|
||||||
/// print(is_def_var("foo")); // prints false
|
|
||||||
///
|
|
||||||
/// {
|
|
||||||
/// let y = 1;
|
|
||||||
/// print(is_def_var("y")); // prints true
|
|
||||||
/// }
|
|
||||||
///
|
|
||||||
/// print(is_def_var("y")); // prints false
|
|
||||||
/// ```
|
|
||||||
fn is_def_var(var_name: String) -> bool;
|
|
||||||
|
|
||||||
/// Return `true` if the variable is shared.
|
|
||||||
///
|
|
||||||
/// # Example
|
|
||||||
///
|
|
||||||
/// ```rhai
|
|
||||||
/// let x = 42;
|
|
||||||
///
|
|
||||||
/// print(is_shared(x)); // prints false
|
|
||||||
///
|
|
||||||
/// let f = || x; // capture 'x', making it shared
|
|
||||||
///
|
|
||||||
/// print(is_shared(x)); // prints true
|
|
||||||
/// ```
|
|
||||||
fn is_shared(variable: ?) -> bool;
|
|
||||||
|
|
||||||
/// Evaluate a text script within the current scope.
|
|
||||||
///
|
|
||||||
/// # Example
|
|
||||||
///
|
|
||||||
/// ```rhai
|
|
||||||
/// let x = 42;
|
|
||||||
///
|
|
||||||
/// eval("let y = x; x = 123;");
|
|
||||||
///
|
|
||||||
/// print(x); // prints 123
|
|
||||||
/// print(y); // prints 42
|
|
||||||
/// ```
|
|
||||||
fn eval(script: String) -> ?;
|
|
@ -57,7 +57,6 @@ impl Engine {
|
|||||||
}
|
}
|
||||||
/// Set whether `if`-expression is allowed.
|
/// Set whether `if`-expression is allowed.
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
#[must_use]
|
|
||||||
pub fn set_allow_if_expression(&mut self, enable: bool) {
|
pub fn set_allow_if_expression(&mut self, enable: bool) {
|
||||||
self.options.set(LangOptions::IF_EXPR, enable);
|
self.options.set(LangOptions::IF_EXPR, enable);
|
||||||
}
|
}
|
||||||
|
@ -149,6 +149,18 @@ impl Engine {
|
|||||||
}
|
}
|
||||||
_ if global.always_search_scope => (0, expr.start_position()),
|
_ if global.always_search_scope => (0, expr.start_position()),
|
||||||
Expr::Variable(.., Some(i), pos) => (i.get() as usize, *pos),
|
Expr::Variable(.., Some(i), pos) => (i.get() as usize, *pos),
|
||||||
|
// Scripted function with the same name
|
||||||
|
#[cfg(not(feature = "no_function"))]
|
||||||
|
Expr::Variable(v, None, pos)
|
||||||
|
if lib
|
||||||
|
.iter()
|
||||||
|
.flat_map(|&m| m.iter_script_fn())
|
||||||
|
.any(|(_, _, f, ..)| f == v.3) =>
|
||||||
|
{
|
||||||
|
let val: Dynamic =
|
||||||
|
crate::FnPtr::new_unchecked(v.3.as_str(), Default::default()).into();
|
||||||
|
return Ok((val.into(), *pos));
|
||||||
|
}
|
||||||
Expr::Variable(v, None, pos) => (v.0.map_or(0, NonZeroUsize::get), *pos),
|
Expr::Variable(v, None, pos) => (v.0.map_or(0, NonZeroUsize::get), *pos),
|
||||||
_ => unreachable!("Expr::Variable expected but gets {:?}", expr),
|
_ => unreachable!("Expr::Variable expected but gets {:?}", expr),
|
||||||
};
|
};
|
||||||
|
@ -244,12 +244,12 @@ impl Engine {
|
|||||||
// We shouldn't do this for too many variants because, soon or later, the added comparisons
|
// We shouldn't do this for too many variants because, soon or later, the added comparisons
|
||||||
// will cost more than the mis-predicted `match` branch.
|
// will cost more than the mis-predicted `match` branch.
|
||||||
if let Stmt::Assignment(x, ..) = stmt {
|
if let Stmt::Assignment(x, ..) = stmt {
|
||||||
|
let (op_info, BinaryExpr { lhs, rhs }) = &**x;
|
||||||
|
|
||||||
#[cfg(not(feature = "unchecked"))]
|
#[cfg(not(feature = "unchecked"))]
|
||||||
self.inc_operations(&mut global.num_operations, stmt.position())?;
|
self.inc_operations(&mut global.num_operations, stmt.position())?;
|
||||||
|
|
||||||
let result = if x.1.lhs.is_variable_access(false) {
|
let result = if let Expr::Variable(x, ..) = lhs {
|
||||||
let (op_info, BinaryExpr { lhs, rhs }) = &**x;
|
|
||||||
|
|
||||||
let rhs_result = self
|
let rhs_result = self
|
||||||
.eval_expr(scope, global, caches, lib, this_ptr, rhs, level)
|
.eval_expr(scope, global, caches, lib, this_ptr, rhs, level)
|
||||||
.map(Dynamic::flatten);
|
.map(Dynamic::flatten);
|
||||||
@ -261,7 +261,7 @@ impl Engine {
|
|||||||
if let Ok(search_val) = search_result {
|
if let Ok(search_val) = search_result {
|
||||||
let (mut lhs_ptr, pos) = search_val;
|
let (mut lhs_ptr, pos) = search_val;
|
||||||
|
|
||||||
let var_name = lhs.get_variable_name(false).expect("`Expr::Variable`");
|
let var_name = x.3.as_str();
|
||||||
|
|
||||||
#[cfg(not(feature = "no_closure"))]
|
#[cfg(not(feature = "no_closure"))]
|
||||||
// Also handle case where target is a `Dynamic` shared value
|
// Also handle case where target is a `Dynamic` shared value
|
||||||
|
@ -26,6 +26,16 @@ def_package! {
|
|||||||
#[export_module]
|
#[export_module]
|
||||||
mod time_functions {
|
mod time_functions {
|
||||||
/// Create a timestamp containing the current system time.
|
/// Create a timestamp containing the current system time.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```rhai
|
||||||
|
/// let now = timestamp();
|
||||||
|
///
|
||||||
|
/// sleep(10.0); // sleep for 10 seconds
|
||||||
|
///
|
||||||
|
/// print(now.elapsed); // prints 10.???
|
||||||
|
/// ```
|
||||||
pub fn timestamp() -> Instant {
|
pub fn timestamp() -> Instant {
|
||||||
Instant::now()
|
Instant::now()
|
||||||
}
|
}
|
||||||
|
@ -159,17 +159,34 @@ impl<'e> ParseState<'e> {
|
|||||||
/// The return value is the offset to be deducted from `ParseState::stack::len()`,
|
/// The return value is the offset to be deducted from `ParseState::stack::len()`,
|
||||||
/// i.e. the top element of [`ParseState`]'s variables stack is offset 1.
|
/// i.e. the top element of [`ParseState`]'s variables stack is offset 1.
|
||||||
///
|
///
|
||||||
/// Return `None` when the variable name is not found in the `stack`.
|
/// # Return value: `(index, is_func)`
|
||||||
|
///
|
||||||
|
/// * `index`: `None` when the variable name is not found in the `stack`,
|
||||||
|
/// otherwise the index value.
|
||||||
|
///
|
||||||
|
/// * `is_func`: `true` if the variable is actually the name of a function
|
||||||
|
/// (in which case it will be converted into a function pointer).
|
||||||
#[inline]
|
#[inline]
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn access_var(&mut self, name: &str, pos: Position) -> Option<NonZeroUsize> {
|
pub fn access_var(
|
||||||
|
&mut self,
|
||||||
|
name: &str,
|
||||||
|
lib: &FnLib,
|
||||||
|
pos: Position,
|
||||||
|
) -> (Option<NonZeroUsize>, bool) {
|
||||||
let _pos = pos;
|
let _pos = pos;
|
||||||
|
|
||||||
let (index, hit_barrier) = self.find_var(name);
|
let (index, hit_barrier) = self.find_var(name);
|
||||||
|
|
||||||
|
#[cfg(not(feature = "no_function"))]
|
||||||
|
let is_func = lib.values().any(|f| f.name == name);
|
||||||
|
|
||||||
|
#[cfg(feature = "no_function")]
|
||||||
|
let is_func = false;
|
||||||
|
|
||||||
#[cfg(not(feature = "no_closure"))]
|
#[cfg(not(feature = "no_closure"))]
|
||||||
if self.allow_capture {
|
if self.allow_capture {
|
||||||
if index == 0 && !self.external_vars.iter().any(|v| v.as_str() == name) {
|
if !is_func && index == 0 && !self.external_vars.iter().any(|v| v.as_str() == name) {
|
||||||
self.external_vars.push(crate::ast::Ident {
|
self.external_vars.push(crate::ast::Ident {
|
||||||
name: name.into(),
|
name: name.into(),
|
||||||
pos: _pos,
|
pos: _pos,
|
||||||
@ -179,11 +196,13 @@ impl<'e> ParseState<'e> {
|
|||||||
self.allow_capture = true;
|
self.allow_capture = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if hit_barrier {
|
let index = if hit_barrier {
|
||||||
None
|
None
|
||||||
} else {
|
} else {
|
||||||
NonZeroUsize::new(index)
|
NonZeroUsize::new(index)
|
||||||
}
|
};
|
||||||
|
|
||||||
|
(index, is_func)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Find a module by name in the [`ParseState`], searching in reverse.
|
/// Find a module by name in the [`ParseState`], searching in reverse.
|
||||||
@ -1366,12 +1385,13 @@ impl Engine {
|
|||||||
#[cfg(not(feature = "no_closure"))]
|
#[cfg(not(feature = "no_closure"))]
|
||||||
new_state.external_vars.iter().try_for_each(
|
new_state.external_vars.iter().try_for_each(
|
||||||
|crate::ast::Ident { name, pos }| {
|
|crate::ast::Ident { name, pos }| {
|
||||||
let index = state.access_var(name, *pos);
|
let (index, is_func) = state.access_var(name, lib, *pos);
|
||||||
|
|
||||||
if settings.options.contains(LangOptions::STRICT_VAR)
|
if settings.options.contains(LangOptions::STRICT_VAR)
|
||||||
&& !settings.is_closure_scope
|
&& !settings.is_closure_scope
|
||||||
&& index.is_none()
|
&& index.is_none()
|
||||||
&& !state.scope.contains(name)
|
&& !state.scope.contains(name)
|
||||||
|
&& !is_func
|
||||||
{
|
{
|
||||||
// If the parent scope is not inside another capturing closure
|
// If the parent scope is not inside another capturing closure
|
||||||
// then we can conclude that the captured variable doesn't exist.
|
// then we can conclude that the captured variable doesn't exist.
|
||||||
@ -1512,11 +1532,12 @@ impl Engine {
|
|||||||
}
|
}
|
||||||
// Normal variable access
|
// Normal variable access
|
||||||
_ => {
|
_ => {
|
||||||
let index = state.access_var(&s, settings.pos);
|
let (index, is_func) = state.access_var(&s, lib, settings.pos);
|
||||||
|
|
||||||
if settings.options.contains(LangOptions::STRICT_VAR)
|
if settings.options.contains(LangOptions::STRICT_VAR)
|
||||||
&& index.is_none()
|
&& index.is_none()
|
||||||
&& !state.scope.contains(&s)
|
&& !state.scope.contains(&s)
|
||||||
|
&& !is_func
|
||||||
{
|
{
|
||||||
return Err(
|
return Err(
|
||||||
PERR::VariableUndefined(s.to_string()).into_err(settings.pos)
|
PERR::VariableUndefined(s.to_string()).into_err(settings.pos)
|
||||||
|
@ -1,9 +1,7 @@
|
|||||||
//! Implement deserialization support of [`Dynamic`][crate::Dynamic] for [`serde`].
|
//! Implement deserialization support of [`Dynamic`][crate::Dynamic] for [`serde`].
|
||||||
|
|
||||||
use crate::types::dynamic::Union;
|
use crate::types::dynamic::Union;
|
||||||
use crate::{
|
use crate::{Dynamic, ImmutableString, LexError, Position, RhaiError, RhaiResultOf, ERR};
|
||||||
Dynamic, ImmutableString, LexError, Position, RhaiError, RhaiResultOf, SmartString, ERR,
|
|
||||||
};
|
|
||||||
use serde::de::{Error, IntoDeserializer, Visitor};
|
use serde::de::{Error, IntoDeserializer, Visitor};
|
||||||
use serde::{Deserialize, Deserializer};
|
use serde::{Deserialize, Deserializer};
|
||||||
#[cfg(feature = "no_std")]
|
#[cfg(feature = "no_std")]
|
||||||
@ -422,7 +420,7 @@ impl<'de> Deserializer<'de> for &mut DynamicDeserializer<'de> {
|
|||||||
|| self.type_error(),
|
|| self.type_error(),
|
||||||
|map| {
|
|map| {
|
||||||
_visitor.visit_map(IterateMap::new(
|
_visitor.visit_map(IterateMap::new(
|
||||||
map.keys().map(SmartString::as_str),
|
map.keys().map(crate::SmartString::as_str),
|
||||||
map.values(),
|
map.values(),
|
||||||
))
|
))
|
||||||
},
|
},
|
||||||
|
@ -186,7 +186,7 @@ fn test_fn_ptr_raw() -> Result<(), Box<EvalAltResult>> {
|
|||||||
fn foo(x) { this += x; }
|
fn foo(x) { this += x; }
|
||||||
|
|
||||||
let x = 41;
|
let x = 41;
|
||||||
x.bar(Fn("foo"), 1);
|
x.bar(foo, 1);
|
||||||
x
|
x
|
||||||
"#
|
"#
|
||||||
)?,
|
)?,
|
||||||
|
@ -77,6 +77,20 @@ fn test_fn_ptr() -> Result<(), Box<EvalAltResult>> {
|
|||||||
if fn_name == "foo" && matches!(*err, EvalAltResult::ErrorUnboundThis(..))
|
if fn_name == "foo" && matches!(*err, EvalAltResult::ErrorUnboundThis(..))
|
||||||
));
|
));
|
||||||
|
|
||||||
|
#[cfg(not(feature = "no_function"))]
|
||||||
|
assert_eq!(
|
||||||
|
engine.eval::<INT>(
|
||||||
|
r#"
|
||||||
|
fn foo(x) { x + 1 }
|
||||||
|
let f = foo;
|
||||||
|
let g = 42;
|
||||||
|
g = foo;
|
||||||
|
call(f, 39) + call(g, 1)
|
||||||
|
"#
|
||||||
|
)?,
|
||||||
|
42
|
||||||
|
);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -104,6 +104,10 @@ fn test_options_strict_var() -> Result<(), Box<EvalAltResult>> {
|
|||||||
|
|
||||||
#[cfg(not(feature = "no_function"))]
|
#[cfg(not(feature = "no_function"))]
|
||||||
{
|
{
|
||||||
|
assert_eq!(
|
||||||
|
engine.eval_with_scope::<INT>(&mut scope, "fn foo(z) { z } let f = foo; call(f, x)")?,
|
||||||
|
42
|
||||||
|
);
|
||||||
assert!(engine.compile("let f = |y| x * y;").is_err());
|
assert!(engine.compile("let f = |y| x * y;").is_err());
|
||||||
#[cfg(not(feature = "no_closure"))]
|
#[cfg(not(feature = "no_closure"))]
|
||||||
{
|
{
|
||||||
|
Loading…
Reference in New Issue
Block a user