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))] |
5 | use proc_macro::TokenStream; |
6 | use proc_macro2::TokenStream as TokenStream2; |
7 | use 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 | }; |
12 | use quote::quote; |
13 | use 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 ] |
37 | pub 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 ] |
62 | pub 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 ] |
109 | pub 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 ] |
138 | pub 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))] |
152 | pub 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 | |
161 | fn 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 | |
176 | fn 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 | |
191 | fn 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 | |
211 | fn methods_type() -> PyClassMethodsType { |
212 | if cfg!(feature = "multiple-pymethods" ) { |
213 | PyClassMethodsType::Inventory |
214 | } else { |
215 | PyClassMethodsType::Specialization |
216 | } |
217 | } |
218 | |
219 | trait UnwrapOrCompileError { |
220 | fn unwrap_or_compile_error(self) -> TokenStream2; |
221 | } |
222 | |
223 | impl 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 | |