pyo3_macros_backend/
module.rs

1//! Code generation for the function that initializes a python module and adds classes and function.
2
3#[cfg(feature = "experimental-inspect")]
4use crate::introspection::{
5    attribute_introspection_code, introspection_id_const, module_introspection_code,
6};
7#[cfg(feature = "experimental-inspect")]
8use crate::utils::expr_to_python;
9use crate::{
10    attributes::{
11        self, kw, take_attributes, take_pyo3_options, CrateAttribute, GILUsedAttribute,
12        ModuleAttribute, NameAttribute, SubmoduleAttribute,
13    },
14    combine_errors::CombineErrors,
15    get_doc,
16    pyclass::PyClassPyO3Option,
17    pyfunction::{impl_wrap_pyfunction, PyFunctionOptions},
18    utils::{has_attribute, has_attribute_with_namespace, Ctx, IdentOrStr, LitCStr},
19};
20use proc_macro2::{Span, TokenStream};
21use quote::quote;
22use std::ffi::CString;
23use syn::{
24    ext::IdentExt,
25    parse::{Parse, ParseStream},
26    parse_quote, parse_quote_spanned,
27    punctuated::Punctuated,
28    spanned::Spanned,
29    token::Comma,
30    Item, Meta, Path, Result,
31};
32
33#[derive(Default)]
34pub struct PyModuleOptions {
35    krate: Option<CrateAttribute>,
36    name: Option<NameAttribute>,
37    module: Option<ModuleAttribute>,
38    submodule: Option<kw::submodule>,
39    gil_used: Option<GILUsedAttribute>,
40}
41
42impl Parse for PyModuleOptions {
43    fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
44        let mut options: PyModuleOptions = Default::default();
45
46        options.add_attributes(
47            Punctuated::<PyModulePyO3Option, syn::Token![,]>::parse_terminated(input)?,
48        )?;
49
50        Ok(options)
51    }
52}
53
54impl PyModuleOptions {
55    fn take_pyo3_options(&mut self, attrs: &mut Vec<syn::Attribute>) -> Result<()> {
56        self.add_attributes(take_pyo3_options(attrs)?)
57    }
58
59    fn add_attributes(
60        &mut self,
61        attrs: impl IntoIterator<Item = PyModulePyO3Option>,
62    ) -> Result<()> {
63        macro_rules! set_option {
64            ($key:ident $(, $extra:literal)?) => {
65                {
66                    ensure_spanned!(
67                        self.$key.is_none(),
68                        $key.span() => concat!("`", stringify!($key), "` may only be specified once" $(, $extra)?)
69                    );
70                    self.$key = Some($key);
71                }
72            };
73        }
74        attrs
75            .into_iter()
76            .map(|attr| {
77                match attr {
78                    PyModulePyO3Option::Crate(krate) => set_option!(krate),
79                    PyModulePyO3Option::Name(name) => set_option!(name),
80                    PyModulePyO3Option::Module(module) => set_option!(module),
81                    PyModulePyO3Option::Submodule(submodule) => set_option!(
82                        submodule,
83                        " (it is implicitly always specified for nested modules)"
84                    ),
85                    PyModulePyO3Option::GILUsed(gil_used) => {
86                        set_option!(gil_used)
87                    }
88                }
89
90                Ok(())
91            })
92            .try_combine_syn_errors()?;
93        Ok(())
94    }
95}
96
97pub fn pymodule_module_impl(
98    module: &mut syn::ItemMod,
99    mut options: PyModuleOptions,
100) -> Result<TokenStream> {
101    let syn::ItemMod {
102        attrs,
103        vis,
104        unsafety: _,
105        ident,
106        mod_token,
107        content,
108        semi: _,
109    } = module;
110    let items = if let Some((_, items)) = content {
111        items
112    } else {
113        bail_spanned!(mod_token.span() => "`#[pymodule]` can only be used on inline modules")
114    };
115    options.take_pyo3_options(attrs)?;
116    let ctx = &Ctx::new(&options.krate, None);
117    let Ctx { pyo3_path, .. } = ctx;
118    let doc = get_doc(attrs, None, ctx)?;
119    let name = options
120        .name
121        .map_or_else(|| ident.unraw(), |name| name.value.0);
122    let full_name = if let Some(module) = &options.module {
123        format!("{}.{}", module.value.value(), name)
124    } else {
125        name.to_string()
126    };
127
128    let mut module_items = Vec::new();
129    let mut module_items_cfg_attrs = Vec::new();
130    #[cfg(feature = "experimental-inspect")]
131    let mut introspection_chunks = Vec::new();
132    #[cfg(not(feature = "experimental-inspect"))]
133    let introspection_chunks = Vec::<TokenStream>::new();
134
135    fn extract_use_items(
136        source: &syn::UseTree,
137        cfg_attrs: &[syn::Attribute],
138        target_items: &mut Vec<syn::Ident>,
139        target_cfg_attrs: &mut Vec<Vec<syn::Attribute>>,
140    ) -> Result<()> {
141        match source {
142            syn::UseTree::Name(name) => {
143                target_items.push(name.ident.clone());
144                target_cfg_attrs.push(cfg_attrs.to_vec());
145            }
146            syn::UseTree::Path(path) => {
147                extract_use_items(&path.tree, cfg_attrs, target_items, target_cfg_attrs)?
148            }
149            syn::UseTree::Group(group) => {
150                for tree in &group.items {
151                    extract_use_items(tree, cfg_attrs, target_items, target_cfg_attrs)?
152                }
153            }
154            syn::UseTree::Glob(glob) => {
155                bail_spanned!(glob.span() => "#[pymodule] cannot import glob statements")
156            }
157            syn::UseTree::Rename(rename) => {
158                target_items.push(rename.rename.clone());
159                target_cfg_attrs.push(cfg_attrs.to_vec());
160            }
161        }
162        Ok(())
163    }
164
165    let mut pymodule_init = None;
166    let mut module_consts = Vec::new();
167    let mut module_consts_cfg_attrs = Vec::new();
168
169    let _: Vec<()> = (*items).iter_mut().map(|item|{
170        match item {
171            Item::Use(item_use) => {
172                let is_pymodule_export =
173                    find_and_remove_attribute(&mut item_use.attrs, "pymodule_export");
174                if is_pymodule_export {
175                    let cfg_attrs = get_cfg_attributes(&item_use.attrs);
176                    extract_use_items(
177                        &item_use.tree,
178                        &cfg_attrs,
179                        &mut module_items,
180                        &mut module_items_cfg_attrs,
181                    )?;
182                }
183            }
184            Item::Fn(item_fn) => {
185                ensure_spanned!(
186                    !has_attribute(&item_fn.attrs, "pymodule_export"),
187                    item.span() => "`#[pymodule_export]` may only be used on `use` or `const` statements"
188                );
189                let is_pymodule_init =
190                    find_and_remove_attribute(&mut item_fn.attrs, "pymodule_init");
191                let ident = &item_fn.sig.ident;
192                if is_pymodule_init {
193                    ensure_spanned!(
194                        !has_attribute(&item_fn.attrs, "pyfunction"),
195                        item_fn.span() => "`#[pyfunction]` cannot be used alongside `#[pymodule_init]`"
196                    );
197                    ensure_spanned!(pymodule_init.is_none(), item_fn.span() => "only one `#[pymodule_init]` may be specified");
198                    pymodule_init = Some(quote! { #ident(module)?; });
199                } else if has_attribute(&item_fn.attrs, "pyfunction")
200                    || has_attribute_with_namespace(
201                        &item_fn.attrs,
202                        Some(pyo3_path),
203                        &["pyfunction"],
204                    )
205                    || has_attribute_with_namespace(
206                        &item_fn.attrs,
207                        Some(pyo3_path),
208                        &["prelude", "pyfunction"],
209                    )
210                {
211                    module_items.push(ident.clone());
212                    module_items_cfg_attrs.push(get_cfg_attributes(&item_fn.attrs));
213                }
214            }
215            Item::Struct(item_struct) => {
216                ensure_spanned!(
217                    !has_attribute(&item_struct.attrs, "pymodule_export"),
218                    item.span() => "`#[pymodule_export]` may only be used on `use` or `const` statements"
219                );
220                if has_attribute(&item_struct.attrs, "pyclass")
221                    || has_attribute_with_namespace(
222                        &item_struct.attrs,
223                        Some(pyo3_path),
224                        &["pyclass"],
225                    )
226                    || has_attribute_with_namespace(
227                        &item_struct.attrs,
228                        Some(pyo3_path),
229                        &["prelude", "pyclass"],
230                    )
231                {
232                    module_items.push(item_struct.ident.clone());
233                    module_items_cfg_attrs.push(get_cfg_attributes(&item_struct.attrs));
234                    if !has_pyo3_module_declared::<PyClassPyO3Option>(
235                        &item_struct.attrs,
236                        "pyclass",
237                        |option| matches!(option, PyClassPyO3Option::Module(_)),
238                    )? {
239                        set_module_attribute(&mut item_struct.attrs, &full_name);
240                    }
241                }
242            }
243            Item::Enum(item_enum) => {
244                ensure_spanned!(
245                    !has_attribute(&item_enum.attrs, "pymodule_export"),
246                    item.span() => "`#[pymodule_export]` may only be used on `use` or `const` statements"
247                );
248                if has_attribute(&item_enum.attrs, "pyclass")
249                    || has_attribute_with_namespace(&item_enum.attrs, Some(pyo3_path), &["pyclass"])
250                    || has_attribute_with_namespace(
251                        &item_enum.attrs,
252                        Some(pyo3_path),
253                        &["prelude", "pyclass"],
254                    )
255                {
256                    module_items.push(item_enum.ident.clone());
257                    module_items_cfg_attrs.push(get_cfg_attributes(&item_enum.attrs));
258                    if !has_pyo3_module_declared::<PyClassPyO3Option>(
259                        &item_enum.attrs,
260                        "pyclass",
261                        |option| matches!(option, PyClassPyO3Option::Module(_)),
262                    )? {
263                        set_module_attribute(&mut item_enum.attrs, &full_name);
264                    }
265                }
266            }
267            Item::Mod(item_mod) => {
268                ensure_spanned!(
269                    !has_attribute(&item_mod.attrs, "pymodule_export"),
270                    item.span() => "`#[pymodule_export]` may only be used on `use` or `const` statements"
271                );
272                if has_attribute(&item_mod.attrs, "pymodule")
273                    || has_attribute_with_namespace(&item_mod.attrs, Some(pyo3_path), &["pymodule"])
274                    || has_attribute_with_namespace(
275                        &item_mod.attrs,
276                        Some(pyo3_path),
277                        &["prelude", "pymodule"],
278                    )
279                {
280                    module_items.push(item_mod.ident.clone());
281                    module_items_cfg_attrs.push(get_cfg_attributes(&item_mod.attrs));
282                    if !has_pyo3_module_declared::<PyModulePyO3Option>(
283                        &item_mod.attrs,
284                        "pymodule",
285                        |option| matches!(option, PyModulePyO3Option::Module(_)),
286                    )? {
287                        set_module_attribute(&mut item_mod.attrs, &full_name);
288                    }
289                    item_mod
290                        .attrs
291                        .push(parse_quote_spanned!(item_mod.mod_token.span()=> #[pyo3(submodule)]));
292                }
293            }
294            Item::ForeignMod(item) => {
295                ensure_spanned!(
296                    !has_attribute(&item.attrs, "pymodule_export"),
297                    item.span() => "`#[pymodule_export]` may only be used on `use` or `const` statements"
298                );
299            }
300            Item::Trait(item) => {
301                ensure_spanned!(
302                    !has_attribute(&item.attrs, "pymodule_export"),
303                    item.span() => "`#[pymodule_export]` may only be used on `use` or `const` statements"
304                );
305            }
306            Item::Const(item) => {
307                if !find_and_remove_attribute(&mut item.attrs, "pymodule_export") {
308                    return Ok(());
309                }
310                module_consts.push(item.ident.clone());
311                module_consts_cfg_attrs.push(get_cfg_attributes(&item.attrs));
312                #[cfg(feature = "experimental-inspect")]
313                {
314                    let cfg_attrs = get_cfg_attributes(&item.attrs);
315                    let chunk = attribute_introspection_code(
316                        pyo3_path,
317                        None,
318                        item.ident.unraw().to_string(),
319                        expr_to_python(&item.expr),
320                        (*item.ty).clone(),
321                        true,
322                    );
323                    introspection_chunks.push(quote! {
324                        #(#cfg_attrs)*
325                        #chunk
326                    });
327                }
328            }
329            Item::Static(item) => {
330                ensure_spanned!(
331                    !has_attribute(&item.attrs, "pymodule_export"),
332                    item.span() => "`#[pymodule_export]` may only be used on `use` or `const` statements"
333                );
334            }
335            Item::Macro(item) => {
336                ensure_spanned!(
337                    !has_attribute(&item.attrs, "pymodule_export"),
338                    item.span() => "`#[pymodule_export]` may only be used on `use` or `const` statements"
339                );
340            }
341            Item::ExternCrate(item) => {
342                ensure_spanned!(
343                    !has_attribute(&item.attrs, "pymodule_export"),
344                    item.span() => "`#[pymodule_export]` may only be used on `use` or `const` statements"
345                );
346            }
347            Item::Impl(item) => {
348                ensure_spanned!(
349                    !has_attribute(&item.attrs, "pymodule_export"),
350                    item.span() => "`#[pymodule_export]` may only be used on `use` or `const` statements"
351                );
352            }
353            Item::TraitAlias(item) => {
354                ensure_spanned!(
355                    !has_attribute(&item.attrs, "pymodule_export"),
356                    item.span() => "`#[pymodule_export]` may only be used on `use` or `const` statements"
357                );
358            }
359            Item::Type(item) => {
360                ensure_spanned!(
361                    !has_attribute(&item.attrs, "pymodule_export"),
362                    item.span() => "`#[pymodule_export]` may only be used on `use` or `const` statements"
363                );
364            }
365            Item::Union(item) => {
366                ensure_spanned!(
367                    !has_attribute(&item.attrs, "pymodule_export"),
368                    item.span() => "`#[pymodule_export]` may only be used on `use` or `const` statements"
369                );
370            }
371            _ => (),
372        }
373        Ok(())
374    }).try_combine_syn_errors()?;
375
376    #[cfg(feature = "experimental-inspect")]
377    let introspection = module_introspection_code(
378        pyo3_path,
379        &name.to_string(),
380        &module_items,
381        &module_items_cfg_attrs,
382        pymodule_init.is_some(),
383    );
384    #[cfg(not(feature = "experimental-inspect"))]
385    let introspection = quote! {};
386    #[cfg(feature = "experimental-inspect")]
387    let introspection_id = introspection_id_const();
388    #[cfg(not(feature = "experimental-inspect"))]
389    let introspection_id = quote! {};
390
391    let module_def = quote! {{
392        use #pyo3_path::impl_::pymodule as impl_;
393        const INITIALIZER: impl_::ModuleInitializer = impl_::ModuleInitializer(__pyo3_pymodule);
394        unsafe {
395           impl_::ModuleDef::new(
396                __PYO3_NAME,
397                #doc,
398                INITIALIZER
399            )
400        }
401    }};
402    let initialization = module_initialization(
403        &name,
404        ctx,
405        module_def,
406        options.submodule.is_some(),
407        options.gil_used.map_or(true, |op| op.value.value),
408    );
409
410    let module_consts_names = module_consts.iter().map(|i| i.unraw().to_string());
411
412    Ok(quote!(
413        #(#attrs)*
414        #vis #mod_token #ident {
415            #(#items)*
416
417            #initialization
418            #introspection
419            #introspection_id
420            #(#introspection_chunks)*
421
422            fn __pyo3_pymodule(module: &#pyo3_path::Bound<'_, #pyo3_path::types::PyModule>) -> #pyo3_path::PyResult<()> {
423                use #pyo3_path::impl_::pymodule::PyAddToModule;
424                #(
425                    #(#module_items_cfg_attrs)*
426                    #module_items::_PYO3_DEF.add_to_module(module)?;
427                )*
428
429                #(
430                    #(#module_consts_cfg_attrs)*
431                    #pyo3_path::types::PyModuleMethods::add(module, #module_consts_names, #module_consts)?;
432                )*
433
434                #pymodule_init
435                ::std::result::Result::Ok(())
436            }
437        }
438    ))
439}
440
441/// Generates the function that is called by the python interpreter to initialize the native
442/// module
443pub fn pymodule_function_impl(
444    function: &mut syn::ItemFn,
445    mut options: PyModuleOptions,
446) -> Result<TokenStream> {
447    options.take_pyo3_options(&mut function.attrs)?;
448    process_functions_in_module(&options, function)?;
449    let ctx = &Ctx::new(&options.krate, None);
450    let Ctx { pyo3_path, .. } = ctx;
451    let ident = &function.sig.ident;
452    let name = options
453        .name
454        .map_or_else(|| ident.unraw(), |name| name.value.0);
455    let vis = &function.vis;
456    let doc = get_doc(&function.attrs, None, ctx)?;
457
458    let initialization = module_initialization(
459        &name,
460        ctx,
461        quote! { MakeDef::make_def() },
462        false,
463        options.gil_used.map_or(true, |op| op.value.value),
464    );
465
466    #[cfg(feature = "experimental-inspect")]
467    let introspection =
468        module_introspection_code(pyo3_path, &name.unraw().to_string(), &[], &[], true);
469    #[cfg(not(feature = "experimental-inspect"))]
470    let introspection = quote! {};
471    #[cfg(feature = "experimental-inspect")]
472    let introspection_id = introspection_id_const();
473    #[cfg(not(feature = "experimental-inspect"))]
474    let introspection_id = quote! {};
475
476    // Module function called with optional Python<'_> marker as first arg, followed by the module.
477    let mut module_args = Vec::new();
478    if function.sig.inputs.len() == 2 {
479        module_args.push(quote!(module.py()));
480    }
481    module_args
482        .push(quote!(::std::convert::Into::into(#pyo3_path::impl_::pymethods::BoundRef(module))));
483
484    Ok(quote! {
485        #[doc(hidden)]
486        #vis mod #ident {
487            #initialization
488            #introspection
489            #introspection_id
490        }
491
492        // Generate the definition inside an anonymous function in the same scope as the original function -
493        // this avoids complications around the fact that the generated module has a different scope
494        // (and `super` doesn't always refer to the outer scope, e.g. if the `#[pymodule] is
495        // inside a function body)
496        #[allow(unknown_lints, non_local_definitions)]
497        impl #ident::MakeDef {
498            const fn make_def() -> #pyo3_path::impl_::pymodule::ModuleDef {
499                fn __pyo3_pymodule(module: &#pyo3_path::Bound<'_, #pyo3_path::types::PyModule>) -> #pyo3_path::PyResult<()> {
500                    #ident(#(#module_args),*)
501                }
502
503                const INITIALIZER: #pyo3_path::impl_::pymodule::ModuleInitializer = #pyo3_path::impl_::pymodule::ModuleInitializer(__pyo3_pymodule);
504                unsafe {
505                    #pyo3_path::impl_::pymodule::ModuleDef::new(
506                        #ident::__PYO3_NAME,
507                        #doc,
508                        INITIALIZER
509                    )
510                }
511            }
512        }
513    })
514}
515
516fn module_initialization(
517    name: &syn::Ident,
518    ctx: &Ctx,
519    module_def: TokenStream,
520    is_submodule: bool,
521    gil_used: bool,
522) -> TokenStream {
523    let Ctx { pyo3_path, .. } = ctx;
524    let pyinit_symbol = format!("PyInit_{name}");
525    let name = name.to_string();
526    let pyo3_name = LitCStr::new(CString::new(name).unwrap(), Span::call_site(), ctx);
527
528    let mut result = quote! {
529        #[doc(hidden)]
530        pub const __PYO3_NAME: &'static ::std::ffi::CStr = #pyo3_name;
531
532        pub(super) struct MakeDef;
533        #[doc(hidden)]
534        pub static _PYO3_DEF: #pyo3_path::impl_::pymodule::ModuleDef = #module_def;
535        #[doc(hidden)]
536        // so wrapped submodules can see what gil_used is
537        pub static __PYO3_GIL_USED: bool = #gil_used;
538    };
539    if !is_submodule {
540        result.extend(quote! {
541            /// This autogenerated function is called by the python interpreter when importing
542            /// the module.
543            #[doc(hidden)]
544            #[export_name = #pyinit_symbol]
545            pub unsafe extern "C" fn __pyo3_init() -> *mut #pyo3_path::ffi::PyObject {
546                unsafe { #pyo3_path::impl_::trampoline::module_init(|py| _PYO3_DEF.make_module(py, #gil_used)) }
547            }
548        });
549    }
550    result
551}
552
553/// Finds and takes care of the #[pyfn(...)] in `#[pymodule]`
554fn process_functions_in_module(options: &PyModuleOptions, func: &mut syn::ItemFn) -> Result<()> {
555    let ctx = &Ctx::new(&options.krate, None);
556    let Ctx { pyo3_path, .. } = ctx;
557    let mut stmts: Vec<syn::Stmt> = Vec::new();
558
559    for mut stmt in func.block.stmts.drain(..) {
560        if let syn::Stmt::Item(Item::Fn(func)) = &mut stmt {
561            if let Some((pyfn_span, pyfn_args)) = get_pyfn_attr(&mut func.attrs)? {
562                let module_name = pyfn_args.modname;
563                let wrapped_function = impl_wrap_pyfunction(func, pyfn_args.options)?;
564                let name = &func.sig.ident;
565                let statements: Vec<syn::Stmt> = syn::parse_quote_spanned! {
566                    pyfn_span =>
567                    #wrapped_function
568                    {
569                        use #pyo3_path::types::PyModuleMethods;
570                        #module_name.add_function(#pyo3_path::wrap_pyfunction!(#name, #module_name.as_borrowed())?)?;
571                        #[deprecated(note = "`pyfn` will be removed in a future PyO3 version, use declarative `#[pymodule]` with `mod` instead")]
572                        #[allow(dead_code)]
573                        const PYFN_ATTRIBUTE: () = ();
574                        const _: () = PYFN_ATTRIBUTE;
575                    }
576                };
577                stmts.extend(statements);
578            }
579        };
580        stmts.push(stmt);
581    }
582
583    func.block.stmts = stmts;
584    Ok(())
585}
586
587pub struct PyFnArgs {
588    modname: Path,
589    options: PyFunctionOptions,
590}
591
592impl Parse for PyFnArgs {
593    fn parse(input: syn::parse::ParseStream<'_>) -> syn::Result<Self> {
594        let modname = input.parse().map_err(
595            |e| err_spanned!(e.span() => "expected module as first argument to #[pyfn()]"),
596        )?;
597
598        if input.is_empty() {
599            return Ok(Self {
600                modname,
601                options: Default::default(),
602            });
603        }
604
605        let _: Comma = input.parse()?;
606
607        Ok(Self {
608            modname,
609            options: input.parse()?,
610        })
611    }
612}
613
614/// Extracts the data from the #[pyfn(...)] attribute of a function
615fn get_pyfn_attr(attrs: &mut Vec<syn::Attribute>) -> syn::Result<Option<(Span, PyFnArgs)>> {
616    let mut pyfn_args: Option<(Span, PyFnArgs)> = None;
617
618    take_attributes(attrs, |attr| {
619        if attr.path().is_ident("pyfn") {
620            ensure_spanned!(
621                pyfn_args.is_none(),
622                attr.span() => "`#[pyfn] may only be specified once"
623            );
624            pyfn_args = Some((attr.path().span(), attr.parse_args()?));
625            Ok(true)
626        } else {
627            Ok(false)
628        }
629    })?;
630
631    if let Some((_, pyfn_args)) = &mut pyfn_args {
632        pyfn_args
633            .options
634            .add_attributes(take_pyo3_options(attrs)?)?;
635    }
636
637    Ok(pyfn_args)
638}
639
640fn get_cfg_attributes(attrs: &[syn::Attribute]) -> Vec<syn::Attribute> {
641    attrs
642        .iter()
643        .filter(|attr| attr.path().is_ident("cfg"))
644        .cloned()
645        .collect()
646}
647
648fn find_and_remove_attribute(attrs: &mut Vec<syn::Attribute>, ident: &str) -> bool {
649    let mut found = false;
650    attrs.retain(|attr| {
651        if attr.path().is_ident(ident) {
652            found = true;
653            false
654        } else {
655            true
656        }
657    });
658    found
659}
660
661impl PartialEq<syn::Ident> for IdentOrStr<'_> {
662    fn eq(&self, other: &syn::Ident) -> bool {
663        match self {
664            IdentOrStr::Str(s) => other == s,
665            IdentOrStr::Ident(i) => other == i,
666        }
667    }
668}
669
670fn set_module_attribute(attrs: &mut Vec<syn::Attribute>, module_name: &str) {
671    attrs.push(parse_quote!(#[pyo3(module = #module_name)]));
672}
673
674fn has_pyo3_module_declared<T: Parse>(
675    attrs: &[syn::Attribute],
676    root_attribute_name: &str,
677    is_module_option: impl Fn(&T) -> bool + Copy,
678) -> Result<bool> {
679    for attr in attrs {
680        if (attr.path().is_ident("pyo3") || attr.path().is_ident(root_attribute_name))
681            && matches!(attr.meta, Meta::List(_))
682        {
683            for option in &attr.parse_args_with(Punctuated::<T, Comma>::parse_terminated)? {
684                if is_module_option(option) {
685                    return Ok(true);
686                }
687            }
688        }
689    }
690    Ok(false)
691}
692
693enum PyModulePyO3Option {
694    Submodule(SubmoduleAttribute),
695    Crate(CrateAttribute),
696    Name(NameAttribute),
697    Module(ModuleAttribute),
698    GILUsed(GILUsedAttribute),
699}
700
701impl Parse for PyModulePyO3Option {
702    fn parse(input: ParseStream<'_>) -> Result<Self> {
703        let lookahead = input.lookahead1();
704        if lookahead.peek(attributes::kw::name) {
705            input.parse().map(PyModulePyO3Option::Name)
706        } else if lookahead.peek(syn::Token![crate]) {
707            input.parse().map(PyModulePyO3Option::Crate)
708        } else if lookahead.peek(attributes::kw::module) {
709            input.parse().map(PyModulePyO3Option::Module)
710        } else if lookahead.peek(attributes::kw::submodule) {
711            input.parse().map(PyModulePyO3Option::Submodule)
712        } else if lookahead.peek(attributes::kw::gil_used) {
713            input.parse().map(PyModulePyO3Option::GILUsed)
714        } else {
715            Err(lookahead.error())
716        }
717    }
718}