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