Skip to main content

rustpython_vm/
macros.rs

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