git.delta.rocks / jrsonnet / refs/commits / 0831da3ed8d9

difftreelog

source

crates/jrsonnet-interner/src/lib.rs5.6 KiBsourcehistory
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;2223/// Interned string24///25/// Provides O(1) comparsions and hashing, cheap copy, and cheap conversion to [`IBytes`]26#[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		// SAFETY: Inner::check_utf8 is called on IStr construction, data is utf-855		unsafe { self.0.as_str_unchecked() }56	}57}5859impl PartialEq for IStr {60	fn eq(&self, other: &Self) -> bool {61		// all IStr should be inlined into same pool62		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		// IStr is always obtained from pool, where no string have duplicate, thus every unique string has unique address75		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			// May fail on program termination85			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		// First reference - current object, second - POOL91		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}108109/// Interned byte array110#[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	/// # Safety128	/// data should be valid utf8129	unsafe fn cast_str_unchecked(self) -> IStr {130		// SAFETY: data is utf8131		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		// all IStr should be inlined into same pool152		Inner::ptr_eq(&self.0, &other.0)153	}154}155156impl Hash for IBytes {157	fn hash<H: Hasher>(&self, state: &mut H) {158		// IBytes is always obtained from pool, where no string have duplicate, thus every unique string has unique address159		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			// May fail on program termination169			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		// First reference - current object, second - POOL175		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	// SAFETY: Rust strings always utf8271	unsafe { intern_bytes(str.as_bytes()).cast_str_unchecked() }272}