Skip to content

Commit a18dca3

Browse files
committed
namespace constructor and __replace__
1 parent 5eaf531 commit a18dca3

File tree

1 file changed

+61
-2
lines changed

1 file changed

+61
-2
lines changed

crates/vm/src/builtins/namespace.rs

Lines changed: 61 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,15 +42,74 @@ impl PyNamespace {
4242
);
4343
result.into_pytuple(vm)
4444
}
45+
46+
#[pymethod]
47+
fn __replace__(zelf: PyObjectRef, args: FuncArgs, vm: &VirtualMachine) -> PyResult {
48+
if !args.args.is_empty() {
49+
return Err(vm.new_type_error("__replace__() takes no positional arguments"));
50+
}
51+
52+
// Create a new instance of the same type
53+
let cls: PyObjectRef = zelf.class().to_owned().into();
54+
let result = cls.call((), vm)?;
55+
56+
// Copy the current namespace dict to the new instance
57+
let src_dict = zelf.dict().unwrap();
58+
let dst_dict = result.dict().unwrap();
59+
for (key, value) in src_dict {
60+
dst_dict.set_item(&*key, value, vm)?;
61+
}
62+
63+
// Update with the provided kwargs
64+
for (name, value) in args.kwargs {
65+
let name = vm.ctx.new_str(name);
66+
result.set_attr(&name, value, vm)?;
67+
}
68+
69+
Ok(result)
70+
}
4571
}
4672

4773
impl Initializer for PyNamespace {
4874
type Args = FuncArgs;
4975

5076
fn init(zelf: PyRef<Self>, args: Self::Args, vm: &VirtualMachine) -> PyResult<()> {
51-
if !args.args.is_empty() {
52-
return Err(vm.new_type_error("no positional arguments expected"));
77+
// SimpleNamespace accepts 0 or 1 positional argument (a mapping)
78+
if args.args.len() > 1 {
79+
return Err(vm.new_type_error(format!(
80+
"{} expected at most 1 positional argument, got {}",
81+
zelf.class().name(),
82+
args.args.len()
83+
)));
5384
}
85+
86+
// If there's a positional argument, treat it as a mapping
87+
if let Some(mapping) = args.args.first() {
88+
// Convert to dict if not already
89+
let dict: PyRef<PyDict> = if let Some(d) = mapping.downcast_ref::<PyDict>() {
90+
d.to_owned()
91+
} else {
92+
// Call dict() on the mapping
93+
let dict_type: PyObjectRef = vm.ctx.types.dict_type.to_owned().into();
94+
dict_type
95+
.call((mapping.clone(),), vm)?
96+
.downcast()
97+
.map_err(|_| vm.new_type_error("dict() did not return a dict"))?
98+
};
99+
100+
// Validate keys are strings and set attributes
101+
for (key, value) in dict.into_iter() {
102+
let key_str = key.downcast_ref::<crate::builtins::PyStr>().ok_or_else(|| {
103+
vm.new_type_error(format!(
104+
"keywords must be strings, not '{}'",
105+
key.class().name()
106+
))
107+
})?;
108+
zelf.as_object().set_attr(key_str, value, vm)?;
109+
}
110+
}
111+
112+
// Apply keyword arguments (these override positional mapping values)
54113
for (name, value) in args.kwargs {
55114
let name = vm.ctx.new_str(name);
56115
zelf.as_object().set_attr(&name, value, vm)?;

0 commit comments

Comments
 (0)