Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
Prev Previous commit
symboltable takes responsibility of __debug__
  • Loading branch information
youknowone committed Jan 17, 2026
commit 0d8a37289e4151399b2f6bbd598ed1620e763422
3 changes: 0 additions & 3 deletions Lib/test/test_future_stmt/test_future.py
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
37 changes: 1 addition & 36 deletions crates/codegen/src/compile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<ir::CodeInfo>,
Expand Down Expand Up @@ -1523,11 +1515,7 @@ impl Compiler {
self._name_inner(name, |i| &mut i.metadata.names)
}
fn varname(&mut self, name: &str) -> CompileResult<bytecode::NameIdx> {
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(
Expand Down Expand Up @@ -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 {
Expand All @@ -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__" {
Expand Down Expand Up @@ -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 });
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -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 });
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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 {
Expand Down
77 changes: 57 additions & 20 deletions crates/codegen/src/symboltable.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
)?;
Comment thread
coderabbitai[bot] marked this conversation as resolved.

let usage = if parameter.annotation.is_some() {
SymbolUsage::AnnotationParameter
} else {
Expand Down Expand Up @@ -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
Expand All @@ -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)?;
}
}
}
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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 => {
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -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,
Expand All @@ -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();
Expand Down
10 changes: 8 additions & 2 deletions extra_tests/snippets/syntax_forbidden_name.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)]")
Loading