Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
set_function_attribute
  • Loading branch information
youknowone committed Jul 14, 2025
commit 7787c6f75e511b2199865356762bd032ba19c72a
75 changes: 75 additions & 0 deletions vm/src/builtins/function.rs
Original file line number Diff line number Diff line change
Expand Up @@ -395,6 +395,81 @@ impl Py<PyFunction> {
pub(crate) fn set_closure(&self, closure: Option<PyTupleRef>) {
let _ = unsafe { self.closure.swap(closure) };
}

/// Set function attribute based on MakeFunctionFlags
/// This is used by SET_FUNCTION_ATTRIBUTE instruction in CPython 3.13 style
pub(crate) fn set_function_attribute(
&self,
attr: bytecode::MakeFunctionFlags,
attr_value: PyObjectRef,
vm: &VirtualMachine,
) -> PyResult<()> {
use crate::builtins::PyDict;
if attr.contains(bytecode::MakeFunctionFlags::DEFAULTS) {
let defaults = attr_value.clone().downcast::<PyTuple>().map_err(|_| {
vm.new_type_error(format!(
"__defaults__ must be a tuple, not {}",
attr_value.class().name()
))
})?;
self.set___defaults__(Some(defaults));
} else if attr.contains(bytecode::MakeFunctionFlags::KW_ONLY_DEFAULTS) {
let kwdefaults = attr_value.clone().downcast::<PyDict>().map_err(|_| {
vm.new_type_error(format!(
"__kwdefaults__ must be a dict, not {}",
attr_value.class().name()
))
})?;
self.set___kwdefaults__(Some(kwdefaults));
} else if attr.contains(bytecode::MakeFunctionFlags::ANNOTATIONS) {
let annotations = attr_value.clone().downcast::<PyDict>().map_err(|_| {
vm.new_type_error(format!(
"__annotations__ must be a dict, not {}",
attr_value.class().name()
))
})?;
*self.annotations.lock() = annotations;
} else if attr.contains(bytecode::MakeFunctionFlags::CLOSURE) {
// For closure, we need special handling
// The closure tuple contains cell objects
let closure_tuple =
attr_value
.clone()
.downcast_exact::<PyTuple>(vm)
.map_err(|obj| {
vm.new_type_error(format!(
"closure must be a tuple, not {}",
obj.class().name()
))
})?;

// Convert to tuple of cells
let cells: Result<Vec<_>, _> = closure_tuple
.iter()
.map(|cell| cell.clone().downcast_exact::<PyCell>(vm))
.collect();
let cells = cells
.map_err(|_| vm.new_type_error("closure must be a tuple of cells".to_owned()))?;

// Convert cells to PyTuple
let cells_objects: Vec<PyObjectRef> = cells
.into_iter()
.map(|cell| cell.into_pyref().into())
.collect();
let cells_tuple = PyTuple::new_ref(cells_objects, &vm.ctx);

self.set_closure(Some(cells_tuple));
} else if attr.contains(bytecode::MakeFunctionFlags::TYPE_PARAMS) {
let type_params = attr_value.clone().downcast::<PyTuple>().map_err(|_| {
vm.new_type_error(format!(
"__type_params__ must be a tuple, not {}",
attr_value.class().name()
))
})?;
*self.type_params.lock() = type_params;
}
Ok(())
}
}

impl PyPayload for PyFunction {
Expand Down
46 changes: 5 additions & 41 deletions vm/src/frame.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1883,48 +1883,12 @@ impl ExecutingFrame<'_> {
)));
}

// Set the attribute based on the flag
if attr.contains(bytecode::MakeFunctionFlags::DEFAULTS) {
func.set_attr("__defaults__", attr_value, vm)?;
} else if attr.contains(bytecode::MakeFunctionFlags::KW_ONLY_DEFAULTS) {
func.set_attr("__kwdefaults__", attr_value, vm)?;
} else if attr.contains(bytecode::MakeFunctionFlags::ANNOTATIONS) {
func.set_attr("__annotations__", attr_value, vm)?;
} else if attr.contains(bytecode::MakeFunctionFlags::CLOSURE) {
// For closure, we need special handling
// The closure tuple contains cell objects
let closure_tuple = attr_value.downcast_exact::<PyTuple>(vm).map_err(|obj| {
vm.new_type_error(format!(
"closure must be a tuple, not {}",
obj.class().name()
))
})?;
// Get the function reference and call the new method
let func_ref = func
.downcast_ref::<PyFunction>()
.expect("SET_FUNCTION_ATTRIBUTE expects function on stack");

// Convert to tuple of cells
let cells: Result<Vec<_>, _> = closure_tuple
.iter()
.map(|cell| cell.clone().downcast_exact::<PyCell>(vm))
.collect();
let cells = cells
.map_err(|_| vm.new_type_error("closure must be a tuple of cells".to_owned()))?;

// Convert cells to PyTuple
let cells_objects: Vec<PyObjectRef> = cells
.into_iter()
.map(|cell| cell.into_pyref().into())
.collect();
let cells_tuple = PyTuple::new_ref(cells_objects, &vm.ctx);

// Get reference to the function
let func_ref = func
.downcast_ref::<PyFunction>()
.expect("SET_FUNCTION_ATTRIBUTE expects function on stack");

func_ref.set_closure(Some(cells_tuple));
} else if attr.contains(bytecode::MakeFunctionFlags::TYPE_PARAMS) {
// Type params can be set via attribute
func.set_attr("__type_params__", attr_value, vm)?;
}
func_ref.set_function_attribute(attr, attr_value, vm)?;

// Push function back onto stack
self.push_value(func);
Expand Down