SetFunctionAttribute (#5968)

* PyRef::into_non_null

* SetFunctionAttribute

* set_function_attribute

* frame helper in PyFuncion

* remove closure lock

* cleanup unused args
This commit is contained in:
Jeong, YunWon authored and GitHub committed 2025-07-15 03:12:23 +09:00
1 parent d4f85cf073
commit 1d3603419e
11 files changed
+444 -268

No files matched your search

-2
View File
@@ -176,8 +176,6 @@ class FunctionPropertiesTest(FuncAttrsTest):
self.assertEqual(self.fi.a.__name__, 'a')
self.cannot_set_attr(self.fi.a, "__name__", 'a', AttributeError)
# TODO: RUSTPYTHON
@unittest.expectedFailure
def test___qualname__(self):
# PEP 3155
self.assertEqual(self.b.__qualname__, 'FuncAttrsTest.setUp.<locals>.b')
-2
View File
@@ -176,8 +176,6 @@ class ReprTests(unittest.TestCase):
self.assertTrue(s.endswith(">"))
self.assertIn(s.find("..."), [12, 13])
# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_lambda(self):
r = repr(lambda x: x)
self.assertTrue(r.startswith("<function ReprTests.test_lambda.<locals>.<lambda"), r)
-2
View File
@@ -5353,8 +5353,6 @@ class ForwardRefTests(BaseTestCase):
some.__no_type_check__
self.assertEqual(get_type_hints(some), {'args': int, 'return': int})
# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_no_type_check_lambda(self):
@no_type_check
class A:
+242 -107
View File
@@ -1482,26 +1482,7 @@ impl Compiler<'_> {
Ok(())
}
fn enter_function(
&mut self,
name: &str,
parameters: &Parameters,
) -> CompileResult<bytecode::MakeFunctionFlags> {
let defaults: Vec<_> = std::iter::empty()
.chain(&parameters.posonlyargs)
.chain(&parameters.args)
.filter_map(|x| x.default.as_deref())
.collect();
let have_defaults = !defaults.is_empty();
if have_defaults {
// Construct a tuple:
let size = defaults.len().to_u32();
for element in &defaults {
self.compile_expression(element)?;
}
emit!(self, Instruction::BuildTuple { size });
}
fn enter_function(&mut self, name: &str, parameters: &Parameters) -> CompileResult<()> {
// TODO: partition_in_place
let mut kw_without_defaults = vec![];
let mut kw_with_defaults = vec![];
@@ -1513,31 +1494,6 @@ impl Compiler<'_> {
}
}
// let (kw_without_defaults, kw_with_defaults) = args.split_kwonlyargs();
if !kw_with_defaults.is_empty() {
let default_kw_count = kw_with_defaults.len();
for (arg, default) in kw_with_defaults.iter() {
self.emit_load_const(ConstantData::Str {
value: arg.name.as_str().into(),
});
self.compile_expression(default)?;
}
emit!(
self,
Instruction::BuildMap {
size: default_kw_count.to_u32(),
}
);
}
let mut func_flags = bytecode::MakeFunctionFlags::empty();
if have_defaults {
func_flags |= bytecode::MakeFunctionFlags::DEFAULTS;
}
if !kw_with_defaults.is_empty() {
func_flags |= bytecode::MakeFunctionFlags::KW_ONLY_DEFAULTS;
}
self.push_output(
bytecode::CodeFlags::NEW_LOCALS | bytecode::CodeFlags::IS_OPTIMIZED,
parameters.posonlyargs.len().to_u32(),
@@ -1565,7 +1521,7 @@ impl Compiler<'_> {
self.varname(name.name.as_str())?;
}
Ok(func_flags)
Ok(())
}
fn prepare_decorators(&mut self, decorator_list: &[Decorator]) -> CompileResult<()> {
@@ -1869,7 +1825,57 @@ impl Compiler<'_> {
self.push_symbol_table();
}
let mut func_flags = self.enter_function(name, parameters)?;
// Prepare defaults and kwdefaults before entering function
let defaults: Vec<_> = std::iter::empty()
.chain(&parameters.posonlyargs)
.chain(&parameters.args)
.filter_map(|x| x.default.as_deref())
.collect();
let have_defaults = !defaults.is_empty();
// Compile defaults before entering function scope
if have_defaults {
// Construct a tuple:
let size = defaults.len().to_u32();
for element in &defaults {
self.compile_expression(element)?;
}
emit!(self, Instruction::BuildTuple { size });
}
// Prepare keyword-only defaults
let mut kw_with_defaults = vec![];
for kwonlyarg in &parameters.kwonlyargs {
if let Some(default) = &kwonlyarg.default {
kw_with_defaults.push((&kwonlyarg.parameter, default));
}
}
let have_kwdefaults = !kw_with_defaults.is_empty();
if have_kwdefaults {
let default_kw_count = kw_with_defaults.len();
for (arg, default) in kw_with_defaults.iter() {
self.emit_load_const(ConstantData::Str {
value: arg.name.as_str().into(),
});
self.compile_expression(default)?;
}
emit!(
self,
Instruction::BuildMap {
size: default_kw_count.to_u32(),
}
);
}
self.enter_function(name, parameters)?;
let mut func_flags = bytecode::MakeFunctionFlags::empty();
if have_defaults {
func_flags |= bytecode::MakeFunctionFlags::DEFAULTS;
}
if have_kwdefaults {
func_flags |= bytecode::MakeFunctionFlags::KW_ONLY_DEFAULTS;
}
self.current_code_info()
.flags
.set(bytecode::CodeFlags::IS_COROUTINE, is_async);
@@ -1888,7 +1894,7 @@ impl Compiler<'_> {
};
// Set qualname using the new method
let qualname = self.set_qualname();
self.set_qualname();
let (doc_str, body) = split_doc(body, &self.opts);
@@ -1965,7 +1971,7 @@ impl Compiler<'_> {
}
// Create function with closure
self.make_closure(code, &qualname, func_flags)?;
self.make_closure(code, func_flags)?;
if let Some(value) = doc_str {
emit!(self, Instruction::Duplicate);
@@ -1982,58 +1988,92 @@ impl Compiler<'_> {
self.store_name(name)
}
/// Determines if a variable should be CELL or FREE type
// = get_ref_type
fn get_ref_type(&self, name: &str) -> Result<SymbolScope, CodegenErrorType> {
// Special handling for __class__ and __classdict__ in class scope
if self.ctx.in_class && (name == "__class__" || name == "__classdict__") {
return Ok(SymbolScope::Cell);
}
let table = self.symbol_table_stack.last().unwrap();
match table.lookup(name) {
Some(symbol) => match symbol.scope {
SymbolScope::Cell | SymbolScope::TypeParams => Ok(SymbolScope::Cell),
SymbolScope::Free => Ok(SymbolScope::Free),
_ if symbol.flags.contains(SymbolFlags::FREE_CLASS) => Ok(SymbolScope::Free),
_ => Err(CodegenErrorType::SyntaxError(format!(
"get_ref_type: invalid scope for '{name}'"
))),
},
None => Err(CodegenErrorType::SyntaxError(format!(
"get_ref_type: cannot find symbol '{name}'"
))),
}
}
/// Loads closure variables if needed and creates a function object
// = compiler_make_closure
fn make_closure(
&mut self,
code: CodeObject,
qualname: &str,
mut flags: bytecode::MakeFunctionFlags,
flags: bytecode::MakeFunctionFlags,
) -> CompileResult<()> {
// Handle free variables (closure)
if !code.freevars.is_empty() {
let has_freevars = !code.freevars.is_empty();
if has_freevars {
// Build closure tuple by loading free variables
for var in &code.freevars {
let table = self.symbol_table_stack.last().unwrap();
let symbol = match table.lookup(var) {
Some(s) => s,
None => {
return Err(self.error(CodegenErrorType::SyntaxError(format!(
"compiler_make_closure: cannot find symbol '{var}'",
))));
}
};
for var in &code.freevars {
// Special case: If a class contains a method with a
// free variable that has the same name as a method,
// the name will be considered free *and* local in the
// class. It should be handled by the closure, as
// well as by the normal name lookup logic.
// Get reference type using our get_ref_type function
let ref_type = self.get_ref_type(var).map_err(|e| self.error(e))?;
// Get parent code info
let parent_code = self.code_stack.last().unwrap();
let vars = match symbol.scope {
SymbolScope::Free => &parent_code.metadata.freevars,
SymbolScope::Cell => &parent_code.metadata.cellvars,
SymbolScope::TypeParams => &parent_code.metadata.cellvars,
_ if symbol.flags.contains(SymbolFlags::FREE_CLASS) => {
&parent_code.metadata.freevars
}
let cellvars_len = parent_code.metadata.cellvars.len();
// Look up the variable index based on reference type
let idx = match ref_type {
SymbolScope::Cell => parent_code
.metadata
.cellvars
.get_index_of(var)
.or_else(|| {
parent_code
.metadata
.freevars
.get_index_of(var)
.map(|i| i + cellvars_len)
})
.ok_or_else(|| {
self.error(CodegenErrorType::SyntaxError(format!(
"compiler_make_closure: cannot find '{var}' in parent vars",
)))
})?,
SymbolScope::Free => parent_code
.metadata
.freevars
.get_index_of(var)
.map(|i| i + cellvars_len)
.or_else(|| parent_code.metadata.cellvars.get_index_of(var))
.ok_or_else(|| {
self.error(CodegenErrorType::SyntaxError(format!(
"compiler_make_closure: cannot find '{var}' in parent vars",
)))
})?,
_ => {
return Err(self.error(CodegenErrorType::SyntaxError(format!(
"compiler_make_closure: invalid scope for '{var}'",
"compiler_make_closure: unexpected ref_type {ref_type:?} for '{var}'",
))));
}
};
let idx = match vars.get_index_of(var) {
Some(i) => i,
None => {
return Err(self.error(CodegenErrorType::SyntaxError(format!(
"compiler_make_closure: cannot find '{var}' in parent vars",
))));
}
};
let idx = if let SymbolScope::Free = symbol.scope {
idx + parent_code.metadata.cellvars.len()
} else {
idx
};
emit!(self, Instruction::LoadClosure(idx.to_u32()));
}
@@ -2044,22 +2084,73 @@ impl Compiler<'_> {
size: code.freevars.len().to_u32(),
}
);
flags |= bytecode::MakeFunctionFlags::CLOSURE;
}
// Load code object
// load code object and create function
self.emit_load_const(ConstantData::Code {
code: Box::new(code),
});
// Load qualified name
self.emit_load_const(ConstantData::Str {
value: qualname.into(),
});
// Create function with no flags
emit!(self, Instruction::MakeFunction);
// Make function with proper flags
emit!(self, Instruction::MakeFunction(flags));
// Now set attributes one by one using SET_FUNCTION_ATTRIBUTE
// Note: The order matters! Values must be on stack before calling SET_FUNCTION_ATTRIBUTE
// Set closure if needed
if has_freevars {
// Closure tuple is already on stack
emit!(
self,
Instruction::SetFunctionAttribute {
attr: bytecode::MakeFunctionFlags::CLOSURE
}
);
}
// Set annotations if present
if flags.contains(bytecode::MakeFunctionFlags::ANNOTATIONS) {
// Annotations dict is already on stack
emit!(
self,
Instruction::SetFunctionAttribute {
attr: bytecode::MakeFunctionFlags::ANNOTATIONS
}
);
}
// Set kwdefaults if present
if flags.contains(bytecode::MakeFunctionFlags::KW_ONLY_DEFAULTS) {
// kwdefaults dict is already on stack
emit!(
self,
Instruction::SetFunctionAttribute {
attr: bytecode::MakeFunctionFlags::KW_ONLY_DEFAULTS
}
);
}
// Set defaults if present
if flags.contains(bytecode::MakeFunctionFlags::DEFAULTS) {
// defaults tuple is already on stack
emit!(
self,
Instruction::SetFunctionAttribute {
attr: bytecode::MakeFunctionFlags::DEFAULTS
}
);
}
// Set type_params if present
if flags.contains(bytecode::MakeFunctionFlags::TYPE_PARAMS) {
// type_params tuple is already on stack
emit!(
self,
Instruction::SetFunctionAttribute {
attr: bytecode::MakeFunctionFlags::TYPE_PARAMS
}
);
}
Ok(())
}
@@ -2262,7 +2353,7 @@ impl Compiler<'_> {
func_flags |= bytecode::MakeFunctionFlags::TYPE_PARAMS;
// Create class function with closure
self.make_closure(class_code, name, func_flags)?;
self.make_closure(class_code, func_flags)?;
self.emit_load_const(ConstantData::Str { value: name.into() });
// Compile original bases
@@ -2311,19 +2402,14 @@ impl Compiler<'_> {
let type_params_code = self.exit_scope();
// Execute the type params function
let type_params_name = format!("<generic parameters of {name}>");
self.make_closure(
type_params_code,
&type_params_name,
bytecode::MakeFunctionFlags::empty(),
)?;
self.make_closure(type_params_code, bytecode::MakeFunctionFlags::empty())?;
emit!(self, Instruction::CallFunctionPositional { nargs: 0 });
} else {
// Non-generic class: standard path
emit!(self, Instruction::LoadBuildClass);
// Create class function with closure
self.make_closure(class_code, name, bytecode::MakeFunctionFlags::empty())?;
self.make_closure(class_code, bytecode::MakeFunctionFlags::empty())?;
self.emit_load_const(ConstantData::Str { value: name.into() });
let call = if let Some(arguments) = arguments {
@@ -4033,10 +4119,59 @@ impl Compiler<'_> {
parameters, body, ..
}) => {
let prev_ctx = self.ctx;
let name = "<lambda>".to_owned();
let func_flags = self
.enter_function(&name, parameters.as_deref().unwrap_or(&Default::default()))?;
let default_params = Default::default();
let params = parameters.as_deref().unwrap_or(&default_params);
// Prepare defaults before entering function
let defaults: Vec<_> = std::iter::empty()
.chain(&params.posonlyargs)
.chain(&params.args)
.filter_map(|x| x.default.as_deref())
.collect();
let have_defaults = !defaults.is_empty();
if have_defaults {
let size = defaults.len().to_u32();
for element in &defaults {
self.compile_expression(element)?;
}
emit!(self, Instruction::BuildTuple { size });
}
// Prepare keyword-only defaults
let mut kw_with_defaults = vec![];
for kwonlyarg in &params.kwonlyargs {
if let Some(default) = &kwonlyarg.default {
kw_with_defaults.push((&kwonlyarg.parameter, default));
}
}
let have_kwdefaults = !kw_with_defaults.is_empty();
if have_kwdefaults {
let default_kw_count = kw_with_defaults.len();
for (arg, default) in kw_with_defaults.iter() {
self.emit_load_const(ConstantData::Str {
value: arg.name.as_str().into(),
});
self.compile_expression(default)?;
}
emit!(
self,
Instruction::BuildMap {
size: default_kw_count.to_u32(),
}
);
}
self.enter_function(&name, params)?;
let mut func_flags = bytecode::MakeFunctionFlags::empty();
if have_defaults {
func_flags |= bytecode::MakeFunctionFlags::DEFAULTS;
}
if have_kwdefaults {
func_flags |= bytecode::MakeFunctionFlags::KW_ONLY_DEFAULTS;
}
// Set qualname for lambda
self.set_qualname();
@@ -4057,7 +4192,7 @@ impl Compiler<'_> {
let code = self.exit_scope();
// Create lambda function with closure
self.make_closure(code, &name, func_flags)?;
self.make_closure(code, func_flags)?;
self.ctx = prev_ctx;
}
@@ -4602,7 +4737,7 @@ impl Compiler<'_> {
self.ctx = prev_ctx;
// Create comprehension function with closure
self.make_closure(code, name, bytecode::MakeFunctionFlags::empty())?;
self.make_closure(code, bytecode::MakeFunctionFlags::empty())?;
// Evaluate iterated item:
self.compile_expression(&generators[0].iter)?;
+1
View File
@@ -127,6 +127,7 @@ pub enum SymbolScope {
GlobalImplicit,
Free,
Cell,
// TODO: wrong place. not a symbol scope, but a COMPILER_SCOPE_TYPEPARAMS
TypeParams,
}
+13 -9
View File
@@ -528,7 +528,10 @@ pub enum Instruction {
JumpIfFalseOrPop {
target: Arg<Label>,
},
MakeFunction(Arg<MakeFunctionFlags>),
MakeFunction,
SetFunctionAttribute {
attr: Arg<MakeFunctionFlags>,
},
CallFunctionPositional {
nargs: Arg<u32>,
},
@@ -1296,13 +1299,13 @@ impl Instruction {
-1
}
}
MakeFunction(flags) => {
let flags = flags.get(arg);
-2 - flags.contains(MakeFunctionFlags::CLOSURE) as i32
- flags.contains(MakeFunctionFlags::ANNOTATIONS) as i32
- flags.contains(MakeFunctionFlags::KW_ONLY_DEFAULTS) as i32
- flags.contains(MakeFunctionFlags::DEFAULTS) as i32
+ 1
MakeFunction => {
// CPython 3.13 style: MakeFunction only pops code object
-1 + 1 // pop code, push function
}
SetFunctionAttribute { .. } => {
// pops attribute value and function, pushes function back
-2 + 1
}
CallFunctionPositional { nargs } => -(nargs.get(arg) as i32) - 1 + 1,
CallMethodPositional { nargs } => -(nargs.get(arg) as i32) - 3 + 1,
@@ -1497,7 +1500,8 @@ impl Instruction {
JumpIfFalse { target } => w!(JumpIfFalse, target),
JumpIfTrueOrPop { target } => w!(JumpIfTrueOrPop, target),
JumpIfFalseOrPop { target } => w!(JumpIfFalseOrPop, target),
MakeFunction(flags) => w!(MakeFunction, ?flags),
MakeFunction => w!(MakeFunction),
SetFunctionAttribute { attr } => w!(SetFunctionAttribute, ?attr),
CallFunctionPositional { nargs } => w!(CallFunctionPositional, nargs),
CallFunctionKeyword { nargs } => w!(CallFunctionKeyword, nargs),
CallFunctionEx { has_kwargs } => w!(CallFunctionEx, has_kwargs),
+34 -11
View File
@@ -122,24 +122,47 @@ impl StackMachine {
}
self.stack.push(StackValue::Map(map));
}
Instruction::MakeFunction(_flags) => {
let _name = if let Some(StackValue::String(name)) = self.stack.pop() {
name
} else {
panic!("Expected function name")
};
Instruction::MakeFunction => {
let code = if let Some(StackValue::Code(code)) = self.stack.pop() {
code
} else {
panic!("Expected function code")
};
let annotations = if let Some(StackValue::Map(map)) = self.stack.pop() {
map
// Other attributes will be set by SET_FUNCTION_ATTRIBUTE
self.stack.push(StackValue::Function(Function {
code,
annotations: HashMap::new(), // empty annotations, will be set later if needed
}));
}
Instruction::SetFunctionAttribute { attr } => {
// Stack: [..., attr_value, func] -> [..., func]
let func = if let Some(StackValue::Function(func)) = self.stack.pop() {
func
} else {
panic!("Expected function annotations")
panic!("Expected function on stack for SET_FUNCTION_ATTRIBUTE")
};
self.stack
.push(StackValue::Function(Function { code, annotations }));
let attr_value = self.stack.pop().expect("Expected attribute value on stack");
// For now, we only handle ANNOTATIONS flag in JIT tests
if attr
.get(arg)
.contains(rustpython_compiler_core::bytecode::MakeFunctionFlags::ANNOTATIONS)
{
if let StackValue::Map(annotations) = attr_value {
// Update function's annotations
let updated_func = Function {
code: func.code,
annotations,
};
self.stack.push(StackValue::Function(updated_func));
} else {
panic!("Expected annotations to be a map");
}
} else {
// For other attributes, just push the function back unchanged
// (since JIT tests mainly care about type annotations)
self.stack.push(StackValue::Function(func));
}
}
Instruction::Duplicate => {
let value = self.stack.last().unwrap().clone();
+96 -50
View File
@@ -36,11 +36,11 @@ pub struct PyFunction {
name: PyMutex<PyStrRef>,
qualname: PyMutex<PyStrRef>,
type_params: PyMutex<PyTupleRef>,
#[cfg(feature = "jit")]
jitted_code: OnceCell<CompiledCode>,
annotations: PyMutex<PyDictRef>,
module: PyMutex<PyObjectRef>,
doc: PyMutex<PyObjectRef>,
#[cfg(feature = "jit")]
jitted_code: OnceCell<CompiledCode>,
}
unsafe impl Traverse for PyFunction {
@@ -54,18 +54,10 @@ unsafe impl Traverse for PyFunction {
}
impl PyFunction {
#[allow(clippy::too_many_arguments)]
#[inline]
pub(crate) fn new(
code: PyRef<PyCode>,
globals: PyDictRef,
closure: Option<PyRef<PyTuple<PyCellRef>>>,
defaults: Option<PyTupleRef>,
kw_only_defaults: Option<PyDictRef>,
qualname: PyStrRef,
type_params: PyTupleRef,
annotations: PyDictRef,
doc: PyObjectRef,
vm: &VirtualMachine,
) -> PyResult<Self> {
let name = PyMutex::new(code.obj_name.to_owned());
@@ -79,27 +71,22 @@ impl PyFunction {
}
});
let qualname = vm.ctx.new_str(code.qualname.as_str());
let func = Self {
code,
code: code.clone(),
globals,
builtins,
closure,
defaults_and_kwdefaults: PyMutex::new((defaults, kw_only_defaults)),
closure: None,
defaults_and_kwdefaults: PyMutex::new((None, None)),
name,
qualname: PyMutex::new(qualname),
type_params: PyMutex::new(type_params),
annotations: PyMutex::new(annotations),
type_params: PyMutex::new(vm.ctx.empty_tuple.clone()),
annotations: PyMutex::new(vm.ctx.new_dict()),
module: PyMutex::new(module),
doc: PyMutex::new(doc),
doc: PyMutex::new(vm.ctx.none()),
#[cfg(feature = "jit")]
jitted_code: OnceCell::new(),
};
// let name = qualname.as_str().split('.').next_back().unwrap();
// func.set_attr(identifier!(vm, __name__), vm.new_pyobj(name), vm)?;
// func.set_attr(identifier!(vm, __qualname__), qualname, vm)?;
// func.set_attr(identifier!(vm, __doc__), doc, vm)?;
Ok(func)
}
@@ -316,6 +303,76 @@ impl PyFunction {
Ok(())
}
/// Set function attribute based on MakeFunctionFlags
pub(crate) fn set_function_attribute(
&mut self,
attr: bytecode::MakeFunctionFlags,
attr_value: PyObjectRef,
vm: &VirtualMachine,
) -> PyResult<()> {
use crate::builtins::PyDict;
if attr == bytecode::MakeFunctionFlags::DEFAULTS {
let defaults = match attr_value.downcast::<PyTuple>() {
Ok(tuple) => tuple,
Err(obj) => {
return Err(vm.new_type_error(format!(
"__defaults__ must be a tuple, not {}",
obj.class().name()
)));
}
};
self.defaults_and_kwdefaults.lock().0 = Some(defaults);
} else if attr == bytecode::MakeFunctionFlags::KW_ONLY_DEFAULTS {
let kwdefaults = match attr_value.downcast::<PyDict>() {
Ok(dict) => dict,
Err(obj) => {
return Err(vm.new_type_error(format!(
"__kwdefaults__ must be a dict, not {}",
obj.class().name()
)));
}
};
self.defaults_and_kwdefaults.lock().1 = Some(kwdefaults);
} else if attr == bytecode::MakeFunctionFlags::ANNOTATIONS {
let annotations = match attr_value.downcast::<PyDict>() {
Ok(dict) => dict,
Err(obj) => {
return Err(vm.new_type_error(format!(
"__annotations__ must be a dict, not {}",
obj.class().name()
)));
}
};
*self.annotations.lock() = annotations;
} else if attr == 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()
))
})?
.into_pyref();
self.closure = Some(closure_tuple.try_into_typed::<PyCell>(vm)?);
} else if attr == 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;
} else {
unreachable!("This is a compiler bug");
}
Ok(())
}
}
impl Py<PyFunction> {
@@ -622,34 +679,23 @@ impl Constructor for PyFunction {
None
};
// Get function name - use provided name or default to code object name
let name = args
.name
.into_option()
.unwrap_or_else(|| PyStr::from(args.code.obj_name.as_str()).into_ref(&vm.ctx));
// Get qualname - for now just use the name
let qualname = name.clone();
// Create empty type_params and annotations
let type_params = vm.ctx.new_tuple(vec![]);
let annotations = vm.ctx.new_dict();
// Get doc from code object - for now just use None
let doc = vm.ctx.none();
let func = Self::new(
args.code,
args.globals,
closure,
args.defaults.into_option(),
args.kwdefaults.into_option(),
qualname,
type_params,
annotations,
doc,
vm,
)?;
let mut func = Self::new(args.code.clone(), args.globals.clone(), vm)?;
// Set function name if provided
if let Some(name) = args.name.into_option() {
*func.name.lock() = name.clone();
// Also update qualname to match the name
*func.qualname.lock() = name;
}
// Now set additional attributes directly
if let Some(closure_tuple) = closure {
func.closure = Some(closure_tuple);
}
if let Some(defaults) = args.defaults.into_option() {
func.defaults_and_kwdefaults.lock().0 = Some(defaults);
}
if let Some(kwdefaults) = args.kwdefaults.into_option() {
func.defaults_and_kwdefaults.lock().1 = Some(kwdefaults);
}
func.into_ref_with_type(vm, cls).map(Into::into)
}
+37 -69
View File
@@ -1159,8 +1159,9 @@ impl ExecutingFrame<'_> {
}
}
bytecode::Instruction::ForIter { target } => self.execute_for_iter(vm, target.get(arg)),
bytecode::Instruction::MakeFunction(flags) => {
self.execute_make_function(vm, flags.get(arg))
bytecode::Instruction::MakeFunction => self.execute_make_function(vm),
bytecode::Instruction::SetFunctionAttribute { attr } => {
self.execute_set_function_attribute(vm, attr.get(arg))
}
bytecode::Instruction::CallFunctionPositional { nargs } => {
let args = self.collect_positional_args(nargs.get(arg));
@@ -1846,82 +1847,49 @@ impl ExecutingFrame<'_> {
}
}
}
fn execute_make_function(
&mut self,
vm: &VirtualMachine,
flags: bytecode::MakeFunctionFlags,
) -> FrameResult {
let qualified_name = self
.pop_value()
.downcast::<PyStr>()
.expect("qualified name to be a string");
fn execute_make_function(&mut self, vm: &VirtualMachine) -> FrameResult {
// MakeFunction only takes code object, no flags
let code_obj: PyRef<PyCode> = self
.pop_value()
.downcast()
.expect("Second to top value on the stack must be a code object");
.expect("Stack value should be code object");
let closure = if flags.contains(bytecode::MakeFunctionFlags::CLOSURE) {
let tuple = PyTupleRef::try_from_object(vm, self.pop_value()).unwrap();
Some(tuple.try_into_typed(vm).expect("This is a compiler bug"))
} else {
None
};
let annotations = if flags.contains(bytecode::MakeFunctionFlags::ANNOTATIONS) {
self.pop_value()
} else {
vm.ctx.new_dict().into()
};
let type_params: PyTupleRef = if flags.contains(bytecode::MakeFunctionFlags::TYPE_PARAMS) {
self.pop_value()
.downcast()
.map_err(|_| vm.new_type_error("Type params must be a tuple."))?
} else {
vm.ctx.empty_tuple.clone()
};
let kw_only_defaults = if flags.contains(bytecode::MakeFunctionFlags::KW_ONLY_DEFAULTS) {
Some(
self.pop_value()
.downcast::<PyDict>()
.expect("Stack value for keyword only defaults expected to be a dict"),
)
} else {
None
};
let defaults = if flags.contains(bytecode::MakeFunctionFlags::DEFAULTS) {
Some(
self.pop_value()
.downcast::<PyTuple>()
.expect("Stack value for defaults expected to be a tuple"),
)
} else {
None
};
// pop argc arguments
// argument: name, args, globals
// let scope = self.scope.clone();
let func_obj = PyFunction::new(
code_obj,
self.globals.clone(),
closure,
defaults,
kw_only_defaults,
qualified_name.clone(),
type_params,
annotations.downcast().unwrap(),
vm.ctx.none(),
vm,
)?
.into_pyobject(vm);
// Create function with minimal attributes
let func_obj = PyFunction::new(code_obj, self.globals.clone(), vm)?.into_pyobject(vm);
self.push_value(func_obj);
Ok(None)
}
fn execute_set_function_attribute(
&mut self,
vm: &VirtualMachine,
attr: bytecode::MakeFunctionFlags,
) -> FrameResult {
// CPython 3.13 style: SET_FUNCTION_ATTRIBUTE sets attributes on a function
// Stack: [..., attr_value, func] -> [..., func]
// Stack order: func is at -1, attr_value is at -2
let func = self.pop_value();
let attr_value = self.replace_top(func);
let func = self.top_value();
// Get the function reference and call the new method
let func_ref = func
.downcast_ref::<PyFunction>()
.expect("SET_FUNCTION_ATTRIBUTE expects function on stack");
let payload: &PyFunction = func_ref.payload();
// SetFunctionAttribute always follows MakeFunction, so at this point
// there are no other references to func. It is therefore safe to treat it as mutable.
unsafe {
let payload_ptr = payload as *const PyFunction as *mut PyFunction;
(*payload_ptr).set_function_attribute(attr, attr_value, vm)?;
};
Ok(None)
}
#[cfg_attr(feature = "flame-it", flame("Frame"))]
fn execute_bin_op(&mut self, vm: &VirtualMachine, op: bytecode::BinaryOperator) -> FrameResult {
let b_ref = &self.pop_value();
+20 -3
View File
@@ -906,6 +906,11 @@ impl<T: PyObjectPayload> Py<T> {
_marker: PhantomData,
})
}
pub fn payload(&self) -> &T {
// SAFETY: we know the payload is T because of the type parameter
unsafe { self.as_object().payload_unchecked() }
}
}
impl<T> ToOwned for Py<T> {
@@ -1018,11 +1023,23 @@ impl<T> Clone for PyRef<T> {
}
impl<T: PyObjectPayload> PyRef<T> {
// #[inline(always)]
// pub(crate) const fn into_non_null(self) -> NonNull<Py<T>> {
// let ptr = self.ptr;
// std::mem::forget(self);
// ptr
// }
#[inline(always)]
pub(crate) const unsafe fn from_non_null(ptr: NonNull<Py<T>>) -> Self {
Self { ptr }
}
/// # Safety
/// The raw pointer must point to a valid `Py<T>` object
#[inline(always)]
pub(crate) const unsafe fn from_raw(raw: *const Py<T>) -> Self {
Self {
ptr: unsafe { NonNull::new_unchecked(raw as *mut _) },
}
unsafe { Self::from_non_null(NonNull::new_unchecked(raw as *mut _)) }
}
/// Safety: payload type of `obj` must be `T`
+1 -13
View File
@@ -448,19 +448,7 @@ impl VirtualMachine {
use crate::builtins::PyFunction;
// Create a function object for module code, similar to CPython's PyEval_EvalCode
let qualname = self.ctx.new_str(code.obj_name.as_str());
let func = PyFunction::new(
code.clone(),
scope.globals.clone(),
None, // closure
None, // defaults
None, // kwdefaults
qualname,
self.ctx.new_tuple(vec![]), // type_params
self.ctx.new_dict(), // annotations
self.ctx.none(), // doc
self,
)?;
let func = PyFunction::new(code.clone(), scope.globals.clone(), self)?;
let func_obj = func.into_ref(&self.ctx).into();
let frame = Frame::new(code, scope, self.builtins.dict(), &[], Some(func_obj), self)