diff --git a/compiler/codegen/src/compile.rs b/compiler/codegen/src/compile.rs index 59c7a28932..f9d6f64988 100644 --- a/compiler/codegen/src/compile.rs +++ b/compiler/codegen/src/compile.rs @@ -1652,6 +1652,49 @@ impl Compiler<'_> { } } + /// Compile type parameter bound or default in a separate scope and return closure + fn compile_type_param_bound_or_default( + &mut self, + expr: &Expr, + name: &str, + allow_starred: bool, + ) -> CompileResult<()> { + // Push the next symbol table onto the stack + self.push_symbol_table(); + + // Get the current symbol table + let key = self.symbol_table_stack.len() - 1; + let lineno = expr.range().start().to_u32(); + + // Enter scope with the type parameter name + self.enter_scope(name, CompilerScope::TypeParams, key, lineno)?; + + // Compile the expression + if allow_starred && matches!(expr, Expr::Starred(_)) { + if let Expr::Starred(starred) = expr { + self.compile_expression(&starred.value)?; + emit!(self, Instruction::UnpackSequence { size: 1 }); + } + } else { + self.compile_expression(expr)?; + } + + // Return value + emit!(self, Instruction::ReturnValue); + + // Exit scope and create closure + let code = self.exit_scope(); + // Note: exit_scope already calls pop_symbol_table, so we don't need to call it again + + // Create type params function with closure + self.make_closure(code, bytecode::MakeFunctionFlags::empty())?; + + // Call the function immediately + emit!(self, Instruction::CallFunctionPositional { nargs: 0 }); + + Ok(()) + } + /// Store each type parameter so it is accessible to the current scope, and leave a tuple of /// all the type parameters on the stack. fn compile_type_params(&mut self, type_params: &TypeParams) -> CompileResult<()> { @@ -1664,21 +1707,25 @@ impl Compiler<'_> { default, .. }) => { + self.emit_load_const(ConstantData::Str { + value: name.as_str().into(), + }); + if let Some(expr) = &bound { - self.compile_expression(expr)?; - self.emit_load_const(ConstantData::Str { - value: name.as_str().into(), - }); - emit!( - self, - Instruction::CallIntrinsic2 { - func: bytecode::IntrinsicFunction2::TypeVarWithBound - } - ); + let scope_name = if expr.is_tuple_expr() { + format!("") + } else { + format!("") + }; + self.compile_type_param_bound_or_default(expr, &scope_name, false)?; + + let intrinsic = if expr.is_tuple_expr() { + bytecode::IntrinsicFunction2::TypeVarWithConstraint + } else { + bytecode::IntrinsicFunction2::TypeVarWithBound + }; + emit!(self, Instruction::CallIntrinsic2 { func: intrinsic }); } else { - self.emit_load_const(ConstantData::Str { - value: name.as_str().into(), - }); emit!( self, Instruction::CallIntrinsic1 { @@ -1689,9 +1736,8 @@ impl Compiler<'_> { // Handle default value if present (PEP 695) if let Some(default_expr) = default { - // Compile the default expression - self.compile_expression(default_expr)?; - + let scope_name = format!(""); + self.compile_type_param_bound_or_default(default_expr, &scope_name, false)?; emit!( self, Instruction::CallIntrinsic2 { @@ -1716,9 +1762,8 @@ impl Compiler<'_> { // Handle default value if present (PEP 695) if let Some(default_expr) = default { - // Compile the default expression - self.compile_expression(default_expr)?; - + let scope_name = format!(""); + self.compile_type_param_bound_or_default(default_expr, &scope_name, false)?; emit!( self, Instruction::CallIntrinsic2 { @@ -1743,10 +1788,9 @@ impl Compiler<'_> { // Handle default value if present (PEP 695) if let Some(default_expr) = default { - // Compile the default expression - self.compile_expression(default_expr)?; - - // Handle starred expression (*default) + // TypeVarTuple allows starred expressions + let scope_name = format!(""); + self.compile_type_param_bound_or_default(default_expr, &scope_name, true)?; emit!( self, Instruction::CallIntrinsic2 { diff --git a/compiler/codegen/src/symboltable.rs b/compiler/codegen/src/symboltable.rs index 8ab697f248..fdfc83e797 100644 --- a/compiler/codegen/src/symboltable.rs +++ b/compiler/codegen/src/symboltable.rs @@ -54,6 +54,9 @@ pub struct SymbolTable { /// Whether this class scope needs an implicit __classdict__ cell pub needs_classdict: bool, + + /// Whether this type param scope can see the parent class scope + pub can_see_class_scope: bool, } impl SymbolTable { @@ -68,6 +71,7 @@ impl SymbolTable { varnames: Vec::new(), needs_class_closure: false, needs_classdict: false, + can_see_class_scope: false, } } @@ -665,6 +669,31 @@ impl SymbolTableBuilder<'_> { self.current_varnames.clear(); } + fn enter_type_param_block(&mut self, name: &str, line_number: u32) -> SymbolTableResult { + // Check if we're in a class scope + let in_class = self + .tables + .last() + .is_some_and(|t| t.typ == CompilerScope::Class); + + self.enter_scope(name, CompilerScope::TypeParams, line_number); + + // If we're in a class, mark that this type param scope can see the class scope + if let Some(table) = self.tables.last_mut() { + table.can_see_class_scope = in_class; + + // Add __classdict__ as a USE symbol in type param scope if in class + if in_class { + self.register_name("__classdict__", SymbolUsage::Used, TextRange::default())?; + } + } + + // Register .type_params as a SET symbol (it will be converted to cell variable later) + self.register_name(".type_params", SymbolUsage::Assigned, TextRange::default())?; + + Ok(()) + } + /// Pop symbol table and add to sub table of parent table. fn leave_scope(&mut self) { let mut table = self.tables.pop().unwrap(); @@ -746,12 +775,10 @@ impl SymbolTableBuilder<'_> { self.scan_annotation(expression)?; } if let Some(type_params) = type_params { - self.enter_scope( + self.enter_type_param_block( &format!("", name.as_str()), - CompilerScope::TypeParams, - // FIXME: line no - self.line_index_start(*range), - ); + self.line_index_start(type_params.range), + )?; self.scan_type_params(type_params)?; } self.enter_scope_with_parameters( @@ -774,11 +801,10 @@ impl SymbolTableBuilder<'_> { range, }) => { if let Some(type_params) = type_params { - self.enter_scope( + self.enter_type_param_block( &format!("", name.as_str()), - CompilerScope::TypeParams, self.line_index_start(type_params.range), - ); + )?; self.scan_type_params(type_params)?; } self.enter_scope( @@ -965,12 +991,10 @@ impl SymbolTableBuilder<'_> { .. }) => { if let Some(type_params) = type_params { - self.enter_scope( - // &name.to_string(), + self.enter_type_param_block( "TypeAlias", - CompilerScope::TypeParams, self.line_index_start(type_params.range), - ); + )?; self.scan_type_params(type_params)?; self.scan_expression(value, ExpressionContext::Load)?; self.leave_scope(); @@ -1344,6 +1368,26 @@ impl SymbolTableBuilder<'_> { Ok(()) } + /// Scan type parameter bound or default in a separate scope + // = symtable_visit_type_param_bound_or_default + fn scan_type_param_bound_or_default(&mut self, expr: &Expr, name: &str) -> SymbolTableResult { + // Enter a new TypeParams scope for the bound/default expression + // This allows the expression to access outer scope symbols + let line_number = self.line_index_start(expr.range()); + self.enter_scope(name, CompilerScope::TypeParams, line_number); + + // Note: In CPython, can_see_class_scope is preserved in the new scope + // In RustPython, this is handled through the scope hierarchy + + // Scan the expression in this new scope + let result = self.scan_expression(expr, ExpressionContext::Load); + + // Exit the scope + self.leave_scope(); + + result + } + fn scan_type_params(&mut self, type_params: &TypeParams) -> SymbolTableResult { // Register .type_params as a type parameter (automatically becomes cell variable) self.register_name(".type_params", SymbolUsage::TypeParam, type_params.range)?; @@ -1355,26 +1399,51 @@ impl SymbolTableBuilder<'_> { name, bound, range: type_var_range, - .. + default, }) => { self.register_name(name.as_str(), SymbolUsage::TypeParam, *type_var_range)?; + + // Process bound in a separate scope if let Some(binding) = bound { - self.scan_expression(binding, ExpressionContext::Load)?; + let scope_name = if binding.is_tuple_expr() { + format!("") + } else { + format!("") + }; + self.scan_type_param_bound_or_default(binding, &scope_name)?; + } + + // Process default in a separate scope + if let Some(default_value) = default { + let scope_name = format!(""); + self.scan_type_param_bound_or_default(default_value, &scope_name)?; } } TypeParam::ParamSpec(TypeParamParamSpec { name, range: param_spec_range, - .. + default, }) => { self.register_name(name, SymbolUsage::TypeParam, *param_spec_range)?; + + // Process default in a separate scope + if let Some(default_value) = default { + let scope_name = format!(""); + self.scan_type_param_bound_or_default(default_value, &scope_name)?; + } } TypeParam::TypeVarTuple(TypeParamTypeVarTuple { name, range: type_var_tuple_range, - .. + default, }) => { self.register_name(name, SymbolUsage::TypeParam, *type_var_tuple_range)?; + + // Process default in a separate scope + if let Some(default_value) = default { + let scope_name = format!(""); + self.scan_type_param_bound_or_default(default_value, &scope_name)?; + } } } }