Skip to main content

rustpython_derive/
lib.rs

1#![recursion_limit = "128"]
2#![doc(html_logo_url = "https://raw.githubusercontent.com/RustPython/RustPython/main/logo.png")]
3#![doc(html_root_url = "https://docs.rs/rustpython-derive/")]
4
5use proc_macro::TokenStream;
6use rustpython_derive_impl as derive_impl;
7use syn::parse_macro_input;
8use syn::punctuated::Punctuated;
9
10#[proc_macro_derive(FromArgs, attributes(pyarg))]
11pub fn derive_from_args(input: TokenStream) -> TokenStream {
12    let input = parse_macro_input!(input);
13    derive_impl::derive_from_args(input).into()
14}
15
16/// The attribute can be applied either to a struct, trait, or impl.
17/// # Struct
18/// This implements `MaybeTraverse`, `PyClassDef`, and `StaticType` for the struct.
19/// Consider deriving `Traverse` to implement it.
20/// ## Arguments
21/// - `module`: the module which contains the class --  can be omitted if in a `#[pymodule]`.
22/// - `name`: the name of the Python class, by default it is the name of the struct.
23/// - `base`: the base class of the Python class.
24///   This does not cause inheritance of functions or attributes that must be done by a separate trait.
25/// # Impl
26/// This part implements `PyClassImpl` for the struct.
27/// This includes methods, getters/setters, etc.; only annotated methods will be included.
28/// Common functions and abilities like instantiation and `__call__` are often implemented by
29/// traits rather than in the `impl` itself; see `Constructor` and `Callable` respectively for those.
30/// ## Arguments
31/// - `name`: the name of the Python class, when no name is provided the struct name is used.
32/// - `flags`: the flags of the class, see `PyTypeFlags`.
33///     - `BASETYPE`: allows the class to be inheritable.
34///     - `IMMUTABLETYPE`: class attributes are immutable.
35/// - `with`: which trait implementations are to be included in the python class.
36/// ```rust, ignore
37/// #[pyclass(module = "my_module", name = "MyClass", base = BaseClass)]
38/// struct MyStruct {
39///    x: i32,
40/// }
41///
42/// impl Constructor for MyStruct {
43///     ...
44/// }
45///
46/// #[pyclass(with(Constructor))]
47/// impl MyStruct {
48///    ...
49/// }
50/// ```
51/// ## Inner markers
52/// ### pymethod/pyclassmethod/pystaticmethod
53/// `pymethod` is used to mark a method of the Python class.
54/// `pyclassmethod` is used to mark a class method.
55/// `pystaticmethod` is used to mark a static method.
56/// #### Method signature
57/// The first parameter can be either `&self` or `<var>: PyRef<Self>` for `pymethod`.
58/// The first parameter can be `cls: PyTypeRef` for `pyclassmethod`.
59/// There is no mandatory parameter for `pystaticmethod`.
60/// Both are valid and essentially the same, but the latter can yield more control.
61/// The last parameter can optionally be of the type `&VirtualMachine` to access the VM.
62/// All other values must implement `IntoPyResult`.
63/// Numeric types, `String`, `bool`, and `PyObjectRef` implement this trait,
64/// but so does any object that implements `PyValue`.
65/// Consider using `OptionalArg` for optional arguments.
66/// #### Arguments
67/// - `name`: the name of the method in Python,
68///   by default it is the same as the Rust method, or surrounded by double underscores if magic is present.
69///   This overrides `magic` and the default name and cannot be used with `magic` to prevent ambiguity.
70/// ### pygetset
71/// This is used to mark a getter/setter pair.
72/// #### Arguments
73/// - `setter`: marks the method as a setter, it acts as a getter by default.
74///   Setter method names should be prefixed with `set_`.
75/// - `name`: the name of the attribute in Python, by default it is the same as the Rust method.
76/// - `magic`: marks the method as a magic method: the method name is surrounded with double underscores.
77///   This cannot be used with `name` to prevent ambiguity.
78///
79/// Ensure both the getter and setter are marked with `name` and `magic` in the same manner.
80/// #### Examples
81/// ```rust, ignore
82/// #[pyclass]
83/// impl MyStruct {
84///    #[pygetset]
85///    fn x(&self) -> PyResult<i32> {
86///       Ok(self.x.lock())
87///     }
88///    #[pygetset(setter)]
89///   fn set_x(&mut self, value: i32) -> PyResult<()> {
90///      self.x.set(value);
91///     Ok(())
92///     }
93/// }
94/// ```
95/// ### pyslot
96/// This is used to mark a slot method it should be marked by prefixing the method in rust with `slot_`.
97/// #### Arguments
98/// - name: the name of the slot method.
99/// ### pyattr
100/// ### extend_class
101/// This helps inherit attributes from a parent class.
102/// The method this is applied on should be called `extend_class_with_fields`.
103/// #### Examples
104/// ```rust, ignore
105/// #[extend_class]
106/// fn extend_class_with_fields(ctx: &Context, class: &'static Py<PyType>) {
107///     class.set_attr(
108///         identifier!(ctx, _fields),
109///         ctx.new_tuple(vec![
110///             ctx.new_str(ascii!("body")).into(),
111///             ctx.new_str(ascii!("type_ignores")).into(),
112///         ])
113///         .into(),
114///     );
115///     class.set_attr(identifier!(ctx, _attributes), ctx.new_list(vec![]).into());
116/// }
117/// ```
118/// ### pymember
119/// # Trait
120/// `#[pyclass]` on traits functions a lot like `#[pyclass]` on `impl` blocks.
121/// Note that associated functions that are annotated with `#[pymethod]` or similar **must**
122/// have a body, abstract functions should be wrapped before applying an annotation.
123#[proc_macro_attribute]
124pub fn pyclass(attr: TokenStream, item: TokenStream) -> TokenStream {
125    let attr = parse_macro_input!(attr with Punctuated::parse_terminated);
126    let item = parse_macro_input!(item);
127    derive_impl::pyclass(attr, item).into()
128}
129
130/// Helper macro to define `Exception` types.
131/// More-or-less is an alias to `pyclass` macro.
132///
133/// This macro serves a goal of generating multiple
134/// `BaseException` / `Exception`
135/// subtypes in a uniform and convenient manner.
136/// It looks like `SimpleExtendsException` in `CPython`.
137/// <https://github.com/python/cpython/blob/main/Objects/exceptions.c>
138#[proc_macro_attribute]
139pub fn pyexception(attr: TokenStream, item: TokenStream) -> TokenStream {
140    let attr = parse_macro_input!(attr with Punctuated::parse_terminated);
141    let item = parse_macro_input!(item);
142    derive_impl::pyexception(attr, item).into()
143}
144
145/// This attribute must be applied to an inline module.
146/// It defines a Python module in the form of a `module_def` function in the module;
147/// this has to be used in a `add_native_module` to properly register the module.
148/// Additionally, this macro defines 'MODULE_NAME' and 'DOC' in the module.
149/// # Arguments
150/// - `name`: the name of the python module,
151///   by default, it is the name of the module, but this can be configured.
152/// ```rust, ignore
153/// // This will create a module named `my_module`
154/// #[pymodule(name = "my_module")]
155/// mod module {
156/// }
157/// ```
158/// - `sub`: declare the module as a submodule of another module.
159/// ```rust, ignore
160/// #[pymodule(sub)]
161/// mod submodule {
162/// }
163///
164/// #[pymodule(with(submodule))]
165/// mod my_module {
166/// }
167/// ```
168/// - `with`: declare the list of submodules that this module contains (see `sub` for example).
169/// ## Inner markers
170/// ### pyattr
171/// `pyattr` is a multipurpose marker that can be used in a pymodule.
172/// The most common use is to mark a function or class as a part of the module.
173/// This can be done by applying it to a function or struct prior to the `#[pyfunction]` or `#[pyclass]` macro.
174/// If applied to a constant, it will be added to the module as an attribute.
175/// If applied to a function not marked with `pyfunction`,
176/// it will also be added to the module as an attribute but the value is the result of the function.
177/// If `#[pyattr(once)]` is used in this case, the function will be called once
178/// and the result will be stored using a `static_cell`.
179/// #### Examples
180/// ```rust, ignore
181/// #[pymodule]
182/// mod my_module {
183///     #[pyattr]
184///     const MY_CONSTANT: i32 = 42;
185///     #[pyattr]
186///    fn another_constant() -> PyResult<i32> {
187///       Ok(42)
188///    }
189///   #[pyattr(once)]
190///   fn once() -> PyResult<i32> {
191///     // This will only be called once and the result will be stored.
192///     Ok(2 ** 24)
193///  }
194///
195///     #[pyattr]
196///     #[pyfunction]
197///     fn my_function(vm: &VirtualMachine) -> PyResult<()> {
198///         ...
199///     }
200/// }
201/// ```
202/// ### pyfunction
203/// This is used to create a python function.
204/// #### Function signature
205/// The last argument can optionally be of the type `&VirtualMachine` to access the VM.
206/// Refer to the `pymethod` documentation (located in the `pyclass` macro documentation)
207/// for more information on what regular argument types are permitted.
208/// #### Arguments
209/// - `name`: the name of the function in Python, by default it is the same as the associated Rust function.
210#[proc_macro_attribute]
211pub fn pymodule(attr: TokenStream, item: TokenStream) -> TokenStream {
212    let attr = parse_macro_input!(attr as derive_impl::PyModuleArgs);
213    let item = parse_macro_input!(item);
214    derive_impl::pymodule(attr, item).into()
215}
216
217/// Attribute macro for defining Python struct sequence types.
218///
219/// This macro is applied to an empty struct to create a Python type
220/// that wraps a Data struct.
221///
222/// # Example
223/// ```ignore
224/// #[pystruct_sequence_data]
225/// struct StructTimeData {
226///     pub tm_year: PyObjectRef,
227///     #[pystruct_sequence(skip)]
228///     pub tm_gmtoff: PyObjectRef,
229/// }
230///
231/// #[pystruct_sequence(name = "struct_time", module = "time", data = "StructTimeData")]
232/// struct PyStructTime;
233/// ```
234#[proc_macro_attribute]
235pub fn pystruct_sequence(attr: TokenStream, item: TokenStream) -> TokenStream {
236    let attr = parse_macro_input!(attr with Punctuated::parse_terminated);
237    let item = parse_macro_input!(item);
238    derive_impl::pystruct_sequence(attr, item).into()
239}
240
241/// Attribute macro for struct sequence Data structs.
242///
243/// Generates field name constants, index constants, and `into_tuple()` method.
244///
245/// # Example
246/// ```ignore
247/// #[pystruct_sequence_data]
248/// struct StructTimeData {
249///     pub tm_year: PyObjectRef,
250///     pub tm_mon: PyObjectRef,
251///     #[pystruct_sequence(skip)]  // optional field, not included in tuple
252///     pub tm_gmtoff: PyObjectRef,
253/// }
254/// ```
255///
256/// # Options
257/// - `try_from_object`: Generate `try_from_elements()` method and `TryFromObject` impl
258///
259/// ```ignore
260/// #[pystruct_sequence_data(try_from_object)]
261/// struct StructTimeData { ... }
262/// ```
263#[proc_macro_attribute]
264pub fn pystruct_sequence_data(attr: TokenStream, item: TokenStream) -> TokenStream {
265    let attr = parse_macro_input!(attr with Punctuated::parse_terminated);
266    let item = parse_macro_input!(item);
267    derive_impl::pystruct_sequence_data(attr, item).into()
268}
269
270struct Compiler;
271impl derive_impl::Compiler for Compiler {
272    fn compile(
273        &self,
274        source: &str,
275        mode: rustpython_compiler::Mode,
276        module_name: String,
277    ) -> Result<rustpython_compiler::CodeObject, Box<dyn core::error::Error>> {
278        use rustpython_compiler::{CompileOpts, compile};
279        Ok(compile(source, mode, &module_name, CompileOpts::default())?)
280    }
281}
282
283#[proc_macro]
284pub fn py_compile(input: TokenStream) -> TokenStream {
285    derive_impl::py_compile(input.into(), &Compiler).into()
286}
287
288#[proc_macro]
289pub fn py_freeze(input: TokenStream) -> TokenStream {
290    derive_impl::py_freeze(input.into(), &Compiler).into()
291}
292
293#[proc_macro_derive(PyPayload)]
294pub fn pypayload(input: TokenStream) -> TokenStream {
295    let input = parse_macro_input!(input);
296    derive_impl::pypayload(input).into()
297}
298
299/// use on struct with named fields like `struct A{x:PyRef<B>, y:PyRef<C>}` to impl `Traverse` for datatype.
300///
301/// use `#[pytraverse(skip)]` on fields you wish not to trace
302///
303/// add `trace` attr to `#[pyclass]` to make it impl `MaybeTraverse` that will call `Traverse`'s `traverse` method so make it
304/// traceable(Even from type-erased PyObject)(i.e. write `#[pyclass(trace)]`).
305/// # Example
306/// ```rust, ignore
307/// #[pyclass(module = false, traverse)]
308/// #[derive(Default, Traverse)]
309/// pub struct PyList {
310///     elements: PyRwLock<Vec<PyObjectRef>>,
311///     #[pytraverse(skip)]
312///     len: AtomicCell<usize>,
313/// }
314/// ```
315/// This create both `MaybeTraverse` that call `Traverse`'s `traverse` method and `Traverse` that impl `Traverse`
316/// for `PyList` which call elements' `traverse` method and ignore `len` field.
317#[proc_macro_derive(Traverse, attributes(pytraverse))]
318pub fn pytraverse(item: proc_macro::TokenStream) -> proc_macro::TokenStream {
319    let item = parse_macro_input!(item);
320    derive_impl::pytraverse(item).into()
321}