From 1af3c027b49a27036c37497892c0e49cf0d48b26 Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" Date: Sat, 17 Jan 2026 20:48:37 +0900 Subject: [PATCH 1/5] marshal v5 --- crates/codegen/src/compile.rs | 8 +++++++- crates/compiler-core/src/marshal.rs | 10 +++++++++- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/crates/codegen/src/compile.rs b/crates/codegen/src/compile.rs index d479c0d0e69..6644d4c37ea 100644 --- a/crates/codegen/src/compile.rs +++ b/crates/codegen/src/compile.rs @@ -4186,6 +4186,10 @@ impl Compiler { firstlineno: u32, ) -> CompileResult { // 1. Enter class scope + // Reset conditional-annotation index for class scope (restore after) + let saved_conditional_index = self.next_conditional_annotation_index; + self.next_conditional_annotation_index = 0; + let key = self.symbol_table_stack.len(); self.push_symbol_table()?; self.enter_scope(name, CompilerScope::Class, key, firstlineno)?; @@ -4290,7 +4294,9 @@ impl Compiler { self.emit_return_value(); // Exit scope and return the code object - Ok(self.exit_scope()) + let code = self.exit_scope(); + self.next_conditional_annotation_index = saved_conditional_index; + Ok(code) } fn compile_class_def( diff --git a/crates/compiler-core/src/marshal.rs b/crates/compiler-core/src/marshal.rs index d9bf368ecab..6a6100ca003 100644 --- a/crates/compiler-core/src/marshal.rs +++ b/crates/compiler-core/src/marshal.rs @@ -4,7 +4,7 @@ use malachite_bigint::{BigInt, Sign}; use num_complex::Complex64; use rustpython_wtf8::Wtf8; -pub const FORMAT_VERSION: u32 = 4; +pub const FORMAT_VERSION: u32 = 5; #[derive(Debug)] pub enum MarshalError { @@ -65,6 +65,7 @@ enum Type { // Unknown = b'?', Set = b'<', FrozenSet = b'>', + Slice = b':', // Added in version 5 Ascii = b'a', // AsciiInterned = b'A', // SmallTuple = b')', @@ -101,6 +102,7 @@ impl TryFrom for Type { // b'?' => Unknown, b'<' => Set, b'>' => FrozenSet, + b':' => Slice, b'a' => Ascii, // b'A' => AsciiInterned, // b')' => SmallTuple, @@ -466,6 +468,12 @@ pub fn deserialize_value(rdr: &mut R, bag: Bag) -> Res bag.make_bytes(value) } Type::Code => bag.make_code(deserialize_code(rdr, bag.constant_bag())?), + Type::Slice => { + // Slice constants are not yet supported in RustPython + // This would require adding a Slice variant to ConstantData enum + // For now, return an error if we encounter a slice in marshal data + return Err(MarshalError::BadType); + } }; Ok(value) } From 7c2e16e7fb0a3f5a4d6cd21eb1e621ec73761119 Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" Date: Sat, 17 Jan 2026 20:53:31 +0900 Subject: [PATCH 2/5] conditional blclk --- crates/codegen/src/compile.rs | 69 ++++++++++++++++++++++++----------- crates/codegen/src/ir.rs | 10 +++++ 2 files changed, 58 insertions(+), 21 deletions(-) diff --git a/crates/codegen/src/compile.rs b/crates/codegen/src/compile.rs index 6644d4c37ea..362664e6207 100644 --- a/crates/codegen/src/compile.rs +++ b/crates/codegen/src/compile.rs @@ -130,10 +130,6 @@ struct Compiler { ctx: CompileContext, opts: CompileOpts, in_annotation: bool, - // PEP 649: Track if we're inside a conditional block (if/for/while/etc.) - in_conditional_block: bool, - // PEP 649: Next index for conditional annotation tracking - next_conditional_annotation_index: u32, } enum DoneWithFuture { @@ -424,6 +420,8 @@ impl Compiler { in_inlined_comp: false, fblock: Vec::with_capacity(MAXBLOCKS), symbol_table_index: 0, // Module is always the first symbol table + in_conditional_block: 0, + next_conditional_annotation_index: 0, }; Self { code_stack: vec![module_code], @@ -441,8 +439,6 @@ impl Compiler { }, opts, in_annotation: false, - in_conditional_block: false, - next_conditional_annotation_index: 0, } } @@ -1045,6 +1041,8 @@ impl Compiler { in_inlined_comp: false, fblock: Vec::with_capacity(MAXBLOCKS), symbol_table_index: key, + in_conditional_block: 0, + next_conditional_annotation_index: 0, }; // Push the old compiler unit on the stack (like PyCapsule) @@ -2135,6 +2133,7 @@ impl Compiler { elif_else_clauses, .. }) => { + self.enter_conditional_block(); match elif_else_clauses.as_slice() { // Only if [] => { @@ -2182,6 +2181,7 @@ impl Compiler { self.switch_to_block(after_block); } } + self.leave_conditional_block(); } Stmt::While(StmtWhile { test, body, orelse, .. @@ -2228,11 +2228,13 @@ impl Compiler { is_star, .. }) => { + self.enter_conditional_block(); if *is_star { self.compile_try_star_except(body, handlers, orelse, finalbody)? } else { self.compile_try_statement(body, handlers, orelse, finalbody)? } + self.leave_conditional_block(); } Stmt::FunctionDef(StmtFunctionDef { name, @@ -4186,10 +4188,6 @@ impl Compiler { firstlineno: u32, ) -> CompileResult { // 1. Enter class scope - // Reset conditional-annotation index for class scope (restore after) - let saved_conditional_index = self.next_conditional_annotation_index; - self.next_conditional_annotation_index = 0; - let key = self.symbol_table_stack.len(); self.push_symbol_table()?; self.enter_scope(name, CompilerScope::Class, key, firstlineno)?; @@ -4294,9 +4292,7 @@ impl Compiler { self.emit_return_value(); // Exit scope and return the code object - let code = self.exit_scope(); - self.next_conditional_annotation_index = saved_conditional_index; - Ok(code) + Ok(self.exit_scope()) } fn compile_class_def( @@ -4491,6 +4487,8 @@ impl Compiler { } fn compile_while(&mut self, test: &Expr, body: &[Stmt], orelse: &[Stmt]) -> CompileResult<()> { + self.enter_conditional_block(); + let while_block = self.new_block(); let else_block = self.new_block(); let after_block = self.new_block(); @@ -4519,6 +4517,8 @@ impl Compiler { // Note: PopBlock is no longer emitted for loops self.compile_statements(orelse)?; self.switch_to_block(after_block); + + self.leave_conditional_block(); Ok(()) } @@ -4528,6 +4528,8 @@ impl Compiler { body: &[Stmt], is_async: bool, ) -> CompileResult<()> { + self.enter_conditional_block(); + // Python 3.12+ style with statement: // // BEFORE_WITH # TOS: ctx_mgr -> [__exit__, __enter__ result] @@ -4740,6 +4742,7 @@ impl Compiler { // ===== After block ===== self.switch_to_block(after_block); + self.leave_conditional_block(); Ok(()) } @@ -4751,6 +4754,8 @@ impl Compiler { orelse: &[Stmt], is_async: bool, ) -> CompileResult<()> { + self.enter_conditional_block(); + // Start loop let for_block = self.new_block(); let else_block = self.new_block(); @@ -4818,6 +4823,7 @@ impl Compiler { self.switch_to_block(after_block); + self.leave_conditional_block(); Ok(()) } @@ -5790,8 +5796,10 @@ impl Compiler { } fn compile_match(&mut self, subject: &Expr, cases: &[MatchCase]) -> CompileResult<()> { + self.enter_conditional_block(); let mut pattern_context = PatternContext::new(); self.compile_match_inner(subject, cases, &mut pattern_context)?; + self.leave_conditional_block(); Ok(()) } @@ -5952,16 +5960,22 @@ impl Compiler { } else { // PEP 649: Handle conditional annotations if self.current_symbol_table().has_conditional_annotations { - // Determine if this annotation is conditional - let is_module = self.current_symbol_table().typ == CompilerScope::Module; - let is_conditional = is_module || self.in_conditional_block; + // Allocate an index for every annotation when has_conditional_annotations + // This keeps indices aligned with compile_module_annotate's enumeration + let code_info = self.current_code_info(); + let annotation_index = code_info.next_conditional_annotation_index; + code_info.next_conditional_annotation_index += 1; + // Determine if this annotation is conditional + // Module and Class scopes both need all annotations tracked + let scope_type = self.current_symbol_table().typ; + let in_conditional_block = self.current_code_info().in_conditional_block > 0; + let is_conditional = + matches!(scope_type, CompilerScope::Module | CompilerScope::Class) + || in_conditional_block; + + // Only add to __conditional_annotations__ set if actually conditional if is_conditional { - // Get the current annotation index and increment - let annotation_index = self.next_conditional_annotation_index; - self.next_conditional_annotation_index += 1; - - // Add index to __conditional_annotations__ set let cond_annotations_name = self.name("__conditional_annotations__"); emit!(self, Instruction::LoadName(cond_annotations_name)); self.emit_load_const(ConstantData::Integer { @@ -7517,6 +7531,19 @@ impl Compiler { self.code_stack.last_mut().expect("no code on stack") } + /// Enter a conditional block (if/for/while/match/try/with) + /// PEP 649: Track conditional annotation context + fn enter_conditional_block(&mut self) { + self.current_code_info().in_conditional_block += 1; + } + + /// Leave a conditional block + fn leave_conditional_block(&mut self) { + let code_info = self.current_code_info(); + debug_assert!(code_info.in_conditional_block > 0); + code_info.in_conditional_block -= 1; + } + /// Compile break or continue statement with proper fblock cleanup. /// compiler_break, compiler_continue /// This handles unwinding through With blocks and exception handlers. diff --git a/crates/codegen/src/ir.rs b/crates/codegen/src/ir.rs index 8935f2063c3..bb350abeebf 100644 --- a/crates/codegen/src/ir.rs +++ b/crates/codegen/src/ir.rs @@ -145,6 +145,14 @@ pub struct CodeInfo { // Reference to the symbol table for this scope pub symbol_table_index: usize, + + // PEP 649: Track nesting depth inside conditional blocks (if/for/while/etc.) + // u_in_conditional_block + pub in_conditional_block: u32, + + // PEP 649: Next index for conditional annotation tracking + // u_next_conditional_annotation_index + pub next_conditional_annotation_index: u32, } impl CodeInfo { @@ -171,6 +179,8 @@ impl CodeInfo { in_inlined_comp: _, fblock: _, symbol_table_index: _, + in_conditional_block: _, + next_conditional_annotation_index: _, } = self; let CodeUnitMetadata { From 279282c5eef26f488261b685b61c51ca4991025d Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" Date: Sat, 17 Jan 2026 20:55:26 +0900 Subject: [PATCH 3/5] fix jit jit don't use lossy string --- Cargo.lock | 1 + crates/jit/Cargo.toml | 1 + crates/jit/tests/common.rs | 61 +++++++++++++++++++++++++++++++------- 3 files changed, 52 insertions(+), 11 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1ce839a96d6..41ab3280428 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3104,6 +3104,7 @@ dependencies = [ "num-traits", "rustpython-compiler-core", "rustpython-derive", + "rustpython-wtf8", "thiserror 2.0.17", ] diff --git a/crates/jit/Cargo.toml b/crates/jit/Cargo.toml index fde10b7c006..77776e9eefe 100644 --- a/crates/jit/Cargo.toml +++ b/crates/jit/Cargo.toml @@ -23,6 +23,7 @@ cranelift-module = "0.127" [dev-dependencies] rustpython-derive = { workspace = true } +rustpython-wtf8 = { workspace = true } approx = "0.5.1" diff --git a/crates/jit/tests/common.rs b/crates/jit/tests/common.rs index da324088d32..ef5a25f0843 100644 --- a/crates/jit/tests/common.rs +++ b/crates/jit/tests/common.rs @@ -3,19 +3,20 @@ use rustpython_compiler_core::bytecode::{ CodeObject, ConstantData, Instruction, OpArg, OpArgState, }; use rustpython_jit::{CompiledCode, JitType}; +use rustpython_wtf8::{Wtf8, Wtf8Buf}; use std::collections::HashMap; #[derive(Debug, Clone)] pub struct Function { code: Box, - annotations: HashMap, + annotations: HashMap, } impl Function { pub fn compile(self) -> CompiledCode { let mut arg_types = Vec::new(); for arg in self.code.arg_names().args { - let arg_type = match self.annotations.get(arg) { + let arg_type = match self.annotations.get(AsRef::::as_ref(arg.as_str())) { Some(StackValue::String(annotation)) => match annotation.as_str() { "int" => JitType::Int, "float" => JitType::Float, @@ -27,7 +28,7 @@ impl Function { arg_types.push(arg_type); } - let ret_type = match self.annotations.get("return") { + let ret_type = match self.annotations.get(AsRef::::as_ref("return")) { Some(StackValue::String(annotation)) => match annotation.as_str() { "int" => Some(JitType::Int), "float" => Some(JitType::Float), @@ -45,7 +46,7 @@ impl Function { enum StackValue { String(String), None, - Map(HashMap), + Map(HashMap), Code(Box), Function(Function), } @@ -66,7 +67,7 @@ impl From for StackValue { /// Extract annotations from an annotate function's bytecode. /// The annotate function uses BUILD_MAP with key-value pairs loaded before it. /// Keys are parameter names (from LOAD_CONST), values are type names (from LOAD_NAME/LOAD_GLOBAL). -fn extract_annotations_from_annotate_code(code: &CodeObject) -> HashMap { +fn extract_annotations_from_annotate_code(code: &CodeObject) -> HashMap { let mut annotations = HashMap::new(); let mut stack: Vec<(bool, usize)> = Vec::new(); // (is_const, index) let mut op_arg_state = OpArgState::default(); @@ -91,17 +92,55 @@ fn extract_annotations_from_annotate_code(code: &CodeObject) -> HashMap { + Some(value.as_str().map(|s| s.to_owned()).unwrap_or_else( + |_| value.to_string_lossy().into_owned(), + )) + } + Some(other) => { + eprintln!( + "Warning: Malformed annotation for '{:?}': expected string constant at index {}, got {:?}", + param_name, val_idx, other + ); + None + } + None => { + eprintln!( + "Warning: Malformed annotation for '{:?}': constant index {} out of bounds (len={})", + param_name, + val_idx, + code.constants.len() + ); + None + } + } + } else { + match code.names.get(val_idx) { + Some(name) => Some(name.clone()), + None => { + eprintln!( + "Warning: Malformed annotation for '{}': name index {} out of bounds (len={})", + param_name, + val_idx, + code.names.len() + ); + None + } + } + }; + if let Some(type_name) = type_name { annotations - .insert(param_name, StackValue::String(type_name.clone())); + .insert(param_name.clone(), StackValue::String(type_name)); } } } @@ -183,7 +222,7 @@ impl StackMachine { for _ in 0..size.get(arg) { let value = self.stack.pop().unwrap(); let name = if let Some(StackValue::String(name)) = self.stack.pop() { - name + Wtf8Buf::from(name) } else { unimplemented!("no string keys isn't yet supported in py_function!") }; From d8acea386ed0f8e8f620e0db9ed67785998d0b97 Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" Date: Sat, 17 Jan 2026 21:00:05 +0900 Subject: [PATCH 4/5] add varname --- crates/codegen/src/symboltable.rs | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/crates/codegen/src/symboltable.rs b/crates/codegen/src/symboltable.rs index 0bc76b897dc..bffdd84e5ec 100644 --- a/crates/codegen/src/symboltable.rs +++ b/crates/codegen/src/symboltable.rs @@ -780,6 +780,8 @@ struct SymbolTableBuilder { source_file: SourceFile, // Current scope's varnames being collected (temporary storage) current_varnames: Vec, + // Stack to preserve parent varnames when entering nested scopes + varnames_stack: Vec>, // Track if we're inside an iterable definition expression (for nested comprehensions) in_iter_def_exp: bool, // Track if we're inside an annotation (yield/await/named expr not allowed) @@ -815,6 +817,7 @@ impl SymbolTableBuilder { future_annotations: false, source_file, current_varnames: Vec::new(), + varnames_stack: Vec::new(), in_iter_def_exp: false, in_annotation: false, in_type_alias: false, @@ -845,8 +848,9 @@ impl SymbolTableBuilder { .unwrap_or(false); let table = SymbolTable::new(name.to_owned(), typ, line_number, is_nested); self.tables.push(table); - // Clear current_varnames for the new scope - self.current_varnames.clear(); + // Save parent's varnames and start fresh for the new scope + self.varnames_stack + .push(core::mem::take(&mut self.current_varnames)); } fn enter_type_param_block(&mut self, name: &str, line_number: u32) -> SymbolTableResult { @@ -880,6 +884,8 @@ impl SymbolTableBuilder { // Save the collected varnames to the symbol table table.varnames = core::mem::take(&mut self.current_varnames); self.tables.last_mut().unwrap().sub_tables.push(table); + // Restore parent's varnames + self.current_varnames = self.varnames_stack.pop().unwrap_or_default(); } /// Enter annotation scope (PEP 649) @@ -907,7 +913,10 @@ impl SymbolTableBuilder { // Take the annotation block and push to stack for processing let annotation_table = current.annotation_block.take().unwrap(); self.tables.push(*annotation_table); - self.current_varnames.clear(); + // Save parent's varnames and seed with existing annotation varnames (e.g., "format") + self.varnames_stack + .push(core::mem::take(&mut self.current_varnames)); + self.current_varnames = self.tables.last().unwrap().varnames.clone(); if can_see_class_scope && !self.future_annotations { self.add_classdict_freevar(); @@ -927,6 +936,8 @@ impl SymbolTableBuilder { // Store back to parent's annotation_block (not sub_tables) let parent = self.tables.last_mut().unwrap(); parent.annotation_block = Some(Box::new(table)); + // Restore parent's varnames + self.current_varnames = self.varnames_stack.pop().unwrap_or_default(); } fn add_classdict_freevar(&mut self) { From 0d8a37289e4151399b2f6bbd598ed1620e763422 Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" Date: Sat, 17 Jan 2026 21:09:15 +0900 Subject: [PATCH 5/5] symboltable takes responsibility of __debug__ --- Lib/test/test_future_stmt/test_future.py | 3 - crates/codegen/src/compile.rs | 37 +-------- crates/codegen/src/symboltable.rs | 77 ++++++++++++++----- extra_tests/snippets/syntax_forbidden_name.py | 10 ++- 4 files changed, 66 insertions(+), 61 deletions(-) diff --git a/Lib/test/test_future_stmt/test_future.py b/Lib/test/test_future_stmt/test_future.py index febd9fb095a..e57c7227cec 100644 --- a/Lib/test/test_future_stmt/test_future.py +++ b/Lib/test/test_future_stmt/test_future.py @@ -459,9 +459,6 @@ def test_infinity_numbers(self): self.assertAnnotationEqual("('inf', 1e1000, 'infxxx', 1e1000j)", expected=f"('inf', {inf}, 'infxxx', {infj})") self.assertAnnotationEqual("(1e1000, (1e1000j,))", expected=f"({inf}, ({infj},))") - # TODO: RUSTPYTHON - # AssertionError: SyntaxError not raised - @unittest.expectedFailure def test_annotation_with_complex_target(self): with self.assertRaises(SyntaxError): exec( diff --git a/crates/codegen/src/compile.rs b/crates/codegen/src/compile.rs index 362664e6207..c531c5ff258 100644 --- a/crates/codegen/src/compile.rs +++ b/crates/codegen/src/compile.rs @@ -110,14 +110,6 @@ enum NameUsage { Store, Delete, } - -fn is_forbidden_name(name: &str) -> bool { - // See https://docs.python.org/3/library/constants.html#built-in-constants - const BUILTIN_CONSTANTS: &[&str] = &["__debug__"]; - - BUILTIN_CONSTANTS.contains(&name) -} - /// Main structure holding the state of compilation. struct Compiler { code_stack: Vec, @@ -1523,11 +1515,7 @@ impl Compiler { self._name_inner(name, |i| &mut i.metadata.names) } fn varname(&mut self, name: &str) -> CompileResult { - if Self::is_forbidden_arg_name(name) { - return Err(self.error(CodegenErrorType::SyntaxError(format!( - "cannot assign to {name}", - )))); - } + // Note: __debug__ checks are now handled in symboltable phase Ok(self._name_inner(name, |i| &mut i.metadata.varnames)) } fn _name_inner( @@ -1812,15 +1800,6 @@ impl Compiler { symboltable::mangle_name(private, name) } - fn check_forbidden_name(&mut self, name: &str, usage: NameUsage) -> CompileResult<()> { - let msg = match usage { - NameUsage::Store if is_forbidden_name(name) => "cannot assign to", - NameUsage::Delete if is_forbidden_name(name) => "cannot delete", - _ => return Ok(()), - }; - Err(self.error(CodegenErrorType::SyntaxError(format!("{msg} {name}")))) - } - // = compiler_nameop fn compile_name(&mut self, name: &str, usage: NameUsage) -> CompileResult<()> { enum NameOp { @@ -1832,7 +1811,6 @@ impl Compiler { } let name = self.mangle(name); - self.check_forbidden_name(&name, usage)?; // Special handling for __debug__ if NameUsage::Load == usage && name == "__debug__" { @@ -2434,7 +2412,6 @@ impl Compiler { match &expression { Expr::Name(ExprName { id, .. }) => self.compile_name(id.as_str(), NameUsage::Delete)?, Expr::Attribute(ExprAttribute { value, attr, .. }) => { - self.check_forbidden_name(attr.as_str(), NameUsage::Delete)?; self.compile_expression(value)?; let idx = self.name(attr.as_str()); emit!(self, Instruction::DeleteAttr { idx }); @@ -3453,10 +3430,6 @@ impl Compiler { Ok(()) } - fn is_forbidden_arg_name(name: &str) -> bool { - is_forbidden_name(name) - } - /// Compile default arguments // = compiler_default_arguments fn compile_default_arguments( @@ -6000,7 +5973,6 @@ impl Compiler { self.compile_subscript(value, slice, *ctx)?; } Expr::Attribute(ExprAttribute { value, attr, .. }) => { - self.check_forbidden_name(attr.as_str(), NameUsage::Store)?; self.compile_expression(value)?; let idx = self.name(attr.as_str()); emit!(self, Instruction::StoreAttr { idx }); @@ -6095,7 +6067,6 @@ impl Compiler { } Expr::Attribute(ExprAttribute { value, attr, .. }) => { let attr = attr.as_str(); - self.check_forbidden_name(attr, NameUsage::Store)?; self.compile_expression(value)?; emit!(self, Instruction::Copy { index: 1_u32 }); let idx = self.name(attr); @@ -6923,12 +6894,6 @@ impl Compiler { let (size, unpack) = self.gather_elements(additional_positional, &arguments.args)?; let has_double_star = arguments.keywords.iter().any(|k| k.arg.is_none()); - for keyword in &arguments.keywords { - if let Some(name) = &keyword.arg { - self.check_forbidden_name(name.as_str(), NameUsage::Store)?; - } - } - if unpack || has_double_star { // Create a tuple with positional args: if unpack { diff --git a/crates/codegen/src/symboltable.rs b/crates/codegen/src/symboltable.rs index bffdd84e5ec..63b330db92f 100644 --- a/crates/codegen/src/symboltable.rs +++ b/crates/codegen/src/symboltable.rs @@ -988,6 +988,12 @@ impl SymbolTableBuilder { } fn scan_parameter(&mut self, parameter: &Parameter) -> SymbolTableResult { + self.check_name( + parameter.name.as_str(), + ExpressionContext::Store, + parameter.name.range, + )?; + let usage = if parameter.annotation.is_some() { SymbolUsage::AnnotationParameter } else { @@ -1250,6 +1256,7 @@ impl SymbolTableBuilder { for name in names { if let Some(alias) = &name.asname { // `import my_module as my_alias` + self.check_name(alias.as_str(), ExpressionContext::Store, alias.range)?; self.register_ident(alias, SymbolUsage::Imported)?; } else if name.name.as_str() == "*" { // Star imports are only allowed at module level @@ -1264,12 +1271,10 @@ impl SymbolTableBuilder { } // Don't register star imports as symbols } else { - // `import module` - self.register_name( - name.name.split('.').next().unwrap(), - SymbolUsage::Imported, - name.name.range, - )?; + // `import module` or `from x import name` + let imported_name = name.name.split('.').next().unwrap(); + self.check_name(imported_name, ExpressionContext::Store, name.name.range)?; + self.register_name(imported_name, SymbolUsage::Imported, name.name.range)?; } } } @@ -1306,7 +1311,11 @@ impl SymbolTableBuilder { // https://github.com/python/cpython/blob/main/Python/symtable.c#L1233 match &**target { Expr::Name(ast::ExprName { id, .. }) if *simple => { - self.register_name(id.as_str(), SymbolUsage::AnnotationAssigned, *range)?; + let id_str = id.as_str(); + + self.check_name(id_str, ExpressionContext::Store, *range)?; + + self.register_name(id_str, SymbolUsage::AnnotationAssigned, *range)?; // PEP 649: Register annotate function in module/class scope let current_scope = self.tables.last().map(|t| t.typ); match current_scope { @@ -1523,8 +1532,9 @@ impl SymbolTableBuilder { self.scan_expression(slice, ExpressionContext::Load)?; } Expr::Attribute(ExprAttribute { - value, range: _, .. + value, attr, range, .. }) => { + self.check_name(attr.as_str(), context, *range)?; self.scan_expression(value, ExpressionContext::Load)?; } Expr::Dict(ExprDict { @@ -1668,11 +1678,17 @@ impl SymbolTableBuilder { self.scan_expressions(&arguments.args, ExpressionContext::Load)?; for keyword in &arguments.keywords { + if let Some(arg) = &keyword.arg { + self.check_name(arg.as_str(), ExpressionContext::Store, keyword.range)?; + } self.scan_expression(&keyword.value, ExpressionContext::Load)?; } } Expr::Name(ExprName { id, range, .. }) => { let id = id.as_str(); + + self.check_name(id, context, *range)?; + // Determine the contextual usage of this symbol: match context { ExpressionContext::Delete => { @@ -1796,6 +1812,7 @@ impl SymbolTableBuilder { // propagate inner names. if let Expr::Name(ExprName { id, .. }) = &**target { let id = id.as_str(); + self.check_name(id, ExpressionContext::Store, *range)?; let table = self.tables.last().unwrap(); if table.typ == CompilerScope::Comprehension { self.register_name( @@ -2161,6 +2178,37 @@ impl SymbolTableBuilder { self.register_name(ident.as_str(), role, ident.range) } + fn check_name( + &self, + name: &str, + context: ExpressionContext, + range: TextRange, + ) -> SymbolTableResult { + if name == "__debug__" { + let location = Some( + self.source_file + .to_source_code() + .source_location(range.start(), PositionEncoding::Utf8), + ); + match context { + ExpressionContext::Store | ExpressionContext::Iter => { + return Err(SymbolTableError { + error: "cannot assign to __debug__".to_owned(), + location, + }); + } + ExpressionContext::Delete => { + return Err(SymbolTableError { + error: "cannot delete __debug__".to_owned(), + location, + }); + } + _ => {} + } + } + Ok(()) + } + fn register_name( &mut self, name: &str, @@ -2173,18 +2221,7 @@ impl SymbolTableBuilder { .source_location(range.start(), PositionEncoding::Utf8); let location = Some(location); - // Check for forbidden names like __debug__ - if name == "__debug__" - && matches!( - role, - SymbolUsage::Parameter | SymbolUsage::AnnotationParameter | SymbolUsage::Assigned - ) - { - return Err(SymbolTableError { - error: "cannot assign to __debug__".to_owned(), - location, - }); - } + // Note: __debug__ checks are handled by check_name function, so no check needed here. let scope_depth = self.tables.len(); let table = self.tables.last_mut().unwrap(); diff --git a/extra_tests/snippets/syntax_forbidden_name.py b/extra_tests/snippets/syntax_forbidden_name.py index 2e114fe8800..3bd8148436e 100644 --- a/extra_tests/snippets/syntax_forbidden_name.py +++ b/extra_tests/snippets/syntax_forbidden_name.py @@ -21,6 +21,12 @@ def raisesSyntaxError(parse_stmt, exec_stmt=None): raisesSyntaxError("", "del __debug__") raisesSyntaxError("", "(a, __debug__, c) = (1, 2, 3)") raisesSyntaxError("", "(a, *__debug__, c) = (1, 2, 3)") +raisesSyntaxError("", "__debug__ : int") +raisesSyntaxError("", "__debug__ : int = 1") -# TODO: -# raisesSyntaxError("", "__debug__ : int") +# Import statements +raisesSyntaxError("import sys as __debug__") +raisesSyntaxError("from sys import path as __debug__") + +# Comprehension iteration targets +raisesSyntaxError("[x for __debug__ in range(5)]")