pyo3_stub_gen_derive/
gen_stub.rs

1//! Code generation for embedding metadata for generating Python stub file.
2//!
3//! These metadata are embedded as `inventory::submit!` block like:
4//!
5//! ```rust
6//! # use pyo3::*;
7//! # use pyo3_stub_gen::type_info::*;
8//! # struct PyPlaceholder;
9//! inventory::submit!{
10//!     PyClassInfo {
11//!         pyclass_name: "Placeholder",
12//!         module: Some("my_module"),
13//!         struct_id: std::any::TypeId::of::<PyPlaceholder>,
14//!         getters: &[
15//!             MemberInfo {
16//!                 name: "name",
17//!                 r#type: <String as ::pyo3_stub_gen::PyStubType>::type_output,
18//!                 doc: "",
19//!                 default: None,
20//!                 deprecated: None,
21//!             },
22//!             MemberInfo {
23//!                 name: "ndim",
24//!                 r#type: <usize as ::pyo3_stub_gen::PyStubType>::type_output,
25//!                 doc: "",
26//!                 default: None,
27//!                 deprecated: None,
28//!             },
29//!             MemberInfo {
30//!                 name: "description",
31//!                 r#type: <Option<String> as ::pyo3_stub_gen::PyStubType>::type_output,
32//!                 doc: "",
33//!                 default: None,
34//!                 deprecated: None,
35//!             },
36//!         ],
37//!         setters: &[],
38//!         doc: "",
39//!         bases: &[],
40//!         has_eq: false,
41//!         has_ord: false,
42//!         has_hash: false,
43//!         has_str: false,
44//!         subclass: false,
45//!     }
46//! }
47//! ```
48//!
49//! and this submodule responsible for generating such codes from Rust code like
50//!
51//! ```rust
52//! # use pyo3::*;
53//! #[pyclass(mapping, module = "my_module", name = "Placeholder")]
54//! #[derive(Debug, Clone)]
55//! pub struct PyPlaceholder {
56//!     #[pyo3(get)]
57//!     pub name: String,
58//!     #[pyo3(get)]
59//!     pub ndim: usize,
60//!     #[pyo3(get)]
61//!     pub description: Option<String>,
62//!     pub custom_latex: Option<String>,
63//! }
64//! ```
65//!
66//! Mechanism
67//! ----------
68//! Code generation will take three steps:
69//!
70//! 1. Parse input [proc_macro2::TokenStream] into corresponding syntax tree component in [syn],
71//!    - e.g. [ItemStruct] for `#[pyclass]`, [ItemImpl] for `#[pymethods]`, and so on.
72//! 2. Convert syntax tree components into `*Info` struct using [TryInto].
73//!    - e.g. [PyClassInfo] is converted from [ItemStruct], [PyMethodsInfo] is converted from [ItemImpl], and so on.
74//! 3. Generate token streams using implementation of [quote::ToTokens] trait for `*Info` structs.
75//!    - [quote::quote!] macro uses this trait.
76//!
77
78mod arg;
79mod attr;
80mod member;
81mod method;
82mod parse_python;
83mod pyclass;
84mod pyclass_complex_enum;
85mod pyclass_enum;
86mod pyfunction;
87mod pymethods;
88mod renaming;
89mod signature;
90mod stub_type;
91mod util;
92mod variant;
93
94use arg::*;
95use attr::*;
96use member::*;
97use method::*;
98use pyclass::*;
99use pyclass_complex_enum::*;
100use pyclass_enum::*;
101use pyfunction::*;
102use pymethods::*;
103use renaming::*;
104use signature::*;
105use stub_type::*;
106use util::*;
107
108use proc_macro2::TokenStream as TokenStream2;
109use quote::quote;
110use syn::{parse2, ItemEnum, ItemFn, ItemImpl, ItemStruct, LitStr, Result};
111
112pub fn pyclass(item: TokenStream2) -> Result<TokenStream2> {
113    let mut item_struct = parse2::<ItemStruct>(item)?;
114    let inner = PyClassInfo::try_from(item_struct.clone())?;
115    let derive_stub_type = StubType::from(&inner);
116    pyclass::prune_attrs(&mut item_struct);
117    Ok(quote! {
118        #item_struct
119        #derive_stub_type
120        pyo3_stub_gen::inventory::submit! {
121            #inner
122        }
123    })
124}
125
126pub fn pyclass_enum(item: TokenStream2) -> Result<TokenStream2> {
127    let inner = PyEnumInfo::try_from(parse2::<ItemEnum>(item.clone())?)?;
128    let derive_stub_type = StubType::from(&inner);
129    Ok(quote! {
130        #item
131        #derive_stub_type
132        pyo3_stub_gen::inventory::submit! {
133            #inner
134        }
135    })
136}
137
138pub fn pyclass_complex_enum(item: TokenStream2) -> Result<TokenStream2> {
139    let inner = PyComplexEnumInfo::try_from(parse2::<ItemEnum>(item.clone())?)?;
140    let derive_stub_type = StubType::from(&inner);
141    Ok(quote! {
142        #item
143        #derive_stub_type
144        pyo3_stub_gen::inventory::submit! {
145            #inner
146        }
147    })
148}
149
150pub fn pymethods(item: TokenStream2) -> Result<TokenStream2> {
151    let mut item_impl = parse2::<ItemImpl>(item)?;
152    let inner = PyMethodsInfo::try_from(item_impl.clone())?;
153    pymethods::prune_attrs(&mut item_impl);
154    Ok(quote! {
155        #item_impl
156        #[automatically_derived]
157        pyo3_stub_gen::inventory::submit! {
158            #inner
159        }
160    })
161}
162
163pub fn pyfunction(attr: TokenStream2, item: TokenStream2) -> Result<TokenStream2> {
164    let mut item_fn = parse2::<ItemFn>(item)?;
165    let mut inner = PyFunctionInfo::try_from(item_fn.clone())?;
166    let python_stub = inner.parse_attr(attr)?;
167    pyfunction::prune_attrs(&mut item_fn);
168
169    // If python parameter is provided, use it instead of auto-generated metadata
170    if let Some(stub_str) = python_stub {
171        let mut python_inner = parse_python::parse_python_function_stub(stub_str)?;
172        // Preserve module information from attributes
173        python_inner.module = inner.module;
174        Ok(quote! {
175            #item_fn
176            #[automatically_derived]
177            pyo3_stub_gen::inventory::submit! {
178                #python_inner
179            }
180        })
181    } else {
182        Ok(quote! {
183            #item_fn
184            #[automatically_derived]
185            pyo3_stub_gen::inventory::submit! {
186                #inner
187            }
188        })
189    }
190}
191
192pub fn gen_function_from_python_impl(input: TokenStream2) -> Result<TokenStream2> {
193    let parsed: parse_python::GenFunctionFromPythonInput = parse2(input)?;
194    let inner = parse_python::parse_gen_function_from_python_input(parsed)?;
195    Ok(quote! { #inner })
196}
197
198pub fn gen_methods_from_python_impl(input: TokenStream2) -> Result<TokenStream2> {
199    let stub_str: LitStr = parse2(input)?;
200    let inner = parse_python::parse_python_methods_stub(&stub_str)?;
201    Ok(quote! { #inner })
202}
203
204pub fn prune_gen_stub(item: TokenStream2) -> Result<TokenStream2> {
205    fn prune_attrs<T: syn::parse::Parse + quote::ToTokens>(
206        item: &TokenStream2,
207        fn_prune_attrs: fn(&mut T),
208    ) -> Result<TokenStream2> {
209        parse2::<T>(item.clone()).map(|mut item| {
210            fn_prune_attrs(&mut item);
211            quote! { #item }
212        })
213    }
214    prune_attrs::<ItemStruct>(&item, pyclass::prune_attrs)
215        .or_else(|_| prune_attrs::<ItemImpl>(&item, pymethods::prune_attrs))
216        .or_else(|_| prune_attrs::<ItemFn>(&item, pyfunction::prune_attrs))
217}