1use crate::{
2 builtins::{PyStr, PyStrInterned, PyTypeRef},
3 common::lock::PyRwLock,
4 convert::ToPyObject,
5 AsObject, Py, PyExact, PyObject, PyObjectRef, PyPayload, PyRef, PyRefExact, VirtualMachine,
6};
7use std::{
8 borrow::{Borrow, ToOwned},
9 ops::Deref,
10};
11
12#[derive(Debug)]
13pub struct StringPool {
14 inner: PyRwLock<std::collections::HashSet<CachedPyStrRef, ahash::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 #[inline]
35 pub unsafe fn intern<S: InternableString>(
36 &self,
37 s: S,
38 typ: PyTypeRef,
39 ) -> &'static PyStrInterned {
40 if let Some(found) = self.interned(s.as_ref()) {
41 return found;
42 }
43
44 #[cold]
45 fn miss(zelf: &StringPool, s: PyRefExact<PyStr>) -> &'static PyStrInterned {
46 let cache = CachedPyStrRef { inner: s };
47 let inserted = zelf.inner.write().insert(cache.clone());
48 if inserted {
49 let interned = unsafe { cache.as_interned_str() };
50 unsafe { interned.as_object().mark_intern() };
51 interned
52 } else {
53 unsafe {
54 zelf.inner
55 .read()
56 .get(cache.as_ref())
57 .expect("inserted is false")
58 .as_interned_str()
59 }
60 }
61 }
62 let str_ref = s.into_pyref_exact(typ);
63 miss(self, str_ref)
64 }
65
66 #[inline]
67 pub fn interned<S: MaybeInternedString + ?Sized>(
68 &self,
69 s: &S,
70 ) -> Option<&'static PyStrInterned> {
71 if let Some(interned) = s.as_interned() {
72 return Some(interned);
73 }
74 self.inner
75 .read()
76 .get(s.as_ref())
77 .map(|cached| unsafe { cached.as_interned_str() })
78 }
79}
80
81#[derive(Debug, Clone)]
82#[repr(transparent)]
83pub struct CachedPyStrRef {
84 inner: PyRefExact<PyStr>,
85}
86
87impl std::hash::Hash for CachedPyStrRef {
88 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
89 self.inner.as_str().hash(state)
90 }
91}
92
93impl PartialEq for CachedPyStrRef {
94 fn eq(&self, other: &Self) -> bool {
95 self.inner.as_str() == other.inner.as_str()
96 }
97}
98
99impl Eq for CachedPyStrRef {}
100
101impl std::borrow::Borrow<str> for CachedPyStrRef {
102 #[inline]
103 fn borrow(&self) -> &str {
104 self.inner.as_str()
105 }
106}
107
108impl AsRef<str> for CachedPyStrRef {
109 #[inline]
110 fn as_ref(&self) -> &str {
111 self.as_str()
112 }
113}
114
115impl CachedPyStrRef {
116 #[inline]
119 unsafe fn as_interned_str(&self) -> &'static PyStrInterned {
120 std::mem::transmute_copy(self)
121 }
122
123 #[inline]
124 fn as_str(&self) -> &str {
125 self.inner.as_str()
126 }
127}
128
129pub struct PyInterned<T>
130where
131 T: PyPayload,
132{
133 inner: Py<T>,
134}
135
136impl<T: PyPayload> PyInterned<T> {
137 #[inline]
138 pub fn leak(cache: PyRef<T>) -> &'static Self {
139 unsafe { std::mem::transmute(cache) }
140 }
141
142 #[inline]
143 fn as_ptr(&self) -> *const Py<T> {
144 self as *const _ as *const _
145 }
146
147 #[inline]
148 pub fn to_owned(&'static self) -> PyRef<T> {
149 unsafe { (*(&self as *const _ as *const PyRef<T>)).clone() }
150 }
151
152 #[inline]
153 pub fn to_object(&'static self) -> PyObjectRef {
154 self.to_owned().into()
155 }
156}
157
158impl<T: PyPayload> Borrow<PyObject> for PyInterned<T> {
159 #[inline(always)]
160 fn borrow(&self) -> &PyObject {
161 self.inner.borrow()
162 }
163}
164
165impl<T: PyPayload> std::hash::Hash for PyInterned<T> {
168 #[inline(always)]
169 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
170 self.get_id().hash(state)
171 }
172}
173
174impl<T: PyPayload> AsRef<Py<T>> for PyInterned<T> {
175 #[inline(always)]
176 fn as_ref(&self) -> &Py<T> {
177 &self.inner
178 }
179}
180
181impl<T: PyPayload> Deref for PyInterned<T> {
182 type Target = Py<T>;
183 #[inline(always)]
184 fn deref(&self) -> &Self::Target {
185 &self.inner
186 }
187}
188
189impl<T: PyPayload> PartialEq for PyInterned<T> {
190 #[inline(always)]
191 fn eq(&self, other: &Self) -> bool {
192 std::ptr::eq(self, other)
193 }
194}
195
196impl<T: PyPayload> Eq for PyInterned<T> {}
197
198impl<T: PyPayload + std::fmt::Debug> std::fmt::Debug for PyInterned<T> {
199 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
200 std::fmt::Debug::fmt(&**self, f)?;
201 write!(f, "@{:p}", self.as_ptr())
202 }
203}
204
205impl<T: PyPayload> ToPyObject for &'static PyInterned<T> {
206 fn to_pyobject(self, _vm: &VirtualMachine) -> PyObjectRef {
207 self.to_owned().into()
208 }
209}
210
211mod sealed {
212 use crate::{
213 builtins::PyStr,
214 object::{Py, PyExact, PyRefExact},
215 };
216
217 pub trait SealedInternable {}
218
219 impl SealedInternable for String {}
220 impl SealedInternable for &str {}
221 impl SealedInternable for PyRefExact<PyStr> {}
222
223 pub trait SealedMaybeInterned {}
224
225 impl SealedMaybeInterned for str {}
226 impl SealedMaybeInterned for PyExact<PyStr> {}
227 impl SealedMaybeInterned for Py<PyStr> {}
228}
229
230pub trait InternableString
232where
233 Self: sealed::SealedInternable + ToPyObject + AsRef<Self::Interned>,
234 Self::Interned: MaybeInternedString,
235{
236 type Interned: ?Sized;
237 fn into_pyref_exact(self, str_type: PyTypeRef) -> PyRefExact<PyStr>;
238}
239
240impl InternableString for String {
241 type Interned = str;
242 #[inline]
243 fn into_pyref_exact(self, str_type: PyTypeRef) -> PyRefExact<PyStr> {
244 let obj = PyRef::new_ref(PyStr::from(self), str_type, None);
245 unsafe { PyRefExact::new_unchecked(obj) }
246 }
247}
248
249impl InternableString for &str {
250 type Interned = str;
251 #[inline]
252 fn into_pyref_exact(self, str_type: PyTypeRef) -> PyRefExact<PyStr> {
253 self.to_owned().into_pyref_exact(str_type)
254 }
255}
256
257impl InternableString for PyRefExact<PyStr> {
258 type Interned = Py<PyStr>;
259 #[inline]
260 fn into_pyref_exact(self, _str_type: PyTypeRef) -> PyRefExact<PyStr> {
261 self
262 }
263}
264
265pub trait MaybeInternedString:
266 AsRef<str> + crate::dictdatatype::DictKey + sealed::SealedMaybeInterned
267{
268 fn as_interned(&self) -> Option<&'static PyStrInterned>;
269}
270
271impl MaybeInternedString for str {
272 #[inline(always)]
273 fn as_interned(&self) -> Option<&'static PyStrInterned> {
274 None
275 }
276}
277
278impl MaybeInternedString for PyExact<PyStr> {
279 #[inline(always)]
280 fn as_interned(&self) -> Option<&'static PyStrInterned> {
281 None
282 }
283}
284
285impl MaybeInternedString for Py<PyStr> {
286 #[inline(always)]
287 fn as_interned(&self) -> Option<&'static PyStrInterned> {
288 if self.as_object().is_interned() {
289 Some(unsafe { std::mem::transmute::<&Py<PyStr>, &PyInterned<PyStr>>(self) })
290 } else {
291 None
292 }
293 }
294}
295
296impl PyObject {
297 #[inline]
298 pub fn as_interned_str(&self, vm: &crate::VirtualMachine) -> Option<&'static PyStrInterned> {
299 let s: Option<&Py<PyStr>> = self.downcast_ref();
300 if self.is_interned() {
301 s.unwrap().as_interned()
302 } else if let Some(s) = s {
303 vm.ctx.interned_str(s.as_str())
304 } else {
305 None
306 }
307 }
308}