Remove private comprehension flags (#8537)

* Remove the private assignment-expression marker

Why:
extend_namedexpr_scope() already propagates assignment-expression
targets and records DEF_GLOBAL or DEF_NONLOCAL in comprehension scopes.
ASSIGNED_IN_COMPREHENSION remained only for conflict checks. Its private
bit overlaps CPython's packed LOCAL scope bit.

Changes:
- Register assignment-expression targets as ordinary assignments.
- Detect later target conflicts from existing declaration flags.

Assisted-by: Codex:gpt-5.6-sol

* Remove the private comprehension iterator flag

Why:
ITER duplicates the DEF_LOCAL and DEF_COMP_ITER facts already recorded
for comprehension targets. Its private bit also overlaps CPython's
packed scope field.

Changes:
- Use those flags for rebinding checks and local restoration around
  inlined comprehensions.

Assisted-by: Codex:gpt-5.6-sol

* Preserve source names in named-expression diagnostics

Why:
Assignment-expression diagnostics exposed the mangled keys used for
symbol-table lookup. CPython reports identifiers as written in source.

Changes:
- Use source names in both comprehension conflict diagnostics.
- Enable test_named_expression_invalid_mangled_class_variables.

Assisted-by: Codex:gpt-5.6-sol
This commit is contained in:
sijun-yang authored and GitHub committed 2026-08-16 20:13:54 +09:00
1 parent 7eb6d38c88
commit aa3c8b5a0d
3 files changed
+14 -70

No files matched your search

-1
View File
@@ -365,7 +365,6 @@ class NamedExpressionInvalidTest(unittest.TestCase):
with self.assertRaisesRegex(SyntaxError, msg):
exec(f"lambda: {code}", {}) # Function scope
@unittest.expectedFailure # TODO: RUSTPYTHON; wrong error message
def test_named_expression_invalid_mangled_class_variables(self):
code = """class Foo:
def bar(self):
+1 -3
View File
@@ -10919,9 +10919,7 @@ impl<'warnings> Compiler<'warnings> {
if sym.flags.contains(SymbolFlags::DEF_PARAM) {
continue; // skip .0
}
let is_local = sym
.flags
.intersects(SymbolFlags::DEF_LOCAL | SymbolFlags::ITER)
let is_local = sym.flags.contains(SymbolFlags::DEF_LOCAL)
&& !sym.flags.contains(SymbolFlags::DEF_NONLOCAL);
if is_local {
pushed_locals.push(name.clone());
+13 -66
View File
@@ -312,19 +312,8 @@ bitflags! {
Self::DEF_LOCAL.bits()
| Self::DEF_PARAM.bits()
| Self::DEF_IMPORT.bits()
| Self::ITER.bits()
| Self::DEF_TYPE_PARAM.bits()
);
// TODO: Remove these, RustPython specific
// indicates if the symbol gets a value assigned by a named expression in a comprehension
// this is required to correct the scope in the analysis.
const ASSIGNED_IN_COMPREHENSION = 2 << 11;
// indicates that the symbol is used a bound iterator variable. We distinguish this case
// from normal assignment to detect disallowed re-assignment to iterator variables.
const ITER = 2 << 12;
}
}
@@ -1030,7 +1019,6 @@ enum SymbolUsage {
AnnotationAssigned,
Parameter,
AnnotationParameter,
AssignedNamedExprInComprehension,
Iter,
TypeParam,
}
@@ -1048,8 +1036,6 @@ struct SymbolTableBuilder {
varnames_stack: Vec<Vec<String>>,
// Track if we're inside an iterable definition expression (for nested comprehensions)
in_iter_def_exp: bool,
// Track if we're scanning an inner loop iteration target (not the first generator)
in_comp_inner_loop_target: bool,
// yield/yield from inside comprehension scopes is rejected with a
// message that names the comprehension kind.
comprehension_yield_context: Option<&'static str>,
@@ -1084,7 +1070,6 @@ impl SymbolTableBuilder {
current_varnames: Vec::new(),
varnames_stack: Vec::new(),
in_iter_def_exp: false,
in_comp_inner_loop_target: false,
comprehension_yield_context: None,
in_conditional_block: false,
recursion_depth: 0,
@@ -2525,24 +2510,8 @@ impl SymbolTableBuilder {
self.scan_expression(value, ExpressionContext::Load)?;
// special handling for assigned identifier in named expressions
// that are used in comprehensions. This required to correctly
// propagate the scope of the named assigned named and not to
// propagate inner names.
if let Some((id, target_range)) = named_target {
let table = self.tables.last().unwrap();
if table.typ == CompilerScope::Comprehension {
self.register_name(
id,
SymbolUsage::AssignedNamedExprInComprehension,
target_range,
)?;
} else {
// omit one recursion. When the handling of an store changes for
// Identifiers this needs adapted - more forward safe would be
// calling scan_expression directly.
self.register_name(id, SymbolUsage::Assigned, target_range)?;
}
self.register_name(id, SymbolUsage::Assigned, target_range)?;
} else {
self.scan_expression(target, ExpressionContext::Store)?;
}
@@ -2613,9 +2582,7 @@ impl SymbolTableBuilder {
}
for generator in &generators[1..] {
self.in_comp_inner_loop_target = true;
self.scan_expression(&generator.target, ExpressionContext::Iter)?;
self.in_comp_inner_loop_target = false;
let was_in_iter_def_exp = self.in_iter_def_exp;
self.in_iter_def_exp = true;
self.scan_expression(&generator.iter, ExpressionContext::IterDefinitionExp)?;
@@ -3037,11 +3004,15 @@ impl SymbolTableBuilder {
if self.tables[table_idx]
.symbols
.get(mangled.as_str())
.is_some_and(|symbol| symbol.flags.contains(SymbolFlags::ITER))
.is_some_and(|symbol| {
symbol
.flags
.contains(SymbolFlags::DEF_LOCAL | SymbolFlags::DEF_COMP_ITER)
})
{
return Err(SymbolTableError {
error: format!(
"assignment expression cannot rebind comprehension iteration variable '{mangled}'"
"assignment expression cannot rebind comprehension iteration variable '{name}'"
),
location,
});
@@ -3151,7 +3122,6 @@ impl SymbolTableBuilder {
| SymbolUsage::AnnotationAssigned
| SymbolUsage::Parameter
| SymbolUsage::AnnotationParameter
| SymbolUsage::AssignedNamedExprInComprehension
| SymbolUsage::Iter
| SymbolUsage::TypeParam
) {
@@ -3179,16 +3149,16 @@ impl SymbolTableBuilder {
let symbol = if let Some(symbol) = table.symbols.get_mut(name.as_ref()) {
let flags = &symbol.flags;
// INNER_LOOP_CONFLICT: comprehension inner loop cannot rebind
// a variable that was used as a named expression target
// Mirrors CPython's INNER_LOOP_CONFLICT check. extend_namedexpr_scope()
// marks named-expression targets as global or nonlocal in the comprehension.
// Example: [i for i in range(5) if (j := 0) for j in range(5)]
// Here 'j' is used in named expr first, then as inner loop iter target
if self.in_comp_inner_loop_target
&& flags.contains(SymbolFlags::ASSIGNED_IN_COMPREHENSION)
if matches!(role, SymbolUsage::Iter)
&& flags.intersects(SymbolFlags::DEF_GLOBAL | SymbolFlags::DEF_NONLOCAL)
{
return Err(SymbolTableError {
error: format!(
"comprehension inner loop cannot rebind assignment expression target '{name}'"
"comprehension inner loop cannot rebind assignment expression target '{original_name}'"
),
location,
});
@@ -3345,9 +3315,6 @@ impl SymbolTableBuilder {
SymbolUsage::Assigned => {
flags.insert(SymbolFlags::DEF_LOCAL);
}
SymbolUsage::AssignedNamedExprInComprehension => {
flags.insert(SymbolFlags::DEF_LOCAL | SymbolFlags::ASSIGNED_IN_COMPREHENSION);
}
SymbolUsage::Global => {
symbol.scope = SymbolScope::GlobalExplicit;
flags.insert(SymbolFlags::DEF_GLOBAL);
@@ -3356,33 +3323,13 @@ impl SymbolTableBuilder {
flags.insert(SymbolFlags::USE);
}
SymbolUsage::Iter => {
// CPython symtable_add_def_helper() records an inlined
// comprehension target as a local definition as well as a
// comprehension iterator. Keep ITER as the internal
// re-assignment check marker; DEF_LOCAL is part of the public
// ste_symbols flags exposed by _symtable.
flags.insert(
SymbolFlags::DEF_LOCAL | SymbolFlags::ITER | SymbolFlags::DEF_COMP_ITER,
);
flags.insert(SymbolFlags::DEF_LOCAL | SymbolFlags::DEF_COMP_ITER);
}
SymbolUsage::TypeParam => {
flags.insert(SymbolFlags::DEF_LOCAL | SymbolFlags::DEF_TYPE_PARAM);
}
}
// and even more checking
// it is not allowed to assign to iterator variables (by named expressions)
if flags.contains(SymbolFlags::ITER)
&& flags.contains(SymbolFlags::ASSIGNED_IN_COMPREHENSION)
{
return Err(SymbolTableError {
error: format!(
"assignment expression cannot rebind comprehension iteration variable '{}'",
symbol.name
),
location,
});
}
Ok(())
}
}