1#[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
441pub 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 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 #[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 pub static __PYO3_GIL_USED: bool = #gil_used;
538 };
539 if !is_submodule {
540 result.extend(quote! {
541 #[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
553fn 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_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! {
566 #wrapped_function
567 {
568 use #pyo3_path::types::PyModuleMethods;
569 #module_name.add_function(#pyo3_path::wrap_pyfunction!(#name, #module_name.as_borrowed())?)?;
570 }
571 };
572 stmts.extend(statements);
573 }
574 };
575 stmts.push(stmt);
576 }
577
578 func.block.stmts = stmts;
579 Ok(())
580}
581
582pub struct PyFnArgs {
583 modname: Path,
584 options: PyFunctionOptions,
585}
586
587impl Parse for PyFnArgs {
588 fn parse(input: syn::parse::ParseStream<'_>) -> syn::Result<Self> {
589 let modname = input.parse().map_err(
590 |e| err_spanned!(e.span() => "expected module as first argument to #[pyfn()]"),
591 )?;
592
593 if input.is_empty() {
594 return Ok(Self {
595 modname,
596 options: Default::default(),
597 });
598 }
599
600 let _: Comma = input.parse()?;
601
602 Ok(Self {
603 modname,
604 options: input.parse()?,
605 })
606 }
607}
608
609fn get_pyfn_attr(attrs: &mut Vec<syn::Attribute>) -> syn::Result<Option<PyFnArgs>> {
611 let mut pyfn_args: Option<PyFnArgs> = None;
612
613 take_attributes(attrs, |attr| {
614 if attr.path().is_ident("pyfn") {
615 ensure_spanned!(
616 pyfn_args.is_none(),
617 attr.span() => "`#[pyfn] may only be specified once"
618 );
619 pyfn_args = Some(attr.parse_args()?);
620 Ok(true)
621 } else {
622 Ok(false)
623 }
624 })?;
625
626 if let Some(pyfn_args) = &mut pyfn_args {
627 pyfn_args
628 .options
629 .add_attributes(take_pyo3_options(attrs)?)?;
630 }
631
632 Ok(pyfn_args)
633}
634
635fn get_cfg_attributes(attrs: &[syn::Attribute]) -> Vec<syn::Attribute> {
636 attrs
637 .iter()
638 .filter(|attr| attr.path().is_ident("cfg"))
639 .cloned()
640 .collect()
641}
642
643fn find_and_remove_attribute(attrs: &mut Vec<syn::Attribute>, ident: &str) -> bool {
644 let mut found = false;
645 attrs.retain(|attr| {
646 if attr.path().is_ident(ident) {
647 found = true;
648 false
649 } else {
650 true
651 }
652 });
653 found
654}
655
656impl PartialEq<syn::Ident> for IdentOrStr<'_> {
657 fn eq(&self, other: &syn::Ident) -> bool {
658 match self {
659 IdentOrStr::Str(s) => other == s,
660 IdentOrStr::Ident(i) => other == i,
661 }
662 }
663}
664
665fn set_module_attribute(attrs: &mut Vec<syn::Attribute>, module_name: &str) {
666 attrs.push(parse_quote!(#[pyo3(module = #module_name)]));
667}
668
669fn has_pyo3_module_declared<T: Parse>(
670 attrs: &[syn::Attribute],
671 root_attribute_name: &str,
672 is_module_option: impl Fn(&T) -> bool + Copy,
673) -> Result<bool> {
674 for attr in attrs {
675 if (attr.path().is_ident("pyo3") || attr.path().is_ident(root_attribute_name))
676 && matches!(attr.meta, Meta::List(_))
677 {
678 for option in &attr.parse_args_with(Punctuated::<T, Comma>::parse_terminated)? {
679 if is_module_option(option) {
680 return Ok(true);
681 }
682 }
683 }
684 }
685 Ok(false)
686}
687
688enum PyModulePyO3Option {
689 Submodule(SubmoduleAttribute),
690 Crate(CrateAttribute),
691 Name(NameAttribute),
692 Module(ModuleAttribute),
693 GILUsed(GILUsedAttribute),
694}
695
696impl Parse for PyModulePyO3Option {
697 fn parse(input: ParseStream<'_>) -> Result<Self> {
698 let lookahead = input.lookahead1();
699 if lookahead.peek(attributes::kw::name) {
700 input.parse().map(PyModulePyO3Option::Name)
701 } else if lookahead.peek(syn::Token![crate]) {
702 input.parse().map(PyModulePyO3Option::Crate)
703 } else if lookahead.peek(attributes::kw::module) {
704 input.parse().map(PyModulePyO3Option::Module)
705 } else if lookahead.peek(attributes::kw::submodule) {
706 input.parse().map(PyModulePyO3Option::Submodule)
707 } else if lookahead.peek(attributes::kw::gil_used) {
708 input.parse().map(PyModulePyO3Option::GILUsed)
709 } else {
710 Err(lookahead.error())
711 }
712 }
713}