diff --git a/src/README.md b/src/README.md index 5feefa30..fc1d7bbf 100644 --- a/src/README.md +++ b/src/README.md @@ -11,7 +11,7 @@ Root Sources | `tokenizer.rs` | Script tokenizer/lexer | | `parser.rs` | Script parser | | `optimizer.rs` | Script optimizer | -| `unsafe.rs` | `unsafe` functions | +| `reify.rs` | Utilities for making generic types concrete | | `tests.rs` | Unit tests (not integration tests, which are in the `rhai/tests` sub-directory) | diff --git a/src/api/custom_syntax.rs b/src/api/custom_syntax.rs index 0411f3f0..d5263ac4 100644 --- a/src/api/custom_syntax.rs +++ b/src/api/custom_syntax.rs @@ -3,16 +3,15 @@ use crate::ast::Expr; use crate::func::native::SendSync; use crate::parser::ParseResult; -use crate::r#unsafe::unsafe_try_cast; use crate::tokenizer::{is_valid_identifier, Token}; use crate::types::dynamic::Variant; use crate::{ Engine, EvalContext, Identifier, ImmutableString, LexError, Position, RhaiResult, Shared, - StaticVec, INT, + StaticVec, reify, }; #[cfg(feature = "no_std")] use std::prelude::v1::*; -use std::{any::TypeId, ops::Deref}; +use std::ops::Deref; /// Collection of special markers for custom syntax definition. pub mod markers { @@ -94,46 +93,23 @@ impl Expression<'_> { #[must_use] pub fn get_literal_value(&self) -> Option { // Coded this way in order to maximally leverage potentials for dead-code removal. + match self.0 { + Expr::IntegerConstant(x, _) => reify!(x, |x: T| Some(x), || None), - if TypeId::of::() == TypeId::of::() { - return match self.0 { - Expr::IntegerConstant(x, _) => unsafe_try_cast(*x), - _ => None, - }; + #[cfg(not(feature = "no_float"))] + Expr::FloatConstant(x, _) => reify!(x, |x: T| Some(x), || None), + + Expr::CharConstant(x, _) => reify!(x, |x: T| Some(x), || None), + Expr::StringConstant(x, _) => reify!(x.clone(), |x: T| Some(x), || None), + Expr::Variable(_, _, x) => { + let x = Into::::into(&x.2); + reify!(x, |x: T| Some(x), || None) + } + Expr::BoolConstant(x, _) => reify!(x, |x: T| Some(x), || None), + Expr::Unit(_) => reify!((), |x: T| Some(x), || None), + + _ => None, } - #[cfg(not(feature = "no_float"))] - if TypeId::of::() == TypeId::of::() { - return match self.0 { - Expr::FloatConstant(x, _) => unsafe_try_cast(*x), - _ => None, - }; - } - if TypeId::of::() == TypeId::of::() { - return match self.0 { - Expr::CharConstant(x, _) => unsafe_try_cast(*x), - _ => None, - }; - } - if TypeId::of::() == TypeId::of::() { - return match self.0 { - Expr::StringConstant(x, _) => unsafe_try_cast(x.clone()), - Expr::Variable(_, _, x) => unsafe_try_cast(Into::::into(&x.2)), - _ => None, - }; - } - if TypeId::of::() == TypeId::of::() { - return match self.0 { - Expr::BoolConstant(x, _) => unsafe_try_cast(*x), - _ => None, - }; - } - if TypeId::of::() == TypeId::of::<()>() { - return match self.0 { - Expr::Unit(_) => unsafe_try_cast(()), - _ => None, - }; - } - None } } diff --git a/src/func/register.rs b/src/func/register.rs index 90a1e01c..d2171cfb 100644 --- a/src/func/register.rs +++ b/src/func/register.rs @@ -5,10 +5,9 @@ use super::call::FnCallArgs; use super::callable_function::CallableFunction; use super::native::{FnAny, SendSync}; -use crate::r#unsafe::unsafe_cast; use crate::tokenizer::Position; use crate::types::dynamic::{DynamicWriteLock, Variant}; -use crate::{Dynamic, NativeCallContext, RhaiResultOf, ERR}; +use crate::{Dynamic, NativeCallContext, RhaiResultOf, ERR, reify}; #[cfg(feature = "no_std")] use std::prelude::v1::*; use std::{any::TypeId, mem}; @@ -46,11 +45,12 @@ pub fn by_value(data: &mut Dynamic) -> T { // If T is `&str`, data must be `ImmutableString`, so map directly to it data.flatten_in_place(); let ref_str = data.as_str_ref().expect("&str"); - let ref_t = unsafe { mem::transmute::<_, &T>(&ref_str) }; + let ref_t = reify!(ref_str, |ref_t: &T| ref_t, || unreachable!()); ref_t.clone() } else if TypeId::of::() == TypeId::of::() { // If T is `String`, data must be `ImmutableString`, so map directly to it - unsafe_cast(mem::take(data).into_string().expect("`ImmutableString`")) + let t = mem::take(data).into_string().expect("`ImmutableString`"); + reify!(t, |t: T| t, || unreachable!()) } else { // We consume the argument and then replace it with () - the argument is not supposed to be used again. // This way, we avoid having to clone the argument again, because it is already a clone when passed here. diff --git a/src/lib.rs b/src/lib.rs index ea94ed65..98438971 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -68,6 +68,7 @@ extern crate no_std_compat as std; use std::prelude::v1::*; // Internal modules +mod reify; mod api; mod ast; @@ -81,7 +82,6 @@ mod parser; mod tests; mod tokenizer; mod types; -mod r#unsafe; /// Error encountered when parsing a script. type PERR = ParseErrorType; diff --git a/src/reify.rs b/src/reify.rs new file mode 100644 index 00000000..82d3bdbe --- /dev/null +++ b/src/reify.rs @@ -0,0 +1,26 @@ +/// Runs `$code` if `$old` is of type `$t`. +#[macro_export] +macro_rules! reify { + ($old:ident, |$new:ident : $t:ty| $code:expr, || $fallback:expr) => {{ + #[allow(unused_imports)] + use ::std::{any::{Any, TypeId}, mem::{ManuallyDrop, transmute_copy}}; + if TypeId::of::<$t>() == $old.type_id() { + // SAFETY: This is safe because we check to make sure the two types are + // actually the same type. + let $new: $t = unsafe { transmute_copy(&ManuallyDrop::new($old)) }; + $code + } else { + $fallback + } + }}; + ($old:expr, |$new:ident : $t:ty| $code:expr, || $fallback:expr) => {{ + let old = $old; + reify!(old, |$new : $t| $code, || $fallback) + }}; + ($old:ident, |$new:ident : $t:ty| $code:expr) => { + reify!($old, |$new : $t| $code, || ()) + }; + ($old:expr, |$new:ident : $t:ty| $code:expr) => { + reify!($old, |$new : $t| $code, || ()) + }; +} diff --git a/src/types/dynamic.rs b/src/types/dynamic.rs index 48d0d124..489e4884 100644 --- a/src/types/dynamic.rs +++ b/src/types/dynamic.rs @@ -1,8 +1,7 @@ //! Helper module which defines the [`Any`] trait to to allow dynamic value handling. use crate::func::native::SendSync; -use crate::r#unsafe::{unsafe_cast, unsafe_cast_box, unsafe_try_cast}; -use crate::{ExclusiveRange, FnPtr, ImmutableString, InclusiveRange, INT}; +use crate::{ExclusiveRange, FnPtr, ImmutableString, InclusiveRange, INT, reify}; #[cfg(feature = "no_std")] use std::prelude::v1::*; use std::{ @@ -1137,10 +1136,6 @@ impl Dynamic { } /// Create a [`Dynamic`] from any type. A [`Dynamic`] value is simply returned as is. /// - /// # Safety - /// - /// This type uses some unsafe code, mainly for type casting. - /// /// # Notes /// /// Beware that you need to pass in an [`Array`][crate::Array] type for it to be recognized as an [`Array`][crate::Array]. @@ -1175,74 +1170,37 @@ impl Dynamic { pub fn from(value: T) -> Self { // Coded this way in order to maximally leverage potentials for dead-code removal. - if TypeId::of::() == TypeId::of::() { - return unsafe_cast::<_, Dynamic>(value); - } + reify!(value, |v: Dynamic| return v); + reify!(value, |v: INT| return v.into()); - let val = value.as_any(); - - if TypeId::of::() == TypeId::of::() { - return (*val.downcast_ref::().expect(CHECKED)).into(); - } #[cfg(not(feature = "no_float"))] - if TypeId::of::() == TypeId::of::() { - return (*val.downcast_ref::().expect(CHECKED)).into(); - } + reify!(value, |v: crate::FLOAT| return v.into()); + #[cfg(feature = "decimal")] - if TypeId::of::() == TypeId::of::() { - return (*val.downcast_ref::().expect(CHECKED)).into(); - } - if TypeId::of::() == TypeId::of::() { - return (*val.downcast_ref::().expect(CHECKED)).into(); - } - if TypeId::of::() == TypeId::of::() { - return (*val.downcast_ref::().expect(CHECKED)).into(); - } - if TypeId::of::() == TypeId::of::() { - return val - .downcast_ref::() - .expect(CHECKED) - .clone() - .into(); - } - if TypeId::of::() == TypeId::of::<&str>() { - return val.downcast_ref::<&str>().expect(CHECKED).deref().into(); - } - if TypeId::of::() == TypeId::of::<()>() { - return ().into(); - } + reify!(value, |v: rust_decimal::Decimal| return v.into()); - if TypeId::of::() == TypeId::of::() { - return unsafe_cast::<_, String>(value).into(); - } - #[cfg(not(feature = "no_float"))] - if TypeId::of::() == TypeId::of::() { - return unsafe_cast::<_, crate::FLOAT>(value).into(); - } + reify!(value, |v: bool| return v.into()); + reify!(value, |v: char| return v.into()); + reify!(value, |v: ImmutableString| return v.into()); + reify!(value, |v: &str| return v.into()); + reify!(value, |v: ()| return v.into()); + + reify!(value, |v: String| return v.into()); #[cfg(not(feature = "no_index"))] - if TypeId::of::() == TypeId::of::() { - return unsafe_cast::<_, crate::Array>(value).into(); - } + reify!(value, |v: crate::Array| return v.into()); #[cfg(not(feature = "no_index"))] - if TypeId::of::() == TypeId::of::() { - return Dynamic::from_blob(unsafe_cast::<_, crate::Blob>(value)); // don't use blob.into() because it'll be converted into an Array - } + reify!(value, |v: crate::Blob| { + // don't use blob.into() because it'll be converted into an Array + return Dynamic::from_blob(v); + }); #[cfg(not(feature = "no_object"))] - if TypeId::of::() == TypeId::of::() { - return unsafe_cast::<_, crate::Map>(value).into(); - } - if TypeId::of::() == TypeId::of::() { - return unsafe_cast::<_, FnPtr>(value).into(); - } + reify!(value, |v: crate::Map| return v.into()); + reify!(value, |v: FnPtr| return v.into()); #[cfg(not(feature = "no_std"))] - if TypeId::of::() == TypeId::of::() { - return unsafe_cast::<_, Instant>(value).into(); - } + reify!(value, |v: Instant| return v.into()); #[cfg(not(feature = "no_closure"))] - if TypeId::of::() == TypeId::of::>>() { - return unsafe_cast::<_, crate::Shared>>(value).into(); - } + reify!(value, |v: crate::Shared>| return v.into()); Self(Union::Variant( Box::new(Box::new(value)), @@ -1311,112 +1269,34 @@ impl Dynamic { return self.flatten().try_cast::(); } - if TypeId::of::() == TypeId::of::() { - return unsafe_try_cast::<_, T>(self); - } - - if TypeId::of::() == TypeId::of::() { - return match self.0 { - Union::Int(v, _, _) => unsafe_try_cast(v), - _ => None, - }; - } - - #[cfg(not(feature = "no_float"))] - if TypeId::of::() == TypeId::of::() { - return match self.0 { - Union::Float(v, _, _) => unsafe_try_cast(*v), - _ => None, - }; - } - - #[cfg(feature = "decimal")] - if TypeId::of::() == TypeId::of::() { - return match self.0 { - Union::Decimal(v, _, _) => unsafe_try_cast(*v), - _ => None, - }; - } - - if TypeId::of::() == TypeId::of::() { - return match self.0 { - Union::Bool(v, _, _) => unsafe_try_cast(v), - _ => None, - }; - } - - if TypeId::of::() == TypeId::of::() { - return match self.0 { - Union::Str(v, _, _) => unsafe_try_cast(v), - _ => None, - }; - } - - if TypeId::of::() == TypeId::of::() { - return match self.0 { - Union::Str(v, _, _) => unsafe_try_cast(v.to_string()), - _ => None, - }; - } - - if TypeId::of::() == TypeId::of::() { - return match self.0 { - Union::Char(v, _, _) => unsafe_try_cast(v), - _ => None, - }; - } - - #[cfg(not(feature = "no_index"))] - if TypeId::of::() == TypeId::of::() { - return match self.0 { - Union::Array(v, _, _) => unsafe_cast_box::<_, T>(v), - _ => None, - }; - } - - #[cfg(not(feature = "no_index"))] - if TypeId::of::() == TypeId::of::() { - return match self.0 { - Union::Blob(v, _, _) => unsafe_cast_box::<_, T>(v), - _ => None, - }; - } - - #[cfg(not(feature = "no_object"))] - if TypeId::of::() == TypeId::of::() { - return match self.0 { - Union::Map(v, _, _) => unsafe_cast_box::<_, T>(v), - _ => None, - }; - } - - if TypeId::of::() == TypeId::of::() { - return match self.0 { - Union::FnPtr(v, _, _) => unsafe_cast_box::<_, T>(v), - _ => None, - }; - } - - #[cfg(not(feature = "no_std"))] - if TypeId::of::() == TypeId::of::() { - return match self.0 { - Union::TimeStamp(v, _, _) => unsafe_cast_box::<_, T>(v), - _ => None, - }; - } - - if TypeId::of::() == TypeId::of::<()>() { - return match self.0 { - Union::Unit(v, _, _) => unsafe_try_cast(v), - _ => None, - }; - } + reify!(self, |v: T| return Some(v)); match self.0 { + Union::Int(v, ..) => reify!(v, |v: T| Some(v), || None), + #[cfg(not(feature = "no_float"))] + Union::Float(v, ..) => reify!(v, |v: T| Some(v), || None), + #[cfg(feature = "decimal")] + Union::Decimal(v, ..) => reify!(v, |v: T| Some(v), || None), + Union::Bool(v, ..) => reify!(v, |v: T| Some(v), || None), + Union::Str(v, ..) => { + reify!(v, |v: T| Some(v), || { + reify!(v.to_string(), |v: T| Some(v), || None) + }) + }, + Union::Char(v, ..) => reify!(v, |v: T| Some(v), || None), + #[cfg(not(feature = "no_index"))] + Union::Array(v, ..) => reify!(v, |v: Box| Some(*v), || None), + #[cfg(not(feature = "no_index"))] + Union::Blob(v, ..) => reify!(v, |v: Box| Some(*v), || None), + #[cfg(not(feature = "no_object"))] + Union::Map(v, ..) => reify!(v, |v: Box| Some(*v), || None), + Union::FnPtr(v, ..) => reify!(v, |v: Box| Some(*v), || None), + #[cfg(not(feature = "no_std"))] + Union::TimeStamp(v, ..) => reify!(v, |v: Box| Some(*v), || None), + Union::Unit(v, ..) => reify!(v, |v: T| Some(v), || None), Union::Variant(v, _, _) => (*v).as_boxed_any().downcast().ok().map(|x| *x), #[cfg(not(feature = "no_closure"))] Union::Shared(_, _, _) => unreachable!("Union::Shared case should be already handled"), - _ => None, } } /// Convert the [`Dynamic`] value into a specific type. diff --git a/src/unsafe.rs b/src/unsafe.rs deleted file mode 100644 index 9c1eb467..00000000 --- a/src/unsafe.rs +++ /dev/null @@ -1,54 +0,0 @@ -//! A helper module containing unsafe utility functions. - -#[cfg(feature = "no_std")] -use std::prelude::v1::*; -use std::{ - any::{Any, TypeId}, - mem, ptr, -}; - -/// Cast a type into another type. -/// -/// # Undefined Behavior -/// -/// It is UB if the types are not compatible. -#[inline(always)] -#[must_use] -pub fn unsafe_cast(a: A) -> B { - unsafe { - let ret: B = ptr::read(&a as *const _ as *const B); - // We explicitly forget the value immediately after moving out, - // removing any chance of a destructor running or value otherwise - // being used again. - mem::forget(a); - ret - } -} - -/// Cast a type into another type. -#[inline(always)] -#[must_use] -pub fn unsafe_try_cast(a: A) -> Option { - if TypeId::of::() == a.type_id() { - // SAFETY: Just checked we have the right type. - Some(unsafe_cast(a)) - } else { - None - } -} - -/// Cast a Boxed type into another type. -#[inline(always)] -#[must_use] -pub fn unsafe_cast_box(item: Box) -> Option { - // Only allow casting to the exact same type - if TypeId::of::() == TypeId::of::() { - // SAFETY: just checked whether we are pointing to the correct type - unsafe { - let raw: *mut dyn Any = Box::into_raw(item as Box); - Some(*Box::from_raw(raw as *mut T)) - } - } else { - None - } -}