use crate::error::Diagnostic; use crate::pystructseq::PyStructSequenceMeta; use crate::util::{ ALL_ALLOWED_NAMES, AttrItemMeta, AttributeExt, ClassItemMeta, ContentItem, ContentItemInner, ErrorVec, ItemMeta, ItemNursery, ModuleItemMeta, SimpleItemMeta, infer_native_call_flags, internal_doc_tokens, iter_use_idents, pyclass_ident_and_attrs, }; use core::str::FromStr; use proc_macro2::{Delimiter, Group, TokenStream, TokenTree}; 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::NestedMeta; /// A `with(...)` item that may be gated by `#[cfg(...)]` attributes. pub struct WithItem { pub cfg_attrs: Vec, pub path: syn::Path, } impl syn::parse::Parse for WithItem { fn parse(input: syn::parse::ParseStream<'_>) -> Result { 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(Self { cfg_attrs, path }) } } /// Parsed arguments for `#[pymodule(...)]`, supporting `#[cfg]` inside `with(...)`. pub struct PyModuleArgs { pub metas: Vec, pub with_items: Vec, } impl syn::parse::Parse for PyModuleArgs { fn parse(input: syn::parse::ParseStream<'_>) -> Result { 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::::parse_terminated( &content, )?; with_items.extend(items); if !input.is_empty() { input.parse::()?; } continue; } } metas.push(input.parse::()?); if input.is_empty() { break; } input.parse::()?; } Ok(Self { metas, with_items }) } } /// Generate `#[cfg(not(...))]` attributes that negate the given `#[cfg(...)]` attributes. fn negate_cfg_attrs(cfg_attrs: &[Attribute]) -> Vec { if cfg_attrs.is_empty() { return vec![]; } let predicates = 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 { Function, Attr, Class, Exception, StructSequence, } impl core::fmt::Display for AttrName { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { let s = match self { Self::Function => "pyfunction", Self::Attr => "pyattr", Self::Class => "pyclass", Self::Exception => "pyexception", Self::StructSequence => "pystruct_sequence", }; s.fmt(f) } } impl FromStr for AttrName { type Err = String; fn from_str(s: &str) -> core::result::Result { Ok(match s { "pyfunction" => Self::Function, "pyattr" => Self::Attr, "pyclass" => Self::Class, "pyexception" => Self::Exception, "pystruct_sequence" => Self::StructSequence, s => { return Err(s.to_owned()); } }) } } #[derive(Default)] struct ModuleContext { name: String, function_items: FunctionNursery, attribute_items: ItemNursery, has_module_exec: bool, errors: Vec, } pub(crate) fn impl_pymodule(args: PyModuleArgs, module_item: Item) -> Result { 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, metas.into_iter())?; // generation resources let mut context = ModuleContext { name: module_meta.simple_name()?, ..Default::default() }; let items = module_item.items_mut().ok_or_else(|| { module_meta.new_meta_error("requires actual module, not a module declaration") })?; // collect to context for item in items.iter_mut() { // Check if module_exec function is already defined if let Item::Fn(func) = item && func.sig.ident == "module_exec" { context.has_module_exec = true; } if matches!(item, Item::Impl(_) | Item::Trait(_)) { // #[pyclass] implementations continue; } let r = item.try_split_attr_mut(|attrs, item| { let (py_items, cfgs) = attrs_to_module_items(attrs, module_item_new)?; for py_item in py_items.iter().rev() { let r = py_item.gen_module_item(ModuleItemArgs { item, attrs, context: &mut context, cfgs: cfgs.as_slice(), }); context.errors.ok_or_push(r); } Ok(()) }); context.errors.ok_or_push(r); } // Detect nested #[pymodule] items (non-sub) and generate submodule init code let mut submodule_inits: Vec = Vec::new(); for item in items.iter() { if let Item::Mod(item_mod) = item { let r = (|| -> Result<()> { let attr = match item_mod .attrs .iter() .find(|a| a.path().is_ident("pymodule")) { Some(attr) => attr, None => return Ok(()), }; let args_tokens = match &attr.meta { syn::Meta::Path(_) => TokenStream::new(), syn::Meta::List(list) => list.tokens.clone(), _ => return Ok(()), }; let mod_args: PyModuleArgs = syn::parse2(args_tokens)?; let fake_ident = Ident::new("pymodule", attr.span()); let mod_meta = ModuleItemMeta::from_nested( item_mod.ident.clone(), fake_ident, mod_args.metas.into_iter(), )?; if mod_meta.sub()? { return Ok(()); } let py_name = mod_meta.simple_name()?; let mod_ident = &item_mod.ident; let cfgs: Vec<_> = item_mod .attrs .iter() .filter(|a| a.path().is_ident("cfg")) .cloned() .collect(); submodule_inits.push(quote! { #(#cfgs)* { let child_def = #mod_ident::module_def(ctx); let child = child_def.create_module(vm).expect("submodule create_module failed"); child.__init_methods(vm).expect("submodule __init_methods failed"); #mod_ident::module_exec(vm, &child).expect("submodule module_exec failed"); let child: ::rustpython_vm::PyObjectRef = child.into(); vm.__module_set_attr(module, ctx.intern_str(#py_name), child).expect("module set_attr submodule failed"); } }); Ok(()) })(); context.errors.ok_or_push(r); } } // append additional items let module_name = context.name.as_str(); let function_items = context.function_items.validate()?; let attribute_items = context.attribute_items.validate()?; let doc = doc.or_else(|| DB.get(module_name).copied().map(str::to_owned)); let doc = if let Some(doc) = doc { quote!(Some(#doc)) } else { quote!(None) }; let is_submodule = module_meta.sub()?; if !is_submodule { items.extend([ parse_quote! { pub(crate) const MODULE_NAME: &'static str = #module_name; }, parse_quote! { pub(crate) const DOC: Option<&'static str> = #doc; }, parse_quote! { pub(crate) fn module_def( ctx: &::rustpython_vm::Context, ) -> &'static ::rustpython_vm::builtins::PyModuleDef { DEF.get_or_init(|| { let mut def = ::rustpython_vm::builtins::PyModuleDef { name: ctx.intern_str(MODULE_NAME), doc: DOC.map(|doc| ctx.intern_str(doc)), methods: METHOD_DEFS, slots: Default::default(), }; def.slots.exec = Some(module_exec); def }) } }, ]); } if !is_submodule && !context.has_module_exec { items.push(parse_quote! { pub(crate) fn module_exec(vm: &::rustpython_vm::VirtualMachine, module: &::rustpython_vm::Py<::rustpython_vm::builtins::PyModule>) -> ::rustpython_vm::PyResult<()> { __module_exec(vm, module); Ok(()) } }); } // 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 = 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 = cond_withs .iter() .enumerate() .map(|(i, _)| format_ident!("__WITH_METHODS_{}", i)) .collect::>(); let cond_const_decls= 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::#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 = 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! { pub(crate) static DEF: ::rustpython_vm::builtins::PyModuleDef; } }, parse_quote! { pub(crate) const METHOD_DEFS: &'static [::rustpython_vm::function::PyMethodDef] = &#method_defs; }, parse_quote! { pub(crate) fn __init_attributes( vm: &::rustpython_vm::VirtualMachine, module: &::rustpython_vm::Py<::rustpython_vm::builtins::PyModule>, ) { #(#init_with_calls)* let ctx = &vm.ctx; #attribute_items #(#submodule_inits)* } }, parse_quote! { pub(crate) fn __module_exec( vm: &::rustpython_vm::VirtualMachine, module: &::rustpython_vm::Py<::rustpython_vm::builtins::PyModule>, ) { __init_attributes(vm, module); } }, parse_quote! { pub(crate) fn __init_dict( vm: &::rustpython_vm::VirtualMachine, module: &::rustpython_vm::Py<::rustpython_vm::builtins::PyModule>, ) { ::rustpython_vm::builtins::PyModule::__init_dict_from_def(vm, module); } }, ]); Ok(if let Some(error) = context.errors.into_error() { let error = Diagnostic::from(error); quote! { #module_item #error } } else { module_item.into_token_stream() }) } fn module_item_new( index: usize, attr_name: AttrName, py_attrs: Vec, ) -> Box> { match attr_name { AttrName::Function => Box::new(FunctionItem { inner: ContentItemInner { index, attr_name }, py_attrs, }), AttrName::Attr => Box::new(AttributeItem { inner: ContentItemInner { index, attr_name }, py_attrs, }), // pyexception is treated like pyclass - both define types AttrName::Class | AttrName::Exception => Box::new(ClassItem { inner: ContentItemInner { index, attr_name }, py_attrs, }), AttrName::StructSequence => Box::new(StructSequenceItem { inner: ContentItemInner { index, attr_name }, py_attrs, }), } } fn attrs_to_module_items(attrs: &[Attribute], item_new: F) -> Result<(Vec, Vec)> where F: Fn(usize, AttrName, Vec) -> R, { let mut cfgs: Vec = Vec::new(); let mut result = Vec::new(); let mut iter = attrs.iter().enumerate().peekable(); while let Some((_, attr)) = iter.peek() { // take all cfgs but no py items let attr = *attr; if let Some(ident) = attr.get_ident() { let attr_name = ident.to_string(); if attr_name == "cfg" { cfgs.push(attr.clone()); } else if ALL_ALLOWED_NAMES.contains(&attr_name.as_str()) { break; } } iter.next(); } let mut closed = false; let mut py_attrs = Vec::new(); for (i, attr) in iter { // take py items but no cfgs let attr_name = if let Some(ident) = attr.get_ident() { ident.to_string() } else { continue; }; if attr_name == "cfg" { bail_span!(attr, "#[py*] items must be placed under `cfgs`") } let attr_name = match AttrName::from_str(attr_name.as_str()) { Ok(name) => name, Err(wrong_name) => { if !ALL_ALLOWED_NAMES.contains(&wrong_name.as_str()) { continue; } else if closed { bail_span!(attr, "Only one #[pyattr] annotated #[py*] item can exist") } bail_span!(attr, "#[pymodule] doesn't accept #[{}]", wrong_name) } }; if attr_name == AttrName::Attr { if !result.is_empty() { bail_span!( attr, "#[pyattr] must be placed on top of other #[py*] items", ) } py_attrs.push(i); continue; } if py_attrs.is_empty() { result.push(item_new(i, attr_name, Vec::new())); } else { match attr_name { AttrName::Class | AttrName::Function | AttrName::Exception | AttrName::StructSequence => { result.push(item_new(i, attr_name, py_attrs.clone())); } _ => { bail_span!( attr, "#[pyclass], #[pyfunction], #[pyexception], or #[pystruct_sequence] can follow #[pyattr]", ) } } py_attrs.clear(); closed = true; } } if let Some(last) = py_attrs.pop() { assert!(!closed); result.push(item_new(last, AttrName::Attr, py_attrs)); } Ok((result, cfgs)) } #[derive(Default)] struct FunctionNursery { items: Vec, } struct FunctionNurseryItem { py_names: Vec, cfgs: Vec, ident: Ident, doc: TokenStream, call_flags: TokenStream, } impl FunctionNursery { fn add_item(&mut self, item: FunctionNurseryItem) { self.items.push(item); } fn validate(self) -> Result { let mut name_set = HashSet::new(); for item in &self.items { for py_name in &item.py_names { if !name_set.insert((py_name.to_owned(), &item.cfgs)) { bail_span!(item.ident, "duplicate method name `{}`", py_name); } } } Ok(ValidatedFunctionNursery(self)) } } struct ValidatedFunctionNursery(FunctionNursery); impl ToTokens for ValidatedFunctionNursery { fn to_tokens(&self, tokens: &mut TokenStream) { let mut inner_tokens = TokenStream::new(); for item in &self.0.items { let ident = &item.ident; let cfgs = &item.cfgs; let cfgs = quote!(#(#cfgs)*); let py_names = &item.py_names; let doc = &item.doc; let flags = &item.call_flags; inner_tokens.extend(quote![ #( #cfgs rustpython_vm::function::PyMethodDef::new_const( #py_names, #ident, #flags, #doc, ), )* ]); } let array: TokenTree = Group::new(Delimiter::Bracket, inner_tokens).into(); tokens.extend([array]); } } /// #[pyfunction] struct FunctionItem { inner: ContentItemInner, py_attrs: Vec, } /// #[pyclass] struct ClassItem { inner: ContentItemInner, py_attrs: Vec, } /// #[pyattr] struct AttributeItem { inner: ContentItemInner, py_attrs: Vec, } /// #[pystruct_sequence] struct StructSequenceItem { inner: ContentItemInner, py_attrs: Vec, } impl ContentItem for FunctionItem { type AttrName = AttrName; fn inner(&self) -> &ContentItemInner { &self.inner } } impl ContentItem for ClassItem { type AttrName = AttrName; fn inner(&self) -> &ContentItemInner { &self.inner } } impl ContentItem for AttributeItem { type AttrName = AttrName; fn inner(&self) -> &ContentItemInner { &self.inner } } impl ContentItem for StructSequenceItem { type AttrName = AttrName; fn inner(&self) -> &ContentItemInner { &self.inner } } struct ModuleItemArgs<'a> { item: &'a mut Item, attrs: &'a mut Vec, context: &'a mut ModuleContext, cfgs: &'a [Attribute], } impl<'a> ModuleItemArgs<'a> { fn module_name(&'a self) -> &'a str { self.context.name.as_str() } } trait ModuleItem: ContentItem { fn gen_module_item(&self, args: ModuleItemArgs<'_>) -> Result<()>; } impl ModuleItem for FunctionItem { fn gen_module_item(&self, args: ModuleItemArgs<'_>) -> Result<()> { let func = args .item .function_or_method() .map_err(|_| self.new_syn_error(args.item.span(), "can only be on a function"))?; let ident = &func.sig().ident; let item_attr = args.attrs.remove(self.index()); let item_meta = SimpleItemMeta::from_attr(ident.clone(), &item_attr)?; let py_name = item_meta.simple_name()?; let module = args.module_name(); // TODO: doc must exist at least one of code or CPython let doc = args.attrs.doc().or_else(|| { DB.get(&format!("{module}.{py_name}")) .copied() .map(str::to_owned) }); let doc = internal_doc_tokens(func.sig(), &py_name, None, doc, None, Some("$module")); let py_names = { if self.py_attrs.is_empty() { vec![py_name] } else { let mut py_names = HashSet::new(); py_names.insert(py_name); for attr_index in self.py_attrs.iter().rev() { let mut loop_unit = || { let attr_attr = args.attrs.remove(*attr_index); let item_meta = SimpleItemMeta::from_attr(ident.clone(), &attr_attr)?; let py_name = item_meta.simple_name()?; let inserted = py_names.insert(py_name.clone()); if !inserted { return Err(self.new_syn_error( ident.span(), &format!( "`{py_name}` is duplicated name for multiple py* attribute" ), )); } Ok(()) }; let r = loop_unit(); args.context.errors.ok_or_push(r); } py_names.into_iter().collect::>() } }; let call_flags = infer_native_call_flags(func.sig(), 0); args.context.function_items.add_item(FunctionNurseryItem { ident: ident.to_owned(), py_names, cfgs: args.cfgs.to_vec(), doc, call_flags, }); Ok(()) } } impl ModuleItem for ClassItem { fn gen_module_item(&self, args: ModuleItemArgs<'_>) -> Result<()> { let (ident, _) = pyclass_ident_and_attrs(args.item)?; let (class_name, class_new) = { let class_attr = &mut args.attrs[self.inner.index]; let no_attr = class_attr.try_remove_name("no_attr")?; if self.py_attrs.is_empty() { // check no_attr before ClassItemMeta::from_attr if no_attr.is_none() { bail_span!( ident, "#[{name}] requires #[pyattr] to be a module attribute. \ To keep it free type, try #[{name}(no_attr)]", name = self.attr_name() ) } } let no_attr = no_attr.is_some(); let is_use = matches!(&args.item, syn::Item::Use(_)); let class_meta = ClassItemMeta::from_attr(ident.clone(), class_attr)?; let module_name = args.context.name.clone(); let module_name = if let Some(class_module_name) = class_meta.module().ok().flatten() { class_module_name } else { class_attr.fill_nested_meta("module", || { parse_quote! {module = #module_name} })?; module_name }; let class_name = if no_attr && is_use { "".to_owned() } else { class_meta.class_name()? }; let class_new = quote_spanned!(ident.span() => let new_class = <#ident as ::rustpython_vm::class::PyClassImpl>::make_static_type(); // Only set __module__ string if the class doesn't already have a // getset descriptor for __module__ (which provides instance-level // module resolution, e.g. TypeAliasType) { let module_key = rustpython_vm::identifier!(ctx, __module__); let has_module_getset = new_class.attributes .get(module_key) .is_some_and(|v| v.downcastable::()); if !has_module_getset { new_class.set_attr(module_key, vm.new_pyobj(#module_name)); } } ); (class_name, class_new) }; let mut py_names = Vec::new(); for attr_index in self.py_attrs.iter().rev() { let mut loop_unit = || { let attr_attr = args.attrs.remove(*attr_index); let item_meta = SimpleItemMeta::from_attr(ident.clone(), &attr_attr)?; let py_name = item_meta .optional_name() .unwrap_or_else(|| class_name.clone()); py_names.push(py_name); Ok(()) }; let r = loop_unit(); args.context.errors.ok_or_push(r); } let set_attr = match py_names.len() { 0 => quote! { let _ = new_class; // suppress warning let _ = vm.ctx.intern_str(#class_name); }, 1 => { let py_name = &py_names[0]; quote! { vm.__module_set_attr(&module, vm.ctx.intern_str(#py_name), new_class).unwrap(); } } _ => quote! { for name in [#(#py_names,)*] { vm.__module_set_attr(&module, vm.ctx.intern_str(name), new_class.clone()).unwrap(); } }, }; args.context.attribute_items.add_item( ident.clone(), py_names, args.cfgs.to_vec(), quote_spanned! { ident.span() => #class_new #set_attr }, 0, ); Ok(()) } } impl ModuleItem for StructSequenceItem { fn gen_module_item(&self, args: ModuleItemArgs<'_>) -> Result<()> { // Get the struct identifier (this IS the Python type, e.g., PyStructTime) let pytype_ident = match args.item { Item::Struct(s) => s.ident.clone(), other => bail_span!(other, "#[pystruct_sequence] can only be on a struct"), }; // Parse the #[pystruct_sequence(name = "...", module = "...", no_attr)] attribute let structseq_attr = &args.attrs[self.inner.index]; let meta = PyStructSequenceMeta::from_attr(pytype_ident.clone(), structseq_attr)?; let class_name = meta.class_name()?.ok_or_else(|| { syn::Error::new_spanned( structseq_attr, "#[pystruct_sequence] requires name parameter", ) })?; let module_opt = meta.module()?; let has_module = module_opt.is_some(); let module_name = module_opt.unwrap_or_else(|| args.context.name.clone()); if !has_module { let structseq_attr = &mut args.attrs[self.inner.index]; structseq_attr.fill_nested_meta("module", || { parse_quote! {module = #module_name} })?; } let no_attr = meta.no_attr()?; // Generate the class creation code let class_new = quote_spanned!(pytype_ident.span() => let new_class = <#pytype_ident as ::rustpython_vm::class::PyClassImpl>::make_static_type(); { let module_key = rustpython_vm::identifier!(ctx, __module__); let has_module_getset = new_class.attributes .get(module_key) .is_some_and(|v| v.downcastable::()); if !has_module_getset { new_class.set_attr(module_key, vm.new_pyobj(#module_name)); } } ); // Handle py_attrs for custom names, or use class_name as default let mut py_names = Vec::new(); for attr_index in self.py_attrs.iter().rev() { let attr_attr = args.attrs.remove(*attr_index); let item_meta = SimpleItemMeta::from_attr(pytype_ident.clone(), &attr_attr)?; let py_name = item_meta .optional_name() .unwrap_or_else(|| class_name.clone()); py_names.push(py_name); } // Require explicit #[pyattr] or no_attr, like #[pyclass] if self.py_attrs.is_empty() && !no_attr { bail_span!( pytype_ident, "#[pystruct_sequence] requires #[pyattr] to be a module attribute. \ To keep it free type, try #[pystruct_sequence(..., no_attr)]" ) } let set_attr = match py_names.len() { 0 => quote! { let _ = new_class; // suppress warning }, 1 => { let py_name = &py_names[0]; quote! { vm.__module_set_attr(&module, vm.ctx.intern_str(#py_name), new_class).unwrap(); } } _ => quote! { for name in [#(#py_names,)*] { vm.__module_set_attr(&module, vm.ctx.intern_str(name), new_class.clone()).unwrap(); } }, }; args.context.attribute_items.add_item( pytype_ident.clone(), py_names, args.cfgs.to_vec(), quote_spanned! { pytype_ident.span() => #class_new #set_attr }, 0, ); Ok(()) } } impl ModuleItem for AttributeItem { fn gen_module_item(&self, args: ModuleItemArgs<'_>) -> Result<()> { let cfgs = args.cfgs.to_vec(); let attr = args.attrs.remove(self.index()); let (ident, py_name, let_obj) = match args.item { Item::Fn(syn::ItemFn { sig, block, .. }) => { let ident = &sig.ident; // If `once` keyword is in #[pyattr], // wrapping it with static_cell for preventing it from using it as function let attr_meta = AttrItemMeta::from_attr(ident.clone(), &attr)?; if attr_meta.inner()._bool("once")? { let stmts = &block.stmts; let return_type = match &sig.output { syn::ReturnType::Default => { unreachable!("#[pyattr] attached function must have return type.") } syn::ReturnType::Type(_, ty) => ty, }; let stmt: syn::Stmt = parse_quote! { { rustpython_common::static_cell! { static ERROR: #return_type; } ERROR .get_or_init(|| { #(#stmts)* }) .clone() } }; block.stmts = vec![stmt]; } let py_name = attr_meta.simple_name()?; ( ident.clone(), py_name, quote_spanned! { ident.span() => let obj = vm.new_pyobj(#ident(vm)); }, ) } Item::Const(syn::ItemConst { ident, .. }) => { let item_meta = SimpleItemMeta::from_attr(ident.clone(), &attr)?; let py_name = item_meta.simple_name()?; ( ident.clone(), py_name, quote_spanned! { ident.span() => let obj = vm.new_pyobj(#ident); }, ) } Item::Use(item) => { if !self.py_attrs.is_empty() { return Err(self .new_syn_error(item.span(), "Only single #[pyattr] is allowed for `use`")); } let _ = iter_use_idents(item, |ident, is_unique| { let item_meta = SimpleItemMeta::from_attr(ident.clone(), &attr)?; let py_name = if is_unique { item_meta.simple_name()? } else if item_meta.optional_name().is_some() { // this check actually doesn't need to be placed in loop return Err(self.new_syn_error( ident.span(), "`name` attribute is not allowed for multiple use items", )); } else { ident.to_string() }; let tokens = quote_spanned! { ident.span() => vm.__module_set_attr(module, vm.ctx.intern_str(#py_name), vm.new_pyobj(#ident)).unwrap(); }; args.context.attribute_items.add_item( ident.clone(), vec![py_name], cfgs.clone(), tokens, 1, ); Ok(()) }); return Ok(()); } other => { return Err( self.new_syn_error(other.span(), "can only be on a function, const and use") ); } }; let (tokens, py_names) = if self.py_attrs.is_empty() { ( quote_spanned! { ident.span() => { #let_obj vm.__module_set_attr(module, vm.ctx.intern_str(#py_name), obj).unwrap(); }}, vec![py_name], ) } else { let mut names = vec![py_name]; for attr_index in self.py_attrs.iter().rev() { let mut loop_unit = || { let attr_attr = args.attrs.remove(*attr_index); let item_meta = AttrItemMeta::from_attr(ident.clone(), &attr_attr)?; if item_meta.inner()._bool("once")? { return Err(self.new_syn_error( ident.span(), "#[pyattr(once)] is only allowed for the bottom-most item", )); } let py_name = item_meta.optional_name().ok_or_else(|| { self.new_syn_error( ident.span(), "#[pyattr(name = ...)] is mandatory except for the bottom-most item", ) })?; names.push(py_name); Ok(()) }; let r = loop_unit(); args.context.errors.ok_or_push(r); } ( quote_spanned! { ident.span() => { #let_obj for name in [#(#names),*] { vm.__module_set_attr(module, vm.ctx.intern_str(name), obj.clone()).unwrap(); } }}, names, ) }; args.context .attribute_items .add_item(ident, py_names, cfgs, tokens, 1); Ok(()) } }