#![allow(clippy::inline_always)]
use crate::{
JsBigInt, JsObject, JsSymbol, JsVariant, bigint::RawBigInt, object::ErasedVTableObject,
symbol::RawJsSymbol,
};
use boa_gc::{Finalize, GcBox, Trace, custom_trace};
use boa_string::{JsString, RawJsString};
use core::fmt;
use static_assertions::const_assert;
use std::{
mem::ManuallyDrop,
ptr::{self, NonNull},
};
const _NAN_BOX_COMPAT_CHECK: () = const {
assert!(
size_of::<usize>() == size_of::<u64>() || size_of::<usize>() == size_of::<u32>(),
"this platform is not compatible with a nan-boxed `JsValueInner`\n\
enable the `jsvalue-enum` feature to use the enum-based `JsValueInner`"
);
assert!(
align_of::<*mut ()>() >= 4,
"this platform is not compatible with a nan-boxed `JsValueInner`\n\
enable the `jsvalue-enum` feature to use the enum-based `JsValueInner`"
);
};
mod bits {
const MASK_NAN: u64 = 0x7FF0_0000_0000_0000;
pub(super) const MASK_KIND: u64 = MASK_NAN | 0xF_0000_0000_0000;
const TAG_INF: u64 = 0x0_0000_0000_0000;
const TAG_NAN: u64 = 0x8_0000_0000_0000;
const TAG_INT32: u64 = 0x9_0000_0000_0000;
const TAG_BOOLEAN: u64 = 0xA_0000_0000_0000;
const TAG_OTHER: u64 = 0xB_0000_0000_0000;
const TAG_OBJECT: u64 = 0xC_0000_0000_0000;
const TAG_STRING: u64 = 0xD_0000_0000_0000;
const TAG_SYMBOL: u64 = 0xE_0000_0000_0000;
const TAG_BIGINT: u64 = 0xF_0000_0000_0000;
pub(super) const MASK_INT32: u64 = MASK_NAN | TAG_INT32;
pub(super) const MASK_BOOLEAN: u64 = MASK_NAN | TAG_BOOLEAN;
pub(super) const MASK_OTHER: u64 = MASK_NAN | TAG_OTHER;
pub(super) const MASK_OBJECT: u64 = MASK_NAN | TAG_OBJECT;
pub(super) const MASK_STRING: u64 = MASK_NAN | TAG_STRING;
pub(super) const MASK_SYMBOL: u64 = MASK_NAN | TAG_SYMBOL;
pub(super) const MASK_BIGINT: u64 = MASK_NAN | TAG_BIGINT;
const MASK_INT32_VALUE: u64 = 0xFFFF_FFFF;
const MASK_POINTER_VALUE: u64 = 0x0000_FFFF_FFFF_FFFF;
const MASK_BOOLEAN_VALUE: u64 = 1;
pub(super) const VALUE_NULL: u64 = MASK_OTHER;
pub(super) const VALUE_UNDEFINED: u64 = MASK_OTHER | 1;
pub(super) const VALUE_FALSE: u64 = MASK_BOOLEAN;
pub(super) const VALUE_TRUE: u64 = MASK_BOOLEAN | 1;
#[inline(always)]
pub(super) const fn is_bool(value: u64) -> bool {
value & MASK_KIND == MASK_BOOLEAN
}
#[inline(always)]
pub(super) const fn is_float(value: u64) -> bool {
(value & MASK_NAN != MASK_NAN)
|| (value & MASK_KIND) == (MASK_NAN | TAG_INF)
|| (value & MASK_KIND) == (MASK_NAN | TAG_NAN)
}
#[inline(always)]
pub(super) const fn is_integer32(value: u64) -> bool {
value & MASK_KIND == MASK_INT32
}
#[inline(always)]
pub(super) const fn is_bigint(value: u64) -> bool {
value & MASK_KIND == MASK_BIGINT
}
#[inline(always)]
pub(super) const fn is_object(value: u64) -> bool {
value & MASK_KIND == MASK_OBJECT
}
#[inline(always)]
pub(super) const fn is_symbol(value: u64) -> bool {
value & MASK_KIND == MASK_SYMBOL
}
#[inline(always)]
pub(super) const fn is_string(value: u64) -> bool {
value & MASK_KIND == MASK_STRING
}
#[inline(always)]
pub(super) const fn tag_f64(value: f64) -> u64 {
if value.is_nan() {
f64::NAN.to_bits()
} else {
value.to_bits()
}
}
#[inline(always)]
pub(super) const fn tag_i32(value: i32) -> u64 {
value as u64 & MASK_INT32_VALUE | MASK_INT32
}
#[inline(always)]
pub(super) const fn untag_i32(value: u64) -> i32 {
value as i32
}
#[inline(always)]
pub(super) const fn tag_bool(value: bool) -> u64 {
value as u64 | MASK_BOOLEAN
}
#[inline(always)]
pub(super) const fn untag_bool(value: u64) -> bool {
value & MASK_BOOLEAN_VALUE != 0
}
pub(super) fn tag_pointer<T>(ptr: *mut T, type_mask: u64) -> u64 {
let value = ptr.addr() as u64;
let value_masked: u64 = value & MASK_POINTER_VALUE;
assert_eq!(
value_masked, value,
"this platform is not compatible with a nan-boxed `JsValueInner`\n\
enable the `jsvalue-enum` feature to use the enum-based `JsValueInner`"
);
assert_ne!(value_masked, 0, "pointer is null");
value_masked | type_mask
}
#[inline(always)]
pub(super) const fn untag_pointer(value: u64) -> usize {
(value & MASK_POINTER_VALUE) as usize
}
}
const_assert!(f64::from_bits(bits::VALUE_UNDEFINED).is_nan());
const_assert!(f64::from_bits(bits::VALUE_NULL).is_nan());
const_assert!(f64::from_bits(bits::VALUE_FALSE).is_nan());
const_assert!(f64::from_bits(bits::VALUE_TRUE).is_nan());
const_assert!(f64::from_bits(bits::MASK_INT32).is_nan());
const_assert!(f64::from_bits(bits::MASK_BOOLEAN).is_nan());
const_assert!(f64::from_bits(bits::MASK_OTHER).is_nan());
const_assert!(f64::from_bits(bits::MASK_OBJECT).is_nan());
const_assert!(f64::from_bits(bits::MASK_STRING).is_nan());
const_assert!(f64::from_bits(bits::MASK_SYMBOL).is_nan());
const_assert!(f64::from_bits(bits::MASK_BIGINT).is_nan());
pub(crate) struct NanBoxedValue {
#[cfg(target_pointer_width = "32")]
half: u32,
ptr: *mut (),
}
impl fmt::Debug for NanBoxedValue {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self.as_variant() {
JsVariant::Undefined => f.debug_tuple("Undefined").finish(),
JsVariant::Null => f.debug_tuple("Null").finish(),
JsVariant::Boolean(b) => f.debug_tuple("Boolean").field(&b).finish(),
JsVariant::Float64(n) => f.debug_tuple("Float64").field(&n).finish(),
JsVariant::Integer32(n) => f.debug_tuple("Integer32").field(&n).finish(),
JsVariant::BigInt(n) => f.debug_tuple("BigInt").field(&n).finish(),
JsVariant::Object(n) => f.debug_tuple("Object").field(&n).finish(),
JsVariant::Symbol(n) => f.debug_tuple("Symbol").field(&n).finish(),
JsVariant::String(n) => f.debug_tuple("String").field(&n).finish(),
}
}
}
impl Finalize for NanBoxedValue {
fn finalize(&self) {
if let Some(o) = self.as_object() {
o.finalize();
}
}
}
#[allow(unsafe_op_in_unsafe_fn)]
unsafe impl Trace for NanBoxedValue {
custom_trace! {this, mark, {
if let Some(o) = this.as_object() {
mark(&o);
}
}}
}
impl Clone for NanBoxedValue {
#[inline(always)]
fn clone(&self) -> Self {
if let Some(o) = self.as_object() {
Self::object(o.clone())
} else if let Some(s) = self.as_string() {
Self::string(s.clone())
} else if let Some(b) = self.as_bigint() {
Self::bigint(b.clone())
} else if let Some(s) = self.as_symbol() {
Self::symbol(s.clone())
} else {
Self {
#[cfg(target_pointer_width = "32")]
half: self.half,
ptr: self.ptr,
}
}
}
}
impl NanBoxedValue {
#[must_use]
#[inline(always)]
const fn from_inner_unchecked(inner: u64) -> Self {
Self {
#[cfg(target_pointer_width = "32")]
half: (inner >> 32) as u32,
ptr: ptr::without_provenance_mut(inner as usize),
}
}
fn from_object_like<T>(ptr: *mut T, addr: u64) -> Self {
Self {
#[cfg(target_pointer_width = "32")]
half: (addr >> 32) as u32,
ptr: ptr.cast::<()>().with_addr(addr as usize),
}
}
#[must_use]
#[inline(always)]
fn value(&self) -> u64 {
let value = self.ptr.addr() as u64;
#[cfg(target_pointer_width = "32")]
let value = ((self.half as u64) << 32) | value;
value
}
#[must_use]
#[inline(always)]
pub(crate) const fn null() -> Self {
Self::from_inner_unchecked(bits::VALUE_NULL)
}
#[must_use]
#[inline(always)]
pub(crate) const fn undefined() -> Self {
Self::from_inner_unchecked(bits::VALUE_UNDEFINED)
}
#[must_use]
#[inline(always)]
pub(crate) const fn float64(value: f64) -> Self {
Self::from_inner_unchecked(bits::tag_f64(value))
}
#[must_use]
#[inline(always)]
pub(crate) const fn integer32(value: i32) -> Self {
Self::from_inner_unchecked(bits::tag_i32(value))
}
#[must_use]
#[inline(always)]
pub(crate) const fn boolean(value: bool) -> Self {
Self::from_inner_unchecked(bits::tag_bool(value))
}
#[must_use]
#[inline(always)]
pub(crate) fn bigint(value: JsBigInt) -> Self {
let ptr = value.into_raw().cast_mut();
let addr = bits::tag_pointer(ptr, bits::MASK_BIGINT);
Self::from_object_like(ptr, addr)
}
#[must_use]
#[inline(always)]
pub(crate) fn object(value: JsObject) -> Self {
let ptr = value.into_raw().as_ptr();
let addr = bits::tag_pointer(ptr, bits::MASK_OBJECT);
Self::from_object_like(ptr, addr)
}
#[must_use]
#[inline(always)]
pub(crate) fn symbol(value: JsSymbol) -> Self {
let ptr = value.into_raw().as_ptr();
let addr = bits::tag_pointer(ptr, bits::MASK_SYMBOL);
Self::from_object_like(ptr, addr)
}
#[must_use]
#[inline(always)]
pub(crate) fn string(value: JsString) -> Self {
let ptr = value.into_raw().as_ptr();
let addr = bits::tag_pointer(ptr, bits::MASK_STRING);
Self::from_object_like(ptr, addr)
}
#[must_use]
#[inline(always)]
pub(crate) fn is_undefined(&self) -> bool {
self.value() == bits::VALUE_UNDEFINED
}
#[must_use]
#[inline(always)]
pub(crate) fn is_null(&self) -> bool {
self.value() == bits::VALUE_NULL
}
#[must_use]
#[inline(always)]
pub(crate) fn is_bool(&self) -> bool {
bits::is_bool(self.value())
}
#[must_use]
#[inline(always)]
pub(crate) fn is_float64(&self) -> bool {
bits::is_float(self.value())
}
#[must_use]
#[inline(always)]
pub(crate) fn is_integer32(&self) -> bool {
bits::is_integer32(self.value())
}
#[must_use]
#[inline(always)]
pub(crate) fn is_bigint(&self) -> bool {
bits::is_bigint(self.value())
}
#[must_use]
#[inline(always)]
pub(crate) fn is_object(&self) -> bool {
bits::is_object(self.value())
}
#[must_use]
#[inline(always)]
pub(crate) fn is_symbol(&self) -> bool {
bits::is_symbol(self.value())
}
#[must_use]
#[inline(always)]
pub(crate) fn is_string(&self) -> bool {
bits::is_string(self.value())
}
#[must_use]
#[inline(always)]
pub(crate) fn as_float64(&self) -> Option<f64> {
if self.is_float64() {
Some(f64::from_bits(self.value()))
} else {
None
}
}
#[must_use]
#[inline(always)]
pub(crate) fn as_integer32(&self) -> Option<i32> {
if self.is_integer32() {
Some(bits::untag_i32(self.value()))
} else {
None
}
}
#[must_use]
#[inline(always)]
pub(crate) fn as_bool(&self) -> Option<bool> {
match self.value() {
bits::VALUE_FALSE => Some(false),
bits::VALUE_TRUE => Some(true),
_ => None,
}
}
#[must_use]
#[inline(always)]
pub(crate) fn as_bigint(&self) -> Option<JsBigInt> {
if self.is_bigint() {
unsafe { Some((*self.as_bigint_unchecked()).clone()) }
} else {
None
}
}
#[must_use]
#[inline(always)]
unsafe fn as_bigint_unchecked(&self) -> ManuallyDrop<JsBigInt> {
let addr = bits::untag_pointer(self.value());
unsafe {
ManuallyDrop::new(JsBigInt::from_raw(
self.ptr.with_addr(addr).cast::<RawBigInt>().cast_const(),
))
}
}
#[must_use]
#[inline(always)]
pub(crate) fn as_object(&self) -> Option<JsObject> {
if self.is_object() {
unsafe { Some((*self.as_object_unchecked()).clone()) }
} else {
None
}
}
#[must_use]
#[inline(always)]
unsafe fn as_object_unchecked(&self) -> ManuallyDrop<JsObject> {
let addr = bits::untag_pointer(self.value());
unsafe {
ManuallyDrop::new(JsObject::from_raw(NonNull::new_unchecked(
self.ptr.with_addr(addr).cast::<GcBox<ErasedVTableObject>>(),
)))
}
}
#[must_use]
#[inline(always)]
pub(crate) fn as_symbol(&self) -> Option<JsSymbol> {
if self.is_symbol() {
unsafe { Some((*self.as_symbol_unchecked()).clone()) }
} else {
None
}
}
#[must_use]
#[inline(always)]
unsafe fn as_symbol_unchecked(&self) -> ManuallyDrop<JsSymbol> {
let addr = bits::untag_pointer(self.value());
unsafe {
ManuallyDrop::new(JsSymbol::from_raw(NonNull::new_unchecked(
self.ptr.with_addr(addr).cast::<RawJsSymbol>(),
)))
}
}
#[must_use]
#[inline(always)]
pub(crate) fn as_string(&self) -> Option<JsString> {
if self.is_string() {
unsafe { Some((*self.as_string_unchecked()).clone()) }
} else {
None
}
}
#[must_use]
#[inline(always)]
unsafe fn as_string_unchecked(&self) -> ManuallyDrop<JsString> {
let addr = bits::untag_pointer(self.value());
unsafe {
ManuallyDrop::new(JsString::from_raw(NonNull::new_unchecked(
self.ptr.with_addr(addr).cast::<RawJsString>(),
)))
}
}
#[must_use]
#[inline(always)]
pub(crate) fn as_variant(&self) -> JsVariant {
match self.value() & bits::MASK_KIND {
bits::MASK_OBJECT => {
JsVariant::Object(unsafe { (*self.as_object_unchecked()).clone() })
}
bits::MASK_STRING => {
JsVariant::String(unsafe { (*self.as_string_unchecked()).clone() })
}
bits::MASK_SYMBOL => {
JsVariant::Symbol(unsafe { (*self.as_symbol_unchecked()).clone() })
}
bits::MASK_BIGINT => {
JsVariant::BigInt(unsafe { (*self.as_bigint_unchecked()).clone() })
}
bits::MASK_INT32 => JsVariant::Integer32(bits::untag_i32(self.value())),
bits::MASK_BOOLEAN => JsVariant::Boolean(bits::untag_bool(self.value())),
bits::MASK_OTHER => match self.value() {
bits::VALUE_NULL => JsVariant::Null,
_ => JsVariant::Undefined,
},
_ => JsVariant::Float64(f64::from_bits(self.value())),
}
}
}
impl Drop for NanBoxedValue {
#[inline(always)]
fn drop(&mut self) {
match self.value() & bits::MASK_KIND {
bits::MASK_OBJECT => {
unsafe { ManuallyDrop::into_inner(self.as_object_unchecked()) };
}
bits::MASK_STRING => {
unsafe { ManuallyDrop::into_inner(self.as_string_unchecked()) };
}
bits::MASK_SYMBOL => {
unsafe { ManuallyDrop::into_inner(self.as_symbol_unchecked()) };
}
bits::MASK_BIGINT => {
unsafe { ManuallyDrop::into_inner(self.as_bigint_unchecked()) };
}
_ => {}
}
}
}
#[cfg(test)]
macro_rules! assert_type {
(@@is $value: ident, $u: literal, $n: literal, $b: literal, $i: literal, $f: literal, $bi: literal, $s: literal, $o: literal, $sy: literal) => {
assert_eq!($u != 0, $value.is_undefined());
assert_eq!($n != 0, $value.is_null());
assert_eq!($b != 0, $value.is_bool());
assert_eq!($i != 0, $value.is_integer32());
assert_eq!($f != 0, $value.is_float64());
assert_eq!($bi != 0, $value.is_bigint());
assert_eq!($s != 0, $value.is_string());
assert_eq!($o != 0, $value.is_object());
assert_eq!($sy != 0, $value.is_symbol());
};
(@@as $value: ident, $u: literal, $n: literal, $b: literal, $i: literal, $f: literal, $bi: literal, $s: literal, $o: literal, $sy: literal) => {
if $b == 0 { assert_eq!($value.as_bool(), None); }
if $i == 0 { assert_eq!($value.as_integer32(), None); }
if $f == 0 { assert_eq!($value.as_float64(), None); }
if $bi == 0 { assert_eq!($value.as_bigint(), None); }
if $s == 0 { assert_eq!($value.as_string(), None); }
if $o == 0 { assert_eq!($value.as_object(), None); }
if $sy == 0 { assert_eq!($value.as_symbol(), None); }
};
($value: ident is undefined) => {
assert_type!(@@is $value, 1, 0, 0, 0, 0, 0, 0, 0, 0);
assert_eq!($value.as_variant(), JsVariant::Undefined);
};
($value: ident is null) => {
assert_type!(@@is $value, 0, 1, 0, 0, 0, 0, 0, 0, 0);
assert_eq!($value.as_variant(), JsVariant::Null);
};
($value: ident is bool($scalar: ident)) => {
assert_type!(@@is $value, 0, 0, 1, 0, 0, 0, 0, 0, 0);
assert_type!(@@as $value, 0, 0, 1, 0, 0, 0, 0, 0, 0);
assert_eq!(Some($scalar), $value.as_bool());
assert_eq!($value.as_variant(), JsVariant::Boolean($scalar));
};
($value: ident is integer($scalar: ident)) => {
assert_type!(@@is $value, 0, 0, 0, 1, 0, 0, 0, 0, 0);
assert_type!(@@as $value, 0, 0, 0, 1, 0, 0, 0, 0, 0);
assert_eq!(Some($scalar), $value.as_integer32());
assert_eq!($value.as_variant(), JsVariant::Integer32($scalar));
};
($value: ident is float($scalar: ident)) => {
assert_type!(@@is $value, 0, 0, 0, 0, 1, 0, 0, 0, 0);
assert_type!(@@as $value, 0, 0, 0, 0, 1, 0, 0, 0, 0);
assert_eq!(Some($scalar), $value.as_float64());
assert_eq!(Some(1.0 / $scalar), $value.as_float64().map(|f| 1.0 / f));
assert_eq!($value.as_variant(), JsVariant::Float64($scalar));
let new_value = $value.clone();
assert_eq!(Some($scalar), new_value.as_float64());
assert_eq!($value.as_float64(), new_value.as_float64());
assert_eq!(Some(1.0 / $scalar), new_value.as_float64().map(|f| 1.0 / f));
assert_eq!(new_value.as_variant(), JsVariant::Float64($scalar));
let JsVariant::Float64(new_scalar) = new_value.as_variant() else {
panic!("Expected Float64, got {:?}", new_value.as_variant());
};
assert_eq!(Some(new_scalar), new_value.as_float64());
assert_eq!($value.as_float64(), new_value.as_float64());
assert_eq!(Some(1.0 / new_scalar), new_value.as_float64().map(|f| 1.0 / f));
assert_eq!(new_value.as_variant(), JsVariant::Float64(new_scalar));
};
($value: ident is nan) => {
assert_type!(@@is $value, 0, 0, 0, 0, 1, 0, 0, 0, 0);
assert_type!(@@as $value, 0, 0, 0, 0, 1, 0, 0, 0, 0);
assert!($value.as_float64().unwrap().is_nan());
assert!(matches!($value.as_variant(), JsVariant::Float64(f) if f.is_nan()));
};
($value: ident is bigint($scalar: ident)) => {
assert_type!(@@is $value, 0, 0, 0, 0, 0, 1, 0, 0, 0);
assert_type!(@@as $value, 0, 0, 0, 0, 0, 1, 0, 0, 0);
assert_eq!(Some(&$scalar), $value.as_bigint().as_ref());
assert_eq!($value.as_variant(), JsVariant::BigInt($scalar));
};
($value: ident is object($scalar: ident)) => {
assert_type!(@@is $value, 0, 0, 0, 0, 0, 0, 0, 1, 0);
assert_type!(@@as $value, 0, 0, 0, 0, 0, 0, 0, 1, 0);
assert_eq!(Some(&$scalar), $value.as_object().as_ref());
assert_eq!($value.as_variant(), JsVariant::Object($scalar));
};
($value: ident is symbol($scalar: ident)) => {
assert_type!(@@is $value, 0, 0, 0, 0, 0, 0, 0, 0, 1);
assert_type!(@@as $value, 0, 0, 0, 0, 0, 0, 0, 0, 1);
assert_eq!(Some(&$scalar), $value.as_symbol().as_ref());
assert_eq!($value.as_variant(), JsVariant::Symbol($scalar));
};
($value: ident is string($scalar: ident)) => {
assert_type!(@@is $value, 0, 0, 0, 0, 0, 0, 1, 0, 0);
assert_type!(@@as $value, 0, 0, 0, 0, 0, 0, 1, 0, 0);
assert_eq!(Some(&$scalar), $value.as_string().as_ref());
assert_eq!($value.as_variant(), JsVariant::String($scalar));
};
}
#[test]
fn null() {
let v = NanBoxedValue::null();
assert_type!(v is null);
}
#[test]
fn undefined() {
let v = NanBoxedValue::undefined();
assert_type!(v is undefined);
}
#[test]
fn boolean() {
let v = NanBoxedValue::boolean(true);
assert_type!(v is bool(true));
let v = NanBoxedValue::boolean(false);
assert_type!(v is bool(false));
}
#[test]
fn integer() {
fn assert_integer(i: i32) {
let v = NanBoxedValue::integer32(i);
assert_type!(v is integer(i));
}
assert_integer(0);
assert_integer(1);
assert_integer(-1);
assert_integer(42);
assert_integer(-42);
assert_integer(i32::MAX);
assert_integer(i32::MIN);
assert_integer(i32::MAX - 1);
assert_integer(i32::MIN + 1);
}
#[test]
#[allow(clippy::float_cmp)]
fn float() {
fn assert_float(f: f64) {
let v = NanBoxedValue::float64(f);
assert_type!(v is float(f));
}
assert_float(0.0);
assert_float(-0.0);
assert_float(0.1 + 0.2);
assert_float(-42.123);
assert_float(f64::INFINITY);
assert_float(f64::NEG_INFINITY);
let neg_zero = NanBoxedValue::float64(-0.0);
assert!(neg_zero.as_float64().unwrap().is_sign_negative());
assert_eq!(0.0f64, neg_zero.as_float64().unwrap());
let pos_zero = NanBoxedValue::float64(0.0);
assert!(!pos_zero.as_float64().unwrap().is_sign_negative());
assert_eq!(0.0f64, pos_zero.as_float64().unwrap());
assert_eq!(pos_zero.as_float64(), neg_zero.as_float64());
let nan = NanBoxedValue::float64(f64::NAN);
assert_type!(nan is nan);
}
#[test]
fn bigint() {
let bigint = JsBigInt::from(42);
let v = NanBoxedValue::bigint(bigint.clone());
assert_type!(v is bigint(bigint));
}
#[test]
fn object() {
let object = JsObject::with_null_proto();
let v = NanBoxedValue::object(object.clone());
assert_type!(v is object(object));
}
#[test]
fn string() {
let str = crate::js_string!("Hello World");
let v = NanBoxedValue::string(str.clone());
assert_type!(v is string(str));
}
#[test]
fn symbol() {
let sym = JsSymbol::new(Some(JsString::from("Hello World"))).unwrap();
let v = NanBoxedValue::symbol(sym.clone());
assert_type!(v is symbol(sym));
let sym = JsSymbol::new(None).unwrap();
let v = NanBoxedValue::symbol(sym.clone());
assert_type!(v is symbol(sym));
}