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_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 | }; |
12 | use quote::quote; |
13 | use syn::{parse_macro_input, Item}; |
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 | /// | `#[pyo3(submodule)]` | Skips adding a `PyInit_` FFI symbol to the compiled binary. | |
28 | /// | `#[pyo3(module = "...")]` | Defines the Python `dotted.path` to the parent module for use in introspection. | |
29 | /// | `#[pyo3(crate = "pyo3")]` | Defines the path to PyO3 to use code generated by the macro. | |
30 | /// |
31 | /// For more on creating Python modules see the [module section of the guide][1]. |
32 | /// |
33 | /// Due to technical limitations on how `#[pymodule]` is implemented, a function marked |
34 | /// `#[pymodule]` cannot have a module with the same name in the same scope. (The |
35 | /// `#[pymodule]` implementation generates a hidden module with the same name containing |
36 | /// metadata about the module, which is used by `wrap_pymodule!`). |
37 | /// |
38 | #[doc = concat!("[1]: https://pyo3.rs/v" , env!("CARGO_PKG_VERSION" ), "/module.html" )] |
39 | #[proc_macro_attribute ] |
40 | pub fn pymodule (args: TokenStream, input: TokenStream) -> TokenStream { |
41 | let options = parse_macro_input!(args as PyModuleOptions); |
42 | |
43 | let mut ast = parse_macro_input!(input as Item); |
44 | let expanded = match &mut ast { |
45 | Item::Mod(module) => { |
46 | match pymodule_module_impl(module, options) { |
47 | // #[pymodule] on a module will rebuild the original ast, so we don't emit it here |
48 | Ok(expanded) => return expanded.into(), |
49 | Err(e) => Err(e), |
50 | } |
51 | } |
52 | Item::Fn(function) => pymodule_function_impl(function, options), |
53 | unsupported => Err(syn::Error::new_spanned( |
54 | unsupported, |
55 | "#[pymodule] only supports modules and functions." , |
56 | )), |
57 | } |
58 | .unwrap_or_compile_error(); |
59 | |
60 | quote!( |
61 | #ast |
62 | #expanded |
63 | ) |
64 | .into() |
65 | } |
66 | |
67 | #[proc_macro_attribute ] |
68 | pub fn pyclass (attr: TokenStream, input: TokenStream) -> TokenStream { |
69 | let item: Item = parse_macro_input!(input as Item); |
70 | match item { |
71 | Item::Struct(struct_: ItemStruct) => pyclass_impl(attrs:attr, ast:struct_, methods_type()), |
72 | Item::Enum(enum_: ItemEnum) => pyclass_enum_impl(attrs:attr, ast:enum_, methods_type()), |
73 | unsupported: Item => { |
74 | synTokenStream::Error::new_spanned(tokens:unsupported, message:"#[pyclass] only supports structs and enums." ) |
75 | .into_compile_error() |
76 | .into() |
77 | } |
78 | } |
79 | } |
80 | |
81 | /// A proc macro used to expose methods to Python. |
82 | /// |
83 | /// Methods within a `#[pymethods]` block can be annotated with as well as the following: |
84 | /// |
85 | /// | Annotation | Description | |
86 | /// | :- | :- | |
87 | /// | [`#[new]`][4] | Defines the class constructor, like Python's `__new__` method. | |
88 | /// | [`#[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).| |
89 | /// | [`#[staticmethod]`][6]| Defines the method as a staticmethod, like Python's `@staticmethod` decorator.| |
90 | /// | [`#[classmethod]`][7] | Defines the method as a classmethod, like Python's `@classmethod` decorator.| |
91 | /// | [`#[classattr]`][9] | Defines a class variable. | |
92 | /// | [`#[args]`][10] | Deprecated way to define a method's default arguments and allows the function to receive `*args` and `**kwargs`. Use `#[pyo3(signature = (...))]` instead. | |
93 | /// | <nobr>[`#[pyo3(<option> = <value>)`][pyo3-method-options]</nobr> | Any of the `#[pyo3]` options supported on [`macro@pyfunction`]. | |
94 | /// |
95 | /// For more on creating class methods, |
96 | /// see the [class section of the guide][1]. |
97 | /// |
98 | /// If the [`multiple-pymethods`][2] feature is enabled, it is possible to implement |
99 | /// multiple `#[pymethods]` blocks for a single `#[pyclass]`. |
100 | /// This will add a transitive dependency on the [`inventory`][3] crate. |
101 | /// |
102 | #[doc = concat!("[1]: https://pyo3.rs/v" , env!("CARGO_PKG_VERSION" ), "/class.html#instance-methods" )] |
103 | #[doc = concat!("[2]: https://pyo3.rs/v" , env!("CARGO_PKG_VERSION" ), "/features.html#multiple-pymethods" )] |
104 | /// [3]: https://docs.rs/inventory/ |
105 | #[doc = concat!("[4]: https://pyo3.rs/v" , env!("CARGO_PKG_VERSION" ), "/class.html#constructor" )] |
106 | #[doc = concat!("[5]: https://pyo3.rs/v" , env!("CARGO_PKG_VERSION" ), "/class.html#object-properties-using-getter-and-setter" )] |
107 | #[doc = concat!("[6]: https://pyo3.rs/v" , env!("CARGO_PKG_VERSION" ), "/class.html#static-methods" )] |
108 | #[doc = concat!("[7]: https://pyo3.rs/v" , env!("CARGO_PKG_VERSION" ), "/class.html#class-methods" )] |
109 | #[doc = concat!("[8]: https://pyo3.rs/v" , env!("CARGO_PKG_VERSION" ), "/class.html#callable-objects" )] |
110 | #[doc = concat!("[9]: https://pyo3.rs/v" , env!("CARGO_PKG_VERSION" ), "/class.html#class-attributes" )] |
111 | #[doc = concat!("[10]: https://pyo3.rs/v" , env!("CARGO_PKG_VERSION" ), "/class.html#method-arguments" )] |
112 | #[doc = concat!("[11]: https://pyo3.rs/v" , env!("CARGO_PKG_VERSION" ), "/class.html#object-properties-using-pyo3get-set" )] |
113 | #[proc_macro_attribute ] |
114 | pub fn pymethods (attr: TokenStream, input: TokenStream) -> TokenStream { |
115 | let methods_type: PyClassMethodsType = if cfg!(feature = "multiple-pymethods" ) { |
116 | PyClassMethodsType::Inventory |
117 | } else { |
118 | PyClassMethodsType::Specialization |
119 | }; |
120 | pymethods_impl(attr, input, methods_type) |
121 | } |
122 | |
123 | /// A proc macro used to expose Rust functions to Python. |
124 | /// |
125 | /// Functions annotated with `#[pyfunction]` can also be annotated with the following `#[pyo3]` |
126 | /// options: |
127 | /// |
128 | /// | Annotation | Description | |
129 | /// | :- | :- | |
130 | /// | `#[pyo3(name = "...")]` | Defines the name of the function in Python. | |
131 | /// | `#[pyo3(text_signature = "...")]` | Defines the `__text_signature__` attribute of the function in Python. | |
132 | /// | `#[pyo3(pass_module)]` | Passes the module containing the function as a `&PyModule` first argument to the function. | |
133 | /// |
134 | /// For more on exposing functions see the [function section of the guide][1]. |
135 | /// |
136 | /// Due to technical limitations on how `#[pyfunction]` is implemented, a function marked |
137 | /// `#[pyfunction]` cannot have a module with the same name in the same scope. (The |
138 | /// `#[pyfunction]` implementation generates a hidden module with the same name containing |
139 | /// metadata about the function, which is used by `wrap_pyfunction!`). |
140 | /// |
141 | #[doc = concat!("[1]: https://pyo3.rs/v" , env!("CARGO_PKG_VERSION" ), "/function.html" )] |
142 | #[proc_macro_attribute ] |
143 | pub fn pyfunction (attr: TokenStream, input: TokenStream) -> TokenStream { |
144 | let mut ast: ItemFn = parse_macro_input!(input as syn::ItemFn); |
145 | let options: PyFunctionOptions = parse_macro_input!(attr as PyFunctionOptions); |
146 | |
147 | let expanded: TokenStream = build_py_function(&mut ast, options).unwrap_or_compile_error(); |
148 | |
149 | quoteTokenStream!( |
150 | #ast |
151 | #expanded |
152 | ) |
153 | .into() |
154 | } |
155 | |
156 | #[proc_macro_derive (IntoPyObject, attributes(pyo3))] |
157 | pub fn derive_into_py_object(item: TokenStream) -> TokenStream { |
158 | let ast: DeriveInput = parse_macro_input!(item as syn::DeriveInput); |
159 | let expanded: TokenStream = build_derive_into_pyobject::<false>(&ast).unwrap_or_compile_error(); |
160 | quoteTokenStream!( |
161 | #expanded |
162 | ) |
163 | .into() |
164 | } |
165 | |
166 | #[proc_macro_derive (IntoPyObjectRef, attributes(pyo3))] |
167 | pub fn derive_into_py_object_ref(item: TokenStream) -> TokenStream { |
168 | let ast: DeriveInput = parse_macro_input!(item as syn::DeriveInput); |
169 | let expanded: TokenStream = |
170 | pyo3_macros_backend::build_derive_into_pyobject::<true>(&ast).unwrap_or_compile_error(); |
171 | quoteTokenStream!( |
172 | #expanded |
173 | ) |
174 | .into() |
175 | } |
176 | |
177 | #[proc_macro_derive (FromPyObject, attributes(pyo3))] |
178 | pub fn derive_from_py_object(item: TokenStream) -> TokenStream { |
179 | let ast: DeriveInput = parse_macro_input!(item as syn::DeriveInput); |
180 | let expanded: TokenStream = build_derive_from_pyobject(&ast).unwrap_or_compile_error(); |
181 | quoteTokenStream!( |
182 | #expanded |
183 | ) |
184 | .into() |
185 | } |
186 | |
187 | fn pyclass_impl( |
188 | attrs: TokenStream, |
189 | mut ast: syn::ItemStruct, |
190 | methods_type: PyClassMethodsType, |
191 | ) -> TokenStream { |
192 | let args: PyClassArgs = parse_macro_input!(attrs with PyClassArgs::parse_struct_args); |
193 | let expanded: TokenStream = build_py_class(&mut ast, args, methods_type).unwrap_or_compile_error(); |
194 | |
195 | quoteTokenStream!( |
196 | #ast |
197 | #expanded |
198 | ) |
199 | .into() |
200 | } |
201 | |
202 | fn pyclass_enum_impl( |
203 | attrs: TokenStream, |
204 | mut ast: syn::ItemEnum, |
205 | methods_type: PyClassMethodsType, |
206 | ) -> TokenStream { |
207 | let args: PyClassArgs = parse_macro_input!(attrs with PyClassArgs::parse_enum_args); |
208 | let expanded: TokenStream = build_py_enum(&mut ast, args, method_type:methods_type).unwrap_or_compile_error(); |
209 | |
210 | quoteTokenStream!( |
211 | #ast |
212 | #expanded |
213 | ) |
214 | .into() |
215 | } |
216 | |
217 | fn pymethods_impl( |
218 | attr: TokenStream, |
219 | input: TokenStream, |
220 | methods_type: PyClassMethodsType, |
221 | ) -> TokenStream { |
222 | let mut ast: ItemImpl = parse_macro_input!(input as syn::ItemImpl); |
223 | // Apply all options as a #[pyo3] attribute on the ItemImpl |
224 | // e.g. #[pymethods(crate = "crate")] impl Foo { } |
225 | // -> #[pyo3(crate = "crate")] impl Foo { } |
226 | let attr: TokenStream2 = attr.into(); |
227 | ast.attrs.push(syn::parse_quote!( #[pyo3(#attr)] )); |
228 | let expanded: TokenStream = build_py_methods(&mut ast, methods_type).unwrap_or_compile_error(); |
229 | |
230 | quoteTokenStream!( |
231 | #ast |
232 | #expanded |
233 | ) |
234 | .into() |
235 | } |
236 | |
237 | fn methods_type() -> PyClassMethodsType { |
238 | if cfg!(feature = "multiple-pymethods" ) { |
239 | PyClassMethodsType::Inventory |
240 | } else { |
241 | PyClassMethodsType::Specialization |
242 | } |
243 | } |
244 | |
245 | trait UnwrapOrCompileError { |
246 | fn unwrap_or_compile_error(self) -> TokenStream2; |
247 | } |
248 | |
249 | impl UnwrapOrCompileError for syn::Result<TokenStream2> { |
250 | fn unwrap_or_compile_error(self) -> TokenStream2 { |
251 | self.unwrap_or_else(|e: Error| e.into_compile_error()) |
252 | } |
253 | } |
254 | |