Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion crates/derive-impl/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ use quote::ToTokens;
use syn::{DeriveInput, Item};
use syn_ext::types::PunctuatedNestedMeta;

pub use pymodule::PyModuleArgs;

pub use compile_bytecode::Compiler;

fn result_to_tokens(result: Result<TokenStream, impl Into<Diagnostic>>) -> TokenStream {
Expand Down Expand Up @@ -54,7 +56,7 @@ pub fn pyexception(attr: PunctuatedNestedMeta, item: Item) -> TokenStream {
}
}

pub fn pymodule(attr: PunctuatedNestedMeta, item: Item) -> TokenStream {
pub fn pymodule(attr: PyModuleArgs, item: Item) -> TokenStream {
result_to_tokens(pymodule::impl_pymodule(attr, item))
}

Expand Down
155 changes: 144 additions & 11 deletions crates/derive-impl/src/pymodule.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,97 @@ use crate::util::{
};
use core::str::FromStr;
use proc_macro2::{Delimiter, Group, TokenStream, TokenTree};
use quote::{ToTokens, quote, quote_spanned};
use quote::{ToTokens, format_ident, quote, quote_spanned};
use rustpython_doc::DB;
use std::collections::HashSet;
use syn::{Attribute, Ident, Item, Result, parse_quote, spanned::Spanned};
use syn_ext::ext::*;
use syn_ext::types::PunctuatedNestedMeta;
use syn_ext::types::NestedMeta;

/// A `with(...)` item that may be gated by `#[cfg(...)]` attributes.
pub struct WithItem {
pub cfg_attrs: Vec<Attribute>,
pub path: syn::Path,
}

impl syn::parse::Parse for WithItem {
fn parse(input: syn::parse::ParseStream<'_>) -> Result<Self> {
let cfg_attrs = Attribute::parse_outer(input)?;
for attr in &cfg_attrs {
if !attr.path().is_ident("cfg") {
return Err(syn::Error::new_spanned(
attr,
"only #[cfg(...)] is supported in with()",
));
}
}
let path = input.parse()?;
Ok(WithItem { cfg_attrs, path })
}
}

/// Parsed arguments for `#[pymodule(...)]`, supporting `#[cfg]` inside `with(...)`.
pub struct PyModuleArgs {
pub metas: Vec<NestedMeta>,
pub with_items: Vec<WithItem>,
}

impl syn::parse::Parse for PyModuleArgs {
fn parse(input: syn::parse::ParseStream<'_>) -> Result<Self> {
let mut metas = Vec::new();
let mut with_items = Vec::new();

while !input.is_empty() {
// Detect `with(...)` — an ident "with" followed by a paren group
if input.peek(Ident) && input.peek2(syn::token::Paren) {
let fork = input.fork();
let ident: Ident = fork.parse()?;
if ident == "with" {
// Advance past "with"
let _: Ident = input.parse()?;
let content;
syn::parenthesized!(content in input);
let items =
syn::punctuated::Punctuated::<WithItem, syn::Token![,]>::parse_terminated(
&content,
)?;
with_items.extend(items);
if !input.is_empty() {
input.parse::<syn::Token![,]>()?;
}
continue;
}
}
metas.push(input.parse::<NestedMeta>()?);
if input.is_empty() {
break;
}
input.parse::<syn::Token![,]>()?;
}

Ok(PyModuleArgs { metas, with_items })
}
}

/// Generate `#[cfg(not(...))]` attributes that negate the given `#[cfg(...)]` attributes.
fn negate_cfg_attrs(cfg_attrs: &[Attribute]) -> Vec<Attribute> {
if cfg_attrs.is_empty() {
return vec![];
}
let predicates: Vec<_> = cfg_attrs
.iter()
.map(|attr| match &attr.meta {
syn::Meta::List(list) => list.tokens.clone(),
_ => unreachable!("only #[cfg(...)] should be here"),
})
.collect();
if predicates.len() == 1 {
let predicate = &predicates[0];
vec![parse_quote!(#[cfg(not(#predicate))])]
} else {
vec![parse_quote!(#[cfg(not(all(#(#predicates),*)))])]
}
}

#[derive(Clone, Copy, Eq, PartialEq)]
enum AttrName {
Expand Down Expand Up @@ -62,14 +147,15 @@ struct ModuleContext {
errors: Vec<syn::Error>,
}

pub fn impl_pymodule(attr: PunctuatedNestedMeta, module_item: Item) -> Result<TokenStream> {
pub fn impl_pymodule(args: PyModuleArgs, module_item: Item) -> Result<TokenStream> {
let PyModuleArgs { metas, with_items } = args;
let (doc, mut module_item) = match module_item {
Item::Mod(m) => (m.attrs.doc(), m),
other => bail_span!(other, "#[pymodule] can only be on a full module"),
};
let fake_ident = Ident::new("pymodule", module_item.span());
let module_meta =
ModuleItemMeta::from_nested(module_item.ident.clone(), fake_ident, attr.into_iter())?;
ModuleItemMeta::from_nested(module_item.ident.clone(), fake_ident, metas.into_iter())?;

// generation resources
let mut context = ModuleContext {
Expand Down Expand Up @@ -119,7 +205,6 @@ pub fn impl_pymodule(attr: PunctuatedNestedMeta, module_item: Item) -> Result<To
quote!(None)
};
let is_submodule = module_meta.sub()?;
let withs = module_meta.with()?;
if !is_submodule {
items.extend([
parse_quote! {
Expand Down Expand Up @@ -154,16 +239,66 @@ pub fn impl_pymodule(attr: PunctuatedNestedMeta, module_item: Item) -> Result<To
}
});
}
let method_defs = if withs.is_empty() {
// Split with_items into unconditional and cfg-gated groups
let (uncond_withs, cond_withs): (Vec<_>, Vec<_>) =
with_items.iter().partition(|w| w.cfg_attrs.is_empty());
let uncond_paths: Vec<_> = uncond_withs.iter().map(|w| &w.path).collect();

let method_defs = if with_items.is_empty() {
quote!(#function_items)
} else {
// For cfg-gated with items, generate conditional const declarations
// so the total array size adapts to the cfg at compile time
let cond_const_names: Vec<_> = cond_withs
.iter()
.enumerate()
.map(|(i, _)| format_ident!("__WITH_METHODS_{}", i))
.collect();
let cond_const_decls: Vec<_> = cond_withs
.iter()
.zip(&cond_const_names)
.map(|(w, name)| {
let cfg_attrs = &w.cfg_attrs;
let neg_attrs = negate_cfg_attrs(&w.cfg_attrs);
let path = &w.path;
quote! {
#(#cfg_attrs)*
const #name: &'static [::rustpython_vm::function::PyMethodDef] = super::#path::METHOD_DEFS;
#(#neg_attrs)*
const #name: &'static [::rustpython_vm::function::PyMethodDef] = &[];
}
})
.collect();

quote!({
const OWN_METHODS: &'static [::rustpython_vm::function::PyMethodDef] = &#function_items;
#(#cond_const_decls)*
rustpython_vm::function::PyMethodDef::__const_concat_arrays::<
{ OWN_METHODS.len() #(+ super::#withs::METHOD_DEFS.len())* },
>(&[#(super::#withs::METHOD_DEFS,)* OWN_METHODS])
{ OWN_METHODS.len()
#(+ super::#uncond_paths::METHOD_DEFS.len())*
#(+ #cond_const_names.len())*
},
>(&[
#(super::#uncond_paths::METHOD_DEFS,)*
#(#cond_const_names,)*
OWN_METHODS
])
})
};

// Generate __init_attributes calls, wrapping cfg-gated items
let init_with_calls: Vec<_> = with_items
.iter()
.map(|w| {
let cfg_attrs = &w.cfg_attrs;
let path = &w.path;
quote! {
#(#cfg_attrs)*
super::#path::__init_attributes(vm, module);
}
})
.collect();

items.extend([
parse_quote! {
::rustpython_vm::common::static_cell! {
Expand All @@ -178,9 +313,7 @@ pub fn impl_pymodule(attr: PunctuatedNestedMeta, module_item: Item) -> Result<To
vm: &::rustpython_vm::VirtualMachine,
module: &::rustpython_vm::Py<::rustpython_vm::builtins::PyModule>,
) {
#(
super::#withs::__init_attributes(vm, module);
)*
#(#init_with_calls)*
let ctx = &vm.ctx;
#attribute_items
}
Expand Down
16 changes: 1 addition & 15 deletions crates/derive-impl/src/util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -315,7 +315,7 @@ impl ItemMeta for SimpleItemMeta {
pub(crate) struct ModuleItemMeta(pub ItemMetaInner);

impl ItemMeta for ModuleItemMeta {
const ALLOWED_NAMES: &'static [&'static str] = &["name", "with", "sub"];
const ALLOWED_NAMES: &'static [&'static str] = &["name", "sub"];

fn from_inner(inner: ItemMetaInner) -> Self {
Self(inner)
Expand All @@ -330,20 +330,6 @@ impl ModuleItemMeta {
pub fn sub(&self) -> Result<bool> {
self.inner()._bool("sub")
}

pub fn with(&self) -> Result<Vec<&syn::Path>> {
let mut withs = Vec::new();
let Some(nested) = self.inner()._optional_list("with")? else {
return Ok(withs);
};
for meta in nested {
let NestedMeta::Meta(Meta::Path(path)) = meta else {
bail_span!(meta, "#[pymodule(with(...))] arguments should be paths")
};
withs.push(path);
}
Ok(withs)
}
}

pub(crate) struct AttrItemMeta(pub ItemMetaInner);
Expand Down
2 changes: 1 addition & 1 deletion crates/derive/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -209,7 +209,7 @@ pub fn pyexception(attr: TokenStream, item: TokenStream) -> TokenStream {
/// - `name`: the name of the function in Python, by default it is the same as the associated Rust function.
#[proc_macro_attribute]
pub fn pymodule(attr: TokenStream, item: TokenStream) -> TokenStream {
let attr = parse_macro_input!(attr with Punctuated::parse_terminated);
let attr = parse_macro_input!(attr as derive_impl::PyModuleArgs);
let item = parse_macro_input!(item);
derive_impl::pymodule(attr, item).into()
}
Expand Down
19 changes: 6 additions & 13 deletions crates/stdlib/src/openssl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,12 @@ fn probe() -> &'static ProbeResult {
}

#[allow(non_upper_case_globals)]
#[pymodule(with(cert::ssl_cert, ssl_error::ssl_error, ossl101, ossl111, windows))]
#[pymodule(with(
cert::ssl_cert,
ssl_error::ssl_error,
#[cfg(ossl101)] ossl101,
#[cfg(ossl111)] ossl111,
#[cfg(windows)] windows))]
mod _ssl {
use super::{bio, probe};

Expand Down Expand Up @@ -4070,18 +4075,6 @@ mod _ssl {
}
}

#[cfg(not(ossl101))]
#[pymodule(sub)]
mod ossl101 {}

#[cfg(not(ossl111))]
#[pymodule(sub)]
mod ossl111 {}

#[cfg(not(windows))]
#[pymodule(sub)]
mod windows {}

#[allow(non_upper_case_globals)]
#[cfg(ossl101)]
#[pymodule(sub)]
Expand Down
8 changes: 2 additions & 6 deletions crates/vm/src/stdlib/time.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ unsafe extern "C" {
fn c_tzset();
}

#[pymodule(name = "time", with(platform))]
#[pymodule(name = "time", with(#[cfg(any(unix, windows))] platform))]
mod decl {
use crate::{
AsObject, Py, PyObjectRef, PyResult, VirtualMachine,
Expand Down Expand Up @@ -571,6 +571,7 @@ mod decl {
}
}

#[cfg(any(unix, windows))]
#[allow(unused_imports)]
use super::platform::*;

Expand Down Expand Up @@ -986,8 +987,3 @@ mod platform {
Ok(Duration::from_nanos((k_time + u_time) * 100))
}
}

// mostly for wasm32
#[cfg(not(any(unix, windows)))]
#[pymodule(sub)]
mod platform {}
Loading