From 9cf06c14cf394d7389e7d7a209de1e2c26305d31 Mon Sep 17 00:00:00 2001 From: sijun-yang Date: Tue, 4 Aug 2026 20:26:36 +0900 Subject: [PATCH 1/3] 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 --- crates/codegen/src/symboltable.rs | 52 +++---------------------------- 1 file changed, 5 insertions(+), 47 deletions(-) diff --git a/crates/codegen/src/symboltable.rs b/crates/codegen/src/symboltable.rs index a66410f9018..7c01d191463 100644 --- a/crates/codegen/src/symboltable.rs +++ b/crates/codegen/src/symboltable.rs @@ -319,9 +319,6 @@ bitflags! { // 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 +1027,6 @@ enum SymbolUsage { AnnotationAssigned, Parameter, AnnotationParameter, - AssignedNamedExprInComprehension, Iter, TypeParam, } @@ -1048,8 +1044,6 @@ struct SymbolTableBuilder { varnames_stack: Vec>, // 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 +1078,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 +2518,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 +2590,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)?; @@ -3151,7 +3126,6 @@ impl SymbolTableBuilder { | SymbolUsage::AnnotationAssigned | SymbolUsage::Parameter | SymbolUsage::AnnotationParameter - | SymbolUsage::AssignedNamedExprInComprehension | SymbolUsage::Iter | SymbolUsage::TypeParam ) { @@ -3179,12 +3153,12 @@ 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!( @@ -3345,9 +3319,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); @@ -3370,19 +3341,6 @@ impl SymbolTableBuilder { } } - // 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(()) } } From a7385d22d46eebb9f6fe03edc387a926a2246c23 Mon Sep 17 00:00:00 2001 From: sijun-yang Date: Tue, 4 Aug 2026 20:32:42 +0900 Subject: [PATCH 2/3] 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 --- crates/codegen/src/compile.rs | 4 +--- crates/codegen/src/symboltable.rs | 23 ++++++----------------- 2 files changed, 7 insertions(+), 20 deletions(-) diff --git a/crates/codegen/src/compile.rs b/crates/codegen/src/compile.rs index afe868dab6c..fe0a983187a 100644 --- a/crates/codegen/src/compile.rs +++ b/crates/codegen/src/compile.rs @@ -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()); diff --git a/crates/codegen/src/symboltable.rs b/crates/codegen/src/symboltable.rs index 7c01d191463..a621100d4c3 100644 --- a/crates/codegen/src/symboltable.rs +++ b/crates/codegen/src/symboltable.rs @@ -312,16 +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 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; } } @@ -3012,7 +3004,11 @@ 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!( @@ -3327,14 +3323,7 @@ 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); From b7f4952e4d25e28c10c62f10852a6fa133af3d31 Mon Sep 17 00:00:00 2001 From: sijun-yang Date: Tue, 4 Aug 2026 20:38:29 +0900 Subject: [PATCH 3/3] 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 --- Lib/test/test_named_expressions.py | 1 - crates/codegen/src/symboltable.rs | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/Lib/test/test_named_expressions.py b/Lib/test/test_named_expressions.py index 2e0643484fc..a859e051de2 100644 --- a/Lib/test/test_named_expressions.py +++ b/Lib/test/test_named_expressions.py @@ -365,7 +365,6 @@ def test_named_expression_invalid_dict_comprehension_iterable_expression(self): 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): diff --git a/crates/codegen/src/symboltable.rs b/crates/codegen/src/symboltable.rs index a621100d4c3..a771e19d36f 100644 --- a/crates/codegen/src/symboltable.rs +++ b/crates/codegen/src/symboltable.rs @@ -3012,7 +3012,7 @@ impl SymbolTableBuilder { { return Err(SymbolTableError { error: format!( - "assignment expression cannot rebind comprehension iteration variable '{mangled}'" + "assignment expression cannot rebind comprehension iteration variable '{name}'" ), location, }); @@ -3158,7 +3158,7 @@ impl SymbolTableBuilder { { 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, });