| 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 | |