1#![cfg_attr(docsrs, feature(doc_cfg))]
5use proc_macro::TokenStream;
6use proc_macro2::TokenStream as TokenStream2;
7use pyforge_macros_backend::{
8 build_derive_from_pyobject, build_derive_into_pyobject, build_py_class, build_py_enum,
9 build_py_function, build_py_methods, pymodule_function_impl, pymodule_module_impl, PyClassArgs,
10 PyClassMethodsType, PyFunctionOptions, PyModuleOptions,
11};
12use quote::quote;
13use syn::{parse_macro_input, Item};
14
15#[doc = concat!("[1]: https://github.com/abdulwahed-sweden/pyforge/v", env!("CARGO_PKG_VERSION"), "/module.html")]
41#[proc_macro_attribute]
42pub fn pymodule(args: TokenStream, input: TokenStream) -> TokenStream {
43 let options = parse_macro_input!(args as PyModuleOptions);
44
45 let mut ast = parse_macro_input!(input as Item);
46 let expanded = match &mut ast {
47 Item::Mod(module) => {
48 match pymodule_module_impl(module, options) {
49 Ok(expanded) => return expanded.into(),
51 Err(e) => Err(e),
52 }
53 }
54 Item::Fn(function) => pymodule_function_impl(function, options),
55 unsupported => Err(syn::Error::new_spanned(
56 unsupported,
57 "#[pymodule] only supports modules and functions.",
58 )),
59 }
60 .unwrap_or_compile_error();
61
62 quote!(
63 #ast
64 #expanded
65 )
66 .into()
67}
68
69#[proc_macro_attribute]
70pub fn pyclass(attr: TokenStream, input: TokenStream) -> TokenStream {
71 let item = parse_macro_input!(input as Item);
72 match item {
73 Item::Struct(struct_) => pyclass_impl(attr, struct_, methods_type()),
74 Item::Enum(enum_) => pyclass_enum_impl(attr, enum_, methods_type()),
75 unsupported => {
76 syn::Error::new_spanned(unsupported, "#[pyclass] only supports structs and enums.")
77 .into_compile_error()
78 .into()
79 }
80 }
81}
82
83#[doc = concat!("[1]: https://github.com/abdulwahed-sweden/pyforge/v", env!("CARGO_PKG_VERSION"), "/class.html#instance-methods")]
105#[doc = concat!("[2]: https://github.com/abdulwahed-sweden/pyforge/v", env!("CARGO_PKG_VERSION"), "/features.html#multiple-pymethods")]
106#[doc = concat!("[4]: https://github.com/abdulwahed-sweden/pyforge/v", env!("CARGO_PKG_VERSION"), "/class.html#constructor")]
108#[doc = concat!("[5]: https://github.com/abdulwahed-sweden/pyforge/v", env!("CARGO_PKG_VERSION"), "/class.html#object-properties-using-getter-and-setter")]
109#[doc = concat!("[6]: https://github.com/abdulwahed-sweden/pyforge/v", env!("CARGO_PKG_VERSION"), "/class.html#static-methods")]
110#[doc = concat!("[7]: https://github.com/abdulwahed-sweden/pyforge/v", env!("CARGO_PKG_VERSION"), "/class.html#class-methods")]
111#[doc = concat!("[8]: https://github.com/abdulwahed-sweden/pyforge/v", env!("CARGO_PKG_VERSION"), "/class.html#callable-objects")]
112#[doc = concat!("[9]: https://github.com/abdulwahed-sweden/pyforge/v", env!("CARGO_PKG_VERSION"), "/class.html#class-attributes")]
113#[doc = concat!("[10]: https://github.com/abdulwahed-sweden/pyforge/v", env!("CARGO_PKG_VERSION"), "/class.html#method-arguments")]
114#[doc = concat!("[11]: https://github.com/abdulwahed-sweden/pyforge/v", env!("CARGO_PKG_VERSION"), "/function.html#function-options")]
115#[doc = concat!("[12]: https://github.com/abdulwahed-sweden/pyforge/v", env!("CARGO_PKG_VERSION"), "/class.html#object-properties-using-pyo3get-set")]
116#[proc_macro_attribute]
117pub fn pymethods(attr: TokenStream, input: TokenStream) -> TokenStream {
118 let methods_type = if cfg!(feature = "multiple-pymethods") {
119 PyClassMethodsType::Inventory
120 } else {
121 PyClassMethodsType::Specialization
122 };
123 pymethods_impl(attr, input, methods_type)
124}
125
126#[doc = concat!("[1]: https://github.com/abdulwahed-sweden/pyforge/v", env!("CARGO_PKG_VERSION"), "/function.html")]
146#[proc_macro_attribute]
147pub fn pyfunction(attr: TokenStream, input: TokenStream) -> TokenStream {
148 let mut ast = parse_macro_input!(input as syn::ItemFn);
149 let options = parse_macro_input!(attr as PyFunctionOptions);
150
151 let expanded = build_py_function(&mut ast, options).unwrap_or_compile_error();
152
153 quote!(
154 #ast
155 #expanded
156 )
157 .into()
158}
159
160#[proc_macro_derive(IntoPyObject, attributes(pyo3))]
161pub fn derive_into_py_object(item: TokenStream) -> TokenStream {
162 let ast = parse_macro_input!(item as syn::DeriveInput);
163 let expanded = build_derive_into_pyobject::<false>(&ast).unwrap_or_compile_error();
164 quote!(
165 #expanded
166 )
167 .into()
168}
169
170#[proc_macro_derive(IntoPyObjectRef, attributes(pyo3))]
171pub fn derive_into_py_object_ref(item: TokenStream) -> TokenStream {
172 let ast = parse_macro_input!(item as syn::DeriveInput);
173 let expanded =
174 pyforge_macros_backend::build_derive_into_pyobject::<true>(&ast).unwrap_or_compile_error();
175 quote!(
176 #expanded
177 )
178 .into()
179}
180
181#[proc_macro_derive(FromPyObject, attributes(pyo3))]
182pub fn derive_from_py_object(item: TokenStream) -> TokenStream {
183 let ast = parse_macro_input!(item as syn::DeriveInput);
184 let expanded = build_derive_from_pyobject(&ast).unwrap_or_compile_error();
185 quote!(
186 #expanded
187 )
188 .into()
189}
190
191fn pyclass_impl(
192 attrs: TokenStream,
193 mut ast: syn::ItemStruct,
194 methods_type: PyClassMethodsType,
195) -> TokenStream {
196 let args = parse_macro_input!(attrs with PyClassArgs::parse_struct_args);
197 let expanded = build_py_class(&mut ast, args, methods_type).unwrap_or_compile_error();
198
199 quote!(
200 #ast
201 #expanded
202 )
203 .into()
204}
205
206fn pyclass_enum_impl(
207 attrs: TokenStream,
208 mut ast: syn::ItemEnum,
209 methods_type: PyClassMethodsType,
210) -> TokenStream {
211 let args = parse_macro_input!(attrs with PyClassArgs::parse_enum_args);
212 let expanded = build_py_enum(&mut ast, args, methods_type).unwrap_or_compile_error();
213
214 quote!(
215 #ast
216 #expanded
217 )
218 .into()
219}
220
221fn pymethods_impl(
222 attr: TokenStream,
223 input: TokenStream,
224 methods_type: PyClassMethodsType,
225) -> TokenStream {
226 let mut ast = parse_macro_input!(input as syn::ItemImpl);
227 let attr: TokenStream2 = attr.into();
231 ast.attrs.push(syn::parse_quote!( #[pyo3(#attr)] ));
232 let expanded = build_py_methods(&mut ast, methods_type).unwrap_or_compile_error();
233
234 quote!(
235 #ast
236 #expanded
237 )
238 .into()
239}
240
241fn methods_type() -> PyClassMethodsType {
242 if cfg!(feature = "multiple-pymethods") {
243 PyClassMethodsType::Inventory
244 } else {
245 PyClassMethodsType::Specialization
246 }
247}
248
249trait UnwrapOrCompileError {
250 fn unwrap_or_compile_error(self) -> TokenStream2;
251}
252
253impl UnwrapOrCompileError for syn::Result<TokenStream2> {
254 fn unwrap_or_compile_error(self) -> TokenStream2 {
255 self.unwrap_or_else(|e| e.into_compile_error())
256 }
257}