//! This crate declares only the proc macro attributes, as a crate defining proc macro attributes //! must not contain any other public items. #![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] extern crate proc_macro; use proc_macro::TokenStream; use proc_macro2::TokenStream as TokenStream2; use pyo3_macros_backend::{ build_derive_from_pyobject, build_py_class, build_py_enum, build_py_function, build_py_methods, get_doc, process_functions_in_module, pymodule_impl, PyClassArgs, PyClassMethodsType, PyFunctionOptions, PyModuleOptions, }; use quote::quote; use syn::{parse::Nothing, parse_macro_input}; /// A proc macro used to implement Python modules. /// /// The name of the module will be taken from the function name, unless `#[pyo3(name = "my_name")]` /// is also annotated on the function to override the name. **Important**: the module name should /// match the `lib.name` setting in `Cargo.toml`, so that Python is able to import the module /// without needing a custom import loader. /// /// Functions annotated with `#[pymodule]` can also be annotated with the following: /// /// | Annotation | Description | /// | :- | :- | /// | `#[pyo3(name = "...")]` | Defines the name of the module in Python. | /// /// For more on creating Python modules see the [module section of the guide][1]. /// /// Due to technical limitations on how `#[pymodule]` is implemented, a function marked /// `#[pymodule]` cannot have a module with the same name in the same scope. (The /// `#[pymodule]` implementation generates a hidden module with the same name containing /// metadata about the module, which is used by `wrap_pymodule!`). /// /// [1]: https://pyo3.rs/latest/module.html #[proc_macro_attribute] pub fn pymodule(args: TokenStream, input: TokenStream) -> TokenStream { parse_macro_input!(args as Nothing); let mut ast = parse_macro_input!(input as syn::ItemFn); let options = match PyModuleOptions::from_attrs(&mut ast.attrs) { Ok(options) => options, Err(e) => return e.into_compile_error().into(), }; if let Err(err) = process_functions_in_module(&options, &mut ast) { return err.into_compile_error().into(); } let doc = get_doc(&ast.attrs, None); let expanded = pymodule_impl(&ast.sig.ident, options, doc, &ast.vis); quote!( #ast #expanded ) .into() } #[proc_macro_attribute] pub fn pyclass(attr: TokenStream, input: TokenStream) -> TokenStream { use syn::Item; let item = parse_macro_input!(input as Item); match item { Item::Struct(struct_) => pyclass_impl(attr, struct_, methods_type()), Item::Enum(enum_) => pyclass_enum_impl(attr, enum_, methods_type()), unsupported => { syn::Error::new_spanned(unsupported, "#[pyclass] only supports structs and enums.") .into_compile_error() .into() } } } /// A proc macro used to expose methods to Python. /// /// Methods within a `#[pymethods]` block can be annotated with as well as the following: /// /// | Annotation | Description | /// | :- | :- | /// | [`#[new]`][4] | Defines the class constructor, like Python's `__new__` method. | /// | [`#[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).| /// | [`#[staticmethod]`][6]| Defines the method as a staticmethod, like Python's `@staticmethod` decorator.| /// | [`#[classmethod]`][7] | Defines the method as a classmethod, like Python's `@classmethod` decorator.| /// | [`#[classattr]`][9] | Defines a class variable. | /// | [`#[args]`][10] | Deprecated way to define a method's default arguments and allows the function to receive `*args` and `**kwargs`. Use `#[pyo3(signature = (...))]` instead. | /// | [`#[pyo3( | Any of the `#[pyo3]` options supported on [`macro@pyfunction`]. | /// /// For more on creating class methods, /// see the [class section of the guide][1]. /// /// If the [`multiple-pymethods`][2] feature is enabled, it is possible to implement /// multiple `#[pymethods]` blocks for a single `#[pyclass]`. /// This will add a transitive dependency on the [`inventory`][3] crate. /// /// [1]: https://pyo3.rs/latest/class.html#instance-methods /// [2]: https://pyo3.rs/latest/features.html#multiple-pymethods /// [3]: https://docs.rs/inventory/ /// [4]: https://pyo3.rs/latest/class.html#constructor /// [5]: https://pyo3.rs/latest/class.html#object-properties-using-getter-and-setter /// [6]: https://pyo3.rs/latest/class.html#static-methods /// [7]: https://pyo3.rs/latest/class.html#class-methods /// [8]: https://pyo3.rs/latest/class.html#callable-objects /// [9]: https://pyo3.rs/latest/class.html#class-attributes /// [10]: https://pyo3.rs/latest/class.html#method-arguments /// [11]: https://pyo3.rs/latest/class.html#object-properties-using-pyo3get-set #[proc_macro_attribute] pub fn pymethods(attr: TokenStream, input: TokenStream) -> TokenStream { let methods_type = if cfg!(feature = "multiple-pymethods") { PyClassMethodsType::Inventory } else { PyClassMethodsType::Specialization }; pymethods_impl(attr, input, methods_type) } /// A proc macro used to expose Rust functions to Python. /// /// Functions annotated with `#[pyfunction]` can also be annotated with the following `#[pyo3]` /// options: /// /// | Annotation | Description | /// | :- | :- | /// | `#[pyo3(name = "...")]` | Defines the name of the function in Python. | /// | `#[pyo3(text_signature = "...")]` | Defines the `__text_signature__` attribute of the function in Python. | /// | `#[pyo3(pass_module)]` | Passes the module containing the function as a `&PyModule` first argument to the function. | /// /// For more on exposing functions see the [function section of the guide][1]. /// /// Due to technical limitations on how `#[pyfunction]` is implemented, a function marked /// `#[pyfunction]` cannot have a module with the same name in the same scope. (The /// `#[pyfunction]` implementation generates a hidden module with the same name containing /// metadata about the function, which is used by `wrap_pyfunction!`). /// /// [1]: https://pyo3.rs/latest/function.html #[proc_macro_attribute] pub fn pyfunction(attr: TokenStream, input: TokenStream) -> TokenStream { let mut ast = parse_macro_input!(input as syn::ItemFn); let options = parse_macro_input!(attr as PyFunctionOptions); let expanded = build_py_function(&mut ast, options).unwrap_or_compile_error(); quote!( #ast #expanded ) .into() } #[proc_macro_derive(FromPyObject, attributes(pyo3))] pub fn derive_from_py_object(item: TokenStream) -> TokenStream { let ast = parse_macro_input!(item as syn::DeriveInput); let expanded = build_derive_from_pyobject(&ast).unwrap_or_compile_error(); quote!( #expanded ) .into() } fn pyclass_impl( attrs: TokenStream, mut ast: syn::ItemStruct, methods_type: PyClassMethodsType, ) -> TokenStream { let args = parse_macro_input!(attrs with PyClassArgs::parse_stuct_args); let expanded = build_py_class(&mut ast, args, methods_type).unwrap_or_compile_error(); quote!( #ast #expanded ) .into() } fn pyclass_enum_impl( attrs: TokenStream, mut ast: syn::ItemEnum, methods_type: PyClassMethodsType, ) -> TokenStream { let args = parse_macro_input!(attrs with PyClassArgs::parse_enum_args); let expanded = build_py_enum(&mut ast, args, methods_type).unwrap_or_compile_error(); quote!( #ast #expanded ) .into() } fn pymethods_impl( attr: TokenStream, input: TokenStream, methods_type: PyClassMethodsType, ) -> TokenStream { let mut ast = parse_macro_input!(input as syn::ItemImpl); // Apply all options as a #[pyo3] attribute on the ItemImpl // e.g. #[pymethods(crate = "crate")] impl Foo { } // -> #[pyo3(crate = "crate")] impl Foo { } let attr: TokenStream2 = attr.into(); ast.attrs.push(syn::parse_quote!( #[pyo3(#attr)] )); let expanded = build_py_methods(&mut ast, methods_type).unwrap_or_compile_error(); quote!( #ast #expanded ) .into() } fn methods_type() -> PyClassMethodsType { if cfg!(feature = "multiple-pymethods") { PyClassMethodsType::Inventory } else { PyClassMethodsType::Specialization } } trait UnwrapOrCompileError { fn unwrap_or_compile_error(self) -> TokenStream2; } impl UnwrapOrCompileError for syn::Result { fn unwrap_or_compile_error(self) -> TokenStream2 { self.unwrap_or_else(|e| e.into_compile_error()) } }