1//! This crate declares only the proc macro attributes, as a crate defining proc macro attributes
2//! must not contain any other public items.
3
4#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))]
5use proc_macro::TokenStream;
6use proc_macro2::TokenStream as TokenStream2;
7use pyo3_macros_backend::{
8 build_derive_from_pyobject, build_py_class, build_py_enum, build_py_function, build_py_methods,
9 get_doc, process_functions_in_module, pymodule_impl, PyClassArgs, PyClassMethodsType,
10 PyFunctionOptions, PyModuleOptions,
11};
12use quote::quote;
13use syn::{parse::Nothing, parse_macro_input};
14
15/// A proc macro used to implement Python modules.
16///
17/// The name of the module will be taken from the function name, unless `#[pyo3(name = "my_name")]`
18/// is also annotated on the function to override the name. **Important**: the module name should
19/// match the `lib.name` setting in `Cargo.toml`, so that Python is able to import the module
20/// without needing a custom import loader.
21///
22/// Functions annotated with `#[pymodule]` can also be annotated with the following:
23///
24/// | Annotation | Description |
25/// | :- | :- |
26/// | `#[pyo3(name = "...")]` | Defines the name of the module in Python. |
27///
28/// For more on creating Python modules see the [module section of the guide][1].
29///
30/// Due to technical limitations on how `#[pymodule]` is implemented, a function marked
31/// `#[pymodule]` cannot have a module with the same name in the same scope. (The
32/// `#[pymodule]` implementation generates a hidden module with the same name containing
33/// metadata about the module, which is used by `wrap_pymodule!`).
34///
35/// [1]: https://pyo3.rs/latest/module.html
36#[proc_macro_attribute]
37pub fn pymodule(args: TokenStream, input: TokenStream) -> TokenStream {
38 parse_macro_input!(args as Nothing);
39
40 let mut ast: ItemFn = parse_macro_input!(input as syn::ItemFn);
41 let options: PyModuleOptions = match PyModuleOptions::from_attrs(&mut ast.attrs) {
42 Ok(options: PyModuleOptions) => options,
43 Err(e: Error) => return e.into_compile_error().into(),
44 };
45
46 if let Err(err: Error) = process_functions_in_module(&options, &mut ast) {
47 return err.into_compile_error().into();
48 }
49
50 let doc: PythonDoc = get_doc(&ast.attrs, text_signature:None);
51
52 let expanded: TokenStream = pymodule_impl(&ast.sig.ident, options, doc, &ast.vis);
53
54 quoteTokenStream!(
55 #ast
56 #expanded
57 )
58 .into()
59}
60
61#[proc_macro_attribute]
62pub fn pyclass(attr: TokenStream, input: TokenStream) -> TokenStream {
63 use syn::Item;
64 let item: Item = parse_macro_input!(input as Item);
65 match item {
66 Item::Struct(struct_: ItemStruct) => pyclass_impl(attrs:attr, ast:struct_, methods_type()),
67 Item::Enum(enum_: ItemEnum) => pyclass_enum_impl(attrs:attr, ast:enum_, methods_type()),
68 unsupported: Item => {
69 synTokenStream::Error::new_spanned(tokens:unsupported, message:"#[pyclass] only supports structs and enums.")
70 .into_compile_error()
71 .into()
72 }
73 }
74}
75
76/// A proc macro used to expose methods to Python.
77///
78/// Methods within a `#[pymethods]` block can be annotated with as well as the following:
79///
80/// | Annotation | Description |
81/// | :- | :- |
82/// | [`#[new]`][4] | Defines the class constructor, like Python's `__new__` method. |
83/// | [`#[getter]`][5] and [`#[setter]`][5] | These define getters and setters, similar to Python's `@property` decorator. This is useful for getters/setters that require computation or side effects; if that is not the case consider using [`#[pyo3(get, set)]`][11] on the struct's field(s).|
84/// | [`#[staticmethod]`][6]| Defines the method as a staticmethod, like Python's `@staticmethod` decorator.|
85/// | [`#[classmethod]`][7] | Defines the method as a classmethod, like Python's `@classmethod` decorator.|
86/// | [`#[classattr]`][9] | Defines a class variable. |
87/// | [`#[args]`][10] | Deprecated way to define a method's default arguments and allows the function to receive `*args` and `**kwargs`. Use `#[pyo3(signature = (...))]` instead. |
88/// | <nobr>[`#[pyo3(<option> = <value>)`][pyo3-method-options]</nobr> | Any of the `#[pyo3]` options supported on [`macro@pyfunction`]. |
89///
90/// For more on creating class methods,
91/// see the [class section of the guide][1].
92///
93/// If the [`multiple-pymethods`][2] feature is enabled, it is possible to implement
94/// multiple `#[pymethods]` blocks for a single `#[pyclass]`.
95/// This will add a transitive dependency on the [`inventory`][3] crate.
96///
97/// [1]: https://pyo3.rs/latest/class.html#instance-methods
98/// [2]: https://pyo3.rs/latest/features.html#multiple-pymethods
99/// [3]: https://docs.rs/inventory/
100/// [4]: https://pyo3.rs/latest/class.html#constructor
101/// [5]: https://pyo3.rs/latest/class.html#object-properties-using-getter-and-setter
102/// [6]: https://pyo3.rs/latest/class.html#static-methods
103/// [7]: https://pyo3.rs/latest/class.html#class-methods
104/// [8]: https://pyo3.rs/latest/class.html#callable-objects
105/// [9]: https://pyo3.rs/latest/class.html#class-attributes
106/// [10]: https://pyo3.rs/latest/class.html#method-arguments
107/// [11]: https://pyo3.rs/latest/class.html#object-properties-using-pyo3get-set
108#[proc_macro_attribute]
109pub fn pymethods(attr: TokenStream, input: TokenStream) -> TokenStream {
110 let methods_type: PyClassMethodsType = if cfg!(feature = "multiple-pymethods") {
111 PyClassMethodsType::Inventory
112 } else {
113 PyClassMethodsType::Specialization
114 };
115 pymethods_impl(attr, input, methods_type)
116}
117
118/// A proc macro used to expose Rust functions to Python.
119///
120/// Functions annotated with `#[pyfunction]` can also be annotated with the following `#[pyo3]`
121/// options:
122///
123/// | Annotation | Description |
124/// | :- | :- |
125/// | `#[pyo3(name = "...")]` | Defines the name of the function in Python. |
126/// | `#[pyo3(text_signature = "...")]` | Defines the `__text_signature__` attribute of the function in Python. |
127/// | `#[pyo3(pass_module)]` | Passes the module containing the function as a `&PyModule` first argument to the function. |
128///
129/// For more on exposing functions see the [function section of the guide][1].
130///
131/// Due to technical limitations on how `#[pyfunction]` is implemented, a function marked
132/// `#[pyfunction]` cannot have a module with the same name in the same scope. (The
133/// `#[pyfunction]` implementation generates a hidden module with the same name containing
134/// metadata about the function, which is used by `wrap_pyfunction!`).
135///
136/// [1]: https://pyo3.rs/latest/function.html
137#[proc_macro_attribute]
138pub fn pyfunction(attr: TokenStream, input: TokenStream) -> TokenStream {
139 let mut ast: ItemFn = parse_macro_input!(input as syn::ItemFn);
140 let options: PyFunctionOptions = parse_macro_input!(attr as PyFunctionOptions);
141
142 let expanded: TokenStream = build_py_function(&mut ast, options).unwrap_or_compile_error();
143
144 quoteTokenStream!(
145 #ast
146 #expanded
147 )
148 .into()
149}
150
151#[proc_macro_derive(FromPyObject, attributes(pyo3))]
152pub fn derive_from_py_object(item: TokenStream) -> TokenStream {
153 let ast: DeriveInput = parse_macro_input!(item as syn::DeriveInput);
154 let expanded: TokenStream = build_derive_from_pyobject(&ast).unwrap_or_compile_error();
155 quoteTokenStream!(
156 #expanded
157 )
158 .into()
159}
160
161fn pyclass_impl(
162 attrs: TokenStream,
163 mut ast: syn::ItemStruct,
164 methods_type: PyClassMethodsType,
165) -> TokenStream {
166 let args: PyClassArgs = parse_macro_input!(attrs with PyClassArgs::parse_stuct_args);
167 let expanded: TokenStream = build_py_class(&mut ast, args, methods_type).unwrap_or_compile_error();
168
169 quoteTokenStream!(
170 #ast
171 #expanded
172 )
173 .into()
174}
175
176fn pyclass_enum_impl(
177 attrs: TokenStream,
178 mut ast: syn::ItemEnum,
179 methods_type: PyClassMethodsType,
180) -> TokenStream {
181 let args: PyClassArgs = parse_macro_input!(attrs with PyClassArgs::parse_enum_args);
182 let expanded: TokenStream = build_py_enum(&mut ast, args, method_type:methods_type).unwrap_or_compile_error();
183
184 quoteTokenStream!(
185 #ast
186 #expanded
187 )
188 .into()
189}
190
191fn pymethods_impl(
192 attr: TokenStream,
193 input: TokenStream,
194 methods_type: PyClassMethodsType,
195) -> TokenStream {
196 let mut ast: ItemImpl = parse_macro_input!(input as syn::ItemImpl);
197 // Apply all options as a #[pyo3] attribute on the ItemImpl
198 // e.g. #[pymethods(crate = "crate")] impl Foo { }
199 // -> #[pyo3(crate = "crate")] impl Foo { }
200 let attr: TokenStream2 = attr.into();
201 ast.attrs.push(syn::parse_quote!( #[pyo3(#attr)] ));
202 let expanded: TokenStream = build_py_methods(&mut ast, methods_type).unwrap_or_compile_error();
203
204 quoteTokenStream!(
205 #ast
206 #expanded
207 )
208 .into()
209}
210
211fn methods_type() -> PyClassMethodsType {
212 if cfg!(feature = "multiple-pymethods") {
213 PyClassMethodsType::Inventory
214 } else {
215 PyClassMethodsType::Specialization
216 }
217}
218
219trait UnwrapOrCompileError {
220 fn unwrap_or_compile_error(self) -> TokenStream2;
221}
222
223impl UnwrapOrCompileError for syn::Result<TokenStream2> {
224 fn unwrap_or_compile_error(self) -> TokenStream2 {
225 self.unwrap_or_else(|e: Error| e.into_compile_error())
226 }
227}
228