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}