1#![deny(2 unsafe_op_in_unsafe_fn,3 clippy::missing_safety_doc,4 clippy::undocumented_unsafe_blocks5)]6#![warn(clippy::pedantic, clippy::nursery)]7use std::{8 borrow::Cow,9 cell::RefCell,10 fmt::{self, Display},11 hash::{BuildHasherDefault, Hash, Hasher},12 ops::Deref,13 str,14};1516use hashbrown::HashMap;17use jrsonnet_gcmodule::Trace;18use rustc_hash::FxHasher;1920mod inner;21use inner::Inner;2223242526#[derive(Clone, PartialOrd, Ord, Eq)]27pub struct IStr(Inner);28impl Trace for IStr {29 fn is_type_tracked() -> bool {30 false31 }32}3334impl IStr {35 #[must_use]36 pub fn empty() -> Self {37 "".into()38 }39 #[must_use]40 pub fn as_str(&self) -> &str {41 self as &str42 }4344 #[must_use]45 pub fn cast_bytes(self) -> IBytes {46 IBytes(self.0.clone())47 }48}4950impl Deref for IStr {51 type Target = str;5253 fn deref(&self) -> &Self::Target {54 55 unsafe { self.0.as_str_unchecked() }56 }57}5859impl PartialEq for IStr {60 fn eq(&self, other: &Self) -> bool {61 62 Inner::ptr_eq(&self.0, &other.0)63 }64}6566impl PartialEq<str> for IStr {67 fn eq(&self, other: &str) -> bool {68 self as &str == other69 }70}7172impl Hash for IStr {73 fn hash<H: Hasher>(&self, state: &mut H) {74 75 state.write_usize(Inner::as_ptr(&self.0).cast::<()>() as usize);76 }77}7879impl Drop for IStr {80 fn drop(&mut self) {81 #[cold]82 #[inline(never)]83 fn unpool(inner: &Inner) {84 85 let res = POOL.try_with(|pool| pool.borrow_mut().remove(inner));86 if res.is_ok() {87 debug_assert_eq!(Inner::strong_count(inner), 1);88 }89 }90 91 if Inner::strong_count(&self.0) <= 2 {92 unpool(&self.0);93 }94 }95}9697impl fmt::Debug for IStr {98 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {99 fmt::Debug::fmt(self as &str, f)100 }101}102103impl Display for IStr {104 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {105 fmt::Display::fmt(self as &str, f)106 }107}108109110#[derive(Clone, PartialOrd, Ord, Eq)]111pub struct IBytes(Inner);112impl Trace for IBytes {113 fn is_type_tracked() -> bool {114 false115 }116}117118impl IBytes {119 #[must_use]120 pub fn cast_str(self) -> Option<IStr> {121 if Inner::check_utf8(&self.0) {122 Some(IStr(self.0.clone()))123 } else {124 None125 }126 }127 128 129 unsafe fn cast_str_unchecked(self) -> IStr {130 131 unsafe { Inner::assume_utf8(&self.0) };132 IStr(self.0.clone())133 }134135 #[must_use]136 pub const fn as_slice(&self) -> &[u8] {137 self.0.as_slice()138 }139}140141impl Deref for IBytes {142 type Target = [u8];143144 fn deref(&self) -> &Self::Target {145 self.0.as_slice()146 }147}148149impl PartialEq for IBytes {150 fn eq(&self, other: &Self) -> bool {151 152 Inner::ptr_eq(&self.0, &other.0)153 }154}155156impl Hash for IBytes {157 fn hash<H: Hasher>(&self, state: &mut H) {158 159 state.write_usize(Inner::as_ptr(&self.0).cast::<()>() as usize);160 }161}162163impl Drop for IBytes {164 fn drop(&mut self) {165 #[cold]166 #[inline(never)]167 fn unpool(inner: &Inner) {168 169 let res = POOL.try_with(|pool| pool.borrow_mut().remove(inner));170 if res.is_ok() {171 debug_assert_eq!(Inner::strong_count(inner), 1);172 }173 }174 175 if Inner::strong_count(&self.0) <= 2 {176 unpool(&self.0);177 }178 }179}180181impl fmt::Debug for IBytes {182 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {183 fmt::Debug::fmt(self as &[u8], f)184 }185}186187impl<'c> From<Cow<'c, str>> for IStr {188 fn from(v: Cow<'c, str>) -> Self {189 intern_str(&v)190 }191}192impl From<&str> for IStr {193 fn from(v: &str) -> Self {194 intern_str(v)195 }196}197impl From<String> for IStr {198 fn from(s: String) -> Self {199 s.as_str().into()200 }201}202impl From<&[u8]> for IBytes {203 fn from(v: &[u8]) -> Self {204 intern_bytes(v)205 }206}207208#[cfg(feature = "serde")]209impl serde::Serialize for IStr {210 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>211 where212 S: serde::Serializer,213 {214 self.as_str().serialize(serializer)215 }216}217218#[cfg(feature = "serde")]219impl<'de> serde::Deserialize<'de> for IStr {220 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>221 where222 D: serde::Deserializer<'de>,223 {224 let str = <&str>::deserialize(deserializer)?;225 Ok(intern_str(str))226 }227}228229#[cfg(feature = "structdump")]230impl structdump::Codegen for IStr {231 fn gen_code(232 &self,233 res: &mut structdump::CodegenResult,234 _unique: bool,235 ) -> structdump::TokenStream {236 let s: &str = self;237 res.add_code(238 structdump::quote! {239 structdump_import::IStr::from(#s)240 },241 Some(structdump::quote![structdump_import::IStr]),242 false,243 )244 }245}246247thread_local! {248 static POOL: RefCell<HashMap<Inner, (), BuildHasherDefault<FxHasher>>> = RefCell::new(HashMap::with_capacity_and_hasher(200, BuildHasherDefault::default()));249}250251#[must_use]252pub fn intern_bytes(bytes: &[u8]) -> IBytes {253 POOL.with(|pool| {254 let mut pool = pool.borrow_mut();255 let entry = pool.raw_entry_mut().from_key(bytes);256 match entry {257 hashbrown::hash_map::RawEntryMut::Occupied(mut i) => {258 IBytes(i.get_key_value().0.clone())259 }260 hashbrown::hash_map::RawEntryMut::Vacant(e) => {261 let (k, _) = e.insert(Inner::new_bytes(bytes), ());262 IBytes(k.clone())263 }264 }265 })266}267268#[must_use]269pub fn intern_str(str: &str) -> IStr {270 271 unsafe { intern_bytes(str.as_bytes()).cast_str_unchecked() }272}