use std::cmp::Ordering;
use std::fmt::{self, Debug, Display, Formatter};
use std::hash::Hash;
use std::ops::Deref;
use crate::value::IValue;
#[doc(hidden)]
pub fn init_cache() {
crate::value::interned::init_cache();
}
#[repr(transparent)]
#[derive(Clone)]
pub struct IString(pub(crate) IValue);
value_subtype_impls!(IString, into_string, as_string, as_string_mut);
impl IString {
#[must_use]
pub fn intern(s: &str) -> Self {
IString(IValue::new_string(s))
}
#[must_use]
pub fn len(&self) -> usize {
self.as_str().len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.len() == 0
}
#[must_use]
pub fn as_str(&self) -> &str {
self.0.as_str().expect("an IString always wraps a string")
}
#[must_use]
pub fn as_bytes(&self) -> &[u8] {
self.as_str().as_bytes()
}
#[must_use]
pub fn new() -> Self {
Self::intern("")
}
}
impl Deref for IString {
type Target = str;
fn deref(&self) -> &str {
self.as_str()
}
}
#[cfg(feature = "broken-borrow-impl-compat")]
impl std::borrow::Borrow<str> for IString {
fn borrow(&self) -> &str {
self.as_str()
}
}
impl From<&str> for IString {
fn from(other: &str) -> Self {
Self::intern(other)
}
}
impl From<&mut str> for IString {
fn from(other: &mut str) -> Self {
Self::intern(other)
}
}
impl From<String> for IString {
fn from(other: String) -> Self {
Self::intern(other.as_str())
}
}
impl From<&String> for IString {
fn from(other: &String) -> Self {
Self::intern(other.as_str())
}
}
impl From<&mut String> for IString {
fn from(other: &mut String) -> Self {
Self::intern(other.as_str())
}
}
impl From<IString> for String {
fn from(other: IString) -> Self {
other.as_str().into()
}
}
impl PartialEq for IString {
fn eq(&self, other: &Self) -> bool {
self.0.raw_eq(&other.0)
}
}
impl PartialEq<str> for IString {
fn eq(&self, other: &str) -> bool {
self.as_str() == other
}
}
impl PartialEq<IString> for str {
fn eq(&self, other: &IString) -> bool {
self == other.as_str()
}
}
impl PartialEq<String> for IString {
fn eq(&self, other: &String) -> bool {
self.as_str() == other
}
}
impl PartialEq<IString> for String {
fn eq(&self, other: &IString) -> bool {
self == other.as_str()
}
}
impl Default for IString {
fn default() -> Self {
Self::new()
}
}
impl Eq for IString {}
impl Ord for IString {
fn cmp(&self, other: &Self) -> Ordering {
if self == other {
Ordering::Equal
} else {
self.as_str().cmp(other.as_str())
}
}
}
impl PartialOrd for IString {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Hash for IString {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.0.raw_hash(state);
}
}
impl Debug for IString {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
Debug::fmt(self.as_str(), f)
}
}
impl Display for IString {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
Display::fmt(self.as_str(), f)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::value::inline::string::CAPACITY;
const LONG_A: &str = "interned_string_a";
const LONG_B: &str = "interned_string_b";
#[mockalloc::test]
fn can_intern() {
let x = IString::intern(LONG_A);
let y = IString::intern(LONG_B);
let z = IString::intern(LONG_A);
assert_eq!(x.as_ptr(), z.as_ptr());
assert_ne!(x.as_ptr(), y.as_ptr());
assert_eq!(x.as_str(), LONG_A);
assert_eq!(y.as_str(), LONG_B);
}
#[mockalloc::test]
fn default_interns_string() {
let x = IString::intern(LONG_A);
let y = IString::intern(LONG_A);
assert_eq!(x.as_ptr(), y.as_ptr());
}
#[mockalloc::test]
fn short_strings_are_inline() {
let mut cases: Vec<String> = vec![
String::new(),
"a".into(),
"no".into(),
"yes".into(),
"é".into(), "x".repeat(CAPACITY),
];
cases.dedup();
for s in &cases {
let a = IString::intern(s);
let b = IString::intern(s);
assert!(a.0.is_inline(), "{:?} should be inline", s);
assert_eq!(a.as_str(), s.as_str());
assert_eq!(a.as_bytes(), s.as_bytes());
assert_eq!(a.len(), s.len());
assert_eq!(a.is_empty(), s.is_empty());
assert_eq!(a, b);
assert_eq!(a, IString::from(s.as_str()));
}
}
#[mockalloc::test]
fn inline_heap_boundary() {
let inline = "x".repeat(CAPACITY);
let heap = "x".repeat(CAPACITY + 1);
let a = IString::intern(&inline);
let b = IString::intern(&heap);
assert!(a.0.is_inline());
assert!(!b.0.is_inline());
assert_eq!(a.as_str(), inline);
assert_eq!(b.as_str(), heap);
assert_ne!(a, b);
assert!(a < b); }
#[test]
fn empty_string_is_inline() {
let a = IString::new();
let b = IString::intern("");
assert!(a.0.is_inline());
assert_eq!(a, b);
assert!(a.is_empty());
assert_eq!(a.as_str(), "");
assert!(!IValue::from(a).is_null());
}
#[mockalloc::test]
fn inline_and_heap_mix_in_object() {
let mut obj = crate::IObject::new();
obj.insert("id", IValue::from(1));
obj.insert(LONG_A, IValue::from(2));
obj.insert("no", IValue::from(3));
assert_eq!(obj["id"], IValue::from(1));
assert_eq!(obj[LONG_A], IValue::from(2));
assert_eq!(obj["no"], IValue::from(3));
assert_eq!(obj.len(), 3);
}
#[mockalloc::test]
fn conversions_and_formatting() {
let base = IString::from(LONG_A);
let owned = String::from(LONG_A);
assert_eq!(IString::from(owned.clone()), base); assert_eq!(IString::from(&owned), base); {
let mut o1 = String::from(LONG_A);
assert_eq!(IString::from(o1.as_mut_str()), base); assert_eq!(IString::from(&mut o1), base); }
assert_eq!(String::from(base.clone()), LONG_A);
assert!(base == *LONG_A);
assert!(*LONG_A == base);
assert!(base == owned);
assert!(owned == base);
let s: &str = &base;
assert_eq!(s, LONG_A);
assert_eq!(base.as_bytes(), LONG_A.as_bytes());
assert_eq!(format!("{:?}", base), format!("{:?}", LONG_A));
assert_eq!(format!("{base}"), LONG_A);
assert_eq!(IString::default().as_str(), "");
}
#[mockalloc::test]
fn ord_and_hash() {
use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};
let a = IString::intern(LONG_A);
let a2 = IString::intern(LONG_A);
let b = IString::intern(LONG_B);
assert_eq!(a.cmp(&a2), Ordering::Equal);
assert_eq!(a.partial_cmp(&b), Some(Ordering::Less));
let hash = |s: &IString| {
let mut h = DefaultHasher::new();
s.hash(&mut h);
h.finish()
};
assert_eq!(hash(&a), hash(&a2));
}
}