1use crate::{
4 AsObject, PyPayload,
5 builtins::{PyBaseObject, PyType, PyTypeRef, descriptor::PyWrapper},
6 function::{ItemDoc, PyMethodDef, plain_doc},
7 object::Py,
8 types::{PyTypeFlags, PyTypeSlots, SLOT_DEFS, fn_addr, hash_not_implemented},
9 vm::{Context, VirtualMachine},
10};
11use rustpython_common::static_cell;
12
13#[cfg(feature = "doc")]
19pub fn add_operators(class: &'static Py<PyType>, ctx: &Context, attr_docs: &[(&str, u32, u32)]) {
20 add_operators_inner(class, ctx, |name| match attr_doc(attr_docs, name) {
21 Some((offset, len)) if len != 0 => (offset, len),
22 _ => (0, 0),
23 })
24}
25
26#[cfg(not(feature = "doc"))]
27pub fn add_operators(class: &'static Py<PyType>, ctx: &Context, attr_docs: &[&str]) {
28 add_operators_inner(class, ctx, |name| {
29 if attr_name_present(attr_docs, name) {
30 (0, 1)
32 } else {
33 (0, 0)
34 }
35 })
36}
37
38fn add_operators_inner(
39 class: &'static Py<PyType>,
40 ctx: &Context,
41 mut plain_span: impl FnMut(&str) -> (u32, u32),
42) {
43 for def in SLOT_DEFS {
44 let (plain_off, plain_len) = plain_span(def.name);
45 if def.name == "__new__" {
47 continue;
48 }
49
50 if def.name == "__hash__"
52 && class.slots().hash.load().is_some_and(|h| {
53 fn_addr(h) == fn_addr(hash_not_implemented as crate::types::HashFunc)
54 })
55 {
56 class.set_attr(ctx.names.__hash__, ctx.none.clone().into());
57 continue;
58 }
59
60 if def.name == "__getattr__" {
64 continue;
65 }
66
67 let Some(slot_func) = def.accessor.get_slot_func_with_op(class.slots(), def.op) else {
69 continue;
70 };
71
72 let attr_name = ctx.intern_str(def.name);
74 if class.attributes().contains(attr_name) {
75 continue;
76 }
77
78 let wrapper = PyWrapper {
80 typ: class,
81 name: attr_name,
82 wrapped: slot_func,
83 doc: Some(def.doc),
84 plain_off,
85 plain_len,
86 };
87 class.set_attr(attr_name, wrapper.into_ref(ctx).into());
88 }
89}
90
91pub trait StaticType {
92 fn static_cell() -> &'static static_cell::StaticCell<PyTypeRef>;
94
95 #[inline]
96 #[must_use]
97 fn static_metaclass() -> &'static Py<PyType> {
98 PyType::static_type()
99 }
100
101 #[inline]
102 #[must_use]
103 fn static_baseclass() -> &'static Py<PyType> {
104 PyBaseObject::static_type()
105 }
106
107 #[inline]
108 #[must_use]
109 fn static_type() -> &'static Py<PyType> {
110 #[cold]
111 fn fail() -> ! {
112 panic!(
113 "static type has not been initialized. e.g. the native types defined in different module may be used before importing library."
114 );
115 }
116 Self::static_cell().get().unwrap_or_else(|| fail())
117 }
118
119 #[must_use]
120 fn init_manually(typ: PyTypeRef) -> &'static Py<PyType> {
121 let cell = Self::static_cell();
122 cell.set(typ)
123 .unwrap_or_else(|_| panic!("double initialization from init_manually"));
124 let typ = cell.get().unwrap();
125 typ.as_object().make_immortal();
126 typ
127 }
128
129 #[must_use]
130 fn init_builtin_type() -> &'static Py<PyType>
131 where
132 Self: PyClassImpl,
133 {
134 let typ = Self::create_static_type();
135 let cell = Self::static_cell();
136 cell.set(typ)
137 .unwrap_or_else(|_| panic!("double initialization of {}", Self::NAME));
138 let typ = cell.get().unwrap();
139 typ.as_object().make_immortal();
140 typ
141 }
142
143 #[must_use]
144 fn create_static_type() -> PyTypeRef
145 where
146 Self: PyClassImpl,
147 {
148 let mut slots = Self::make_slots();
151 if slots.itemsize == 0 {
152 slots.itemsize = Self::static_baseclass().slots.itemsize;
153 }
154 PyType::new_static(
155 Self::static_baseclass().to_owned(),
156 Default::default(),
157 slots,
158 Self::static_metaclass().to_owned(),
159 )
160 .unwrap()
161 }
162}
163
164pub trait PyClassDef {
165 const NAME: &'static str;
166 const MODULE_NAME: Option<&'static str>;
167 const TP_NAME: &'static str;
168 const DOC: ItemDoc = ItemDoc::NONE;
169 #[cfg(feature = "doc")]
172 const ATTR_DOCS: &'static [(&'static str, u32, u32)] = &[];
173 #[cfg(not(feature = "doc"))]
175 const ATTR_DOCS: &'static [&'static str] = &[];
176 const BASICSIZE: usize;
177 const ITEMSIZE: usize = 0;
178 const UNHASHABLE: bool = false;
179 const MEMBERS: &'static [crate::builtins::descriptor::PyMemberSpec] = &[];
180
181 fn assert_member_layout() {}
182
183 type Base: PyClassDef;
186}
187
188const fn cmp_str(left: &str, right: &str) -> i8 {
189 let left = left.as_bytes();
190 let right = right.as_bytes();
191 let n = if left.len() < right.len() {
192 left.len()
193 } else {
194 right.len()
195 };
196 let mut i = 0;
197 while i < n {
198 if left[i] != right[i] {
199 return if left[i] < right[i] { -1 } else { 1 };
200 }
201 i += 1;
202 }
203 if left.len() == right.len() {
204 0
205 } else if left.len() < right.len() {
206 -1
207 } else {
208 1
209 }
210}
211
212#[must_use]
213pub const fn attr_name_present(table: &[&str], name: &str) -> bool {
214 let mut lo = 0;
215 let mut hi = table.len();
216 while lo < hi {
217 let mid = (lo + hi) / 2;
218 let ord = cmp_str(table[mid], name);
219 if ord == 0 {
220 return true;
221 } else if ord < 0 {
222 lo = mid + 1;
223 } else {
224 hi = mid;
225 }
226 }
227 false
228}
229
230#[must_use]
232pub fn class_attr_item_doc<T: PyClassDef + ?Sized>(name: &str) -> ItemDoc {
233 #[cfg(feature = "doc")]
234 if let Some((offset, len)) = attr_doc(T::ATTR_DOCS, name) {
235 if len != 0 {
236 return ItemDoc {
237 text: None,
238 offset,
239 len,
240 };
241 }
242 if offset == u32::MAX {
243 return ItemDoc::EMPTY;
244 }
245 }
246 #[cfg(not(feature = "doc"))]
247 let _ = name;
248 ItemDoc::NONE
249}
250
251pub fn assign_missing_doc(vm: &VirtualMachine, class: &Py<PyType>, doc: ItemDoc) {
253 let Some(text) = plain_doc(doc) else {
254 return;
255 };
256 let doc_name = identifier!(vm, __doc__);
257 let missing = class
258 .attributes()
259 .get(doc_name)
260 .is_none_or(|value| value.is(&vm.ctx.none));
261 if missing {
262 class.set_attr(doc_name, vm.ctx.new_str(text).into());
263 }
264}
265
266#[must_use]
268#[inline(never)]
269pub const fn attr_doc(table: &[(&str, u32, u32)], name: &str) -> Option<(u32, u32)> {
270 let mut lo = 0;
271 let mut hi = table.len();
272 while lo < hi {
273 let mid = (lo + hi) / 2;
274 let ord = cmp_str(table[mid].0, name);
275 if ord == 0 {
276 return Some((table[mid].1, table[mid].2));
277 } else if ord < 0 {
278 lo = mid + 1;
279 } else {
280 hi = mid;
281 }
282 }
283 None
284}
285
286pub trait PyClassImpl: PyClassDef {
287 const TP_FLAGS: PyTypeFlags = PyTypeFlags::empty();
288
289 const INTERNAL_DOC: ItemDoc = ItemDoc::NONE;
291
292 const METHOD_DEFS: &'static [PyMethodDef];
293
294 fn impl_extend_class(ctx: &'static Context, class: &'static Py<PyType>);
295
296 fn extend_slots(slots: &mut PyTypeSlots);
297
298 fn extend_class(ctx: &'static Context, class: &'static Py<PyType>)
299 where
300 Self: Sized,
301 {
302 #[cfg(debug_assertions)]
303 debug_assert!(
304 class
305 .slots()
306 .flags
307 .contains(&PyTypeFlags::_CREATED_WITH_FLAGS)
308 );
309
310 let _ = ctx.intern_str(Self::NAME); if Self::TP_FLAGS.contains(&PyTypeFlags::HAS_DICT)
313 && !Self::MEMBERS.iter().any(|member| member.name == "__dict__")
314 {
315 let __dict__ = identifier!(ctx, __dict__);
316 class.set_attr(
317 __dict__,
318 ctx.new_static_getset(
319 "__dict__",
320 class,
321 crate::builtins::object::object_get_dict,
322 crate::builtins::object::object_set_dict,
323 )
324 .into(),
325 );
326 }
327
328 Self::assert_member_layout();
329 for member in Self::MEMBERS {
330 class.set_str_attr(
331 member.name,
332 ctx.new_member(
333 member.name,
334 member.kind,
335 member.offset,
336 member.flags,
337 class,
338 member.doc,
339 ),
340 ctx,
341 );
342 }
343
344 Self::impl_extend_class(ctx, class);
345
346 let doc_attr_name = identifier!(ctx, __doc__);
349 if class.attributes().get(doc_attr_name).is_none() {
350 let doc =
351 plain_doc(Self::DOC).map_or_else(|| ctx.none(), |doc| ctx.new_str(doc).into());
352 class.set_attr(doc_attr_name, doc);
353 }
354
355 if let Some(module_name) = Self::MODULE_NAME {
356 let module_key = identifier!(ctx, __module__);
357 let has_getset = class
360 .attributes()
361 .get(module_key)
362 .is_some_and(|v| v.downcastable::<crate::builtins::PyGetSet>());
363 if !has_getset {
364 class.set_attr(module_key, ctx.new_str(module_name).into());
365 }
366 }
367
368 if let Some(slot_new) = class.slots().new.load() {
372 let object_new = ctx.types.object_type.slots().new.load();
373 let is_object_itself = core::ptr::eq(class, ctx.types.object_type);
374 let is_inherited_from_object = !is_object_itself
375 && object_new.is_some_and(|obj_new| fn_addr(slot_new) == fn_addr(obj_new));
376
377 if !is_inherited_from_object {
378 let bound_new =
379 ctx.slot_new_wrapper
380 .build_bound_method(ctx, class.to_owned().into(), class);
381 class.set_attr(identifier!(ctx, __new__), bound_new.into());
382 }
383 }
384
385 add_operators(class, ctx, Self::ATTR_DOCS);
387
388 let mro = {
392 let guard = class.mro.read();
393 guard[1..].to_vec()
394 };
395 for base in &mro {
396 class.inherit_slots(base);
397 }
398
399 class.extend_methods(class.slots().methods, ctx);
400 }
401
402 #[must_use]
403 fn make_static_type() -> PyTypeRef
404 where
405 Self: StaticType + Sized,
406 {
407 let typ = Self::static_cell().get_or_init(|| {
408 let typ = Self::create_static_type();
409 Self::extend_class(Context::genesis(), unsafe {
410 let r: &Py<PyType> = &typ;
412 let r: &'static Py<PyType> = core::mem::transmute(r);
413 r
414 });
415 typ
416 });
417 typ.as_object().make_immortal();
422 (*typ).to_owned()
423 }
424
425 fn make_slots() -> PyTypeSlots {
426 let mut slots = PyTypeSlots {
427 flags: crate::types::AtomicPyTypeFlags::from_plain(Self::TP_FLAGS),
428 name: Self::TP_NAME,
429 basicsize: Self::BASICSIZE,
430 itemsize: Self::ITEMSIZE,
431 doc: {
432 let internal = Self::INTERNAL_DOC;
433 if internal.text.is_some() || internal.len != 0 {
434 internal
435 } else {
436 Self::DOC
437 }
438 },
439 methods: Self::METHOD_DEFS,
440 ..Default::default()
441 };
442
443 if Self::UNHASHABLE {
444 slots.hash.store(Some(hash_not_implemented));
445 }
446
447 Self::extend_slots(&mut slots);
448 slots
449 }
450}
451
452pub trait PySubclass: crate::PyPayload {
461 type Base: crate::PyPayload;
462
463 fn as_base(&self) -> &Self::Base;
465}