From a903bea52be1f44d900bcbed225152832f7dbaf0 Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Sat, 12 Jul 2025 17:42:32 +0900 Subject: [PATCH 1/4] compile_class_body --- compiler/codegen/src/compile.rs | 106 +++++++++++++++++++++----------- 1 file changed, 71 insertions(+), 35 deletions(-) diff --git a/compiler/codegen/src/compile.rs b/compiler/codegen/src/compile.rs index 14495499c5e..70a90df99af 100644 --- a/compiler/codegen/src/compile.rs +++ b/compiler/codegen/src/compile.rs @@ -2069,39 +2069,20 @@ impl Compiler<'_> { false } - fn compile_class_def( + /// Compile the class body into a code object + /// This is similar to CPython's compiler_class_body + fn compile_class_body( &mut self, name: &str, body: &[Stmt], - decorator_list: &[Decorator], type_params: Option<&TypeParams>, - arguments: Option<&Arguments>, - ) -> CompileResult<()> { - self.prepare_decorators(decorator_list)?; - - let prev_ctx = self.ctx; - self.ctx = CompileContext { - func: FunctionContext::NoFunction, - in_class: true, - loop_data: None, - }; - - // If there are type params, we need to push a special symbol table just for them - if let Some(type_params) = type_params { - self.push_symbol_table(); - // Save current private name to restore later - let saved_private = self.code_stack.last().and_then(|info| info.private.clone()); - // Compile type parameters and store as .type_params - self.compile_type_params(type_params)?; - // Restore private name after type param scope - if let Some(private) = saved_private { - self.code_stack.last_mut().unwrap().private = Some(private); - } - let dot_type_params = self.name(".type_params"); - emit!(self, Instruction::StoreLocal(dot_type_params)); - } - - self.push_output(bytecode::CodeFlags::empty(), 0, 0, 0, name.to_owned()); + firstlineno: u32, + ) -> CompileResult { + // 1. Enter class scope + // Use enter_scope instead of push_output to match CPython + let key = self.symbol_table_stack.len(); + self.push_symbol_table(); + self.enter_scope(name, SymbolTableType::Class, key, firstlineno)?; // Set qualname using the new method let qualname = self.set_qualname(); @@ -2109,26 +2090,35 @@ impl Compiler<'_> { // For class scopes, set u_private to the class name for name mangling self.code_stack.last_mut().unwrap().private = Some(name.to_owned()); + // 2. Set up class namespace let (doc_str, body) = split_doc(body, &self.opts); + // Load (global) __name__ and store as __module__ let dunder_name = self.name("__name__"); emit!(self, Instruction::LoadGlobal(dunder_name)); let dunder_module = self.name("__module__"); emit!(self, Instruction::StoreLocal(dunder_module)); + + // Store __qualname__ self.emit_load_const(ConstantData::Str { value: qualname.into(), }); let qualname_name = self.name("__qualname__"); emit!(self, Instruction::StoreLocal(qualname_name)); + + // Store __doc__ self.load_docstring(doc_str); let doc = self.name("__doc__"); emit!(self, Instruction::StoreLocal(doc)); - // setup annotations - if Self::find_ann(body) { - emit!(self, Instruction::SetupAnnotation); - } - // Set __type_params__ from .type_params if we have type parameters (PEP 695) + // Store __firstlineno__ (new in Python 3.12+) + self.emit_load_const(ConstantData::Integer { + value: BigInt::from(firstlineno), + }); + let firstlineno_name = self.name("__firstlineno__"); + emit!(self, Instruction::StoreLocal(firstlineno_name)); + + // Set __type_params__ if we have type parameters if type_params.is_some() { // Load .type_params from enclosing scope let dot_type_params = self.name(".type_params"); @@ -2139,8 +2129,15 @@ impl Compiler<'_> { emit!(self, Instruction::StoreLocal(dunder_type_params)); } + // Setup annotations if needed + if Self::find_ann(body) { + emit!(self, Instruction::SetupAnnotation); + } + + // 3. Compile the class body self.compile_statements(body)?; + // 4. Handle __classcell__ if needed let classcell_idx = self .code_stack .last_mut() @@ -2159,9 +2156,48 @@ impl Compiler<'_> { self.emit_load_const(ConstantData::None); } + // Return the class namespace self.emit_return_value(); - let code = self.exit_scope(); + // Exit scope and return the code object + Ok(self.exit_scope()) + } + + fn compile_class_def( + &mut self, + name: &str, + body: &[Stmt], + decorator_list: &[Decorator], + type_params: Option<&TypeParams>, + arguments: Option<&Arguments>, + ) -> CompileResult<()> { + self.prepare_decorators(decorator_list)?; + + let prev_ctx = self.ctx; + self.ctx = CompileContext { + func: FunctionContext::NoFunction, + in_class: true, + loop_data: None, + }; + + // If there are type params, we need to push a special symbol table just for them + if let Some(type_params) = type_params { + self.push_symbol_table(); + // Save current private name to restore later + let saved_private = self.code_stack.last().and_then(|info| info.private.clone()); + // Compile type parameters and store as .type_params + self.compile_type_params(type_params)?; + // Restore private name after type param scope + if let Some(private) = saved_private { + self.code_stack.last_mut().unwrap().private = Some(private); + } + let dot_type_params = self.name(".type_params"); + emit!(self, Instruction::StoreLocal(dot_type_params)); + } + + // Compile the class body into a code object + let firstlineno = self.get_source_line_number().get().to_u32(); + let code = self.compile_class_body(name, body, type_params, firstlineno)?; self.ctx = prev_ctx; emit!(self, Instruction::LoadBuildClass); From e146de532d565dddd366182984723bfb777d4ba7 Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Tue, 8 Jul 2025 00:16:24 +0900 Subject: [PATCH 2/4] type.__orig_bases__ regression of test_all_exported_names --- Lib/test/test_typing.py | 4 -- compiler/codegen/src/compile.rs | 66 +++++++++++++++++++++++++-------- vm/src/builtins/genericalias.rs | 10 +++-- 3 files changed, 57 insertions(+), 23 deletions(-) diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py index d0fe1b0188c..96fddfce030 100644 --- a/Lib/test/test_typing.py +++ b/Lib/test/test_typing.py @@ -6934,8 +6934,6 @@ class Y(Generic[T], NamedTuple): with self.assertRaises(TypeError): G[int, str] - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_generic_pep695(self): class X[T](NamedTuple): x: T @@ -7560,8 +7558,6 @@ class FooBarGeneric(BarGeneric[int]): {'a': typing.Optional[T], 'b': int, 'c': str} ) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_pep695_generic_typeddict(self): class A[T](TypedDict): a: T diff --git a/compiler/codegen/src/compile.rs b/compiler/codegen/src/compile.rs index 70a90df99af..ba206257682 100644 --- a/compiler/codegen/src/compile.rs +++ b/compiler/codegen/src/compile.rs @@ -2193,6 +2193,22 @@ impl Compiler<'_> { } let dot_type_params = self.name(".type_params"); emit!(self, Instruction::StoreLocal(dot_type_params)); + + // Create .generic_base in the type params scope (like CPython) + // Load .type_params + emit!(self, Instruction::LoadNameAny(dot_type_params)); + + // Call INTRINSIC_SUBSCRIPT_GENERIC to create Generic[*type_params] + emit!( + self, + Instruction::CallIntrinsic1 { + func: bytecode::IntrinsicFunction1::SubscriptGeneric + } + ); + + // Store as .generic_base in the type params scope + let dot_generic_base = self.name(".generic_base"); + emit!(self, Instruction::StoreLocal(dot_generic_base)); } // Compile the class body into a code object @@ -2228,29 +2244,49 @@ impl Compiler<'_> { // For PEP 695 classes: handle Generic base creation if type_params.is_some() { + // Stack currently: [function, class_name] + + // Compile the original bases (if any) if let Some(arguments) = arguments { - // Has explicit bases - use them as is, don't add Generic - // CPython doesn't add Generic when explicit bases are present - let call = self.compile_call_inner(2, arguments)?; - self.compile_normal_call(call); + // Compile all the positional arguments (bases) + for arg in &arguments.args { + self.compile_expression(arg)?; + } + } + + // Load .generic_base from the type params scope + let dot_generic_base = self.name(".generic_base"); + emit!(self, Instruction::LoadNameAny(dot_generic_base)); + + // Calculate total number of positional args + let nargs = if let Some(arguments) = arguments { + 2 + arguments.args.len() as u32 + 1 // function, name, bases..., generic_base } else { - // No explicit bases, add Generic[*type_params] as the only base - // Stack currently: [function, class_name] + 3 // function, name, generic_base + }; - // Load .type_params for creating Generic base - let dot_type_params = self.name(".type_params"); - emit!(self, Instruction::LoadNameAny(dot_type_params)); + // Handle keyword arguments if any + if let Some(arguments) = arguments + && !arguments.keywords.is_empty() + { + // Compile keyword arguments + for keyword in &arguments.keywords { + if let Some(name) = &keyword.arg { + self.emit_load_const(ConstantData::Str { + value: name.as_str().into(), + }); + } + self.compile_expression(&keyword.value)?; + } - // Call INTRINSIC_SUBSCRIPT_GENERIC to create Generic[*type_params] emit!( self, - Instruction::CallIntrinsic1 { - func: bytecode::IntrinsicFunction1::SubscriptGeneric + Instruction::CallFunctionKeyword { + nargs: nargs + arguments.keywords.len() as u32 } ); - - // Call __build_class__ with 3 positional args: function, class_name, Generic[T] - emit!(self, Instruction::CallFunctionPositional { nargs: 3 }); + } else { + emit!(self, Instruction::CallFunctionPositional { nargs }); } } else { // No type params, normal compilation diff --git a/vm/src/builtins/genericalias.rs b/vm/src/builtins/genericalias.rs index fc666190bfa..00bd65583d8 100644 --- a/vm/src/builtins/genericalias.rs +++ b/vm/src/builtins/genericalias.rs @@ -617,19 +617,21 @@ impl Iterable for PyGenericAlias { /// This is used for PEP 695 classes to create Generic[T] from type parameters // _Py_subscript_generic pub fn subscript_generic(type_params: PyObjectRef, vm: &VirtualMachine) -> PyResult { - // Get typing.Generic type + // Get typing module and _GenericAlias let typing_module = vm.import("typing", 0)?; let generic_type = typing_module.get_attr("Generic", vm)?; - let generic_type = PyTypeRef::try_from_object(vm, generic_type)?; - // Create GenericAlias: Generic[type_params] + // Call typing._GenericAlias(Generic, type_params) + let generic_alias_class = typing_module.get_attr("_GenericAlias", vm)?; + let args = if let Ok(tuple) = type_params.try_to_ref::(vm) { tuple.to_owned() } else { PyTuple::new_ref(vec![type_params], &vm.ctx) }; - Ok(PyGenericAlias::new(generic_type, args, false, vm).into_pyobject(vm)) + // Create _GenericAlias instance + generic_alias_class.call((generic_type, args.to_pyobject(vm)), vm) } pub fn init(context: &Context) { From 3c6e2cd800f7de8fd34c36e23901dd89460a209c Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Sat, 12 Jul 2025 19:19:40 +0900 Subject: [PATCH 3/4] rework type_params scope --- compiler/codegen/src/compile.rs | 178 +++++++++++++++++++------------- 1 file changed, 108 insertions(+), 70 deletions(-) diff --git a/compiler/codegen/src/compile.rs b/compiler/codegen/src/compile.rs index ba206257682..859ac22de73 100644 --- a/compiler/codegen/src/compile.rs +++ b/compiler/codegen/src/compile.rs @@ -2173,103 +2173,97 @@ impl Compiler<'_> { ) -> CompileResult<()> { self.prepare_decorators(decorator_list)?; - let prev_ctx = self.ctx; - self.ctx = CompileContext { - func: FunctionContext::NoFunction, - in_class: true, - loop_data: None, - }; + let has_type_params = type_params.is_some(); + let firstlineno = self.get_source_line_number().get().to_u32(); + + if has_type_params { + // For PEP 695 classes, we need to: + // 1. Enter type params scope + // 2. Compile type params + // 3. Call compile_class_body (creates nested scope) + // 4. Generate class creation code + // 5. Exit type params scope and wrap everything in a function + + let type_params_name = format!("", name); + self.push_output( + bytecode::CodeFlags::IS_OPTIMIZED | bytecode::CodeFlags::NEW_LOCALS, + 0, + 0, + 0, + type_params_name.clone(), + ); + + // Set private name for name mangling + self.code_stack.last_mut().unwrap().private = Some(name.to_owned()); - // If there are type params, we need to push a special symbol table just for them - if let Some(type_params) = type_params { - self.push_symbol_table(); - // Save current private name to restore later - let saved_private = self.code_stack.last().and_then(|info| info.private.clone()); // Compile type parameters and store as .type_params - self.compile_type_params(type_params)?; - // Restore private name after type param scope - if let Some(private) = saved_private { - self.code_stack.last_mut().unwrap().private = Some(private); - } + self.compile_type_params(type_params.unwrap())?; let dot_type_params = self.name(".type_params"); emit!(self, Instruction::StoreLocal(dot_type_params)); - // Create .generic_base in the type params scope (like CPython) - // Load .type_params - emit!(self, Instruction::LoadNameAny(dot_type_params)); + // Compile the class body (creates its own scope) + let prev_ctx = self.ctx; + self.ctx = CompileContext { + func: FunctionContext::NoFunction, + in_class: true, + loop_data: None, + }; + let class_code = self.compile_class_body(name, body, type_params, firstlineno)?; + self.ctx = prev_ctx; - // Call INTRINSIC_SUBSCRIPT_GENERIC to create Generic[*type_params] + // Back in type params scope, create .generic_base + emit!(self, Instruction::LoadNameAny(dot_type_params)); emit!( self, Instruction::CallIntrinsic1 { func: bytecode::IntrinsicFunction1::SubscriptGeneric } ); - - // Store as .generic_base in the type params scope let dot_generic_base = self.name(".generic_base"); emit!(self, Instruction::StoreLocal(dot_generic_base)); - } - - // Compile the class body into a code object - let firstlineno = self.get_source_line_number().get().to_u32(); - let code = self.compile_class_body(name, body, type_params, firstlineno)?; - self.ctx = prev_ctx; - emit!(self, Instruction::LoadBuildClass); + // Generate the class creation code (still in type params scope) + emit!(self, Instruction::LoadBuildClass); - let mut func_flags = bytecode::MakeFunctionFlags::empty(); + // Set up the class function + let mut func_flags = bytecode::MakeFunctionFlags::empty(); - // Prepare generic type parameters: - if type_params.is_some() { - // Load .type_params from the type params scope - let dot_type_params = self.name(".type_params"); + // Load .type_params for the class function emit!(self, Instruction::LoadNameAny(dot_type_params)); func_flags |= bytecode::MakeFunctionFlags::TYPE_PARAMS; - } - if self.build_closure(&code) { - func_flags |= bytecode::MakeFunctionFlags::CLOSURE; - } - - self.emit_load_const(ConstantData::Code { - code: Box::new(code), - }); - self.emit_load_const(ConstantData::Str { value: name.into() }); + if self.build_closure(&class_code) { + func_flags |= bytecode::MakeFunctionFlags::CLOSURE; + } - // Turn code object into function object: - emit!(self, Instruction::MakeFunction(func_flags)); + self.emit_load_const(ConstantData::Code { + code: Box::new(class_code), + }); + self.emit_load_const(ConstantData::Str { value: name.into() }); - self.emit_load_const(ConstantData::Str { value: name.into() }); + emit!(self, Instruction::MakeFunction(func_flags)); - // For PEP 695 classes: handle Generic base creation - if type_params.is_some() { - // Stack currently: [function, class_name] + self.emit_load_const(ConstantData::Str { value: name.into() }); - // Compile the original bases (if any) - if let Some(arguments) = arguments { - // Compile all the positional arguments (bases) + // Compile bases with .generic_base appended + let base_count = if let Some(arguments) = arguments { for arg in &arguments.args { self.compile_expression(arg)?; } - } + arguments.args.len() + } else { + 0 + }; - // Load .generic_base from the type params scope - let dot_generic_base = self.name(".generic_base"); + // Load .generic_base as the last base emit!(self, Instruction::LoadNameAny(dot_generic_base)); - // Calculate total number of positional args - let nargs = if let Some(arguments) = arguments { - 2 + arguments.args.len() as u32 + 1 // function, name, bases..., generic_base - } else { - 3 // function, name, generic_base - }; + let nargs = 2 + base_count as u32 + 1; // function, name, bases..., generic_base - // Handle keyword arguments if any + // Handle keyword arguments if let Some(arguments) = arguments && !arguments.keywords.is_empty() { - // Compile keyword arguments for keyword in &arguments.keywords { if let Some(name) = &keyword.arg { self.emit_load_const(ConstantData::Str { @@ -2288,8 +2282,58 @@ impl Compiler<'_> { } else { emit!(self, Instruction::CallFunctionPositional { nargs }); } + + // Return the created class + self.emit_return_value(); + + // Exit type params scope and get the code object + let type_params_code = self.exit_scope(); + + // Now execute the type params function + if self.build_closure(&type_params_code) { + // Should not need closure + } + self.emit_load_const(ConstantData::Code { + code: Box::new(type_params_code), + }); + self.emit_load_const(ConstantData::Str { + value: type_params_name.into(), + }); + emit!( + self, + Instruction::MakeFunction(bytecode::MakeFunctionFlags::empty()) + ); + + emit!(self, Instruction::CallFunctionPositional { nargs: 0 }); } else { - // No type params, normal compilation + // Traditional class without type params + let prev_ctx = self.ctx; + self.ctx = CompileContext { + func: FunctionContext::NoFunction, + in_class: true, + loop_data: None, + }; + + let code = self.compile_class_body(name, body, type_params, firstlineno)?; + self.ctx = prev_ctx; + + emit!(self, Instruction::LoadBuildClass); + + let mut func_flags = bytecode::MakeFunctionFlags::empty(); + + if self.build_closure(&code) { + func_flags |= bytecode::MakeFunctionFlags::CLOSURE; + } + + self.emit_load_const(ConstantData::Code { + code: Box::new(code), + }); + self.emit_load_const(ConstantData::Str { value: name.into() }); + + emit!(self, Instruction::MakeFunction(func_flags)); + + self.emit_load_const(ConstantData::Str { value: name.into() }); + let call = if let Some(arguments) = arguments { self.compile_call_inner(2, arguments)? } else { @@ -2298,13 +2342,7 @@ impl Compiler<'_> { self.compile_normal_call(call); } - // Pop the special type params symbol table - if type_params.is_some() { - self.pop_symbol_table(); - } - self.apply_decorators(decorator_list); - self.store_name(name) } From 94039198ef996fa4af3baf879f7913b5d94bf8e5 Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Sat, 12 Jul 2025 19:26:41 +0900 Subject: [PATCH 4/4] refactor compile_class_def --- Lib/test/test_descr.py | 2 - compiler/codegen/src/compile.rs | 88 ++++++++++++++------------------- 2 files changed, 36 insertions(+), 54 deletions(-) diff --git a/Lib/test/test_descr.py b/Lib/test/test_descr.py index d1c83cc337a..b0414d5b00d 100644 --- a/Lib/test/test_descr.py +++ b/Lib/test/test_descr.py @@ -5124,8 +5124,6 @@ def test_iter_keys(self): self.assertEqual(keys, ['__dict__', '__doc__', '__module__', '__weakref__', 'meth']) - # TODO: RUSTPYTHON - @unittest.expectedFailure @unittest.skipIf(hasattr(sys, 'gettrace') and sys.gettrace(), 'trace function introduces __local__') def test_iter_values(self): diff --git a/compiler/codegen/src/compile.rs b/compiler/codegen/src/compile.rs index 859ac22de73..0bfe4adb416 100644 --- a/compiler/codegen/src/compile.rs +++ b/compiler/codegen/src/compile.rs @@ -2173,24 +2173,18 @@ impl Compiler<'_> { ) -> CompileResult<()> { self.prepare_decorators(decorator_list)?; - let has_type_params = type_params.is_some(); + let is_generic = type_params.is_some(); let firstlineno = self.get_source_line_number().get().to_u32(); - if has_type_params { - // For PEP 695 classes, we need to: - // 1. Enter type params scope - // 2. Compile type params - // 3. Call compile_class_body (creates nested scope) - // 4. Generate class creation code - // 5. Exit type params scope and wrap everything in a function - - let type_params_name = format!("", name); + // Step 1: If generic, enter type params scope and compile type params + if is_generic { + let type_params_name = format!(""); self.push_output( bytecode::CodeFlags::IS_OPTIMIZED | bytecode::CodeFlags::NEW_LOCALS, 0, 0, 0, - type_params_name.clone(), + type_params_name, ); // Set private name for name mangling @@ -2200,18 +2194,25 @@ impl Compiler<'_> { self.compile_type_params(type_params.unwrap())?; let dot_type_params = self.name(".type_params"); emit!(self, Instruction::StoreLocal(dot_type_params)); + } - // Compile the class body (creates its own scope) - let prev_ctx = self.ctx; - self.ctx = CompileContext { - func: FunctionContext::NoFunction, - in_class: true, - loop_data: None, - }; - let class_code = self.compile_class_body(name, body, type_params, firstlineno)?; - self.ctx = prev_ctx; + // Step 2: Compile class body (always done, whether generic or not) + let prev_ctx = self.ctx; + self.ctx = CompileContext { + func: FunctionContext::NoFunction, + in_class: true, + loop_data: None, + }; + let class_code = self.compile_class_body(name, body, type_params, firstlineno)?; + self.ctx = prev_ctx; - // Back in type params scope, create .generic_base + // Step 3: Generate the rest of the code for the call + if is_generic { + // Still in type params scope + let dot_type_params = self.name(".type_params"); + let dot_generic_base = self.name(".generic_base"); + + // Create .generic_base emit!(self, Instruction::LoadNameAny(dot_type_params)); emit!( self, @@ -2219,16 +2220,13 @@ impl Compiler<'_> { func: bytecode::IntrinsicFunction1::SubscriptGeneric } ); - let dot_generic_base = self.name(".generic_base"); emit!(self, Instruction::StoreLocal(dot_generic_base)); - // Generate the class creation code (still in type params scope) + // Generate class creation code emit!(self, Instruction::LoadBuildClass); - // Set up the class function + // Set up the class function with type params let mut func_flags = bytecode::MakeFunctionFlags::empty(); - - // Load .type_params for the class function emit!(self, Instruction::LoadNameAny(dot_type_params)); func_flags |= bytecode::MakeFunctionFlags::TYPE_PARAMS; @@ -2240,12 +2238,10 @@ impl Compiler<'_> { code: Box::new(class_code), }); self.emit_load_const(ConstantData::Str { value: name.into() }); - emit!(self, Instruction::MakeFunction(func_flags)); - self.emit_load_const(ConstantData::Str { value: name.into() }); - // Compile bases with .generic_base appended + // Compile original bases let base_count = if let Some(arguments) = arguments { for arg in &arguments.args { self.compile_expression(arg)?; @@ -2258,7 +2254,7 @@ impl Compiler<'_> { // Load .generic_base as the last base emit!(self, Instruction::LoadNameAny(dot_generic_base)); - let nargs = 2 + base_count as u32 + 1; // function, name, bases..., generic_base + let nargs = 2 + u32::try_from(base_count).expect("too many base classes") + 1; // function, name, bases..., generic_base // Handle keyword arguments if let Some(arguments) = arguments @@ -2272,11 +2268,12 @@ impl Compiler<'_> { } self.compile_expression(&keyword.value)?; } - emit!( self, Instruction::CallFunctionKeyword { - nargs: nargs + arguments.keywords.len() as u32 + nargs: nargs + + u32::try_from(arguments.keywords.len()) + .expect("too many keyword arguments") } ); } else { @@ -2286,10 +2283,10 @@ impl Compiler<'_> { // Return the created class self.emit_return_value(); - // Exit type params scope and get the code object + // Exit type params scope and wrap in function let type_params_code = self.exit_scope(); - // Now execute the type params function + // Execute the type params function if self.build_closure(&type_params_code) { // Should not need closure } @@ -2297,41 +2294,27 @@ impl Compiler<'_> { code: Box::new(type_params_code), }); self.emit_load_const(ConstantData::Str { - value: type_params_name.into(), + value: format!("").into(), }); emit!( self, Instruction::MakeFunction(bytecode::MakeFunctionFlags::empty()) ); - emit!(self, Instruction::CallFunctionPositional { nargs: 0 }); } else { - // Traditional class without type params - let prev_ctx = self.ctx; - self.ctx = CompileContext { - func: FunctionContext::NoFunction, - in_class: true, - loop_data: None, - }; - - let code = self.compile_class_body(name, body, type_params, firstlineno)?; - self.ctx = prev_ctx; - + // Non-generic class: standard path emit!(self, Instruction::LoadBuildClass); let mut func_flags = bytecode::MakeFunctionFlags::empty(); - - if self.build_closure(&code) { + if self.build_closure(&class_code) { func_flags |= bytecode::MakeFunctionFlags::CLOSURE; } self.emit_load_const(ConstantData::Code { - code: Box::new(code), + code: Box::new(class_code), }); self.emit_load_const(ConstantData::Str { value: name.into() }); - emit!(self, Instruction::MakeFunction(func_flags)); - self.emit_load_const(ConstantData::Str { value: name.into() }); let call = if let Some(arguments) = arguments { @@ -2342,6 +2325,7 @@ impl Compiler<'_> { self.compile_normal_call(call); } + // Step 4: Apply decorators and store (common to both paths) self.apply_decorators(decorator_list); self.store_name(name) }