mirror of
https://github.com/RustPython/RustPython.git
synced 2026-10-28 10:44:36 +08:00
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:
1 parent
d4f85cf073
commit
1d3603419e
11 files changed
+444
-268
No files matched your search
Vendored
-2
@@ -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')
|
||||
|
||||
Vendored
-2
@@ -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)
|
||||
|
||||
Vendored
-2
@@ -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
@@ -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(¶meters.posonlyargs)
|
||||
.chain(¶meters.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(¶meters.posonlyargs)
|
||||
.chain(¶meters.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 ¶meters.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(¶ms.posonlyargs)
|
||||
.chain(¶ms.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 ¶ms.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)?;
|
||||
|
||||
@@ -127,6 +127,7 @@ pub enum SymbolScope {
|
||||
GlobalImplicit,
|
||||
Free,
|
||||
Cell,
|
||||
// TODO: wrong place. not a symbol scope, but a COMPILER_SCOPE_TYPEPARAMS
|
||||
TypeParams,
|
||||
}
|
||||
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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)
|
||||
|
||||
Reference in new issue
Block a user