1use rustpython_common::wtf8::{Wtf8, Wtf8Buf};
2
3use crate::{
4 AsObject, Py, PyExact, PyObject, PyObjectRef, PyPayload, PyRef, PyRefExact, VirtualMachine,
5 builtins::{PyStr, PyStrInterned, PyTypeRef},
6 common::lock::PyRwLock,
7 convert::ToPyObject,
8};
9use alloc::borrow::ToOwned;
10use core::{borrow::Borrow, ops::Deref};
11
12#[derive(Debug)]
13pub(crate) struct StringPool {
14 inner: PyRwLock<std::collections::HashSet<CachedPyStrRef, rapidhash::quality::RandomState>>,
15}
16
17impl Default for StringPool {
18 fn default() -> Self {
19 Self {
20 inner: PyRwLock::new(Default::default()),
21 }
22 }
23}
24
25impl Clone for StringPool {
26 fn clone(&self) -> Self {
27 Self {
28 inner: PyRwLock::new(self.inner.read().clone()),
29 }
30 }
31}
32
33impl StringPool {
34 #[cfg(all(unix, feature = "threading", feature = "host_env"))]
40 pub(crate) unsafe fn reinit_after_fork(&self) {
41 unsafe { crate::common::lock::reinit_rwlock_after_fork(&self.inner) };
42 }
43
44 #[inline]
45 pub(crate) unsafe fn intern<S: InternableString>(
46 &self,
47 s: S,
48 typ: PyTypeRef,
49 ) -> &'static PyStrInterned {
50 if let Some(found) = self.interned(s.as_ref()) {
51 return found;
52 }
53
54 #[cold]
55 fn miss(zelf: &StringPool, s: PyRefExact<PyStr>) -> &'static PyStrInterned {
56 let cache = CachedPyStrRef { inner: s };
57 let mut inner = zelf.inner.write();
58 if inner.insert(cache.clone()) {
59 drop(inner);
60 let interned = unsafe { cache.as_interned_str() };
61 unsafe { interned.as_object().mark_intern() };
68 interned
69 } else {
70 unsafe {
71 inner
72 .get(cache.as_ref())
73 .expect("inserted is false")
74 .as_interned_str()
75 }
76 }
77 }
78 let str_ref = s.into_pyref_exact(typ);
79 miss(self, str_ref)
80 }
81
82 #[inline]
83 pub(crate) fn interned<S: MaybeInternedString + ?Sized>(
84 &self,
85 s: &S,
86 ) -> Option<&'static PyStrInterned> {
87 if let Some(interned) = s.as_interned() {
88 return Some(interned);
89 }
90 self.inner
91 .read()
92 .get(s.as_ref())
93 .map(|cached| unsafe { cached.as_interned_str() })
94 }
95}
96
97#[derive(Debug, Clone)]
98#[repr(transparent)]
99pub(crate) struct CachedPyStrRef {
100 inner: PyRefExact<PyStr>,
101}
102
103impl core::hash::Hash for CachedPyStrRef {
104 fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
105 self.inner.as_wtf8().hash(state)
106 }
107}
108
109impl PartialEq for CachedPyStrRef {
110 fn eq(&self, other: &Self) -> bool {
111 self.inner.as_wtf8() == other.inner.as_wtf8()
112 }
113}
114
115impl Eq for CachedPyStrRef {}
116
117impl core::borrow::Borrow<Wtf8> for CachedPyStrRef {
118 #[inline]
119 fn borrow(&self) -> &Wtf8 {
120 self.as_wtf8()
121 }
122}
123
124impl AsRef<Wtf8> for CachedPyStrRef {
125 #[inline]
126 fn as_ref(&self) -> &Wtf8 {
127 self.as_wtf8()
128 }
129}
130
131impl CachedPyStrRef {
132 #[inline]
135 const unsafe fn as_interned_str(&self) -> &'static PyStrInterned {
136 unsafe { core::mem::transmute_copy(self) }
137 }
138
139 #[inline]
140 fn as_wtf8(&self) -> &Wtf8 {
141 self.inner.as_wtf8()
142 }
143}
144
145#[repr(transparent)]
146pub struct PyInterned<T> {
147 inner: Py<T>,
148}
149
150impl PyInterned<PyStr> {
151 #[inline]
157 pub fn as_str(&self) -> &str {
158 self.inner
159 .to_str()
160 .expect("interned str is always valid UTF-8")
161 }
162}
163
164impl<T: PyPayload> PyInterned<T> {
165 #[inline]
166 pub fn leak(cache: PyRef<T>) -> &'static Self {
167 unsafe { core::mem::transmute(cache) }
168 }
169
170 #[inline]
171 const fn as_ptr(&self) -> *const Py<T> {
172 self as *const _ as *const _
173 }
174
175 #[inline]
176 pub fn to_owned(&'static self) -> PyRef<T> {
177 unsafe { (*(&self as *const _ as *const PyRef<T>)).clone() }
178 }
179
180 #[inline]
181 pub fn to_object(&'static self) -> PyObjectRef {
182 self.to_owned().into()
183 }
184}
185
186impl<T: PyPayload> Borrow<PyObject> for PyInterned<T> {
187 #[inline(always)]
188 fn borrow(&self) -> &PyObject {
189 self.inner.borrow()
190 }
191}
192
193impl<T: PyPayload> core::hash::Hash for PyInterned<T> {
196 #[inline(always)]
197 fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
198 self.get_id().hash(state)
199 }
200}
201
202impl<T> AsRef<Py<T>> for PyInterned<T> {
203 #[inline(always)]
204 fn as_ref(&self) -> &Py<T> {
205 &self.inner
206 }
207}
208
209impl<T> Deref for PyInterned<T> {
210 type Target = Py<T>;
211 #[inline(always)]
212 fn deref(&self) -> &Self::Target {
213 &self.inner
214 }
215}
216
217impl<T: PyPayload> PartialEq for PyInterned<T> {
218 #[inline(always)]
219 fn eq(&self, other: &Self) -> bool {
220 core::ptr::eq(self, other)
221 }
222}
223
224impl<T: PyPayload> Eq for PyInterned<T> {}
225
226impl<T: core::fmt::Debug + PyPayload> core::fmt::Debug for PyInterned<T> {
227 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
228 core::fmt::Debug::fmt(&**self, f)?;
229 write!(f, "@{:p}", self.as_ptr())
230 }
231}
232
233impl<T: PyPayload> ToPyObject for &'static PyInterned<T> {
234 fn to_pyobject(self, _vm: &VirtualMachine) -> PyObjectRef {
235 self.to_owned().into()
236 }
237}
238
239mod sealed {
240 use rustpython_common::wtf8::{Wtf8, Wtf8Buf};
241
242 use crate::{
243 builtins::PyStr,
244 object::{Py, PyExact, PyRefExact},
245 };
246
247 pub trait SealedInternable {}
248
249 impl SealedInternable for String {}
250 impl SealedInternable for &str {}
251 impl SealedInternable for Wtf8Buf {}
252 impl SealedInternable for &Wtf8 {}
253 impl SealedInternable for PyRefExact<PyStr> {}
254
255 pub trait SealedMaybeInterned {}
256
257 impl SealedMaybeInterned for str {}
258 impl SealedMaybeInterned for Wtf8 {}
259 impl SealedMaybeInterned for PyExact<PyStr> {}
260 impl SealedMaybeInterned for Py<PyStr> {}
261}
262
263pub trait InternableString: sealed::SealedInternable + ToPyObject + AsRef<Self::Interned> {
265 type Interned: MaybeInternedString + ?Sized;
266
267 fn into_pyref_exact(self, str_type: PyTypeRef) -> PyRefExact<PyStr>;
268}
269
270impl InternableString for String {
271 type Interned = str;
272 #[inline]
273 fn into_pyref_exact(self, str_type: PyTypeRef) -> PyRefExact<PyStr> {
274 let obj = PyRef::new_ref(PyStr::from(self), str_type, None);
275 unsafe { PyRefExact::new_unchecked(obj) }
276 }
277}
278
279impl InternableString for &str {
280 type Interned = str;
281
282 #[inline]
283 fn into_pyref_exact(self, str_type: PyTypeRef) -> PyRefExact<PyStr> {
284 self.to_owned().into_pyref_exact(str_type)
285 }
286}
287
288impl InternableString for Wtf8Buf {
289 type Interned = Wtf8;
290
291 fn into_pyref_exact(self, str_type: PyTypeRef) -> PyRefExact<PyStr> {
292 let obj = PyRef::new_ref(PyStr::from(self), str_type, None);
293 unsafe { PyRefExact::new_unchecked(obj) }
294 }
295}
296
297impl InternableString for &Wtf8 {
298 type Interned = Wtf8;
299
300 fn into_pyref_exact(self, str_type: PyTypeRef) -> PyRefExact<PyStr> {
301 self.to_owned().into_pyref_exact(str_type)
302 }
303}
304
305impl InternableString for PyRefExact<PyStr> {
306 type Interned = Py<PyStr>;
307
308 #[inline]
309 fn into_pyref_exact(self, _str_type: PyTypeRef) -> PyRefExact<PyStr> {
310 self
311 }
312}
313
314pub trait MaybeInternedString:
315 AsRef<Wtf8> + crate::dict_inner::DictKey + sealed::SealedMaybeInterned
316{
317 fn as_interned(&self) -> Option<&'static PyStrInterned>;
318}
319
320impl MaybeInternedString for str {
321 #[inline(always)]
322 fn as_interned(&self) -> Option<&'static PyStrInterned> {
323 None
324 }
325}
326
327impl MaybeInternedString for Wtf8 {
328 #[inline(always)]
329 fn as_interned(&self) -> Option<&'static PyStrInterned> {
330 None
331 }
332}
333
334impl MaybeInternedString for PyExact<PyStr> {
335 #[inline(always)]
336 fn as_interned(&self) -> Option<&'static PyStrInterned> {
337 None
338 }
339}
340
341impl MaybeInternedString for Py<PyStr> {
342 #[inline(always)]
343 fn as_interned(&self) -> Option<&'static PyStrInterned> {
344 if self.as_object().is_interned() {
345 Some(unsafe { core::mem::transmute::<&Self, &PyInterned<PyStr>>(self) })
346 } else {
347 None
348 }
349 }
350}
351
352impl PyObject {
353 #[inline]
354 pub fn as_interned_str(&self, vm: &crate::VirtualMachine) -> Option<&'static PyStrInterned> {
355 let s: Option<&Py<PyStr>> = self.downcast_ref();
356 if self.is_interned() {
357 s.unwrap().as_interned()
358 } else if let Some(s) = s {
359 vm.ctx.interned_str(s.as_wtf8())
360 } else {
361 None
362 }
363 }
364}