Skip to main content

pyo3/impl_/
pymodule.rs

1// TODO https://github.com/PyO3/pyo3/issues/5487
2#![allow(clippy::undocumented_unsafe_blocks)]
3
4//! Implementation details of `#[pymodule]` which need to be accessible from proc-macro generated code.
5#[allow(unused_imports, reason = "conditionally used")]
6use crate::platform::prelude::*;
7use core::{
8    cell::UnsafeCell,
9    ffi::CStr,
10    ffi::{c_int, c_void},
11    marker::PhantomData,
12};
13
14#[cfg(all(
15    not(any(PyPy, GraalPy)),
16    not(all(windows, Py_LIMITED_API, not(Py_3_10))),
17))]
18use core::sync::atomic::Ordering;
19
20#[cfg(all(
21    not(any(PyPy, GraalPy)),
22    not(all(windows, Py_LIMITED_API, not(Py_3_10))),
23    target_has_atomic = "64",
24))]
25use core::sync::atomic::AtomicI64;
26#[cfg(all(
27    not(any(PyPy, GraalPy)),
28    not(all(windows, Py_LIMITED_API, not(Py_3_10))),
29    not(target_has_atomic = "64"),
30))]
31use portable_atomic::AtomicI64;
32
33#[cfg(not(any(PyPy, GraalPy)))]
34use crate::exceptions::PyImportError;
35use crate::ffi_ptr_ext::FfiPtrExt;
36#[cfg(any(not(all(Py_LIMITED_API, Py_GIL_DISABLED)), Py_3_15))]
37use crate::internal_tricks::array_ptr_as_mut;
38use crate::prelude::PyTypeMethods;
39use crate::{err::error_on_minusone, py_result_ext::PyResultExt};
40use crate::{
41    ffi,
42    impl_::pyfunction::PyFunctionDef,
43    types::{PyModule, PyModuleMethods},
44    Bound, PyClass, PyResult, PyTypeInfo,
45};
46use crate::{
47    sync::PyOnceLock,
48    types::{any::PyAnyMethods, dict::PyDictMethods, PyDict},
49    Py, PyAny, Python,
50};
51
52/// `Sync` wrapper of `ffi::PyModuleDef`.
53pub struct ModuleDef {
54    // wrapped in UnsafeCell so that Rust compiler treats this as interior mutability
55    #[cfg(not(all(Py_LIMITED_API, Py_GIL_DISABLED)))]
56    ffi_def: UnsafeCell<ffi::PyModuleDef>,
57    name: &'static CStr,
58    #[cfg(Py_3_15)]
59    slots: &'static PyModuleSlots,
60    /// Interpreter ID where module was initialized (not applicable on PyPy).
61    #[cfg(all(
62        not(any(PyPy, GraalPy)),
63        not(all(windows, Py_LIMITED_API, not(Py_3_10)))
64    ))]
65    interpreter: AtomicI64,
66    /// Initialized module object, cached to avoid reinitialization.
67    module: PyOnceLock<Py<PyModule>>,
68}
69
70unsafe impl Sync for ModuleDef {}
71
72impl ModuleDef {
73    /// Make new module definition with given module name.
74    pub const fn new(
75        name: &'static CStr,
76        doc: &'static CStr,
77        slots: &'static PrimaryModuleSlots,
78        secondary_slots: &'static SecondaryModuleSlots,
79    ) -> Self {
80        // This is only used in PyO3 for append_to_inittab on Python 3.15 and newer.
81        // There could also be other tools that need the legacy init hook.
82        #[cfg(not(all(Py_LIMITED_API, Py_GIL_DISABLED)))]
83        let ffi_def = UnsafeCell::new(ffi::PyModuleDef {
84            m_base: ffi::PyModuleDef_HEAD_INIT,
85            m_name: name.as_ptr(),
86            m_doc: doc.as_ptr(),
87            m_size: 0,
88            m_methods: core::ptr::null_mut(),
89            m_slots: array_ptr_as_mut({
90                cfg_select! {
91                    Py_3_15 => secondary_slots.0.get(),
92                    _ => slots.0.get(),
93                }
94            }),
95            m_traverse: None,
96            m_clear: None,
97            m_free: None,
98        });
99
100        #[cfg(any(not(Py_3_15), all(Py_LIMITED_API, Py_GIL_DISABLED)))]
101        let _ = secondary_slots;
102        #[cfg(all(Py_LIMITED_API, Py_GIL_DISABLED))]
103        let _ = doc;
104
105        ModuleDef {
106            #[cfg(not(all(Py_LIMITED_API, Py_GIL_DISABLED)))]
107            ffi_def,
108            name,
109            #[cfg(Py_3_15)]
110            slots,
111            // -1 is never expected to be a valid interpreter ID
112            #[cfg(all(
113                not(any(PyPy, GraalPy)),
114                not(all(windows, Py_LIMITED_API, not(Py_3_10)))
115            ))]
116            interpreter: AtomicI64::new(-1),
117            module: PyOnceLock::new(),
118        }
119    }
120
121    #[cfg(not(all(Py_LIMITED_API, Py_GIL_DISABLED)))]
122    pub fn init_multi_phase(&'static self) -> *mut ffi::PyObject {
123        unsafe { ffi::PyModuleDef_Init(self.ffi_def.get()) }
124    }
125
126    /// Builds a module object directly. Used for [`#[pymodule]`][crate::pymodule] submodules.
127    pub fn make_module(&'static self, py: Python<'_>) -> PyResult<Py<PyModule>> {
128        // Check the interpreter ID has not changed, since we currently have no way to guarantee
129        // that static data is not reused across interpreters.
130        //
131        // PyPy does not have subinterpreters, so no need to check interpreter ID.
132        //
133        // TODO: it should be possible to use the Py_mod_multiple_interpreters slot on sufficiently
134        // new Python versions to remove the need for this custom logic
135        #[cfg(not(any(PyPy, GraalPy)))]
136        {
137            // PyInterpreterState_Get is missing from python3.dll for Windows
138            // stable API on 3.9
139            #[cfg(not(all(windows, Py_LIMITED_API, not(Py_3_10))))]
140            {
141                let current_interpreter =
142                    unsafe { ffi::PyInterpreterState_GetID(ffi::PyInterpreterState_Get()) };
143                crate::err::error_on_minusone(py, current_interpreter)?;
144                if let Err(initialized_interpreter) = self.interpreter.compare_exchange(
145                    -1,
146                    current_interpreter,
147                    Ordering::SeqCst,
148                    Ordering::SeqCst,
149                ) {
150                    if initialized_interpreter != current_interpreter {
151                        return Err(PyImportError::new_err(
152                            "PyO3 modules do not yet support subinterpreters, see https://github.com/PyO3/pyo3/issues/576",
153                        ));
154                    }
155                }
156            }
157            #[cfg(all(windows, Py_LIMITED_API, not(Py_3_10)))]
158            {
159                // The Windows stable API before 3.10 cannot check the interpreter ID, so best that
160                // can be done to guard against subinterpreters is fail if the module is initialized
161                // twice
162                if self.module.get(py).is_some() {
163                    return Err(PyImportError::new_err(
164                        "PyO3 modules compiled for the stable API on Windows targeting Python 3.9 may only be initialized once per interpreter process"
165                    ));
166                }
167            }
168        }
169
170        // Make a dummy spec, needs a `name` attribute and that seems to be sufficient
171        // for the loader system
172
173        static SIMPLE_NAMESPACE: PyOnceLock<Py<PyAny>> = PyOnceLock::new();
174        let simple_ns = SIMPLE_NAMESPACE.import(py, "types", "SimpleNamespace")?;
175
176        let kwargs = PyDict::new(py);
177        kwargs.set_item("name", self.name)?;
178        let spec = simple_ns.call((), Some(&kwargs))?;
179
180        self.module
181            .get_or_try_init(py, || {
182                // SAFETY: slots / def are static and fully initialized, spec is a valid object,
183                // and these functions are known to create a valid module object on success
184                let module: Bound<'_, PyModule> = unsafe {
185                    cfg_select! {
186                        Py_3_15 => ffi::PyModule_FromSlotsAndSpec(self.get_slots(), spec.as_ptr()),
187                        not(Py_3_15) => ffi::PyModule_FromDefAndSpec(self.ffi_def.get(), spec.as_ptr()),
188                    }.assume_owned_or_err(py)
189                    .cast_into_unchecked()
190                }?;
191
192                // SAFETY: module is a known valid module object
193                error_on_minusone(py, unsafe {
194                    cfg_select! {
195                        Py_3_15 => ffi::PyModule_Exec(module.as_ptr()),
196                        not(Py_3_15) => ffi::PyModule_ExecDef(module.as_ptr(), self.ffi_def.get()),
197                    }
198                })?;
199
200                Ok(module.unbind())
201            })
202            .map(|py_module| py_module.clone_ref(py))
203    }
204
205    #[cfg(Py_3_15)]
206    pub fn get_slots(&'static self) -> *mut ffi::PySlot {
207        array_ptr_as_mut(self.slots.0.get())
208    }
209}
210
211/// Defines the `PyModExport_<name>` entry point used by Python 3.15 and newer.
212///
213/// This is wrapped in a `macro_rules!` so the proc-macro backend can emit a single
214/// version-agnostic invocation; the body only expands on Python 3.15+, where
215/// `ffi::PySlot` is defined.
216#[cfg(Py_3_15)]
217#[doc(hidden)]
218#[macro_export]
219macro_rules! __pyo3_pymodexport {
220    ($symbol:literal, $def:path) => {
221        #[doc(hidden)]
222        #[export_name = $symbol]
223        pub unsafe extern "C" fn __pyo3_export() -> *mut $crate::ffi::PySlot {
224            $def.get_slots()
225        }
226    };
227}
228
229#[cfg(not(Py_3_15))]
230#[doc(hidden)]
231#[macro_export]
232macro_rules! __pyo3_pymodexport {
233    ($symbol:literal, $def:path) => {};
234}
235
236/// Defines the `PyInit_<name>` entry point used by Python 3.14 and older.
237///
238/// This is wrapped in a `macro_rules!` so the proc-macro backend can emit a single
239/// version-agnostic invocation; the body only expands on Python 3.14 and older
240#[cfg(not(all(Py_3_15, Py_LIMITED_API, Py_GIL_DISABLED)))]
241#[doc(hidden)]
242#[macro_export]
243macro_rules! __pyo3_pyinit {
244    ($symbol:literal, $def:path) => {
245        #[doc(hidden)]
246        #[export_name = $symbol]
247        pub unsafe extern "C" fn __pyo3_init() -> *mut $crate::ffi::PyObject {
248            $def.init_multi_phase()
249        }
250    };
251}
252
253#[cfg(all(Py_3_15, Py_LIMITED_API, Py_GIL_DISABLED))]
254#[doc(hidden)]
255#[macro_export]
256macro_rules! __pyo3_pyinit {
257    ($symbol:literal, $def:path) => {};
258}
259
260/// Type of the exec slot used to initialise module contents
261pub type ModuleExecSlot = unsafe extern "C" fn(*mut ffi::PyObject) -> c_int;
262
263const MAX_SLOTS: usize =
264    // Py_mod_exec
265    1 +
266    // Py_mod_gil
267    cfg!(Py_3_13) as usize +
268    // Py_mod_name, Py_mod_doc, and Py_mod_abi
269    3 * (cfg!(Py_3_15) as usize);
270const MAX_SLOTS_WITH_TRAILING_NULL: usize = MAX_SLOTS + 1;
271
272/// On Python 3.15+ we use `PySlot` system and `PyModule_FromSlotsAndSpec`
273#[cfg(Py_3_15)]
274pub type PrimaryModuleSlots = PyModuleSlots;
275#[cfg(all(Py_3_15, not(all(Py_LIMITED_API, Py_GIL_DISABLED))))]
276pub type SecondaryModuleSlots = PyModuleDefSlots;
277
278/// On Python 3.14 and older the primary system is `ffi::PyModuleDef`.
279#[cfg(not(Py_3_15))]
280pub type PrimaryModuleSlots = PyModuleDefSlots;
281#[cfg(not(all(Py_3_15, not(all(Py_LIMITED_API, Py_GIL_DISABLED)))))]
282pub type SecondaryModuleSlots = ();
283
284pub const fn secondary_slots(slots: &'static PrimaryModuleSlots) -> SecondaryModuleSlots {
285    cfg_select! {
286        // On Python 3.15+ we populate `PyModuleDefSlots` to point at primary slots
287        // (as long as not using abi3t where `PyModuleDef` is opaque and we cannot know the layout)
288        all(Py_3_15, not(all(Py_LIMITED_API, Py_GIL_DISABLED))) => PyModuleDefSlots(UnsafeCell::new([
289            ffi::PyModuleDef_Slot {
290                slot: ffi::Py_slot_subslots,
291                value: slots.0.get().cast(),
292            },
293            // SAFETY: terminator of C-style array
294            unsafe { core::mem::zeroed() },
295        ])),
296        // Older versions have no secondary slots
297        _ => { let _ = slots; }
298    }
299}
300
301/// Builder to create module slots. The size of the number of slots desired must
302/// be known up front, and N needs to be at least one greater than the number of
303/// actual slots pushed due to the need to have a zeroed element on the end.
304pub struct PyModuleSlotsBuilder {
305    // values (initially all zeroed)
306    slots: PrimaryModuleSlots,
307    // current length
308    len: usize,
309}
310
311// note that macros cannot use conditional compilation,
312// so all implementations below must be available in all
313// Python versions
314// By handling it here we can avoid conditional
315// compilation within the macros; they can always emit
316// e.g. a `.with_gil_used()` call.
317impl PyModuleSlotsBuilder {
318    #[allow(clippy::new_without_default)]
319    pub const fn new() -> Self {
320        Self {
321            slots: cfg_select! {
322                Py_3_15 => PyModuleSlots(UnsafeCell::new(
323                    // SAFETY: `PySlot` is legal to be zeroed (terminates C-style array)
324                    [unsafe { core::mem::zeroed::<ffi::PySlot>() }; MAX_SLOTS_WITH_TRAILING_NULL],
325                )),
326                _ => PyModuleDefSlots(UnsafeCell::new(
327                    // SAFETY: `PyModuleDef_Slot` is legal to be zeroed (terminates C-style array)
328                    [unsafe { core::mem::zeroed::<ffi::PyModuleDef_Slot>() }; MAX_SLOTS_WITH_TRAILING_NULL],
329                ))
330            },
331            len: 0,
332        }
333    }
334
335    pub const fn with_mod_exec(self, exec: ModuleExecSlot) -> Self {
336        #[cfg(not(Py_3_15))]
337        {
338            self.push(ffi::Py_mod_exec, exec as *mut c_void)
339        }
340        #[cfg(Py_3_15)]
341        {
342            // safety: exce is not NULL
343            self.push_value(unsafe { ffi::PySlot_FUNC(ffi::Py_mod_exec, exec as *mut c_void) })
344        }
345    }
346
347    pub const fn with_gil_used(self, gil_used: bool) -> Self {
348        #[cfg(all(Py_3_13, not(Py_3_15)))]
349        {
350            self.push(
351                ffi::Py_mod_gil,
352                if gil_used {
353                    ffi::Py_MOD_GIL_USED
354                } else {
355                    ffi::Py_MOD_GIL_NOT_USED
356                },
357            )
358        }
359
360        #[cfg(Py_3_15)]
361        {
362            self.push_value(ffi::PySlot_DATA(
363                ffi::Py_mod_gil,
364                if gil_used {
365                    ffi::Py_MOD_GIL_USED
366                } else {
367                    ffi::Py_MOD_GIL_NOT_USED
368                },
369            ))
370        }
371
372        #[cfg(not(Py_3_13))]
373        {
374            // Silence unused variable warning
375            let _ = gil_used;
376            self
377        }
378    }
379
380    pub const fn with_name(self, name: &'static CStr) -> Self {
381        #[cfg(Py_3_15)]
382        {
383            self.push_value(ffi::PySlot_STATIC_DATA(
384                ffi::Py_mod_name,
385                name.as_ptr() as *mut c_void,
386            ))
387        }
388
389        #[cfg(not(Py_3_15))]
390        {
391            // Silence unused variable warning
392            let _ = name;
393            self
394        }
395    }
396
397    pub const fn with_abi_info(self) -> Self {
398        #[cfg(Py_3_15)]
399        {
400            ffi::PyABIInfo_VAR!(ABI_INFO);
401            self.push_value(ffi::PySlot_STATIC_DATA(
402                ffi::Py_mod_abi,
403                (&raw mut ABI_INFO).cast(),
404            ))
405        }
406
407        #[cfg(not(Py_3_15))]
408        {
409            self
410        }
411    }
412
413    pub const fn with_doc(self, doc: &'static CStr) -> Self {
414        #[cfg(Py_3_15)]
415        {
416            self.push_value(ffi::PySlot_STATIC_DATA(
417                ffi::Py_mod_doc,
418                doc.as_ptr() as *mut c_void,
419            ))
420        }
421
422        #[cfg(not(Py_3_15))]
423        {
424            // Silence unused variable warning
425            let _ = doc;
426            self
427        }
428    }
429
430    pub const fn build(self) -> PrimaryModuleSlots {
431        self.slots
432    }
433
434    #[cfg(not(Py_3_15))]
435    const fn push(mut self, slot: c_int, value: *mut c_void) -> Self {
436        // Required to guarantee there's still a zeroed element
437        // at the end
438        assert!(
439            self.len < MAX_SLOTS,
440            "Cannot add more than MAX_SLOTS slots to a PyModuleSlots",
441        );
442        self.slots.0.get_mut()[self.len] = ffi::PyModuleDef_Slot { slot, value };
443        self.len += 1;
444        self
445    }
446
447    #[cfg(Py_3_15)]
448    const fn push_value(mut self, value: ffi::PySlot) -> Self {
449        assert!(
450            self.len < MAX_SLOTS,
451            "Cannot add more than MAX_SLOTS slots to a PyModuleSlots",
452        );
453        self.slots.0.get_mut()[self.len] = value;
454        self.len += 1;
455        self
456    }
457}
458
459/// Wrapper to safely store module slots, to be used in a `ModuleDef`.
460pub struct PyModuleSlots(
461    // necessarily empty before Python 3.15; PySlot doesn't exist
462    #[cfg(Py_3_15)] UnsafeCell<[ffi::PySlot; MAX_SLOTS_WITH_TRAILING_NULL]>,
463);
464
465/// Slots to populate a `PyModuleDef`
466/// Cannot create a `PyModuleDef` on abi3t due to lack of knowledge of object layout
467#[cfg(not(all(Py_LIMITED_API, Py_GIL_DISABLED)))]
468pub struct PyModuleDefSlots(
469    UnsafeCell<
470        [ffi::PyModuleDef_Slot; cfg_select! {
471            // on Python 3.15+ only one slot for pointing at the primary slots, plus trailing null
472            Py_3_15 => 2,
473            _ => MAX_SLOTS_WITH_TRAILING_NULL
474        }],
475    >,
476);
477
478// It might be possible to avoid this with SyncUnsafeCell in the future
479//
480// SAFETY: the inner values are only accessed within a `ModuleDef`,
481// used to call `PyModule_FromSlotsAndSpec`
482unsafe impl Sync for PyModuleSlots {}
483// SAFETY: the inner values are only accessed within a `ModuleDef`,
484// which only uses them to build the `ffi::ModuleDef`.
485#[cfg(not(all(Py_LIMITED_API, Py_GIL_DISABLED)))]
486unsafe impl Sync for PyModuleDefSlots {}
487
488/// Trait to add an element (class, function...) to a module.
489///
490/// Currently only implemented for classes.
491pub trait PyAddToModule: crate::sealed::Sealed {
492    fn add_to_module(&'static self, module: &Bound<'_, PyModule>) -> PyResult<()>;
493}
494
495/// For adding native types (non-pyclass) to a module.
496pub struct AddTypeToModule<T>(PhantomData<T>);
497
498impl<T> AddTypeToModule<T> {
499    #[allow(clippy::new_without_default)]
500    pub const fn new() -> Self {
501        AddTypeToModule(PhantomData)
502    }
503}
504
505impl<T: PyTypeInfo> PyAddToModule for AddTypeToModule<T> {
506    fn add_to_module(&'static self, module: &Bound<'_, PyModule>) -> PyResult<()> {
507        let object = T::type_object(module.py());
508        module.add(object.name()?, object)
509    }
510}
511
512/// For adding a class to a module.
513pub struct AddClassToModule<T>(PhantomData<T>);
514
515impl<T> AddClassToModule<T> {
516    #[allow(clippy::new_without_default)]
517    pub const fn new() -> Self {
518        AddClassToModule(PhantomData)
519    }
520}
521
522impl<T: PyClass> PyAddToModule for AddClassToModule<T> {
523    fn add_to_module(&'static self, module: &Bound<'_, PyModule>) -> PyResult<()> {
524        module.add_class::<T>()
525    }
526}
527
528/// For adding a function to a module.
529impl PyAddToModule for PyFunctionDef {
530    fn add_to_module(&'static self, module: &Bound<'_, PyModule>) -> PyResult<()> {
531        // safety: self is static
532        module.add_function(self.create_py_c_function(module.py(), Some(module))?)
533    }
534}
535
536/// For adding a module to a module.
537impl PyAddToModule for ModuleDef {
538    fn add_to_module(&'static self, module: &Bound<'_, PyModule>) -> PyResult<()> {
539        module.add_submodule(self.make_module(module.py())?.bind(module.py()))
540    }
541}
542
543#[cfg(test)]
544mod tests {
545    use alloc::borrow::Cow;
546    use core::{ffi::c_int, ffi::CStr};
547
548    use crate::impl_::trampoline;
549
550    use super::*;
551
552    unsafe extern "C" fn module_exec(_module: *mut ffi::PyObject) -> c_int {
553        0
554    }
555
556    #[test]
557    fn module_init() {
558        unsafe extern "C" fn module_exec(module: *mut ffi::PyObject) -> c_int {
559            unsafe {
560                trampoline::module_exec(module, |m| {
561                    m.add("SOME_CONSTANT", 42)?;
562                    Ok(())
563                })
564            }
565        }
566
567        static NAME: &CStr = c"test_module";
568        static DOC: &CStr = c"some doc";
569
570        static SLOTS: PrimaryModuleSlots = PyModuleSlotsBuilder::new()
571            .with_mod_exec(module_exec)
572            .with_gil_used(false)
573            .with_abi_info()
574            .with_name(NAME)
575            .with_doc(DOC)
576            .build();
577
578        static SECONDARY_SLOTS: SecondaryModuleSlots = secondary_slots(&SLOTS);
579
580        static MODULE_DEF: ModuleDef = ModuleDef::new(NAME, DOC, &SLOTS, &SECONDARY_SLOTS);
581
582        Python::attach(|py| {
583            let module = MODULE_DEF.make_module(py).unwrap().into_bound(py);
584            assert_eq!(
585                module
586                    .getattr("__name__")
587                    .unwrap()
588                    .extract::<Cow<'_, str>>()
589                    .unwrap(),
590                "test_module",
591            );
592            assert_eq!(
593                module
594                    .getattr("__doc__")
595                    .unwrap()
596                    .extract::<Cow<'_, str>>()
597                    .unwrap(),
598                "some doc",
599            );
600            assert_eq!(
601                module
602                    .getattr("SOME_CONSTANT")
603                    .unwrap()
604                    .extract::<u8>()
605                    .unwrap(),
606                42,
607            );
608        })
609    }
610
611    #[test]
612    fn module_def_new() {
613        // To get coverage for ModuleDef::new() need to create a non-static ModuleDef, however init
614        // etc require static ModuleDef, so this test needs to be separated out.
615        static NAME: &CStr = c"test_module";
616        static DOC: &CStr = c"some doc";
617
618        static SLOTS: PrimaryModuleSlots = PyModuleSlotsBuilder::new().build();
619        static SECONDARY_SLOTS: SecondaryModuleSlots = secondary_slots(&SLOTS);
620
621        let module_def: ModuleDef = ModuleDef::new(NAME, DOC, &SLOTS, &SECONDARY_SLOTS);
622
623        #[cfg(not(all(Py_LIMITED_API, Py_GIL_DISABLED)))]
624        unsafe {
625            let expected_slots = cfg_select! {
626                Py_3_15 => SECONDARY_SLOTS.0.get().cast(),
627                _ => SLOTS.0.get().cast(),
628            };
629            assert_eq!((*module_def.ffi_def.get()).m_slots, expected_slots);
630        }
631        #[cfg(all(Py_3_15, not(all(Py_LIMITED_API, Py_GIL_DISABLED))))]
632        unsafe {
633            let secondary_slots = &*SECONDARY_SLOTS.0.get();
634            assert_eq!(secondary_slots[0].slot, ffi::Py_slot_subslots);
635            assert_eq!(secondary_slots[0].value, SLOTS.0.get().cast());
636            assert!(secondary_slots[1] == ffi::PyModuleDef_Slot::default());
637        }
638
639        assert_eq!(module_def.name, NAME);
640    }
641
642    #[test]
643    #[cfg(panic = "unwind")]
644    fn test_build_maximal_slots() {
645        let mut builder = PyModuleSlotsBuilder::new()
646            .with_mod_exec(module_exec)
647            .with_name(c"test_module")
648            .with_doc(c"some doc")
649            .with_gil_used(false)
650            .with_abi_info();
651
652        #[cfg(Py_3_15)]
653        {
654            let second_last = builder.slots.0.get_mut()[builder.len - 1];
655            let last = builder.slots.0.get_mut()[builder.len];
656            let zeroed = unsafe { core::mem::zeroed() };
657            fn raw_bytes(inst: &ffi::PySlot) -> &[u8] {
658                unsafe {
659                    core::slice::from_raw_parts(
660                        inst as *const ffi::PySlot as *const u8,
661                        core::mem::size_of::<ffi::PySlot>(),
662                    )
663                }
664            }
665            let zeroed_bytes = raw_bytes(&zeroed);
666            assert_eq!(raw_bytes(&last), zeroed_bytes);
667            assert_ne!(raw_bytes(&second_last), zeroed_bytes);
668        }
669        #[cfg(not(Py_3_15))]
670        {
671            let second_last = builder.slots.0.get_mut()[builder.len - 1];
672            let last = builder.slots.0.get_mut()[builder.len];
673            let zeroed = ffi::PyModuleDef_Slot::default();
674            assert!(last == zeroed);
675            assert!(second_last != zeroed);
676        }
677        assert!(builder.len == MAX_SLOTS);
678
679        let result = std::panic::catch_unwind(|| builder.with_mod_exec(module_exec).build());
680
681        assert!(result.is_err());
682    }
683
684    #[test]
685    #[should_panic]
686    fn test_module_slots_builder_overflow() {
687        let mut builder = PyModuleSlotsBuilder::new();
688        for _ in 0..MAX_SLOTS + 1 {
689            builder = builder.with_mod_exec(module_exec);
690        }
691    }
692}
⚠️ Internal Docs ⚠️ Not Public API 👉 Official Docs Here