use core::fmt::{self, Debug, Formatter};
use core::hash::{Hash, Hasher};
use core::mem;
use core::ptr::{self, NonNull};
use crate::array::VArray;
use crate::bytes::VBytes;
use crate::datetime::VDateTime;
use crate::number::VNumber;
use crate::object::VObject;
use crate::other::{OtherKind, VQName, VUuid, get_other_kind};
use crate::string::{VSafeString, VString};
pub(crate) const ALIGNMENT: usize = 8;
#[repr(usize)]
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub(crate) enum TypeTag {
Number = 0,
StringOrNull = 1,
BytesOrFalse = 2,
ArrayOrTrue = 3,
Object = 4,
DateTime = 5,
InlineString = 6,
Other = 7,
}
impl From<usize> for TypeTag {
fn from(other: usize) -> Self {
match other & 0b111 {
0 => TypeTag::Number,
1 => TypeTag::StringOrNull,
2 => TypeTag::BytesOrFalse,
3 => TypeTag::ArrayOrTrue,
4 => TypeTag::Object,
5 => TypeTag::DateTime,
6 => TypeTag::InlineString,
7 => TypeTag::Other,
_ => unreachable!(),
}
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum ValueType {
Null,
Bool,
Number,
String,
Bytes,
Array,
Object,
DateTime,
QName,
Uuid,
}
#[repr(transparent)]
pub struct Value {
ptr: NonNull<u8>,
}
unsafe impl Send for Value {}
unsafe impl Sync for Value {}
impl Value {
pub const NULL: Self = unsafe { Self::new_inline(TypeTag::StringOrNull) };
pub const FALSE: Self = unsafe { Self::new_inline(TypeTag::BytesOrFalse) };
pub const TRUE: Self = unsafe { Self::new_inline(TypeTag::ArrayOrTrue) };
const unsafe fn new_inline(tag: TypeTag) -> Self {
unsafe {
Self {
ptr: NonNull::new_unchecked(ptr::without_provenance_mut(tag as usize)),
}
}
}
pub(crate) unsafe fn new_ptr(p: *mut u8, tag: TypeTag) -> Self {
debug_assert!(!p.is_null());
debug_assert!((p as usize).is_multiple_of(ALIGNMENT));
unsafe {
Self {
ptr: NonNull::new_unchecked(p.wrapping_add(tag as usize)),
}
}
}
#[allow(dead_code)]
pub(crate) unsafe fn new_ref<T>(r: &T, tag: TypeTag) -> Self {
unsafe { Self::new_ptr(r as *const T as *mut u8, tag) }
}
pub(crate) unsafe fn from_bits(bits: usize) -> Self {
debug_assert!(bits != 0);
Self {
ptr: unsafe { NonNull::new_unchecked(ptr::without_provenance_mut(bits)) },
}
}
pub(crate) fn ptr_usize(&self) -> usize {
self.ptr.as_ptr().addr()
}
fn is_inline(&self) -> bool {
self.ptr_usize() < ALIGNMENT || self.is_inline_string()
}
fn type_tag(&self) -> TypeTag {
TypeTag::from(self.ptr_usize())
}
#[inline]
pub(crate) fn is_inline_string(&self) -> bool {
matches!(self.type_tag(), TypeTag::InlineString)
}
pub(crate) fn heap_ptr(&self) -> *const u8 {
self.ptr.as_ptr().map_addr(|a| a & !(ALIGNMENT - 1)) as *const u8
}
pub(crate) unsafe fn heap_ptr_mut(&mut self) -> *mut u8 {
self.ptr.as_ptr().map_addr(|a| a & !(ALIGNMENT - 1))
}
pub(crate) unsafe fn set_ptr(&mut self, ptr: *mut u8) {
let tag = self.type_tag();
unsafe {
self.ptr = NonNull::new_unchecked(ptr.wrapping_add(tag as usize));
}
}
#[allow(dead_code)]
pub(crate) fn raw_eq(&self, other: &Self) -> bool {
self.ptr == other.ptr
}
#[allow(dead_code)]
pub(crate) fn raw_hash<H: Hasher>(&self, state: &mut H) {
self.ptr.hash(state);
}
#[must_use]
pub fn value_type(&self) -> ValueType {
match (self.type_tag(), self.is_inline()) {
(TypeTag::Number, false) => ValueType::Number,
(TypeTag::StringOrNull, false) => ValueType::String,
(TypeTag::BytesOrFalse, false) => ValueType::Bytes,
(TypeTag::ArrayOrTrue, false) => ValueType::Array,
(TypeTag::Object, false) => ValueType::Object,
(TypeTag::DateTime, false) => ValueType::DateTime,
(TypeTag::InlineString, false) => ValueType::String,
(TypeTag::Other, false) => {
match unsafe { get_other_kind(self) } {
OtherKind::QName => ValueType::QName,
OtherKind::Uuid => ValueType::Uuid,
}
}
(TypeTag::StringOrNull, true) => ValueType::Null,
(TypeTag::BytesOrFalse, true) => ValueType::Bool, (TypeTag::ArrayOrTrue, true) => ValueType::Bool, (TypeTag::InlineString, true) => ValueType::String,
(TypeTag::Number, true)
| (TypeTag::Object, true)
| (TypeTag::DateTime, true)
| (TypeTag::Other, true) => {
unreachable!("invalid inline value with Number, Object, DateTime, or Other tag")
}
}
}
#[must_use]
pub fn is_null(&self) -> bool {
self.ptr == Self::NULL.ptr
}
#[must_use]
pub fn is_bool(&self) -> bool {
self.ptr == Self::TRUE.ptr || self.ptr == Self::FALSE.ptr
}
#[must_use]
pub fn is_true(&self) -> bool {
self.ptr == Self::TRUE.ptr
}
#[must_use]
pub fn is_false(&self) -> bool {
self.ptr == Self::FALSE.ptr
}
#[must_use]
pub fn is_number(&self) -> bool {
self.type_tag() == TypeTag::Number && !self.is_inline()
}
#[must_use]
pub fn is_string(&self) -> bool {
match self.type_tag() {
TypeTag::StringOrNull => !self.is_inline(),
TypeTag::InlineString => true,
_ => false,
}
}
#[must_use]
pub fn is_bytes(&self) -> bool {
self.type_tag() == TypeTag::BytesOrFalse && !self.is_inline()
}
#[must_use]
pub fn is_array(&self) -> bool {
self.type_tag() == TypeTag::ArrayOrTrue && !self.is_inline()
}
#[must_use]
pub fn is_object(&self) -> bool {
self.type_tag() == TypeTag::Object && !self.is_inline()
}
#[must_use]
pub fn is_datetime(&self) -> bool {
self.type_tag() == TypeTag::DateTime && !self.is_inline()
}
#[must_use]
pub fn is_qname(&self) -> bool {
self.value_type() == ValueType::QName
}
#[must_use]
pub fn is_uuid(&self) -> bool {
self.value_type() == ValueType::Uuid
}
#[must_use]
pub fn as_bool(&self) -> Option<bool> {
if self.is_bool() {
Some(self.is_true())
} else {
None
}
}
#[must_use]
pub fn as_number(&self) -> Option<&VNumber> {
if self.is_number() {
Some(unsafe { &*(self as *const Value as *const VNumber) })
} else {
None
}
}
pub fn as_number_mut(&mut self) -> Option<&mut VNumber> {
if self.is_number() {
Some(unsafe { &mut *(self as *mut Value as *mut VNumber) })
} else {
None
}
}
#[must_use]
pub fn as_string(&self) -> Option<&VString> {
if self.is_string() {
Some(unsafe { &*(self as *const Value as *const VString) })
} else {
None
}
}
pub fn as_string_mut(&mut self) -> Option<&mut VString> {
if self.is_string() {
Some(unsafe { &mut *(self as *mut Value as *mut VString) })
} else {
None
}
}
#[must_use]
pub fn is_safe_string(&self) -> bool {
self.as_string().is_some_and(|s| s.is_safe())
}
#[must_use]
pub fn as_safe_string(&self) -> Option<&VSafeString> {
if self.is_safe_string() {
Some(unsafe { &*(self as *const Value as *const VSafeString) })
} else {
None
}
}
pub fn as_safe_string_mut(&mut self) -> Option<&mut VSafeString> {
if self.is_safe_string() {
Some(unsafe { &mut *(self as *mut Value as *mut VSafeString) })
} else {
None
}
}
#[must_use]
pub fn as_bytes(&self) -> Option<&VBytes> {
if self.is_bytes() {
Some(unsafe { &*(self as *const Value as *const VBytes) })
} else {
None
}
}
pub fn as_bytes_mut(&mut self) -> Option<&mut VBytes> {
if self.is_bytes() {
Some(unsafe { &mut *(self as *mut Value as *mut VBytes) })
} else {
None
}
}
#[must_use]
pub fn as_array(&self) -> Option<&VArray> {
if self.is_array() {
Some(unsafe { &*(self as *const Value as *const VArray) })
} else {
None
}
}
pub fn as_array_mut(&mut self) -> Option<&mut VArray> {
if self.is_array() {
Some(unsafe { &mut *(self as *mut Value as *mut VArray) })
} else {
None
}
}
#[must_use]
pub fn as_object(&self) -> Option<&VObject> {
if self.is_object() {
Some(unsafe { &*(self as *const Value as *const VObject) })
} else {
None
}
}
pub fn as_object_mut(&mut self) -> Option<&mut VObject> {
if self.is_object() {
Some(unsafe { &mut *(self as *mut Value as *mut VObject) })
} else {
None
}
}
#[must_use]
pub fn as_datetime(&self) -> Option<&VDateTime> {
if self.is_datetime() {
Some(unsafe { &*(self as *const Value as *const VDateTime) })
} else {
None
}
}
pub fn as_datetime_mut(&mut self) -> Option<&mut VDateTime> {
if self.is_datetime() {
Some(unsafe { &mut *(self as *mut Value as *mut VDateTime) })
} else {
None
}
}
#[must_use]
pub fn as_qname(&self) -> Option<&VQName> {
if self.is_qname() {
Some(unsafe { &*(self as *const Value as *const VQName) })
} else {
None
}
}
pub fn as_qname_mut(&mut self) -> Option<&mut VQName> {
if self.is_qname() {
Some(unsafe { &mut *(self as *mut Value as *mut VQName) })
} else {
None
}
}
#[must_use]
pub fn as_uuid(&self) -> Option<&VUuid> {
if self.is_uuid() {
Some(unsafe { &*(self as *const Value as *const VUuid) })
} else {
None
}
}
pub fn as_uuid_mut(&mut self) -> Option<&mut VUuid> {
if self.is_uuid() {
Some(unsafe { &mut *(self as *mut Value as *mut VUuid) })
} else {
None
}
}
pub const fn take(&mut self) -> Value {
mem::replace(self, Value::NULL)
}
}
impl Clone for Value {
fn clone(&self) -> Self {
match self.value_type() {
ValueType::Null | ValueType::Bool => {
Self { ptr: self.ptr }
}
ValueType::Number => unsafe { self.as_number().unwrap_unchecked() }.clone_impl(),
ValueType::String => unsafe { self.as_string().unwrap_unchecked() }.clone_impl(),
ValueType::Bytes => unsafe { self.as_bytes().unwrap_unchecked() }.clone_impl(),
ValueType::Array => unsafe { self.as_array().unwrap_unchecked() }.clone_impl(),
ValueType::Object => unsafe { self.as_object().unwrap_unchecked() }.clone_impl(),
ValueType::DateTime => unsafe { self.as_datetime().unwrap_unchecked() }.clone_impl(),
ValueType::QName => unsafe { self.as_qname().unwrap_unchecked() }.clone_impl(),
ValueType::Uuid => unsafe { self.as_uuid().unwrap_unchecked() }.clone_impl(),
}
}
}
impl Drop for Value {
fn drop(&mut self) {
match self.value_type() {
ValueType::Null | ValueType::Bool => {
}
ValueType::Number => unsafe { self.as_number_mut().unwrap_unchecked() }.drop_impl(),
ValueType::String => unsafe { self.as_string_mut().unwrap_unchecked() }.drop_impl(),
ValueType::Bytes => unsafe { self.as_bytes_mut().unwrap_unchecked() }.drop_impl(),
ValueType::Array => unsafe { self.as_array_mut().unwrap_unchecked() }.drop_impl(),
ValueType::Object => unsafe { self.as_object_mut().unwrap_unchecked() }.drop_impl(),
ValueType::DateTime => unsafe { self.as_datetime_mut().unwrap_unchecked() }.drop_impl(),
ValueType::QName => unsafe { self.as_qname_mut().unwrap_unchecked() }.drop_impl(),
ValueType::Uuid => unsafe { self.as_uuid_mut().unwrap_unchecked() }.drop_impl(),
}
}
}
impl PartialEq for Value {
fn eq(&self, other: &Self) -> bool {
let (t1, t2) = (self.value_type(), other.value_type());
if t1 != t2 {
return false;
}
match t1 {
ValueType::Null | ValueType::Bool => self.ptr == other.ptr,
ValueType::Number => unsafe {
self.as_number().unwrap_unchecked() == other.as_number().unwrap_unchecked()
},
ValueType::String => unsafe {
self.as_string().unwrap_unchecked() == other.as_string().unwrap_unchecked()
},
ValueType::Bytes => unsafe {
self.as_bytes().unwrap_unchecked() == other.as_bytes().unwrap_unchecked()
},
ValueType::Array => unsafe {
self.as_array().unwrap_unchecked() == other.as_array().unwrap_unchecked()
},
ValueType::Object => unsafe {
self.as_object().unwrap_unchecked() == other.as_object().unwrap_unchecked()
},
ValueType::DateTime => unsafe {
self.as_datetime().unwrap_unchecked() == other.as_datetime().unwrap_unchecked()
},
ValueType::QName => unsafe {
self.as_qname().unwrap_unchecked() == other.as_qname().unwrap_unchecked()
},
ValueType::Uuid => unsafe {
self.as_uuid().unwrap_unchecked() == other.as_uuid().unwrap_unchecked()
},
}
}
}
impl Eq for Value {}
impl PartialOrd for Value {
fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
use core::cmp::Ordering;
let (t1, t2) = (self.value_type(), other.value_type());
if t1 != t2 {
return t1.partial_cmp(&t2);
}
match t1 {
ValueType::Null => Some(Ordering::Equal),
ValueType::Bool => self.is_true().partial_cmp(&other.is_true()),
ValueType::Number => unsafe {
self.as_number()
.unwrap_unchecked()
.partial_cmp(other.as_number().unwrap_unchecked())
},
ValueType::String => unsafe {
self.as_string()
.unwrap_unchecked()
.partial_cmp(other.as_string().unwrap_unchecked())
},
ValueType::Bytes => unsafe {
self.as_bytes()
.unwrap_unchecked()
.partial_cmp(other.as_bytes().unwrap_unchecked())
},
ValueType::Array => unsafe {
self.as_array()
.unwrap_unchecked()
.partial_cmp(other.as_array().unwrap_unchecked())
},
ValueType::Object => None,
ValueType::DateTime => unsafe {
self.as_datetime()
.unwrap_unchecked()
.partial_cmp(other.as_datetime().unwrap_unchecked())
},
ValueType::QName => None,
ValueType::Uuid => unsafe {
self.as_uuid()
.unwrap_unchecked()
.as_bytes()
.partial_cmp(other.as_uuid().unwrap_unchecked().as_bytes())
},
}
}
}
impl Hash for Value {
fn hash<H: Hasher>(&self, state: &mut H) {
self.value_type().hash(state);
match self.value_type() {
ValueType::Null => {}
ValueType::Bool => self.is_true().hash(state),
ValueType::Number => unsafe { self.as_number().unwrap_unchecked() }.hash(state),
ValueType::String => unsafe { self.as_string().unwrap_unchecked() }.hash(state),
ValueType::Bytes => unsafe { self.as_bytes().unwrap_unchecked() }.hash(state),
ValueType::Array => unsafe { self.as_array().unwrap_unchecked() }.hash(state),
ValueType::Object => unsafe { self.as_object().unwrap_unchecked() }.hash(state),
ValueType::DateTime => unsafe { self.as_datetime().unwrap_unchecked() }.hash(state),
ValueType::QName => unsafe { self.as_qname().unwrap_unchecked() }.hash(state),
ValueType::Uuid => unsafe { self.as_uuid().unwrap_unchecked() }.hash(state),
}
}
}
impl Debug for Value {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
match self.value_type() {
ValueType::Null => f.write_str("null"),
ValueType::Bool => Debug::fmt(&self.is_true(), f),
ValueType::Number => Debug::fmt(unsafe { self.as_number().unwrap_unchecked() }, f),
ValueType::String => Debug::fmt(unsafe { self.as_string().unwrap_unchecked() }, f),
ValueType::Bytes => Debug::fmt(unsafe { self.as_bytes().unwrap_unchecked() }, f),
ValueType::Array => Debug::fmt(unsafe { self.as_array().unwrap_unchecked() }, f),
ValueType::Object => Debug::fmt(unsafe { self.as_object().unwrap_unchecked() }, f),
ValueType::DateTime => Debug::fmt(unsafe { self.as_datetime().unwrap_unchecked() }, f),
ValueType::QName => Debug::fmt(unsafe { self.as_qname().unwrap_unchecked() }, f),
ValueType::Uuid => Debug::fmt(unsafe { self.as_uuid().unwrap_unchecked() }, f),
}
}
}
impl Default for Value {
fn default() -> Self {
Self::NULL
}
}
impl From<bool> for Value {
fn from(b: bool) -> Self {
if b { Self::TRUE } else { Self::FALSE }
}
}
impl<T: Into<Value>> From<Option<T>> for Value {
fn from(opt: Option<T>) -> Self {
match opt {
Some(v) => v.into(),
None => Self::NULL,
}
}
}
#[cfg(feature = "alloc")]
impl<T: Into<Value>> core::iter::FromIterator<T> for Value {
fn from_iter<I: IntoIterator<Item = T>>(iter: I) -> Self {
VArray::from_iter(iter).into()
}
}
#[cfg(feature = "alloc")]
impl<K: Into<VString>, V: Into<Value>> core::iter::FromIterator<(K, V)> for Value {
fn from_iter<I: IntoIterator<Item = (K, V)>>(iter: I) -> Self {
VObject::from_iter(iter).into()
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum Destructured {
Null,
Bool(bool),
Number(VNumber),
String(VString),
Bytes(VBytes),
Array(VArray),
Object(VObject),
DateTime(VDateTime),
QName(VQName),
Uuid(VUuid),
}
#[derive(Debug, Copy, Clone, PartialEq)]
pub enum DestructuredRef<'a> {
Null,
Bool(bool),
Number(&'a VNumber),
String(&'a VString),
Bytes(&'a VBytes),
Array(&'a VArray),
Object(&'a VObject),
DateTime(&'a VDateTime),
QName(&'a VQName),
Uuid(&'a VUuid),
}
#[derive(Debug)]
pub enum DestructuredMut<'a> {
Null,
Bool(bool),
Number(&'a mut VNumber),
String(&'a mut VString),
Bytes(&'a mut VBytes),
Array(&'a mut VArray),
Object(&'a mut VObject),
DateTime(&'a mut VDateTime),
QName(&'a mut VQName),
Uuid(&'a mut VUuid),
}
impl Value {
#[must_use]
pub fn destructure(self) -> Destructured {
match self.value_type() {
ValueType::Null => Destructured::Null,
ValueType::Bool => Destructured::Bool(self.is_true()),
ValueType::Number => Destructured::Number(VNumber(self)),
ValueType::String => Destructured::String(VString(self)),
ValueType::Bytes => Destructured::Bytes(VBytes(self)),
ValueType::Array => Destructured::Array(VArray(self)),
ValueType::Object => Destructured::Object(VObject(self)),
ValueType::DateTime => Destructured::DateTime(VDateTime(self)),
ValueType::QName => Destructured::QName(VQName(self)),
ValueType::Uuid => Destructured::Uuid(VUuid(self)),
}
}
#[must_use]
pub fn destructure_ref(&self) -> DestructuredRef<'_> {
match self.value_type() {
ValueType::Null => DestructuredRef::Null,
ValueType::Bool => DestructuredRef::Bool(self.is_true()),
ValueType::Number => {
DestructuredRef::Number(unsafe { self.as_number().unwrap_unchecked() })
}
ValueType::String => {
DestructuredRef::String(unsafe { self.as_string().unwrap_unchecked() })
}
ValueType::Bytes => {
DestructuredRef::Bytes(unsafe { self.as_bytes().unwrap_unchecked() })
}
ValueType::Array => {
DestructuredRef::Array(unsafe { self.as_array().unwrap_unchecked() })
}
ValueType::Object => {
DestructuredRef::Object(unsafe { self.as_object().unwrap_unchecked() })
}
ValueType::DateTime => {
DestructuredRef::DateTime(unsafe { self.as_datetime().unwrap_unchecked() })
}
ValueType::QName => {
DestructuredRef::QName(unsafe { self.as_qname().unwrap_unchecked() })
}
ValueType::Uuid => DestructuredRef::Uuid(unsafe { self.as_uuid().unwrap_unchecked() }),
}
}
pub fn destructure_mut(&mut self) -> DestructuredMut<'_> {
match self.value_type() {
ValueType::Null => DestructuredMut::Null,
ValueType::Bool => DestructuredMut::Bool(self.is_true()),
ValueType::Number => {
DestructuredMut::Number(unsafe { self.as_number_mut().unwrap_unchecked() })
}
ValueType::String => {
DestructuredMut::String(unsafe { self.as_string_mut().unwrap_unchecked() })
}
ValueType::Bytes => {
DestructuredMut::Bytes(unsafe { self.as_bytes_mut().unwrap_unchecked() })
}
ValueType::Array => {
DestructuredMut::Array(unsafe { self.as_array_mut().unwrap_unchecked() })
}
ValueType::Object => {
DestructuredMut::Object(unsafe { self.as_object_mut().unwrap_unchecked() })
}
ValueType::DateTime => {
DestructuredMut::DateTime(unsafe { self.as_datetime_mut().unwrap_unchecked() })
}
ValueType::QName => {
DestructuredMut::QName(unsafe { self.as_qname_mut().unwrap_unchecked() })
}
ValueType::Uuid => {
DestructuredMut::Uuid(unsafe { self.as_uuid_mut().unwrap_unchecked() })
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::string::VString;
#[test]
fn test_size() {
assert_eq!(
core::mem::size_of::<Value>(),
core::mem::size_of::<usize>(),
"Value should be pointer-sized"
);
assert_eq!(
core::mem::size_of::<Option<Value>>(),
core::mem::size_of::<usize>(),
"Option<Value> should be pointer-sized (niche optimization)"
);
}
#[test]
fn test_null() {
let v = Value::NULL;
assert!(v.is_null());
assert_eq!(v.value_type(), ValueType::Null);
assert!(!v.is_bool());
assert!(!v.is_number());
}
#[test]
fn test_bool() {
let t = Value::TRUE;
let f = Value::FALSE;
assert!(t.is_bool());
assert!(t.is_true());
assert!(!t.is_false());
assert_eq!(t.as_bool(), Some(true));
assert!(f.is_bool());
assert!(f.is_false());
assert!(!f.is_true());
assert_eq!(f.as_bool(), Some(false));
assert_eq!(Value::from(true), Value::TRUE);
assert_eq!(Value::from(false), Value::FALSE);
}
#[test]
fn test_clone_inline() {
let v = Value::TRUE;
let v2 = v.clone();
assert_eq!(v, v2);
}
#[test]
fn test_inline_short_string() {
let v: Value = VString::new("inline").into();
assert_eq!(v.value_type(), ValueType::String);
assert!(v.is_string());
assert!(v.is_inline());
}
#[test]
fn short_strings_are_stored_inline() {
for len in 0..=VString::INLINE_LEN_MAX {
let data = "s".repeat(len);
let v = Value::from(data.as_str());
assert!(
v.is_inline_string(),
"expected inline string for length {len}, ptr={:#x}",
v.ptr_usize()
);
assert!(
v.is_inline(),
"inline flag should be true for strings of length {len}"
);
assert_eq!(
v.as_string().unwrap().as_str(),
data,
"round-trip mismatch for inline string"
);
}
}
#[test]
fn long_strings_force_heap_storage() {
let long = "l".repeat(VString::INLINE_LEN_MAX + 16);
let v = Value::from(long.as_str());
assert!(
!v.is_inline_string(),
"expected heap storage for long string ptr={:#x}",
v.ptr_usize()
);
assert_eq!(
v.as_string().unwrap().as_str(),
long,
"heap string should round-trip"
);
}
#[test]
fn clone_preserves_inline_string_representation() {
let original = Value::from("inline");
assert!(original.is_inline_string());
let clone = original.clone();
assert!(
clone.is_inline_string(),
"clone lost inline tag for ptr={:#x}",
clone.ptr_usize()
);
assert_eq!(
clone.as_string().unwrap().as_str(),
"inline",
"clone should preserve payload"
);
}
#[test]
fn string_mutations_transition_inline_and_heap() {
let mut value = Value::from("seed");
assert!(value.is_inline_string());
{
let slot = value.as_string_mut().expect("string value");
let mut owned = slot.to_string();
while owned.len() <= VString::INLINE_LEN_MAX {
owned.push('g');
}
owned.push_str("OVERFLOW");
*slot = VString::new(&owned);
}
assert!(
!value.is_inline_string(),
"string expected to spill to heap after grow"
);
{
let slot = value.as_string_mut().expect("string value");
let mut owned = slot.to_string();
owned.truncate(VString::INLINE_LEN_MAX);
*slot = VString::new(&owned);
}
assert!(
value.is_inline_string(),
"string should return to inline storage after shrink"
);
}
}