Skip to main content

rustpython_vm/builtins/
namespace.rs

1use super::{PyStr, PyTupleRef, PyType, tuple::IntoPyTuple};
2use crate::{
3    AsObject, Context, Py, PyObject, PyObjectRef, PyPayload, PyRef, PyResult, VirtualMachine,
4    builtins::PyDict,
5    class::PyClassImpl,
6    function::{KwArgs, NameChanges, OptionalArg, PyComparisonValue},
7    recursion::ReprGuard,
8    types::{
9        Comparable, Constructor, DefaultConstructor, Initializer, PyComparisonOp, Representable,
10    },
11};
12use rustpython_common::wtf8::Wtf8Buf;
13
14#[pyclass(module = "types", name = "SimpleNamespace", unhashable = true)]
15// The dict lives in the object extension, not the payload. The offset is
16// the dict cell at the front of that extension.
17#[pymember(name = "__dict__", offset = ::rustpython_vm::object::dict_member_offset())]
18#[derive(Debug, Default)]
19pub struct PyNamespace {}
20
21impl PyPayload for PyNamespace {
22    #[inline]
23    fn class(ctx: &Context) -> &'static Py<PyType> {
24        ctx.types.namespace_type
25    }
26}
27
28impl DefaultConstructor for PyNamespace {}
29
30#[pyclass(
31    flags(BASETYPE, HAS_DICT, HAS_WEAKREF),
32    with(Constructor, Initializer, Comparable, Representable)
33)]
34impl PyNamespace {
35    #[pymethod]
36    fn __reduce__(zelf: PyObjectRef, vm: &VirtualMachine) -> PyTupleRef {
37        let dict = zelf.as_object().dict().unwrap();
38        let obj = zelf.as_object().to_owned();
39        let result: (PyObjectRef, PyObjectRef, PyObjectRef) = (
40            obj.class().to_owned().into(),
41            vm.new_tuple(()).into(),
42            dict.into(),
43        );
44        result.into_pytuple(vm)
45    }
46
47    #[pymethod]
48    fn __replace__(
49        zelf: PyObjectRef,
50        changes: KwArgs<PyObjectRef, NameChanges>,
51        vm: &VirtualMachine,
52    ) -> PyResult {
53        // Create a new instance of the same type
54        let cls: PyObjectRef = zelf.class().to_owned().into();
55        let result = cls.call((), vm)?;
56
57        if !zelf.class().is(result.class()) {
58            return Err(vm.new_type_error(format!(
59                "expect {} type, but {}() returned '{}' object",
60                Self::class(&vm.ctx).slot_name(),
61                zelf.class()
62                    .__qualname__(vm)
63                    .downcast_ref::<PyStr>()
64                    .map_or_else(
65                        || zelf.class().name().to_string(),
66                        |n| n.as_wtf8().to_string()
67                    ),
68                result.class().name(),
69            )));
70        }
71
72        // Copy the current namespace dict to the new instance
73        let src_dict = zelf.dict().unwrap();
74        let dst_dict = result.dict().unwrap();
75        for (key, value) in src_dict {
76            dst_dict.set_item(&*key, value, vm)?;
77        }
78
79        // Update with the provided kwargs
80        for (name, value) in changes {
81            let name = vm.ctx.new_str(name);
82            result.set_attr(&name, value, vm)?;
83        }
84
85        Ok(result)
86    }
87}
88
89#[derive(FromArgs)]
90pub struct NamespaceArgs {
91    #[pyarg(positional, default, py_default = "()")]
92    mapping_or_iterable: OptionalArg<PyObjectRef>,
93    #[pyarg(flatten)]
94    kwargs: KwArgs,
95}
96
97impl Initializer for PyNamespace {
98    type Args = NamespaceArgs;
99
100    fn init(zelf: &Py<Self>, args: Self::Args, vm: &VirtualMachine) -> PyResult<()> {
101        // If there's a positional argument, treat it as a mapping
102        if let OptionalArg::Present(mapping) = args.mapping_or_iterable {
103            // Convert to dict if not already
104            let dict: PyRef<PyDict> = if let Some(d) = mapping.downcast_ref::<PyDict>() {
105                d.to_owned()
106            } else {
107                // Call dict() on the mapping
108                let dict_type: PyObjectRef = vm.ctx.types.dict_type.to_owned().into();
109                dict_type
110                    .call((mapping,), vm)?
111                    .downcast()
112                    .map_err(|_| vm.new_type_error("dict() did not return a dict"))?
113            };
114
115            // Validate keys are strings and set attributes
116            for (key, value) in dict {
117                let key_str = key
118                    .downcast_ref::<crate::builtins::PyStr>()
119                    .ok_or_else(|| {
120                        vm.new_type_error(format!(
121                            "keywords must be strings, not '{}'",
122                            key.class().name()
123                        ))
124                    })?;
125                zelf.as_object().set_attr(key_str, value, vm)?;
126            }
127        }
128
129        // Apply keyword arguments (these override positional mapping values)
130        for (name, value) in args.kwargs {
131            let name = vm.ctx.new_str(name);
132            zelf.as_object().set_attr(&name, value, vm)?;
133        }
134        Ok(())
135    }
136}
137
138impl Comparable for PyNamespace {
139    fn cmp(
140        zelf: &Py<Self>,
141        other: &PyObject,
142        op: PyComparisonOp,
143        vm: &VirtualMachine,
144    ) -> PyResult<PyComparisonValue> {
145        let other = class_or_notimplemented!(Self, other);
146        let (d1, d2) = (
147            zelf.as_object().dict().unwrap(),
148            other.as_object().dict().unwrap(),
149        );
150        PyDict::cmp(&d1, d2.as_object(), op, vm)
151    }
152}
153
154impl Representable for PyNamespace {
155    #[inline]
156    fn repr_wtf8(zelf: &Py<Self>, vm: &VirtualMachine) -> PyResult<Wtf8Buf> {
157        let o = zelf.as_object();
158        let name = if o.class().is(vm.ctx.types.namespace_type) {
159            "namespace".to_owned()
160        } else {
161            o.class().slot_name().to_owned()
162        };
163
164        let repr = if let Some(_guard) = ReprGuard::enter(vm, zelf.as_object()) {
165            let dict = zelf.as_object().dict().unwrap();
166            let mut result = Wtf8Buf::from(format!("{name}("));
167            let mut first = true;
168            for (key, value) in dict {
169                let Some(key_str) = key.downcast_ref::<PyStr>() else {
170                    continue;
171                };
172                if key_str.as_wtf8().is_empty() {
173                    continue;
174                }
175                if !first {
176                    result.push_str(", ");
177                }
178                first = false;
179                result.push_wtf8(key_str.as_wtf8());
180                result.push_char('=');
181                result.push_wtf8(value.repr(vm)?.as_wtf8());
182            }
183            result.push_char(')');
184            result
185        } else {
186            Wtf8Buf::from(format!("{name}(...)"))
187        };
188        Ok(repr)
189    }
190}
191
192pub(crate) fn init(context: &'static Context) {
193    PyNamespace::extend_class(context, context.types.namespace_type);
194}