Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
2 changes: 0 additions & 2 deletions Lib/test/test_descr.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down
4 changes: 0 additions & 4 deletions Lib/test/test_typing.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
248 changes: 171 additions & 77 deletions compiler/codegen/src/compile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2069,66 +2069,56 @@ 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<CodeObject> {
// 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();

// 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");
Expand All @@ -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()
Expand All @@ -2159,65 +2156,167 @@ impl Compiler<'_> {
self.emit_load_const(ConstantData::None);
}

// Return the class namespace
self.emit_return_value();

let code = self.exit_scope();
self.ctx = prev_ctx;
// 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)?;

emit!(self, Instruction::LoadBuildClass);
let is_generic = type_params.is_some();
let firstlineno = self.get_source_line_number().get().to_u32();

let mut func_flags = bytecode::MakeFunctionFlags::empty();
// Step 1: If generic, enter type params scope and compile type params
if is_generic {
let type_params_name = format!("<generic parameters of {name}>");
self.push_output(
bytecode::CodeFlags::IS_OPTIMIZED | bytecode::CodeFlags::NEW_LOCALS,
0,
0,
0,
type_params_name,
);

// Prepare generic type parameters:
if type_params.is_some() {
// Load .type_params from the type params scope
// Set private name for name mangling
self.code_stack.last_mut().unwrap().private = Some(name.to_owned());

// Compile type parameters and store as .type_params
self.compile_type_params(type_params.unwrap())?;
let dot_type_params = self.name(".type_params");
emit!(self, Instruction::LoadNameAny(dot_type_params));
func_flags |= bytecode::MakeFunctionFlags::TYPE_PARAMS;
emit!(self, Instruction::StoreLocal(dot_type_params));
}

if self.build_closure(&code) {
func_flags |= bytecode::MakeFunctionFlags::CLOSURE;
}
// 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;

self.emit_load_const(ConstantData::Code {
code: Box::new(code),
});
self.emit_load_const(ConstantData::Str { value: name.into() });
// 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");

// Turn code object into function object:
emit!(self, Instruction::MakeFunction(func_flags));
// Create .generic_base
emit!(self, Instruction::LoadNameAny(dot_type_params));
emit!(
self,
Instruction::CallIntrinsic1 {
func: bytecode::IntrinsicFunction1::SubscriptGeneric
}
);
emit!(self, Instruction::StoreLocal(dot_generic_base));

self.emit_load_const(ConstantData::Str { value: name.into() });
// Generate class creation code
emit!(self, Instruction::LoadBuildClass);

// For PEP 695 classes: handle Generic base creation
if type_params.is_some() {
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);
// Set up the class function with type params
let mut func_flags = bytecode::MakeFunctionFlags::empty();
emit!(self, Instruction::LoadNameAny(dot_type_params));
func_flags |= bytecode::MakeFunctionFlags::TYPE_PARAMS;

if self.build_closure(&class_code) {
func_flags |= bytecode::MakeFunctionFlags::CLOSURE;
}

self.emit_load_const(ConstantData::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() });

// Compile original bases
let base_count = if let Some(arguments) = arguments {
for arg in &arguments.args {
self.compile_expression(arg)?;
}
arguments.args.len()
} else {
// No explicit bases, add Generic[*type_params] as the only base
// Stack currently: [function, class_name]
0
};

// Load .generic_base as the last base
emit!(self, Instruction::LoadNameAny(dot_generic_base));

// Load .type_params for creating Generic base
let dot_type_params = self.name(".type_params");
emit!(self, Instruction::LoadNameAny(dot_type_params));
let nargs = 2 + u32::try_from(base_count).expect("too many base classes") + 1; // function, name, bases..., generic_base

// Call INTRINSIC_SUBSCRIPT_GENERIC to create Generic[*type_params]
// Handle keyword arguments
if let Some(arguments) = arguments
&& !arguments.keywords.is_empty()
{
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)?;
}
emit!(
self,
Instruction::CallIntrinsic1 {
func: bytecode::IntrinsicFunction1::SubscriptGeneric
Instruction::CallFunctionKeyword {
nargs: nargs
+ u32::try_from(arguments.keywords.len())
.expect("too many keyword arguments")
}
);
} else {
emit!(self, Instruction::CallFunctionPositional { nargs });
}

// Return the created class
self.emit_return_value();

// Call __build_class__ with 3 positional args: function, class_name, Generic[T]
emit!(self, Instruction::CallFunctionPositional { nargs: 3 });
// Exit type params scope and wrap in function
let type_params_code = self.exit_scope();

// 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: format!("<generic parameters of {name}>").into(),
});
emit!(
self,
Instruction::MakeFunction(bytecode::MakeFunctionFlags::empty())
);
emit!(self, Instruction::CallFunctionPositional { nargs: 0 });
} else {
// No type params, normal compilation
// Non-generic class: standard path
emit!(self, Instruction::LoadBuildClass);

let mut func_flags = bytecode::MakeFunctionFlags::empty();
if self.build_closure(&class_code) {
func_flags |= bytecode::MakeFunctionFlags::CLOSURE;
}

self.emit_load_const(ConstantData::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 {
self.compile_call_inner(2, arguments)?
} else {
Expand All @@ -2226,13 +2325,8 @@ impl Compiler<'_> {
self.compile_normal_call(call);
}

// Pop the special type params symbol table
if type_params.is_some() {
self.pop_symbol_table();
}

// Step 4: Apply decorators and store (common to both paths)
self.apply_decorators(decorator_list);

self.store_name(name)
}

Expand Down
10 changes: 6 additions & 4 deletions vm/src/builtins/genericalias.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::<PyTuple>(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) {
Expand Down
Loading