Rename downcast to try_cast and add cast for Dynamic.

This commit is contained in:
Stephen Chung 2020-04-02 12:35:44 +08:00
parent c4a51b1390
commit 5e7c9b47d5
6 changed files with 169 additions and 115 deletions

View File

@ -279,7 +279,6 @@ The following primitive types are supported natively:
| **System floating-point** (current configuration, disabled with [`no_float`]) | `rhai::FLOAT` (`f32` or `f64`) | `"f32"` or `"f64"` | `"123.456"` etc. | | **System floating-point** (current configuration, disabled with [`no_float`]) | `rhai::FLOAT` (`f32` or `f64`) | `"f32"` or `"f64"` | `"123.456"` etc. |
| **Nothing/void/nil/null** (or whatever you want to call it) | `()` | `"()"` | `""` _(empty string)_ | | **Nothing/void/nil/null** (or whatever you want to call it) | `()` | `"()"` | `""` _(empty string)_ |
[`Dynamic`]: #values-and-types
[`()`]: #values-and-types [`()`]: #values-and-types
All types are treated strictly separate by Rhai, meaning that `i32` and `i64` and `u32` are completely different - they even cannot be added together. This is very similar to Rust. All types are treated strictly separate by Rhai, meaning that `i32` and `i64` and `u32` are completely different - they even cannot be added together. This is very similar to Rust.
@ -293,7 +292,7 @@ If no floating-point is needed or supported, use the [`no_float`] feature to rem
The `to_string` function converts a standard type into a string for display purposes. The `to_string` function converts a standard type into a string for display purposes.
The `type_of` function detects the actual type of a value. This is useful because all variables are `Dynamic`. The `type_of` function detects the actual type of a value. This is useful because all variables are [`Dynamic`] in nature.
```rust ```rust
// Use 'type_of()' to get the actual types of values // Use 'type_of()' to get the actual types of values
@ -313,6 +312,74 @@ if type_of(x) == "string" {
} }
``` ```
Dynamic values
--------------
[`Dynamic`]: #dynamic-values
A `Dynamic` value can be _any_ type.
Because [`type_of()`] a `Dynamic` value returns the type of the actual value, it is usually used to perform type-specific
actions based on the actual value's type.
```rust
let mystery = get_some_dynamic_value();
if type_of(mystery) == "i64" {
print("Hey, I got an integer here!");
} else if type_of(mystery) == "f64" {
print("Hey, I got a float here!");
} else if type_of(mystery) == "string" {
print("Hey, I got a string here!");
} else if type_of(mystery) == "bool" {
print("Hey, I got a boolean here!");
} else if type_of(mystery) == "array" {
print("Hey, I got an array here!");
} else if type_of(mystery) == "map" {
print("Hey, I got an object map here!");
} else if type_of(mystery) == "TestStruct" {
print("Hey, I got the TestStruct custom type here!");
} else {
print("I don't know what this is: " + type_of(mystery));
}
```
In Rust, sometimes a `Dynamic` forms part of the return value - a good example is elements within an `Array` which are `Dynamic`,
or property values in an object map. In order to get the _real_ value, the actual value type _must_ be known in advance.
There is no easy way for Rust to detect, at run-time, what type the `Dynamic` value is (short of using the `type_name`
function to get the textual name of the type and then matching on that).
To use a `Dynamic` value in Rust, use the `cast` method to convert the value into a specific, known type.
Alternatively, use the `try_cast` method which does not panic but returns an error when the cast fails.
```rust
use rhai::AnyExt; // Pull in the trait.
let list: Array = engine.eval("...")?; // return type is 'Array'
let item = list[0]; // an element in an 'Array' is 'Dynamic'
let value = item.cast::<i64>(); // if the element is 'i64', this succeeds; otherwise it panics
let value: i64 = item.cast(); // type can also be inferred
let value = item.try_cast::<i64>()?; // 'try_cast' does not panic when the cast fails, but returns an error
```
The `type_name` method gets the name of the actual type as a string, which you may match against.
```rust
use rhai::Any; // Pull in the trait.
let list: Array = engine.eval("...")?; // return type is 'Array'
let item = list[0]; // an element in an 'Array' is 'Dynamic'
match item.type_name() { // 'type_name' returns the name of the actual Rust type
"i64" => ...
"std::string::String" => ...
"bool" => ...
"path::to::module::TestStruct" => ...
}
```
Value conversions Value conversions
----------------- -----------------
@ -972,10 +1039,9 @@ Arrays
Arrays are first-class citizens in Rhai. Like C, arrays are accessed with zero-based, non-negative integer indices. Arrays are first-class citizens in Rhai. Like C, arrays are accessed with zero-based, non-negative integer indices.
Array literals are built within square brackets '`[`' ... '`]`' and separated by commas '`,`'. Array literals are built within square brackets '`[`' ... '`]`' and separated by commas '`,`'.
All elements stored in an array are [`Dynamic`], and the array can freely grow or shrink with elements added or removed.
The Rust type of a Rhai array is `rhai::Array`. The Rust type of a Rhai array is `rhai::Array`. [`type_of()`] an array returns `"array"`.
[`type_of()`] an array returns `"array"`.
Arrays are disabled via the [`no_index`] feature. Arrays are disabled via the [`no_index`] feature.
@ -1053,7 +1119,7 @@ engine.register_fn("push", |list: &mut Array, item: MyType| list.push(Box::new(i
Object maps Object maps
----------- -----------
Object maps are dictionaries. Properties of any type (`Dynamic`) can be freely added and retrieved. Object maps are dictionaries. Properties are all [`Dynamic`] and can be freely added and retrieved.
Object map literals are built within braces '`#{`' ... '`}`' (_name_ `:` _value_ syntax similar to Rust) Object map literals are built within braces '`#{`' ... '`}`' (_name_ `:` _value_ syntax similar to Rust)
and separated by commas '`,`'. The property _name_ can be a simple variable name following the same and separated by commas '`,`'. The property _name_ can be a simple variable name following the same
naming rules as [variables], or an arbitrary string literal. naming rules as [variables], or an arbitrary string literal.
@ -1064,9 +1130,7 @@ The index notation allows setting/getting properties of arbitrary names (even th
**Important:** Trying to read a non-existent property returns `()` instead of causing an error. **Important:** Trying to read a non-existent property returns `()` instead of causing an error.
The Rust type of a Rhai object map is `rhai::Map`. The Rust type of a Rhai object map is `rhai::Map`. [`type_of()`] an object map returns `"map"`.
[`type_of()`] an object map returns `"map"`.
Object maps are disabled via the [`no_object`] feature. Object maps are disabled via the [`no_object`] feature.
@ -1642,9 +1706,9 @@ Function volatility considerations
--------------------------------- ---------------------------------
Even if a custom function does not mutate state nor cause side effects, it may still be _volatile_, i.e. it _depends_ on the external Even if a custom function does not mutate state nor cause side effects, it may still be _volatile_, i.e. it _depends_ on the external
environment and not _pure_. A perfect example is a function that gets the current time - obviously each run will return a different value! environment and is not _pure_. A perfect example is a function that gets the current time - obviously each run will return a different value!
The optimizer, when using [`OptimizationLevel::Full`], _assumes_ that all functions are _pure_, so when it finds constant arguments The optimizer, when using [`OptimizationLevel::Full`], _assumes_ that all functions are _pure_, so when it finds constant arguments
it will eagerly run execute the function call. This causes the script to behave differently from the intended semantics because it will eagerly execute the function call. This causes the script to behave differently from the intended semantics because
essentially the result of the function call will always be the same value. essentially the result of the function call will always be the same value.
Therefore, **avoid using [`OptimizationLevel::Full`]** if you intend to register non-_pure_ custom types and/or functions. Therefore, **avoid using [`OptimizationLevel::Full`]** if you intend to register non-_pure_ custom types and/or functions.

View File

@ -89,7 +89,14 @@ impl Clone for Dynamic {
/// An extension trait that allows down-casting a `Dynamic` value to a specific type. /// An extension trait that allows down-casting a `Dynamic` value to a specific type.
pub trait AnyExt: Sized { pub trait AnyExt: Sized {
/// Get a copy of a `Dynamic` value as a specific type. /// Get a copy of a `Dynamic` value as a specific type.
fn downcast<T: Any + Clone>(self) -> Result<Box<T>, Self>; fn try_cast<T: Any + Clone>(self) -> Result<T, Self>;
/// Get a copy of a `Dynamic` value as a specific type.
///
/// # Panics
///
/// Panics if the cast fails (e.g. the type of the actual value is not the same as the specified type).
fn cast<T: Any + Clone>(self) -> T;
/// This trait may only be implemented by `rhai`. /// This trait may only be implemented by `rhai`.
#[doc(hidden)] #[doc(hidden)]
@ -106,19 +113,38 @@ impl AnyExt for Dynamic {
/// ///
/// let x: Dynamic = 42_u32.into_dynamic(); /// let x: Dynamic = 42_u32.into_dynamic();
/// ///
/// assert_eq!(*x.downcast::<u32>().unwrap(), 42); /// assert_eq!(x.try_cast::<u32>().unwrap(), 42);
/// ``` /// ```
fn downcast<T: Any + Clone>(self) -> Result<Box<T>, Self> { fn try_cast<T: Any + Clone>(self) -> Result<T, Self> {
if self.is::<T>() { if self.is::<T>() {
unsafe { unsafe {
let raw: *mut Variant = Box::into_raw(self); let raw: *mut Variant = Box::into_raw(self);
Ok(Box::from_raw(raw as *mut T)) Ok(*Box::from_raw(raw as *mut T))
} }
} else { } else {
Err(self) Err(self)
} }
} }
/// Get a copy of the `Dynamic` value as a specific type.
///
/// # Panics
///
/// Panics if the cast fails (e.g. the type of the actual value is not the same as the specified type).
///
/// # Example
///
/// ```
/// use rhai::{Dynamic, Any, AnyExt};
///
/// let x: Dynamic = 42_u32.into_dynamic();
///
/// assert_eq!(x.cast::<u32>(), 42);
/// ```
fn cast<T: Any + Clone>(self) -> T {
self.try_cast::<T>().expect("cast failed")
}
fn _closed(&self) -> _Private { fn _closed(&self) -> _Private {
_Private _Private
} }

View File

@ -692,8 +692,7 @@ impl<'e> Engine<'e> {
ast: &AST, ast: &AST,
) -> Result<T, EvalAltResult> { ) -> Result<T, EvalAltResult> {
self.eval_ast_with_scope_raw(scope, false, ast)? self.eval_ast_with_scope_raw(scope, false, ast)?
.downcast::<T>() .try_cast::<T>()
.map(|v| *v)
.map_err(|a| { .map_err(|a| {
EvalAltResult::ErrorMismatchOutputType( EvalAltResult::ErrorMismatchOutputType(
self.map_type_name((*a).type_name()).to_string(), self.map_type_name((*a).type_name()).to_string(),
@ -735,9 +734,8 @@ impl<'e> Engine<'e> {
/// Evaluate a file, but throw away the result and only return error (if any). /// 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. /// Useful for when you don't need the result, but still need to keep track of possible errors.
/// ///
/// # Note /// If `retain_functions` is set to `true`, functions defined by previous scripts are _retained_
/// /// and not cleared from run to run.
/// If `retain_functions` is set to `true`, functions defined by previous scripts are _retained_ and not cleared from run to run.
#[cfg(not(feature = "no_std"))] #[cfg(not(feature = "no_std"))]
pub fn consume_file( pub fn consume_file(
&mut self, &mut self,
@ -750,9 +748,8 @@ impl<'e> Engine<'e> {
/// Evaluate a file with own scope, but throw away the result and only return error (if any). /// Evaluate a file with own scope, 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. /// Useful for when you don't need the result, but still need to keep track of possible errors.
/// ///
/// # Note /// If `retain_functions` is set to `true`, functions defined by previous scripts are _retained_
/// /// and not cleared from run to run.
/// If `retain_functions` is set to `true`, functions defined by previous scripts are _retained_ and not cleared from run to run.
#[cfg(not(feature = "no_std"))] #[cfg(not(feature = "no_std"))]
pub fn consume_file_with_scope( pub fn consume_file_with_scope(
&mut self, &mut self,
@ -767,9 +764,8 @@ impl<'e> Engine<'e> {
/// Evaluate a string, but throw away the result and only return error (if any). /// Evaluate a string, 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. /// Useful for when you don't need the result, but still need to keep track of possible errors.
/// ///
/// # Note /// If `retain_functions` is set to `true`, functions defined by previous scripts are _retained_
/// /// and not cleared from run to run.
/// If `retain_functions` is set to `true`, functions defined by previous scripts are _retained_and not cleared from run to run.
pub fn consume(&mut self, retain_functions: bool, input: &str) -> Result<(), EvalAltResult> { pub fn consume(&mut self, retain_functions: bool, input: &str) -> Result<(), EvalAltResult> {
self.consume_with_scope(&mut Scope::new(), retain_functions, input) self.consume_with_scope(&mut Scope::new(), retain_functions, input)
} }
@ -777,9 +773,8 @@ impl<'e> Engine<'e> {
/// Evaluate a string with own scope, but throw away the result and only return error (if any). /// Evaluate a string with own scope, 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. /// Useful for when you don't need the result, but still need to keep track of possible errors.
/// ///
/// # Note /// If `retain_functions` is set to `true`, functions defined by previous scripts are _retained_
/// /// and not cleared from run to run.
/// If `retain_functions` is set to `true`, functions defined by previous scripts are _retained_and not cleared from run to run.
pub fn consume_with_scope( pub fn consume_with_scope(
&mut self, &mut self,
scope: &mut Scope, scope: &mut Scope,
@ -797,9 +792,8 @@ impl<'e> Engine<'e> {
/// Evaluate an AST, but throw away the result and only return error (if any). /// Evaluate an AST, 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. /// Useful for when you don't need the result, but still need to keep track of possible errors.
/// ///
/// # Note /// If `retain_functions` is set to `true`, functions defined by previous scripts are _retained_
/// /// and not cleared from run to run.
/// If `retain_functions` is set to `true`, functions defined by previous scripts are _retained_and not cleared from run to run.
pub fn consume_ast(&mut self, retain_functions: bool, ast: &AST) -> Result<(), EvalAltResult> { pub fn consume_ast(&mut self, retain_functions: bool, ast: &AST) -> Result<(), EvalAltResult> {
self.consume_ast_with_scope(&mut Scope::new(), retain_functions, ast) self.consume_ast_with_scope(&mut Scope::new(), retain_functions, ast)
} }
@ -807,9 +801,8 @@ impl<'e> Engine<'e> {
/// Evaluate an `AST` with own scope, but throw away the result and only return error (if any). /// Evaluate an `AST` with own scope, 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. /// Useful for when you don't need the result, but still need to keep track of possible errors.
/// ///
/// # Note /// If `retain_functions` is set to `true`, functions defined by previous scripts are _retained_
/// /// and not cleared from run to run.
/// If `retain_functions` is set to `true`, functions defined by previous scripts are _retained_and not cleared from run to run.
pub fn consume_ast_with_scope( pub fn consume_ast_with_scope(
&mut self, &mut self,
scope: &mut Scope, scope: &mut Scope,
@ -884,8 +877,7 @@ impl<'e> Engine<'e> {
let mut arg_values: Vec<_> = values.iter_mut().map(Dynamic::as_mut).collect(); let mut arg_values: Vec<_> = values.iter_mut().map(Dynamic::as_mut).collect();
self.call_fn_raw(name, &mut arg_values, None, Position::none(), 0)? self.call_fn_raw(name, &mut arg_values, None, Position::none(), 0)?
.downcast() .try_cast()
.map(|b| *b)
.map_err(|a| { .map_err(|a| {
EvalAltResult::ErrorMismatchOutputType( EvalAltResult::ErrorMismatchOutputType(
self.map_type_name((*a).type_name()).into(), self.map_type_name((*a).type_name()).into(),

View File

@ -628,9 +628,9 @@ impl Engine<'_> {
// val_array[idx] // val_array[idx]
if let Some(arr) = val.downcast_ref::<Array>() { if let Some(arr) = val.downcast_ref::<Array>() {
let idx = *self let idx = self
.eval_expr(scope, idx_expr, level)? .eval_expr(scope, idx_expr, level)?
.downcast::<INT>() .try_cast::<INT>()
.map_err(|_| EvalAltResult::ErrorNumericIndexExpr(idx_expr.position()))?; .map_err(|_| EvalAltResult::ErrorNumericIndexExpr(idx_expr.position()))?;
return if idx >= 0 { return if idx >= 0 {
@ -647,9 +647,9 @@ impl Engine<'_> {
{ {
// val_map[idx] // val_map[idx]
if let Some(map) = val.downcast_ref::<Map>() { if let Some(map) = val.downcast_ref::<Map>() {
let idx = *self let idx = self
.eval_expr(scope, idx_expr, level)? .eval_expr(scope, idx_expr, level)?
.downcast::<String>() .try_cast::<String>()
.map_err(|_| EvalAltResult::ErrorStringIndexExpr(idx_expr.position()))?; .map_err(|_| EvalAltResult::ErrorStringIndexExpr(idx_expr.position()))?;
return Ok(( return Ok((
@ -662,9 +662,9 @@ impl Engine<'_> {
// val_string[idx] // val_string[idx]
if let Some(s) = val.downcast_ref::<String>() { if let Some(s) = val.downcast_ref::<String>() {
let idx = *self let idx = self
.eval_expr(scope, idx_expr, level)? .eval_expr(scope, idx_expr, level)?
.downcast::<INT>() .try_cast::<INT>()
.map_err(|_| EvalAltResult::ErrorNumericIndexExpr(idx_expr.position()))?; .map_err(|_| EvalAltResult::ErrorNumericIndexExpr(idx_expr.position()))?;
return if idx >= 0 { return if idx >= 0 {
@ -795,9 +795,9 @@ impl Engine<'_> {
let s = scope.get_mut_by_type::<String>(src); let s = scope.get_mut_by_type::<String>(src);
let pos = new_val.1; let pos = new_val.1;
// Value must be a character // Value must be a character
let ch = *new_val let ch = new_val
.0 .0
.downcast::<char>() .try_cast::<char>()
.map_err(|_| EvalAltResult::ErrorCharMismatch(pos))?; .map_err(|_| EvalAltResult::ErrorCharMismatch(pos))?;
Self::str_replace_char(s, idx.as_num(), ch); Self::str_replace_char(s, idx.as_num(), ch);
Ok(().into_dynamic()) Ok(().into_dynamic())
@ -830,8 +830,8 @@ impl Engine<'_> {
if let Some(s) = target.downcast_mut::<String>() { if let Some(s) = target.downcast_mut::<String>() {
// Value must be a character // Value must be a character
let ch = *new_val let ch = new_val
.downcast::<char>() .try_cast::<char>()
.map_err(|_| EvalAltResult::ErrorCharMismatch(pos))?; .map_err(|_| EvalAltResult::ErrorCharMismatch(pos))?;
Self::str_replace_char(s, idx.as_num(), ch); Self::str_replace_char(s, idx.as_num(), ch);
return Ok(target); return Ok(target);
@ -1258,32 +1258,32 @@ impl Engine<'_> {
} }
Expr::And(lhs, rhs) => Ok(Box::new( Expr::And(lhs, rhs) => Ok(Box::new(
*self self
.eval_expr(scope, &*lhs, level)? .eval_expr(scope, &*lhs, level)?
.downcast::<bool>() .try_cast::<bool>()
.map_err(|_| { .map_err(|_| {
EvalAltResult::ErrorBooleanArgMismatch("AND".into(), lhs.position()) EvalAltResult::ErrorBooleanArgMismatch("AND".into(), lhs.position())
})? })?
&& // Short-circuit using && && // Short-circuit using &&
*self self
.eval_expr(scope, &*rhs, level)? .eval_expr(scope, &*rhs, level)?
.downcast::<bool>() .try_cast::<bool>()
.map_err(|_| { .map_err(|_| {
EvalAltResult::ErrorBooleanArgMismatch("AND".into(), rhs.position()) EvalAltResult::ErrorBooleanArgMismatch("AND".into(), rhs.position())
})?, })?,
)), )),
Expr::Or(lhs, rhs) => Ok(Box::new( Expr::Or(lhs, rhs) => Ok(Box::new(
*self self
.eval_expr(scope, &*lhs, level)? .eval_expr(scope, &*lhs, level)?
.downcast::<bool>() .try_cast::<bool>()
.map_err(|_| { .map_err(|_| {
EvalAltResult::ErrorBooleanArgMismatch("OR".into(), lhs.position()) EvalAltResult::ErrorBooleanArgMismatch("OR".into(), lhs.position())
})? })?
|| // Short-circuit using || || // Short-circuit using ||
*self self
.eval_expr(scope, &*rhs, level)? .eval_expr(scope, &*rhs, level)?
.downcast::<bool>() .try_cast::<bool>()
.map_err(|_| { .map_err(|_| {
EvalAltResult::ErrorBooleanArgMismatch("OR".into(), rhs.position()) EvalAltResult::ErrorBooleanArgMismatch("OR".into(), rhs.position())
})?, })?,
@ -1334,10 +1334,10 @@ impl Engine<'_> {
// If-else statement // If-else statement
Stmt::IfThenElse(guard, if_body, else_body) => self Stmt::IfThenElse(guard, if_body, else_body) => self
.eval_expr(scope, guard, level)? .eval_expr(scope, guard, level)?
.downcast::<bool>() .try_cast::<bool>()
.map_err(|_| EvalAltResult::ErrorLogicGuard(guard.position())) .map_err(|_| EvalAltResult::ErrorLogicGuard(guard.position()))
.and_then(|guard_val| { .and_then(|guard_val| {
if *guard_val { if guard_val {
self.eval_stmt(scope, if_body, level) self.eval_stmt(scope, if_body, level)
} else if let Some(stmt) = else_body { } else if let Some(stmt) = else_body {
self.eval_stmt(scope, stmt.as_ref(), level) self.eval_stmt(scope, stmt.as_ref(), level)
@ -1348,8 +1348,8 @@ impl Engine<'_> {
// While loop // While loop
Stmt::While(guard, body) => loop { Stmt::While(guard, body) => loop {
match self.eval_expr(scope, guard, level)?.downcast::<bool>() { match self.eval_expr(scope, guard, level)?.try_cast::<bool>() {
Ok(guard_val) if *guard_val => match self.eval_stmt(scope, body, level) { Ok(guard_val) if guard_val => match self.eval_stmt(scope, body, level) {
Ok(_) | Err(EvalAltResult::ErrorLoopBreak(false, _)) => (), Ok(_) | Err(EvalAltResult::ErrorLoopBreak(false, _)) => (),
Err(EvalAltResult::ErrorLoopBreak(true, _)) => return Ok(().into_dynamic()), Err(EvalAltResult::ErrorLoopBreak(true, _)) => return Ok(().into_dynamic()),
Err(x) => return Err(x), Err(x) => return Err(x),
@ -1425,9 +1425,7 @@ impl Engine<'_> {
Stmt::ReturnWithVal(Some(a), ReturnType::Exception, pos) => { Stmt::ReturnWithVal(Some(a), ReturnType::Exception, pos) => {
let val = self.eval_expr(scope, a, level)?; let val = self.eval_expr(scope, a, level)?;
Err(EvalAltResult::ErrorRuntime( Err(EvalAltResult::ErrorRuntime(
val.downcast::<String>() val.try_cast::<String>().unwrap_or_else(|_| "".to_string()),
.map(|s| *s)
.unwrap_or_else(|_| "".to_string()),
*pos, *pos,
)) ))
} }

View File

@ -2673,41 +2673,21 @@ pub fn parse<'a, 'e>(
pub fn map_dynamic_to_expr(value: Dynamic, pos: Position) -> (Option<Expr>, Dynamic) { pub fn map_dynamic_to_expr(value: Dynamic, pos: Position) -> (Option<Expr>, Dynamic) {
if value.is::<INT>() { if value.is::<INT>() {
let value2 = value.clone(); let value2 = value.clone();
( (Some(Expr::IntegerConstant(value.cast(), pos)), value2)
Some(Expr::IntegerConstant(
*value.downcast::<INT>().expect("value should be INT"),
pos,
)),
value2,
)
} else if value.is::<char>() { } else if value.is::<char>() {
let value2 = value.clone(); let value2 = value.clone();
( (Some(Expr::CharConstant(value.cast(), pos)), value2)
Some(Expr::CharConstant(
*value.downcast::<char>().expect("value should be char"),
pos,
)),
value2,
)
} else if value.is::<String>() { } else if value.is::<String>() {
let value2 = value.clone(); let value2 = value.clone();
( (Some(Expr::StringConstant(value.cast(), pos)), value2)
Some(Expr::StringConstant(
*value.downcast::<String>().expect("value should be String"),
pos,
)),
value2,
)
} else if value.is::<bool>() { } else if value.is::<bool>() {
let value2 = value.clone(); let value2 = value.clone();
( (
Some( Some(if value.cast::<bool>() {
if *value.downcast::<bool>().expect("value should be bool") {
Expr::True(pos) Expr::True(pos)
} else { } else {
Expr::False(pos) Expr::False(pos)
}, }),
),
value2, value2,
) )
} else { } else {
@ -2715,13 +2695,7 @@ pub fn map_dynamic_to_expr(value: Dynamic, pos: Position) -> (Option<Expr>, Dyna
{ {
if value.is::<FLOAT>() { if value.is::<FLOAT>() {
let value2 = value.clone(); let value2 = value.clone();
return ( return (Some(Expr::FloatConstant(value.cast(), pos)), value2);
Some(Expr::FloatConstant(
*value.downcast::<FLOAT>().expect("value should be FLOAT"),
pos,
)),
value2,
);
} }
} }

View File

@ -73,14 +73,14 @@ fn test_map_indexing() -> Result<(), EvalAltResult> {
fn test_map_assign() -> Result<(), EvalAltResult> { fn test_map_assign() -> Result<(), EvalAltResult> {
let mut engine = Engine::new(); let mut engine = Engine::new();
let x = engine.eval::<Map>(r#"let x = #{a: 1, b: true, "c#": "hello"}; x"#)?; let x = engine.eval::<Map>(r#"let x = #{a: 1, b: true, "c$": "hello"}; x"#)?;
let a = x.get("a").cloned().unwrap(); let a = x.get("a").cloned().expect("should have property a");
let b = x.get("b").cloned().unwrap(); let b = x.get("b").cloned().expect("should have property b");
let c = x.get("c#").cloned().unwrap(); let c = x.get("c$").cloned().expect("should have property c$");
assert_eq!(*a.downcast::<INT>().unwrap(), 1); assert_eq!(a.cast::<INT>(), 1);
assert_eq!(*b.downcast::<bool>().unwrap(), true); assert_eq!(b.cast::<bool>(), true);
assert_eq!(*c.downcast::<String>().unwrap(), "hello"); assert_eq!(c.cast::<String>(), "hello");
Ok(()) Ok(())
} }
@ -89,14 +89,14 @@ fn test_map_assign() -> Result<(), EvalAltResult> {
fn test_map_return() -> Result<(), EvalAltResult> { fn test_map_return() -> Result<(), EvalAltResult> {
let mut engine = Engine::new(); let mut engine = Engine::new();
let x = engine.eval::<Map>(r#"#{a: 1, b: true, c: "hello"}"#)?; let x = engine.eval::<Map>(r#"#{a: 1, b: true, "c$": "hello"}"#)?;
let a = x.get("a").cloned().unwrap(); let a = x.get("a").cloned().expect("should have property a");
let b = x.get("b").cloned().unwrap(); let b = x.get("b").cloned().expect("should have property b");
let c = x.get("c").cloned().unwrap(); let c = x.get("c$").cloned().expect("should have property c$");
assert_eq!(*a.downcast::<INT>().unwrap(), 1); assert_eq!(a.cast::<INT>(), 1);
assert_eq!(*b.downcast::<bool>().unwrap(), true); assert_eq!(b.cast::<bool>(), true);
assert_eq!(*c.downcast::<String>().unwrap(), "hello"); assert_eq!(c.cast::<String>(), "hello");
Ok(()) Ok(())
} }