GV

GeezerVison

The World Wide Web, restored to 800 pixels of glory.

Home|About|Gaming cabinet|Archives|How it works|Starter sites

On this site

  • Sign in
  • ActionsCI/CD and automation
  • CodespacesCloud development environments
  • All Features
  • Documentation
  • Training
  • Certifications
  • Build with Copilot
  • Customize Copilot
  • Integrations
  • Trending repos
  • GitHub Sponsors
  • Developer articles
  • Enterprises

This view

  • Original page
  • New address

GeezerVison is showing github.com — compile_type_param_bound_or_default by youknowone · Pull Request #6005 · RustPython/RustPython

compile_type_param_bound_or_default #6005

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

Sign up for GitHub

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
youknowone merged 2 commits into RustPython:main from youknowone:scope
Jul 20, 2025

Uh oh!

There was an error while loading. Please reload this page.

Merged

compile_type_param_bound_or_default #6005

Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
2 commits
Select commit Hold shift + click to select a range
a1b3520
enter_type_param_block
youknowone Jul 20, 2025
c3b56bf
Typeparams scope
youknowone Jul 12, 2025
File filter
Filter by extension

Clear filters
Conversations
Failed to load comments.
Loading

Uh oh!

There was an error while loading. Please reload this page.

Jump to
Jump to file
Failed to load files.
Loading

Uh oh!

There was an error while loading. Please reload this page.

Diff view
Diff view

There are no files selected for viewing

90 changes: 67 additions & 23 deletions 90 compiler/codegen/src/compile.rs
Show comments
View file
Open in desktop
Original file line number Diff line number Diff line change
Expand Up @@ -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<()> {
Expand All @@ -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!("<TypeVar constraint of {name}>")
} else {
format!("<TypeVar bound of {name}>")
};
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 {
Expand All @@ -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!("<TypeVar default of {name}>");
self.compile_type_param_bound_or_default(default_expr, &scope_name, false)?;
emit!(
self,
Instruction::CallIntrinsic2 {
Expand All @@ -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!("<ParamSpec default of {name}>");
self.compile_type_param_bound_or_default(default_expr, &scope_name, false)?;
emit!(
self,
Instruction::CallIntrinsic2 {
Expand All @@ -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!("<TypeVarTuple default of {name}>");
self.compile_type_param_bound_or_default(default_expr, &scope_name, true)?;
emit!(
self,
Instruction::CallIntrinsic2 {
Expand Down
101 changes: 85 additions & 16 deletions 101 compiler/codegen/src/symboltable.rs
Show comments
View file
Open in desktop
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -68,6 +71,7 @@ impl SymbolTable {
varnames: Vec::new(),
needs_class_closure: false,
needs_classdict: false,
can_see_class_scope: false,
}
}

Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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!("<generic parameters of {}>", 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(
Expand All @@ -774,11 +801,10 @@ impl SymbolTableBuilder<'_> {
range,
}) => {
if let Some(type_params) = type_params {
self.enter_scope(
self.enter_type_param_block(
&format!("<generic parameters of {}>", name.as_str()),
CompilerScope::TypeParams,
self.line_index_start(type_params.range),
);
)?;
self.scan_type_params(type_params)?;
}
self.enter_scope(
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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)?;
Expand All @@ -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!("<TypeVar constraint of {name}>")
} else {
format!("<TypeVar bound of {name}>")
};
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!("<TypeVar default of {name}>");
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!("<ParamSpec default of {name}>");
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!("<TypeVarTuple default of {name}>");
self.scan_type_param_bound_or_default(default_value, &scope_name)?;
}
}
}
}
Expand Down
Add this suggestion to a batch that can be applied as a single commit. This suggestion is invalid because no changes were made to the code. Suggestions cannot be applied while the pull request is closed. Suggestions cannot be applied while viewing a subset of changes. Only one suggestion per line can be applied in a batch. Add this suggestion to a batch that can be applied as a single commit. Applying suggestions on deleted lines is not supported. You must change the existing code in this line in order to create a valid suggestion. Outdated suggestions cannot be applied. This suggestion has been applied or marked resolved. Suggestions cannot be applied from pending reviews. Suggestions cannot be applied on multi-line comments. Suggestions cannot be applied while the pull request is queued to merge. Suggestion cannot be applied right now. Please check back later.
You are visitor 01402520 — GeezerVison does not store the pages you look up.
Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required.