1#[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
414pub 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 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 #[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 pub static __PYO3_GIL_USED: bool = #gil_used;
511 };
512 if !is_submodule {
513 result.extend(quote! {
514 #[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
526fn 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
582fn 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}