-
Notifications
You must be signed in to change notification settings - Fork 779
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
add #[pymodule] mod some_module { ... }, v2 #3294
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,6 +2,7 @@ | |
|
||
use crate::{ | ||
attributes::{self, take_attributes, take_pyo3_options, CrateAttribute, NameAttribute}, | ||
get_doc, | ||
pyfunction::{impl_wrap_pyfunction, PyFunctionOptions}, | ||
utils::{get_pyo3_crate, PythonDoc}, | ||
}; | ||
|
@@ -56,9 +57,132 @@ impl PyModuleOptions { | |
} | ||
} | ||
|
||
pub fn pymodule_module_impl(mut module: syn::ItemMod) -> Result<TokenStream> { | ||
let syn::ItemMod { | ||
attrs, | ||
vis, | ||
ident, | ||
mod_token, | ||
content, | ||
unsafety: _, | ||
semi: _, | ||
} = &mut module; | ||
let items = match content { | ||
Some((_, items)) => items, | ||
None => bail_spanned!(module.span() => "`#[pymodule]` can only be used on inline modules"), | ||
}; | ||
let options = PyModuleOptions::from_attrs(attrs)?; | ||
let doc = get_doc(attrs, None); | ||
|
||
let name = options.name.unwrap_or_else(|| ident.unraw()); | ||
let krate = get_pyo3_crate(&options.krate); | ||
let pyinit_symbol = format!("PyInit_{}", name); | ||
|
||
let mut module_items = Vec::new(); | ||
let mut module_attrs = Vec::new(); | ||
|
||
fn extract_use_items( | ||
source: &syn::UseTree, | ||
cfg_attrs: &Vec<syn::Attribute>, | ||
names: &mut Vec<Ident>, | ||
attrs: &mut Vec<Vec<syn::Attribute>>, | ||
) -> Result<()> { | ||
match source { | ||
syn::UseTree::Name(name) => { | ||
names.push(name.ident.clone()); | ||
attrs.push(cfg_attrs.clone()); | ||
} | ||
syn::UseTree::Path(path) => extract_use_items(&path.tree, cfg_attrs, names, attrs)?, | ||
syn::UseTree::Group(group) => { | ||
for tree in &group.items { | ||
extract_use_items(tree, cfg_attrs, names, attrs)? | ||
} | ||
} | ||
syn::UseTree::Glob(glob) => { | ||
bail_spanned!(glob.span() => "#[pyo3] cannot import glob statements") | ||
} | ||
Comment on lines
+101
to
+103
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It would be nice to add a UI test for declarative modules which checks this error. |
||
syn::UseTree::Rename(rename) => { | ||
names.push(rename.ident.clone()); | ||
attrs.push(cfg_attrs.clone()); | ||
} | ||
} | ||
Ok(()) | ||
} | ||
|
||
let mut pymodule_init = None; | ||
|
||
for item in items.iter_mut() { | ||
match item { | ||
syn::Item::Use(item_use) => { | ||
let mut is_pyo3 = false; | ||
item_use.attrs.retain(|attr| { | ||
let found = attr.path().is_ident("pyo3"); | ||
is_pyo3 |= found; | ||
!found | ||
}); | ||
if is_pyo3 { | ||
let cfg_attrs: Vec<_> = item_use | ||
.attrs | ||
.iter() | ||
.filter(|attr| attr.path().is_ident("cfg")) | ||
.map(Clone::clone) | ||
.collect(); | ||
extract_use_items( | ||
&item_use.tree, | ||
&cfg_attrs, | ||
&mut module_items, | ||
&mut module_attrs, | ||
)?; | ||
} | ||
} | ||
syn::Item::Fn(item_fn) => { | ||
let mut is_module_init = false; | ||
item_fn.attrs.retain(|attr| { | ||
let found = attr.path().is_ident("pymodule_init"); | ||
is_module_init |= found; | ||
!found | ||
}); | ||
Comment on lines
+140
to
+144
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think this is where we can scan for If the implementation of how it would be cleanest to add these items to the module isn't readily available I'm happy to help figure this out. |
||
if is_module_init { | ||
ensure_spanned!(pymodule_init.is_none(), item_fn.span() => "only one pymodule_init may be specified"); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Similarly a UI test for this error here would be desirable. |
||
let ident = &item_fn.sig.ident; | ||
pymodule_init = Some(quote! { #ident(module)?; }); | ||
} | ||
} | ||
_ => {} | ||
} | ||
} | ||
|
||
Ok(quote! { | ||
#vis #mod_token #ident { | ||
#(#items)* | ||
|
||
pub static DEF: #krate::impl_::pymodule::ModuleDef = unsafe { | ||
use #krate::impl_::pymodule as impl_; | ||
impl_::ModuleDef::new(concat!(stringify!(#name), "\0"), #doc, impl_::ModuleInitializer(__pyo3_pymodule)) | ||
}; | ||
|
||
pub fn __pyo3_pymodule(_py: #krate::Python, module: &#krate::types::PyModule) -> #krate::PyResult<()> { | ||
#( | ||
#(#module_attrs)* | ||
#module_items::DEF.add_to_module(module)?; | ||
)* | ||
#pymodule_init | ||
Ok(()) | ||
} | ||
|
||
/// This autogenerated function is called by the python interpreter when importing | ||
/// the module. | ||
#[export_name = #pyinit_symbol] | ||
pub unsafe extern "C" fn __pyo3_init() -> *mut #krate::ffi::PyObject { | ||
#krate::impl_::trampoline::module_init(|py| DEF.make_module(py)) | ||
} | ||
} | ||
}) | ||
} | ||
|
||
/// Generates the function that is called by the python interpreter to initialize the native | ||
/// module | ||
pub fn pymodule_impl( | ||
pub fn pymodule_function_impl( | ||
fnname: &Ident, | ||
options: PyModuleOptions, | ||
doc: PythonDoc, | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -82,6 +82,10 @@ impl ModuleDef { | |
(self.initializer.0)(py, module.as_ref(py))?; | ||
Ok(module) | ||
} | ||
|
||
pub fn add_to_module(&'static self, module: &PyModule) -> PyResult<()> { | ||
module.add_object(self.make_module(module.py())?.into()) | ||
} | ||
Comment on lines
+86
to
+88
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think to avoid the reinitialization we could use a hypothetical I think this exists on all CPython versions, for PyPy it only exists on PyPy 3.9 (with the wrong name) and PyPy 3.10. Probably it'll be good enough to just reinitialize on PyPy 3.8 and older, they're out of support anyway. I'll send a PR now to fix the PyPy definitions for |
||
} | ||
|
||
#[cfg(test)] | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
UI test also makes sense for this one, I think.