Skip to main content

rustpython_derive_impl/
pymodule.rs

1use crate::error::Diagnostic;
2use crate::pystructseq::PyStructSequenceMeta;
3use crate::util::{
4    ALL_ALLOWED_NAMES, AttrItemMeta, AttributeExt, ClassItemMeta, ContentItem, ContentItemInner,
5    ErrorVec, ItemMeta, ItemNursery, ModuleItemMeta, SimpleItemMeta, format_doc,
6    infer_native_call_flags, iter_use_idents, pyclass_ident_and_attrs, text_signature,
7};
8use core::str::FromStr;
9use proc_macro2::{Delimiter, Group, TokenStream, TokenTree};
10use quote::{ToTokens, format_ident, quote, quote_spanned};
11use rustpython_doc::DB;
12use std::collections::HashSet;
13use syn::{Attribute, Ident, Item, Result, parse_quote, spanned::Spanned};
14use syn_ext::ext::*;
15use syn_ext::types::NestedMeta;
16
17/// A `with(...)` item that may be gated by `#[cfg(...)]` attributes.
18pub struct WithItem {
19    pub cfg_attrs: Vec<Attribute>,
20    pub path: syn::Path,
21}
22
23impl syn::parse::Parse for WithItem {
24    fn parse(input: syn::parse::ParseStream<'_>) -> Result<Self> {
25        let cfg_attrs = Attribute::parse_outer(input)?;
26        for attr in &cfg_attrs {
27            if !attr.path().is_ident("cfg") {
28                return Err(syn::Error::new_spanned(
29                    attr,
30                    "only #[cfg(...)] is supported in with()",
31                ));
32            }
33        }
34        let path = input.parse()?;
35        Ok(WithItem { cfg_attrs, path })
36    }
37}
38
39/// Parsed arguments for `#[pymodule(...)]`, supporting `#[cfg]` inside `with(...)`.
40pub struct PyModuleArgs {
41    pub metas: Vec<NestedMeta>,
42    pub with_items: Vec<WithItem>,
43}
44
45impl syn::parse::Parse for PyModuleArgs {
46    fn parse(input: syn::parse::ParseStream<'_>) -> Result<Self> {
47        let mut metas = Vec::new();
48        let mut with_items = Vec::new();
49
50        while !input.is_empty() {
51            // Detect `with(...)` — an ident "with" followed by a paren group
52            if input.peek(Ident) && input.peek2(syn::token::Paren) {
53                let fork = input.fork();
54                let ident: Ident = fork.parse()?;
55                if ident == "with" {
56                    // Advance past "with"
57                    let _: Ident = input.parse()?;
58                    let content;
59                    syn::parenthesized!(content in input);
60                    let items =
61                        syn::punctuated::Punctuated::<WithItem, syn::Token![,]>::parse_terminated(
62                            &content,
63                        )?;
64                    with_items.extend(items);
65                    if !input.is_empty() {
66                        input.parse::<syn::Token![,]>()?;
67                    }
68                    continue;
69                }
70            }
71            metas.push(input.parse::<NestedMeta>()?);
72            if input.is_empty() {
73                break;
74            }
75            input.parse::<syn::Token![,]>()?;
76        }
77
78        Ok(PyModuleArgs { metas, with_items })
79    }
80}
81
82/// Generate `#[cfg(not(...))]` attributes that negate the given `#[cfg(...)]` attributes.
83fn negate_cfg_attrs(cfg_attrs: &[Attribute]) -> Vec<Attribute> {
84    if cfg_attrs.is_empty() {
85        return vec![];
86    }
87    let predicates: Vec<_> = cfg_attrs
88        .iter()
89        .map(|attr| match &attr.meta {
90            syn::Meta::List(list) => list.tokens.clone(),
91            _ => unreachable!("only #[cfg(...)] should be here"),
92        })
93        .collect();
94    if predicates.len() == 1 {
95        let predicate = &predicates[0];
96        vec![parse_quote!(#[cfg(not(#predicate))])]
97    } else {
98        vec![parse_quote!(#[cfg(not(all(#(#predicates),*)))])]
99    }
100}
101
102#[derive(Clone, Copy, Eq, PartialEq)]
103enum AttrName {
104    Function,
105    Attr,
106    Class,
107    Exception,
108    StructSequence,
109}
110
111impl core::fmt::Display for AttrName {
112    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
113        let s = match self {
114            Self::Function => "pyfunction",
115            Self::Attr => "pyattr",
116            Self::Class => "pyclass",
117            Self::Exception => "pyexception",
118            Self::StructSequence => "pystruct_sequence",
119        };
120        s.fmt(f)
121    }
122}
123
124impl FromStr for AttrName {
125    type Err = String;
126
127    fn from_str(s: &str) -> core::result::Result<Self, Self::Err> {
128        Ok(match s {
129            "pyfunction" => Self::Function,
130            "pyattr" => Self::Attr,
131            "pyclass" => Self::Class,
132            "pyexception" => Self::Exception,
133            "pystruct_sequence" => Self::StructSequence,
134            s => {
135                return Err(s.to_owned());
136            }
137        })
138    }
139}
140
141#[derive(Default)]
142struct ModuleContext {
143    name: String,
144    function_items: FunctionNursery,
145    attribute_items: ItemNursery,
146    has_module_exec: bool,
147    errors: Vec<syn::Error>,
148}
149
150pub fn impl_pymodule(args: PyModuleArgs, module_item: Item) -> Result<TokenStream> {
151    let PyModuleArgs { metas, with_items } = args;
152    let (doc, mut module_item) = match module_item {
153        Item::Mod(m) => (m.attrs.doc(), m),
154        other => bail_span!(other, "#[pymodule] can only be on a full module"),
155    };
156    let fake_ident = Ident::new("pymodule", module_item.span());
157    let module_meta =
158        ModuleItemMeta::from_nested(module_item.ident.clone(), fake_ident, metas.into_iter())?;
159
160    // generation resources
161    let mut context = ModuleContext {
162        name: module_meta.simple_name()?,
163        ..Default::default()
164    };
165    let items = module_item.items_mut().ok_or_else(|| {
166        module_meta.new_meta_error("requires actual module, not a module declaration")
167    })?;
168
169    // collect to context
170    for item in items.iter_mut() {
171        // Check if module_exec function is already defined
172        if let Item::Fn(func) = item
173            && func.sig.ident == "module_exec"
174        {
175            context.has_module_exec = true;
176        }
177        if matches!(item, Item::Impl(_) | Item::Trait(_)) {
178            // #[pyclass] implementations
179            continue;
180        }
181        let r = item.try_split_attr_mut(|attrs, item| {
182            let (py_items, cfgs) = attrs_to_module_items(attrs, module_item_new)?;
183            for py_item in py_items.iter().rev() {
184                let r = py_item.gen_module_item(ModuleItemArgs {
185                    item,
186                    attrs,
187                    context: &mut context,
188                    cfgs: cfgs.as_slice(),
189                });
190                context.errors.ok_or_push(r);
191            }
192            Ok(())
193        });
194        context.errors.ok_or_push(r);
195    }
196
197    // Detect nested #[pymodule] items (non-sub) and generate submodule init code
198    let mut submodule_inits: Vec<TokenStream> = Vec::new();
199    for item in items.iter() {
200        if let Item::Mod(item_mod) = item {
201            let r = (|| -> Result<()> {
202                let attr = match item_mod
203                    .attrs
204                    .iter()
205                    .find(|a| a.path().is_ident("pymodule"))
206                {
207                    Some(attr) => attr,
208                    None => return Ok(()),
209                };
210                let args_tokens = match &attr.meta {
211                    syn::Meta::Path(_) => TokenStream::new(),
212                    syn::Meta::List(list) => list.tokens.clone(),
213                    _ => return Ok(()),
214                };
215                let mod_args: PyModuleArgs = syn::parse2(args_tokens)?;
216                let fake_ident = Ident::new("pymodule", attr.span());
217                let mod_meta = ModuleItemMeta::from_nested(
218                    item_mod.ident.clone(),
219                    fake_ident,
220                    mod_args.metas.into_iter(),
221                )?;
222                if mod_meta.sub()? {
223                    return Ok(());
224                }
225                let py_name = mod_meta.simple_name()?;
226                let mod_ident = &item_mod.ident;
227                let cfgs: Vec<_> = item_mod
228                    .attrs
229                    .iter()
230                    .filter(|a| a.path().is_ident("cfg"))
231                    .cloned()
232                    .collect();
233                submodule_inits.push(quote! {
234                    #(#cfgs)*
235                    {
236                        let child_def = #mod_ident::module_def(ctx);
237                        let child = child_def.create_module(vm).expect("submodule create_module failed");
238                        child.__init_methods(vm).expect("submodule __init_methods failed");
239                        #mod_ident::module_exec(vm, &child).expect("submodule module_exec failed");
240                        let child: ::rustpython_vm::PyObjectRef = child.into();
241                        vm.__module_set_attr(module, ctx.intern_str(#py_name), child).expect("module set_attr submodule failed");
242                    }
243                });
244                Ok(())
245            })();
246            context.errors.ok_or_push(r);
247        }
248    }
249
250    // append additional items
251    let module_name = context.name.as_str();
252    let function_items = context.function_items.validate()?;
253    let attribute_items = context.attribute_items.validate()?;
254    let doc = doc.or_else(|| DB.get(module_name).copied().map(str::to_owned));
255    let doc = if let Some(doc) = doc {
256        quote!(Some(#doc))
257    } else {
258        quote!(None)
259    };
260    let is_submodule = module_meta.sub()?;
261    if !is_submodule {
262        items.extend([
263            parse_quote! {
264                pub(crate) const MODULE_NAME: &'static str = #module_name;
265            },
266            parse_quote! {
267                pub(crate) const DOC: Option<&'static str> = #doc;
268            },
269            parse_quote! {
270                pub(crate) fn module_def(
271                    ctx: &::rustpython_vm::Context,
272                ) -> &'static ::rustpython_vm::builtins::PyModuleDef {
273                    DEF.get_or_init(|| {
274                        let mut def = ::rustpython_vm::builtins::PyModuleDef {
275                            name: ctx.intern_str(MODULE_NAME),
276                            doc: DOC.map(|doc| ctx.intern_str(doc)),
277                            methods: METHOD_DEFS,
278                            slots: Default::default(),
279                        };
280                        def.slots.exec = Some(module_exec);
281                        def
282                    })
283                }
284            },
285        ]);
286    }
287    if !is_submodule && !context.has_module_exec {
288        items.push(parse_quote! {
289            pub(crate) fn module_exec(vm: &::rustpython_vm::VirtualMachine, module: &::rustpython_vm::Py<::rustpython_vm::builtins::PyModule>) -> ::rustpython_vm::PyResult<()> {
290                __module_exec(vm, module);
291                Ok(())
292            }
293        });
294    }
295    // Split with_items into unconditional and cfg-gated groups
296    let (uncond_withs, cond_withs): (Vec<_>, Vec<_>) =
297        with_items.iter().partition(|w| w.cfg_attrs.is_empty());
298    let uncond_paths: Vec<_> = uncond_withs.iter().map(|w| &w.path).collect();
299
300    let method_defs = if with_items.is_empty() {
301        quote!(#function_items)
302    } else {
303        // For cfg-gated with items, generate conditional const declarations
304        // so the total array size adapts to the cfg at compile time
305        let cond_const_names: Vec<_> = cond_withs
306            .iter()
307            .enumerate()
308            .map(|(i, _)| format_ident!("__WITH_METHODS_{}", i))
309            .collect();
310        let cond_const_decls: Vec<_> = cond_withs
311            .iter()
312            .zip(&cond_const_names)
313            .map(|(w, name)| {
314                let cfg_attrs = &w.cfg_attrs;
315                let neg_attrs = negate_cfg_attrs(&w.cfg_attrs);
316                let path = &w.path;
317                quote! {
318                    #(#cfg_attrs)*
319                    const #name: &'static [::rustpython_vm::function::PyMethodDef] = super::#path::METHOD_DEFS;
320                    #(#neg_attrs)*
321                    const #name: &'static [::rustpython_vm::function::PyMethodDef] = &[];
322                }
323            })
324            .collect();
325
326        quote!({
327            const OWN_METHODS: &'static [::rustpython_vm::function::PyMethodDef] = &#function_items;
328            #(#cond_const_decls)*
329            rustpython_vm::function::PyMethodDef::__const_concat_arrays::<
330                { OWN_METHODS.len()
331                    #(+ super::#uncond_paths::METHOD_DEFS.len())*
332                    #(+ #cond_const_names.len())*
333                },
334            >(&[
335                #(super::#uncond_paths::METHOD_DEFS,)*
336                #(#cond_const_names,)*
337                OWN_METHODS
338            ])
339        })
340    };
341
342    // Generate __init_attributes calls, wrapping cfg-gated items
343    let init_with_calls: Vec<_> = with_items
344        .iter()
345        .map(|w| {
346            let cfg_attrs = &w.cfg_attrs;
347            let path = &w.path;
348            quote! {
349                #(#cfg_attrs)*
350                super::#path::__init_attributes(vm, module);
351            }
352        })
353        .collect();
354
355    items.extend([
356        parse_quote! {
357            ::rustpython_vm::common::static_cell! {
358                pub(crate) static DEF: ::rustpython_vm::builtins::PyModuleDef;
359            }
360        },
361        parse_quote! {
362            pub(crate) const METHOD_DEFS: &'static [::rustpython_vm::function::PyMethodDef] = &#method_defs;
363        },
364        parse_quote! {
365            pub(crate) fn __init_attributes(
366                vm: &::rustpython_vm::VirtualMachine,
367                module: &::rustpython_vm::Py<::rustpython_vm::builtins::PyModule>,
368            ) {
369                #(#init_with_calls)*
370                let ctx = &vm.ctx;
371                #attribute_items
372                #(#submodule_inits)*
373            }
374        },
375        parse_quote! {
376            pub(crate) fn __module_exec(
377                vm: &::rustpython_vm::VirtualMachine,
378                module: &::rustpython_vm::Py<::rustpython_vm::builtins::PyModule>,
379            ) {
380                __init_attributes(vm, module);
381            }
382        },
383        parse_quote! {
384            pub(crate) fn __init_dict(
385                vm: &::rustpython_vm::VirtualMachine,
386                module: &::rustpython_vm::Py<::rustpython_vm::builtins::PyModule>,
387            ) {
388                ::rustpython_vm::builtins::PyModule::__init_dict_from_def(vm, module);
389            }
390        },
391    ]);
392
393    Ok(if let Some(error) = context.errors.into_error() {
394        let error = Diagnostic::from(error);
395        quote! {
396            #module_item
397            #error
398        }
399    } else {
400        module_item.into_token_stream()
401    })
402}
403
404fn module_item_new(
405    index: usize,
406    attr_name: AttrName,
407    py_attrs: Vec<usize>,
408) -> Box<dyn ModuleItem<AttrName = AttrName>> {
409    match attr_name {
410        AttrName::Function => Box::new(FunctionItem {
411            inner: ContentItemInner { index, attr_name },
412            py_attrs,
413        }),
414        AttrName::Attr => Box::new(AttributeItem {
415            inner: ContentItemInner { index, attr_name },
416            py_attrs,
417        }),
418        // pyexception is treated like pyclass - both define types
419        AttrName::Class | AttrName::Exception => Box::new(ClassItem {
420            inner: ContentItemInner { index, attr_name },
421            py_attrs,
422        }),
423        AttrName::StructSequence => Box::new(StructSequenceItem {
424            inner: ContentItemInner { index, attr_name },
425            py_attrs,
426        }),
427    }
428}
429
430fn attrs_to_module_items<F, R>(attrs: &[Attribute], item_new: F) -> Result<(Vec<R>, Vec<Attribute>)>
431where
432    F: Fn(usize, AttrName, Vec<usize>) -> R,
433{
434    let mut cfgs: Vec<Attribute> = Vec::new();
435    let mut result = Vec::new();
436
437    let mut iter = attrs.iter().enumerate().peekable();
438    while let Some((_, attr)) = iter.peek() {
439        // take all cfgs but no py items
440        let attr = *attr;
441        if let Some(ident) = attr.get_ident() {
442            let attr_name = ident.to_string();
443            if attr_name == "cfg" {
444                cfgs.push(attr.clone());
445            } else if ALL_ALLOWED_NAMES.contains(&attr_name.as_str()) {
446                break;
447            }
448        }
449        iter.next();
450    }
451
452    let mut closed = false;
453    let mut py_attrs = Vec::new();
454    for (i, attr) in iter {
455        // take py items but no cfgs
456        let attr_name = if let Some(ident) = attr.get_ident() {
457            ident.to_string()
458        } else {
459            continue;
460        };
461        if attr_name == "cfg" {
462            bail_span!(attr, "#[py*] items must be placed under `cfgs`")
463        }
464
465        let attr_name = match AttrName::from_str(attr_name.as_str()) {
466            Ok(name) => name,
467            Err(wrong_name) => {
468                if !ALL_ALLOWED_NAMES.contains(&wrong_name.as_str()) {
469                    continue;
470                } else if closed {
471                    bail_span!(attr, "Only one #[pyattr] annotated #[py*] item can exist")
472                } else {
473                    bail_span!(attr, "#[pymodule] doesn't accept #[{}]", wrong_name)
474                }
475            }
476        };
477
478        if attr_name == AttrName::Attr {
479            if !result.is_empty() {
480                bail_span!(
481                    attr,
482                    "#[pyattr] must be placed on top of other #[py*] items",
483                )
484            }
485            py_attrs.push(i);
486            continue;
487        }
488
489        if py_attrs.is_empty() {
490            result.push(item_new(i, attr_name, Vec::new()));
491        } else {
492            match attr_name {
493                AttrName::Class
494                | AttrName::Function
495                | AttrName::Exception
496                | AttrName::StructSequence => {
497                    result.push(item_new(i, attr_name, py_attrs.clone()));
498                }
499                _ => {
500                    bail_span!(
501                        attr,
502                        "#[pyclass], #[pyfunction], #[pyexception], or #[pystruct_sequence] can follow #[pyattr]",
503                    )
504                }
505            }
506            py_attrs.clear();
507            closed = true;
508        }
509    }
510
511    if let Some(last) = py_attrs.pop() {
512        assert!(!closed);
513        result.push(item_new(last, AttrName::Attr, py_attrs));
514    }
515    Ok((result, cfgs))
516}
517
518#[derive(Default)]
519struct FunctionNursery {
520    items: Vec<FunctionNurseryItem>,
521}
522
523struct FunctionNurseryItem {
524    py_names: Vec<String>,
525    cfgs: Vec<Attribute>,
526    ident: Ident,
527    doc: String,
528    call_flags: TokenStream,
529}
530
531impl FunctionNursery {
532    fn add_item(&mut self, item: FunctionNurseryItem) {
533        self.items.push(item);
534    }
535
536    fn validate(self) -> Result<ValidatedFunctionNursery> {
537        let mut name_set = HashSet::new();
538        for item in &self.items {
539            for py_name in &item.py_names {
540                if !name_set.insert((py_name.to_owned(), &item.cfgs)) {
541                    bail_span!(item.ident, "duplicate method name `{}`", py_name);
542                }
543            }
544        }
545        Ok(ValidatedFunctionNursery(self))
546    }
547}
548
549struct ValidatedFunctionNursery(FunctionNursery);
550
551impl ToTokens for ValidatedFunctionNursery {
552    fn to_tokens(&self, tokens: &mut TokenStream) {
553        let mut inner_tokens = TokenStream::new();
554        for item in &self.0.items {
555            let ident = &item.ident;
556            let cfgs = &item.cfgs;
557            let cfgs = quote!(#(#cfgs)*);
558            let py_names = &item.py_names;
559            let doc = &item.doc;
560            let doc = quote!(Some(#doc));
561            let flags = &item.call_flags;
562
563            inner_tokens.extend(quote![
564                #(
565                    #cfgs
566                    rustpython_vm::function::PyMethodDef::new_const(
567                        #py_names,
568                        #ident,
569                        #flags,
570                        #doc,
571                    ),
572                )*
573            ]);
574        }
575        let array: TokenTree = Group::new(Delimiter::Bracket, inner_tokens).into();
576        tokens.extend([array]);
577    }
578}
579
580/// #[pyfunction]
581struct FunctionItem {
582    inner: ContentItemInner<AttrName>,
583    py_attrs: Vec<usize>,
584}
585
586/// #[pyclass]
587struct ClassItem {
588    inner: ContentItemInner<AttrName>,
589    py_attrs: Vec<usize>,
590}
591
592/// #[pyattr]
593struct AttributeItem {
594    inner: ContentItemInner<AttrName>,
595    py_attrs: Vec<usize>,
596}
597
598/// #[pystruct_sequence]
599struct StructSequenceItem {
600    inner: ContentItemInner<AttrName>,
601    py_attrs: Vec<usize>,
602}
603
604impl ContentItem for FunctionItem {
605    type AttrName = AttrName;
606
607    fn inner(&self) -> &ContentItemInner<AttrName> {
608        &self.inner
609    }
610}
611
612impl ContentItem for ClassItem {
613    type AttrName = AttrName;
614
615    fn inner(&self) -> &ContentItemInner<AttrName> {
616        &self.inner
617    }
618}
619
620impl ContentItem for AttributeItem {
621    type AttrName = AttrName;
622
623    fn inner(&self) -> &ContentItemInner<AttrName> {
624        &self.inner
625    }
626}
627
628impl ContentItem for StructSequenceItem {
629    type AttrName = AttrName;
630
631    fn inner(&self) -> &ContentItemInner<AttrName> {
632        &self.inner
633    }
634}
635
636struct ModuleItemArgs<'a> {
637    item: &'a mut Item,
638    attrs: &'a mut Vec<Attribute>,
639    context: &'a mut ModuleContext,
640    cfgs: &'a [Attribute],
641}
642
643impl<'a> ModuleItemArgs<'a> {
644    fn module_name(&'a self) -> &'a str {
645        self.context.name.as_str()
646    }
647}
648
649trait ModuleItem: ContentItem {
650    fn gen_module_item(&self, args: ModuleItemArgs<'_>) -> Result<()>;
651}
652
653impl ModuleItem for FunctionItem {
654    fn gen_module_item(&self, args: ModuleItemArgs<'_>) -> Result<()> {
655        let func = args
656            .item
657            .function_or_method()
658            .map_err(|_| self.new_syn_error(args.item.span(), "can only be on a function"))?;
659        let ident = &func.sig().ident;
660
661        let item_attr = args.attrs.remove(self.index());
662        let item_meta = SimpleItemMeta::from_attr(ident.clone(), &item_attr)?;
663
664        let py_name = item_meta.simple_name()?;
665        let sig_doc = text_signature(func.sig(), &py_name);
666
667        let module = args.module_name();
668        // TODO: doc must exist at least one of code or CPython
669        let doc = args.attrs.doc().or_else(|| {
670            DB.get(&format!("{module}.{py_name}"))
671                .copied()
672                .map(str::to_owned)
673        });
674        let doc = if let Some(doc) = doc {
675            format_doc(&sig_doc, &doc)
676        } else {
677            sig_doc
678        };
679
680        let py_names = {
681            if self.py_attrs.is_empty() {
682                vec![py_name]
683            } else {
684                let mut py_names = HashSet::new();
685                py_names.insert(py_name);
686                for attr_index in self.py_attrs.iter().rev() {
687                    let mut loop_unit = || {
688                        let attr_attr = args.attrs.remove(*attr_index);
689                        let item_meta = SimpleItemMeta::from_attr(ident.clone(), &attr_attr)?;
690
691                        let py_name = item_meta.simple_name()?;
692                        let inserted = py_names.insert(py_name.clone());
693                        if !inserted {
694                            return Err(self.new_syn_error(
695                                ident.span(),
696                                &format!(
697                                    "`{py_name}` is duplicated name for multiple py* attribute"
698                                ),
699                            ));
700                        }
701                        Ok(())
702                    };
703                    let r = loop_unit();
704                    args.context.errors.ok_or_push(r);
705                }
706                let py_names: Vec<_> = py_names.into_iter().collect();
707                py_names
708            }
709        };
710        let call_flags = infer_native_call_flags(func.sig(), 0);
711
712        args.context.function_items.add_item(FunctionNurseryItem {
713            ident: ident.to_owned(),
714            py_names,
715            cfgs: args.cfgs.to_vec(),
716            doc,
717            call_flags,
718        });
719        Ok(())
720    }
721}
722
723impl ModuleItem for ClassItem {
724    fn gen_module_item(&self, args: ModuleItemArgs<'_>) -> Result<()> {
725        let (ident, _) = pyclass_ident_and_attrs(args.item)?;
726        let (class_name, class_new) = {
727            let class_attr = &mut args.attrs[self.inner.index];
728            let no_attr = class_attr.try_remove_name("no_attr")?;
729            if self.py_attrs.is_empty() {
730                // check no_attr before ClassItemMeta::from_attr
731                if no_attr.is_none() {
732                    bail_span!(
733                        ident,
734                        "#[{name}] requires #[pyattr] to be a module attribute. \
735                         To keep it free type, try #[{name}(no_attr)]",
736                        name = self.attr_name()
737                    )
738                }
739            }
740            let no_attr = no_attr.is_some();
741            let is_use = matches!(&args.item, syn::Item::Use(_));
742
743            let class_meta = ClassItemMeta::from_attr(ident.clone(), class_attr)?;
744            let module_name = args.context.name.clone();
745            let module_name = if let Some(class_module_name) = class_meta.module().ok().flatten() {
746                class_module_name
747            } else {
748                class_attr.fill_nested_meta("module", || {
749                    parse_quote! {module = #module_name}
750                })?;
751                module_name
752            };
753            let class_name = if no_attr && is_use {
754                "<NO ATTR>".to_owned()
755            } else {
756                class_meta.class_name()?
757            };
758            let class_new = quote_spanned!(ident.span() =>
759                let new_class = <#ident as ::rustpython_vm::class::PyClassImpl>::make_static_type();
760                // Only set __module__ string if the class doesn't already have a
761                // getset descriptor for __module__ (which provides instance-level
762                // module resolution, e.g. TypeAliasType)
763                {
764                    let module_key = rustpython_vm::identifier!(ctx, __module__);
765                    let has_module_getset = new_class.attributes.read()
766                        .get(module_key)
767                        .is_some_and(|v| v.downcastable::<rustpython_vm::builtins::PyGetSet>());
768                    if !has_module_getset {
769                        new_class.set_attr(module_key, vm.new_pyobj(#module_name));
770                    }
771                }
772            );
773            (class_name, class_new)
774        };
775
776        let mut py_names = Vec::new();
777        for attr_index in self.py_attrs.iter().rev() {
778            let mut loop_unit = || {
779                let attr_attr = args.attrs.remove(*attr_index);
780                let item_meta = SimpleItemMeta::from_attr(ident.clone(), &attr_attr)?;
781
782                let py_name = item_meta
783                    .optional_name()
784                    .unwrap_or_else(|| class_name.clone());
785                py_names.push(py_name);
786
787                Ok(())
788            };
789            let r = loop_unit();
790            args.context.errors.ok_or_push(r);
791        }
792
793        let set_attr = match py_names.len() {
794            0 => quote! {
795                let _ = new_class;  // suppress warning
796                let _ = vm.ctx.intern_str(#class_name);
797            },
798            1 => {
799                let py_name = &py_names[0];
800                quote! {
801                    vm.__module_set_attr(&module, vm.ctx.intern_str(#py_name), new_class).unwrap();
802                }
803            }
804            _ => quote! {
805                for name in [#(#py_names,)*] {
806                    vm.__module_set_attr(&module, vm.ctx.intern_str(name), new_class.clone()).unwrap();
807                }
808            },
809        };
810
811        args.context.attribute_items.add_item(
812            ident.clone(),
813            py_names,
814            args.cfgs.to_vec(),
815            quote_spanned! { ident.span() =>
816                #class_new
817                #set_attr
818            },
819            0,
820        )?;
821        Ok(())
822    }
823}
824
825impl ModuleItem for StructSequenceItem {
826    fn gen_module_item(&self, args: ModuleItemArgs<'_>) -> Result<()> {
827        // Get the struct identifier (this IS the Python type, e.g., PyStructTime)
828        let pytype_ident = match args.item {
829            Item::Struct(s) => s.ident.clone(),
830            other => bail_span!(other, "#[pystruct_sequence] can only be on a struct"),
831        };
832
833        // Parse the #[pystruct_sequence(name = "...", module = "...", no_attr)] attribute
834        let structseq_attr = &args.attrs[self.inner.index];
835        let meta = PyStructSequenceMeta::from_attr(pytype_ident.clone(), structseq_attr)?;
836
837        let class_name = meta.class_name()?.ok_or_else(|| {
838            syn::Error::new_spanned(
839                structseq_attr,
840                "#[pystruct_sequence] requires name parameter",
841            )
842        })?;
843        let module_opt = meta.module()?;
844        let has_module = module_opt.is_some();
845        let module_name = module_opt.unwrap_or_else(|| args.context.name.clone());
846        if !has_module {
847            let structseq_attr = &mut args.attrs[self.inner.index];
848            structseq_attr.fill_nested_meta("module", || {
849                parse_quote! {module = #module_name}
850            })?;
851        }
852        let no_attr = meta.no_attr()?;
853
854        // Generate the class creation code
855        let class_new = quote_spanned!(pytype_ident.span() =>
856            let new_class = <#pytype_ident as ::rustpython_vm::class::PyClassImpl>::make_static_type();
857            {
858                let module_key = rustpython_vm::identifier!(ctx, __module__);
859                let has_module_getset = new_class.attributes.read()
860                    .get(module_key)
861                    .is_some_and(|v| v.downcastable::<rustpython_vm::builtins::PyGetSet>());
862                if !has_module_getset {
863                    new_class.set_attr(module_key, vm.new_pyobj(#module_name));
864                }
865            }
866        );
867
868        // Handle py_attrs for custom names, or use class_name as default
869        let mut py_names = Vec::new();
870        for attr_index in self.py_attrs.iter().rev() {
871            let attr_attr = args.attrs.remove(*attr_index);
872            let item_meta = SimpleItemMeta::from_attr(pytype_ident.clone(), &attr_attr)?;
873            let py_name = item_meta
874                .optional_name()
875                .unwrap_or_else(|| class_name.clone());
876            py_names.push(py_name);
877        }
878
879        // Require explicit #[pyattr] or no_attr, like #[pyclass]
880        if self.py_attrs.is_empty() && !no_attr {
881            bail_span!(
882                pytype_ident,
883                "#[pystruct_sequence] requires #[pyattr] to be a module attribute. \
884                 To keep it free type, try #[pystruct_sequence(..., no_attr)]"
885            )
886        }
887
888        let set_attr = match py_names.len() {
889            0 => quote! {
890                let _ = new_class;  // suppress warning
891            },
892            1 => {
893                let py_name = &py_names[0];
894                quote! {
895                    vm.__module_set_attr(&module, vm.ctx.intern_str(#py_name), new_class).unwrap();
896                }
897            }
898            _ => quote! {
899                for name in [#(#py_names,)*] {
900                    vm.__module_set_attr(&module, vm.ctx.intern_str(name), new_class.clone()).unwrap();
901                }
902            },
903        };
904
905        args.context.attribute_items.add_item(
906            pytype_ident.clone(),
907            py_names,
908            args.cfgs.to_vec(),
909            quote_spanned! { pytype_ident.span() =>
910                #class_new
911                #set_attr
912            },
913            0,
914        )?;
915        Ok(())
916    }
917}
918
919impl ModuleItem for AttributeItem {
920    fn gen_module_item(&self, args: ModuleItemArgs<'_>) -> Result<()> {
921        let cfgs = args.cfgs.to_vec();
922        let attr = args.attrs.remove(self.index());
923        let (ident, py_name, let_obj) = match args.item {
924            Item::Fn(syn::ItemFn { sig, block, .. }) => {
925                let ident = &sig.ident;
926                // If `once` keyword is in #[pyattr],
927                // wrapping it with static_cell for preventing it from using it as function
928                let attr_meta = AttrItemMeta::from_attr(ident.clone(), &attr)?;
929                if attr_meta.inner()._bool("once")? {
930                    let stmts = &block.stmts;
931                    let return_type = match &sig.output {
932                        syn::ReturnType::Default => {
933                            unreachable!("#[pyattr] attached function must have return type.")
934                        }
935                        syn::ReturnType::Type(_, ty) => ty,
936                    };
937                    let stmt: syn::Stmt = parse_quote! {
938                        {
939                            rustpython_common::static_cell! {
940                                static ERROR: #return_type;
941                            }
942                            ERROR
943                                .get_or_init(|| {
944                                    #(#stmts)*
945                                })
946                                .clone()
947                        }
948                    };
949                    block.stmts = vec![stmt];
950                }
951
952                let py_name = attr_meta.simple_name()?;
953                (
954                    ident.clone(),
955                    py_name,
956                    quote_spanned! { ident.span() =>
957                        let obj = vm.new_pyobj(#ident(vm));
958                    },
959                )
960            }
961            Item::Const(syn::ItemConst { ident, .. }) => {
962                let item_meta = SimpleItemMeta::from_attr(ident.clone(), &attr)?;
963                let py_name = item_meta.simple_name()?;
964                (
965                    ident.clone(),
966                    py_name,
967                    quote_spanned! { ident.span() =>
968                        let obj = vm.new_pyobj(#ident);
969                    },
970                )
971            }
972            Item::Use(item) => {
973                if !self.py_attrs.is_empty() {
974                    return Err(self
975                        .new_syn_error(item.span(), "Only single #[pyattr] is allowed for `use`"));
976                }
977                let _ = iter_use_idents(item, |ident, is_unique| {
978                    let item_meta = SimpleItemMeta::from_attr(ident.clone(), &attr)?;
979                    let py_name = if is_unique {
980                        item_meta.simple_name()?
981                    } else if item_meta.optional_name().is_some() {
982                        // this check actually doesn't need to be placed in loop
983                        return Err(self.new_syn_error(
984                            ident.span(),
985                            "`name` attribute is not allowed for multiple use items",
986                        ));
987                    } else {
988                        ident.to_string()
989                    };
990                    let tokens = quote_spanned! { ident.span() =>
991                        vm.__module_set_attr(module, vm.ctx.intern_str(#py_name), vm.new_pyobj(#ident)).unwrap();
992                    };
993                    args.context.attribute_items.add_item(
994                        ident.clone(),
995                        vec![py_name],
996                        cfgs.clone(),
997                        tokens,
998                        1,
999                    )?;
1000                    Ok(())
1001                })?;
1002                return Ok(());
1003            }
1004            other => {
1005                return Err(
1006                    self.new_syn_error(other.span(), "can only be on a function, const and use")
1007                );
1008            }
1009        };
1010
1011        let (tokens, py_names) = if self.py_attrs.is_empty() {
1012            (
1013                quote_spanned! { ident.span() => {
1014                    #let_obj
1015                    vm.__module_set_attr(module, vm.ctx.intern_str(#py_name), obj).unwrap();
1016                }},
1017                vec![py_name],
1018            )
1019        } else {
1020            let mut names = vec![py_name];
1021            for attr_index in self.py_attrs.iter().rev() {
1022                let mut loop_unit = || {
1023                    let attr_attr = args.attrs.remove(*attr_index);
1024                    let item_meta = AttrItemMeta::from_attr(ident.clone(), &attr_attr)?;
1025                    if item_meta.inner()._bool("once")? {
1026                        return Err(self.new_syn_error(
1027                            ident.span(),
1028                            "#[pyattr(once)] is only allowed for the bottom-most item",
1029                        ));
1030                    }
1031
1032                    let py_name = item_meta.optional_name().ok_or_else(|| {
1033                        self.new_syn_error(
1034                            ident.span(),
1035                            "#[pyattr(name = ...)] is mandatory except for the bottom-most item",
1036                        )
1037                    })?;
1038                    names.push(py_name);
1039                    Ok(())
1040                };
1041                let r = loop_unit();
1042                args.context.errors.ok_or_push(r);
1043            }
1044            (
1045                quote_spanned! { ident.span() => {
1046                    #let_obj
1047                    for name in [#(#names),*] {
1048                        vm.__module_set_attr(module, vm.ctx.intern_str(name), obj.clone()).unwrap();
1049                    }
1050                }},
1051                names,
1052            )
1053        };
1054
1055        args.context
1056            .attribute_items
1057            .add_item(ident, py_names, cfgs, tokens, 1)?;
1058
1059        Ok(())
1060    }
1061}