mirror of
https://github.com/RustPython/RustPython.git
synced 2026-10-29 11:14:38 +08:00
compiler parity for typealias (#7645)
This commit is contained in:
1 parent
f8862e4eed
commit
b929a50647
4 files changed
+205
-141
No files matched your search
Vendored
-1
@@ -1158,7 +1158,6 @@ class TestSpecifics(unittest.TestCase):
|
||||
self.assertIn('LOAD_ATTR', instructions)
|
||||
self.assertIn('CALL', instructions)
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: 'LOAD_SMALL_INT' not found in ['RESUME', 'LOAD_CONST', 'RETURN_VALUE']
|
||||
def test_folding_type_param(self):
|
||||
get_code_fn_cls = lambda x: x.co_consts[0].co_consts[2]
|
||||
get_code_type_alias = lambda x: x.co_consts[0].co_consts[3]
|
||||
|
||||
+130
-104
@@ -1249,12 +1249,35 @@ impl Compiler {
|
||||
|
||||
// Build freevars using dictbytype (FREE scope, offset by cellvars size)
|
||||
let mut freevar_cache = IndexSet::default();
|
||||
let annotation_free_names: IndexSet<String> = ste
|
||||
.annotation_block
|
||||
.as_ref()
|
||||
.map(|annotation| {
|
||||
annotation
|
||||
.symbols
|
||||
.iter()
|
||||
.filter(|(_, s)| {
|
||||
s.scope == SymbolScope::Free || s.flags.contains(SymbolFlags::FREE_CLASS)
|
||||
})
|
||||
.map(|(name, _)| name.clone())
|
||||
.collect()
|
||||
})
|
||||
.unwrap_or_default();
|
||||
let mut free_names: Vec<_> = ste
|
||||
.symbols
|
||||
.iter()
|
||||
.filter(|(_, s)| {
|
||||
s.scope == SymbolScope::Free || s.flags.contains(SymbolFlags::FREE_CLASS)
|
||||
})
|
||||
.filter(|(name, symbol)| {
|
||||
if !matches!(
|
||||
scope_type,
|
||||
CompilerScope::Function | CompilerScope::AsyncFunction | CompilerScope::Lambda
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
!(annotation_free_names.contains(*name) && symbol.flags.is_empty())
|
||||
})
|
||||
.map(|(name, _)| name.clone())
|
||||
.collect();
|
||||
free_names.sort();
|
||||
@@ -1866,9 +1889,18 @@ impl Compiler {
|
||||
let mut parent_idx = stack_size - 2;
|
||||
let mut parent = &self.code_stack[parent_idx];
|
||||
|
||||
// If parent is ast::TypeParams scope, look at grandparent
|
||||
// Check if parent is a type params scope by name pattern
|
||||
if parent.metadata.name.starts_with("<generic parameters of ") {
|
||||
let parent_scope = self
|
||||
.symbol_table_stack
|
||||
.get(parent_idx)
|
||||
.map(|table| table.typ);
|
||||
|
||||
// CPython skips both generic-parameter scopes and annotation scopes
|
||||
// when building qualnames for the contained function/class code object.
|
||||
if matches!(
|
||||
parent_scope,
|
||||
Some(CompilerScope::TypeParams | CompilerScope::Annotation)
|
||||
) || parent.metadata.name.starts_with("<generic parameters of ")
|
||||
{
|
||||
if stack_size == 2 {
|
||||
// If we're immediately within the module, qualname is just the name
|
||||
return current_obj_name;
|
||||
@@ -2294,7 +2326,13 @@ impl Compiler {
|
||||
NameOp::Name
|
||||
}
|
||||
}
|
||||
SymbolScope::GlobalExplicit => NameOp::Global,
|
||||
SymbolScope::GlobalExplicit => {
|
||||
if can_see_class_scope {
|
||||
NameOp::DictOrGlobals
|
||||
} else {
|
||||
NameOp::Global
|
||||
}
|
||||
}
|
||||
SymbolScope::Unknown => {
|
||||
if module_global_from_nested_scope {
|
||||
NameOp::Global
|
||||
@@ -2797,24 +2835,14 @@ impl Compiler {
|
||||
value,
|
||||
..
|
||||
}) => {
|
||||
// let name_string = name.to_string();
|
||||
let Some(name) = name.as_name_expr() else {
|
||||
// FIXME: is error here?
|
||||
return Err(self.error(CodegenErrorType::SyntaxError(
|
||||
"type alias expect name".to_owned(),
|
||||
)));
|
||||
};
|
||||
let name_string = name.id.to_string();
|
||||
|
||||
// For PEP 695 syntax, we need to compile type_params first
|
||||
// so that they're available when compiling the value expression
|
||||
// Push name first
|
||||
self.emit_load_const(ConstantData::Str {
|
||||
value: name_string.clone().into(),
|
||||
});
|
||||
|
||||
if let Some(type_params) = type_params {
|
||||
// Outer scope for TypeParams
|
||||
self.push_symbol_table()?;
|
||||
let key = self.symbol_table_stack.len() - 1;
|
||||
let lineno = self.get_source_line_number().get().to_u32();
|
||||
@@ -2830,34 +2858,18 @@ impl Compiler {
|
||||
in_async_scope: false,
|
||||
};
|
||||
|
||||
// Compile type params inside the scope
|
||||
self.emit_load_const(ConstantData::Str {
|
||||
value: name_string.clone().into(),
|
||||
});
|
||||
self.compile_type_params(type_params)?;
|
||||
// Stack: [type_params_tuple]
|
||||
|
||||
// Inner closure for lazy value evaluation
|
||||
self.push_symbol_table()?;
|
||||
let inner_key = self.symbol_table_stack.len() - 1;
|
||||
self.enter_scope("TypeAlias", CompilerScope::TypeParams, inner_key, lineno)?;
|
||||
// Evaluator takes a positional-only format parameter
|
||||
self.current_code_info().metadata.argcount = 1;
|
||||
self.current_code_info().metadata.posonlyargcount = 1;
|
||||
self.current_code_info()
|
||||
.metadata
|
||||
.varnames
|
||||
.insert("format".to_owned());
|
||||
self.emit_format_validation()?;
|
||||
self.compile_expression(value)?;
|
||||
emit!(self, Instruction::ReturnValue);
|
||||
let value_code = self.exit_scope();
|
||||
self.make_closure(value_code, bytecode::MakeFunctionFlags::new())?;
|
||||
// Stack: [type_params_tuple, value_closure]
|
||||
|
||||
// Swap so unpack_sequence reverse gives correct order
|
||||
emit!(self, Instruction::Swap { i: 2 });
|
||||
// Stack: [value_closure, type_params_tuple]
|
||||
|
||||
// Build tuple and return from TypeParams scope
|
||||
emit!(self, Instruction::BuildTuple { count: 2 });
|
||||
self.compile_typealias_value_closure(&name_string, value)?;
|
||||
emit!(self, Instruction::BuildTuple { count: 3 });
|
||||
emit!(
|
||||
self,
|
||||
Instruction::CallIntrinsic1 {
|
||||
func: bytecode::IntrinsicFunction1::TypeAlias
|
||||
}
|
||||
);
|
||||
emit!(self, Instruction::ReturnValue);
|
||||
|
||||
let code = self.exit_scope();
|
||||
@@ -2865,54 +2877,21 @@ impl Compiler {
|
||||
self.make_closure(code, bytecode::MakeFunctionFlags::new())?;
|
||||
emit!(self, Instruction::PushNull);
|
||||
emit!(self, Instruction::Call { argc: 0 });
|
||||
|
||||
// Unpack: (value_closure, type_params_tuple)
|
||||
// UnpackSequence reverses → stack: [name, type_params_tuple, value_closure]
|
||||
emit!(self, Instruction::UnpackSequence { count: 2 });
|
||||
} else {
|
||||
// Push None for type_params
|
||||
self.emit_load_const(ConstantData::Str {
|
||||
value: name_string.clone().into(),
|
||||
});
|
||||
self.emit_load_const(ConstantData::None);
|
||||
// Stack: [name, None]
|
||||
|
||||
// Create a closure for lazy evaluation of the value
|
||||
self.push_symbol_table()?;
|
||||
let key = self.symbol_table_stack.len() - 1;
|
||||
let lineno = self.get_source_line_number().get().to_u32();
|
||||
self.enter_scope("TypeAlias", CompilerScope::TypeParams, key, lineno)?;
|
||||
// Evaluator takes a positional-only format parameter
|
||||
self.current_code_info().metadata.argcount = 1;
|
||||
self.current_code_info().metadata.posonlyargcount = 1;
|
||||
self.current_code_info()
|
||||
.metadata
|
||||
.varnames
|
||||
.insert("format".to_owned());
|
||||
self.emit_format_validation()?;
|
||||
|
||||
let prev_ctx = self.ctx;
|
||||
self.ctx = CompileContext {
|
||||
loop_data: None,
|
||||
in_class: prev_ctx.in_class,
|
||||
func: FunctionContext::Function,
|
||||
in_async_scope: false,
|
||||
};
|
||||
|
||||
self.compile_expression(value)?;
|
||||
emit!(self, Instruction::ReturnValue);
|
||||
|
||||
let code = self.exit_scope();
|
||||
self.ctx = prev_ctx;
|
||||
self.make_closure(code, bytecode::MakeFunctionFlags::new())?;
|
||||
// Stack: [name, None, closure]
|
||||
self.compile_typealias_value_closure(&name_string, value)?;
|
||||
emit!(self, Instruction::BuildTuple { count: 3 });
|
||||
emit!(
|
||||
self,
|
||||
Instruction::CallIntrinsic1 {
|
||||
func: bytecode::IntrinsicFunction1::TypeAlias
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// Build tuple of 3 elements and call intrinsic
|
||||
emit!(self, Instruction::BuildTuple { count: 3 });
|
||||
emit!(
|
||||
self,
|
||||
Instruction::CallIntrinsic1 {
|
||||
func: bytecode::IntrinsicFunction1::TypeAlias
|
||||
}
|
||||
);
|
||||
self.store_name(&name_string)?;
|
||||
}
|
||||
ast::Stmt::IpyEscapeCommand(_) => todo!(),
|
||||
@@ -3015,6 +2994,10 @@ impl Compiler {
|
||||
name: &str,
|
||||
allow_starred: bool,
|
||||
) -> CompileResult<()> {
|
||||
self.emit_load_const(ConstantData::Tuple {
|
||||
elements: vec![ConstantData::Integer { value: 1.into() }],
|
||||
});
|
||||
|
||||
// Push the next symbol table onto the stack
|
||||
self.push_symbol_table()?;
|
||||
|
||||
@@ -3023,11 +3006,8 @@ impl Compiler {
|
||||
let lineno = self.get_source_line_number().get().to_u32();
|
||||
|
||||
// Enter scope with the type parameter name
|
||||
self.enter_scope(name, CompilerScope::TypeParams, key, lineno)?;
|
||||
self.enter_scope(name, CompilerScope::Annotation, key, lineno)?;
|
||||
|
||||
// Evaluator takes a positional-only format parameter
|
||||
self.current_code_info().metadata.argcount = 1;
|
||||
self.current_code_info().metadata.posonlyargcount = 1;
|
||||
self.current_code_info()
|
||||
.metadata
|
||||
.varnames
|
||||
@@ -3061,8 +3041,50 @@ impl Compiler {
|
||||
let code = self.exit_scope();
|
||||
self.ctx = prev_ctx;
|
||||
|
||||
// Create closure for lazy evaluation
|
||||
self.make_closure(code, bytecode::MakeFunctionFlags::new())?;
|
||||
self.make_closure(
|
||||
code,
|
||||
bytecode::MakeFunctionFlags::from([bytecode::MakeFunctionFlag::Defaults]),
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn compile_typealias_value_closure(
|
||||
&mut self,
|
||||
alias_name: &str,
|
||||
value: &ast::Expr,
|
||||
) -> CompileResult<()> {
|
||||
self.emit_load_const(ConstantData::Tuple {
|
||||
elements: vec![ConstantData::Integer { value: 1.into() }],
|
||||
});
|
||||
|
||||
self.push_symbol_table()?;
|
||||
let key = self.symbol_table_stack.len() - 1;
|
||||
let lineno = self.get_source_line_number().get().to_u32();
|
||||
self.enter_scope(alias_name, CompilerScope::Annotation, key, lineno)?;
|
||||
self.current_code_info()
|
||||
.metadata
|
||||
.varnames
|
||||
.insert(".format".to_owned());
|
||||
self.emit_format_validation()?;
|
||||
|
||||
let prev_ctx = self.ctx;
|
||||
self.ctx = CompileContext {
|
||||
loop_data: None,
|
||||
in_class: prev_ctx.in_class,
|
||||
func: FunctionContext::Function,
|
||||
in_async_scope: false,
|
||||
};
|
||||
|
||||
self.compile_expression(value)?;
|
||||
emit!(self, Instruction::ReturnValue);
|
||||
|
||||
let code = self.exit_scope();
|
||||
self.ctx = prev_ctx;
|
||||
self.make_closure(
|
||||
code,
|
||||
bytecode::MakeFunctionFlags::from([bytecode::MakeFunctionFlag::Defaults]),
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -3084,12 +3106,7 @@ impl Compiler {
|
||||
});
|
||||
|
||||
if let Some(expr) = &bound {
|
||||
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)?;
|
||||
self.compile_type_param_bound_or_default(expr, name.as_str(), false)?;
|
||||
|
||||
let intrinsic = if expr.is_tuple_expr() {
|
||||
bytecode::IntrinsicFunction2::TypeVarWithConstraint
|
||||
@@ -3107,8 +3124,11 @@ impl Compiler {
|
||||
}
|
||||
|
||||
if let Some(default_expr) = default {
|
||||
let scope_name = format!("<TypeVar default of {name}>");
|
||||
self.compile_type_param_bound_or_default(default_expr, &scope_name, false)?;
|
||||
self.compile_type_param_bound_or_default(
|
||||
default_expr,
|
||||
name.as_str(),
|
||||
false,
|
||||
)?;
|
||||
emit!(
|
||||
self,
|
||||
Instruction::CallIntrinsic2 {
|
||||
@@ -3132,8 +3152,11 @@ impl Compiler {
|
||||
);
|
||||
|
||||
if let Some(default_expr) = default {
|
||||
let scope_name = format!("<ParamSpec default of {name}>");
|
||||
self.compile_type_param_bound_or_default(default_expr, &scope_name, false)?;
|
||||
self.compile_type_param_bound_or_default(
|
||||
default_expr,
|
||||
name.as_str(),
|
||||
false,
|
||||
)?;
|
||||
emit!(
|
||||
self,
|
||||
Instruction::CallIntrinsic2 {
|
||||
@@ -3160,8 +3183,11 @@ impl Compiler {
|
||||
|
||||
if let Some(default_expr) = 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)?;
|
||||
self.compile_type_param_bound_or_default(
|
||||
default_expr,
|
||||
name.as_str(),
|
||||
true,
|
||||
)?;
|
||||
emit!(
|
||||
self,
|
||||
Instruction::CallIntrinsic2 {
|
||||
@@ -4560,7 +4586,7 @@ impl Compiler {
|
||||
self.current_code_info()
|
||||
.metadata
|
||||
.varnames
|
||||
.insert("format".to_owned());
|
||||
.insert(".format".to_owned());
|
||||
|
||||
// Emit format validation: if format > VALUE_WITH_FAKE_GLOBALS: raise NotImplementedError
|
||||
self.emit_format_validation()?;
|
||||
|
||||
@@ -65,6 +65,11 @@ pub struct SymbolTable {
|
||||
/// Annotations are compiled as a separate `__annotate__` function
|
||||
pub annotation_block: Option<Box<SymbolTable>>,
|
||||
|
||||
/// True only for deferred function/class/module annotation scopes that
|
||||
/// should resolve outer names as if they were siblings of the owning
|
||||
/// function body, matching CPython's PEP 649 lookup rules.
|
||||
pub skip_enclosing_function_scope: bool,
|
||||
|
||||
/// PEP 649: Whether this scope has conditional annotations
|
||||
/// (annotations inside if/for/while/etc. blocks or at module level)
|
||||
pub has_conditional_annotations: bool,
|
||||
@@ -95,6 +100,7 @@ impl SymbolTable {
|
||||
is_generator: false,
|
||||
comp_inlined: false,
|
||||
annotation_block: None,
|
||||
skip_enclosing_function_scope: false,
|
||||
has_conditional_annotations: false,
|
||||
future_annotations: false,
|
||||
mangled_names: None,
|
||||
@@ -458,7 +464,7 @@ use stack::StackStack;
|
||||
#[derive(Default)]
|
||||
#[repr(transparent)]
|
||||
struct SymbolTableAnalyzer {
|
||||
tables: StackStack<(SymbolMap, CompilerScope)>,
|
||||
tables: StackStack<(SymbolMap, CompilerScope, bool)>,
|
||||
}
|
||||
|
||||
impl SymbolTableAnalyzer {
|
||||
@@ -500,7 +506,11 @@ impl SymbolTableAnalyzer {
|
||||
let mut child_frees: Vec<(IndexSet<String>, bool)> = Vec::new();
|
||||
let mut annotation_free: Option<IndexSet<String>> = None;
|
||||
|
||||
let mut info = (symbols, symbol_table.typ);
|
||||
let mut info = (
|
||||
symbols,
|
||||
symbol_table.typ,
|
||||
symbol_table.skip_enclosing_function_scope,
|
||||
);
|
||||
self.tables.with_append(&mut info, |list| {
|
||||
let inner_scope = unsafe { &mut *(list as *mut _ as *mut Self) };
|
||||
for sub_table in sub_tables.iter_mut() {
|
||||
@@ -577,7 +587,13 @@ impl SymbolTableAnalyzer {
|
||||
|
||||
// Analyze symbols in current scope
|
||||
for symbol in symbol_table.symbols.values_mut() {
|
||||
self.analyze_symbol(symbol, symbol_table.typ, sub_tables, class_entry)?;
|
||||
self.analyze_symbol(
|
||||
symbol,
|
||||
symbol_table.typ,
|
||||
symbol_table.skip_enclosing_function_scope,
|
||||
sub_tables,
|
||||
class_entry,
|
||||
)?;
|
||||
|
||||
// Collect free variables from this scope
|
||||
if symbol.scope == SymbolScope::Free || symbol.flags.contains(SymbolFlags::FREE_CLASS) {
|
||||
@@ -618,6 +634,7 @@ impl SymbolTableAnalyzer {
|
||||
&mut self,
|
||||
symbol: &mut Symbol,
|
||||
st_typ: CompilerScope,
|
||||
skip_enclosing_function_scope: bool,
|
||||
sub_tables: &[SymbolTable],
|
||||
class_entry: Option<&SymbolMap>,
|
||||
) -> SymbolTableResult {
|
||||
@@ -638,8 +655,11 @@ impl SymbolTableAnalyzer {
|
||||
let scope_depth = self.tables.as_ref().len();
|
||||
// check if the name is already defined in any outer scope
|
||||
if scope_depth < 2
|
||||
|| self.found_in_outer_scope(&symbol.name, st_typ)
|
||||
!= Some(SymbolScope::Free)
|
||||
|| self.found_in_outer_scope(
|
||||
&symbol.name,
|
||||
st_typ,
|
||||
skip_enclosing_function_scope,
|
||||
) != Some(SymbolScope::Free)
|
||||
{
|
||||
return Err(SymbolTableError {
|
||||
error: format!("no binding for nonlocal '{}' found", symbol.name),
|
||||
@@ -649,7 +669,7 @@ impl SymbolTableAnalyzer {
|
||||
}
|
||||
// Check if the nonlocal binding refers to a type parameter
|
||||
if symbol.flags.contains(SymbolFlags::NONLOCAL) {
|
||||
for (symbols, _typ) in self.tables.iter().rev() {
|
||||
for (symbols, _typ, _skip) in self.tables.iter().rev() {
|
||||
if let Some(sym) = symbols.get(&symbol.name) {
|
||||
if sym.flags.contains(SymbolFlags::TYPE_PARAM) {
|
||||
return Err(SymbolTableError {
|
||||
@@ -699,7 +719,11 @@ impl SymbolTableAnalyzer {
|
||||
self.found_in_inner_scope(sub_tables, &symbol.name, st_typ)
|
||||
.unwrap_or(SymbolScope::Local)
|
||||
}
|
||||
} else if let Some(scope) = self.found_in_outer_scope(&symbol.name, st_typ) {
|
||||
} else if let Some(scope) = self.found_in_outer_scope(
|
||||
&symbol.name,
|
||||
st_typ,
|
||||
skip_enclosing_function_scope,
|
||||
) {
|
||||
// If found in enclosing scope (function/TypeParams), use that
|
||||
scope
|
||||
} else if let Some(class_symbols) = class_entry
|
||||
@@ -724,19 +748,26 @@ impl SymbolTableAnalyzer {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn found_in_outer_scope(&mut self, name: &str, st_typ: CompilerScope) -> Option<SymbolScope> {
|
||||
fn found_in_outer_scope(
|
||||
&mut self,
|
||||
name: &str,
|
||||
st_typ: CompilerScope,
|
||||
skip_enclosing_function_scope: bool,
|
||||
) -> Option<SymbolScope> {
|
||||
let mut decl_depth = None;
|
||||
for (i, (symbols, typ)) in self.tables.iter().rev().enumerate() {
|
||||
for (i, (symbols, typ, _skip)) in self.tables.iter().rev().enumerate() {
|
||||
if matches!(typ, CompilerScope::Module)
|
||||
|| matches!(typ, CompilerScope::Class if name != "__class__" && name != "__classdict__" && name != "__conditional_annotations__")
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// PEP 649: Annotation scope is conceptually a sibling of the function,
|
||||
// not a child. Skip the immediate parent function scope when looking
|
||||
// for outer variables from annotation scope.
|
||||
// Real PEP 649 annotation blocks resolve names as siblings of the
|
||||
// owning function body. Other annotation-like scopes such as type
|
||||
// aliases and TypeVar bound/default evaluators keep normal lexical
|
||||
// lookup and therefore leave this path disabled.
|
||||
if st_typ == CompilerScope::Annotation
|
||||
&& skip_enclosing_function_scope
|
||||
&& i == 0
|
||||
&& matches!(
|
||||
typ,
|
||||
@@ -785,7 +816,7 @@ impl SymbolTableAnalyzer {
|
||||
let is_class_implicit =
|
||||
name == "__classdict__" || name == "__conditional_annotations__";
|
||||
|
||||
for (table, typ) in self.tables.iter_mut().rev().take(decl_depth) {
|
||||
for (table, typ, _skip) in self.tables.iter_mut().rev().take(decl_depth) {
|
||||
if let CompilerScope::Class = typ {
|
||||
if let Some(free_class) = table.get_mut(name) {
|
||||
free_class.flags.insert(SymbolFlags::FREE_CLASS)
|
||||
@@ -1126,6 +1157,7 @@ impl SymbolTableBuilder {
|
||||
);
|
||||
// Annotation scope in class can see class scope
|
||||
annotation_table.can_see_class_scope = can_see_class_scope;
|
||||
annotation_table.skip_enclosing_function_scope = true;
|
||||
// Add 'format' parameter
|
||||
annotation_table.varnames.push("format".to_owned());
|
||||
current.annotation_block = Some(Box::new(annotation_table));
|
||||
@@ -1734,6 +1766,17 @@ impl SymbolTableBuilder {
|
||||
type_params,
|
||||
..
|
||||
}) => {
|
||||
let Some(name_expr) = name.as_name_expr() else {
|
||||
return Err(SymbolTableError {
|
||||
error: "type alias expect name".to_owned(),
|
||||
location: Some(
|
||||
self.source_file
|
||||
.to_source_code()
|
||||
.source_location(name.range().start(), PositionEncoding::Utf8),
|
||||
),
|
||||
});
|
||||
};
|
||||
let alias_name = name_expr.id.to_string();
|
||||
let was_in_type_alias = self.in_type_alias;
|
||||
self.in_type_alias = true;
|
||||
// Check before entering any sub-scopes
|
||||
@@ -1744,7 +1787,7 @@ impl SymbolTableBuilder {
|
||||
let is_generic = type_params.is_some();
|
||||
if let Some(type_params) = type_params {
|
||||
self.enter_type_param_block(
|
||||
"TypeAlias",
|
||||
&format!("<generic parameters of {alias_name}>"),
|
||||
self.line_index_start(type_params.range),
|
||||
false,
|
||||
)?;
|
||||
@@ -1752,12 +1795,12 @@ impl SymbolTableBuilder {
|
||||
}
|
||||
// Value scope for lazy evaluation
|
||||
self.enter_scope(
|
||||
"TypeAlias",
|
||||
CompilerScope::TypeParams,
|
||||
&alias_name,
|
||||
CompilerScope::Annotation,
|
||||
self.line_index_start(value.range()),
|
||||
);
|
||||
// Evaluator takes a format parameter
|
||||
self.register_name("format", SymbolUsage::Parameter, TextRange::default())?;
|
||||
self.register_name(".format", SymbolUsage::Parameter, TextRange::default())?;
|
||||
if in_class {
|
||||
if let Some(table) = self.tables.last_mut() {
|
||||
table.can_see_class_scope = true;
|
||||
@@ -2272,13 +2315,12 @@ impl SymbolTableBuilder {
|
||||
scope_name: &str,
|
||||
scope_info: &'static str,
|
||||
) -> SymbolTableResult {
|
||||
// Enter a new TypeParams scope for the bound/default expression
|
||||
// This allows the expression to access outer scope symbols
|
||||
// Bounds/defaults are compiled as annotation scopes in CPython.
|
||||
let in_class = self.tables.last().is_some_and(|t| t.can_see_class_scope);
|
||||
let line_number = self.line_index_start(expr.range());
|
||||
self.enter_scope(scope_name, CompilerScope::TypeParams, line_number);
|
||||
self.enter_scope(scope_name, CompilerScope::Annotation, line_number);
|
||||
// Evaluator takes a format parameter
|
||||
self.register_name("format", SymbolUsage::Parameter, TextRange::default())?;
|
||||
self.register_name(".format", SymbolUsage::Parameter, TextRange::default())?;
|
||||
|
||||
if in_class {
|
||||
if let Some(table) = self.tables.last_mut() {
|
||||
@@ -2358,23 +2400,19 @@ impl SymbolTableBuilder {
|
||||
|
||||
// Process bound in a separate scope
|
||||
if let Some(binding) = bound {
|
||||
let (scope_name, scope_info) = if binding.is_tuple_expr() {
|
||||
(
|
||||
format!("<TypeVar constraint of {name}>"),
|
||||
"a TypeVar constraint",
|
||||
)
|
||||
let scope_info = if binding.is_tuple_expr() {
|
||||
"a TypeVar constraint"
|
||||
} else {
|
||||
(format!("<TypeVar bound of {name}>"), "a TypeVar bound")
|
||||
"a TypeVar bound"
|
||||
};
|
||||
self.scan_type_param_bound_or_default(binding, &scope_name, scope_info)?;
|
||||
self.scan_type_param_bound_or_default(binding, name.as_str(), scope_info)?;
|
||||
}
|
||||
|
||||
// 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,
|
||||
name.as_str(),
|
||||
"a TypeVar default",
|
||||
)?;
|
||||
}
|
||||
@@ -2389,10 +2427,9 @@ impl SymbolTableBuilder {
|
||||
|
||||
// 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,
|
||||
name,
|
||||
"a ParamSpec default",
|
||||
)?;
|
||||
}
|
||||
@@ -2407,10 +2444,9 @@ impl SymbolTableBuilder {
|
||||
|
||||
// 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,
|
||||
name,
|
||||
"a TypeVarTuple default",
|
||||
)?;
|
||||
}
|
||||
|
||||
@@ -155,11 +155,14 @@ mod _symtable {
|
||||
#[pygetset(name = "type")]
|
||||
fn typ(&self) -> i32 {
|
||||
match self.symtable.typ {
|
||||
CompilerScope::Function => TYPE_FUNCTION,
|
||||
CompilerScope::Function
|
||||
| CompilerScope::AsyncFunction
|
||||
| CompilerScope::Lambda
|
||||
| CompilerScope::Comprehension => TYPE_FUNCTION,
|
||||
CompilerScope::Class => TYPE_CLASS,
|
||||
CompilerScope::Module => TYPE_MODULE,
|
||||
CompilerScope::Annotation => TYPE_ANNOTATION,
|
||||
CompilerScope::TypeParams => TYPE_TYPE_PARAMETERS,
|
||||
_ => -1, // TODO: missing types from the C implementation
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user