Skip to main content

rustpython_vm/
macros.rs

1/// Expands to the first group when this crate is built with its `doc` feature, else to the second.
2/// Derive output uses it so doc-dependent code follows `rustpython-vm`'s features, not the
3/// features of the crate the derive expands in.
4#[cfg(feature = "doc")]
5#[doc(hidden)]
6#[macro_export]
7macro_rules! __cfg_doc {
8    ({ $($doc:tt)* } else { $($no_doc:tt)* }) => { $($doc)* };
9}
10
11#[cfg(not(feature = "doc"))]
12#[doc(hidden)]
13#[macro_export]
14macro_rules! __cfg_doc {
15    ({ $($doc:tt)* } else { $($no_doc:tt)* }) => { $($no_doc)* };
16}
17
18#[macro_export]
19macro_rules! extend_module {
20    ( $vm:expr, $module:expr, { $($name:expr => $value:expr),* $(,)? }) => {{
21        $(
22            $vm.__module_set_attr($module, $vm.ctx.intern_str($name), $value).unwrap();
23        )*
24    }};
25}
26
27#[macro_export]
28macro_rules! py_class {
29    ( $ctx:expr, $class_name:expr, $class_base:expr, { $($name:tt => $value:expr),* $(,)* }) => {
30        py_class!($ctx, $class_name, $class_base, $crate::types::PyTypeFlags::from_element($crate::types::PyTypeFlags::BASETYPE), { $($name => $value),* })
31    };
32    ( $ctx:expr, $class_name:expr, $class_base:expr, $flags:expr, { $($name:tt => $value:expr),* $(,)* }) => {
33        {
34            #[allow(unused_mut)]
35            let mut slots = $crate::types::PyTypeSlots::heap_default();
36            slots.flags = $crate::types::AtomicPyTypeFlags::from_plain($flags);
37            $($crate::py_class!(@extract_slots($ctx, &mut slots, $name, $value));)*
38            let py_class = $ctx.new_class(None, $class_name, $class_base, slots);
39            $($crate::py_class!(@extract_attrs($ctx, &py_class, $name, $value));)*
40            py_class
41        }
42    };
43    (@extract_slots($ctx:expr, $slots:expr, (slot $slot_name:ident), $value:expr)) => {
44        $slots.$slot_name.store(Some($value));
45    };
46    (@extract_slots($ctx:expr, $class:expr, $name:expr, $value:expr)) => {};
47    (@extract_attrs($ctx:expr, $slots:expr, (slot $slot_name:ident), $value:expr)) => {};
48    (@extract_attrs($ctx:expr, $class:expr, $name:expr, $value:expr)) => {
49        $class.set_attr($name, $value);
50    };
51}
52
53#[macro_export]
54macro_rules! extend_class {
55    ( $ctx:expr, $class:expr, { $($name:expr => $value:expr),* $(,)* }) => {
56        $(
57            $class.set_attr($ctx.intern_str($name), $value.into());
58        )*
59    };
60}
61
62#[macro_export]
63macro_rules! py_namespace {
64    ( $vm:expr, { $($name:expr => $value:expr),* $(,)* }) => {
65        {
66            let namespace = $crate::object::PyPayload::into_ref($crate::builtins::PyNamespace {}, &$vm.ctx);
67            let obj = $crate::object::AsObject::as_object(&namespace);
68            $(
69                obj.generic_setattr($vm.ctx.intern_str($name), $crate::function::PySetterValue::Assign($value.into()), $vm).unwrap();
70            )*
71            namespace
72        }
73    }
74}
75
76/// Macro to match on the built-in class of a Python object.
77///
78/// Like `match`, `match_class!` must be exhaustive, so a default arm without
79/// casting is required.
80///
81/// # Examples
82///
83/// ```rust
84/// use malachite_bigint::ToBigInt;
85/// use num_traits::Zero;
86///
87/// use rustpython_vm::{PyPayload, match_class};
88/// use rustpython_vm::builtins::{PyFloat, PyInt};
89///
90/// # rustpython_vm::Interpreter::without_stdlib(Default::default()).enter(|vm| {
91/// let obj = PyInt::from(0).into_pyobject(vm);
92/// assert_eq!(
93///     "int",
94///     match_class!(match obj {
95///         PyInt => "int",
96///         PyFloat => "float",
97///         _ => "neither",
98///     })
99/// );
100/// # });
101///
102/// ```
103///
104/// With a binding to the downcasted type:
105///
106/// ```rust
107/// use malachite_bigint::ToBigInt;
108/// use num_traits::Zero;
109///
110/// use rustpython_vm::{PyPayload, match_class};
111/// use rustpython_vm::builtins::{PyFloat, PyInt};
112///
113/// # rustpython_vm::Interpreter::without_stdlib(Default::default()).enter(|vm| {
114/// let obj = PyInt::from(0).into_pyobject(vm);
115///
116/// let int_value = match_class!(match obj {
117///     i @ PyInt => i.as_bigint().clone(),
118///     f @ PyFloat => f.to_f64().to_bigint().unwrap(),
119///     obj => panic!("non-numeric object {:?}", obj),
120/// });
121///
122/// assert!(int_value.is_zero());
123/// # });
124/// ```
125#[macro_export]
126macro_rules! match_class {
127    // The default arm.
128    (match ($obj:expr) { _ => $default:expr $(,)? }) => {
129        $default
130    };
131
132    // The default arm, binding the original object to the specified identifier.
133    (match ($obj:expr) { $binding:ident => $default:expr $(,)? }) => {{
134        #[allow(clippy::redundant_locals, reason = "macro arm intentionally binds expression once to a local")]
135        let $binding = $obj;
136        $default
137    }};
138    (match ($obj:expr) { ref $binding:ident => $default:expr $(,)? }) => {{
139        #[allow(clippy::redundant_locals, reason = "macro arm intentionally binds expression once to a local reference")]
140        let $binding = &$obj;
141        $default
142    }};
143
144    // An arm taken when the object is an instance of the specified built-in
145    // class and binding the downcasted object to the specified identifier and
146    // the target expression is a block.
147    (match ($obj:expr) { $binding:ident @ $class:ty => $expr:block $($rest:tt)* }) => {
148        $crate::match_class!(match ($obj) { $binding @ $class => ($expr), $($rest)* })
149    };
150    (match ($obj:expr) { ref $binding:ident @ $class:ty => $expr:block $($rest:tt)* }) => {
151        $crate::match_class!(match ($obj) { ref $binding @ $class => ($expr), $($rest)* })
152    };
153
154    // An arm taken when the object is an instance of the specified built-in
155    // class and binding the downcasted object to the specified identifier.
156    (match ($obj:expr) { $binding:ident @ $class:ty => $expr:expr, $($rest:tt)* }) => {
157        match $obj.downcast::<$class>() {
158            Ok($binding) => $expr,
159            Err(_obj) => $crate::match_class!(match (_obj) { $($rest)* }),
160        }
161    };
162    (match ($obj:expr) { ref $binding:ident @ $class:ty => $expr:expr, $($rest:tt)* }) => {
163        match $obj.downcast_ref::<$class>() {
164            core::option::Option::Some($binding) => $expr,
165            core::option::Option::None => $crate::match_class!(match ($obj) { $($rest)* }),
166        }
167    };
168
169    // An arm taken when the object is an instance of the specified built-in
170    // class and the target expression is a block.
171    (match ($obj:expr) { $class:ty => $expr:block $($rest:tt)* }) => {
172        $crate::match_class!(match ($obj) { $class => ($expr), $($rest)* })
173    };
174
175    // An arm taken when the object is an instance of the specified built-in
176    // class.
177    (match ($obj:expr) { $class:ty => $expr:expr, $($rest:tt)* }) => {
178        if $obj.downcastable::<$class>() {
179            $expr
180        } else {
181            $crate::match_class!(match ($obj) { $($rest)* })
182        }
183    };
184
185    // To allow match expressions without parens around the match target
186    (match $($rest:tt)*) => {
187        $crate::match_class!(@parse_match () ($($rest)*))
188    };
189    (@parse_match ($($target:tt)*) ({ $($inner:tt)* })) => {
190        $crate::match_class!(match ($($target)*) { $($inner)* })
191    };
192    (@parse_match ($($target:tt)*) ($next:tt $($rest:tt)*)) => {
193        $crate::match_class!(@parse_match ($($target)* $next) ($($rest)*))
194    };
195}
196
197#[macro_export]
198macro_rules! identifier(
199    ($as_ctx:expr, $name:ident) => {
200        $as_ctx.as_ref().names.$name
201    };
202);
203
204#[macro_export]
205macro_rules! identifier_utf8(
206    ($as_ctx:expr, $name:ident) => {{
207        // Safety: All known identifiers are ascii strings.
208        let interned = $as_ctx.as_ref().names.$name;
209        unsafe { $crate::builtins::PyUtf8StrInterned::from_str_interned_unchecked(interned) }
210    }};
211);
212
213/// Super detailed logging. Might soon overflow your log buffers
214/// Default, this logging is discarded, except when a the `vm-tracing-logging`
215/// build feature is enabled.
216macro_rules! vm_trace {
217    ($($arg:tt)+) => {
218        #[cfg(feature = "vm-tracing-logging")]
219        trace!($($arg)+);
220    }
221}
222
223macro_rules! flame_guard {
224    ($name:expr) => {
225        #[cfg(feature = "flame-it")]
226        let _guard = ::flame::start_guard($name);
227    };
228}
229
230#[macro_export]
231macro_rules! class_or_notimplemented {
232    ($t:ty, $obj:expr) => {{
233        let a: &$crate::PyObject = &*$obj;
234        match $crate::PyObject::downcast_ref::<$t>(&a) {
235            Some(pyref) => pyref,
236            None => return Ok($crate::function::PyArithmeticValue::NotImplemented),
237        }
238    }};
239}
240
241#[macro_export]
242macro_rules! named_function {
243    ($ctx:expr, $module:ident, $func:ident) => {{
244        #[allow(unused_variables)] // weird lint, something to do with paste probably
245        let ctx: &$crate::Context = &$ctx;
246        $crate::__exports::paste::expr! {
247            ctx.new_method_def(
248                stringify!($func),
249                [<$module _ $func>],
250                ::rustpython_vm::function::PyMethodFlags::empty(),
251                ::rustpython_vm::function::ItemDoc::NONE,
252            )
253            .to_function()
254            .with_module(ctx.intern_str(stringify!($module)).into())
255            .into_ref(ctx)
256        }
257    }};
258}
259
260// can't use PyThreadingConstraint for stuff like this since it's not an auto trait, and
261// therefore we can't add it ad-hoc to a trait object
262macro_rules! py_dyn_fn {
263    (dyn Fn($($arg:ty),*$(,)*) -> $ret:ty) => {
264        cfg_select! {
265            feature = "threading" => {
266                dyn Fn($($arg),*) -> $ret + Send + Sync + 'static
267            }
268            _ => {
269                dyn Fn($($arg),*) -> $ret + 'static
270            }
271        }
272    };
273}